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 001/224] 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 002/224] 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 003/224] 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 004/224] 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 005/224] 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 59ba68dfd0767635290cc1ed51bd1fe52a57d471 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Thu, 29 Aug 2019 20:49:32 +0100 Subject: [PATCH 006/224] 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 007/224] 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 008/224] 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 009/224] 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 010/224] 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 011/224] 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 012/224] 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 013/224] 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 6bb8a15a878346a9188577a2cca966df1e927bdf Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Oct 2019 15:07:01 +1100 Subject: [PATCH 014/224] 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 015/224] 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 016/224] 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 017/224] 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 832803a9f64d7ab3a8f6dbb2b94f59f75f7f3318 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Oct 2019 14:35:43 +1100 Subject: [PATCH 018/224] 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 019/224] 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 020/224] 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 021/224] 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 9303a497328116a3b995eefaa6dcb7509c226799 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Oct 2019 19:08:03 +1100 Subject: [PATCH 022/224] 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 193892f0847bec49f4f7891e8dd296c10627a7a1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Oct 2019 16:48:21 +1100 Subject: [PATCH 023/224] 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 024/224] 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 025/224] 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 026/224] 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 027/224] 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 028/224] 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 029/224] 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 030/224] 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 21290fd13430e7dfbf8b75f96b8833beee805461 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 28 Oct 2019 12:53:08 +0100 Subject: [PATCH 031/224] 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 032/224] 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 033/224] 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 034/224] 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 035/224] 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 036/224] 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 037/224] 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 038/224] 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 c089ecf7b9584d11fbc093ab76174250e957c14d Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 28 Oct 2019 22:31:17 +0100 Subject: [PATCH 039/224] 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 d69356cc1293239709172b892b1541e47e1e0636 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 13:29:22 +1100 Subject: [PATCH 040/224] 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 041/224] 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 042/224] 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 043/224] 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 044/224] 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 3dd7e530ba28dd1c317fb3dcd6e0acb9142ef7fc Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 29 Oct 2019 11:38:32 +0100 Subject: [PATCH 045/224] 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 046/224] 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 4ee6fdd642628499272634e536470c569378a876 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 29 Oct 2019 20:39:38 +0100 Subject: [PATCH 047/224] 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 048/224] 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 049/224] 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 3d66c201c2100be22a606432242fdf633bfe6db7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 31 Oct 2019 14:08:38 +1100 Subject: [PATCH 050/224] 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 717c185e45636c1898beb1eef8988f93707e000f Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 1 Nov 2019 14:22:49 +1100 Subject: [PATCH 051/224] 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 052/224] 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 053/224] 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 17c7f5c05f17c6db3d84010cb12daf7f3810c29f Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 1 Nov 2019 22:56:47 +0100 Subject: [PATCH 054/224] 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 055/224] 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 056/224] 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 057/224] 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 058/224] 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 059/224] 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 6b7a48d00b83f681763b4a7bada9f897bb9bb781 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Nov 2019 12:43:10 +1100 Subject: [PATCH 060/224] 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 061/224] 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 062/224] 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 063/224] 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 064/224] 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 065/224] 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 066/224] 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 067/224] 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 068/224] 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 069/224] 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 070/224] 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 071/224] 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 eee33696e82e81a54ef35dbfea1ad2eb384380f3 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 11 Nov 2019 11:05:57 +0000 Subject: [PATCH 072/224] 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 073/224] 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 074/224] 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 075/224] 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 076/224] 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 077/224] 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 078/224] 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 079/224] 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 080/224] 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 081/224] 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 082/224] 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 083/224] 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 084/224] 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 085/224] 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 086/224] 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 087/224] 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 088/224] 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 089/224] 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 090/224] 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 091/224] 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 2420b9c25362dc1bf55314edbe6a864c34f1bb64 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 26 Nov 2019 10:28:56 +0000 Subject: [PATCH 092/224] 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 093/224] 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 094/224] 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 bd2c3bdcb3aedd474c2232f8509f9515158c3852 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Nov 2019 14:28:53 +1100 Subject: [PATCH 095/224] 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 645a30a8aa0027fd49b85698bd26f4e1ee47a5e0 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 27 Nov 2019 20:24:16 +0000 Subject: [PATCH 096/224] 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 097/224] 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 1471bffeffd1a9a9f68fc8b92b741b35bc227491 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 2 Dec 2019 15:00:56 +0000 Subject: [PATCH 098/224] 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 099/224] 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 100/224] 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 101/224] 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 19497ee7857d544552d57068d4431328f882d4f8 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 3 Dec 2019 11:52:46 +0000 Subject: [PATCH 102/224] Fix up unit test - needed to add the collection to the Compositon for all tests inheriting from UmbracoTestBase --- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index ffc49b47ac..a444e4b81d 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -217,6 +217,9 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(_ => Umbraco.Web.Composing.Current.UmbracoContextAccessor); Composition.RegisterUnique(); Composition.WithCollectionBuilder(); + + Composition.DataValueReferenceFors(); + Composition.RegisterUnique(); Composition.RegisterUnique(); Composition.RegisterUnique(); From 9e1a56eba59a2650905e955fbbb5f9d976ebacf6 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 4 Dec 2019 16:14:33 +0000 Subject: [PATCH 103/224] Rename from DataValueReferenceFor to DataValyeReferenceFactory & Factories for the plural collection --- src/Umbraco.Core/Composing/Current.cs | 4 ++-- src/Umbraco.Core/CompositionExtensions.cs | 6 +++--- .../Repositories/Implement/ContentRepositoryBase.cs | 8 ++++---- .../Implement/DocumentBlueprintRepository.cs | 4 ++-- .../Repositories/Implement/DocumentRepository.cs | 4 ++-- .../Repositories/Implement/MediaRepository.cs | 4 ++-- .../Repositories/Implement/MemberRepository.cs | 4 ++-- .../DataValueReferenceFactoryCollection.cs | 12 ++++++++++++ .../DataValueReferenceFactoryCollectionBuilder.cs | 9 +++++++++ .../DataValueReferenceForCollection.cs | 12 ------------ .../DataValueReferenceForCollectionBuilder.cs | 9 --------- ...ReferenceFor.cs => IDataValueReferenceFactory.cs} | 2 +- src/Umbraco.Core/Runtime/CoreInitialComposer.cs | 3 +-- src/Umbraco.Core/Umbraco.Core.csproj | 6 +++--- .../Repositories/ContentTypeRepositoryTest.cs | 2 +- .../Repositories/DocumentRepositoryTest.cs | 2 +- .../Persistence/Repositories/DomainRepositoryTest.cs | 2 +- .../Persistence/Repositories/MediaRepositoryTest.cs | 2 +- .../Persistence/Repositories/MemberRepositoryTest.cs | 2 +- .../Repositories/PublicAccessRepositoryTest.cs | 2 +- .../Persistence/Repositories/TagRepositoryTest.cs | 4 ++-- .../Repositories/TemplateRepositoryTest.cs | 2 +- .../Persistence/Repositories/UserRepositoryTest.cs | 4 ++-- .../Services/ContentServicePerformanceTest.cs | 2 +- src/Umbraco.Tests/Services/ContentServiceTests.cs | 2 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 2 +- src/Umbraco.Web/Composing/Current.cs | 2 +- 27 files changed, 58 insertions(+), 59 deletions(-) create mode 100644 src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs create mode 100644 src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs delete mode 100644 src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollection.cs delete mode 100644 src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollectionBuilder.cs rename src/Umbraco.Core/PropertyEditors/{IDataValueReferenceFor.cs => IDataValueReferenceFactory.cs} (92%) diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index 899c465ca7..846331a16e 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 DataValueReferenceForCollection DataValueReferenceFors - => Factory.GetInstance(); + public static DataValueReferenceFactoryCollection DataValueReferenceFactories + => Factory.GetInstance(); public static PropertyEditorCollection PropertyEditors => Factory.GetInstance(); diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs index aee4b41be8..ced9a9386a 100644 --- a/src/Umbraco.Core/CompositionExtensions.cs +++ b/src/Umbraco.Core/CompositionExtensions.cs @@ -50,11 +50,11 @@ namespace Umbraco.Core => composition.WithCollectionBuilder(); /// - /// Gets the data value reference collection builder. + /// Gets the data value reference factory collection builder. /// /// The composition. - public static DataValueReferenceForCollectionBuilder DataValueReferenceFors(this Composition composition) - => composition.WithCollectionBuilder(); + public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(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 77d36b8d43..9bd1520b51 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 DataValueReferenceForCollection _dataValueReferenceFors; + private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories; /// /// @@ -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, DataValueReferenceForCollection dataValueReferenceFors) + Lazy propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories) : base(scopeAccessor, cache, logger) { LanguageRepository = languageRepository; RelationRepository = relationRepository; RelationTypeRepository = relationTypeRepository; _propertyEditors = propertyEditors; - _dataValueReferenceFors = dataValueReferenceFors; + _dataValueReferenceFactories = dataValueReferenceFactories; } protected abstract TRepository This { get; } @@ -843,7 +843,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // 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) + foreach (var item in _dataValueReferenceFactories) { // Check if this value reference is for this datatype/editor // Then call it's GetReferences method - to see if the value stored diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs index e3fba3db01..e150b2e632 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, DataValueReferenceForCollection dataValueReferenceFors) - : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFors) + Lazy propertyEditorCollection, DataValueReferenceFactoryCollection dataValueReferenceFactories) + : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFactories) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 8b63b93f16..35e54a39e0 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, DataValueReferenceForCollection dataValueReferenceFors) - : base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFors) + Lazy propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories) + : base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories) { _contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository)); _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index da124db77f..ad47c7af6b 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, DataValueReferenceForCollection dataValueReferenceFors) - : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFors) + Lazy propertyEditorCollection, DataValueReferenceFactoryCollection dataValueReferenceFactories) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFactories) { _mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 06d93dfe50..42e7d1c32f 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, DataValueReferenceForCollection dataValueReferenceFors) - : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFors) + Lazy propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories) { _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs new file mode 100644 index 0000000000..6b6607e1f9 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.PropertyEditors +{ + public class DataValueReferenceFactoryCollection : BuilderCollectionBase + { + public DataValueReferenceFactoryCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs new file mode 100644 index 0000000000..f12071c492 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.PropertyEditors +{ + public class DataValueReferenceFactoryCollectionBuilder : OrderedCollectionBuilderBase + { + protected override DataValueReferenceFactoryCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollection.cs deleted file mode 100644 index c5b9470868..0000000000 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollection.cs +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index a7b03ea482..0000000000 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceForCollectionBuilder.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Core.PropertyEditors -{ - public class DataValueReferenceForCollectionBuilder : OrderedCollectionBuilderBase - { - protected override DataValueReferenceForCollectionBuilder This => this; - } -} diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFor.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs similarity index 92% rename from src/Umbraco.Core/PropertyEditors/IDataValueReferenceFor.cs rename to src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs index e0d5e4bad1..6587e071bf 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFor.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs @@ -1,6 +1,6 @@ namespace Umbraco.Core.PropertyEditors { - public interface IDataValueReferenceFor + public interface IDataValueReferenceFactory { /// /// Gets a value indicating whether the DataValueReference lookup supports a datatype (data editor). diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs index be8a920988..d95ada499b 100644 --- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -77,8 +77,7 @@ namespace Umbraco.Core.Runtime // 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 + composition.DataValueReferenceFactories(); // 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 98871fbf5c..306b764787 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -281,10 +281,10 @@ - - + + - + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 9b99aa9b4c..f7f3adf0a0 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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 42d2d13c6e..291525ba13 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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index 6d78601bba..56138faea9 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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); return domainRepository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 886d0c5ecf..ef436a3489 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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index d3150623c2..060478d64b 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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index 89f7c39abc..4cf440c369 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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index a8a263043d..2c15e91e61 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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } @@ -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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 7caf0ef934..200447e30a 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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); var contentRepo = new DocumentRepository(ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index b632699bfd..3ba00e54cf 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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } @@ -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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index dd561cb3a8..9f4304ebee 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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 70c51c5baa..92d2a68472 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 DataValueReferenceForCollection(Enumerable.Empty()); + var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences); return repository; } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index a444e4b81d..4f7de2d230 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -218,7 +218,7 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(); Composition.WithCollectionBuilder(); - Composition.DataValueReferenceFors(); + Composition.DataValueReferenceFactories(); Composition.RegisterUnique(); Composition.RegisterUnique(); diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index 2dd82d9a4a..ec65046e84 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 DataValueReferenceForCollection DataValueReferenceFors => CoreCurrent.DataValueReferenceFors; + public static DataValueReferenceFactoryCollection DataValueReferenceFactories => CoreCurrent.DataValueReferenceFactories; public static PropertyEditorCollection PropertyEditors => CoreCurrent.PropertyEditors; From cad384a0a6b5e1c8e0bb967d3560663ddc0f43c9 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 5 Dec 2019 11:18:18 +0000 Subject: [PATCH 104/224] Moves logic out from ContentRepositoryBase & into a method on the Collection itself --- .../Implement/ContentRepositoryBase.cs | 31 ++------------ .../DataValueReferenceFactoryCollection.cs | 40 +++++++++++++++++++ ...aValueReferenceFactoryCollectionBuilder.cs | 2 +- 3 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 9bd1520b51..13b687eb4e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -823,35 +823,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected void PersistRelations(TEntity entity) { + // Get all references from our core built in DataEditors/Property Editors + // Along with seeing if deverlopers want to collect additional references from the DataValueReferenceFactories collection var trackedRelations = new List(); - - 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); - - - // 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 _dataValueReferenceFactories) - { - // 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.GetDataValueReference().GetReferences(val)); - } - } + trackedRelations.AddRange(_dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors)); //First delete all auto-relations for this entity RelationRepository.DeleteByParent(entity.Id, Constants.Conventions.RelationTypes.AutomaticRelationTypes); diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs index 6b6607e1f9..c1dbfbaca4 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using Umbraco.Core.Composing; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; namespace Umbraco.Core.PropertyEditors { @@ -8,5 +10,43 @@ namespace Umbraco.Core.PropertyEditors public DataValueReferenceFactoryCollection(IEnumerable items) : base(items) { } + + public IEnumerable GetAllReferences(PropertyCollection properties, PropertyEditorCollection propertyEditors) + { + var trackedRelations = new List(); + + foreach (var p in properties) + { + // Injecting propertyEditorCollection is causing LightInject to throw a Recursive Dependancy error + // Hence using Current.PropertyEditors + if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) 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 valueEditor = editor.GetValueEditor(); + if (valueEditor is IDataValueReference reference) + { + var refs = reference.GetReferences(val); + trackedRelations.AddRange(refs); + } + + // Loop over collection that may be add to existing property editors + // implementation of GetReferences in IDataValueReference. + // Allows developers to add support for references by a + // package /property editor that did not implement IDataValueReference themselves + foreach (var item in this) + { + // Check if this value reference is for this datatype/editor + // Then call it's GetReferences method - to see if the value stored + // in the dataeditor/property has referecnes to media/content items + if (item.IsForEditor(editor)) + trackedRelations.AddRange(item.GetDataValueReference().GetReferences(val)); + } + } + + return trackedRelations; + } } } diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs index f12071c492..2cf76712c8 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs @@ -4,6 +4,6 @@ namespace Umbraco.Core.PropertyEditors { public class DataValueReferenceFactoryCollectionBuilder : OrderedCollectionBuilderBase { - protected override DataValueReferenceFactoryCollectionBuilder This => this; + protected override DataValueReferenceFactoryCollectionBuilder This => this; } } From 4d78a2c848d7399ddaa13257a7d13526205f5b31 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 5 Dec 2019 12:48:41 +0000 Subject: [PATCH 105/224] Remove old & unecessary comment --- .../PropertyEditors/DataValueReferenceFactoryCollection.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs index c1dbfbaca4..386ab6a8f3 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs @@ -17,8 +17,6 @@ namespace Umbraco.Core.PropertyEditors foreach (var p in properties) { - // Injecting propertyEditorCollection is causing LightInject to throw a Recursive Dependancy error - // Hence using Current.PropertyEditors if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) continue; //TODO: Support variants/segments! This is not required for this initial prototype which is why there is a check here From 1513a1254902dd755a2876321b8d50bbcf5ced48 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 10 Dec 2019 13:33:59 +0100 Subject: [PATCH 106/224] Introduce a new IMainDomLock and both default and sql implementations --- .../Migrations/Install/DatabaseDataCreator.cs | 2 + .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../Upgrade/V_8_6_0/AddMainDomLock.cs | 16 ++ ...AddPropertyTypeValidationMessageColumns.cs | 1 + .../Persistence/Constants-Locks.cs | 5 + .../SqlSyntax/SqlServerSyntaxProvider.cs | 7 +- src/Umbraco.Core/Runtime/CoreRuntime.cs | 2 +- src/Umbraco.Core/{ => Runtime}/IMainDom.cs | 1 + src/Umbraco.Core/Runtime/IMainDomLock.cs | 30 +++ src/Umbraco.Core/{ => Runtime}/MainDom.cs | 97 ++++------ .../Runtime/MainDomSemaphoreLock.cs | 92 +++++++++ src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 178 ++++++++++++++++++ src/Umbraco.Core/Scoping/IScopeProvider.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 8 +- .../XmlPublishedSnapshotService.cs | 1 + .../LegacyXmlPublishedCache/XmlStore.cs | 1 + 16 files changed, 375 insertions(+), 69 deletions(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddMainDomLock.cs rename src/Umbraco.Core/{ => Runtime}/IMainDom.cs (96%) create mode 100644 src/Umbraco.Core/Runtime/IMainDomLock.cs rename src/Umbraco.Core/{ => Runtime}/MainDom.cs (77%) create mode 100644 src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs create mode 100644 src/Umbraco.Core/Runtime/SqlMainDomLock.cs diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 94d8cfbc62..f6daf180b7 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -151,6 +151,8 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.KeyValues, Name = "KeyValues" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Languages, Name = "Languages" }); + + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MainDom, Name = "MainDom" }); } private void CreateContentTypeData() diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 223603be14..6164f828f0 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.6.0 To("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}"); + To("{2AB29964-02A1-474D-BD6B-72148D2A53A2}"); //FINAL } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddMainDomLock.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddMainDomLock.cs new file mode 100644 index 0000000000..6ca493ac7e --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddMainDomLock.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 +{ + public class AddMainDomLock : MigrationBase + { + public AddMainDomLock(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + Database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MainDom, Name = "MainDom" }); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddPropertyTypeValidationMessageColumns.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddPropertyTypeValidationMessageColumns.cs index 30eb30109e..f44695da69 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddPropertyTypeValidationMessageColumns.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddPropertyTypeValidationMessageColumns.cs @@ -3,6 +3,7 @@ using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 { + public class AddPropertyTypeValidationMessageColumns : MigrationBase { public AddPropertyTypeValidationMessageColumns(IMigrationContext context) diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs index 1dcd2408e7..e64f40ced7 100644 --- a/src/Umbraco.Core/Persistence/Constants-Locks.cs +++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs @@ -8,6 +8,11 @@ namespace Umbraco.Core /// public static class Locks { + /// + /// The lock + /// + public const int MainDom = -1000; + /// /// All servers. /// diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 3d0adf175e..6dda49cd5e 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -250,6 +250,11 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName) } public override void WriteLock(IDatabase db, params int[] lockIds) + { + WriteLock(db, 1800, lockIds); + } + + public void WriteLock(IDatabase db, int millisecondsTimeout, params int[] lockIds) { // soon as we get Database, a transaction is started @@ -260,7 +265,7 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName) // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks foreach (var lockId in lockIds) { - db.Execute(@"SET LOCK_TIMEOUT 1800;"); + db.Execute($"SET LOCK_TIMEOUT {millisecondsTimeout};"); var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId }); if (i == 0) // ensure we are actually locking! throw new ArgumentException($"LockObject with id={lockId} does not exist."); diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 50653edc7c..49d7658647 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -147,7 +147,7 @@ namespace Umbraco.Core.Runtime // TODO: remove this in netcore, this is purely backwards compat hacks with the empty ctor if (MainDom == null) { - MainDom = new MainDom(Logger); + MainDom = new MainDom(Logger, new MainDomSemaphoreLock()); } diff --git a/src/Umbraco.Core/IMainDom.cs b/src/Umbraco.Core/Runtime/IMainDom.cs similarity index 96% rename from src/Umbraco.Core/IMainDom.cs rename to src/Umbraco.Core/Runtime/IMainDom.cs index 31b2e2eee0..444fc1c7d0 100644 --- a/src/Umbraco.Core/IMainDom.cs +++ b/src/Umbraco.Core/Runtime/IMainDom.cs @@ -1,5 +1,6 @@ using System; +// TODO: Can't change namespace due to breaking changes, change in netcore namespace Umbraco.Core { /// diff --git a/src/Umbraco.Core/Runtime/IMainDomLock.cs b/src/Umbraco.Core/Runtime/IMainDomLock.cs new file mode 100644 index 0000000000..c32e990114 --- /dev/null +++ b/src/Umbraco.Core/Runtime/IMainDomLock.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading.Tasks; + +namespace Umbraco.Core.Runtime +{ + /// + /// An application-wide distributed lock + /// + /// + /// Disposing releases the lock + /// + public interface IMainDomLock : IDisposable + { + /// + /// Acquires an application-wide distributed lock + /// + /// + /// + /// A disposable object which will be disposed in order to release the lock + /// + /// Throws a if the elapsed millsecondsTimeout value is exceeded + Task AcquireLockAsync(int millisecondsTimeout); + + /// + /// Wait on a background thread to receive a signal from another AppDomain + /// + /// + Task ListenAsync(); + } +} diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs similarity index 77% rename from src/Umbraco.Core/MainDom.cs rename to src/Umbraco.Core/Runtime/MainDom.cs index e2049c0190..406fb9b731 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -4,10 +4,13 @@ using System.Linq; using System.Security.Cryptography; using System.Threading; using System.Web.Hosting; +using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; -namespace Umbraco.Core +namespace Umbraco.Core.Runtime { + /// /// Provides the full implementation of . /// @@ -20,18 +23,11 @@ namespace Umbraco.Core #region Vars private readonly ILogger _logger; + private readonly IMainDomLock _mainDomLock; // our own lock for local consistency private object _locko = new object(); - // async lock representing the main domain lock - private readonly SystemLock _systemLock; - private IDisposable _systemLocker; - - // event wait handle used to notify current main domain that it should - // release the lock because a new domain wants to be the main domain - private readonly EventWaitHandle _signal; - private bool _isInitialized; // indicates whether... private bool _isMainDom; // we are the main domain @@ -47,32 +43,12 @@ namespace Umbraco.Core #region Ctor // initializes a new instance of MainDom - public MainDom(ILogger logger) + public MainDom(ILogger logger, IMainDomLock systemLock) { HostingEnvironment.RegisterObject(this); _logger = logger; - - // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail - var appId = HostingEnvironment.ApplicationID?.ReplaceNonAlphanumericChars(string.Empty) ?? string.Empty; - - // combining with the physical path because if running on eg IIS Express, - // two sites could have the same appId even though they are different. - // - // now what could still collide is... two sites, running in two different processes - // and having the same appId, and running on the same app physical path - // - // we *cannot* use the process ID here because when an AppPool restarts it is - // a new process for the same application path - - var appPath = HostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty; - var hash = (appId + ":::" + appPath).GenerateHash(); - - var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK"; - _systemLock = new SystemLock(lockName); - - var eventName = "UMBRACO-" + hash + "-MAINDOM-EVT"; - _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); + _mainDomLock = systemLock; } #endregion @@ -130,7 +106,6 @@ namespace Umbraco.Core { _logger.Info("Stopping ({SignalSource})", source); foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) - { try { callback(); // no timeout on callbacks @@ -140,14 +115,13 @@ namespace Umbraco.Core _logger.Error(e, "Error while running callback"); continue; } - } _logger.Debug("Stopped ({SignalSource})", source); } finally { // in any case... _isMainDom = false; - _systemLocker?.Dispose(); + _mainDomLock.Dispose(); _logger.Info("Released ({SignalSource})", source); } @@ -167,35 +141,11 @@ namespace Umbraco.Core _logger.Info("Acquiring."); - // signal other instances that we want the lock, then wait one the lock, - // which may timeout, and this is accepted - see comments below + // Get the lock + _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).Wait(); - // signal, then wait for the lock, then make sure the event is - // reset (maybe there was noone listening..) - _signal.Set(); - - // if more than 1 instance reach that point, one will get the lock - // and the other one will timeout, which is accepted - - //This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset. - try - { - _systemLocker = _systemLock.Lock(LockTimeoutMilliseconds); - } - 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(); - } - - //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread - - _signal.WaitOneAsync() + // Listen for the signal from another AppDomain coming online to release the lock + _mainDomLock.ListenAsync() .ContinueWith(_ => OnSignal("signal")); _logger.Info("Acquired."); @@ -230,8 +180,7 @@ namespace Umbraco.Core { if (disposing) { - _signal?.Close(); - _signal?.Dispose(); + _mainDomLock.Dispose(); } disposedValue = true; @@ -244,5 +193,25 @@ namespace Umbraco.Core } #endregion + + public static string GetMainDomId() + { + // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail + var appId = HostingEnvironment.ApplicationID?.ReplaceNonAlphanumericChars(string.Empty) ?? string.Empty; + + // combining with the physical path because if running on eg IIS Express, + // two sites could have the same appId even though they are different. + // + // now what could still collide is... two sites, running in two different processes + // and having the same appId, and running on the same app physical path + // + // we *cannot* use the process ID here because when an AppPool restarts it is + // a new process for the same application path + + var appPath = HostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty; + var hash = (appId + ":::" + appPath).GenerateHash(); + + return hash; + } } } diff --git a/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs new file mode 100644 index 0000000000..89eef8a658 --- /dev/null +++ b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs @@ -0,0 +1,92 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Umbraco.Core.Runtime +{ + /// + /// Uses a system-wide Semaphore and EventWaitHandle to synchronize the current AppDomain + /// + internal class MainDomSemaphoreLock : IMainDomLock + { + private readonly SystemLock _systemLock; + + // event wait handle used to notify current main domain that it should + // release the lock because a new domain wants to be the main domain + private readonly EventWaitHandle _signal; + + private IDisposable _lockRelease; + + public MainDomSemaphoreLock() + { + var lockName = "UMBRACO-" + MainDom.GetMainDomId() + "-MAINDOM-LCK"; + _systemLock = new SystemLock(lockName); + + var eventName = "UMBRACO-" + MainDom.GetMainDomId() + "-MAINDOM-EVT"; + _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); + } + + //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread + public Task ListenAsync() => _signal.WaitOneAsync(); + + public Task AcquireLockAsync(int millisecondsTimeout) + { + // signal other instances that we want the lock, then wait on the lock, + // which may timeout, and this is accepted - see comments below + + // signal, then wait for the lock, then make sure the event is + // reset (maybe there was noone listening..) + _signal.Set(); + + // if more than 1 instance reach that point, one will get the lock + // and the other one will timeout, which is accepted + + //This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset. + try + { + _lockRelease = _systemLock.Lock(millisecondsTimeout); + return Task.FromResult(true); + } + catch (TimeoutException) + { + return Task.FromResult(false); + } + finally + { + // we need to reset the event, because otherwise we would end up + // signaling ourselves and committing suicide immediately. + // only 1 instance can reach that point, but other instances may + // have started and be trying to get the lock - they will timeout, + // which is accepted + + _signal.Reset(); + } + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _lockRelease?.Dispose(); + _signal.Close(); + _signal.Dispose(); + } + + disposedValue = true; + } + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + } + #endregion + } +} diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs new file mode 100644 index 0000000000..28449fe889 --- /dev/null +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -0,0 +1,178 @@ +using System; +using System.Data; +using System.Data.SqlClient; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Runtime +{ + internal class SqlMainDomLock : IMainDomLock + { + private string _appDomainId; + private const string MainDomKey = "Umbraco.Core.Runtime.SqlMainDom"; + private readonly ILogger _logger; + private IUmbracoDatabase _db; + private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private SqlServerSyntaxProvider _sqlServerSyntax = new SqlServerSyntaxProvider(); + + public SqlMainDomLock(ILogger logger) + { + _appDomainId = AppDomain.CurrentDomain.Id.ToString(); + _logger = logger; + } + + + + public Task AcquireLockAsync(int millisecondsTimeout) + { + var factory = new UmbracoDatabaseFactory( + Constants.System.UmbracoConnectionName, + _logger, + new Lazy(() => new Persistence.Mappers.MapperCollection(Enumerable.Empty()))); + + _db = factory.CreateDatabase(); + + try + { + _db.BeginTransaction(IsolationLevel.ReadCommitted); + + try + { + // wait to get a write lock + _sqlServerSyntax.WriteLock(_db, millisecondsTimeout, Constants.Locks.MainDom); + } + catch (Exception ex) + { + if (IsLockTimeoutException(ex)) + { + return Task.FromResult(false); + } + + // unexpected + throw; + } + + InsertLockRecord(); + + return Task.FromResult(true); + } + catch(Exception) + { + _db.AbortTransaction(); + + // unexpected + throw; + } + finally + { + _db.CompleteTransaction(); + } + } + + public Task ListenAsync() + { + // Create a long running task (dedicated thread) + // to poll to check if we are still the MainDom registered in the DB + return Task.Factory.StartNew(() => + { + while(true) + { + if (_cancellationTokenSource.IsCancellationRequested) + break; + + // poll every 1 second + Thread.Sleep(1000); + + try + { + _db.BeginTransaction(IsolationLevel.ReadCommitted); + + // get a read lock + _sqlServerSyntax.ReadLock(_db, Constants.Locks.MainDom); + + if (!IsStillMainDom()) + { + // we are no longer main dom, exit + return; + } + } + catch (Exception) + { + _db.AbortTransaction(); + throw; + } + finally + { + _db.CompleteTransaction(); + } + } + + + }, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + + } + + /// + /// Inserts or updates the key/value row to check if the current appdomain is registerd as the maindom + /// + private void InsertLockRecord() + { + _db.InsertOrUpdate(new KeyValueDto + { + Key = MainDomKey, + Value = _appDomainId, + Updated = DateTime.Now + }); + } + + /// + /// Checks if the DB row value is our current appdomain value + /// + /// + private bool IsStillMainDom() + { + return _db.ExecuteScalar("SELECT COUNT(*) FROM umbracoKeyValue WHERE [key] = @key AND [value] = @val", + new { key = MainDomKey, val = _appDomainId }) == 1; + } + + /// + /// Checks if the exception is an SQL timeout + /// + /// + /// + private bool IsLockTimeoutException(Exception exception) => exception is SqlException sqlException && sqlException.Number == 1222; + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _db.Dispose(); + _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); + } + + disposedValue = true; + } + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + } + #endregion + + } +} diff --git a/src/Umbraco.Core/Scoping/IScopeProvider.cs b/src/Umbraco.Core/Scoping/IScopeProvider.cs index 6c9eb63ba0..96bb939f8e 100644 --- a/src/Umbraco.Core/Scoping/IScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/IScopeProvider.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Scoping /// Provides scopes. /// public interface IScopeProvider - { + { /// /// Creates an ambient scope. /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 6f9ff1e783..8d278f5630 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -128,6 +128,10 @@ --> + + + + @@ -398,7 +402,7 @@ - + @@ -729,7 +733,7 @@ - + diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs index ec6b854a46..97fe9057bb 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Runtime; using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Web; diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs index c452c4792a..803b86aec5 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Runtime; using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; From 654511a6564af56e3d50c527cd7b35921ce120cc Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 10 Dec 2019 13:42:53 +0100 Subject: [PATCH 107/224] adds appSetting to switch maindom lock --- src/Umbraco.Core/Constants-AppSettings.cs | 2 ++ src/Umbraco.Web/UmbracoApplication.cs | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs index 509be46b61..85d6b24ae0 100644 --- a/src/Umbraco.Core/Constants-AppSettings.cs +++ b/src/Umbraco.Core/Constants-AppSettings.cs @@ -9,6 +9,8 @@ namespace Umbraco.Core /// public static class AppSettings { + public const string MainDomLock = "Umbraco.Core.MainDom.Lock"; + // TODO: Kill me - still used in Umbraco.Core.IO.SystemFiles:27 [Obsolete("We need to kill this appsetting as we do not use XML content cache umbraco.config anymore due to NuCache")] public const string ContentXML = "Umbraco.Core.ContentXML"; //umbracoContentXML diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 94403bc1be..ad8dcb7e8b 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -1,7 +1,9 @@ -using System.Threading; +using System.Configuration; +using System.Threading; using System.Web; using Umbraco.Core; using Umbraco.Core.Logging.Serilog; +using Umbraco.Core.Runtime; using Umbraco.Web.Runtime; namespace Umbraco.Web @@ -14,7 +16,17 @@ namespace Umbraco.Web protected override IRuntime GetRuntime() { var logger = SerilogLogger.CreateWithDefaultConfiguration(); - return new WebRuntime(this, logger, new MainDom(logger)); + + // Determine if we should use the sql main dom or the default + var appSettingMainDomLock = ConfigurationManager.AppSettings[Constants.AppSettings.MainDomLock]; + + var mainDomLock = appSettingMainDomLock == "SqlMainDomLock" + ? (IMainDomLock)new SqlMainDomLock(logger) + : new MainDomSemaphoreLock(); + + var runtime = new WebRuntime(this, logger, new MainDom(logger, mainDomLock)); + + return runtime; } /// From e919af14d33827de2415f332907cdfc12766ed91 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 10 Dec 2019 13:50:10 +0100 Subject: [PATCH 108/224] adds comments --- src/Umbraco.Core/Runtime/MainDom.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index 406fb9b731..c6ee819cda 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -155,6 +155,10 @@ namespace Umbraco.Core.Runtime /// /// Gets a value indicating whether the current domain is the main domain. /// + /// + /// The lazy initializer call will only call the Acquire callback when it's not been initialized, else it will just return + /// the value from _isMainDom which means when we set _isMainDom to false again after being signaled, this will return false; + /// public bool IsMainDom => LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => Acquire()); // IRegisteredObject From aae8dbdc15ec0f10efaa98c87384d2c2a069b936 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 10 Dec 2019 15:44:47 +0100 Subject: [PATCH 109/224] Updates to allow the sql main dom lock to be able to wait until the previous maindom has fully unwound before it can be taken over. --- src/Umbraco.Core/Runtime/MainDom.cs | 13 +- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 204 +++++++++++++++++++-- 2 files changed, 195 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index c6ee819cda..85cf5ad6a9 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -144,9 +144,16 @@ namespace Umbraco.Core.Runtime // Get the lock _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).Wait(); - // Listen for the signal from another AppDomain coming online to release the lock - _mainDomLock.ListenAsync() - .ContinueWith(_ => OnSignal("signal")); + try + { + // Listen for the signal from another AppDomain coming online to release the lock + _mainDomLock.ListenAsync() + .ContinueWith(_ => OnSignal("signal")); + } + catch (OperationCanceledException) + { + // the waiting task could be canceled if this appdomain is naturally shutting down, we'll just swallow this exception + } _logger.Info("Acquired."); return true; diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index 28449fe889..c077572f85 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -1,6 +1,7 @@ using System; using System.Data; using System.Data.SqlClient; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -14,22 +15,22 @@ namespace Umbraco.Core.Runtime { internal class SqlMainDomLock : IMainDomLock { - private string _appDomainId; + private string _lockId; private const string MainDomKey = "Umbraco.Core.Runtime.SqlMainDom"; private readonly ILogger _logger; private IUmbracoDatabase _db; private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private SqlServerSyntaxProvider _sqlServerSyntax = new SqlServerSyntaxProvider(); + private bool _mainDomChanging = false; public SqlMainDomLock(ILogger logger) { - _appDomainId = AppDomain.CurrentDomain.Id.ToString(); + // unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer + _lockId = Guid.NewGuid().ToString(); _logger = logger; } - - - public Task AcquireLockAsync(int millisecondsTimeout) + public async Task AcquireLockAsync(int millisecondsTimeout) { var factory = new UmbracoDatabaseFactory( Constants.System.UmbracoConnectionName, @@ -38,6 +39,8 @@ namespace Umbraco.Core.Runtime _db = factory.CreateDatabase(); + var tempId = Guid.NewGuid().ToString(); + try { _db.BeginTransaction(IsolationLevel.ReadCommitted); @@ -51,18 +54,29 @@ namespace Umbraco.Core.Runtime { if (IsLockTimeoutException(ex)) { - return Task.FromResult(false); + return false; } // unexpected throw; } - InsertLockRecord(); + var result = InsertLockRecord(tempId); //we change the row to a random Id to signal other MainDom to shutdown + if (result == RecordPersistenceType.Insert) + { + // if we've inserted, then there was no MainDom so we can instantly acquire - return Task.FromResult(true); + // TODO: see the other TODO, could we just delete the row and that would indicate that we + // are MainDom? then we don't leave any orphan rows behind. + + InsertLockRecord(_lockId); // so update with our appdomain id + return true; + } + + // if we've updated, this means there is an active MainDom, now we need to wait to + // for the current MainDom to shutdown which also requires releasing our write lock } - catch(Exception) + catch (Exception) { _db.AbortTransaction(); @@ -73,6 +87,8 @@ namespace Umbraco.Core.Runtime { _db.CompleteTransaction(); } + + return await WaitForExistingAsync(tempId, millisecondsTimeout); } public Task ListenAsync() @@ -96,9 +112,14 @@ namespace Umbraco.Core.Runtime // get a read lock _sqlServerSyntax.ReadLock(_db, Constants.Locks.MainDom); - if (!IsStillMainDom()) + // TODO: We could in theory just check if the main dom row doesn't exist, that could indicate that + // we are still the maindom. An empty value might be better because then we won't have any orphan rows + // if the app is terminated. Could that work? + + if (!IsMainDomValue(_lockId)) { - // we are no longer main dom, exit + // we are no longer main dom, another one has come online, exit + _mainDomChanging = true; return; } } @@ -119,26 +140,134 @@ namespace Umbraco.Core.Runtime } /// - /// Inserts or updates the key/value row to check if the current appdomain is registerd as the maindom + /// Wait for any existing MainDom to release so we can continue booting /// - private void InsertLockRecord() + /// + /// + /// + private Task WaitForExistingAsync(string tempId, int millisecondsTimeout) { - _db.InsertOrUpdate(new KeyValueDto + var updatedTempId = tempId + "_updated"; + + return Task.Run(() => + { + var watch = new Stopwatch(); + watch.Start(); + while(true) + { + // poll very often, we need to take over as fast as we can + Thread.Sleep(100); + + try + { + _db.BeginTransaction(IsolationLevel.ReadCommitted); + + // get a read lock + _sqlServerSyntax.ReadLock(_db, Constants.Locks.MainDom); + + var mainDomRows = _db.Fetch("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); + + if (mainDomRows.Count == 0 || mainDomRows[0].Value == updatedTempId) + { + // the other main dom has updated our record + // Or the other maindom shutdown super fast and just deleted the record + // which indicates that we + // can acquire it and it has shutdown. + + _sqlServerSyntax.WriteLock(_db, Constants.Locks.MainDom); + + // so now we update the row with our appdomain id + InsertLockRecord(_lockId); + + return true; + } + else if (mainDomRows.Count == 1 && mainDomRows[0].Value.EndsWith("_updated")) + { + // in this case, there is a suffixed _updated value but it's not for our ID which means + // another new AppDomain has come online and is wanting to take over. In that case, we will not + // acquire. + + return false; + } + } + catch (Exception ex) + { + _db.AbortTransaction(); + + if (IsLockTimeoutException(ex)) + { + return false; + } + + throw; + } + finally + { + _db.CompleteTransaction(); + } + + if (watch.ElapsedMilliseconds >= millisecondsTimeout) + { + // if the timeout has elapsed, it either means that the other main dom is taking too long to shutdown, + // or it could mean that the previous appdomain was terminated and didn't clear out the main dom SQL row + // and it's just been left as an orphan row. + // There's really know way of knowing unless we are constantly updating the row for the current maindom + // which isn't ideal. + // So... we're going to 'just' take over, if the writelock works then we'll assume we're ok + + + try + { + _db.BeginTransaction(IsolationLevel.ReadCommitted); + + _sqlServerSyntax.WriteLock(_db, Constants.Locks.MainDom); + + // so now we update the row with our appdomain id + InsertLockRecord(_lockId); + return true; + } + catch (Exception ex) + { + _db.AbortTransaction(); + + if (IsLockTimeoutException(ex)) + { + // something is wrong, we cannot acquire, not much we can do + return false; + } + + throw; + } + finally + { + _db.CompleteTransaction(); + } + } + } + }, _cancellationTokenSource.Token); + } + + /// + /// Inserts or updates the key/value row + /// + private RecordPersistenceType InsertLockRecord(string id) + { + return _db.InsertOrUpdate(new KeyValueDto { Key = MainDomKey, - Value = _appDomainId, + Value = id, Updated = DateTime.Now }); } /// - /// Checks if the DB row value is our current appdomain value + /// Checks if the DB row value is equals the value /// /// - private bool IsStillMainDom() + private bool IsMainDomValue(string val) { return _db.ExecuteScalar("SELECT COUNT(*) FROM umbracoKeyValue WHERE [key] = @key AND [value] = @val", - new { key = MainDomKey, val = _appDomainId }) == 1; + new { key = MainDomKey, val = val }) == 1; } /// @@ -157,9 +286,46 @@ namespace Umbraco.Core.Runtime { if (disposing) { - _db.Dispose(); + // capture locally just in case in some strange way a sub task somehow updates this + var mainDomChanging = _mainDomChanging; + + // immediately cancel all sub-tasks, we don't want them to keep querying _cancellationTokenSource.Cancel(); _cancellationTokenSource.Dispose(); + + try + { + _db.BeginTransaction(IsolationLevel.ReadCommitted); + + // get a write lock + _sqlServerSyntax.WriteLock(_db, Constants.Locks.MainDom); + + // When we are disposed, it means we have released the MainDom lock + // and called all MainDom release callbacks, in this case + // if another maindom is actually coming online we need + // to signal to the MainDom coming online that we have shutdown. + // To do that, we update the existing main dom DB record with a suffixed "_updated" string. + // Otherwise, if we are just shutting down, we want to just delete the row. + if (mainDomChanging) + { + _db.Execute("UPDATE umbracoKeyValue SET [value] = [value] + '_updated' WHERE [key] = @key", new { key = MainDomKey }); + } + else + { + _db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); + } + } + catch (Exception) + { + _db.AbortTransaction(); + throw; + } + finally + { + _db.CompleteTransaction(); + + _db.Dispose(); + } } disposedValue = true; From a63de7672bd891a306d5c1a8bc2cc682fd3cc784 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 10 Dec 2019 15:53:57 +0100 Subject: [PATCH 110/224] updates a bool check --- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index c077572f85..edd5af7ea8 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -181,9 +181,9 @@ namespace Umbraco.Core.Runtime return true; } - else if (mainDomRows.Count == 1 && mainDomRows[0].Value.EndsWith("_updated")) + else if (mainDomRows.Count == 1 && !mainDomRows[0].Value.StartsWith(tempId)) { - // in this case, there is a suffixed _updated value but it's not for our ID which means + // in this case, the prefixed ID is different which means // another new AppDomain has come online and is wanting to take over. In that case, we will not // acquire. From 0ef08db7658f1eae04c1d3602200b0c8420e4a88 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 11 Dec 2019 08:55:07 +0100 Subject: [PATCH 111/224] adds error logging and ensure we don't throw exceptions on a background task which would destroy the app domain --- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 138 ++++++++++++++------- 1 file changed, 91 insertions(+), 47 deletions(-) diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index edd5af7ea8..c567b76403 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -22,42 +22,45 @@ namespace Umbraco.Core.Runtime private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private SqlServerSyntaxProvider _sqlServerSyntax = new SqlServerSyntaxProvider(); private bool _mainDomChanging = false; + private readonly UmbracoDatabaseFactory _dbFactory; + private bool _hasError; public SqlMainDomLock(ILogger logger) { // unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer _lockId = Guid.NewGuid().ToString(); _logger = logger; + _dbFactory = new UmbracoDatabaseFactory( + Constants.System.UmbracoConnectionName, + _logger, + new Lazy(() => new Persistence.Mappers.MapperCollection(Enumerable.Empty()))); } public async Task AcquireLockAsync(int millisecondsTimeout) { - var factory = new UmbracoDatabaseFactory( - Constants.System.UmbracoConnectionName, - _logger, - new Lazy(() => new Persistence.Mappers.MapperCollection(Enumerable.Empty()))); - - _db = factory.CreateDatabase(); + var db = GetDatabase(); var tempId = Guid.NewGuid().ToString(); try { - _db.BeginTransaction(IsolationLevel.ReadCommitted); + db.BeginTransaction(IsolationLevel.ReadCommitted); try { // wait to get a write lock - _sqlServerSyntax.WriteLock(_db, millisecondsTimeout, Constants.Locks.MainDom); + _sqlServerSyntax.WriteLock(db, millisecondsTimeout, Constants.Locks.MainDom); } catch (Exception ex) { if (IsLockTimeoutException(ex)) { + _logger.Error(ex, "Sql timeout occurred, could not acquire MainDom."); + _hasError = true; return false; } - // unexpected + // unexpected (will be caught below) throw; } @@ -76,16 +79,17 @@ namespace Umbraco.Core.Runtime // if we've updated, this means there is an active MainDom, now we need to wait to // for the current MainDom to shutdown which also requires releasing our write lock } - catch (Exception) + catch (Exception ex) { - _db.AbortTransaction(); - + ResetDatabase(); // unexpected - throw; + _logger.Error(ex, "Unexpected error, cannot acquire MainDom"); + _hasError = true; + return false; } finally { - _db.CompleteTransaction(); + db?.CompleteTransaction(); } return await WaitForExistingAsync(tempId, millisecondsTimeout); @@ -93,6 +97,12 @@ namespace Umbraco.Core.Runtime public Task ListenAsync() { + if (_hasError) + { + _logger.Warn("Could not acquire MainDom, listening is canceled."); + return Task.CompletedTask; + } + // Create a long running task (dedicated thread) // to poll to check if we are still the MainDom registered in the DB return Task.Factory.StartNew(() => @@ -105,12 +115,14 @@ namespace Umbraco.Core.Runtime // poll every 1 second Thread.Sleep(1000); + var db = GetDatabase(); + try { - _db.BeginTransaction(IsolationLevel.ReadCommitted); + db.BeginTransaction(IsolationLevel.ReadCommitted); // get a read lock - _sqlServerSyntax.ReadLock(_db, Constants.Locks.MainDom); + _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); // TODO: We could in theory just check if the main dom row doesn't exist, that could indicate that // we are still the maindom. An empty value might be better because then we won't have any orphan rows @@ -123,14 +135,17 @@ namespace Umbraco.Core.Runtime return; } } - catch (Exception) + catch (Exception ex) { - _db.AbortTransaction(); - throw; + ResetDatabase(); + // unexpected + _logger.Error(ex, "Unexpected error, listening is canceled."); + _hasError = true; + return; } finally { - _db.CompleteTransaction(); + db?.CompleteTransaction(); } } @@ -139,6 +154,23 @@ namespace Umbraco.Core.Runtime } + private void ResetDatabase() + { + if (_db.InTransaction) + _db.AbortTransaction(); + _db.Dispose(); + _db = null; + } + + private IUmbracoDatabase GetDatabase() + { + if (_db != null) + return _db; + + _db = _dbFactory.CreateDatabase(); + return _db; + } + /// /// Wait for any existing MainDom to release so we can continue booting /// @@ -151,6 +183,7 @@ namespace Umbraco.Core.Runtime return Task.Run(() => { + var db = GetDatabase(); var watch = new Stopwatch(); watch.Start(); while(true) @@ -160,12 +193,13 @@ namespace Umbraco.Core.Runtime try { - _db.BeginTransaction(IsolationLevel.ReadCommitted); + db.BeginTransaction(IsolationLevel.ReadCommitted); // get a read lock - _sqlServerSyntax.ReadLock(_db, Constants.Locks.MainDom); + _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); - var mainDomRows = _db.Fetch("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); + // the row + var mainDomRows = db.Fetch("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); if (mainDomRows.Count == 0 || mainDomRows[0].Value == updatedTempId) { @@ -174,7 +208,7 @@ namespace Umbraco.Core.Runtime // which indicates that we // can acquire it and it has shutdown. - _sqlServerSyntax.WriteLock(_db, Constants.Locks.MainDom); + _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); // so now we update the row with our appdomain id InsertLockRecord(_lockId); @@ -192,18 +226,22 @@ namespace Umbraco.Core.Runtime } catch (Exception ex) { - _db.AbortTransaction(); + ResetDatabase(); if (IsLockTimeoutException(ex)) { + _logger.Error(ex, "Sql timeout occurred, waiting for existing MainDom is canceled."); + _hasError = true; return false; } - - throw; + // unexpected + _logger.Error(ex, "Unexpected error, waiting for existing MainDom is canceled."); + _hasError = true; + return false; } finally { - _db.CompleteTransaction(); + db?.CompleteTransaction(); } if (watch.ElapsedMilliseconds >= millisecondsTimeout) @@ -218,9 +256,9 @@ namespace Umbraco.Core.Runtime try { - _db.BeginTransaction(IsolationLevel.ReadCommitted); + db.BeginTransaction(IsolationLevel.ReadCommitted); - _sqlServerSyntax.WriteLock(_db, Constants.Locks.MainDom); + _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); // so now we update the row with our appdomain id InsertLockRecord(_lockId); @@ -228,19 +266,22 @@ namespace Umbraco.Core.Runtime } catch (Exception ex) { - _db.AbortTransaction(); + ResetDatabase(); if (IsLockTimeoutException(ex)) { - // something is wrong, we cannot acquire, not much we can do + // something is wrong, we cannot acquire, not much we can do + _logger.Error(ex, "Sql timeout occurred, could not forcibly acquire MainDom."); + _hasError = true; return false; } - - throw; + _logger.Error(ex, "Unexpected error, could not forcibly acquire MainDom."); + _hasError = true; + return false; } finally { - _db.CompleteTransaction(); + db?.CompleteTransaction(); } } } @@ -252,7 +293,8 @@ namespace Umbraco.Core.Runtime /// private RecordPersistenceType InsertLockRecord(string id) { - return _db.InsertOrUpdate(new KeyValueDto + var db = GetDatabase(); + return db.InsertOrUpdate(new KeyValueDto { Key = MainDomKey, Value = id, @@ -266,7 +308,8 @@ namespace Umbraco.Core.Runtime /// private bool IsMainDomValue(string val) { - return _db.ExecuteScalar("SELECT COUNT(*) FROM umbracoKeyValue WHERE [key] = @key AND [value] = @val", + var db = GetDatabase(); + return db.ExecuteScalar("SELECT COUNT(*) FROM umbracoKeyValue WHERE [key] = @key AND [value] = @val", new { key = MainDomKey, val = val }) == 1; } @@ -293,12 +336,13 @@ namespace Umbraco.Core.Runtime _cancellationTokenSource.Cancel(); _cancellationTokenSource.Dispose(); + var db = GetDatabase(); try { - _db.BeginTransaction(IsolationLevel.ReadCommitted); + db.BeginTransaction(IsolationLevel.ReadCommitted); // get a write lock - _sqlServerSyntax.WriteLock(_db, Constants.Locks.MainDom); + _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); // When we are disposed, it means we have released the MainDom lock // and called all MainDom release callbacks, in this case @@ -308,23 +352,23 @@ namespace Umbraco.Core.Runtime // Otherwise, if we are just shutting down, we want to just delete the row. if (mainDomChanging) { - _db.Execute("UPDATE umbracoKeyValue SET [value] = [value] + '_updated' WHERE [key] = @key", new { key = MainDomKey }); + db.Execute("UPDATE umbracoKeyValue SET [value] = [value] + '_updated' WHERE [key] = @key", new { key = MainDomKey }); } else { - _db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); + db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); } } - catch (Exception) + catch (Exception ex) { - _db.AbortTransaction(); - throw; + ResetDatabase(); + _logger.Error(ex, "Unexpected error during dipsose."); + _hasError = true; } finally { - _db.CompleteTransaction(); - - _db.Dispose(); + db?.CompleteTransaction(); + ResetDatabase(); } } From 9e9a2d8379877961e2c0563b91286a4ca4f3f3d3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 17 Dec 2019 10:38:03 +1100 Subject: [PATCH 112/224] 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 113/224] 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 114/224] 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 000a8d2c946ef64e7e9b025b4f0e1efa7f3cc6b3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Dec 2019 15:37:00 +1100 Subject: [PATCH 115/224] 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 116/224] 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 117/224] 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 118/224] 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 7501629c541633c8bb33077b473514bbe288f359 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Dec 2019 17:15:57 +1100 Subject: [PATCH 119/224] Adds logging to SqlMainDomLock --- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index c567b76403..a2560b71a3 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -38,6 +38,8 @@ namespace Umbraco.Core.Runtime public async Task AcquireLockAsync(int millisecondsTimeout) { + _logger.Info("Acquiring lock..."); + var db = GetDatabase(); var tempId = Guid.NewGuid().ToString(); @@ -73,6 +75,7 @@ namespace Umbraco.Core.Runtime // are MainDom? then we don't leave any orphan rows behind. InsertLockRecord(_lockId); // so update with our appdomain id + _logger.Info("Acquired with ID {LockId}", _lockId); return true; } @@ -212,7 +215,7 @@ namespace Umbraco.Core.Runtime // so now we update the row with our appdomain id InsertLockRecord(_lockId); - + _logger.Info("Acquired with ID {LockId}", _lockId); return true; } else if (mainDomRows.Count == 1 && !mainDomRows[0].Value.StartsWith(tempId)) @@ -221,6 +224,8 @@ namespace Umbraco.Core.Runtime // another new AppDomain has come online and is wanting to take over. In that case, we will not // acquire. + _logger.Info("Cannot acquire, another booting application detected."); + return false; } } @@ -253,6 +258,7 @@ namespace Umbraco.Core.Runtime // which isn't ideal. // So... we're going to 'just' take over, if the writelock works then we'll assume we're ok + _logger.Info("Timeout elapsed, assuming orphan row, acquiring MainDom."); try { @@ -262,6 +268,7 @@ namespace Umbraco.Core.Runtime // so now we update the row with our appdomain id InsertLockRecord(_lockId); + _logger.Info("Acquired with ID {LockId}", _lockId); return true; } catch (Exception ex) From 0d1101eaa748180d350662beee6040d203bda37f Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Dec 2019 18:14:18 +1100 Subject: [PATCH 120/224] adds notes/logging --- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index a2560b71a3..d7b800c364 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -112,6 +112,10 @@ namespace Umbraco.Core.Runtime { while(true) { + // If cancellation has been requested we will just exit. Depending on timing of the shutdown, + // we will have already flagged _mainDomChanging = true, or we're shutting down faster than + // the other MainDom is taking to startup. In this case the db row will just be deleted and the + // new MainDom will just take over. if (_cancellationTokenSource.IsCancellationRequested) break; @@ -135,6 +139,7 @@ namespace Umbraco.Core.Runtime { // we are no longer main dom, another one has come online, exit _mainDomChanging = true; + _logger.Info("Detected new booting application, releasing MainDom."); return; } } @@ -359,10 +364,12 @@ namespace Umbraco.Core.Runtime // Otherwise, if we are just shutting down, we want to just delete the row. if (mainDomChanging) { + _logger.Info("Releasing MainDom, updating row, new application is booting."); db.Execute("UPDATE umbracoKeyValue SET [value] = [value] + '_updated' WHERE [key] = @key", new { key = MainDomKey }); } else { + _logger.Info("Releasing MainDom, deleting row, application is shutting down."); db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); } } From 5476388e787c778852802042267f1f20ceb3aae6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Dec 2019 18:49:33 +1100 Subject: [PATCH 121/224] adds lock else we end up detecting when not needed --- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 143 +++++++++++---------- 1 file changed, 74 insertions(+), 69 deletions(-) diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index d7b800c364..bb44bbbfbf 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -24,6 +24,7 @@ namespace Umbraco.Core.Runtime private bool _mainDomChanging = false; private readonly UmbracoDatabaseFactory _dbFactory; private bool _hasError; + private object _locker = new object(); public SqlMainDomLock(ILogger logger) { @@ -112,49 +113,53 @@ namespace Umbraco.Core.Runtime { while(true) { - // If cancellation has been requested we will just exit. Depending on timing of the shutdown, - // we will have already flagged _mainDomChanging = true, or we're shutting down faster than - // the other MainDom is taking to startup. In this case the db row will just be deleted and the - // new MainDom will just take over. - if (_cancellationTokenSource.IsCancellationRequested) - break; - // poll every 1 second Thread.Sleep(1000); - var db = GetDatabase(); - - try + lock(_locker) { - db.BeginTransaction(IsolationLevel.ReadCommitted); + // If cancellation has been requested we will just exit. Depending on timing of the shutdown, + // we will have already flagged _mainDomChanging = true, or we're shutting down faster than + // the other MainDom is taking to startup. In this case the db row will just be deleted and the + // new MainDom will just take over. + if (_cancellationTokenSource.IsCancellationRequested) + break; - // get a read lock - _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); + var db = GetDatabase(); - // TODO: We could in theory just check if the main dom row doesn't exist, that could indicate that - // we are still the maindom. An empty value might be better because then we won't have any orphan rows - // if the app is terminated. Could that work? - - if (!IsMainDomValue(_lockId)) + try { - // we are no longer main dom, another one has come online, exit - _mainDomChanging = true; - _logger.Info("Detected new booting application, releasing MainDom."); + db.BeginTransaction(IsolationLevel.ReadCommitted); + + // get a read lock + _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); + + // TODO: We could in theory just check if the main dom row doesn't exist, that could indicate that + // we are still the maindom. An empty value might be better because then we won't have any orphan rows + // if the app is terminated. Could that work? + + if (!IsMainDomValue(_lockId)) + { + // we are no longer main dom, another one has come online, exit + _mainDomChanging = true; + _logger.Info("Detected new booting application, releasing MainDom."); + return; + } + } + catch (Exception ex) + { + ResetDatabase(); + // unexpected + _logger.Error(ex, "Unexpected error, listening is canceled."); + _hasError = true; return; } + finally + { + db?.CompleteTransaction(); + } } - catch (Exception ex) - { - ResetDatabase(); - // unexpected - _logger.Error(ex, "Unexpected error, listening is canceled."); - _hasError = true; - return; - } - finally - { - db?.CompleteTransaction(); - } + } @@ -341,48 +346,48 @@ namespace Umbraco.Core.Runtime { if (disposing) { - // capture locally just in case in some strange way a sub task somehow updates this - var mainDomChanging = _mainDomChanging; - - // immediately cancel all sub-tasks, we don't want them to keep querying - _cancellationTokenSource.Cancel(); - _cancellationTokenSource.Dispose(); - - var db = GetDatabase(); - try + lock (_locker) { - db.BeginTransaction(IsolationLevel.ReadCommitted); + // immediately cancel all sub-tasks, we don't want them to keep querying + _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); - // get a write lock - _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); - - // When we are disposed, it means we have released the MainDom lock - // and called all MainDom release callbacks, in this case - // if another maindom is actually coming online we need - // to signal to the MainDom coming online that we have shutdown. - // To do that, we update the existing main dom DB record with a suffixed "_updated" string. - // Otherwise, if we are just shutting down, we want to just delete the row. - if (mainDomChanging) + var db = GetDatabase(); + try { - _logger.Info("Releasing MainDom, updating row, new application is booting."); - db.Execute("UPDATE umbracoKeyValue SET [value] = [value] + '_updated' WHERE [key] = @key", new { key = MainDomKey }); + db.BeginTransaction(IsolationLevel.ReadCommitted); + + // get a write lock + _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); + + // When we are disposed, it means we have released the MainDom lock + // and called all MainDom release callbacks, in this case + // if another maindom is actually coming online we need + // to signal to the MainDom coming online that we have shutdown. + // To do that, we update the existing main dom DB record with a suffixed "_updated" string. + // Otherwise, if we are just shutting down, we want to just delete the row. + if (_mainDomChanging) + { + _logger.Info("Releasing MainDom, updating row, new application is booting."); + db.Execute("UPDATE umbracoKeyValue SET [value] = [value] + '_updated' WHERE [key] = @key", new { key = MainDomKey }); + } + else + { + _logger.Info("Releasing MainDom, deleting row, application is shutting down."); + db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); + } } - else + catch (Exception ex) { - _logger.Info("Releasing MainDom, deleting row, application is shutting down."); - db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); + ResetDatabase(); + _logger.Error(ex, "Unexpected error during dipsose."); + _hasError = true; + } + finally + { + db?.CompleteTransaction(); + ResetDatabase(); } - } - catch (Exception ex) - { - ResetDatabase(); - _logger.Error(ex, "Unexpected error during dipsose."); - _hasError = true; - } - finally - { - db?.CompleteTransaction(); - ResetDatabase(); } } From b24a87d9868d70bcbc164559f74caab744785b5c Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Dec 2019 18:51:38 +1100 Subject: [PATCH 122/224] Changes logging to debug --- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index bb44bbbfbf..847a0c25df 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -39,7 +39,7 @@ namespace Umbraco.Core.Runtime public async Task AcquireLockAsync(int millisecondsTimeout) { - _logger.Info("Acquiring lock..."); + _logger.Debug("Acquiring lock..."); var db = GetDatabase(); @@ -76,7 +76,7 @@ namespace Umbraco.Core.Runtime // are MainDom? then we don't leave any orphan rows behind. InsertLockRecord(_lockId); // so update with our appdomain id - _logger.Info("Acquired with ID {LockId}", _lockId); + _logger.Debug("Acquired with ID {LockId}", _lockId); return true; } @@ -142,7 +142,7 @@ namespace Umbraco.Core.Runtime { // we are no longer main dom, another one has come online, exit _mainDomChanging = true; - _logger.Info("Detected new booting application, releasing MainDom."); + _logger.Debug("Detected new booting application, releasing MainDom."); return; } } @@ -225,7 +225,7 @@ namespace Umbraco.Core.Runtime // so now we update the row with our appdomain id InsertLockRecord(_lockId); - _logger.Info("Acquired with ID {LockId}", _lockId); + _logger.Debug("Acquired with ID {LockId}", _lockId); return true; } else if (mainDomRows.Count == 1 && !mainDomRows[0].Value.StartsWith(tempId)) @@ -234,7 +234,7 @@ namespace Umbraco.Core.Runtime // another new AppDomain has come online and is wanting to take over. In that case, we will not // acquire. - _logger.Info("Cannot acquire, another booting application detected."); + _logger.Debug("Cannot acquire, another booting application detected."); return false; } @@ -268,7 +268,7 @@ namespace Umbraco.Core.Runtime // which isn't ideal. // So... we're going to 'just' take over, if the writelock works then we'll assume we're ok - _logger.Info("Timeout elapsed, assuming orphan row, acquiring MainDom."); + _logger.Debug("Timeout elapsed, assuming orphan row, acquiring MainDom."); try { @@ -278,7 +278,7 @@ namespace Umbraco.Core.Runtime // so now we update the row with our appdomain id InsertLockRecord(_lockId); - _logger.Info("Acquired with ID {LockId}", _lockId); + _logger.Debug("Acquired with ID {LockId}", _lockId); return true; } catch (Exception ex) @@ -368,12 +368,12 @@ namespace Umbraco.Core.Runtime // Otherwise, if we are just shutting down, we want to just delete the row. if (_mainDomChanging) { - _logger.Info("Releasing MainDom, updating row, new application is booting."); + _logger.Debug("Releasing MainDom, updating row, new application is booting."); db.Execute("UPDATE umbracoKeyValue SET [value] = [value] + '_updated' WHERE [key] = @key", new { key = MainDomKey }); } else { - _logger.Info("Releasing MainDom, deleting row, application is shutting down."); + _logger.Debug("Releasing MainDom, deleting row, application is shutting down."); db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); } } From 00c913d6217ecd9dfe16454d0f883cf034e328f4 Mon Sep 17 00:00:00 2001 From: abi Date: Thu, 2 Jan 2020 12:02:48 +0000 Subject: [PATCH 123/224] add InternalSearchConstants --- src/Umbraco.Web/InternalSearchConstants.cs | 32 +++++++++++++++++++ src/Umbraco.Web/Runtime/WebInitialComposer.cs | 2 +- .../Search/IInternalSearchConstants.cs | 13 ++++++++ src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 12 ++++--- src/Umbraco.Web/Umbraco.Web.csproj | 2 ++ 5 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 src/Umbraco.Web/InternalSearchConstants.cs create mode 100644 src/Umbraco.Web/Search/IInternalSearchConstants.cs diff --git a/src/Umbraco.Web/InternalSearchConstants.cs b/src/Umbraco.Web/InternalSearchConstants.cs new file mode 100644 index 0000000000..012cd98f1d --- /dev/null +++ b/src/Umbraco.Web/InternalSearchConstants.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using Umbraco.Examine; +using Umbraco.Web.Search; + +namespace Umbraco.Web +{ + public class InternalSearchConstants : IInternalSearchConstants + { + private List _backOfficeFields = new List {"id", "__NodeId", "__Key"}; + public List GetBackOfficeFields() + { + return _backOfficeFields; + } + + + private List _backOfficeMembersFields = new List {"email", "loginName"}; + public List GetBackOfficeMembersFields() + { + return _backOfficeMembersFields; + } + private List _backOfficeMediaFields = new List {UmbracoExamineIndex.UmbracoFileFieldName }; + public List GetBackOfficeMediaFields() + { + return _backOfficeMediaFields; + } + private List _backOfficeDocumentFields = new List (); + public List GetBackOfficeDocumentFields() + { + return _backOfficeDocumentFields; + } + } +} diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 87c0f46fba..b8be2319d8 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -95,7 +95,7 @@ namespace Umbraco.Web.Runtime // a way to inject the UmbracoContext - DO NOT register this as Lifetime.Request since LI will dispose the context // in it's own way but we don't want that to happen, we manage its lifetime ourselves. composition.Register(factory => factory.GetInstance().UmbracoContext); - + composition.RegisterUnique(); composition.Register(factory => { var umbCtx = factory.GetInstance(); diff --git a/src/Umbraco.Web/Search/IInternalSearchConstants.cs b/src/Umbraco.Web/Search/IInternalSearchConstants.cs new file mode 100644 index 0000000000..72a37deaa1 --- /dev/null +++ b/src/Umbraco.Web/Search/IInternalSearchConstants.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Umbraco.Web.Search +{ + public interface IInternalSearchConstants + { + List GetBackOfficeFields(); + List GetBackOfficeMembersFields(); + + List GetBackOfficeMediaFields(); + List GetBackOfficeDocumentFields(); + } +} diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 463f4b09df..1345ffd4cc 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -28,13 +28,15 @@ namespace Umbraco.Web.Search private readonly IEntityService _entityService; private readonly UmbracoMapper _mapper; private readonly ISqlContext _sqlContext; + private readonly IInternalSearchConstants _internalSearchConstants; + public UmbracoTreeSearcher(IExamineManager examineManager, UmbracoContext umbracoContext, ILocalizationService languageService, IEntityService entityService, UmbracoMapper mapper, - ISqlContext sqlContext) + ISqlContext sqlContext,IInternalSearchConstants internalSearchConstants) { _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager)); _umbracoContext = umbracoContext; @@ -42,6 +44,7 @@ namespace Umbraco.Web.Search _entityService = entityService; _mapper = mapper; _sqlContext = sqlContext; + _internalSearchConstants = internalSearchConstants; } /// @@ -67,7 +70,7 @@ namespace Umbraco.Web.Search string type; var indexName = Constants.UmbracoIndexes.InternalIndexName; - var fields = new List { "id", "__NodeId", "__Key" }; + var fields = _internalSearchConstants.GetBackOfficeFields(); // TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string // manipulation for things like start paths, member types, etc... @@ -87,7 +90,7 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Member: indexName = Constants.UmbracoIndexes.MembersIndexName; type = "member"; - fields.AddRange(new[]{ "email", "loginName"}); + fields.AddRange(_internalSearchConstants.GetBackOfficeMembersFields()); if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && searchFrom.Trim() != "-1") { sb.Append("+__NodeTypeAlias:"); @@ -97,12 +100,13 @@ namespace Umbraco.Web.Search break; case UmbracoEntityTypes.Media: type = "media"; - fields.AddRange(new[] { UmbracoExamineIndex.UmbracoFileFieldName }); + fields.AddRange(_internalSearchConstants.GetBackOfficeMediaFields()); var allMediaStartNodes = _umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService); AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; case UmbracoEntityTypes.Document: type = "content"; + fields.AddRange(_internalSearchConstants.GetBackOfficeDocumentFields()); var allContentStartNodes = _umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(_entityService); AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5d29e53d4a..159abd435b 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -155,6 +155,7 @@ + @@ -249,6 +250,7 @@ + From 9614dcf0b08bcca001e423733e2dc7f2826a2de8 Mon Sep 17 00:00:00 2001 From: abi Date: Thu, 2 Jan 2020 13:20:32 +0000 Subject: [PATCH 124/224] move InternalSearchConstants to correct place in source code --- src/Umbraco.Web/{ => Search}/InternalSearchConstants.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Umbraco.Web/{ => Search}/InternalSearchConstants.cs (100%) diff --git a/src/Umbraco.Web/InternalSearchConstants.cs b/src/Umbraco.Web/Search/InternalSearchConstants.cs similarity index 100% rename from src/Umbraco.Web/InternalSearchConstants.cs rename to src/Umbraco.Web/Search/InternalSearchConstants.cs From 575ec6fec4e5c0f2aa94a2d4efb216fddef54c18 Mon Sep 17 00:00:00 2001 From: abi Date: Thu, 2 Jan 2020 13:39:23 +0000 Subject: [PATCH 125/224] Correct project references --- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 159abd435b..0980b1b3d6 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -155,7 +155,6 @@ - @@ -251,6 +250,7 @@ + From 12f51dec4e6ee3c4b64719799066749feae04cd5 Mon Sep 17 00:00:00 2001 From: abi Date: Thu, 2 Jan 2020 23:00:53 +0000 Subject: [PATCH 126/224] Use immutable types --- .../Search/IInternalSearchConstants.cs | 10 +++++----- .../Search/InternalSearchConstants.cs | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web/Search/IInternalSearchConstants.cs b/src/Umbraco.Web/Search/IInternalSearchConstants.cs index 72a37deaa1..06222e2ec7 100644 --- a/src/Umbraco.Web/Search/IInternalSearchConstants.cs +++ b/src/Umbraco.Web/Search/IInternalSearchConstants.cs @@ -4,10 +4,10 @@ namespace Umbraco.Web.Search { public interface IInternalSearchConstants { - List GetBackOfficeFields(); - List GetBackOfficeMembersFields(); - - List GetBackOfficeMediaFields(); - List GetBackOfficeDocumentFields(); + IEnumerable GetBackOfficeFields(); + IEnumerable GetBackOfficeMembersFields(); + + IEnumerable GetBackOfficeMediaFields(); + IEnumerable GetBackOfficeDocumentFields(); } } diff --git a/src/Umbraco.Web/Search/InternalSearchConstants.cs b/src/Umbraco.Web/Search/InternalSearchConstants.cs index 012cd98f1d..51e5df4c4e 100644 --- a/src/Umbraco.Web/Search/InternalSearchConstants.cs +++ b/src/Umbraco.Web/Search/InternalSearchConstants.cs @@ -6,25 +6,25 @@ namespace Umbraco.Web { public class InternalSearchConstants : IInternalSearchConstants { - private List _backOfficeFields = new List {"id", "__NodeId", "__Key"}; - public List GetBackOfficeFields() + private IReadOnlyList _backOfficeFields = new List {"id", "__NodeId", "__Key"}; + public IEnumerable GetBackOfficeFields() { return _backOfficeFields; } - private List _backOfficeMembersFields = new List {"email", "loginName"}; - public List GetBackOfficeMembersFields() + private IReadOnlyList _backOfficeMembersFields = new List {"email", "loginName"}; + public IEnumerable GetBackOfficeMembersFields() { return _backOfficeMembersFields; } - private List _backOfficeMediaFields = new List {UmbracoExamineIndex.UmbracoFileFieldName }; - public List GetBackOfficeMediaFields() + private IReadOnlyList _backOfficeMediaFields = new List {UmbracoExamineIndex.UmbracoFileFieldName }; + public IEnumerable GetBackOfficeMediaFields() { return _backOfficeMediaFields; } - private List _backOfficeDocumentFields = new List (); - public List GetBackOfficeDocumentFields() + private IReadOnlyList _backOfficeDocumentFields = new List (); + public IEnumerable GetBackOfficeDocumentFields() { return _backOfficeDocumentFields; } From 3b8e6d3cc05621c0c402b807902cf0e549e2d6e9 Mon Sep 17 00:00:00 2001 From: abi Date: Thu, 2 Jan 2020 23:05:08 +0000 Subject: [PATCH 127/224] rename to UmbracoTreeSearcherFields --- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 6 +++--- ...hConstants.cs => IUmbracoTreeSearcherFields.cs} | 2 +- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 14 +++++++------- ...chConstants.cs => UmbracoTreeSearcherFields.cs} | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) rename src/Umbraco.Web/Search/{IInternalSearchConstants.cs => IUmbracoTreeSearcherFields.cs} (86%) rename src/Umbraco.Web/Search/{InternalSearchConstants.cs => UmbracoTreeSearcherFields.cs} (93%) diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index b8be2319d8..cc6cdc1f60 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -73,7 +73,7 @@ namespace Umbraco.Web.Runtime // register accessors for cultures composition.RegisterUnique(); composition.RegisterUnique(); - + // register the http context and umbraco context accessors // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when // we have no http context, eg when booting Umbraco or in background threads, so instead @@ -95,7 +95,7 @@ namespace Umbraco.Web.Runtime // a way to inject the UmbracoContext - DO NOT register this as Lifetime.Request since LI will dispose the context // in it's own way but we don't want that to happen, we manage its lifetime ourselves. composition.Register(factory => factory.GetInstance().UmbracoContext); - composition.RegisterUnique(); + composition.RegisterUnique(); composition.Register(factory => { var umbCtx = factory.GetInstance(); @@ -268,7 +268,7 @@ namespace Umbraco.Web.Runtime .Append() .Append() .Append(); - + // replace with web implementation composition.RegisterUnique(); diff --git a/src/Umbraco.Web/Search/IInternalSearchConstants.cs b/src/Umbraco.Web/Search/IUmbracoTreeSearcherFields.cs similarity index 86% rename from src/Umbraco.Web/Search/IInternalSearchConstants.cs rename to src/Umbraco.Web/Search/IUmbracoTreeSearcherFields.cs index 06222e2ec7..eaa5d743a1 100644 --- a/src/Umbraco.Web/Search/IInternalSearchConstants.cs +++ b/src/Umbraco.Web/Search/IUmbracoTreeSearcherFields.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; namespace Umbraco.Web.Search { - public interface IInternalSearchConstants + public interface IUmbracoTreeSearcherFields { IEnumerable GetBackOfficeFields(); IEnumerable GetBackOfficeMembersFields(); diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 1345ffd4cc..56c7c6fbf3 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.Search private readonly IEntityService _entityService; private readonly UmbracoMapper _mapper; private readonly ISqlContext _sqlContext; - private readonly IInternalSearchConstants _internalSearchConstants; + private readonly IUmbracoTreeSearcherFields _umbracoTreeSearcherFields; public UmbracoTreeSearcher(IExamineManager examineManager, @@ -36,7 +36,7 @@ namespace Umbraco.Web.Search ILocalizationService languageService, IEntityService entityService, UmbracoMapper mapper, - ISqlContext sqlContext,IInternalSearchConstants internalSearchConstants) + ISqlContext sqlContext,IUmbracoTreeSearcherFields umbracoTreeSearcherFields) { _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager)); _umbracoContext = umbracoContext; @@ -44,7 +44,7 @@ namespace Umbraco.Web.Search _entityService = entityService; _mapper = mapper; _sqlContext = sqlContext; - _internalSearchConstants = internalSearchConstants; + _umbracoTreeSearcherFields = umbracoTreeSearcherFields; } /// @@ -70,7 +70,7 @@ namespace Umbraco.Web.Search string type; var indexName = Constants.UmbracoIndexes.InternalIndexName; - var fields = _internalSearchConstants.GetBackOfficeFields(); + var fields = _umbracoTreeSearcherFields.GetBackOfficeFields(); // TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string // manipulation for things like start paths, member types, etc... @@ -90,7 +90,7 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Member: indexName = Constants.UmbracoIndexes.MembersIndexName; type = "member"; - fields.AddRange(_internalSearchConstants.GetBackOfficeMembersFields()); + fields.AddRange(_umbracoTreeSearcherFields.GetBackOfficeMembersFields()); if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && searchFrom.Trim() != "-1") { sb.Append("+__NodeTypeAlias:"); @@ -100,13 +100,13 @@ namespace Umbraco.Web.Search break; case UmbracoEntityTypes.Media: type = "media"; - fields.AddRange(_internalSearchConstants.GetBackOfficeMediaFields()); + fields.AddRange(_umbracoTreeSearcherFields.GetBackOfficeMediaFields()); var allMediaStartNodes = _umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService); AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; case UmbracoEntityTypes.Document: type = "content"; - fields.AddRange(_internalSearchConstants.GetBackOfficeDocumentFields()); + fields.AddRange(_umbracoTreeSearcherFields.GetBackOfficeDocumentFields()); var allContentStartNodes = _umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(_entityService); AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; diff --git a/src/Umbraco.Web/Search/InternalSearchConstants.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcherFields.cs similarity index 93% rename from src/Umbraco.Web/Search/InternalSearchConstants.cs rename to src/Umbraco.Web/Search/UmbracoTreeSearcherFields.cs index 51e5df4c4e..d1c663024b 100644 --- a/src/Umbraco.Web/Search/InternalSearchConstants.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcherFields.cs @@ -4,7 +4,7 @@ using Umbraco.Web.Search; namespace Umbraco.Web { - public class InternalSearchConstants : IInternalSearchConstants + public class UmbracoTreeSearcherFields : IUmbracoTreeSearcherFields { private IReadOnlyList _backOfficeFields = new List {"id", "__NodeId", "__Key"}; public IEnumerable GetBackOfficeFields() From 358b4c9133ac2f4a2cc45cff21cfbe325c3a57fd Mon Sep 17 00:00:00 2001 From: abi Date: Thu, 2 Jan 2020 23:08:53 +0000 Subject: [PATCH 128/224] Make fields list to allow to add fields --- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 56c7c6fbf3..146177f86f 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -70,7 +70,7 @@ namespace Umbraco.Web.Search string type; var indexName = Constants.UmbracoIndexes.InternalIndexName; - var fields = _umbracoTreeSearcherFields.GetBackOfficeFields(); + var fields = _umbracoTreeSearcherFields.GetBackOfficeFields().ToList(); // TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string // manipulation for things like start paths, member types, etc... From a46e9124d28799067377da5d969d2730425f57bb Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 3 Jan 2020 10:38:48 +1100 Subject: [PATCH 129/224] First commit in fixing deadlock - committing my notes, etc... --- src/Umbraco.Core/Runtime/CoreRuntime.cs | 2 +- src/Umbraco.Core/Runtime/MainDom.cs | 27 ++++- .../Runtime/MainDomSemaphoreLock.cs | 9 +- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 88 +++++++-------- .../PublishedCache/NuCache/ContentStore.cs | 101 ++++++++++++++---- .../NuCache/PublishedSnapshot.cs | 8 ++ .../NuCache/PublishedSnapshotService.cs | 25 ++++- .../NuCache/{notes.txt => readme.md} | 44 ++++---- src/Umbraco.Web/Umbraco.Web.csproj | 4 +- src/Umbraco.Web/UmbracoApplication.cs | 2 +- 10 files changed, 207 insertions(+), 103 deletions(-) rename src/Umbraco.Web/PublishedCache/NuCache/{notes.txt => readme.md} (70%) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index b46058fa82..b852aff2ff 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -147,7 +147,7 @@ namespace Umbraco.Core.Runtime // TODO: remove this in netcore, this is purely backwards compat hacks with the empty ctor if (MainDom == null) { - MainDom = new MainDom(Logger, new MainDomSemaphoreLock()); + MainDom = new MainDom(Logger, new MainDomSemaphoreLock(Logger)); } diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index 85cf5ad6a9..883b69b2a7 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core.Runtime // actions to run before releasing the main domain private readonly List> _callbacks = new List>(); - private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds + private const int LockTimeoutMilliseconds = 10000; // 10 seconds #endregion @@ -106,6 +106,7 @@ namespace Umbraco.Core.Runtime { _logger.Info("Stopping ({SignalSource})", source); foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) + { try { callback(); // no timeout on callbacks @@ -115,6 +116,8 @@ namespace Umbraco.Core.Runtime _logger.Error(e, "Error while running callback"); continue; } + } + _logger.Debug("Stopped ({SignalSource})", source); } finally @@ -142,17 +145,33 @@ namespace Umbraco.Core.Runtime _logger.Info("Acquiring."); // Get the lock - _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).Wait(); + var acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).Result; + + if (!acquired) + { + _logger.Info("Cannot acquire (timeout)."); + + // TODO: Previously we'd throw an exception and the appdomain would not start, what do we want to do? + + // return false; + + throw new TimeoutException("Cannot acquire MainDom"); + } try { // Listen for the signal from another AppDomain coming online to release the lock _mainDomLock.ListenAsync() - .ContinueWith(_ => OnSignal("signal")); + .ContinueWith(_ => + { + _logger.Debug("Signal heard from other appdomain."); + OnSignal("signal"); + }); } - catch (OperationCanceledException) + catch (OperationCanceledException ex) { // the waiting task could be canceled if this appdomain is naturally shutting down, we'll just swallow this exception + _logger.Warn(ex, ex.Message); } _logger.Info("Acquired."); diff --git a/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs index 89eef8a658..2dcc37e25f 100644 --- a/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs +++ b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Umbraco.Core.Logging; namespace Umbraco.Core.Runtime { @@ -14,16 +15,17 @@ namespace Umbraco.Core.Runtime // event wait handle used to notify current main domain that it should // release the lock because a new domain wants to be the main domain private readonly EventWaitHandle _signal; - + private readonly ILogger _logger; private IDisposable _lockRelease; - public MainDomSemaphoreLock() + public MainDomSemaphoreLock(ILogger logger) { var lockName = "UMBRACO-" + MainDom.GetMainDomId() + "-MAINDOM-LCK"; _systemLock = new SystemLock(lockName); var eventName = "UMBRACO-" + MainDom.GetMainDomId() + "-MAINDOM-EVT"; _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); + _logger = logger; } //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread @@ -47,8 +49,9 @@ namespace Umbraco.Core.Runtime _lockRelease = _systemLock.Lock(millisecondsTimeout); return Task.FromResult(true); } - catch (TimeoutException) + catch (TimeoutException ex) { + _logger.Error(ex); return Task.FromResult(false); } finally diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index 847a0c25df..31f8b36ed0 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -109,62 +109,62 @@ namespace Umbraco.Core.Runtime // Create a long running task (dedicated thread) // to poll to check if we are still the MainDom registered in the DB - return Task.Factory.StartNew(() => + return Task.Factory.StartNew(ListeningLoop, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + + } + + private void ListeningLoop() + { + while (true) { - while(true) + // poll every 1 second + Thread.Sleep(1000); + + lock (_locker) { - // poll every 1 second - Thread.Sleep(1000); + // If cancellation has been requested we will just exit. Depending on timing of the shutdown, + // we will have already flagged _mainDomChanging = true, or we're shutting down faster than + // the other MainDom is taking to startup. In this case the db row will just be deleted and the + // new MainDom will just take over. + if (_cancellationTokenSource.IsCancellationRequested) + return; - lock(_locker) + var db = GetDatabase(); + + try { - // If cancellation has been requested we will just exit. Depending on timing of the shutdown, - // we will have already flagged _mainDomChanging = true, or we're shutting down faster than - // the other MainDom is taking to startup. In this case the db row will just be deleted and the - // new MainDom will just take over. - if (_cancellationTokenSource.IsCancellationRequested) - break; + db.BeginTransaction(IsolationLevel.ReadCommitted); - var db = GetDatabase(); + // get a read lock + _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); - try + // TODO: We could in theory just check if the main dom row doesn't exist, that could indicate that + // we are still the maindom. An empty value might be better because then we won't have any orphan rows + // if the app is terminated. Could that work? + + if (!IsMainDomValue(_lockId)) { - db.BeginTransaction(IsolationLevel.ReadCommitted); - - // get a read lock - _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); - - // TODO: We could in theory just check if the main dom row doesn't exist, that could indicate that - // we are still the maindom. An empty value might be better because then we won't have any orphan rows - // if the app is terminated. Could that work? - - if (!IsMainDomValue(_lockId)) - { - // we are no longer main dom, another one has come online, exit - _mainDomChanging = true; - _logger.Debug("Detected new booting application, releasing MainDom."); - return; - } - } - catch (Exception ex) - { - ResetDatabase(); - // unexpected - _logger.Error(ex, "Unexpected error, listening is canceled."); - _hasError = true; + // we are no longer main dom, another one has come online, exit + _mainDomChanging = true; + _logger.Debug("Detected new booting application, releasing MainDom lock."); return; } - finally - { - db?.CompleteTransaction(); - } } - + catch (Exception ex) + { + ResetDatabase(); + // unexpected + _logger.Error(ex, "Unexpected error, listening is canceled."); + _hasError = true; + return; + } + finally + { + db?.CompleteTransaction(); + } } - - - }, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + } } private void ResetDatabase() diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 550fd507d5..81229af9e1 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -115,11 +115,14 @@ namespace Umbraco.Web.PublishedCache.NuCache // TODO: GetScopedWriter? should the dict have a ref onto the scope provider? public IDisposable GetScopedWriteLock(IScopeProvider scopeProvider) { + _logger.Debug("GetScopedWriteLock"); return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ScopedWriteLock(this, scoped)); } private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { + // TODO: We are in a deadlock here somehow?? + Monitor.Enter(_wlocko, ref lockInfo.Taken); var rtaken = false; @@ -193,8 +196,15 @@ namespace Umbraco.Web.PublishedCache.NuCache _localDb.Commit(); } - if (lockInfo.Count) _wlocked--; - if (lockInfo.Taken) Monitor.Exit(_wlocko); + // TODO: Shouldn't this be in a finally block? + // TODO: Shouldn't this be decremented after we exit?? + // TODO: Shouldn't the locked flag never exceed 1? + if (lockInfo.Count) + _wlocked--; + + // TODO: Shouldn't this be in a finally block? + if (lockInfo.Taken) + Monitor.Exit(_wlocko); } private void Release(ReadLockInfo lockInfo) @@ -232,21 +242,29 @@ namespace Umbraco.Web.PublishedCache.NuCache public void ReleaseLocalDb() { + _logger.Info("Releasing DB..."); var lockInfo = new WriteLockInfo(); try { try { // Trying to lock could throw exceptions so always make sure to clean up. + _logger.Info("Trying to lock before releasing DB (lock count = {LockCount}) ...", _wlocked); + Lock(lockInfo); } finally { try { + _logger.Info("Disposing local DB..."); _localDb?.Dispose(); } - catch { /* TBD: May already be throwing so don't throw again */} + catch (Exception ex) + { + /* TBD: May already be throwing so don't throw again */ + _logger.Error(ex, "Error trying to release DB"); + } finally { _localDb = null; @@ -254,8 +272,17 @@ namespace Umbraco.Web.PublishedCache.NuCache } } + catch (Exception ex) + { + _logger.Error(ex, "Error trying to lock"); + throw; + } finally { + _logger.Info("Releasing ContentStore..."); + + // TODO: I don't understand this, we would have already closed the BPlusTree store above?? + // I guess it's 'safe' and will just decrement the write lock counter? Release(lockInfo); } } @@ -275,6 +302,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var lockInfo = new WriteLockInfo(); try { + _logger.Debug("NewContentTypes"); Lock(lockInfo); foreach (var type in types) @@ -297,6 +325,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var lockInfo = new WriteLockInfo(); try { + _logger.Debug("UpdateContentTypes"); Lock(lockInfo); var index = types.ToDictionary(x => x.Id, x => x); @@ -324,10 +353,13 @@ namespace Umbraco.Web.PublishedCache.NuCache public void SetAllContentTypes(IEnumerable types) { - var lockInfo = new WriteLockInfo(); + // TODO: There should be NO lock here! All calls made to this are wrapped in GetScopedWriteLock + + //var lockInfo = new WriteLockInfo(); try { - Lock(lockInfo); + _logger.Debug("SetAllContentTypes"); + //Lock(lockInfo); // clear all existing content types ClearLocked(_contentTypesById); @@ -345,7 +377,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } finally { - Release(lockInfo); + //Release(lockInfo); } } @@ -362,6 +394,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var lockInfo = new WriteLockInfo(); try { + _logger.Debug("UpdateContentTypes"); Lock(lockInfo); var removedContentTypeNodes = new List(); @@ -435,6 +468,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var lockInfo = new WriteLockInfo(); try { + _logger.Debug("UpdateDataTypes"); Lock(lockInfo); var contentTypes = _contentTypesById @@ -545,10 +579,11 @@ namespace Umbraco.Web.PublishedCache.NuCache _logger.Debug("Set content ID: {KitNodeId}", kit.Node.Id); - var lockInfo = new WriteLockInfo(); + // TODO: There should be NO locks here, all calls made to this are done within GetScopedWriteLock + //var lockInfo = new WriteLockInfo(); try { - Lock(lockInfo); + //Lock(lockInfo); // get existing _contentNodes.TryGetValue(kit.Node.Id, out var link); @@ -594,7 +629,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } finally { - Release(lockInfo); + //Release(lockInfo); } return true; @@ -623,11 +658,16 @@ namespace Umbraco.Web.PublishedCache.NuCache /// internal bool SetAllFastSorted(IEnumerable kits, bool fromDb) { - var lockInfo = new WriteLockInfo(); + + //TODO: There should be NO locks here all calls made to this are done within GetScopedWriteLock + + //var lockInfo = new WriteLockInfo(); var ok = true; try { - Lock(lockInfo); + _logger.Debug("SetAllFastSorted"); + + //Lock(lockInfo); ClearLocked(_contentNodes); ClearRootLocked(); @@ -665,7 +705,7 @@ namespace Umbraco.Web.PublishedCache.NuCache previousNode = null; // there is no previous sibling } - _logger.Debug($"Set {thisNode.Id} with parent {thisNode.ParentContentId}"); + _logger.Verbose($"Set {thisNode.Id} with parent {thisNode.ParentContentId}"); SetValueLocked(_contentNodes, thisNode.Id, thisNode); // if we are initializing from the database source ensure the local db is updated @@ -689,7 +729,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } finally { - Release(lockInfo); + //Release(lockInfo); } return ok; @@ -697,11 +737,15 @@ namespace Umbraco.Web.PublishedCache.NuCache public bool SetAll(IEnumerable kits) { - var lockInfo = new WriteLockInfo(); + //TODO: There should be NO locks here all calls made to this are done within GetScopedWriteLock + + //var lockInfo = new WriteLockInfo(); var ok = true; try { - Lock(lockInfo); + _logger.Debug("SetAll"); + + //Lock(lockInfo); ClearLocked(_contentNodes); ClearRootLocked(); @@ -717,7 +761,7 @@ namespace Umbraco.Web.PublishedCache.NuCache ok = false; continue; // skip that one } - _logger.Debug($"Set {kit.Node.Id} with parent {kit.Node.ParentContentId}"); + _logger.Verbose($"Set {kit.Node.Id} with parent {kit.Node.ParentContentId}"); SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); if (_localDb != null) RegisterChange(kit.Node.Id, kit); @@ -728,7 +772,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } finally { - Release(lockInfo); + //Release(lockInfo); } return ok; @@ -737,11 +781,15 @@ namespace Umbraco.Web.PublishedCache.NuCache // IMPORTANT kits must be sorted out by LEVEL and by SORT ORDER public bool SetBranch(int rootContentId, IEnumerable kits) { - var lockInfo = new WriteLockInfo(); + //TODO: There should be NO locks here all calls made to this are done within GetScopedWriteLock + + //var lockInfo = new WriteLockInfo(); var ok = true; try { - Lock(lockInfo); + _logger.Debug("SetBranch"); + + //Lock(lockInfo); // get existing _contentNodes.TryGetValue(rootContentId, out var link); @@ -773,7 +821,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } finally { - Release(lockInfo); + //Release(lockInfo); } return ok; @@ -781,10 +829,13 @@ namespace Umbraco.Web.PublishedCache.NuCache public bool Clear(int id) { - var lockInfo = new WriteLockInfo(); + // TODO: There should be NO locks here! All calls to this are made within GetScopedWriteLock + //var lockInfo = new WriteLockInfo(); try { - Lock(lockInfo); + _logger.Debug("Clear"); + + //Lock(lockInfo); // try to find the content // if it is not there, nothing to do @@ -804,7 +855,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } finally { - Release(lockInfo); + //Release(lockInfo); } } @@ -1200,6 +1251,8 @@ namespace Umbraco.Web.PublishedCache.NuCache var lockInfo = new ReadLockInfo(); try { + // TODO: This would be much simpler with just a lock(_rlocko) { } + // in this case I see no reason why we are using this syntax?! Lock(lockInfo); // if no next generation is required, and we already have one, @@ -1374,6 +1427,8 @@ namespace Umbraco.Web.PublishedCache.NuCache } } + // TODO: This is never used? Should it be? + public async Task WaitForPendingCollect() { Task task; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs index 3f5c1aa4d5..bbb84fc650 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs @@ -39,7 +39,15 @@ namespace Umbraco.Web.PublishedCache.NuCache public void Resync() { + // This is annoying since this can cause deadlocks. Since this will be cleared if something happens in the same + // thread after that calls Elements, it will re-lock the _storesLock but that might already be locked again + // and since we're most likely in a ContentStore write lock, the other thread is probably wanting that one too. + // no lock - published snapshots are single-thread + + // TODO: Instead of clearing, we could hold this value in another var in case the above call to GetElements() Or potentially + // a new TryGetElements() fails (since we might be shutting down). In that case we can use the old value. + _elements?.Dispose(); _elements = null; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index a866297d72..2439633005 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -190,10 +190,15 @@ namespace Umbraco.Web.PublishedCache.NuCache /// private void MainDomRelease() { + _logger.Debug("Releasing from MainDom..."); + lock (_storesLock) { + _logger.Debug("Releasing content store..."); _contentStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned _localContentDb = null; + + _logger.Debug("Releasing media store..."); _mediaStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned _localMediaDb = null; @@ -663,10 +668,20 @@ namespace Umbraco.Web.PublishedCache.NuCache publishedChanged = publishedChanged2; } + // TODO: These resync's are a problem, they cause deadlocks because when this is called, it's generally called within a writelock + // and then we clear out the snapshot and then if there's some event handler that needs the content cache, it tries to re-get it + // which first tries locking on the _storesLock which may have already been acquired and in this case we deadlock because + // we're still holding the write lock. + // We resync at the end of a ScopedWriteLock + // BUT if we don't resync here then the current snapshot is out of date for any event handlers that wish to use the most up to date + // data... + // so we need to figure out how to deal with the _storesLock + if (draftChanged || publishedChanged) ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); } + // Calling this method means we have a lock on the contentStore (i.e. GetScopedWriteLock) private void NotifyLocked(IEnumerable payloads, out bool draftChanged, out bool publishedChanged) { publishedChanged = false; @@ -676,7 +691,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // content (and content types) are read-locked while reading content // contentStore is wlocked (so readable, only no new views) // and it can be wlocked by 1 thread only at a time - // contentStore is write-locked during changes + // contentStore is write-locked during changes - see note above, calls to this method are wrapped in contentStore.GetScopedWriteLock foreach (var payload in payloads) { @@ -1131,6 +1146,12 @@ namespace Umbraco.Web.PublishedCache.NuCache ContentStore.Snapshot contentSnap, mediaSnap; SnapDictionary.Snapshot domainSnap; IAppCache elementsCache; + + // TODO: Idea... TryGetElements? Might check if we are shutting down and return false and callers to this could handle it? + // Else does a readerwriterlockslim work here? (i don't think so) + // Else we have 2x locks, one for startup/shutdown, the other for getting elements and then we can maybe do a Monitor.Try? + // That is sort of the same as a TryGetElements + lock (_storesLock) { var scopeContext = _scopeProvider.Context; @@ -1156,6 +1177,8 @@ namespace Umbraco.Web.PublishedCache.NuCache // elements // just need to make sure nothing gets elements in another enlisted action... so using // a MaxValue to make sure this one runs last, and it should be ok + // ... else there is potential to deadlock since this would recursively go back into trying + // lock on _storesLock but another thread may have already tried that scopeContext.Enlist("Umbraco.Web.PublishedCache.NuCache.PublishedSnapshotService.Resync", () => this, (completed, svc) => { ((PublishedSnapshot)svc.CurrentPublishedSnapshot)?.Resync(); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/notes.txt b/src/Umbraco.Web/PublishedCache/NuCache/readme.md similarity index 70% rename from src/Umbraco.Web/PublishedCache/NuCache/notes.txt rename to src/Umbraco.Web/PublishedCache/NuCache/readme.md index ff2d8dd48b..c8e8a363e9 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/notes.txt +++ b/src/Umbraco.Web/PublishedCache/NuCache/readme.md @@ -1,10 +1,6 @@ NuCache Documentation ====================== -NOTE - RENAMING -Facade = PublishedSnapshot -and everything needs to be renamed accordingly - HOW IT WORKS ------------- @@ -22,12 +18,12 @@ When reading the cache, we read views up the chain until we find a value (which null) for the given id, and finally we read the store itself. -The FacadeService manages a ContentStore for content, and another for media. -When a Facade is created, the FacadeService gets ContentView objects from the stores. +The PublishedSnapshotService manages a ContentStore for content, and another for media. +When a PublishedSnapshot is created, the PublishedSnapshotService gets ContentView objects from the stores. Views provide an immutable snapshot of the content and media. -When the FacadeService is notified of changes, it notifies the stores. -Then it resync the current Facade, so that it requires new views, etc. +When the PublishedSnapshotService is notified of changes, it notifies the stores. +Then it resync the current PublishedSnapshot, so that it requires new views, etc. Whenever a content, media or member is modified or removed, the cmsContentNu table is updated with a json dictionary of alias => property value, so that a content, @@ -50,32 +46,32 @@ Each ContentStore has a _freezeLock object used to protect the 'Frozen' state of the store. It's a disposable object that releases the lock when disposed, so usage would be: using (store.Frozen) { ... }. -The FacadeService has a _storesLock object used to guarantee atomic access to the +The PublishedSnapshotService has a _storesLock object used to guarantee atomic access to the set of content, media stores. CACHE ------ -For each set of views, the FacadeService creates a SnapshotCache. So a SnapshotCache +For each set of views, the PublishedSnapshotService creates a SnapshotCache. So a SnapshotCache is valid until anything changes in the content or media trees. In other words, things that go in the SnapshotCache stay until a content or media is modified. -For each Facade, the FacadeService creates a FacadeCache. So a FacadeCache is valid -for the duration of the Facade (usually, the request). In other words, things that go -in the FacadeCache stay (and are visible to) for the duration of the request only. +For each PublishedSnapshot, the PublishedSnapshotService creates a PublishedSnapshotCache. So a PublishedSnapshotCache is valid +for the duration of the PublishedSnapshot (usually, the request). In other words, things that go +in the PublishedSnapshotCache stay (and are visible to) for the duration of the request only. -The FacadeService defines a static constant FullCacheWhenPreviewing, that defines +The PublishedSnapshotService defines a static constant FullCacheWhenPreviewing, that defines how caches operate when previewing: - when true, the caches in preview mode work normally. -- when false, everything that would go to the SnapshotCache goes to the FacadeCache. +- when false, everything that would go to the SnapshotCache goes to the PublishedSnapshotCache. At the moment it is true in the code, which means that eg converted values for previewed content will go in the SnapshotCache. Makes for faster preview, but uses more memory on the long term... would need some benchmarking to figure out what is best. -Members only live for the duration of the Facade. So, for members SnapshotCache is -never used, and everything goes to the FacadeCache. +Members only live for the duration of the PublishedSnapshot. So, for members SnapshotCache is +never used, and everything goes to the PublishedSnapshotCache. All cache keys are computed in the CacheKeys static class. @@ -85,15 +81,15 @@ TESTS For testing purposes the following mechanisms exist: -The Facade type has a static Current property that is used to obtain the 'current' -facade in many places, going through the PublishedCachesServiceResolver to get the -current service, and asking the current service for the current facade, which by +The PublishedSnapshot type has a static Current property that is used to obtain the 'current' +PublishedSnapshot in many places, going through the PublishedCachesServiceResolver to get the +current service, and asking the current service for the current PublishedSnapshot, which by default relies on UmbracoContext. For test purposes, it is possible to override the -entire mechanism by defining Facade.GetCurrentFacadeFunc which should return a facade. +entire mechanism by defining PublishedSnapshot.GetCurrentPublishedSnapshotFunc which should return a PublishedSnapshot. A PublishedContent keeps only id-references to its parent and children, and needs a way to retrieve the actual objects from the cache - which depends on the current -facade. It is possible to override the entire mechanism by defining PublishedContent. +PublishedSnapshot. It is possible to override the entire mechanism by defining PublishedContent. GetContentByIdFunc or .GetMediaByIdFunc. Setting these functions must be done before Resolution is frozen. @@ -110,7 +106,7 @@ possible to support detached contents & properties, even those that do not have int id, but again this should be refactored entirely anyway. Not doing any row-version checks (see XmlStore) when reloading from database, though it -is maintained in the database. Two FIXME in FacadeService. Should we do it? +is maintained in the database. Two FIXME in PublishedSnapshotService. Should we do it? There is no on-disk cache at all so everything is reloaded from the cmsContentNu table when the site restarts. This is pretty fast, but we should experiment with solutions to @@ -121,4 +117,4 @@ PublishedMember exposes properties that IPublishedContent does not, and that are to be lost soon as the member is wrapped (content set, model...) - so we probably need some sort of IPublishedMember. -/ \ No newline at end of file +/ diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5d29e53d4a..861b95691b 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1216,7 +1216,7 @@ - + @@ -1279,4 +1279,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index ad8dcb7e8b..f8ee238da7 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web var mainDomLock = appSettingMainDomLock == "SqlMainDomLock" ? (IMainDomLock)new SqlMainDomLock(logger) - : new MainDomSemaphoreLock(); + : new MainDomSemaphoreLock(logger); var runtime = new WebRuntime(this, logger, new MainDom(logger, mainDomLock)); From d33928f4259b01e4045a46adf92c2356d7297406 Mon Sep 17 00:00:00 2001 From: abi Date: Fri, 3 Jan 2020 01:43:37 +0100 Subject: [PATCH 130/224] Correct project references --- src/Umbraco.Web/Umbraco.Web.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0980b1b3d6..99eadef2bd 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -249,8 +249,8 @@ - - + + From 3d8e9a78e3da9bd8d64abd993f69c1374991c20f Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 3 Jan 2020 12:39:17 +1100 Subject: [PATCH 131/224] Fixes deadlock --- .../NuCache/PublishedSnapshotService.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 2439633005..97e546e603 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; +using System.Threading; using CSharpTest.Net.Collections; using Newtonsoft.Json; using Umbraco.Core; @@ -54,6 +55,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly ContentStore _mediaStore; private readonly SnapDictionary _domainStore; private readonly object _storesLock = new object(); + private readonly object _elementsLock = new object(); private BPlusTree _localContentDb; private BPlusTree _localMediaDb; @@ -143,7 +145,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _domainStore = new SnapDictionary(); - LoadCachesOnStartup(); + LoadCachesOnStartup(); } Guid GetUid(ContentStore store, int id) => store.LiveSnapshot.Get(id)?.Uid ?? default; @@ -171,7 +173,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var path = GetLocalFilesPath(); var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); var localMediaDbPath = Path.Combine(path, "NuCache.Media.db"); - + _localContentDbExists = File.Exists(localContentDbPath); _localMediaDbExists = File.Exists(localMediaDbPath); @@ -221,7 +223,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true)); if (!okContent) - _logger.Warn("Loading content from local db raised warnings, will reload from database."); + _logger.Warn("Loading content from local db raised warnings, will reload from database."); } if (_localMediaDbExists) @@ -1147,12 +1149,12 @@ namespace Umbraco.Web.PublishedCache.NuCache SnapDictionary.Snapshot domainSnap; IAppCache elementsCache; - // TODO: Idea... TryGetElements? Might check if we are shutting down and return false and callers to this could handle it? - // Else does a readerwriterlockslim work here? (i don't think so) - // Else we have 2x locks, one for startup/shutdown, the other for getting elements and then we can maybe do a Monitor.Try? - // That is sort of the same as a TryGetElements + // Here we are reading/writing to shared objects so we need to lock (can't be _storesLock which manages the actual nucache files + // and would result in a deadlock). Even though we are locking around underlying readlocks (within CreateSnapshot) it's because + // we need to ensure that the result of contentSnap.Gen (etc) and the re-assignment of these values and _elements cache + // are done atomically. - lock (_storesLock) + lock (_elementsLock) { var scopeContext = _scopeProvider.Context; @@ -1177,14 +1179,14 @@ namespace Umbraco.Web.PublishedCache.NuCache // elements // just need to make sure nothing gets elements in another enlisted action... so using // a MaxValue to make sure this one runs last, and it should be ok - // ... else there is potential to deadlock since this would recursively go back into trying - // lock on _storesLock but another thread may have already tried that + scopeContext.Enlist("Umbraco.Web.PublishedCache.NuCache.PublishedSnapshotService.Resync", () => this, (completed, svc) => { ((PublishedSnapshot)svc.CurrentPublishedSnapshot)?.Resync(); }, int.MaxValue); } + // create a new snapshot cache if snapshots are different gens if (contentSnap.Gen != _contentGen || mediaSnap.Gen != _mediaGen || domainSnap.Gen != _domainGen || _elementsCache == null) { @@ -1351,7 +1353,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { //culture changed on an existing language var cultureChanged = e.SavedEntities.Any(x => !x.WasPropertyDirty(nameof(ILanguage.Id)) && x.WasPropertyDirty(nameof(ILanguage.IsoCode))); - if(cultureChanged) + if (cultureChanged) { RebuildContentDbCache(); } From e6b333a3ec2f5234a081bb7f1ab821e42f332a3e Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 3 Jan 2020 12:39:56 +1100 Subject: [PATCH 132/224] Changes readlocks to normal locks, no need to have extra objects allocated and complex code. --- .../PublishedCache/NuCache/ContentStore.cs | 72 +++++-------------- .../PublishedCache/NuCache/SnapDictionary.cs | 58 ++++----------- 2 files changed, 32 insertions(+), 98 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 81229af9e1..450dc2f283 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -20,6 +20,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // this class is an extended version of SnapDictionary // most of the snapshots management code, etc is an exact copy // SnapDictionary has unit tests to ensure it all works correctly + // For locking information, see SnapDictionary private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IVariationContextAccessor _variationContextAccessor; @@ -79,11 +80,6 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly string _instanceId = Guid.NewGuid().ToString("N"); - private class ReadLockInfo - { - public bool Taken; - } - private class WriteLockInfo { public bool Taken; @@ -121,15 +117,10 @@ namespace Umbraco.Web.PublishedCache.NuCache private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { - // TODO: We are in a deadlock here somehow?? - Monitor.Enter(_wlocko, ref lockInfo.Taken); - var rtaken = false; - try + lock(_rlocko) { - Monitor.Enter(_rlocko, ref rtaken); - // see SnapDictionary try { } finally @@ -146,26 +137,16 @@ namespace Umbraco.Web.PublishedCache.NuCache _nextGen = true; } } - } - finally - { - if (rtaken) Monitor.Exit(_rlocko); - } - } - - private void Lock(ReadLockInfo lockInfo) - { - Monitor.Enter(_rlocko, ref lockInfo.Taken); + } } private void Release(WriteLockInfo lockInfo, bool commit = true) { if (commit == false) { - var rtaken = false; - try + lock(_rlocko) { - Monitor.Enter(_rlocko, ref rtaken); + // see SnapDictionary try { } finally { @@ -173,10 +154,6 @@ namespace Umbraco.Web.PublishedCache.NuCache _liveGen -= 1; } } - finally - { - if (rtaken) Monitor.Exit(_rlocko); - } Rollback(_contentNodes); RollbackRoot(); @@ -207,11 +184,6 @@ namespace Umbraco.Web.PublishedCache.NuCache Monitor.Exit(_wlocko); } - private void Release(ReadLockInfo lockInfo) - { - if (lockInfo.Taken) Monitor.Exit(_rlocko); - } - private void RollbackRoot() { if (_root.Gen <= _liveGen) return; @@ -1248,13 +1220,8 @@ namespace Umbraco.Web.PublishedCache.NuCache public Snapshot CreateSnapshot() { - var lockInfo = new ReadLockInfo(); - try + lock(_rlocko) { - // TODO: This would be much simpler with just a lock(_rlocko) { } - // in this case I see no reason why we are using this syntax?! - Lock(lockInfo); - // if no next generation is required, and we already have one, // use it and create a new snapshot if (_nextGen == false && _genObj != null) @@ -1306,10 +1273,6 @@ namespace Umbraco.Web.PublishedCache.NuCache return snapshot; } - finally - { - Release(lockInfo); - } } public Snapshot LiveSnapshot => new Snapshot(this, _liveGen @@ -1427,18 +1390,17 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - // TODO: This is never used? Should it be? - - public async Task WaitForPendingCollect() - { - Task task; - lock (_rlocko) - { - task = _collectTask; - } - if (task != null) - await task; - } + // TODO: This is never used? Should it be? Maybe move to TestHelper below? + //public async Task WaitForPendingCollect() + //{ + // Task task; + // lock (_rlocko) + // { + // task = _collectTask; + // } + // if (task != null) + // await task; + //} public long GenCount => _genObjs.Count; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs index 9671949ff0..e0ad26eb81 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs @@ -22,6 +22,11 @@ namespace Umbraco.Web.PublishedCache.NuCache // This class is optimized for many readers, few writers // Readers are lock-free + // NOTE - we used to lock _rlocko the long hand way with Monitor.Enter(_rlocko, ref lockTaken) but this has + // been replaced with a normal c# lock because that's exactly how the normal c# lock works, + // see https://blogs.msdn.microsoft.com/ericlippert/2009/03/06/locks-and-exceptions-do-not-mix/ + // for the readlock, there's no reason here to use the long hand way. + private readonly ConcurrentDictionary> _items; private readonly ConcurrentQueue _genObjs; private GenObj _genObj; @@ -71,16 +76,11 @@ namespace Umbraco.Web.PublishedCache.NuCache // are all ignored - Release is private and meant to be invoked with 'commit' being false only // only on the outermost lock (by SnapDictionaryWriter) - // using (...) {} for locking is prone to nasty leaks in case of weird exceptions - // such as thread-abort or out-of-memory, but let's not worry about it now + // side note - using (...) {} for locking is prone to nasty leaks in case of weird exceptions + // such as thread-abort or out-of-memory, which is why we've moved away from the old using wrapper we had on locking. private readonly string _instanceId = Guid.NewGuid().ToString("N"); - private class ReadLockInfo - { - public bool Taken; - } - private class WriteLockInfo { public bool Taken; @@ -121,18 +121,16 @@ namespace Umbraco.Web.PublishedCache.NuCache { Monitor.Enter(_wlocko, ref lockInfo.Taken); - var rtaken = false; - try + lock(_rlocko) { - Monitor.Enter(_rlocko, ref rtaken); - // assume everything in finally runs atomically // http://stackoverflow.com/questions/18501678/can-this-unexpected-behavior-of-prepareconstrainedregions-and-thread-abort-be-ex // http://joeduffyblog.com/2005/03/18/atomicity-and-asynchronous-exception-failures/ // http://joeduffyblog.com/2007/02/07/introducing-the-new-readerwriterlockslim-in-orcas/ // http://chabster.blogspot.fr/2013/12/readerwriterlockslim-fails-on-dual.html //RuntimeHelpers.PrepareConstrainedRegions(); - try { } finally + try { } + finally { // increment the lock count, and register that this lock is counting _wlocked++; @@ -149,15 +147,6 @@ namespace Umbraco.Web.PublishedCache.NuCache } } } - finally - { - if (rtaken) Monitor.Exit(_rlocko); - } - } - - private void Lock(ReadLockInfo lockInfo) - { - Monitor.Enter(_rlocko, ref lockInfo.Taken); } private void Release(WriteLockInfo lockInfo, bool commit = true) @@ -168,22 +157,17 @@ namespace Umbraco.Web.PublishedCache.NuCache if (commit == false) { - var rtaken = false; - try + lock(_rlocko) { - Monitor.Enter(_rlocko, ref rtaken); - try { } finally + try { } + finally { // forget about the temp. liveGen _nextGen = false; _liveGen -= 1; } } - finally - { - if (rtaken) Monitor.Exit(_rlocko); - } - + foreach (var item in _items) { var link = item.Value; @@ -202,11 +186,6 @@ namespace Umbraco.Web.PublishedCache.NuCache Monitor.Exit(_wlocko); } - private void Release(ReadLockInfo lockInfo) - { - if (lockInfo.Taken) Monitor.Exit(_rlocko); - } - #endregion #region Set, Clear, Get, Has @@ -347,11 +326,8 @@ namespace Umbraco.Web.PublishedCache.NuCache public Snapshot CreateSnapshot() { - var lockInfo = new ReadLockInfo(); - try + lock(_rlocko) { - Lock(lockInfo); - // if no next generation is required, and we already have a gen object, // use it to create a new snapshot if (_nextGen == false && _genObj != null) @@ -398,10 +374,6 @@ namespace Umbraco.Web.PublishedCache.NuCache return snapshot; } - finally - { - Release(lockInfo); - } } public Task CollectAsync() From 8e3b3c83261e5272fefb219aaceaa645775ad164 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 3 Jan 2020 13:21:49 +1100 Subject: [PATCH 133/224] Changes methods that should already be locked to check that they are and changes their names/adds docs --- .../PublishedCache/NuCache/ContentStore.cs | 488 +++++++++--------- .../NuCache/PublishedSnapshotService.cs | 30 +- 2 files changed, 270 insertions(+), 248 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 450dc2f283..82fae0d32a 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -115,6 +115,12 @@ namespace Umbraco.Web.PublishedCache.NuCache return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ScopedWriteLock(this, scoped)); } + private void EnsureLocked() + { + if (!Monitor.IsEntered(_wlocko)) + throw new InvalidOperationException("Write lock must be acquried."); + } + private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { Monitor.Enter(_wlocko, ref lockInfo.Taken); @@ -323,34 +329,36 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public void SetAllContentTypes(IEnumerable types) + /// + /// Updates/sets data for all content types + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public void SetAllContentTypesLocked(IEnumerable types) { - // TODO: There should be NO lock here! All calls made to this are wrapped in GetScopedWriteLock + EnsureLocked(); - //var lockInfo = new WriteLockInfo(); - try + _logger.Debug("SetAllContentTypes"); + + // clear all existing content types + ClearLocked(_contentTypesById); + ClearLocked(_contentTypesByAlias); + + // set all new content types + foreach (var type in types) { - _logger.Debug("SetAllContentTypes"); - //Lock(lockInfo); - - // clear all existing content types - ClearLocked(_contentTypesById); - ClearLocked(_contentTypesByAlias); - - // set all new content types - foreach (var type in types) - { - SetValueLocked(_contentTypesById, type.Id, type); - SetValueLocked(_contentTypesByAlias, type.Alias, type); - } - - // beware! at that point the cache is inconsistent, - // assuming we are going to SetAll content items! - } - finally - { - //Release(lockInfo); + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); } + + // beware! at that point the cache is inconsistent, + // assuming we are going to SetAll content items! } public void UpdateContentTypes(IReadOnlyCollection removedIds, IReadOnlyCollection refreshedTypes, IReadOnlyCollection kits) @@ -540,8 +548,22 @@ namespace Umbraco.Web.PublishedCache.NuCache return link; } - public bool Set(ContentNodeKit kit) + /// + /// Sets the data for a + /// + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public bool SetLocked(ContentNodeKit kit) { + EnsureLocked(); + // ReSharper disable LocalizableElement if (kit.IsEmpty) throw new ArgumentException("Kit is empty.", nameof(kit)); @@ -551,58 +573,47 @@ namespace Umbraco.Web.PublishedCache.NuCache _logger.Debug("Set content ID: {KitNodeId}", kit.Node.Id); - // TODO: There should be NO locks here, all calls made to this are done within GetScopedWriteLock - //var lockInfo = new WriteLockInfo(); - try + // get existing + _contentNodes.TryGetValue(kit.Node.Id, out var link); + var existing = link?.Value; + + if (!BuildKit(kit, out var parent)) + return false; + + // moving? + var moving = existing != null && existing.ParentContentId != kit.Node.ParentContentId; + + // manage children + if (existing != null) { - //Lock(lockInfo); - - // get existing - _contentNodes.TryGetValue(kit.Node.Id, out var link); - var existing = link?.Value; - - if (!BuildKit(kit, out var parent)) - return false; - - // moving? - var moving = existing != null && existing.ParentContentId != kit.Node.ParentContentId; - - // manage children - if (existing != null) - { - kit.Node.FirstChildContentId = existing.FirstChildContentId; - kit.Node.LastChildContentId = existing.LastChildContentId; - } - - // set - SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); - if (_localDb != null) RegisterChange(kit.Node.Id, kit); - - // manage the tree - if (existing == null) - { - // new, add to parent - AddTreeNodeLocked(kit.Node, parent); - } - else if (moving || existing.SortOrder != kit.Node.SortOrder) - { - // moved, remove existing from its parent, add content to its parent - RemoveTreeNodeLocked(existing); - AddTreeNodeLocked(kit.Node); - } - else - { - // replacing existing, handle siblings - kit.Node.NextSiblingContentId = existing.NextSiblingContentId; - kit.Node.PreviousSiblingContentId = existing.PreviousSiblingContentId; - } - - _xmap[kit.Node.Uid] = kit.Node.Id; + kit.Node.FirstChildContentId = existing.FirstChildContentId; + kit.Node.LastChildContentId = existing.LastChildContentId; } - finally + + // set + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + + // manage the tree + if (existing == null) { - //Release(lockInfo); + // new, add to parent + AddTreeNodeLocked(kit.Node, parent); } + else if (moving || existing.SortOrder != kit.Node.SortOrder) + { + // moved, remove existing from its parent, add content to its parent + RemoveTreeNodeLocked(existing); + AddTreeNodeLocked(kit.Node); + } + else + { + // replacing existing, handle siblings + kit.Node.NextSiblingContentId = existing.NextSiblingContentId; + kit.Node.PreviousSiblingContentId = existing.PreviousSiblingContentId; + } + + _xmap[kit.Node.Uid] = kit.Node.Id; return true; } @@ -624,211 +635,222 @@ namespace Umbraco.Web.PublishedCache.NuCache /// True if the data is coming from the database (not the local cache db) /// /// + /// /// This requires that the collection is sorted by Level + ParentId + Sort Order. /// This should be used only on a site startup as the first generations. /// This CANNOT be used after startup since it bypasses all checks for Generations. + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// /// - internal bool SetAllFastSorted(IEnumerable kits, bool fromDb) + /// + /// Thrown if this method is not called within a write lock + /// + public bool SetAllFastSortedLocked(IEnumerable kits, bool fromDb) { + EnsureLocked(); - //TODO: There should be NO locks here all calls made to this are done within GetScopedWriteLock + _logger.Debug("SetAllFastSorted"); - //var lockInfo = new WriteLockInfo(); var ok = true; - try + + ClearLocked(_contentNodes); + ClearRootLocked(); + + // The name of the game here is to populate each kit's + // FirstChildContentId + // LastChildContentId + // NextSiblingContentId + // PreviousSiblingContentId + + ContentNode previousNode = null; + ContentNode parent = null; + + foreach (var kit in kits) { - _logger.Debug("SetAllFastSorted"); - - //Lock(lockInfo); - - ClearLocked(_contentNodes); - ClearRootLocked(); - - // The name of the game here is to populate each kit's - // FirstChildContentId - // LastChildContentId - // NextSiblingContentId - // PreviousSiblingContentId - - ContentNode previousNode = null; - ContentNode parent = null; - - foreach (var kit in kits) + if (!BuildKit(kit, out var parentLink)) { - if (!BuildKit(kit, out var parentLink)) - { - ok = false; - continue; // skip that one - } - - var thisNode = kit.Node; - - if (parent == null) - { - // first parent - parent = parentLink.Value; - parent.FirstChildContentId = thisNode.Id; // this node is the first node - } - else if (parent.Id != parentLink.Value.Id) - { - // new parent - parent = parentLink.Value; - parent.FirstChildContentId = thisNode.Id; // this node is the first node - previousNode = null; // there is no previous sibling - } - - _logger.Verbose($"Set {thisNode.Id} with parent {thisNode.ParentContentId}"); - SetValueLocked(_contentNodes, thisNode.Id, thisNode); - - // if we are initializing from the database source ensure the local db is updated - if (fromDb && _localDb != null) RegisterChange(thisNode.Id, kit); - - // this node is always the last child - parent.LastChildContentId = thisNode.Id; - - // wire previous node as previous sibling - if (previousNode != null) - { - previousNode.NextSiblingContentId = thisNode.Id; - thisNode.PreviousSiblingContentId = previousNode.Id; - } - - // this node becomes the previous node - previousNode = thisNode; - - _xmap[kit.Node.Uid] = kit.Node.Id; + ok = false; + continue; // skip that one } - } - finally - { - //Release(lockInfo); + + var thisNode = kit.Node; + + if (parent == null) + { + // first parent + parent = parentLink.Value; + parent.FirstChildContentId = thisNode.Id; // this node is the first node + } + else if (parent.Id != parentLink.Value.Id) + { + // new parent + parent = parentLink.Value; + parent.FirstChildContentId = thisNode.Id; // this node is the first node + previousNode = null; // there is no previous sibling + } + + _logger.Verbose($"Set {thisNode.Id} with parent {thisNode.ParentContentId}"); + SetValueLocked(_contentNodes, thisNode.Id, thisNode); + + // if we are initializing from the database source ensure the local db is updated + if (fromDb && _localDb != null) RegisterChange(thisNode.Id, kit); + + // this node is always the last child + parent.LastChildContentId = thisNode.Id; + + // wire previous node as previous sibling + if (previousNode != null) + { + previousNode.NextSiblingContentId = thisNode.Id; + thisNode.PreviousSiblingContentId = previousNode.Id; + } + + // this node becomes the previous node + previousNode = thisNode; + + _xmap[kit.Node.Uid] = kit.Node.Id; } return ok; } - public bool SetAll(IEnumerable kits) + /// + /// Set all data for a collection of + /// + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public bool SetAllLocked(IEnumerable kits) { - //TODO: There should be NO locks here all calls made to this are done within GetScopedWriteLock + EnsureLocked(); - //var lockInfo = new WriteLockInfo(); var ok = true; - try + _logger.Debug("SetAll"); + + ClearLocked(_contentNodes); + ClearRootLocked(); + + // do NOT clear types else they are gone! + //ClearLocked(_contentTypesById); + //ClearLocked(_contentTypesByAlias); + + foreach (var kit in kits) { - _logger.Debug("SetAll"); - - //Lock(lockInfo); - - ClearLocked(_contentNodes); - ClearRootLocked(); - - // do NOT clear types else they are gone! - //ClearLocked(_contentTypesById); - //ClearLocked(_contentTypesByAlias); - - foreach (var kit in kits) + if (!BuildKit(kit, out var parent)) { - if (!BuildKit(kit, out var parent)) - { - ok = false; - continue; // skip that one - } - _logger.Verbose($"Set {kit.Node.Id} with parent {kit.Node.ParentContentId}"); - SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); - - if (_localDb != null) RegisterChange(kit.Node.Id, kit); - AddTreeNodeLocked(kit.Node, parent); - - _xmap[kit.Node.Uid] = kit.Node.Id; + ok = false; + continue; // skip that one } - } - finally - { - //Release(lockInfo); + _logger.Verbose($"Set {kit.Node.Id} with parent {kit.Node.ParentContentId}"); + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + AddTreeNodeLocked(kit.Node, parent); + + _xmap[kit.Node.Uid] = kit.Node.Id; } return ok; } - // IMPORTANT kits must be sorted out by LEVEL and by SORT ORDER - public bool SetBranch(int rootContentId, IEnumerable kits) + /// + /// Sets data for a branch of + /// + /// + /// + /// + /// + /// + /// IMPORTANT kits must be sorted out by LEVEL and by SORT ORDER + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// + /// Thrown if this method is not called within a write lock + /// + public bool SetBranchLocked(int rootContentId, IEnumerable kits) { - //TODO: There should be NO locks here all calls made to this are done within GetScopedWriteLock + EnsureLocked(); - //var lockInfo = new WriteLockInfo(); var ok = true; - try + _logger.Debug("SetBranch"); + + // get existing + _contentNodes.TryGetValue(rootContentId, out var link); + var existing = link?.Value; + + // clear + if (existing != null) { - _logger.Debug("SetBranch"); - - //Lock(lockInfo); - - // get existing - _contentNodes.TryGetValue(rootContentId, out var link); - var existing = link?.Value; - - // clear - if (existing != null) - { - //this zero's out the branch (recursively), if we're in a new gen this will add a NULL placeholder for the gen - ClearBranchLocked(existing); - //TODO: This removes the current GEN from the tree - do we really want to do that? - RemoveTreeNodeLocked(existing); - } - - // now add them all back - foreach (var kit in kits) - { - if (!BuildKit(kit, out var parent)) - { - ok = false; - continue; // skip that one - } - SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); - if (_localDb != null) RegisterChange(kit.Node.Id, kit); - AddTreeNodeLocked(kit.Node, parent); - - _xmap[kit.Node.Uid] = kit.Node.Id; - } + //this zero's out the branch (recursively), if we're in a new gen this will add a NULL placeholder for the gen + ClearBranchLocked(existing); + //TODO: This removes the current GEN from the tree - do we really want to do that? + RemoveTreeNodeLocked(existing); } - finally + + // now add them all back + foreach (var kit in kits) { - //Release(lockInfo); + if (!BuildKit(kit, out var parent)) + { + ok = false; + continue; // skip that one + } + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + AddTreeNodeLocked(kit.Node, parent); + + _xmap[kit.Node.Uid] = kit.Node.Id; } return ok; } - public bool Clear(int id) + /// + /// Clears data for a given node id + /// + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public bool ClearLocked(int id) { - // TODO: There should be NO locks here! All calls to this are made within GetScopedWriteLock - //var lockInfo = new WriteLockInfo(); - try - { - _logger.Debug("Clear"); + EnsureLocked(); - //Lock(lockInfo); + _logger.Debug("Clear"); - // try to find the content - // if it is not there, nothing to do - _contentNodes.TryGetValue(id, out var link); // else null - if (link?.Value == null) return false; + // try to find the content + // if it is not there, nothing to do + _contentNodes.TryGetValue(id, out var link); // else null + if (link?.Value == null) return false; - var content = link.Value; - _logger.Debug("Clear content ID: {ContentId}", content.Id); + var content = link.Value; + _logger.Debug("Clear content ID: {ContentId}", content.Id); - // clear the entire branch - ClearBranchLocked(content); + // clear the entire branch + ClearBranchLocked(content); - // manage the tree - RemoveTreeNodeLocked(content); + // manage the tree + RemoveTreeNodeLocked(content); - return true; - } - finally - { - //Release(lockInfo); - } + return true; } private void ClearBranchLocked(int id) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 97e546e603..b761fe0fa4 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -384,7 +384,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var contentTypes = _serviceContext.ContentTypeService.GetAll() .Select(x => _publishedContentTypeFactory.CreateContentType(x)); - _contentStore.SetAllContentTypes(contentTypes); + _contentStore.SetAllContentTypesLocked(contentTypes); using (_logger.TraceDuration("Loading content from database")) { @@ -395,7 +395,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // IMPORTANT GetAllContentSources sorts kits by level + parentId + sortOrder var kits = _dataSource.GetAllContentSources(scope); - return onStartup ? _contentStore.SetAllFastSorted(kits, true) : _contentStore.SetAll(kits); + return onStartup ? _contentStore.SetAllFastSortedLocked(kits, true) : _contentStore.SetAllLocked(kits); } } @@ -403,7 +403,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { var contentTypes = _serviceContext.ContentTypeService.GetAll() .Select(x => _publishedContentTypeFactory.CreateContentType(x)); - _contentStore.SetAllContentTypes(contentTypes); + _contentStore.SetAllContentTypesLocked(contentTypes); using (_logger.TraceDuration("Loading content from local cache file")) { @@ -455,7 +455,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var mediaTypes = _serviceContext.MediaTypeService.GetAll() .Select(x => _publishedContentTypeFactory.CreateContentType(x)); - _mediaStore.SetAllContentTypes(mediaTypes); + _mediaStore.SetAllContentTypesLocked(mediaTypes); using (_logger.TraceDuration("Loading media from database")) { @@ -467,7 +467,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _logger.Debug("Loading media from database..."); // IMPORTANT GetAllMediaSources sorts kits by level + parentId + sortOrder var kits = _dataSource.GetAllMediaSources(scope); - return onStartup ? _mediaStore.SetAllFastSorted(kits, true) : _mediaStore.SetAll(kits); + return onStartup ? _mediaStore.SetAllFastSortedLocked(kits, true) : _mediaStore.SetAllLocked(kits); } } @@ -475,7 +475,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { var mediaTypes = _serviceContext.MediaTypeService.GetAll() .Select(x => _publishedContentTypeFactory.CreateContentType(x)); - _mediaStore.SetAllContentTypes(mediaTypes); + _mediaStore.SetAllContentTypesLocked(mediaTypes); using (_logger.TraceDuration("Loading media from local cache file")) { @@ -516,7 +516,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return false; } - return onStartup ? store.SetAllFastSorted(kits, false) : store.SetAll(kits); + return onStartup ? store.SetAllFastSortedLocked(kits, false) : store.SetAllLocked(kits); } // keep these around - might be useful @@ -713,7 +713,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) { - if (_contentStore.Clear(payload.Id)) + if (_contentStore.ClearLocked(payload.Id)) draftChanged = publishedChanged = true; continue; } @@ -736,7 +736,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // ?? should we do some RV check here? // IMPORTANT GetbranchContentSources sorts kits by level and by sort order var kits = _dataSource.GetBranchContentSources(scope, capture.Id); - _contentStore.SetBranch(capture.Id, kits); + _contentStore.SetBranchLocked(capture.Id, kits); } else { @@ -744,11 +744,11 @@ namespace Umbraco.Web.PublishedCache.NuCache var kit = _dataSource.GetContentSource(scope, capture.Id); if (kit.IsEmpty) { - _contentStore.Clear(capture.Id); + _contentStore.ClearLocked(capture.Id); } else { - _contentStore.Set(kit); + _contentStore.SetLocked(kit); } } @@ -806,7 +806,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) { - if (_mediaStore.Clear(payload.Id)) + if (_mediaStore.ClearLocked(payload.Id)) anythingChanged = true; continue; } @@ -829,7 +829,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // ?? should we do some RV check here? // IMPORTANT GetbranchContentSources sorts kits by level and by sort order var kits = _dataSource.GetBranchMediaSources(scope, capture.Id); - _mediaStore.SetBranch(capture.Id, kits); + _mediaStore.SetBranchLocked(capture.Id, kits); } else { @@ -837,11 +837,11 @@ namespace Umbraco.Web.PublishedCache.NuCache var kit = _dataSource.GetMediaSource(scope, capture.Id); if (kit.IsEmpty) { - _mediaStore.Clear(capture.Id); + _mediaStore.ClearLocked(capture.Id); } else { - _mediaStore.Set(kit); + _mediaStore.SetLocked(kit); } } From 243e76b3cc3e5fccdf9c021354d4a2d88b6bb707 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 3 Jan 2020 15:04:39 +1100 Subject: [PATCH 134/224] Removes ability to have recursive locks in SnapDictionary, changes logic to require locking around the methods just like ContentStore, updates tests --- .../Cache/SnapDictionaryTests.cs | 126 ++++++++------ .../PublishedCache/NuCache/ContentStore.cs | 104 ++++++------ .../NuCache/PublishedSnapshotService.cs | 14 +- .../PublishedCache/NuCache/SnapDictionary.cs | 154 ++++++++---------- 4 files changed, 204 insertions(+), 194 deletions(-) diff --git a/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs b/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs index b435af9e77..86426d196c 100644 --- a/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs +++ b/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs @@ -9,6 +9,47 @@ using Umbraco.Web.PublishedCache.NuCache.Snap; namespace Umbraco.Tests.Cache { + /// + /// Used for tests + /// + public static class SnapDictionaryExtensions + { + internal static void Set(this SnapDictionary d, TKey key, TValue value) + where TValue : class + { + using (d.GetScopedWriteLock(GetScopeProvider())) + { + d.SetLocked(key, value); + } + } + + internal static void Clear(this SnapDictionary d) + where TValue : class + { + using (d.GetScopedWriteLock(GetScopeProvider())) + { + d.ClearLocked(); + } + } + + internal static void Clear(this SnapDictionary d, TKey key) + where TValue : class + { + using (d.GetScopedWriteLock(GetScopeProvider())) + { + d.ClearLocked(key); + } + } + + private static IScopeProvider GetScopeProvider() + { + var scopeProvider = Mock.Of(); + Mock.Get(scopeProvider) + .Setup(x => x.Context).Returns(() => null); + return scopeProvider; + } + } + [TestFixture] public class SnapDictionaryTests { @@ -223,7 +264,7 @@ namespace Umbraco.Tests.Cache { var d = new SnapDictionary(); d.Test.CollectAuto = false; - + // gen 1 d.Set(1, "one"); Assert.AreEqual(1, d.Test.GetValues(1).Length); @@ -321,7 +362,7 @@ namespace Umbraco.Tests.Cache { var d = new SnapDictionary(); d.Test.CollectAuto = false; - + Assert.AreEqual(0, d.Test.GetValues(1).Length); // gen 1 @@ -416,7 +457,7 @@ namespace Umbraco.Tests.Cache { var d = new SnapDictionary(); d.Test.CollectAuto = false; - + // gen 1 d.Set(1, "one"); Assert.AreEqual(1, d.Test.GetValues(1).Length); @@ -578,7 +619,7 @@ namespace Umbraco.Tests.Cache { var d = new SnapDictionary(); d.Test.CollectAuto = false; - + d.Set(1, "one"); d.Set(2, "two"); @@ -689,7 +730,7 @@ namespace Umbraco.Tests.Cache { // gen 3 Assert.AreEqual(2, d.Test.GetValues(1).Length); - d.Set(1, "ein"); + d.SetLocked(1, "ein"); Assert.AreEqual(3, d.Test.GetValues(1).Length); Assert.AreEqual(3, d.Test.LiveGen); @@ -727,31 +768,25 @@ namespace Umbraco.Tests.Cache using (var w1 = d.GetScopedWriteLock(scopeProvider)) { Assert.AreEqual(1, t.LiveGen); - Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.IsLocked); Assert.IsTrue(t.NextGen); - using (var w2 = d.GetScopedWriteLock(scopeProvider)) + Assert.Throws(() => { - Assert.AreEqual(1, t.LiveGen); - Assert.AreEqual(2, t.WLocked); - Assert.IsTrue(t.NextGen); - - Assert.AreNotSame(w1, w2); // get a new writer each time - - d.Set(1, "one"); - - Assert.AreEqual(0, d.CreateSnapshot().Gen); - } + using (var w2 = d.GetScopedWriteLock(scopeProvider)) + { + } + }); Assert.AreEqual(1, t.LiveGen); - Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.IsLocked); Assert.IsTrue(t.NextGen); Assert.AreEqual(0, d.CreateSnapshot().Gen); } Assert.AreEqual(1, t.LiveGen); - Assert.AreEqual(0, t.WLocked); + Assert.IsFalse(t.IsLocked); Assert.IsTrue(t.NextGen); Assert.AreEqual(1, d.CreateSnapshot().Gen); @@ -772,11 +807,14 @@ namespace Umbraco.Tests.Cache using (var w1 = d.GetScopedWriteLock(scopeProvider)) { + // This one is interesting, although we don't allow recursive locks, since this is + // using the same ScopeContext/key, the lock acquisition is only done once + using (var w2 = d.GetScopedWriteLock(scopeProvider)) { Assert.AreSame(w1, w2); - d.Set(1, "one"); + d.SetLocked(1, "one"); } } } @@ -797,19 +835,16 @@ namespace Umbraco.Tests.Cache using (var w1 = d.GetScopedWriteLock(scopeProvider1)) { Assert.AreEqual(1, t.LiveGen); - Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.IsLocked); Assert.IsTrue(t.NextGen); - using (var w2 = d.GetScopedWriteLock(scopeProvider2)) + Assert.Throws(() => { - Assert.AreEqual(1, t.LiveGen); - Assert.AreEqual(2, t.WLocked); - Assert.IsTrue(t.NextGen); + using (var w2 = d.GetScopedWriteLock(scopeProvider2)) + { + } + }); - Assert.AreNotSame(w1, w2); - - d.Set(1, "one"); - } } } @@ -848,13 +883,13 @@ namespace Umbraco.Tests.Cache Assert.IsFalse(d.Test.NextGen); Assert.AreEqual("uno", s2.Get(1)); - var scopeProvider = GetScopeProvider(); + var scopeProvider = GetScopeProvider(); using (d.GetScopedWriteLock(scopeProvider)) { // gen 3 Assert.AreEqual(2, d.Test.GetValues(1).Length); - d.Set(1, "ein"); + d.SetLocked(1, "ein"); Assert.AreEqual(3, d.Test.GetValues(1).Length); Assert.AreEqual(3, d.Test.LiveGen); @@ -881,6 +916,7 @@ namespace Umbraco.Tests.Cache { var d = new SnapDictionary(); d.Test.CollectAuto = false; + // gen 1 d.Set(1, "one"); @@ -894,12 +930,11 @@ namespace Umbraco.Tests.Cache Assert.AreEqual("uno", s2.Get(1)); var scopeProvider = GetScopeProvider(); - using (d.GetScopedWriteLock(scopeProvider)) { // creating a snapshot in a write-lock does NOT return the "current" content // it uses the previous snapshot, so new snapshot created only on release - d.Set(1, "ein"); + d.SetLocked(1, "ein"); var s3 = d.CreateSnapshot(); Assert.AreEqual(2, s3.Gen); Assert.AreEqual("uno", s3.Get(1)); @@ -934,12 +969,11 @@ namespace Umbraco.Tests.Cache var scopeContext = new ScopeContext(); var scopeProvider = GetScopeProvider(scopeContext); - using (d.GetScopedWriteLock(scopeProvider)) { // creating a snapshot in a write-lock does NOT return the "current" content // it uses the previous snapshot, so new snapshot created only on release - d.Set(1, "ein"); + d.SetLocked(1, "ein"); var s3 = d.CreateSnapshot(); Assert.AreEqual(2, s3.Gen); Assert.AreEqual("uno", s3.Get(1)); @@ -967,7 +1001,7 @@ namespace Umbraco.Tests.Cache var d = new SnapDictionary(); var t = d.Test; t.CollectAuto = false; - + // gen 1 d.Set(1, "one"); var s1 = d.CreateSnapshot(); @@ -984,12 +1018,11 @@ namespace Umbraco.Tests.Cache var scopeContext = new ScopeContext(); var scopeProvider = GetScopeProvider(scopeContext); - using (d.GetScopedWriteLock(scopeProvider)) { // creating a snapshot in a write-lock does NOT return the "current" content // it uses the previous snapshot, so new snapshot created only on release - d.Set(1, "ein"); + d.SetLocked(1, "ein"); var s3 = d.CreateSnapshot(); Assert.AreEqual(2, s3.Gen); Assert.AreEqual("uno", s3.Get(1)); @@ -997,7 +1030,7 @@ namespace Umbraco.Tests.Cache // we made some changes, so a next gen is required Assert.AreEqual(3, t.LiveGen); Assert.IsTrue(t.NextGen); - Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.IsLocked); // but live snapshot contains changes var ls = t.LiveSnapshot; @@ -1008,7 +1041,7 @@ namespace Umbraco.Tests.Cache // nothing is committed until scope exits Assert.AreEqual(3, t.LiveGen); Assert.IsTrue(t.NextGen); - Assert.AreEqual(1, t.WLocked); + Assert.IsTrue(t.IsLocked); // no changes until exit var s4 = d.CreateSnapshot(); @@ -1020,7 +1053,7 @@ namespace Umbraco.Tests.Cache // now things have changed Assert.AreEqual(2, t.LiveGen); Assert.IsFalse(t.NextGen); - Assert.AreEqual(0, t.WLocked); + Assert.IsFalse(t.IsLocked); // no changes since not completed var s5 = d.CreateSnapshot(); @@ -1097,9 +1130,10 @@ namespace Umbraco.Tests.Cache // writer is scope contextual and scoped // when disposed, nothing happens // when the context exists, the writer is released + using (d.GetScopedWriteLock(scopeProvider)) { - d.Set(1, "ein"); + d.SetLocked(1, "ein"); Assert.IsTrue(d.Test.NextGen); Assert.AreEqual(3, d.Test.LiveGen); Assert.IsNotNull(d.Test.GenObj); @@ -1107,7 +1141,7 @@ namespace Umbraco.Tests.Cache } // writer has not released - Assert.AreEqual(1, d.Test.WLocked); + Assert.IsTrue(d.Test.IsLocked); Assert.IsNotNull(d.Test.GenObj); Assert.AreEqual(2, d.Test.GenObj.Gen); @@ -1118,7 +1152,7 @@ namespace Umbraco.Tests.Cache // panic! var s2 = d.CreateSnapshot(); - Assert.AreEqual(1, d.Test.WLocked); + Assert.IsTrue(d.Test.IsLocked); Assert.IsNotNull(d.Test.GenObj); Assert.AreEqual(2, d.Test.GenObj.Gen); Assert.AreEqual(3, d.Test.LiveGen); @@ -1127,7 +1161,7 @@ namespace Umbraco.Tests.Cache // release writer scopeContext.ScopeExit(true); - Assert.AreEqual(0, d.Test.WLocked); + Assert.IsFalse(d.Test.IsLocked); Assert.IsNotNull(d.Test.GenObj); Assert.AreEqual(2, d.Test.GenObj.Gen); Assert.AreEqual(3, d.Test.LiveGen); @@ -1135,7 +1169,7 @@ namespace Umbraco.Tests.Cache var s3 = d.CreateSnapshot(); - Assert.AreEqual(0, d.Test.WLocked); + Assert.IsFalse(d.Test.IsLocked); Assert.IsNotNull(d.Test.GenObj); Assert.AreEqual(3, d.Test.GenObj.Gen); Assert.AreEqual(3, d.Test.LiveGen); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 82fae0d32a..17eb2b6457 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -39,7 +39,6 @@ namespace Umbraco.Web.PublishedCache.NuCache private long _liveGen, _floorGen; private bool _nextGen, _collectAuto; private Task _collectTask; - private volatile int _wlocked; private List> _wchanges; // TODO: collection trigger (ok for now) @@ -83,7 +82,6 @@ namespace Umbraco.Web.PublishedCache.NuCache private class WriteLockInfo { public bool Taken; - public bool Count; } // a scope contextual that represents a locked writer to the dictionary @@ -111,7 +109,6 @@ namespace Umbraco.Web.PublishedCache.NuCache // TODO: GetScopedWriter? should the dict have a ref onto the scope provider? public IDisposable GetScopedWriteLock(IScopeProvider scopeProvider) { - _logger.Debug("GetScopedWriteLock"); return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ScopedWriteLock(this, scoped)); } @@ -123,6 +120,9 @@ namespace Umbraco.Web.PublishedCache.NuCache private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { + if (Monitor.IsEntered(_wlocko)) + throw new InvalidOperationException("Recursive locks not allowed"); + Monitor.Enter(_wlocko, ref lockInfo.Taken); lock(_rlocko) @@ -131,9 +131,7 @@ namespace Umbraco.Web.PublishedCache.NuCache try { } finally { - _wlocked++; - lockInfo.Count = true; - if (_nextGen == false || (forceGen && _wlocked == 1)) + if (_nextGen == false || (forceGen)) { // because we are changing things, a new generation // is created, which will trigger a new snapshot @@ -179,12 +177,6 @@ namespace Umbraco.Web.PublishedCache.NuCache _localDb.Commit(); } - // TODO: Shouldn't this be in a finally block? - // TODO: Shouldn't this be decremented after we exit?? - // TODO: Shouldn't the locked flag never exceed 1? - if (lockInfo.Count) - _wlocked--; - // TODO: Shouldn't this be in a finally block? if (lockInfo.Taken) Monitor.Exit(_wlocko); @@ -220,22 +212,18 @@ namespace Umbraco.Web.PublishedCache.NuCache public void ReleaseLocalDb() { - _logger.Info("Releasing DB..."); var lockInfo = new WriteLockInfo(); try { try { - // Trying to lock could throw exceptions so always make sure to clean up. - _logger.Info("Trying to lock before releasing DB (lock count = {LockCount}) ...", _wlocked); - + // Trying to lock could throw exceptions so always make sure to clean up. Lock(lockInfo); } finally { try { - _logger.Info("Disposing local DB..."); _localDb?.Dispose(); } catch (Exception ex) @@ -275,57 +263,61 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Content types - public void NewContentTypes(IEnumerable types) + /// + /// Sets data for new content types + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public void NewContentTypesLocked(IEnumerable types) { - var lockInfo = new WriteLockInfo(); - try - { - _logger.Debug("NewContentTypes"); - Lock(lockInfo); + EnsureLocked(); - foreach (var type in types) - { - SetValueLocked(_contentTypesById, type.Id, type); - SetValueLocked(_contentTypesByAlias, type.Alias, type); - } - } - finally + foreach (var type in types) { - Release(lockInfo); + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); } } - public void UpdateContentTypes(IEnumerable types) + /// + /// Sets data for updated content types + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public void UpdateContentTypesLocked(IEnumerable types) { //nothing to do if this is empty, no need to lock/allocate/iterate/etc... if (!types.Any()) return; - var lockInfo = new WriteLockInfo(); - try + EnsureLocked(); + + var index = types.ToDictionary(x => x.Id, x => x); + + foreach (var type in index.Values) { - _logger.Debug("UpdateContentTypes"); - Lock(lockInfo); - - var index = types.ToDictionary(x => x.Id, x => x); - - foreach (var type in index.Values) - { - SetValueLocked(_contentTypesById, type.Id, type); - SetValueLocked(_contentTypesByAlias, type.Alias, type); - } - - foreach (var link in _contentNodes.Values) - { - var node = link.Value; - if (node == null) continue; - var contentTypeId = node.ContentType.Id; - if (index.TryGetValue(contentTypeId, out var contentType) == false) continue; - SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType)); - } + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); } - finally + + foreach (var link in _contentNodes.Values) { - Release(lockInfo); + var node = link.Value; + if (node == null) continue; + var contentTypeId = node.ContentType.Id; + if (index.TryGetValue(contentTypeId, out var contentType) == false) continue; + SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType)); } } @@ -1256,7 +1248,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // else we need to try to create a new gen ref // whether we are wlocked or not, noone can rlock while we do, // so _liveGen and _nextGen are safe - if (_wlocked > 0) // volatile, cannot ++ but could -- + if (Monitor.IsEntered(_wlocko)) { // write-locked, cannot use latest gen (at least 1) so use previous var snapGen = _nextGen ? _liveGen - 1 : _liveGen; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index b761fe0fa4..249eb882f9 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -622,7 +622,7 @@ namespace Umbraco.Web.PublishedCache.NuCache .Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false) .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard))) { - _domainStore.Set(domain.Id, domain); + _domainStore.SetLocked(domain.Id, domain); } } @@ -980,7 +980,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } break; case DomainChangeTypes.Remove: - _domainStore.Clear(payload.Id); + _domainStore.ClearLocked(payload.Id); break; case DomainChangeTypes.Refresh: var domain = _serviceContext.DomainService.GetById(payload.Id); @@ -988,7 +988,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (domain.RootContentId.HasValue == false) continue; // anomaly if (domain.LanguageIsoCode.IsNullOrWhiteSpace()) continue; // anomaly var culture = CultureInfo.GetCultureInfo(domain.LanguageIsoCode); - _domainStore.Set(domain.Id, new Domain(domain.Id, domain.DomainName, domain.RootContentId.Value, culture, domain.IsWildcard)); + _domainStore.SetLocked(domain.Id, new Domain(domain.Id, domain.DomainName, domain.RootContentId.Value, culture, domain.IsWildcard)); break; } } @@ -1075,9 +1075,9 @@ namespace Umbraco.Web.PublishedCache.NuCache _contentStore.UpdateContentTypes(removedIds, typesA, kits); if (!otherIds.IsCollectionEmpty()) - _contentStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray())); + _contentStore.UpdateContentTypesLocked(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray())); if (!newIds.IsCollectionEmpty()) - _contentStore.NewContentTypes(CreateContentTypes(PublishedItemType.Content, newIds.ToArray())); + _contentStore.NewContentTypesLocked(CreateContentTypes(PublishedItemType.Content, newIds.ToArray())); scope.Complete(); } } @@ -1106,9 +1106,9 @@ namespace Umbraco.Web.PublishedCache.NuCache _mediaStore.UpdateContentTypes(removedIds, typesA, kits); if (!otherIds.IsCollectionEmpty()) - _mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray()); + _mediaStore.UpdateContentTypesLocked(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray()); if (!newIds.IsCollectionEmpty()) - _mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray()); + _mediaStore.NewContentTypesLocked(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray()); scope.Complete(); } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs index e0ad26eb81..c38940da25 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs @@ -35,7 +35,6 @@ namespace Umbraco.Web.PublishedCache.NuCache private long _liveGen, _floorGen; private bool _nextGen, _collectAuto; private Task _collectTask; - private volatile int _wlocked; // minGenDelta to be adjusted // we may want to throttle collects even if delta is reached @@ -84,7 +83,6 @@ namespace Umbraco.Web.PublishedCache.NuCache private class WriteLockInfo { public bool Taken; - public bool Count; } // a scope contextual that represents a locked writer to the dictionary @@ -92,8 +90,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { private readonly WriteLockInfo _lockinfo = new WriteLockInfo(); private readonly SnapDictionary _dictionary; - private int _released; - + public ScopedWriteLock(SnapDictionary dictionary, bool scoped) { _dictionary = dictionary; @@ -102,8 +99,6 @@ namespace Umbraco.Web.PublishedCache.NuCache public override void Release(bool completed) { - if (Interlocked.CompareExchange(ref _released, 1, 0) != 0) - return; _dictionary.Release(_lockinfo, completed); } } @@ -117,8 +112,17 @@ namespace Umbraco.Web.PublishedCache.NuCache return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ScopedWriteLock(this, scoped)); } + private void EnsureLocked() + { + if (!Monitor.IsEntered(_wlocko)) + throw new InvalidOperationException("Write lock must be acquried."); + } + private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { + if (Monitor.IsEntered(_wlocko)) + throw new InvalidOperationException("Recursive locks not allowed"); + Monitor.Enter(_wlocko, ref lockInfo.Taken); lock(_rlocko) @@ -132,11 +136,7 @@ namespace Umbraco.Web.PublishedCache.NuCache try { } finally { - // increment the lock count, and register that this lock is counting - _wlocked++; - lockInfo.Count = true; - - if (_nextGen == false || (forceGen && _wlocked == 1)) + if (_nextGen == false || (forceGen)) { // because we are changing things, a new generation // is created, which will trigger a new snapshot @@ -181,8 +181,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - // decrement the lock count, if counting, then exit the lock - if (lockInfo.Count) _wlocked--; + // TODO: Shouldn't this be in a finally block? Monitor.Exit(_wlocko); } @@ -198,75 +197,59 @@ namespace Umbraco.Web.PublishedCache.NuCache return link; } - public void Set(TKey key, TValue value) + public void SetLocked(TKey key, TValue value) { - var lockInfo = new WriteLockInfo(); - try - { - Lock(lockInfo); + EnsureLocked(); - // this is safe only because we're write-locked - var link = GetHead(key); - if (link != null) + // this is safe only because we're write-locked + var link = GetHead(key); + if (link != null) + { + // already in the dict + if (link.Gen != _liveGen) { - // already in the dict - if (link.Gen != _liveGen) - { - // for an older gen - if value is different then insert a new - // link for the new gen, with the new value - if (link.Value != value) - _items.TryUpdate(key, new LinkedNode(value, _liveGen, link), link); - } - else - { - // for the live gen - we can fix the live gen - and remove it - // if value is null and there's no next gen - if (value == null && link.Next == null) - _items.TryRemove(key, out link); - else - link.Value = value; - } + // for an older gen - if value is different then insert a new + // link for the new gen, with the new value + if (link.Value != value) + _items.TryUpdate(key, new LinkedNode(value, _liveGen, link), link); } else { - _items.TryAdd(key, new LinkedNode(value, _liveGen)); - } - } - finally - { - Release(lockInfo); - } - } - - public void Clear(TKey key) - { - Set(key, null); - } - - public void Clear() - { - var lockInfo = new WriteLockInfo(); - try - { - Lock(lockInfo); - - // this is safe only because we're write-locked - foreach (var kvp in _items.Where(x => x.Value != null)) - { - if (kvp.Value.Gen < _liveGen) - { - var link = new LinkedNode(null, _liveGen, kvp.Value); - _items.TryUpdate(kvp.Key, link, kvp.Value); - } + // for the live gen - we can fix the live gen - and remove it + // if value is null and there's no next gen + if (value == null && link.Next == null) + _items.TryRemove(key, out link); else - { - kvp.Value.Value = null; - } + link.Value = value; } } - finally + else { - Release(lockInfo); + _items.TryAdd(key, new LinkedNode(value, _liveGen)); + } + } + + public void ClearLocked(TKey key) + { + SetLocked(key, null); + } + + public void ClearLocked() + { + EnsureLocked(); + + // this is safe only because we're write-locked + foreach (var kvp in _items.Where(x => x.Value != null)) + { + if (kvp.Value.Gen < _liveGen) + { + var link = new LinkedNode(null, _liveGen, kvp.Value); + _items.TryUpdate(kvp.Key, link, kvp.Value); + } + else + { + kvp.Value.Value = null; + } } } @@ -336,7 +319,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // else we need to try to create a new gen object // whether we are wlocked or not, noone can rlock while we do, // so _liveGen and _nextGen are safe - if (_wlocked > 0) // volatile, cannot ++ but could -- + if (Monitor.IsEntered(_wlocko)) { // write-locked, cannot use latest gen (at least 1) so use previous var snapGen = _nextGen ? _liveGen - 1 : _liveGen; @@ -468,17 +451,18 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public /*async*/ Task PendingCollect() - { - Task task; - lock (_rlocko) - { - task = _collectTask; - } - return task ?? Task.CompletedTask; - //if (task != null) - // await task; - } + // TODO: This is never used? Should it be? Maybe move to TestHelper below? + //public /*async*/ Task PendingCollect() + //{ + // Task task; + // lock (_rlocko) + // { + // task = _collectTask; + // } + // return task ?? Task.CompletedTask; + // //if (task != null) + // // await task; + //} public long GenCount => _genObjs.Count; @@ -503,7 +487,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public long LiveGen => _dict._liveGen; public long FloorGen => _dict._floorGen; public bool NextGen => _dict._nextGen; - public int WLocked => _dict._wlocked; + public bool IsLocked => Monitor.IsEntered(_dict._wlocko); public bool CollectAuto { From 7a129f890dc5b87094901d2ac3bbe953486be04b Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 3 Jan 2020 15:07:21 +1100 Subject: [PATCH 135/224] Changes MainDom testing timeout to be larger... but not 1.5 mins! --- src/Umbraco.Core/Runtime/MainDom.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index 883b69b2a7..d7bd407015 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core.Runtime // actions to run before releasing the main domain private readonly List> _callbacks = new List>(); - private const int LockTimeoutMilliseconds = 10000; // 10 seconds + private const int LockTimeoutMilliseconds = 40000; // 40 seconds #endregion @@ -151,22 +151,20 @@ namespace Umbraco.Core.Runtime { _logger.Info("Cannot acquire (timeout)."); - // TODO: Previously we'd throw an exception and the appdomain would not start, what do we want to do? - - // return false; + // TODO: In previous versions we'd let a TimeoutException be thrown + // and the appdomain would not start. We have the opportunity to allow it to + // start without having MainDom? This would mean that it couldn't write + // to nucache/examine and would only be ok if this was a super short lived appdomain. + // maybe safer to just keep throwing in this case. throw new TimeoutException("Cannot acquire MainDom"); + // return false; } try { // Listen for the signal from another AppDomain coming online to release the lock - _mainDomLock.ListenAsync() - .ContinueWith(_ => - { - _logger.Debug("Signal heard from other appdomain."); - OnSignal("signal"); - }); + _mainDomLock.ListenAsync().ContinueWith(_ => OnSignal("signal")); } catch (OperationCanceledException ex) { From c355e8f5f0938b2ef9eb5a7247df43728a7f61f0 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Sun, 5 Jan 2020 22:26:13 +0100 Subject: [PATCH 136/224] Use ExternalIndexName as default parameter value --- src/Umbraco.Web/IPublishedContentQuery.cs | 4 ++-- src/Umbraco.Web/PublishedContentQuery.cs | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs index c4d829968f..7066475dc9 100644 --- a/src/Umbraco.Web/IPublishedContentQuery.cs +++ b/src/Umbraco.Web/IPublishedContentQuery.cs @@ -49,7 +49,7 @@ namespace Umbraco.Web /// /// While enumerating results, the ambient culture is changed to be the searched culture. /// - IEnumerable Search(string term, string culture = "*", string indexName = null); + IEnumerable Search(string term, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName); /// /// Searches content. @@ -71,7 +71,7 @@ namespace Umbraco.Web /// /// While enumerating results, the ambient culture is changed to be the searched culture. /// - IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null); + IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName); /// /// Executes the query and converts the results to . diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 833df9971b..44a3208956 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -183,13 +183,13 @@ namespace Umbraco.Web #region Search /// - public IEnumerable Search(string term, string culture = "*", string indexName = null) + public IEnumerable Search(string term, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName) { return Search(term, 0, 0, out _, culture, indexName); } /// - public IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null) + public IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName) { if (string.IsNullOrEmpty(indexName)) { @@ -319,9 +319,6 @@ namespace Umbraco.Web } } - - - #endregion } } From 3bd7718c38e03212a7d9061be7b40e1061611786 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Sun, 5 Jan 2020 22:27:56 +0100 Subject: [PATCH 137/224] Remove ordering by score when converting to PublishedSearchResult --- src/Umbraco.Web/ExamineExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/ExamineExtensions.cs b/src/Umbraco.Web/ExamineExtensions.cs index 8fead91194..421993f8fd 100644 --- a/src/Umbraco.Web/ExamineExtensions.cs +++ b/src/Umbraco.Web/ExamineExtensions.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web var publishedSearchResults = new List(); - foreach (var result in results.OrderByDescending(x => x.Score)) + foreach (var result in results) { if (int.TryParse(result.Id, out var contentId) && cache.GetById(contentId) is IPublishedContent content) @@ -62,7 +62,7 @@ namespace Umbraco.Web var publishedSearchResults = new List(); - foreach (var result in results.OrderByDescending(x => x.Score)) + foreach (var result in results) { if (int.TryParse(result.Id, out var contentId) && result.Values.TryGetValue(LuceneIndex.CategoryFieldName, out var indexType)) From fa23b50d1ee3248499591ffb781d2486d6fa06fe Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Sun, 5 Jan 2020 22:55:55 +0100 Subject: [PATCH 138/224] Add skip and take argument validation --- src/Umbraco.Web/PublishedContentQuery.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 44a3208956..d697898f33 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -191,6 +191,16 @@ namespace Umbraco.Web /// public IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName) { + if (skip < 0) + { + throw new ArgumentOutOfRangeException(nameof(skip), skip, "The value must be greater than or equal to zero."); + } + + if (take < 0) + { + throw new ArgumentOutOfRangeException(nameof(take), take, "The value must be greater than or equal to zero."); + } + if (string.IsNullOrEmpty(indexName)) { indexName = Constants.UmbracoIndexes.ExternalIndexName; @@ -240,6 +250,16 @@ namespace Umbraco.Web /// public IEnumerable Search(IQueryExecutor query, int skip, int take, out long totalRecords) { + if (skip < 0) + { + throw new ArgumentOutOfRangeException(nameof(skip), skip, "The value must be greater than or equal to zero."); + } + + if (take < 0) + { + throw new ArgumentOutOfRangeException(nameof(take), take, "The value must be greater than or equal to zero."); + } + var results = skip == 0 && take == 0 ? query.Execute() : query.Execute(skip + take); From c2ac5e85311b39ee0d9c8734659a39fb6be55ab9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 6 Jan 2020 18:34:04 +1100 Subject: [PATCH 139/224] Fixes a couple more issues with recursive locks --- .../PublishedCache/NuCache/ContentStore.cs | 244 +++++++++--------- .../NuCache/PublishedSnapshotService.cs | 8 +- 2 files changed, 130 insertions(+), 122 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 17eb2b6457..8ce299932c 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -14,7 +14,18 @@ using Umbraco.Web.PublishedCache.NuCache.Snap; namespace Umbraco.Web.PublishedCache.NuCache { - // stores content + /// + /// Stores content in memory and persists it back to disk + /// + /// + /// + /// Methods in this class suffixed with the term "Locked" means that those methods can only be called within a WriteLock. A WriteLock + /// is acquired by the GetScopedWriteLock method. Locks are not allowed to be recursive. + /// + /// + /// This class's logic is based on the class but has been slightly modified to suit these purposes. + /// + /// internal class ContentStore { // this class is an extended version of SnapDictionary @@ -336,8 +347,6 @@ namespace Umbraco.Web.PublishedCache.NuCache { EnsureLocked(); - _logger.Debug("SetAllContentTypes"); - // clear all existing content types ClearLocked(_contentTypesById); ClearLocked(_contentTypesByAlias); @@ -353,8 +362,23 @@ namespace Umbraco.Web.PublishedCache.NuCache // assuming we are going to SetAll content items! } - public void UpdateContentTypes(IReadOnlyCollection removedIds, IReadOnlyCollection refreshedTypes, IReadOnlyCollection kits) + /// + /// Updates/sets/removes data for content types + /// + /// + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public void UpdateContentTypesLocked(IReadOnlyCollection removedIds, IReadOnlyCollection refreshedTypes, IReadOnlyCollection kits) { + EnsureLocked(); + var removedIdsA = removedIds ?? Array.Empty(); var refreshedTypesA = refreshedTypes ?? Array.Empty(); var refreshedIdsA = refreshedTypesA.Select(x => x.Id).ToList(); @@ -363,87 +387,82 @@ namespace Umbraco.Web.PublishedCache.NuCache if (kits.Count == 0 && refreshedIdsA.Count == 0 && removedIdsA.Count == 0) return; //exit - there is nothing to do here - var lockInfo = new WriteLockInfo(); - try + var removedContentTypeNodes = new List(); + var refreshedContentTypeNodes = new List(); + + // find all the nodes that are either refreshed or removed, + // because of their content type being either refreshed or removed + foreach (var link in _contentNodes.Values) { - _logger.Debug("UpdateContentTypes"); - Lock(lockInfo); - - var removedContentTypeNodes = new List(); - var refreshedContentTypeNodes = new List(); - - // find all the nodes that are either refreshed or removed, - // because of their content type being either refreshed or removed - foreach (var link in _contentNodes.Values) - { - var node = link.Value; - if (node == null) continue; - var contentTypeId = node.ContentType.Id; - if (removedIdsA.Contains(contentTypeId)) removedContentTypeNodes.Add(node.Id); - if (refreshedIdsA.Contains(contentTypeId)) refreshedContentTypeNodes.Add(node.Id); - } - - // perform deletion of content with removed content type - // removing content types should have removed their content already - // but just to be 100% sure, clear again here - foreach (var node in removedContentTypeNodes) - ClearBranchLocked(node); - - // perform deletion of removed content types - foreach (var id in removedIdsA) - { - if (_contentTypesById.TryGetValue(id, out var link) == false || link.Value == null) - continue; - SetValueLocked(_contentTypesById, id, null); - SetValueLocked(_contentTypesByAlias, link.Value.Alias, null); - } - - // perform update of refreshed content types - foreach (var type in refreshedTypesA) - { - SetValueLocked(_contentTypesById, type.Id, type); - SetValueLocked(_contentTypesByAlias, type.Alias, type); - } - - // perform update of content with refreshed content type - from the kits - // skip missing type, skip missing parents & un-buildable kits - what else could we do? - // kits are ordered by level, so ParentExists is ok here - var visited = new List(); - foreach (var kit in kits.Where(x => - refreshedIdsA.Contains(x.ContentTypeId) && - BuildKit(x, out _))) - { - // replacing the node: must preserve the parents - var node = GetHead(_contentNodes, kit.Node.Id)?.Value; - if (node != null) - kit.Node.FirstChildContentId = node.FirstChildContentId; - - SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); - - visited.Add(kit.Node.Id); - if (_localDb != null) RegisterChange(kit.Node.Id, kit); - } - - // all content should have been refreshed - but... - var orphans = refreshedContentTypeNodes.Except(visited); - foreach (var id in orphans) - ClearBranchLocked(id); + var node = link.Value; + if (node == null) continue; + var contentTypeId = node.ContentType.Id; + if (removedIdsA.Contains(contentTypeId)) removedContentTypeNodes.Add(node.Id); + if (refreshedIdsA.Contains(contentTypeId)) refreshedContentTypeNodes.Add(node.Id); } - finally + + // perform deletion of content with removed content type + // removing content types should have removed their content already + // but just to be 100% sure, clear again here + foreach (var node in removedContentTypeNodes) + ClearBranchLocked(node); + + // perform deletion of removed content types + foreach (var id in removedIdsA) { - Release(lockInfo); + if (_contentTypesById.TryGetValue(id, out var link) == false || link.Value == null) + continue; + SetValueLocked(_contentTypesById, id, null); + SetValueLocked(_contentTypesByAlias, link.Value.Alias, null); } + + // perform update of refreshed content types + foreach (var type in refreshedTypesA) + { + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); + } + + // perform update of content with refreshed content type - from the kits + // skip missing type, skip missing parents & un-buildable kits - what else could we do? + // kits are ordered by level, so ParentExists is ok here + var visited = new List(); + foreach (var kit in kits.Where(x => + refreshedIdsA.Contains(x.ContentTypeId) && + BuildKit(x, out _))) + { + // replacing the node: must preserve the parents + var node = GetHead(_contentNodes, kit.Node.Id)?.Value; + if (node != null) + kit.Node.FirstChildContentId = node.FirstChildContentId; + + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + + visited.Add(kit.Node.Id); + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + } + + // all content should have been refreshed - but... + var orphans = refreshedContentTypeNodes.Except(visited); + foreach (var id in orphans) + ClearBranchLocked(id); } - public void UpdateDataTypes(IEnumerable dataTypeIds, Func getContentType) + /// + /// Updates data types + /// + /// + /// + /// + /// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock + /// otherwise an exception will occur. + /// + /// + /// Thrown if this method is not called within a write lock + /// + public void UpdateDataTypesLocked(IEnumerable dataTypeIds, Func getContentType) { - var lockInfo = new WriteLockInfo(); - try - { - _logger.Debug("UpdateDataTypes"); - Lock(lockInfo); - - var contentTypes = _contentTypesById + var contentTypes = _contentTypesById .Where(kvp => kvp.Value.Value != null && kvp.Value.Value.PropertyTypes.Any(p => dataTypeIds.Contains(p.DataType.Id))) @@ -452,37 +471,32 @@ namespace Umbraco.Web.PublishedCache.NuCache .Where(x => x != null) // poof, gone, very unlikely and probably an anomaly .ToArray(); - var contentTypeIdsA = contentTypes.Select(x => x.Id).ToArray(); - var contentTypeNodes = new Dictionary>(); - foreach (var id in contentTypeIdsA) - contentTypeNodes[id] = new List(); - foreach (var link in _contentNodes.Values) - { - var node = link.Value; - if (node != null && contentTypeIdsA.Contains(node.ContentType.Id)) - contentTypeNodes[node.ContentType.Id].Add(node.Id); - } - - foreach (var contentType in contentTypes) - { - // again, weird situation - if (contentTypeNodes.ContainsKey(contentType.Id) == false) - continue; - - foreach (var id in contentTypeNodes[contentType.Id]) - { - _contentNodes.TryGetValue(id, out var link); - if (link?.Value == null) - continue; - var node = new ContentNode(link.Value, contentType); - SetValueLocked(_contentNodes, id, node); - if (_localDb != null) RegisterChange(id, node.ToKit()); - } - } - } - finally + var contentTypeIdsA = contentTypes.Select(x => x.Id).ToArray(); + var contentTypeNodes = new Dictionary>(); + foreach (var id in contentTypeIdsA) + contentTypeNodes[id] = new List(); + foreach (var link in _contentNodes.Values) { - Release(lockInfo); + var node = link.Value; + if (node != null && contentTypeIdsA.Contains(node.ContentType.Id)) + contentTypeNodes[node.ContentType.Id].Add(node.Id); + } + + foreach (var contentType in contentTypes) + { + // again, weird situation + if (contentTypeNodes.ContainsKey(contentType.Id) == false) + continue; + + foreach (var id in contentTypeNodes[contentType.Id]) + { + _contentNodes.TryGetValue(id, out var link); + if (link?.Value == null) + continue; + var node = new ContentNode(link.Value, contentType); + SetValueLocked(_contentNodes, id, node); + if (_localDb != null) RegisterChange(id, node.ToKit()); + } } } @@ -644,8 +658,6 @@ namespace Umbraco.Web.PublishedCache.NuCache { EnsureLocked(); - _logger.Debug("SetAllFastSorted"); - var ok = true; ClearLocked(_contentNodes); @@ -684,7 +696,7 @@ namespace Umbraco.Web.PublishedCache.NuCache previousNode = null; // there is no previous sibling } - _logger.Verbose($"Set {thisNode.Id} with parent {thisNode.ParentContentId}"); + _logger.Debug($"Set {thisNode.Id} with parent {thisNode.ParentContentId}"); SetValueLocked(_contentNodes, thisNode.Id, thisNode); // if we are initializing from the database source ensure the local db is updated @@ -726,8 +738,7 @@ namespace Umbraco.Web.PublishedCache.NuCache EnsureLocked(); var ok = true; - _logger.Debug("SetAll"); - + ClearLocked(_contentNodes); ClearRootLocked(); @@ -742,7 +753,7 @@ namespace Umbraco.Web.PublishedCache.NuCache ok = false; continue; // skip that one } - _logger.Verbose($"Set {kit.Node.Id} with parent {kit.Node.ParentContentId}"); + _logger.Debug($"Set {kit.Node.Id} with parent {kit.Node.ParentContentId}"); SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); if (_localDb != null) RegisterChange(kit.Node.Id, kit); @@ -777,8 +788,7 @@ namespace Umbraco.Web.PublishedCache.NuCache EnsureLocked(); var ok = true; - _logger.Debug("SetBranch"); - + // get existing _contentNodes.TryGetValue(rootContentId, out var link); var existing = link?.Value; @@ -826,8 +836,6 @@ namespace Umbraco.Web.PublishedCache.NuCache { EnsureLocked(); - _logger.Debug("Clear"); - // try to find the content // if it is not there, nothing to do _contentNodes.TryGetValue(id, out var link); // else null diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 249eb882f9..c37e5731e4 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -943,14 +943,14 @@ namespace Umbraco.Web.PublishedCache.NuCache using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.ContentTree); - _contentStore.UpdateDataTypes(idsA, id => CreateContentType(PublishedItemType.Content, id)); + _contentStore.UpdateDataTypesLocked(idsA, id => CreateContentType(PublishedItemType.Content, id)); scope.Complete(); } using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.MediaTree); - _mediaStore.UpdateDataTypes(idsA, id => CreateContentType(PublishedItemType.Media, id)); + _mediaStore.UpdateDataTypesLocked(idsA, id => CreateContentType(PublishedItemType.Media, id)); scope.Complete(); } } @@ -1073,7 +1073,7 @@ namespace Umbraco.Web.PublishedCache.NuCache ? Array.Empty() : _dataSource.GetTypeContentSources(scope, refreshedIds).ToArray(); - _contentStore.UpdateContentTypes(removedIds, typesA, kits); + _contentStore.UpdateContentTypesLocked(removedIds, typesA, kits); if (!otherIds.IsCollectionEmpty()) _contentStore.UpdateContentTypesLocked(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray())); if (!newIds.IsCollectionEmpty()) @@ -1104,7 +1104,7 @@ namespace Umbraco.Web.PublishedCache.NuCache ? Array.Empty() : _dataSource.GetTypeMediaSources(scope, refreshedIds).ToArray(); - _mediaStore.UpdateContentTypes(removedIds, typesA, kits); + _mediaStore.UpdateContentTypesLocked(removedIds, typesA, kits); if (!otherIds.IsCollectionEmpty()) _mediaStore.UpdateContentTypesLocked(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray()); if (!newIds.IsCollectionEmpty()) From 94d05b1768bfc2fdc0b6fd8dc11d885d3281d63e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 6 Jan 2020 21:14:46 +1100 Subject: [PATCH 140/224] Fixes #7404 - Deadlock issue --- .../PublishedCache/NuCache/PublishedSnapshotService.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 80662f5db0..6e6ad7b79f 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -54,6 +54,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly ContentStore _mediaStore; private readonly SnapDictionary _domainStore; private readonly object _storesLock = new object(); + private readonly object _elementsLock = new object(); private BPlusTree _localContentDb; private BPlusTree _localMediaDb; @@ -1125,7 +1126,13 @@ namespace Umbraco.Web.PublishedCache.NuCache ContentStore.Snapshot contentSnap, mediaSnap; SnapDictionary.Snapshot domainSnap; IAppCache elementsCache; - lock (_storesLock) + + // Here we are reading/writing to shared objects so we need to lock (can't be _storesLock which manages the actual nucache files + // and would result in a deadlock). Even though we are locking around underlying readlocks (within CreateSnapshot) it's because + // we need to ensure that the result of contentSnap.Gen (etc) and the re-assignment of these values and _elements cache + // are done atomically. + + lock (_elementsLock) { var scopeContext = _scopeProvider.Context; From 95337d5a7008469a1cfc84215b9c98e461798fe1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 6 Jan 2020 21:39:26 +1100 Subject: [PATCH 141/224] Cleans up old notes --- src/Umbraco.Core/Runtime/MainDom.cs | 2 +- .../Cache/SnapDictionaryTests.cs | 82 +++++++++---------- .../PublishedCache/NuCache/ContentStore.cs | 60 +++++++------- .../NuCache/PublishedSnapshot.cs | 8 -- .../NuCache/PublishedSnapshotService.cs | 10 +-- 5 files changed, 73 insertions(+), 89 deletions(-) diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index d7bd407015..67c7be3d21 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -151,7 +151,7 @@ namespace Umbraco.Core.Runtime { _logger.Info("Cannot acquire (timeout)."); - // TODO: In previous versions we'd let a TimeoutException be thrown + // In previous versions we'd let a TimeoutException be thrown // and the appdomain would not start. We have the opportunity to allow it to // start without having MainDom? This would mean that it couldn't write // to nucache/examine and would only be ok if this was a super short lived appdomain. diff --git a/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs b/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs index 86426d196c..00ba721bdb 100644 --- a/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs +++ b/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs @@ -9,47 +9,6 @@ using Umbraco.Web.PublishedCache.NuCache.Snap; namespace Umbraco.Tests.Cache { - /// - /// Used for tests - /// - public static class SnapDictionaryExtensions - { - internal static void Set(this SnapDictionary d, TKey key, TValue value) - where TValue : class - { - using (d.GetScopedWriteLock(GetScopeProvider())) - { - d.SetLocked(key, value); - } - } - - internal static void Clear(this SnapDictionary d) - where TValue : class - { - using (d.GetScopedWriteLock(GetScopeProvider())) - { - d.ClearLocked(); - } - } - - internal static void Clear(this SnapDictionary d, TKey key) - where TValue : class - { - using (d.GetScopedWriteLock(GetScopeProvider())) - { - d.ClearLocked(key); - } - } - - private static IScopeProvider GetScopeProvider() - { - var scopeProvider = Mock.Of(); - Mock.Get(scopeProvider) - .Setup(x => x.Context).Returns(() => null); - return scopeProvider; - } - } - [TestFixture] public class SnapDictionaryTests { @@ -1184,4 +1143,45 @@ namespace Umbraco.Tests.Cache return scopeProvider; } } + + /// + /// Used for tests so that we don't have to wrap every Set/Clear call in locks + /// + public static class SnapDictionaryExtensions + { + internal static void Set(this SnapDictionary d, TKey key, TValue value) + where TValue : class + { + using (d.GetScopedWriteLock(GetScopeProvider())) + { + d.SetLocked(key, value); + } + } + + internal static void Clear(this SnapDictionary d) + where TValue : class + { + using (d.GetScopedWriteLock(GetScopeProvider())) + { + d.ClearLocked(); + } + } + + internal static void Clear(this SnapDictionary d, TKey key) + where TValue : class + { + using (d.GetScopedWriteLock(GetScopeProvider())) + { + d.ClearLocked(key); + } + } + + private static IScopeProvider GetScopeProvider() + { + var scopeProvider = Mock.Of(); + Mock.Get(scopeProvider) + .Setup(x => x.Context).Returns(() => null); + return scopeProvider; + } + } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 8ce299932c..c959d9f67a 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -157,40 +157,44 @@ namespace Umbraco.Web.PublishedCache.NuCache private void Release(WriteLockInfo lockInfo, bool commit = true) { - if (commit == false) + try { - lock(_rlocko) + if (commit == false) { - // see SnapDictionary - try { } - finally + lock (_rlocko) { - _nextGen = false; - _liveGen -= 1; + // see SnapDictionary + try { } + finally + { + _nextGen = false; + _liveGen -= 1; + } } - } - Rollback(_contentNodes); - RollbackRoot(); - Rollback(_contentTypesById); - Rollback(_contentTypesByAlias); - } - else if (_localDb != null && _wchanges != null) - { - foreach (var change in _wchanges) + Rollback(_contentNodes); + RollbackRoot(); + Rollback(_contentTypesById); + Rollback(_contentTypesByAlias); + } + else if (_localDb != null && _wchanges != null) { - if (change.Value.IsNull) - _localDb.TryRemove(change.Key, out ContentNodeKit unused); - else - _localDb[change.Key] = change.Value; + foreach (var change in _wchanges) + { + if (change.Value.IsNull) + _localDb.TryRemove(change.Key, out ContentNodeKit unused); + else + _localDb[change.Key] = change.Value; + } + _wchanges = null; + _localDb.Commit(); } - _wchanges = null; - _localDb.Commit(); } - - // TODO: Shouldn't this be in a finally block? - if (lockInfo.Taken) - Monitor.Exit(_wlocko); + finally + { + if (lockInfo.Taken) + Monitor.Exit(_wlocko); + } } private void RollbackRoot() @@ -256,10 +260,6 @@ namespace Umbraco.Web.PublishedCache.NuCache } finally { - _logger.Info("Releasing ContentStore..."); - - // TODO: I don't understand this, we would have already closed the BPlusTree store above?? - // I guess it's 'safe' and will just decrement the write lock counter? Release(lockInfo); } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs index bbb84fc650..3f5c1aa4d5 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs @@ -39,15 +39,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public void Resync() { - // This is annoying since this can cause deadlocks. Since this will be cleared if something happens in the same - // thread after that calls Elements, it will re-lock the _storesLock but that might already be locked again - // and since we're most likely in a ContentStore write lock, the other thread is probably wanting that one too. - // no lock - published snapshots are single-thread - - // TODO: Instead of clearing, we could hold this value in another var in case the above call to GetElements() Or potentially - // a new TryGetElements() fails (since we might be shutting down). In that case we can use the old value. - _elements?.Dispose(); _elements = null; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index c37e5731e4..aee588d567 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -670,15 +670,7 @@ namespace Umbraco.Web.PublishedCache.NuCache publishedChanged = publishedChanged2; } - // TODO: These resync's are a problem, they cause deadlocks because when this is called, it's generally called within a writelock - // and then we clear out the snapshot and then if there's some event handler that needs the content cache, it tries to re-get it - // which first tries locking on the _storesLock which may have already been acquired and in this case we deadlock because - // we're still holding the write lock. - // We resync at the end of a ScopedWriteLock - // BUT if we don't resync here then the current snapshot is out of date for any event handlers that wish to use the most up to date - // data... - // so we need to figure out how to deal with the _storesLock - + if (draftChanged || publishedChanged) ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); } From 24afe3f130e81155af894081d5c5b1a495533cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Tue, 7 Jan 2020 09:34:12 +0100 Subject: [PATCH 142/224] Changed ArgumentException to InvalidOperationException --- src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs index d16f244a0a..864928d06d 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs @@ -56,7 +56,7 @@ namespace Umbraco.Web.Models.Mapping if (languages.Count == 0 || segments.Count == 0) { // This should not happen - throw new ArgumentException("No languages or segments available"); + throw new InvalidOperationException("No languages or segments available"); } variants = languages From 838b26cedb4cd75d6e6cf25bf8926699142b1742 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 7 Jan 2020 13:04:52 +0100 Subject: [PATCH 143/224] bump version to 8.4.1 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 5614cb8c41..0da90a03fd 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.4.0")] -[assembly: AssemblyInformationalVersion("8.4.0")] +[assembly: AssemblyFileVersion("8.4.1")] +[assembly: AssemblyInformationalVersion("8.4.1")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 65055bd8e4..3bdf9b5618 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -347,9 +347,9 @@ False True - 8400 + 8410 / - http://localhost:8400/ + http://localhost:8410 False False @@ -431,4 +431,4 @@ - + \ No newline at end of file From cae2c3217296157d83a559fc1427ac99112a1520 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 7 Jan 2020 20:32:04 +0100 Subject: [PATCH 144/224] Remove SetFlag and UnsetFlag extension methods --- src/Umbraco.Core/EnumExtensions.cs | 38 ----------------- .../CoreThings/EnumExtensionsTests.cs | 42 ------------------- .../Mapping/ContentTypeMapDefinition.cs | 10 ++--- 3 files changed, 5 insertions(+), 85 deletions(-) diff --git a/src/Umbraco.Core/EnumExtensions.cs b/src/Umbraco.Core/EnumExtensions.cs index b2e5f32c9a..1443a27876 100644 --- a/src/Umbraco.Core/EnumExtensions.cs +++ b/src/Umbraco.Core/EnumExtensions.cs @@ -41,43 +41,5 @@ 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 4a0c1d0f41..72a55cee85 100644 --- a/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs @@ -51,47 +51,5 @@ namespace Umbraco.Tests.CoreThings else Assert.IsFalse(value.HasFlagAny(test)); } - - [TestCase(TreeUse.None, TreeUse.None, TreeUse.None)] - [TestCase(TreeUse.None, TreeUse.Main, TreeUse.Main)] - [TestCase(TreeUse.None, TreeUse.Dialog, TreeUse.Dialog)] - [TestCase(TreeUse.None, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main, TreeUse.None, TreeUse.Main)] - [TestCase(TreeUse.Main, TreeUse.Main, TreeUse.Main)] - [TestCase(TreeUse.Main, TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Dialog, TreeUse.None, TreeUse.Dialog)] - [TestCase(TreeUse.Dialog, TreeUse.Main, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Dialog, TreeUse.Dialog, TreeUse.Dialog)] - [TestCase(TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.None, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] - public void SetFlagTests(TreeUse value, TreeUse flag, TreeUse expected) - { - Assert.AreEqual(expected, value.SetFlag(flag)); - } - - [TestCase(TreeUse.None, TreeUse.None, TreeUse.None)] - [TestCase(TreeUse.None, TreeUse.Main, TreeUse.None)] - [TestCase(TreeUse.None, TreeUse.Dialog, TreeUse.None)] - [TestCase(TreeUse.None, TreeUse.Main | TreeUse.Dialog, TreeUse.None)] - [TestCase(TreeUse.Main, TreeUse.None, TreeUse.Main)] - [TestCase(TreeUse.Main, TreeUse.Main, TreeUse.None)] - [TestCase(TreeUse.Main, TreeUse.Dialog, TreeUse.Main)] - [TestCase(TreeUse.Main, TreeUse.Main | TreeUse.Dialog, TreeUse.None)] - [TestCase(TreeUse.Dialog, TreeUse.None, TreeUse.Dialog)] - [TestCase(TreeUse.Dialog, TreeUse.Main, TreeUse.Dialog)] - [TestCase(TreeUse.Dialog, TreeUse.Dialog, TreeUse.None)] - [TestCase(TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.None)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.None, TreeUse.Main | TreeUse.Dialog)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main, TreeUse.Dialog)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Dialog, TreeUse.Main)] - [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.None)] - public void UnsetFlagTests(TreeUse value, TreeUse flag, TreeUse expected) - { - Assert.AreEqual(expected, value.UnsetFlag(flag)); - } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index 95101da4e3..cdd534a14f 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -226,8 +226,8 @@ namespace Umbraco.Web.Models.Mapping target.ValidationRegExp = source.Validation.Pattern; target.ValidationRegExpMessage = source.Validation.PatternMessage; target.Variations = source.AllowCultureVariant - ? target.Variations.SetFlag(ContentVariation.Culture) - : target.Variations.UnsetFlag(ContentVariation.Culture); + ? target.Variations | ContentVariation.Culture // Set flag using bitwise logical OR + : target.Variations & ~ContentVariation.Culture; // Remove flag using bitwise logical AND with bitwise complement (reversing the bit) if (source.Id > 0) target.Id = source.Id; @@ -399,9 +399,9 @@ namespace Umbraco.Web.Models.Mapping if (!(target is IMemberType)) { - target.Variations = source.AllowCultureVariant - ? target.Variations.SetFlag(ContentVariation.Culture) - : target.Variations.UnsetFlag(ContentVariation.Culture); + target.Variations = source.AllowCultureVariant + ? target.Variations | ContentVariation.Culture // Set flag using bitwise logical OR + : target.Variations & ~ContentVariation.Culture; // Remove flag using bitwise logical AND with bitwise complement (reversing the bit) } // handle property groups and property types From ec4bc11c1b1163790d0ef705ae0ef2e573371948 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 7 Jan 2020 20:35:00 +0100 Subject: [PATCH 145/224] Obsolete HasFlagAll extension method --- src/Umbraco.Core/EnumExtensions.cs | 45 +++++++++---------- .../CoreThings/EnumExtensionsTests.cs | 6 ++- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Core/EnumExtensions.cs b/src/Umbraco.Core/EnumExtensions.cs index 1443a27876..9097432f64 100644 --- a/src/Umbraco.Core/EnumExtensions.cs +++ b/src/Umbraco.Core/EnumExtensions.cs @@ -3,43 +3,42 @@ namespace Umbraco.Core { /// - /// Provides extension methods to enums. + /// Provides extension methods to . /// public static class EnumExtensions { - // note: - // - no need to HasFlagExact, that's basically an == test - // - HasFlagAll cannot be named HasFlag because ext. methods never take priority over instance methods - /// - /// Determines whether a flag enum has all the specified values. + /// Determines whether all the flags/bits are set within the enum value. /// - /// - /// True when all bits set in are set in , though other bits may be set too. - /// This is the behavior of the original method. - /// - public static bool HasFlagAll(this T use, T uses) + /// The enum type. + /// The enum value. + /// The flags. + /// + /// true if all the flags/bits are set within the enum value; otherwise, false. + /// + [Obsolete("Use Enum.HasFlag() or bitwise operations (if performance is important) instead.")] + public static bool HasFlagAll(this T value, T flags) where T : Enum { - var num = Convert.ToUInt64(use); - var nums = Convert.ToUInt64(uses); - - return (num & nums) == nums; + return value.HasFlag(flags); } /// - /// Determines whether a flag enum has any of the specified values. + /// Determines whether any of the flags/bits are set within the enum value. /// - /// - /// True when at least one of the bits set in is set in . - /// - public static bool HasFlagAny(this T use, T uses) + /// The enum type. + /// The value. + /// The flags. + /// + /// true if any of the flags/bits are set within the enum value; otherwise, false. + /// + public static bool HasFlagAny(this T value, T flags) where T : Enum { - var num = Convert.ToUInt64(use); - var nums = Convert.ToUInt64(uses); + var v = Convert.ToUInt64(value); + var f = Convert.ToUInt64(flags); - return (num & nums) > 0; + return (v & f) > 0; } } } diff --git a/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs index 72a55cee85..faa15b0077 100644 --- a/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs @@ -1,6 +1,7 @@ -using NUnit.Framework; -using Umbraco.Web.Trees; +using System; +using NUnit.Framework; using Umbraco.Core; +using Umbraco.Web.Trees; namespace Umbraco.Tests.CoreThings { @@ -22,6 +23,7 @@ namespace Umbraco.Tests.CoreThings Assert.IsFalse(value.HasFlag(test)); } + [Obsolete] [TestCase(TreeUse.Dialog, TreeUse.Dialog, true)] [TestCase(TreeUse.Dialog, TreeUse.Main, false)] [TestCase(TreeUse.Dialog | TreeUse.Main, TreeUse.Dialog, true)] From c38240c872428b366cd716c34c8ce468ced91271 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 9 Jan 2020 09:35:37 +0100 Subject: [PATCH 146/224] Add SetVariesBy extension methods for ContentVariation --- .../ContentVariationExtensions.cs | 32 +++++++++++++++++++ .../Mapping/ContentTypeMapDefinition.cs | 12 +++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index 5b157307ab..0b6b3b5c12 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -195,5 +195,37 @@ namespace Umbraco.Core return true; } + + /// + /// Sets or removes the content type variation depending on the specified value. + /// + /// The content type. + /// The variation to set or remove. + /// If set to true sets the variation; otherwise, removes the variation. + public static void SetVariesBy(this IContentTypeBase contentType, ContentVariation variation, bool value = true) => contentType.Variations = contentType.Variations.SetVariesBy(variation, value); + + /// + /// Sets or removes the property type variation depending on the specified value. + /// + /// The property type. + /// The variation to set or remove. + /// If set to true sets the variation; otherwise, removes the variation. + public static void SetVariesBy(this PropertyType propertyType, ContentVariation variation, bool value = true) => propertyType.Variations = propertyType.Variations.SetVariesBy(variation, value); + + /// + /// Sets or removes the variation depending on the specified value. + /// + /// The existing variations. + /// The variation to set or remove. + /// If set to true sets the variation; otherwise, removes the variation. + /// + /// The variations with the specified variation set or removed. + /// + public static ContentVariation SetVariesBy(this ContentVariation variations, ContentVariation variation, bool value = true) + { + return value + ? variations | variation // Set flag using bitwise logical OR + : variations & ~variation; // Remove flag using bitwise logical AND with bitwise complement (reversing the bit) + } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index cdd534a14f..06155d6888 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -215,7 +215,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate - // Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType + // Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType -Variations private static void Map(PropertyTypeBasic source, PropertyType target, MapperContext context) { target.Name = source.Label; @@ -225,9 +225,7 @@ namespace Umbraco.Web.Models.Mapping target.MandatoryMessage = source.Validation.MandatoryMessage; target.ValidationRegExp = source.Validation.Pattern; target.ValidationRegExpMessage = source.Validation.PatternMessage; - target.Variations = source.AllowCultureVariant - ? target.Variations | ContentVariation.Culture // Set flag using bitwise logical OR - : target.Variations & ~ContentVariation.Culture; // Remove flag using bitwise logical AND with bitwise complement (reversing the bit) + target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); if (source.Id > 0) target.Id = source.Id; @@ -369,7 +367,7 @@ namespace Umbraco.Web.Models.Mapping target.Validation = source.Validation; } - // Umbraco.Code.MapAll -CreatorId -Level -SortOrder + // Umbraco.Code.MapAll -CreatorId -Level -SortOrder -Variations // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate // Umbraco.Code.MapAll -ContentTypeComposition (done by AfterMapSaveToType) private static void MapSaveToTypeBase(TSource source, IContentTypeComposition target, MapperContext context) @@ -399,9 +397,7 @@ namespace Umbraco.Web.Models.Mapping if (!(target is IMemberType)) { - target.Variations = source.AllowCultureVariant - ? target.Variations | ContentVariation.Culture // Set flag using bitwise logical OR - : target.Variations & ~ContentVariation.Culture; // Remove flag using bitwise logical AND with bitwise complement (reversing the bit) + target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); } // handle property groups and property types From babe1aa60aa5518967b7be7cc60a00e1517eb9aa Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 9 Jan 2020 09:50:33 +0100 Subject: [PATCH 147/224] Add documentation and clean code --- .../ContentVariationExtensions.cs | 115 +++++++++++++++--- 1 file changed, 100 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index 0b6b3b5c12..b3688b08b4 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -12,119 +12,199 @@ namespace Umbraco.Core /// /// Determines whether the content type is invariant. /// + /// The content type. + /// + /// A value indicating whether the content type is invariant. + /// public static bool VariesByNothing(this ISimpleContentType contentType) => contentType.Variations.VariesByNothing(); /// /// Determines whether the content type varies by culture. /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture. + /// public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture(); /// /// Determines whether the content type is invariant. /// + /// The content type. + /// + /// A value indicating whether the content type is invariant. + /// public static bool VariesByNothing(this IContentTypeBase contentType) => contentType.Variations.VariesByNothing(); /// /// Determines whether the content type varies by culture. /// - /// And then it could also vary by segment. + /// The content type. + /// + /// A value indicating whether the content type varies by culture. + /// public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture(); /// /// Determines whether the content type varies by segment. /// - /// And then it could also vary by culture. + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment(); /// /// Determines whether the content type varies by culture and segment. /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture and segment. + /// public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => contentType.Variations.VariesByCultureAndSegment(); /// /// Determines whether the property type is invariant. /// + /// The property type. + /// + /// A value indicating whether the property type is invariant. + /// public static bool VariesByNothing(this PropertyType propertyType) => propertyType.Variations.VariesByNothing(); /// /// Determines whether the property type varies by culture. /// - /// And then it could also vary by segment. + /// The property type. + /// + /// A value indicating whether the property type varies by culture. + /// public static bool VariesByCulture(this PropertyType propertyType) => propertyType.Variations.VariesByCulture(); /// /// Determines whether the property type varies by segment. /// - /// And then it could also vary by culture. + /// The property type. + /// + /// A value indicating whether the property type varies by segment. + /// public static bool VariesBySegment(this PropertyType propertyType) => propertyType.Variations.VariesBySegment(); /// /// Determines whether the property type varies by culture and segment. /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture and segment. + /// public static bool VariesByCultureAndSegment(this PropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); /// /// Determines whether the content type is invariant. /// + /// The content type. + /// + /// A value indicating whether the content type is invariant. + /// public static bool VariesByNothing(this IPublishedContentType contentType) => contentType.Variations.VariesByNothing(); /// /// Determines whether the content type varies by culture. /// - /// And then it could also vary by segment. + /// The content type. + /// + /// A value indicating whether the content type varies by culture. + /// public static bool VariesByCulture(this IPublishedContentType contentType) => contentType.Variations.VariesByCulture(); /// /// Determines whether the content type varies by segment. /// - /// And then it could also vary by culture. + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// public static bool VariesBySegment(this IPublishedContentType contentType) => contentType.Variations.VariesBySegment(); /// /// Determines whether the content type varies by culture and segment. /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture and segment. + /// public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); /// /// Determines whether the property type is invariant. /// + /// The property type. + /// + /// A value indicating whether the property type is invariant. + /// public static bool VariesByNothing(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByNothing(); /// /// Determines whether the property type varies by culture. /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture. + /// public static bool VariesByCulture(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture(); /// /// Determines whether the property type varies by segment. /// + /// The property type. + /// + /// A value indicating whether the property type varies by segment. + /// public static bool VariesBySegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment(); /// /// Determines whether the property type varies by culture and segment. /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture and segment. + /// public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); /// /// Determines whether a variation is invariant. /// + /// The variation. + /// + /// A value indicating whether the variation is invariant. + /// public static bool VariesByNothing(this ContentVariation variation) => variation == ContentVariation.Nothing; /// /// Determines whether a variation varies by culture. /// - /// And then it could also vary by segment. + /// The variation. + /// + /// A value indicating whether the variation varies by culture. + /// public static bool VariesByCulture(this ContentVariation variation) => (variation & ContentVariation.Culture) > 0; /// /// Determines whether a variation varies by segment. /// - /// And then it could also vary by culture. + /// The variation. + /// + /// A value indicating whether the variation varies by segment. + /// public static bool VariesBySegment(this ContentVariation variation) => (variation & ContentVariation.Segment) > 0; /// /// Determines whether a variation varies by culture and segment. /// + /// The variation. + /// + /// A value indicating whether the variation varies by culture and segment. + /// public static bool VariesByCultureAndSegment(this ContentVariation variation) => (variation & ContentVariation.CultureAndSegment) == ContentVariation.CultureAndSegment; /// @@ -135,16 +215,18 @@ namespace Umbraco.Core /// The segment. /// A value indicating whether to perform exact validation. /// A value indicating whether to support wildcards. - /// A value indicating whether to throw a when the combination is invalid. - /// True if the combination is valid; otherwise false. + /// A value indicating whether to throw a when the combination is invalid. + /// + /// true if the combination is valid; otherwise false. + /// + /// Occurs when the combination is invalid, and is true. /// /// When validation is exact, the combination must match the variation exactly. For instance, if the variation is Culture, then /// a culture is required. When validation is not strict, the combination must be equivalent, or more restrictive: if the variation is /// Culture, an invariant combination is ok. /// Basically, exact is for one content type, or one property type, and !exact is for "all property types" of one content type. - /// Both and can be "*" to indicate "all of them". + /// Both and can be "*" to indicate "all of them". /// - /// Occurs when the combination is invalid, and is true. public static bool ValidateVariation(this ContentVariation variation, string culture, string segment, bool exact, bool wildcards, bool throwIfInvalid) { culture = culture.NullOrWhiteSpaceAsNull(); @@ -161,13 +243,14 @@ namespace Umbraco.Core if (variation.VariesByCulture()) { // varies by culture - // in exact mode, the culture cannot be null + // in exact mode, the culture cannot be null if (exact && culture == null) { if (throwIfInvalid) throw new NotSupportedException($"Culture may not be null because culture variation is enabled."); + return false; - } + } } else { @@ -178,9 +261,10 @@ namespace Umbraco.Core { if (throwIfInvalid) throw new NotSupportedException($"Culture \"{culture}\" is invalid because culture variation is disabled."); + return false; } - } + } // if it does not vary by segment // the segment cannot have a value @@ -190,6 +274,7 @@ namespace Umbraco.Core { if (throwIfInvalid) throw new NotSupportedException($"Segment \"{segment}\" is invalid because segment variation is disabled."); + return false; } From a10c4403abda8ca1817817061c2042b8373a1284 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 9 Jan 2020 14:26:18 +0100 Subject: [PATCH 148/224] Remove konamiCode directive since it isn't used anywhere in core (#7437) --- .../directives/util/konami.directive.js | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js deleted file mode 100644 index 7914dfc3f0..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Konami Code directive for AngularJS - * @version v0.0.1 - * @license MIT License, https://www.opensource.org/licenses/MIT - */ - -angular.module('umbraco.directives') - .directive('konamiCode', ['$document', function ($document) { - var konamiKeysDefault = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]; - - return { - restrict: 'A', - link: function (scope, element, attr) { - - if (!attr.konamiCode) { - throw ('Konami directive must receive an expression as value.'); - } - - // Let user define a custom code. - var konamiKeys = attr.konamiKeys || konamiKeysDefault; - var keyIndex = 0; - - /** - * Fired when konami code is type. - */ - function activated() { - if ('konamiOnce' in attr) { - stopListening(); - } - // Execute expression. - scope.$eval(attr.konamiCode); - } - - /** - * Handle keydown events. - */ - function keydown(e) { - if (e.keyCode === konamiKeys[keyIndex++]) { - if (keyIndex === konamiKeys.length) { - keyIndex = 0; - activated(); - } - } else { - keyIndex = 0; - } - } - - /** - * Stop to listen typing. - */ - function stopListening() { - $document.off('keydown', keydown); - } - - // Start listening to key typing. - $document.on('keydown', keydown); - - // Stop listening when scope is destroyed. - scope.$on('$destroy', stopListening); - } - }; - }]); From 16faeb1886276b0843087230d3693f721db62013 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Fri, 10 Jan 2020 08:34:23 +0000 Subject: [PATCH 149/224] V8: Date pickers - parsing non-standard formats (#6001) --- .../datepicker/datepicker.controller.js | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js index 0f19bf3b1a..24affc6ac1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js @@ -85,13 +85,27 @@ function dateTimePickerController($scope, angularHelper, dateHelper, validationM }; $scope.datePickerChange = function(date) { - setDate(date); + const momentDate = moment(date); + setDate(momentDate); setDatePickerVal(); }; - $scope.inputChanged = function() { - setDate($scope.model.datetimePickerValue); - setDatePickerVal(); + $scope.inputChanged = function () { + if ($scope.model.datetimePickerValue === "" && $scope.hasDatetimePickerValue) { + // $scope.hasDatetimePickerValue indicates that we had a value before the input was changed, + // but now the input is empty. + $scope.clearDate(); + } else if ($scope.model.datetimePickerValue) { + var momentDate = moment($scope.model.datetimePickerValue, $scope.model.config.format, true); + if (!momentDate || !momentDate.isValid()) { + momentDate = moment(new Date($scope.model.datetimePickerValue)); + } + if (momentDate && momentDate.isValid()) { + setDate(momentDate); + } + setDatePickerVal(); + flatPickr.setDate($scope.model.value, false); + } } //here we declare a special method which will be called whenever the value has changed from the server @@ -103,15 +117,14 @@ function dateTimePickerController($scope, angularHelper, dateHelper, validationM var newDate = moment(newVal); if (newDate.isAfter(minDate)) { - setDate(newVal); + setDate(newDate); } else { $scope.clearDate(); } } }; - function setDate(date) { - const momentDate = moment(date); + function setDate(momentDate) { angularHelper.safeApply($scope, function() { // when a date is changed, update the model if (momentDate && momentDate.isValid()) { @@ -123,12 +136,11 @@ function dateTimePickerController($scope, angularHelper, dateHelper, validationM $scope.hasDatetimePickerValue = false; $scope.model.datetimePickerValue = null; } - updateModelValue(date); + updateModelValue(momentDate); }); } - function updateModelValue(date) { - const momentDate = moment(date); + function updateModelValue(momentDate) { if ($scope.hasDatetimePickerValue) { if ($scope.model.config.pickTime) { //check if we are supposed to offset the time From 2b4279315a1d9619455509a29d83aecef56f634c Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 10 Jan 2020 10:05:52 +0100 Subject: [PATCH 150/224] V8: Make the markdown editor use the Umbraco link picker (#5745) --- .../lib/markdown/markdown.editor.js | 10 ++++++---- .../markdowneditor.controller.js | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js index a75a7f1f3c..60118dbdb3 100644 --- a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js +++ b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js @@ -60,6 +60,7 @@ * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used. */ + hooks.addFalse("insertLinkDialog"); this.getConverter = function () { return markdownConverter; } @@ -1636,7 +1637,7 @@ var that = this; // The function to be executed when you enter a link and press OK or Cancel. // Marks up the link and adds the ref. - var linkEnteredCallback = function (link) { + var linkEnteredCallback = function (link, title) { if (link !== null) { // ( $1 @@ -1667,10 +1668,10 @@ if (!chunk.selection) { if (isImage) { - chunk.selection = "enter image description here"; + chunk.selection = title || "enter image description here"; } else { - chunk.selection = "enter link description here"; + chunk.selection = title || "enter link description here"; } } } @@ -1683,7 +1684,8 @@ ui.prompt('Insert Image', imageDialogText, imageDefaultText, linkEnteredCallback); } else { - ui.prompt('Insert Link', linkDialogText, linkDefaultText, linkEnteredCallback); + if (!this.hooks.insertLinkDialog(linkEnteredCallback)) + ui.prompt('Insert Link', linkDialogText, linkDefaultText, linkEnteredCallback); } return true; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js index f05b1e31d8..bb87f0463d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js @@ -27,6 +27,20 @@ function MarkdownEditorController($scope, $element, assetsService, editorService editorService.mediaPicker(mediaPicker); } + function openLinkPicker(callback) { + var linkPicker = { + hideTarget: true, + submit: function(model) { + callback(model.target.url, model.target.name); + editorService.close(); + }, + close: function() { + editorService.close(); + } + }; + editorService.linkPicker(linkPicker); + } + assetsService .load([ "lib/markdown/markdown.converter.js", @@ -53,6 +67,12 @@ function MarkdownEditorController($scope, $element, assetsService, editorService return true; // tell the editor that we'll take care of getting the image url }); + //subscribe to the link dialog clicks + editor2.hooks.set("insertLinkDialog", function (callback) { + openLinkPicker(callback); + return true; // tell the editor that we'll take care of getting the link url + }); + editor2.hooks.set("onPreviewRefresh", function () { // We must manually update the model as there is no way to hook into the markdown editor events without exstensive edits to the library. if ($scope.model.value !== $("textarea", $element).val()) { From eeaa5a82d46ebbae5616aafa747a452d36bd05c8 Mon Sep 17 00:00:00 2001 From: Dave Woestenborghs Date: Fri, 10 Jan 2020 10:39:22 +0100 Subject: [PATCH 151/224] V8/doctype tours (#6980) --- .../src/common/resources/tour.resource.js | 13 +++- .../common/services/editorstate.service.js | 3 +- .../src/common/services/tour.service.js | 65 ++++++++++------ .../common/drawers/help/help.controller.js | 35 ++++++++- .../src/views/common/drawers/help/help.html | 74 ++++++++++++------- src/Umbraco.Web/Editors/TourController.cs | 33 +++++++++ src/Umbraco.Web/Models/BackOfficeTour.cs | 3 + 7 files changed, 174 insertions(+), 52 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/tour.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/tour.resource.js index 40baf0f389..485b0d299a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/tour.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/tour.resource.js @@ -20,10 +20,21 @@ "GetTours")), 'Failed to get tours'); } + + function getToursForDoctype(doctypeAlias) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "tourApiBaseUrl", + "GetToursForDoctype", + [{ doctypeAlias: doctypeAlias }])), + 'Failed to get tours'); + } var resource = { - getTours: getTours + getTours: getTours, + getToursForDoctype: getToursForDoctype }; return resource; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js index 97a9ac5c4b..28daa3f245 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js @@ -10,7 +10,7 @@ * * it is possible to modify this object, so should be used with care */ -angular.module('umbraco.services').factory("editorState", function ($rootScope) { +angular.module('umbraco.services').factory("editorState", function ($rootScope, eventsService) { var current = null; @@ -30,6 +30,7 @@ angular.module('umbraco.services').factory("editorState", function ($rootScope) */ set: function (entity) { current = entity; + eventsService.emit("editorState.changed", { entity: entity }); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js index e102da5d34..7219395fd6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js @@ -134,31 +134,33 @@ var groupedTours = []; tours.forEach(function (item) { - var groupExists = false; - var newGroup = { - "group": "", - "tours": [] - }; + if (item.contentType === null || item.contentType === '') { + var groupExists = false; + var newGroup = { + "group": "", + "tours": [] + }; - groupedTours.forEach(function(group){ - // extend existing group if it is already added - if(group.group === item.group) { - if(item.groupOrder) { - group.groupOrder = item.groupOrder + groupedTours.forEach(function (group) { + // extend existing group if it is already added + if (group.group === item.group) { + if (item.groupOrder) { + group.groupOrder = item.groupOrder; + } + groupExists = true; + group.tours.push(item); } - groupExists = true; - group.tours.push(item) - } - }); + }); - // push new group to array if it doesn't exist - if(!groupExists) { - newGroup.group = item.group; - if(item.groupOrder) { - newGroup.groupOrder = item.groupOrder + // push new group to array if it doesn't exist + if (!groupExists) { + newGroup.group = item.group; + if (item.groupOrder) { + newGroup.groupOrder = item.groupOrder; + } + newGroup.tours.push(item); + groupedTours.push(newGroup); } - newGroup.tours.push(item); - groupedTours.push(newGroup); } }); @@ -188,6 +190,24 @@ return deferred.promise; } + /** + * @ngdoc method + * @name umbraco.services.tourService#getToursForDoctype + * @methodOf umbraco.services.tourService + * + * @description + * Returns a promise of the tours found by documenttype alias. + * @param {Object} doctypeAlias The doctype alias for which the tours which should be returned + * @returns {Array} An array of tour objects for the doctype + */ + function getToursForDoctype(doctypeAlias) { + var deferred = $q.defer(); + tourResource.getToursForDoctype(doctypeAlias).then(function (tours) { + deferred.resolve(tours); + }); + return deferred.promise; + } + /////////// /** @@ -269,7 +289,8 @@ completeTour: completeTour, getCurrentTour: getCurrentTour, getGroupedTours: getGroupedTours, - getTourByAlias: getTourByAlias + getTourByAlias: getTourByAlias, + getToursForDoctype : getToursForDoctype }; return service; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js index 3323f2bfb3..268bfb3a8c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function HelpDrawerController($scope, $routeParams, $timeout, dashboardResource, localizationService, userService, eventsService, helpService, appState, tourService, $filter) { + function HelpDrawerController($scope, $routeParams, $timeout, dashboardResource, localizationService, userService, eventsService, helpService, appState, tourService, $filter, editorState) { var vm = this; var evts = []; @@ -18,6 +18,10 @@ vm.startTour = startTour; vm.getTourGroupCompletedPercentage = getTourGroupCompletedPercentage; vm.showTourButton = showTourButton; + + vm.showDocTypeTour = false; + vm.docTypeTours = []; + vm.nodeName = ''; function startTour(tour) { tourService.startTour(tour); @@ -58,9 +62,16 @@ handleSectionChange(); })); + evts.push(eventsService.on("editorState.changed", + function (e, args) { + setDocTypeTour(args.entity); + })); + findHelp(vm.section, vm.tree, vm.userType, vm.userLang); }); + + setDocTypeTour(editorState.getCurrent()); // check if a tour is running - if it is open the matching group var currentTour = tourService.getCurrentTour(); @@ -84,7 +95,7 @@ setSectionName(); findHelp(vm.section, vm.tree, vm.userType, vm.userLang); - + setDocTypeTour(); } }); } @@ -168,6 +179,26 @@ }); } + function setDocTypeTour(node) { + vm.showDocTypeTour = false; + vm.docTypeTours = []; + vm.nodeName = ''; + + if (vm.section === 'content' && vm.tree === 'content') { + + if (node) { + tourService.getToursForDoctype(node.contentTypeAlias).then(function (data) { + if (data && data.length > 0) { + vm.docTypeTours = data; + var currentVariant = _.find(node.variants, (x) => x.active); + vm.nodeName = currentVariant.name; + vm.showDocTypeTour = true; + } + }); + } + } + } + evts.push(eventsService.on("appState.tour.complete", function (event, tour) { tourService.getGroupedTours().then(function(groupedTours) { vm.tours = groupedTours; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html index 96f4a404bc..822b5bdb19 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html @@ -8,12 +8,34 @@ - -
+ +
+
Need help editing current item '{{vm.nodeName}}' ?
-
Tours
+
-
+ +
+
+
+
+ {{ tour.name }} +
+
+ +
+
+
+
+
+
+ + +
+ +
Tours
+ +
@@ -51,33 +73,33 @@
-
+
- -
-
-
{{dashboard.label}}
-
-
-
+ +
+
+
{{dashboard.label}}
+
+
+
+
-
- -
-
Articles
-
    -
  • - - - - {{topic.name}} - - - {{topic.description}} - + +
    +
    Articles
    +
    diff --git a/src/Umbraco.Web/Editors/TourController.cs b/src/Umbraco.Web/Editors/TourController.cs index 25b4f7e9fc..8991bcdd6a 100644 --- a/src/Umbraco.Web/Editors/TourController.cs +++ b/src/Umbraco.Web/Editors/TourController.cs @@ -110,6 +110,39 @@ namespace Umbraco.Web.Editors return result.Except(toursToBeRemoved).OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase); } + /// + /// Gets a tours for a specific doctype + /// + /// The documenttype alias + /// A + public IEnumerable GetToursForDoctype(string doctypeAlias) + { + var tourFiles = this.GetTours(); + + var doctypeAliasWithCompositions = new List + { + doctypeAlias + }; + + var contentType = this.Services.ContentTypeService.Get(doctypeAlias); + + if (contentType != null) + { + doctypeAliasWithCompositions.AddRange(contentType.CompositionAliases()); + } + + return tourFiles.SelectMany(x => x.Tours) + .Where(x => + { + if (string.IsNullOrEmpty(x.ContentType)) + { + return false; + } + var contentTypes = x.ContentType.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(ct => ct.Trim()); + return contentTypes.Intersect(doctypeAliasWithCompositions).Any(); + }); + } + private void TryParseTourFile(string tourFile, ICollection result, List filters, diff --git a/src/Umbraco.Web/Models/BackOfficeTour.cs b/src/Umbraco.Web/Models/BackOfficeTour.cs index d5987ec5bc..3e14f278e2 100644 --- a/src/Umbraco.Web/Models/BackOfficeTour.cs +++ b/src/Umbraco.Web/Models/BackOfficeTour.cs @@ -31,5 +31,8 @@ namespace Umbraco.Web.Models [DataMember(Name = "culture")] public string Culture { get; set; } + + [DataMember(Name = "contentType")] + public string ContentType { get; set; } } } From f92d0b59bd64b0b751e2e329499a2785cf613637 Mon Sep 17 00:00:00 2001 From: Dave Woestenborghs Date: Fri, 10 Jan 2020 10:41:37 +0100 Subject: [PATCH 152/224] Make sure the configured url provider mode is used when getting a url (#7189) --- src/Umbraco.Web/PublishedContentExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 75eb6adbcb..061422859c 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1193,7 +1193,7 @@ namespace Umbraco.Web /// if any. In addition, when the content type is multi-lingual, this is the url for the /// specified culture. Otherwise, it is the invariant url. /// - public static string Url(this IPublishedContent content, string culture = null, UrlMode mode = UrlMode.Auto) + public static string Url(this IPublishedContent content, string culture = null, UrlMode mode = UrlMode.Default) { var umbracoContext = Composing.Current.UmbracoContext; From 4a44cd3a5823454f3e0c9cb5b44267023a8c98df Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 10 Jan 2020 20:02:39 +0100 Subject: [PATCH 153/224] =?UTF-8?q?V8:=20If=20Nested=20Content=20has=20mul?= =?UTF-8?q?tiple=20item=20types,=20always=20let=20the=20e=E2=80=A6=20(#542?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lovely work Kenn. Thanks, as ever --- .../nestedcontent/nestedcontent.controller.js | 50 ++++++++++++------- .../nestedcontent.propertyeditor.html | 6 +-- 2 files changed, 36 insertions(+), 20 deletions(-) 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 b61f8b94c2..7de3a5b567 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 @@ -41,7 +41,7 @@ if (vm.maxItems === 0) vm.maxItems = 1000; - vm.singleMode = vm.minItems === 1 && vm.maxItems === 1; + vm.singleMode = vm.minItems === 1 && vm.maxItems === 1 && model.config.contentTypes.length === 1;; vm.showIcons = Object.toBoolean(model.config.showIcons); vm.wideMode = Object.toBoolean(model.config.hideLabel); vm.hasContentTypes = model.config.contentTypes.length > 0; @@ -131,6 +131,7 @@ setCurrentNode(newNode); setDirty(); + validate(); }; vm.openNodeTypePicker = function ($event) { @@ -234,12 +235,22 @@ } }; + vm.canDeleteNode = function (idx) { + return (vm.nodes.length > vm.minItems) + ? true + : model.config.contentTypes.length > 1; + } + function deleteNode(idx) { vm.nodes.splice(idx, 1); setDirty(); updateModel(); + validate(); }; vm.requestDeleteNode = function (idx) { + if (!vm.canDeleteNode(idx)) { + return; + } if (model.config.confirmDeletes === true) { localizationService.localizeMany(["content_nestedContentDeleteItem", "general_delete", "general_cancel", "contentTypeEditor_yesDelete"]).then(function (data) { const overlay = { @@ -468,8 +479,8 @@ } } - // Auto-fill with elementTypes, but only if we have one type to choose from, and if this property is empty. - if (vm.singleMode === true && vm.nodes.length === 0 && model.config.minItems > 0) { + // Enforce min items if we only have one scaffold type + if (vm.nodes.length < vm.minItems && vm.scaffolds.length === 1) { for (var i = vm.nodes.length; i < model.config.minItems; i++) { addNode(vm.scaffolds[0].contentTypeAlias); } @@ -480,6 +491,8 @@ setCurrentNode(vm.nodes[0]); } + validate(); + vm.inited = true; updatePropertyActionStates(); @@ -585,25 +598,28 @@ updateModel(); }); + var validate = function () { + if (vm.nodes.length < vm.minItems) { + $scope.nestedContentForm.minCount.$setValidity("minCount", false); + } + else { + $scope.nestedContentForm.minCount.$setValidity("minCount", true); + } + + if (vm.nodes.length > vm.maxItems) { + $scope.nestedContentForm.maxCount.$setValidity("maxCount", false); + } + else { + $scope.nestedContentForm.maxCount.$setValidity("maxCount", true); + } + } + var watcher = $scope.$watch( function () { return vm.nodes.length; }, function () { - //Validate! - if (vm.nodes.length < vm.minItems) { - $scope.nestedContentForm.minCount.$setValidity("minCount", false); - } - else { - $scope.nestedContentForm.minCount.$setValidity("minCount", true); - } - - if (vm.nodes.length > vm.maxItems) { - $scope.nestedContentForm.maxCount.$setValidity("maxCount", false); - } - else { - $scope.nestedContentForm.maxCount.$setValidity("maxCount", true); - } + validate(); } ); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html index b3821cff3d..47293e433b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html @@ -2,7 +2,7 @@ - +
    @@ -17,7 +17,7 @@ {{vm.labels.copy_icon_title}} -
    From 2b1e33095769e1dc112308efb586afbc38216849 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 10 Jan 2020 20:09:51 +0100 Subject: [PATCH 154/224] =?UTF-8?q?V8:=20Reset=20paging=20in=20user=20sear?= =?UTF-8?q?ch=20when=20free=20text=20searching=20and=20fi=E2=80=A6=20(#704?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks Kenn :) --- .../src/views/users/views/users/users.controller.js | 6 +++--- 1 file changed, 3 insertions(+), 3 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 4ee31806a3..935b1bdfb1 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 @@ -451,7 +451,7 @@ var search = _.debounce(function () { $scope.$apply(function () { - getUsers(); + changePageNumber(1); }); }, 500); @@ -512,7 +512,7 @@ } updateLocation("userStates", vm.usersOptions.userStates.join(",")); - getUsers(); + changePageNumber(1); } function setUserGroupFilter(userGroup) { @@ -529,7 +529,7 @@ } updateLocation("userGroups", vm.usersOptions.userGroups.join(",")); - getUsers(); + changePageNumber(1); } function setOrderByFilter(value, direction) { From 2906eafa791fc2c3a2e8a270e2a850a7c25f3b01 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 10 Jan 2020 20:14:11 +0100 Subject: [PATCH 155/224] =?UTF-8?q?V8:=20Make=20it=20possible=20to=20hide?= =?UTF-8?q?=20anchor/querystring=20input=20in=20the=20li=E2=80=A6=20(#7031?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wonderful work --- .../infiniteeditors/linkpicker/linkpicker.controller.js | 1 + .../views/common/infiniteeditors/linkpicker/linkpicker.html | 4 ++-- .../multiurlpicker/multiurlpicker.controller.js | 1 + .../PropertyEditors/MultiUrlPickerConfiguration.cs | 5 ++++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index b5043293e5..47607b7f0b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -33,6 +33,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", }; $scope.showTarget = $scope.model.hideTarget !== true; + $scope.showAnchor = $scope.model.hideAnchor !== true; // this ensures that we only sync the tree once and only when it's ready var oneTimeTreeSync = { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html index a7d2dbbee2..ad0aaab57c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html @@ -14,7 +14,7 @@ -
    +
    - + Date: Mon, 13 Jan 2020 17:16:55 +1100 Subject: [PATCH 156/224] Fixes the issue of manipulating data for an existing Gen, adds notes, makes the Gen comparison operator consistent. --- .../PublishedCache/NuCache/ContentNode.cs | 6 ++++++ .../PublishedCache/NuCache/ContentStore.cs | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs index 3b4859432d..a724e78b72 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs @@ -109,6 +109,8 @@ namespace Umbraco.Web.PublishedCache.NuCache // everything that is common to both draft and published versions // keep this as small as possible + + public readonly int Id; public readonly Guid Uid; public IPublishedContentType ContentType; @@ -116,10 +118,14 @@ namespace Umbraco.Web.PublishedCache.NuCache public readonly string Path; public readonly int SortOrder; public readonly int ParentContentId; + + // TODO: Can we make everything readonly?? This would make it easier to debug and be less error prone especially for new developers. + // Once a Node is created and exists in the cache it is readonly so we should be able to make that happen at the API level too. public int FirstChildContentId; public int LastChildContentId; public int NextSiblingContentId; public int PreviousSiblingContentId; + public readonly DateTime CreateDate; public readonly int CreatorId; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 550fd507d5..3857a49a28 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -602,7 +602,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private void ClearRootLocked() { - if (_root.Gen < _liveGen) + if (_root.Gen != _liveGen) _root = new LinkedNode(new ContentNode(), _liveGen, _root); else _root.Value.FirstChildContentId = -1; @@ -899,8 +899,18 @@ namespace Umbraco.Web.PublishedCache.NuCache return link; } + /// + /// This removes this current node from the tree hiearchy by removing it from it's parent's linked list + /// + /// + /// + /// This is called within a lock which means a new Gen is being created therefore this will not modify any existing content in a Gen. + /// private void RemoveTreeNodeLocked(ContentNode content) { + // NOTE: DO NOT modify `content` here, this would modify data for an existing Gen, all modifications are done to clones + // which would be targeting the new Gen. + var parentLink = content.ParentContentId < 0 ? _root : GetRequiredLinkedNode(content.ParentContentId, "parent", null); @@ -937,9 +947,6 @@ namespace Umbraco.Web.PublishedCache.NuCache var prev = GenCloneLocked(prevLink); prev.NextSiblingContentId = content.NextSiblingContentId; } - - content.NextSiblingContentId = -1; - content.PreviousSiblingContentId = -1; } private bool ParentPublishedLocked(ContentNodeKit kit) @@ -955,7 +962,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { var node = link.Value; - if (node != null && link.Gen < _liveGen) + if (node != null && link.Gen != _liveGen) { node = new ContentNode(link.Value); if (link == _root) @@ -1109,7 +1116,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // this is safe only because we're write-locked foreach (var kvp in dict.Where(x => x.Value != null)) { - if (kvp.Value.Gen < _liveGen) + if (kvp.Value.Gen != _liveGen) { var link = new LinkedNode(null, _liveGen, kvp.Value); dict.TryUpdate(kvp.Key, link, kvp.Value); From 71b3aa1cb0d1ab848dc20714a133d42806427d78 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 13 Jan 2020 22:16:19 +1100 Subject: [PATCH 157/224] Removes the need for checking variant when binding content --- .../Editors/Binders/ContentItemBinder.cs | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs b/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs index d9d56c584f..a6cba6ce41 100644 --- a/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs @@ -49,22 +49,15 @@ namespace Umbraco.Web.Editors.Binders { foreach (var variant in model.Variants) { - 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); - } - else - { - //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.SetSegment(variant.Segment); - }); - } + //map the property dto collection with the culture of the current variant + variant.PropertyCollectionDto = Current.Mapper.Map( + model.PersistedContent, + context => + { + // either of these may be null and that is ok, if it's invariant they will be null which is what is expected + context.SetCulture(variant.Culture); + context.SetSegment(variant.Segment); + }); //now map all of the saved values to the dto _modelBinderHelper.MapPropertyValuesFromSaved(variant, variant.PropertyCollectionDto); From b02360518d2da1efb64be81fc6c724c9047699db Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 13 Jan 2020 22:25:46 +1100 Subject: [PATCH 158/224] Changed to GetAwaiter/GetResult and updates comment --- src/Umbraco.Core/Runtime/IMainDomLock.cs | 3 +-- src/Umbraco.Core/Runtime/MainDom.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Runtime/IMainDomLock.cs b/src/Umbraco.Core/Runtime/IMainDomLock.cs index c32e990114..6a62f48194 100644 --- a/src/Umbraco.Core/Runtime/IMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/IMainDomLock.cs @@ -16,9 +16,8 @@ namespace Umbraco.Core.Runtime ///
/// /// - /// A disposable object which will be disposed in order to release the lock + /// An awaitable boolean value which will be false if the elapsed millsecondsTimeout value is exceeded /// - /// Throws a if the elapsed millsecondsTimeout value is exceeded Task AcquireLockAsync(int millisecondsTimeout); ///
diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index 67c7be3d21..e6780ec876 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -145,7 +145,7 @@ namespace Umbraco.Core.Runtime _logger.Info("Acquiring."); // Get the lock - var acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).Result; + var acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult(); if (!acquired) { From 3e71de4698f8a46e92ef747e39366b80dcf635aa Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 13 Jan 2020 22:28:25 +1100 Subject: [PATCH 159/224] ensure locks the data types --- src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index c959d9f67a..cecca3eb75 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -462,6 +462,8 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public void UpdateDataTypesLocked(IEnumerable dataTypeIds, Func getContentType) { + EnsureLocked(); + var contentTypes = _contentTypesById .Where(kvp => kvp.Value.Value != null && From d48b33d86d0b81666dec4d33d224b6de2d1ff74c Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Mon, 13 Jan 2020 22:03:34 +0100 Subject: [PATCH 160/224] Icon picker: Accessibility improvements (#6990) --- .../src/less/components/umb-iconpicker.less | 10 ++++++---- .../common/infiniteeditors/iconpicker/iconpicker.html | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less index e8a62f739d..98b2b1d72d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less @@ -15,7 +15,9 @@ overflow: hidden; } -.umb-iconpicker-item a { +.umb-iconpicker-item button { + background: transparent; + border: 0 none; display: flex; justify-content: center; align-items: center; @@ -26,8 +28,8 @@ border-radius: 3px; } -.umb-iconpicker-item a:hover, -.umb-iconpicker-item a:focus { +.umb-iconpicker-item button:hover, +.umb-iconpicker-item button:focus { background: @gray-10; outline: none; } @@ -39,7 +41,7 @@ box-sizing: border-box; } -.umb-iconpicker-item a:active { +.umb-iconpicker-item button:active { background: @gray-10; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html index 3caa6ae03d..c2b0d0e458 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html @@ -46,9 +46,10 @@
  • - - - +
From 8176054432b6187b1cec4921f4542bfb45be2469 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Mon, 13 Jan 2020 22:33:31 +0100 Subject: [PATCH 161/224] Add label and missing translations (#7019) --- .../components/application/umb-search.less | 6 +- .../components/application/umb-search.html | 14 +- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 4737 +++++++++-------- .../Umbraco/config/lang/en_us.xml | 1 + 4 files changed, 2385 insertions(+), 2373 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less index 70e4f3d372..2f9430ef41 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less @@ -16,6 +16,10 @@ box-shadow: 0 10px 20px rgba(0,0,0,.12),0 6px 6px rgba(0,0,0,.14); } +.umb-search__label{ + margin: 0; +} + /* Search field */ @@ -107,4 +111,4 @@ .umb-search-result__description { color: @gray-5; font-size: 13px; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html index 297ade28c0..cc1c8abb7e 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 @@ -2,15 +2,21 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html index f4e4dff3af..b45a3bb843 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html @@ -6,11 +6,11 @@ '-left': direction === 'left'}" on-outside-click="clickCancel()"> - + - +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index 3db1221f5e..5adf121a56 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -402,6 +402,15 @@ angular.module("umbraco") eventsService.emit("grid.rowAdded", { scope: $scope, element: $element, row: row }); + // TODO: find a nicer way to do this without relying on setTimeout + setTimeout(function () { + var newRowEl = $element.find("[data-rowid='" + row.$uniqueId + "']"); + + if(newRowEl !== null) { + newRowEl.focus(); + } + }, 0); + }; $scope.removeRow = function (section, $index) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html index e889067321..bde2b9148e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html @@ -95,7 +95,7 @@
- + -
+
+
- +
- +
-
+
@@ -249,14 +249,16 @@
- - - - + + + Add new role + +
@@ -264,10 +266,11 @@

-
+ ng-click="addRow(section, layout)" + type="button">
@@ -285,7 +288,7 @@ {{layout.label || layout.name}} -
+
From 6ca0a8ba665075acf65efeafbfc0eb46dda665ef Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 16 Jan 2020 14:47:25 +0100 Subject: [PATCH 173/224] V8: A few UX updates to the listview move dialog (#6902) * Add some context to the move operation from listviews * Disable submit button in the listview move dialog until a target is chosen * Fix bad merge --- .../common/infiniteeditors/move/move.html | 6 +- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 + .../Umbraco/config/lang/en_us.xml | 4801 +++++++++-------- 4 files changed, 2408 insertions(+), 2401 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.html index 8424bcefbe..1c9fd5b822 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/move/move.html @@ -13,6 +13,9 @@ +

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

@@ -75,7 +78,8 @@ button-style="success" label-key="general_submit" state="vm.saveButtonState" - action="vm.submit(model)"> + action="vm.submit(model)" + disabled="!model.target"> diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 08a00f502e..78075d9b7a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -35,6 +35,7 @@ Sæt rettigheder for siden %0% Vælg hvor du vil kopiere Vælg hvortil du vil flytte + Vælg hvor du vil flytte de valgte elementer hen til i træstrukturen nedenfor Vælg hvor du vil kopiere de valgte elementer til blev flyttet til diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index d8e94017aa..a2cf6ea475 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -37,6 +37,7 @@ Choose where to move to in the tree structure below Choose where to copy the selected item(s) + Choose where to move the selected item(s) was moved to was copied to was deleted 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 82ba12dc17..56d5f1ade2 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1,2400 +1,2401 @@ - - - - The Umbraco community - https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - - - Culture and Hostnames - Audit Trail - Browse Node - Change Document Type - Copy - Create - Export - Create Package - Create group - Delete - Disable - Empty recycle bin - Enable - Export Document Type - Import Document Type - Import Package - Edit in Canvas - Exit - Move - Notifications - Public access - Publish - Unpublish - Reload - Republish entire site - Rename - Restore - Set permissions for the page %0% - Choose where to copy - Choose where to move - to in the tree structure below - Choose where to copy the selected item(s) - 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) - Click here to remove the image from the media item - Click here to remove the file from the media item - Link to document - Member of group(s) - Not a member of group(s) - Child items - Target - This translates to the following time on the server: - What does this mean?]]> - Are you sure you want to delete this item? - 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 - Select the group whose properties should be displayed. If left blank, the first group on the element type will be used. - Enter an angular expression to evaluate against each item for its name. Use - to display the item index - Add another text box - Remove this text box - Content root - Include drafts: also publish unpublished content items. - 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 - 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 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 - Other - Articles - Videos - Clear - Installing - - - Blue - - - Add group - Add property - Add editor - Add template - Add child node - Add child - Edit data type - Navigate sections - Shortcuts - show shortcuts - Toggle list view - Toggle allow as root - Comment/Uncomment lines - Remove line - Copy Lines Up - Copy Lines Down - Move Lines Up - Move Lines Down - General - Editor - Toggle allow culture variants - - - 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 - - - Tours - The best Umbraco video tutorials - Visit our.umbraco.com - Visit umbraco.tv - - - Default template - To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) - New Tab Title - Node type - Type - Stylesheet - Script - Tab - Tab Title - Tabs - Master Content Type enabled - This Content Type uses - as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself - No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. - Create matching template - Add icon - - - Sort order - Creation date - Sorting complete. - Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items - - 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 - Cultures and hostnames saved - Error saving cultures and hostnames - File not saved - file could not be saved. Please check file permissions - File saved - File saved without any errors - Language saved - Media Type saved - Member Type saved - Member Group saved - Template not saved - Please make sure that you do not have 2 templates with the same alias - Template saved - Template saved without any errors! - Content unpublished - 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 - Warning - You are deleting the row configuration - - Deleting a row configuration name will result in loss of data for any existing content that is based on this configuration. - - - - Compositions - Group - You have not added any groups - Add group - Inherited from - Add property - Required label - Enable list view - Configures the content item to show a sortable and searchable list of its children, the children will not be shown in the tree - Allowed Templates - Choose which templates editors are allowed to use on content of this type - Allow as root - Allow editors to create content of this type in the root of the content tree. - Allowed child node types - Allow content of the specified types to be created underneath content of this type. - Choose child node - Inherit tabs and properties from an existing document type. New tabs will be added to the current document type or merged if a tab with an identical name exists. - This content type is used in a composition, and therefore cannot be composed itself. - There are no content types available to use as a composition. - Removing a composition will delete all the associated property data. Once you save the document type there's no way back. - Create new - Use existing - Editor settings - Configuration - Yes, delete - was moved underneath - was copied underneath - Select the folder to move - Select the folder to copy - to in the tree structure below - All Document types - All Documents - All media items - using this document type will be deleted permanently, please confirm you want to delete these as well. - using this media type will be deleted permanently, please confirm you want to delete these as well. - using this member type will be deleted permanently, please confirm you want to delete these as well - and all documents using this type - and all media items using this type - and all members using this type - Member can edit - Allow this property value to be edited by the member on their profile page - Is sensitive data - Hide this property value from content editors that don't have access to view sensitive information - Show on member profile - Allow this property value to be displayed on the member profile page - tab has no sort order - Where is this composition used? - This composition is currently used in the composition of the following content types: - Allow varying by culture - Allow editors to create content of this type in different languages. - Allow varying by culture - Element type - Is an Element type - An Element type is meant to be used for instance in Nested Content, and not in the tree. - This is not applicable for an Element type - You have made changes to this property. Are you sure you want to discard them? - - - Add language - Mandatory language - Properties on this language have to be filled out before the node can be published. - Default language - An Umbraco site can only have one default language set. - Switching default language may result in default content missing. - Falls back to - No fall back language - To allow multi-lingual content to fall back to another language if not present in the requested language, select it here. - Fall back language - none - - - Add parameter - Edit parameter - Enter macro name - Parameters - Define the parameters that should be available when using this macro. - Select partial view macro file - - - Building models - this can take a bit of time, don't worry - Models generated - Models could not be generated - Models generation has failed, see exception in U log - - - Add fallback field - Fallback field - Add default value - Default value - Fallback field - Default value - Casing - Encoding - Choose field - Convert line breaks - Yes, convert line breaks - Replaces line breaks with 'br' html tag - Custom Fields - Date only - Format and encoding - Format as date - Format the value as a date, or a date with time, according to the active culture - HTML encode - Will replace special characters by their HTML equivalent. - Will be inserted after the field value - Will be inserted before the field value - Lowercase - Modify output - None - Output sample - Insert after field - Insert before field - Recursive - Yes, make it recursive - Separator - Standard Fields - Uppercase - URL encode - Will format special characters in URLs - Will only be used when the field values above are empty - This field will only be used if the primary field is empty - Date and time - - - Translation details - Download XML DTD - Fields - Include subpages - - No translator users found. Please create a translator user before you start sending content to translation - The page '%0%' has been send to translation - Send the page '%0%' to translation - Total words - Translate to - Translation completed. - You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages. - Translation failed, the XML file might be corrupt - Translation options - Translator - Upload translation XML - - - Content - Content Templates - Media - Cache Browser - Recycle Bin - Created packages - Data Types - Dictionary - Installed packages - Install skin - Install starter kit - Languages - Install local package - Macros - Media Types - Members - Member Groups - Member Roles - Member Types - Document Types - Relation Types - Packages - Packages - Partial Views - Partial View Macro Files - Install from repository - Install Runway - Runway modules - Scripting Files - Scripts - Stylesheets - Templates - Log Viewer - Users - Settings - Templating - Third Party - - - New update ready - %0% is ready, click here for download - No connection to server - Error checking for update. Please review trace-stack for further information - - - Access - Based on the assigned groups and start nodes, the user has access to the following nodes - Assign access - Administrator - Category field - User created - Change Your Password - Change photo - New password - hasn't been locked out - The password hasn't been changed - Confirm new password - You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button - Content Channel - Create another user - Create new users to give them access to Umbraco. When a new user is created a password will be generated that you can share with the user. - Description field - Disable User - Document Type - Editor - Excerpt field - Failed login attempts - Go to user profile - Add groups to assign access and permissions - Invite another user - Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours. - Language - Set the language you will see in menus and dialogs - Last lockout date - Last login - Password last changed - Username - Media start node - Limit the media library to a specific start node - Media start nodes - Limit the media library to specific start nodes - Sections - Disable Umbraco Access - has not logged in yet - Old password - Password - Reset password - Your password has been changed! - Please confirm the new password - Enter your new password - Your new password cannot be blank! - Current password - Invalid current password - There was a difference between the new password and the confirmed password. Please try again! - The confirmed password doesn't match the new password! - Replace child node permissions - You are currently modifying permissions for the pages: - Select pages to modify their permissions - Remove photo - Default permissions - Granular permissions - Set permissions for specific nodes - Profile - Search all children - Add sections to give users access - Select user groups - No start node selected - No start nodes selected - Content start node - Limit the content tree to a specific start node - Content start nodes - Limit the content tree to specific start nodes - User last updated - has been created - The new user has successfully been created. To log in to Umbraco use the password below. - User management - Name - User permissions - User group - has been invited - An invitation has been sent to the new user with details about how to log in to Umbraco. - Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar. - Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it. - Uploading a photo of yourself will make it easy for other users to recognize you. Click the circle above to upload your photo. - Writer - Change - Your profile - Your recent history - Session expires in - Invite user - Create user - Send invite - Back to users - Umbraco: Invitation - - - - - - - - - - - -
- - - - - -
- -
- -
-
- - - - - - -
-
-
- - - - -
- - - - -
-

- Hi %0%, -

-

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

-

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

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

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

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

-
-
-


-
-
- - ]]>
- Invite - Resending invitation... - Delete User - Are you sure you wish to delete this user account? - All - Active - Disabled - Locked out - Invited - Inactive - Name (A-Z) - Name (Z-A) - Newest - Oldest - Last login - No user groups have been added - - - Validation - 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% - Check All Groups - Check group - - The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button. - You can add your own health checks, have a look at the documentation for more information about custom health checks.

- ]]> -
- - - Disable URL tracker - Enable URL tracker - 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 - Search the redirect dashboard - Search the user group section - Search the users section - Create item - Create - Edit - Name - - - References - This Data Type has no references. - Used in Document Types - No references to Document Types. - Used in Media Types - No references to Media Types. - Used in Member Types - No references to Member Types. - Used by - - - Log Levels - Saved Searches - Total Items - Timestamp - Level - Machine - Message - Exception - Properties - Search With Google - Search this message with Google - Search With Bing - Search this message with Bing - Search Our Umbraco - Search this message on Our Umbraco forums and docs - Search Our Umbraco with Google - Search Our Umbraco forums using Google - Search Umbraco Source - Search within Umbraco source code on Github - Search Umbraco Issues - Search Umbraco Issues on Github - Delete this search - Find Logs with Request ID - Find Logs with Namespace - Find Logs with Machine Name - Open - - - Copy %0% - %0% from %1% - Remove all items - - - Open Property Actions - - - Wait - Refresh status - Memory Cache - - - - Reload - Database Cache - - Rebuilding can be expensive. - Use it when reloading is not enough, and you think that the database cache has not been - properly generated—which would indicate some critical Umbraco issue. - ]]> - - Rebuild - Internals - - not need to use it. - ]]> - - Collect - Published Cache Status - Caches - - - Performance profiling - - - Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages. -

-

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

-

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

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

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

-

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

- ]]> -
- - - Hours of Umbraco training videos are only a click away - - Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos

- ]]> -
- To get you started - - - Start here - This section contains the building blocks for your Umbraco site. Follow the below links to find out more about working with the items in the Settings section - Find out more - - in the Documentation section of Our Umbraco - ]]> - - - Community Forum - ]]> - - - tutorial videos (some are free, some require a subscription) - ]]> - - - productivity boosting tools and commercial support - ]]> - - - training and certification opportunities - ]]> - - - - Welcome to The Friendly CMS - Thank you for choosing Umbraco - we think this could be the beginning of something beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast as possible. - - - Umbraco Forms - Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it! - -
+ + + + The Umbraco community + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files + + + Culture and Hostnames + Audit Trail + Browse Node + Change Document Type + Copy + Create + Export + Create Package + Create group + Delete + Disable + Empty recycle bin + Enable + Export Document Type + Import Document Type + Import Package + Edit in Canvas + Exit + Move + Notifications + Public access + Publish + Unpublish + Reload + Republish entire site + Rename + Restore + Set permissions for the page %0% + Choose where to copy + Choose where to move + to in the tree structure below + Choose where to copy the selected item(s) + Choose where to move the selected item(s) + was moved to + was copied to + was deleted + Permissions + Rollback + Send To Publish + Send To Translation + Set group + Sort + Translate + Update + Set permissions + Unlock + Create Content Template + Resend Invitation + + + Content + Administration + Structure + Other + + + Allow access to assign culture and hostnames + Allow access to view a node's history log + Allow access to view a node + Allow access to change document type for a node + Allow access to copy a node + Allow access to create nodes + Allow access to delete nodes + Allow access to move a node + Allow access to set and change public access for a node + Allow access to publish a node + Allow access to unpublish a node + Allow access to change permissions for a node + Allow access to roll back a node to a previous state + Allow access to send a node for approval before publishing + Allow access to send a node for translation + Allow access to change the sort order for nodes + Allow access to translate a node + Allow access to save a node + Allow access to create a Content Template + + + Content + Info + + + Permission denied. + Add new Domain + remove + Invalid node. + One or more domains have an invalid format. + Domain has already been assigned. + Language + Domain + New domain '%0%' has been created + Domain '%0%' is deleted + Domain '%0%' has already been assigned + Domain '%0%' has been updated + Edit Current Domains + + + Inherit + Culture + or inherit culture from parent nodes. Will also apply
+ to the current node, unless a domain below applies too.]]>
+ Domains + + + Clear selection + Select + Do something else + Bold + Cancel Paragraph Indent + Insert form field + Insert graphic headline + Edit Html + Indent Paragraph + Italic + Center + Justify Left + Justify Right + Insert Link + Insert local link (anchor) + Bullet List + Numeric List + Insert macro + Insert picture + Publish and close + Publish with descendants + Edit relations + Return to list + Save + Save and close + Save and publish + Save and schedule + 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) + Click here to remove the image from the media item + Click here to remove the file from the media item + Link to document + Member of group(s) + Not a member of group(s) + Child items + Target + This translates to the following time on the server: + What does this mean?]]> + Are you sure you want to delete this item? + 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 + Select the group whose properties should be displayed. If left blank, the first group on the element type will be used. + Enter an angular expression to evaluate against each item for its name. Use + to display the item index + Add another text box + Remove this text box + Content root + Include drafts: also publish unpublished content items. + 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 + 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 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 + Other + Articles + Videos + Clear + Installing + + + Blue + + + Add group + Add property + Add editor + Add template + Add child node + Add child + Edit data type + Navigate sections + Shortcuts + show shortcuts + Toggle list view + Toggle allow as root + Comment/Uncomment lines + Remove line + Copy Lines Up + Copy Lines Down + Move Lines Up + Move Lines Down + General + Editor + Toggle allow culture variants + + + 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 + + + Tours + The best Umbraco video tutorials + Visit our.umbraco.com + Visit umbraco.tv + + + Default template + To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) + New Tab Title + Node type + Type + Stylesheet + Script + Tab + Tab Title + Tabs + Master Content Type enabled + This Content Type uses + as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself + No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. + Create matching template + Add icon + + + Sort order + Creation date + Sorting complete. + Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items + + 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 + Cultures and hostnames saved + Error saving cultures and hostnames + File not saved + file could not be saved. Please check file permissions + File saved + File saved without any errors + Language saved + Media Type saved + Member Type saved + Member Group saved + Template not saved + Please make sure that you do not have 2 templates with the same alias + Template saved + Template saved without any errors! + Content unpublished + 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 + Warning + You are deleting the row configuration + + Deleting a row configuration name will result in loss of data for any existing content that is based on this configuration. + + + + Compositions + Group + You have not added any groups + Add group + Inherited from + Add property + Required label + Enable list view + Configures the content item to show a sortable and searchable list of its children, the children will not be shown in the tree + Allowed Templates + Choose which templates editors are allowed to use on content of this type + Allow as root + Allow editors to create content of this type in the root of the content tree. + Allowed child node types + Allow content of the specified types to be created underneath content of this type. + Choose child node + Inherit tabs and properties from an existing document type. New tabs will be added to the current document type or merged if a tab with an identical name exists. + This content type is used in a composition, and therefore cannot be composed itself. + There are no content types available to use as a composition. + Removing a composition will delete all the associated property data. Once you save the document type there's no way back. + Create new + Use existing + Editor settings + Configuration + Yes, delete + was moved underneath + was copied underneath + Select the folder to move + Select the folder to copy + to in the tree structure below + All Document types + All Documents + All media items + using this document type will be deleted permanently, please confirm you want to delete these as well. + using this media type will be deleted permanently, please confirm you want to delete these as well. + using this member type will be deleted permanently, please confirm you want to delete these as well + and all documents using this type + and all media items using this type + and all members using this type + Member can edit + Allow this property value to be edited by the member on their profile page + Is sensitive data + Hide this property value from content editors that don't have access to view sensitive information + Show on member profile + Allow this property value to be displayed on the member profile page + tab has no sort order + Where is this composition used? + This composition is currently used in the composition of the following content types: + Allow varying by culture + Allow editors to create content of this type in different languages. + Allow varying by culture + Element type + Is an Element type + An Element type is meant to be used for instance in Nested Content, and not in the tree. + This is not applicable for an Element type + You have made changes to this property. Are you sure you want to discard them? + + + Add language + Mandatory language + Properties on this language have to be filled out before the node can be published. + Default language + An Umbraco site can only have one default language set. + Switching default language may result in default content missing. + Falls back to + No fall back language + To allow multi-lingual content to fall back to another language if not present in the requested language, select it here. + Fall back language + none + + + Add parameter + Edit parameter + Enter macro name + Parameters + Define the parameters that should be available when using this macro. + Select partial view macro file + + + Building models + this can take a bit of time, don't worry + Models generated + Models could not be generated + Models generation has failed, see exception in U log + + + Add fallback field + Fallback field + Add default value + Default value + Fallback field + Default value + Casing + Encoding + Choose field + Convert line breaks + Yes, convert line breaks + Replaces line breaks with 'br' html tag + Custom Fields + Date only + Format and encoding + Format as date + Format the value as a date, or a date with time, according to the active culture + HTML encode + Will replace special characters by their HTML equivalent. + Will be inserted after the field value + Will be inserted before the field value + Lowercase + Modify output + None + Output sample + Insert after field + Insert before field + Recursive + Yes, make it recursive + Separator + Standard Fields + Uppercase + URL encode + Will format special characters in URLs + Will only be used when the field values above are empty + This field will only be used if the primary field is empty + Date and time + + + Translation details + Download XML DTD + Fields + Include subpages + + No translator users found. Please create a translator user before you start sending content to translation + The page '%0%' has been send to translation + Send the page '%0%' to translation + Total words + Translate to + Translation completed. + You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages. + Translation failed, the XML file might be corrupt + Translation options + Translator + Upload translation XML + + + Content + Content Templates + Media + Cache Browser + Recycle Bin + Created packages + Data Types + Dictionary + Installed packages + Install skin + Install starter kit + Languages + Install local package + Macros + Media Types + Members + Member Groups + Member Roles + Member Types + Document Types + Relation Types + Packages + Packages + Partial Views + Partial View Macro Files + Install from repository + Install Runway + Runway modules + Scripting Files + Scripts + Stylesheets + Templates + Log Viewer + Users + Settings + Templating + Third Party + + + New update ready + %0% is ready, click here for download + No connection to server + Error checking for update. Please review trace-stack for further information + + + Access + Based on the assigned groups and start nodes, the user has access to the following nodes + Assign access + Administrator + Category field + User created + Change Your Password + Change photo + New password + hasn't been locked out + The password hasn't been changed + Confirm new password + You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button + Content Channel + Create another user + Create new users to give them access to Umbraco. When a new user is created a password will be generated that you can share with the user. + Description field + Disable User + Document Type + Editor + Excerpt field + Failed login attempts + Go to user profile + Add groups to assign access and permissions + Invite another user + Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours. + Language + Set the language you will see in menus and dialogs + Last lockout date + Last login + Password last changed + Username + Media start node + Limit the media library to a specific start node + Media start nodes + Limit the media library to specific start nodes + Sections + Disable Umbraco Access + has not logged in yet + Old password + Password + Reset password + Your password has been changed! + Please confirm the new password + Enter your new password + Your new password cannot be blank! + Current password + Invalid current password + There was a difference between the new password and the confirmed password. Please try again! + The confirmed password doesn't match the new password! + Replace child node permissions + You are currently modifying permissions for the pages: + Select pages to modify their permissions + Remove photo + Default permissions + Granular permissions + Set permissions for specific nodes + Profile + Search all children + Add sections to give users access + Select user groups + No start node selected + No start nodes selected + Content start node + Limit the content tree to a specific start node + Content start nodes + Limit the content tree to specific start nodes + User last updated + has been created + The new user has successfully been created. To log in to Umbraco use the password below. + User management + Name + User permissions + User group + has been invited + An invitation has been sent to the new user with details about how to log in to Umbraco. + Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar. + Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it. + Uploading a photo of yourself will make it easy for other users to recognize you. Click the circle above to upload your photo. + Writer + Change + Your profile + Your recent history + Session expires in + Invite user + Create user + Send invite + Back to users + Umbraco: Invitation + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Hi %0%, +

+

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

+

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

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

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

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

+
+
+


+
+
+ + ]]>
+ Invite + Resending invitation... + Delete User + Are you sure you wish to delete this user account? + All + Active + Disabled + Locked out + Invited + Inactive + Name (A-Z) + Name (Z-A) + Newest + Oldest + Last login + No user groups have been added + + + Validation + 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% + Check All Groups + Check group + + The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button. + You can add your own health checks, have a look at the documentation for more information about custom health checks.

+ ]]> +
+ + + Disable URL tracker + Enable URL tracker + 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 + Search the redirect dashboard + Search the user group section + Search the users section + Create item + Create + Edit + Name + + + References + This Data Type has no references. + Used in Document Types + No references to Document Types. + Used in Media Types + No references to Media Types. + Used in Member Types + No references to Member Types. + Used by + + + Log Levels + Saved Searches + Total Items + Timestamp + Level + Machine + Message + Exception + Properties + Search With Google + Search this message with Google + Search With Bing + Search this message with Bing + Search Our Umbraco + Search this message on Our Umbraco forums and docs + Search Our Umbraco with Google + Search Our Umbraco forums using Google + Search Umbraco Source + Search within Umbraco source code on Github + Search Umbraco Issues + Search Umbraco Issues on Github + Delete this search + Find Logs with Request ID + Find Logs with Namespace + Find Logs with Machine Name + Open + + + Copy %0% + %0% from %1% + Remove all items + + + Open Property Actions + + + Wait + Refresh status + Memory Cache + + + + Reload + Database Cache + + Rebuilding can be expensive. + Use it when reloading is not enough, and you think that the database cache has not been + properly generated—which would indicate some critical Umbraco issue. + ]]> + + Rebuild + Internals + + not need to use it. + ]]> + + Collect + Published Cache Status + Caches + + + Performance profiling + + + Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages. +

+

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

+

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

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

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

+

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

+ ]]> +
+ + + Hours of Umbraco training videos are only a click away + + Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos

+ ]]> +
+ To get you started + + + Start here + This section contains the building blocks for your Umbraco site. Follow the below links to find out more about working with the items in the Settings section + Find out more + + in the Documentation section of Our Umbraco + ]]> + + + Community Forum + ]]> + + + tutorial videos (some are free, some require a subscription) + ]]> + + + productivity boosting tools and commercial support + ]]> + + + training and certification opportunities + ]]> + + + + Welcome to The Friendly CMS + Thank you for choosing Umbraco - we think this could be the beginning of something beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast as possible. + + + Umbraco Forms + Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it! + +
From e85640c48338a2e407b181930b940ef53ab3fb39 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 16 Jan 2020 15:15:04 +0100 Subject: [PATCH 174/224] V8: Set tab focus on newly created nested content items (#6914) * Set tab focus on newly created nested content items * Fix bad merge * Undo unintentional change --- .../nestedcontent/nestedcontent.propertyeditor.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 47293e433b..91afbf3ba9 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 @@ -8,7 +8,7 @@
-
+
From dbe088eedb89b18a3813af1ae523c01e6e93b75d Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 16 Jan 2020 15:36:54 +0100 Subject: [PATCH 175/224] V8: Add infinite editing to datatype references tab (#6907) * Add infinite editing to datatype references tab * Use correct save button label in infinite editing mode --- .../src/common/services/editor.service.js | 18 ++++++ .../views/datatype.info.controller.js | 56 ++++++++++++++++++- .../views/datatypes/views/datatype.info.html | 6 +- .../src/views/membertypes/edit.controller.js | 34 ++++++++--- .../src/views/membertypes/edit.html | 11 +++- 5 files changed, 112 insertions(+), 13 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 1d80d3a3ed..284a7db4d8 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 @@ -640,6 +640,23 @@ When building a custom infinite editor view you can use the same components as a editor.view = "views/mediatypes/edit.html"; 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 @@ -1011,6 +1028,7 @@ When building a custom infinite editor view you can use the same components as a iconPicker: iconPicker, documentTypeEditor: documentTypeEditor, mediaTypeEditor: mediaTypeEditor, + memberTypeEditor: memberTypeEditor, queryBuilder: queryBuilder, treePicker: treePicker, nodePermissions: nodePermissions, diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.controller.js index d4aff871a2..be8ddba592 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.controller.js @@ -6,7 +6,7 @@ * @description * The controller for the info view of the datatype editor */ -function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsService, $timeout) { +function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsService, $timeout, editorService) { var vm = this; var evts = []; @@ -17,6 +17,9 @@ function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsSe vm.view = {}; vm.view.loading = true; + vm.openDocumentType = openDocumentType; + vm.openMediaType = openMediaType; + vm.openMemberType = openMemberType; /** Loads in the data type references one time */ function loadRelations() { @@ -31,6 +34,57 @@ function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsSe } } + function openDocumentType(id, event) { + open(id, event, "documentType"); + } + + function openMediaType(id, event) { + open(id, event, "mediaType"); + } + + function openMemberType(id, event) { + open(id, event, "memberType"); + } + + function open(id, event, type) { + // targeting a new tab/window? + if (event.ctrlKey || + event.shiftKey || + event.metaKey || // apple + (event.button && event.button === 1) // middle click, >IE9 + everyone else + ) { + // yes, let the link open itself + return; + } + event.stopPropagation(); + event.preventDefault(); + + const editor = { + id: id, + submit: function (model) { + editorService.close(); + vm.view.loading = true; + referencesLoaded = false; + loadRelations(); + }, + close: function () { + editorService.close(); + } + }; + + switch (type) { + case "documentType": + editorService.documentTypeEditor(editor); + break; + case "mediaType": + editorService.mediaTypeEditor(editor); + break; + case "memberType": + editorService.memberTypeEditor(editor); + break; + } + } + // load data type references when the references tab is activated evts.push(eventsService.on("app.tabChange", function (event, args) { $timeout(function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.html index 16b2d4b263..0af1634b29 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.html @@ -43,7 +43,7 @@
{{::reference.name}}
{{::reference.alias}}
{{::reference.properties | umbCmsJoinArray:', ':'name'}}
- +
@@ -73,7 +73,7 @@
{{::reference.name}}
{{::reference.alias}}
{{::reference.properties | umbCmsJoinArray:', ':'name'}}
- +
@@ -104,7 +104,7 @@
{{::reference.name}}
{{::reference.alias}}
{{::reference.properties | umbCmsJoinArray:', ':'name'}}
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js index ab54a453b5..b2e515e187 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js @@ -13,8 +13,13 @@ var evts = []; var vm = this; + var infiniteMode = $scope.model && $scope.model.infiniteMode; + var memberTypeId = infiniteMode ? $scope.model.id : $routeParams.id; + var create = infiniteMode ? $scope.model.create : $routeParams.create; vm.save = save; + vm.close = close; + vm.editorfor = "visuallyHiddenTexts_newMember"; vm.header = {}; vm.header.editorfor = "content_membergroup"; @@ -25,6 +30,7 @@ vm.page.loading = false; vm.page.saveButtonState = "init"; vm.labels = {}; + vm.saveButtonKey = infiniteMode ? "buttons_saveAndClose" : "buttons_save"; var labelKeys = [ "general_design", @@ -86,7 +92,7 @@ vm.page.defaultButton = { hotKey: "ctrl+s", hotKeyWhenHidden: true, - labelKey: "buttons_save", + labelKey: vm.saveButtonKey, letter: "S", type: "submit", handler: function () { vm.save(); } @@ -94,7 +100,7 @@ vm.page.subButtons = [{ hotKey: "ctrl+g", hotKeyWhenHidden: true, - labelKey: "buttons_saveAndGenerateModels", + labelKey: infiniteMode ? "buttons_generateModelsAndClose" : "buttons_saveAndGenerateModels", letter: "G", handler: function () { @@ -147,12 +153,12 @@ } }); - if ($routeParams.create) { + if (create) { vm.page.loading = true; //we are creating so get an empty data type item - memberTypeResource.getScaffold($routeParams.id) + memberTypeResource.getScaffold(memberTypeId) .then(function (dt) { init(dt); @@ -163,10 +169,12 @@ vm.page.loading = true; - memberTypeResource.getById($routeParams.id).then(function (dt) { + memberTypeResource.getById(memberTypeId).then(function (dt) { init(dt); - syncTreeNode(vm.contentType, dt.path, true); + if(!infiniteMode) { + syncTreeNode(vm.contentType, dt.path, true); + } vm.page.loading = false; }); @@ -219,10 +227,16 @@ } }).then(function (data) { //success - syncTreeNode(vm.contentType, data.path); + if(!infiniteMode) { + syncTreeNode(vm.contentType, data.path); + } vm.page.saveButtonState = "success"; + if(infiniteMode && $scope.model.submit) { + $scope.model.submit(); + } + deferred.resolve(data); }, function (err) { //error @@ -307,6 +321,12 @@ }); } + + function close() { + if(infiniteMode && $scope.model.close) { + $scope.model.close(); + } + } evts.push(eventsService.on("editors.groupsBuilder.changed", function(name, args) { angularHelper.getCurrentForm($scope).$setDirty(); diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.html b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.html index 07824bc7ec..c4c521c857 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.html @@ -40,6 +40,14 @@ + + + + label-key="{{vm.saveButtonKey}}"> Date: Thu, 16 Jan 2020 16:11:08 +0100 Subject: [PATCH 176/224] Responsive Angular API documentation (#7030) --- src/Umbraco.Web.UI.Docs/umb-docs.css | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Umbraco.Web.UI.Docs/umb-docs.css b/src/Umbraco.Web.UI.Docs/umb-docs.css index 931c22b255..0f2e3e7f74 100644 --- a/src/Umbraco.Web.UI.Docs/umb-docs.css +++ b/src/Umbraco.Web.UI.Docs/umb-docs.css @@ -7,6 +7,21 @@ body { } +.container, .navbar-static-top .container, .navbar-fixed-top .container, .navbar-fixed-bottom .container { + max-width: 1500px; + width: 95%; +} + +.span3 { + width: 220px; + width: calc(90% / 12 * 3); +} + +.span9 { + width: 700px; + width: calc(90% / 12 * 9); +} + .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { font-family: inherit; font-weight: 400; From 4c68f7e53a324aae9fb0dbc57de5f0ab967e4bde Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Thu, 16 Jan 2020 16:13:25 +0100 Subject: [PATCH 177/224] List view configuration - Sort order distance fix (#6905) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks, Jan. You're a 🌟 --- .../lib/bootstrap/less/type.less | 4 ++++ .../listview/orderDirection.prevalues.html | 12 +++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less index bf1167f950..3f93deaf56 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less @@ -132,6 +132,10 @@ ol.inline { display: inline-block; padding-left: 5px; padding-right: 5px; + + &.-no-padding-left{ + padding-left: 0; + } } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/orderDirection.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/orderDirection.prevalues.html index 83a905ccf7..7ec0895936 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/orderDirection.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/orderDirection.prevalues.html @@ -1,10 +1,16 @@ 
- - +
    +
  • + +
  • +
  • + +
  • +
- Required + Required
From 4d5f34a63ddb00507bffbf409366834bdb59c627 Mon Sep 17 00:00:00 2001 From: Kieron Boswell Date: Thu, 16 Jan 2020 16:15:07 +0000 Subject: [PATCH 178/224] Spelling mistake on word Success in instructional comment. (#6966) --- .../Umbraco/PartialViewMacros/Templates/EditProfile.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml index 7034404ca3..74ec033f25 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml @@ -23,7 +23,7 @@ { if (success) { - @* This message will show if RedirectOnSucces is set to false (default) *@ + @* This message will show if profileModel.RedirectUrl is not defined (default) *@

Profile updated

} From f5a5a9a2971fd87829f5a7561e8a0afc0f9d2003 Mon Sep 17 00:00:00 2001 From: Kieron Boswell Date: Thu, 16 Jan 2020 16:19:54 +0000 Subject: [PATCH 179/224] Spelling mistake in comments of partial snippet (#6975) --- .../Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml index 17ed95ea31..804e2307f0 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml @@ -45,7 +45,7 @@ @if (success) { - @* This message will show if RedirectOnSucces is set to false (default) *@ + @* This message will show if registerModel.RedirectUrl is not defined (default) *@

Registration succeeded.

} else From b272a3b791a80c566459198df8e11ab920b20130 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 16 Jan 2020 19:39:17 +0100 Subject: [PATCH 180/224] https://github.com/umbraco/Umbraco-CMS/issues/7466 - Fixes issue with external ModelsBuilder and Dll or LiveDll mode. The issue is because the Mode is determined in the contructor, and not lazy. --- .../Configuration/ModelsBuilderConfig.cs | 90 ++++++++++++------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs index c6bccdcf87..b5c66b649c 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs @@ -12,6 +12,9 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration ///
public class ModelsBuilderConfig : IModelsBuilderConfig { + private const string prefix = "Umbraco.ModelsBuilder."; + private ModelsMode? _modelsMode; + private bool? _flagOutOfDateModels; public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; public const string DefaultModelsDirectory = "~/App_Data/Models"; @@ -20,8 +23,6 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration ///
public ModelsBuilderConfig() { - const string prefix = "Umbraco.ModelsBuilder."; - // giant kill switch, default: false // must be explicitely set to true for anything else to happen Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true"; @@ -34,36 +35,11 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration // stop here, everything is false if (!Enable) return; - // mode - var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"]; - if (!string.IsNullOrWhiteSpace(modelsMode)) - { - switch (modelsMode) - { - case nameof(ModelsMode.Nothing): - ModelsMode = ModelsMode.Nothing; - break; - case nameof(ModelsMode.PureLive): - ModelsMode = ModelsMode.PureLive; - break; - case nameof(ModelsMode.AppData): - ModelsMode = ModelsMode.AppData; - break; - case nameof(ModelsMode.LiveAppData): - ModelsMode = ModelsMode.LiveAppData; - break; - default: - throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." - + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode)))); - } - } - // default: false AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true"); // 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"]; @@ -92,9 +68,6 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration DebugLevel = debugLevel; } - // not flagging if not generating, or live (incl. pure) - if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) - FlagOutOfDateModels = false; } /// @@ -111,11 +84,11 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration int debugLevel = 0) { Enable = enable; - ModelsMode = modelsMode; + _modelsMode = modelsMode; ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace; EnableFactory = enableFactory; - FlagOutOfDateModels = flagOutOfDateModels; + _flagOutOfDateModels = flagOutOfDateModels; ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory; AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory; DebugLevel = debugLevel; @@ -164,7 +137,39 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// /// Gets the models mode. /// - public ModelsMode ModelsMode { get; } + public ModelsMode ModelsMode + { + get + { + if (!_modelsMode.HasValue) + { + // 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)))); + } + } + } + return _modelsMode.Value; + } + } /// /// Gets a value indicating whether system.web/compilation/@debug is true. @@ -196,7 +201,24 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// Models become out-of-date when data types or content types are updated. When this /// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are /// generated through the dashboard, the files is cleared. Default value is false. - public bool FlagOutOfDateModels { get; } + public bool FlagOutOfDateModels + { + get + { + if (!_flagOutOfDateModels.HasValue) + { + var flagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false"); + + if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) + { + flagOutOfDateModels = false; + } + + _flagOutOfDateModels = flagOutOfDateModels; + } + return _flagOutOfDateModels.Value; + } + } /// /// Gets the models directory. From f3d9a01800765b26478a2495c02c9c072571f7af Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 16 Jan 2020 23:00:28 +0100 Subject: [PATCH 181/224] Hide the "Add" button for Nested Content in "single mode" (#7327) --- .../nestedcontent/nestedcontent.propertyeditor.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 91afbf3ba9..da6e466b50 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html @@ -40,7 +40,7 @@ - public class ModelsBuilderConfig : IModelsBuilderConfig { - private const string prefix = "Umbraco.ModelsBuilder."; - private ModelsMode? _modelsMode; - private bool? _flagOutOfDateModels; + private const string Prefix = "Umbraco.ModelsBuilder."; + private object _modelsModelLock; + private bool _modelsModelConfigured; + private ModelsMode _modelsMode; + private object _flagOutOfDateModelsLock; + private bool _flagOutOfDateModelsConfigured; + private bool _flagOutOfDateModels; public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; public const string DefaultModelsDirectory = "~/App_Data/Models"; @@ -25,7 +30,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration { // giant kill switch, default: false // must be explicitely set to true for anything else to happen - Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true"; + Enable = ConfigurationManager.AppSettings[Prefix + "Enable"] == "true"; // ensure defaults are initialized for tests ModelsNamespace = DefaultModelsNamespace; @@ -36,18 +41,18 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration if (!Enable) return; // default: false - AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true"); + AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[Prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true"); // default: true - EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false"); + EnableFactory = !ConfigurationManager.AppSettings[Prefix + "EnableFactory"].InvariantEquals("false"); // default: initialized above with DefaultModelsNamespace const - var value = ConfigurationManager.AppSettings[prefix + "ModelsNamespace"]; + var value = ConfigurationManager.AppSettings[Prefix + "ModelsNamespace"]; if (!string.IsNullOrWhiteSpace(value)) ModelsNamespace = value; // default: initialized above with DefaultModelsDirectory const - value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"]; + value = ConfigurationManager.AppSettings[Prefix + "ModelsDirectory"]; if (!string.IsNullOrWhiteSpace(value)) { var root = IOHelper.MapPath("~/"); @@ -59,11 +64,10 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration } // default: 0 - value = ConfigurationManager.AppSettings[prefix + "DebugLevel"]; + value = ConfigurationManager.AppSettings[Prefix + "DebugLevel"]; if (!string.IsNullOrWhiteSpace(value)) { - int debugLevel; - if (!int.TryParse(value, out debugLevel)) + if (!int.TryParse(value, out var debugLevel)) throw new ConfigurationErrorsException($"Invalid debug level \"{value}\"."); DebugLevel = debugLevel; } @@ -137,39 +141,26 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// /// Gets the models mode. /// - public ModelsMode ModelsMode - { - get + public ModelsMode ModelsMode => + LazyInitializer.EnsureInitialized(ref _modelsMode, ref _modelsModelConfigured, ref _modelsModelLock, () => { - if (!_modelsMode.HasValue) + // mode + var modelsMode = ConfigurationManager.AppSettings[Prefix + "ModelsMode"]; + if (string.IsNullOrWhiteSpace(modelsMode)) return ModelsMode.Nothing; //default + switch (modelsMode) { - // 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)))); - } - } + case nameof(ModelsMode.Nothing): + return ModelsMode.Nothing; + case nameof(ModelsMode.PureLive): + return ModelsMode.PureLive; + case nameof(ModelsMode.AppData): + return ModelsMode.AppData; + case nameof(ModelsMode.LiveAppData): + return ModelsMode.LiveAppData; + default: + throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode)))); } - return _modelsMode.Value; - } - } + }); /// /// Gets a value indicating whether system.web/compilation/@debug is true. @@ -202,23 +193,16 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// 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 + => LazyInitializer.EnsureInitialized(ref _flagOutOfDateModels, ref _flagOutOfDateModelsConfigured, ref _flagOutOfDateModelsLock, () => { - if (!_flagOutOfDateModels.HasValue) + var flagOutOfDateModels = !ConfigurationManager.AppSettings[Prefix + "FlagOutOfDateModels"].InvariantEquals("false"); + if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) { - var flagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false"); - - if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) - { - flagOutOfDateModels = false; - } - - _flagOutOfDateModels = flagOutOfDateModels; + flagOutOfDateModels = false; } - return _flagOutOfDateModels.Value; - } - } + + return flagOutOfDateModels; + }); /// /// Gets the models directory. From 5ddc4b8efb73b15b1ab99286fa27cb042fc39057 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 17 Jan 2020 11:51:31 +0100 Subject: [PATCH 183/224] Bump version to 8.5.2 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 3ffc767f06..9dba3adef1 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.5.1")] -[assembly: AssemblyInformationalVersion("8.5.1")] +[assembly: AssemblyFileVersion("8.5.2")] +[assembly: AssemblyInformationalVersion("8.5.2")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 87712d497e..0c80ccb70b 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -345,9 +345,9 @@ False True - 8510 + 8520 / - http://localhost:8510 + http://localhost:8520 False False From 6637bf520c62e2eab5717b74e68c9206595c4896 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 17 Jan 2020 13:22:13 +0100 Subject: [PATCH 184/224] https://github.com/umbraco/Umbraco-CMS/issues/7469 - Fixed Ambiguous extension method issue by renaming our extension method from Value to GetValue --- .../PublishedElementExtensions.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs index 29429ba74f..33b1803169 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs @@ -17,14 +17,13 @@ namespace Umbraco.Web /// /// Gets the value of a property. /// - public static TValue Value(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) + public static TValue GetValue(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) @@ -45,7 +44,7 @@ namespace Umbraco.Web var attribute = member.GetCustomAttribute(); if (attribute == null) throw new InvalidOperationException("Property is not marked with ImplementPropertyType attribute."); - + return attribute.Alias; } } From baef282b108599166fcf470bbc07ec5923519270 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 17 Jan 2020 13:53:03 +0100 Subject: [PATCH 185/224] https://github.com/umbraco/Umbraco-CMS/issues/7469 - Fixed Ambiguous extension method issue by renaming our extension method from Value to GetValue --- .../PublishedElementExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs index 33b1803169..2c22caf87d 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs @@ -17,13 +17,14 @@ namespace Umbraco.Web /// /// Gets the value of a property. /// - public static TValue GetValue(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) + public static TValue ValueByExpression(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); } + //This cannot be public due to ambiguous issue with external ModelsBuilder if we do not rename. private static string GetAlias(TModel model, Expression> property) { if (property.NodeType != ExpressionType.Lambda) From bcf4c97270792285d2717eb0faaa7452bfb2a5a3 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 17 Jan 2020 13:58:18 +0100 Subject: [PATCH 186/224] V8: Support culture variant URLs in multi URL picker (#7130) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks, Kenn - great work as usual 👍 --- .../src/common/resources/entity.resource.js | 27 +++++++++++++-- .../multiurlpicker.controller.js | 10 ++++++ src/Umbraco.Web/Editors/EntityController.cs | 33 ++++++++++++++++++- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 9cf1181cfa..61d646afc0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -127,6 +127,25 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve url for id:' + id); }, + getUrlByUdi: function (udi, culture) { + + if (!udi) { + return ""; + } + + if (!culture) { + culture = ""; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetUrl", + [{ udi: udi }, {culture: culture }])), + 'Failed to retrieve url for UDI:' + udi); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getById @@ -166,18 +185,22 @@ function entityResource($q, $http, umbRequestHelper) { }, - getUrlAndAnchors: function (id) { + getUrlAndAnchors: function (id, culture) { if (id === -1 || id === "-1") { return null; } + if (!culture) { + culture = ""; + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetUrlAndAnchors", - [{ id: id }])), + [{ id: id }, {culture: culture }])), 'Failed to retrieve url and anchors data for id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index 951b593b8b..2e4313ec76 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -144,6 +144,16 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en if ($scope.model.validation && $scope.model.validation.mandatory && !$scope.model.config.minNumber) { $scope.model.config.minNumber = 1; } + + _.each($scope.model.value, function (item){ + // we must reload the "document" link URLs to match the current editor culture + if (item.udi.indexOf("/document/") > 0) { + item.url = null; + entityResource.getUrlByUdi(item.udi).then(function (data) { + item.url = data; + }); + } + }); } init(); diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 0513017b70..ca5bec4fce 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -206,6 +206,35 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } + /// + /// Gets the url of an entity + /// + /// UDI of the entity to fetch URL for + /// The culture to fetch the URL for + /// The URL or path to the item + public HttpResponseMessage GetUrl(Udi udi, string culture = "*") + { + var intId = Services.EntityService.GetId(udi); + if (!intId.Success) + throw new HttpResponseException(HttpStatusCode.NotFound); + UmbracoEntityTypes entityType; + switch(udi.EntityType) + { + case Constants.UdiEntityType.Document: + entityType = UmbracoEntityTypes.Document; + break; + case Constants.UdiEntityType.Media: + entityType = UmbracoEntityTypes.Media; + break; + case Constants.UdiEntityType.Member: + entityType = UmbracoEntityTypes.Member; + break; + default: + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetUrl(intId.Result, entityType, culture); + } + /// /// Gets the url of an entity /// @@ -303,7 +332,9 @@ namespace Umbraco.Web.Editors [HttpGet] public UrlAndAnchors GetUrlAndAnchors(int id, string culture = "*") { - var url = UmbracoContext.UrlProvider.GetUrl(id); + culture = culture ?? ClientCulture(); + + var url = UmbracoContext.UrlProvider.GetUrl(id, culture: culture); var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id, culture); return new UrlAndAnchors(url, anchorValues); } From 05f7d24a9a4b8bfea675b70243c396408e7cfce5 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 17 Jan 2020 22:42:05 +0100 Subject: [PATCH 187/224] Add VariesBySegment and VariesByCultureAndSegment extension methods for ISimpleContentType --- src/Umbraco.Core/ContentVariationExtensions.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index b3688b08b4..4a918ca70d 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -27,6 +27,24 @@ namespace Umbraco.Core /// public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture(); + /// + /// Determines whether the content type varies by segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// + public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment(); + + /// + /// Determines whether the content type varies by culture and segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this ISimpleContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); + /// /// Determines whether the content type is invariant. /// From d9dfea8ef55de0d07d0159a4752a34b5cda557e8 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 17 Jan 2020 22:43:11 +0100 Subject: [PATCH 188/224] Add remarks and better summary on SetVariesBy extension methods --- src/Umbraco.Core/ContentVariationExtensions.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index 4a918ca70d..7cec5df27f 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -305,6 +305,9 @@ namespace Umbraco.Core /// The content type. /// The variation to set or remove. /// If set to true sets the variation; otherwise, removes the variation. + /// + /// This method does not support setting the variation to nothing. + /// public static void SetVariesBy(this IContentTypeBase contentType, ContentVariation variation, bool value = true) => contentType.Variations = contentType.Variations.SetVariesBy(variation, value); /// @@ -313,17 +316,23 @@ namespace Umbraco.Core /// The property type. /// The variation to set or remove. /// If set to true sets the variation; otherwise, removes the variation. + /// + /// This method does not support setting the variation to nothing. + /// public static void SetVariesBy(this PropertyType propertyType, ContentVariation variation, bool value = true) => propertyType.Variations = propertyType.Variations.SetVariesBy(variation, value); /// - /// Sets or removes the variation depending on the specified value. + /// Returns the variations with the variation set or removed depending on the specified value. /// /// The existing variations. /// The variation to set or remove. /// If set to true sets the variation; otherwise, removes the variation. /// - /// The variations with the specified variation set or removed. + /// The variations with the variation set or removed. /// + /// + /// This method does not support setting the variation to nothing. + /// public static ContentVariation SetVariesBy(this ContentVariation variations, ContentVariation variation, bool value = true) { return value From b3829bfff83b3c0df3aac476964c2aa18186be4c Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 17 Jan 2020 22:44:44 +0100 Subject: [PATCH 189/224] Reorder methods to keep overloads adjacent --- .../ContentVariationExtensions.cs | 360 +++++++++--------- 1 file changed, 180 insertions(+), 180 deletions(-) diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index 7cec5df27f..6430c75686 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -18,33 +18,6 @@ namespace Umbraco.Core /// public static bool VariesByNothing(this ISimpleContentType contentType) => contentType.Variations.VariesByNothing(); - /// - /// Determines whether the content type varies by culture. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by culture. - /// - public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture(); - - /// - /// Determines whether the content type varies by segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by segment. - /// - public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment(); - - /// - /// Determines whether the content type varies by culture and segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this ISimpleContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); - /// /// Determines whether the content type is invariant. /// @@ -54,69 +27,6 @@ namespace Umbraco.Core /// public static bool VariesByNothing(this IContentTypeBase contentType) => contentType.Variations.VariesByNothing(); - /// - /// Determines whether the content type varies by culture. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by culture. - /// - public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture(); - - /// - /// Determines whether the content type varies by segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by segment. - /// - public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment(); - - /// - /// Determines whether the content type varies by culture and segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => contentType.Variations.VariesByCultureAndSegment(); - - /// - /// Determines whether the property type is invariant. - /// - /// The property type. - /// - /// A value indicating whether the property type is invariant. - /// - public static bool VariesByNothing(this PropertyType propertyType) => propertyType.Variations.VariesByNothing(); - - /// - /// Determines whether the property type varies by culture. - /// - /// The property type. - /// - /// A value indicating whether the property type varies by culture. - /// - public static bool VariesByCulture(this PropertyType propertyType) => propertyType.Variations.VariesByCulture(); - - /// - /// Determines whether the property type varies by segment. - /// - /// The property type. - /// - /// A value indicating whether the property type varies by segment. - /// - public static bool VariesBySegment(this PropertyType propertyType) => propertyType.Variations.VariesBySegment(); - - /// - /// Determines whether the property type varies by culture and segment. - /// - /// The property type. - /// - /// A value indicating whether the property type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this PropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); - /// /// Determines whether the content type is invariant. /// @@ -127,31 +37,13 @@ namespace Umbraco.Core public static bool VariesByNothing(this IPublishedContentType contentType) => contentType.Variations.VariesByNothing(); /// - /// Determines whether the content type varies by culture. + /// Determines whether the property type is invariant. /// - /// The content type. + /// The property type. /// - /// A value indicating whether the content type varies by culture. + /// A value indicating whether the property type is invariant. /// - public static bool VariesByCulture(this IPublishedContentType contentType) => contentType.Variations.VariesByCulture(); - - /// - /// Determines whether the content type varies by segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by segment. - /// - public static bool VariesBySegment(this IPublishedContentType contentType) => contentType.Variations.VariesBySegment(); - - /// - /// Determines whether the content type varies by culture and segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); + public static bool VariesByNothing(this PropertyType propertyType) => propertyType.Variations.VariesByNothing(); /// /// Determines whether the property type is invariant. @@ -162,33 +54,6 @@ namespace Umbraco.Core /// public static bool VariesByNothing(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByNothing(); - /// - /// Determines whether the property type varies by culture. - /// - /// The property type. - /// - /// A value indicating whether the property type varies by culture. - /// - public static bool VariesByCulture(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture(); - - /// - /// Determines whether the property type varies by segment. - /// - /// The property type. - /// - /// A value indicating whether the property type varies by segment. - /// - public static bool VariesBySegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment(); - - /// - /// Determines whether the property type varies by culture and segment. - /// - /// The property type. - /// - /// A value indicating whether the property type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); - /// /// Determines whether a variation is invariant. /// @@ -198,6 +63,51 @@ namespace Umbraco.Core /// public static bool VariesByNothing(this ContentVariation variation) => variation == ContentVariation.Nothing; + /// + /// Determines whether the content type varies by culture. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture. + /// + public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture(); + + /// + /// Determines whether the content type varies by culture. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture. + /// + public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture(); + + /// + /// Determines whether the content type varies by culture. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture. + /// + public static bool VariesByCulture(this IPublishedContentType contentType) => contentType.Variations.VariesByCulture(); + + /// + /// Determines whether the property type varies by culture. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture. + /// + public static bool VariesByCulture(this PropertyType propertyType) => propertyType.Variations.VariesByCulture(); + + /// + /// Determines whether the property type varies by culture. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture. + /// + public static bool VariesByCulture(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture(); + /// /// Determines whether a variation varies by culture. /// @@ -207,6 +117,51 @@ namespace Umbraco.Core /// public static bool VariesByCulture(this ContentVariation variation) => (variation & ContentVariation.Culture) > 0; + /// + /// Determines whether the content type varies by segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// + public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment(); + + /// + /// Determines whether the content type varies by segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// + public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment(); + + /// + /// Determines whether the content type varies by segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// + public static bool VariesBySegment(this IPublishedContentType contentType) => contentType.Variations.VariesBySegment(); + + /// + /// Determines whether the property type varies by segment. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by segment. + /// + public static bool VariesBySegment(this PropertyType propertyType) => propertyType.Variations.VariesBySegment(); + + /// + /// Determines whether the property type varies by segment. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by segment. + /// + public static bool VariesBySegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment(); + /// /// Determines whether a variation varies by segment. /// @@ -216,6 +171,51 @@ namespace Umbraco.Core /// public static bool VariesBySegment(this ContentVariation variation) => (variation & ContentVariation.Segment) > 0; + /// + /// Determines whether the content type varies by culture and segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this ISimpleContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the content type varies by culture and segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => contentType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the content type varies by culture and segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the property type varies by culture and segment. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this PropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the property type varies by culture and segment. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); + /// /// Determines whether a variation varies by culture and segment. /// @@ -225,6 +225,47 @@ namespace Umbraco.Core /// public static bool VariesByCultureAndSegment(this ContentVariation variation) => (variation & ContentVariation.CultureAndSegment) == ContentVariation.CultureAndSegment; + /// + /// Sets or removes the content type variation depending on the specified value. + /// + /// The content type. + /// The variation to set or remove. + /// If set to true sets the variation; otherwise, removes the variation. + /// + /// This method does not support setting the variation to nothing. + /// + public static void SetVariesBy(this IContentTypeBase contentType, ContentVariation variation, bool value = true) => contentType.Variations = contentType.Variations.SetVariesBy(variation, value); + + /// + /// Sets or removes the property type variation depending on the specified value. + /// + /// The property type. + /// The variation to set or remove. + /// If set to true sets the variation; otherwise, removes the variation. + /// + /// This method does not support setting the variation to nothing. + /// + public static void SetVariesBy(this PropertyType propertyType, ContentVariation variation, bool value = true) => propertyType.Variations = propertyType.Variations.SetVariesBy(variation, value); + + /// + /// Returns the variations with the variation set or removed depending on the specified value. + /// + /// The existing variations. + /// The variation to set or remove. + /// If set to true sets the variation; otherwise, removes the variation. + /// + /// The variations with the variation set or removed. + /// + /// + /// This method does not support setting the variation to nothing. + /// + public static ContentVariation SetVariesBy(this ContentVariation variations, ContentVariation variation, bool value = true) + { + return value + ? variations | variation // Set flag using bitwise logical OR + : variations & ~variation; // Remove flag using bitwise logical AND with bitwise complement (reversing the bit) + } + /// /// Validates that a combination of culture and segment is valid for the variation. /// @@ -298,46 +339,5 @@ namespace Umbraco.Core return true; } - - /// - /// Sets or removes the content type variation depending on the specified value. - /// - /// The content type. - /// The variation to set or remove. - /// If set to true sets the variation; otherwise, removes the variation. - /// - /// This method does not support setting the variation to nothing. - /// - public static void SetVariesBy(this IContentTypeBase contentType, ContentVariation variation, bool value = true) => contentType.Variations = contentType.Variations.SetVariesBy(variation, value); - - /// - /// Sets or removes the property type variation depending on the specified value. - /// - /// The property type. - /// The variation to set or remove. - /// If set to true sets the variation; otherwise, removes the variation. - /// - /// This method does not support setting the variation to nothing. - /// - public static void SetVariesBy(this PropertyType propertyType, ContentVariation variation, bool value = true) => propertyType.Variations = propertyType.Variations.SetVariesBy(variation, value); - - /// - /// Returns the variations with the variation set or removed depending on the specified value. - /// - /// The existing variations. - /// The variation to set or remove. - /// If set to true sets the variation; otherwise, removes the variation. - /// - /// The variations with the variation set or removed. - /// - /// - /// This method does not support setting the variation to nothing. - /// - public static ContentVariation SetVariesBy(this ContentVariation variations, ContentVariation variation, bool value = true) - { - return value - ? variations | variation // Set flag using bitwise logical OR - : variations & ~variation; // Remove flag using bitwise logical AND with bitwise complement (reversing the bit) - } } } From bc82ccd82458ac0824baa6547b1f84625857fbea Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sun, 19 Jan 2020 10:34:04 +0100 Subject: [PATCH 190/224] Prevented user from changing a document type to an element type if it's already been used to create content (#5668) --- .../IContentTypeRepositoryBase.cs | 5 ++++ .../Implement/ContentTypeRepositoryBase.cs | 11 ++++++++ .../Services/IContentTypeServiceBase.cs | 5 ++++ ...peServiceBaseOfTRepositoryTItemTService.cs | 9 +++++++ .../Repositories/ContentTypeRepositoryTest.cs | 26 +++++++++++++++++++ .../common/resources/contenttype.resource.js | 10 +++++++ .../permissions/permissions.controller.js | 10 ++++++- .../views/permissions/permissions.html | 2 ++ src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 8 +++--- .../Umbraco/config/lang/en_us.xml | 7 ++--- .../Editors/ContentTypeController.cs | 7 +++++ 11 files changed, 92 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs index 69b0698a96..254e04d2d5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs @@ -26,5 +26,10 @@ namespace Umbraco.Core.Persistence.Repositories /// /// bool HasContainerInPath(string contentPath); + + /// + /// Returns true or false depending on whether content nodes have been created based on the provided content type id. + /// + bool HasContentNodes(int id); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index e2c3d8c9b5..6f714ff187 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -1323,6 +1323,17 @@ WHERE {Constants.DatabaseSchema.Tables.Content}.nodeId IN (@ids) AND cmsContentT return Database.ExecuteScalar(sql) > 0; } + /// + /// Returns true or false depending on whether content nodes have been created based on the provided content type id. + /// + public bool HasContentNodes(int id) + { + var sql = new Sql( + $"SELECT CASE WHEN EXISTS (SELECT * FROM {Constants.DatabaseSchema.Tables.Content} WHERE contentTypeId = @id) THEN 1 ELSE 0 END", + new { id }); + return Database.ExecuteScalar(sql) == 1; + } + protected override IEnumerable GetDeleteClauses() { // in theory, services should have ensured that content items of the given content type diff --git a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs index 51e5d756eb..6ed3c85e91 100644 --- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs @@ -39,6 +39,11 @@ namespace Umbraco.Core.Services int Count(); + /// + /// Returns true or false depending on whether content nodes have been created based on the provided content type id. + /// + bool HasContentNodes(int id); + IEnumerable GetAll(params int[] ids); IEnumerable GetAll(IEnumerable ids); diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 7ae330f8f1..da532e2765 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -372,6 +372,15 @@ namespace Umbraco.Core.Services.Implement } } + public bool HasContentNodes(int id) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(ReadLockIds); + return Repository.HasContentNodes(id); + } + } + #endregion #region Save diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index f953b9cce6..bd80d6b154 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -965,6 +965,32 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Verify_Content_Type_Has_Content_Nodes() + { + // Arrange + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + ContentTypeRepository repository; + var contentRepository = CreateRepository((IScopeAccessor)provider, out repository); + var contentTypeId = NodeDto.NodeIdSeed + 1; + var contentType = repository.Get(contentTypeId); + + // Act + var result = repository.HasContentNodes(contentTypeId); + + var subpage = MockedContent.CreateTextpageContent(contentType, "Test Page 1", contentType.Id); + contentRepository.Save(subpage); + + var result2 = repository.HasContentNodes(contentTypeId); + + // Assert + Assert.That(result, Is.False); + Assert.That(result2, Is.True); + } + } + public void CreateTestData() { //Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index 64accc18c1..97bebef062 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -351,6 +351,16 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateDefaultTemplate", { id: id })), 'Failed to create default template for content type with id ' + id); + }, + + hasContentNodes: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "HasContentNodes", + [{ id: id }])), + 'Failed to retrieve indication for whether content type with id ' + id + ' has associated content nodes'); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.controller.js index a3b422e4f3..a4e8c6373c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.controller.js @@ -25,6 +25,7 @@ vm.removeChild = removeChild; vm.toggleAllowAsRoot = toggleAllowAsRoot; vm.toggleAllowCultureVariants = toggleAllowCultureVariants; + vm.canToggleIsElement = false; vm.toggleIsElement = toggleIsElement; /* ---------- INIT ---------- */ @@ -48,9 +49,16 @@ if($scope.model.id === 0) { contentTypeHelper.insertChildNodePlaceholder(vm.contentTypes, $scope.model.name, $scope.model.icon, $scope.model.id); } - }); + // Can only switch to an element type if there are no content nodes already created from the type. + if ($scope.model.id > 0 && !$scope.model.isElement ) { + contentTypeResource.hasContentNodes($scope.model.id).then(function (result) { + vm.canToggleIsElement = !result; + }); + } else { + vm.canToggleIsElement = true; + } } function addChild($event) { diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html index e890921ad4..d459bd9fae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html @@ -61,11 +61,13 @@
+
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index a2cf6ea475..880700c74a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1643,10 +1643,10 @@ To manage your website, simply open the Umbraco back office and start adding con 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 + Is an element type + An element type is meant to be used for instance in Nested Content, and not in the tree. + A document type cannot be changed to an element type once it has been used to create one or more content items. + This is not applicable for an element type You have made changes to this property. Are you sure you want to discard them? 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 56d5f1ade2..546d085b4a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1658,9 +1658,10 @@ To manage your website, simply open the Umbraco back office and start adding con 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 + Is an element type + An element type is meant to be used for instance in Nested Content, and not in the tree. + A document type cannot be changed to an element type once it has been used to create one or more content items. + This is not applicable for an element type You have made changes to this property. Are you sure you want to discard them? diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 0f51c35a14..a566c2cdc7 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -70,6 +70,13 @@ namespace Umbraco.Web.Editors return Services.ContentTypeService.Count(); } + [HttpGet] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] + public bool HasContentNodes(int id) + { + return Services.ContentTypeService.HasContentNodes(id); + } + public DocumentTypeDisplay GetById(int id) { var ct = Services.ContentTypeService.Get(id); From c27634d8a9b0e467e11466247e09542931d125a5 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 19 Jan 2020 10:50:18 +0100 Subject: [PATCH 191/224] V8: Allow reordering of allowed child types (#4927) * Allow explicitly sorting allowed child types for content types * Allow explicitly sorting allowed child types for media types * Remove console.log * Fix the mapping of allowed content types (after introduction of the new mapper) * Fix sorting after merge --- .../components/umbchildselector.directive.js | 19 ++++++++++++++++++- .../less/components/umb-child-selector.less | 3 +++ .../views/components/umb-child-selector.html | 2 +- .../src/views/content/create.html | 2 +- .../permissions/permissions.controller.js | 10 +++++++++- .../views/permissions/permissions.html | 3 ++- .../permissions/permissions.controller.js | 10 +++++++++- .../views/permissions/permissions.html | 3 ++- .../Editors/ContentTypeController.cs | 6 +++--- .../Editors/MediaTypeController.cs | 6 +++--- .../Mapping/ContentTypeMapDefinition.cs | 2 +- 11 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js index 4fc22c4b74..a33fd4be53 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js @@ -188,6 +188,22 @@ Use this directive to render a ui component for selecting child items to a paren syncParentIcon(); })); + // sortable options for allowed child content types + scope.sortableOptions = { + axis: "y", + containment: "parent", + distance: 10, + opacity: 0.7, + tolerance: "pointer", + scroll: true, + zIndex: 6000, + update: function (e, ui) { + if(scope.onSort) { + scope.onSort(); + } + } + }; + // clean up scope.$on('$destroy', function(){ // unbind watchers @@ -209,7 +225,8 @@ Use this directive to render a ui component for selecting child items to a paren parentIcon: "=", parentId: "=", onRemove: "=", - onAdd: "=" + onAdd: "=", + onSort: "=" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less index b6cdc0e8d9..da690663d0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less @@ -30,6 +30,9 @@ .umb-child-selector__children-container { margin-left: 30px; + .umb-child-selector__child { + cursor: move; + } } .umb-child-selector__child-description { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-child-selector.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-child-selector.html index 686937e8c9..0866e1bdf7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-child-selector.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-child-selector.html @@ -14,7 +14,7 @@ -
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/create.html b/src/Umbraco.Web.UI.Client/src/views/content/create.html index cfb4b2c573..1d4b646e05 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/create.html @@ -36,7 +36,7 @@
    -
  • +
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.controller.js index 1d865f2fa0..53a098a26e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.controller.js @@ -1,7 +1,7 @@ (function() { 'use strict'; - function PermissionsController($scope, mediaTypeResource, iconHelper, contentTypeHelper, localizationService, overlayService) { + function PermissionsController($scope, $timeout, mediaTypeResource, iconHelper, contentTypeHelper, localizationService, overlayService) { /* ----------- SCOPE VARIABLES ----------- */ @@ -13,6 +13,7 @@ vm.addChild = addChild; vm.removeChild = removeChild; + vm.sortChildren = sortChildren; vm.toggle = toggle; /* ---------- INIT ---------- */ @@ -71,6 +72,13 @@ $scope.model.allowedContentTypes.splice(selectedChildIndex, 1); } + function sortChildren() { + // we need to wait until the next digest cycle for vm.selectedChildren to be updated + $timeout(function () { + $scope.model.allowedContentTypes = _.pluck(vm.selectedChildren, "id"); + }); + } + /** * Toggle the $scope.model.allowAsRoot value to either true or false */ diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.html index caa4c4dc11..09e7ab4f41 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/views/permissions/permissions.html @@ -27,7 +27,8 @@ parent-icon="model.icon" parent-id="model.id" on-add="vm.addChild" - on-remove="vm.removeChild"> + on-remove="vm.removeChild" + on-sort="vm.sortChildren">
diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index a566c2cdc7..9c248f186b 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -432,11 +432,11 @@ namespace Umbraco.Web.Editors } var contentType = Services.ContentTypeBaseServices.GetContentTypeOf(contentItem); - var ids = contentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray(); + var ids = contentType.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value).ToArray(); if (ids.Any() == false) return Enumerable.Empty(); - types = Services.ContentTypeService.GetAll(ids).ToList(); + types = Services.ContentTypeService.GetAll(ids).OrderBy(c => ids.IndexOf(c.Id)).ToList(); } var basics = types.Where(type => type.IsElement == false).Select(Mapper.Map).ToList(); @@ -459,7 +459,7 @@ namespace Umbraco.Web.Editors } } - return basics; + return basics.OrderBy(c => contentId == Constants.System.Root ? c.Name : string.Empty); } /// diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index 43569c77e2..3a4026423a 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -240,11 +240,11 @@ namespace Umbraco.Web.Editors } var contentType = Services.MediaTypeService.Get(contentItem.ContentTypeId); - var ids = contentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray(); + var ids = contentType.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value).ToArray(); if (ids.Any() == false) return Enumerable.Empty(); - types = Services.MediaTypeService.GetAll(ids).ToList(); + types = Services.MediaTypeService.GetAll(ids).OrderBy(c => ids.IndexOf(c.Id)).ToList(); } var basics = types.Select(Mapper.Map).ToList(); @@ -255,7 +255,7 @@ namespace Umbraco.Web.Editors basic.Description = TranslateItem(basic.Description); } - return basics.OrderBy(x => x.Name); + return basics.OrderBy(c => contentId == Constants.System.Root ? c.Name : string.Empty); } /// diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index 266cd894b6..f18c481297 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -493,7 +493,7 @@ namespace Umbraco.Web.Models.Mapping target.Udi = MapContentTypeUdi(source); target.UpdateDate = source.UpdateDate; - target.AllowedContentTypes = source.AllowedContentTypes.Select(x => x.Id.Value); + target.AllowedContentTypes = source.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value); target.CompositeContentTypes = source.ContentTypeComposition.Select(x => x.Alias); target.LockedCompositeContentTypes = MapLockedCompositions(source); } From d378495942fe1828571444aeb45e84143059f3c4 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 20 Jan 2020 09:11:29 +0000 Subject: [PATCH 192/224] Log message format fix for PR6617 --- src/Umbraco.Core/Composing/ComponentCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Composing/ComponentCollection.cs b/src/Umbraco.Core/Composing/ComponentCollection.cs index fa4a1849b6..62b240f10f 100644 --- a/src/Umbraco.Core/Composing/ComponentCollection.cs +++ b/src/Umbraco.Core/Composing/ComponentCollection.cs @@ -51,7 +51,7 @@ namespace Umbraco.Core.Composing } catch (Exception ex) { - _logger.Error(componentType, ex, "Error while terminating component {ComponentType}.", componentType.FullName); + _logger.Error(ex, "Error while terminating component {ComponentType}.", componentType.FullName); } } } From 9a7b72c8c12b3cc1fa8233fae67ef9166382debe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20St=C3=B6cker?= <1537988+MarcStoecker@users.noreply.github.com> Date: Mon, 20 Jan 2020 13:39:22 +0100 Subject: [PATCH 193/224] Fix typo in DE "installFinish" caption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (and improve wording: "Abschließen" (~ completing something) instead of "Quit" or "Close" as it was; EN is "Finishing") --- src/Umbraco.Web.UI/Umbraco/config/lang/de.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index e45d97e7bd..9c02879db9 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -1173,7 +1173,7 @@ Installiert Installierte Pakete Lokale Installation - Benenden + Abschließen Diese Paket hat keine Einstellungen Es wurden noche keine Pakete angelegt Sie haben keine Pakete installiert From 7fbe4829196d7f131a4bb5e9a6d8263b25eda8a4 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 20 Jan 2020 14:09:13 +0100 Subject: [PATCH 194/224] Revert "https://github.com/umbraco/Umbraco-CMS/issues/7469 - Fixed Ambiguous extension method issue by renaming our extension method from Value to GetValue" This reverts commit baef282b --- .../PublishedElementExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs index 2c22caf87d..29429ba74f 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs @@ -17,14 +17,14 @@ namespace Umbraco.Web /// /// Gets the value of a property. /// - public static TValue ValueByExpression(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) + 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); } - //This cannot be public due to ambiguous issue with external ModelsBuilder if we do not rename. + // fixme that one should be public so ppl can use it private static string GetAlias(TModel model, Expression> property) { if (property.NodeType != ExpressionType.Lambda) @@ -45,7 +45,7 @@ namespace Umbraco.Web var attribute = member.GetCustomAttribute(); if (attribute == null) throw new InvalidOperationException("Property is not marked with ImplementPropertyType attribute."); - + return attribute.Alias; } } From 8cc78631a08f0084fbcf19a296c1cec348a1d09a Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 21 Jan 2020 13:33:39 +1100 Subject: [PATCH 195/224] Adds TestData project along with test endpoints for enabling/disabling segments --- .../Properties/AssemblyInfo.cs | 36 +++ src/Umbraco.TestData/SegmentTestController.cs | 77 +++++ src/Umbraco.TestData/Umbraco.TestData.csproj | 70 +++++ .../UmbracoTestDataController.cs | 288 ++++++++++++++++++ src/Umbraco.TestData/readme.md | 51 ++++ src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 + src/Umbraco.Web.UI/web.Template.Debug.config | 5 + src/umbraco.sln | 7 + 8 files changed, 538 insertions(+) create mode 100644 src/Umbraco.TestData/Properties/AssemblyInfo.cs create mode 100644 src/Umbraco.TestData/SegmentTestController.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/SegmentTestController.cs b/src/Umbraco.TestData/SegmentTestController.cs new file mode 100644 index 0000000000..bcfdfc4ac4 --- /dev/null +++ b/src/Umbraco.TestData/SegmentTestController.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Mvc; + +namespace Umbraco.TestData +{ + public class SegmentTestController : SurfaceController + { + + public ActionResult EnableDocTypeSegments(string alias, string propertyTypeAlias) + { + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return HttpNotFound(); + + var ct = Services.ContentTypeService.Get(alias); + if (ct == null) + return Content($"No document type found by alias {alias}"); + + var propType = ct.PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias); + if (propType == null) + return Content($"The document type {alias} does not have a property type {propertyTypeAlias ?? "null"}"); + + if (ct.Variations.VariesBySegment()) + return Content($"The document type {alias} already allows segments, nothing has been changed"); + + ct.Variations = ct.Variations.SetFlag(ContentVariation.Segment); + + propType.Variations = propType.Variations.SetFlag(ContentVariation.Segment); + + Services.ContentTypeService.Save(ct); + return Content($"The document type {alias} and property type {propertyTypeAlias} now allows segments"); + } + + public ActionResult DisableDocTypeSegments(string alias) + { + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return HttpNotFound(); + + var ct = Services.ContentTypeService.Get(alias); + if (ct == null) + return Content($"No document type found by alias {alias}"); + + if (!ct.VariesBySegment()) + return Content($"The document type {alias} does not allow segments, nothing has been changed"); + + ct.Variations = ct.Variations.UnsetFlag(ContentVariation.Segment); + + Services.ContentTypeService.Save(ct); + return Content($"The document type {alias} no longer allows segments"); + } + + public ActionResult AddSegmentData(int contentId, string propertyAlias, string value, string segment, string culture = null) + { + var content = Services.ContentService.GetById(contentId); + if (content == null) + return Content($"No content found by id {contentId}"); + + if (!content.HasProperty(propertyAlias)) + return Content($"The content by id {contentId} does not contain a property with alias {propertyAlias}"); + + if (content.ContentType.VariesByCulture() && culture.IsNullOrWhiteSpace()) + return Content($"The content by id {contentId} varies by culture but no culture was specified"); + + content.SetValue(propertyAlias, value, culture, segment); + Services.ContentService.Save(content); + + return Content($"Segment value has been set on content {contentId} for property {propertyAlias}"); + } + } +} diff --git a/src/Umbraco.TestData/Umbraco.TestData.csproj b/src/Umbraco.TestData/Umbraco.TestData.csproj new file mode 100644 index 0000000000..d61321ebb8 --- /dev/null +++ b/src/Umbraco.TestData/Umbraco.TestData.csproj @@ -0,0 +1,70 @@ + + + + + Debug + AnyCPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC} + Library + Properties + Umbraco.TestData + Umbraco.TestData + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + {31785bc3-256c-4613-b2f5-a1b0bdded8c1} + Umbraco.Core + + + {651e1350-91b6-44b7-bd60-7207006d7003} + Umbraco.Web + + + + + 28.4.4 + + + 5.2.7 + + + + \ No newline at end of file diff --git a/src/Umbraco.TestData/UmbracoTestDataController.cs b/src/Umbraco.TestData/UmbracoTestDataController.cs new file mode 100644 index 0000000000..402c05cc1c --- /dev/null +++ b/src/Umbraco.TestData/UmbracoTestDataController.cs @@ -0,0 +1,288 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core; +using System.Web.Mvc; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web; +using Umbraco.Web.Mvc; +using System.Configuration; +using Bogus; +using Umbraco.Core.Scoping; + +namespace Umbraco.TestData +{ + /// + /// Creates test data + /// + public class UmbracoTestDataController : SurfaceController + { + private const string RichTextDataTypeName = "UmbracoTestDataContent.RTE"; + private const string MediaPickerDataTypeName = "UmbracoTestDataContent.MediaPicker"; + private const string TextDataTypeName = "UmbracoTestDataContent.Text"; + private const string TestDataContentTypeAlias = "umbTestDataContent"; + private readonly IScopeProvider _scopeProvider; + private readonly PropertyEditorCollection _propertyEditors; + + public UmbracoTestDataController(IScopeProvider scopeProvider, PropertyEditorCollection propertyEditors, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, ILogger logger, IProfilingLogger profilingLogger, UmbracoHelper umbracoHelper) : base(umbracoContextAccessor, databaseFactory, services, appCaches, logger, profilingLogger, umbracoHelper) + { + _scopeProvider = scopeProvider; + _propertyEditors = propertyEditors; + } + + /// + /// Creates a content and associated media tree (hierarchy) + /// + /// + /// + /// + /// + /// + /// Each content item created is associated to a media item via a media picker and therefore a relation is created between the two + /// + public ActionResult CreateTree(int count, int depth, string locale = "en") + { + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return HttpNotFound(); + + if (!Validate(count, depth, out var message, out var perLevel)) + throw new InvalidOperationException(message); + + var faker = new Faker(locale); + var company = faker.Company.CompanyName(); + + using (var scope = _scopeProvider.CreateScope()) + { + var imageIds = CreateMediaTree(company, faker, count, depth).ToList(); + var contentIds = CreateContentTree(company, faker, count, depth, imageIds, out var root).ToList(); + + Services.ContentService.SaveAndPublishBranch(root, true); + + scope.Complete(); + } + + + return Content("Done"); + } + + private bool Validate(int count, int depth, out string message, out int perLevel) + { + perLevel = 0; + message = null; + + if (count <= 0) + { + message = "Count must be more than 0"; + return false; + } + + perLevel = count / depth; + if (perLevel < 1) + { + message = "Count not high enough for specified for number of levels required"; + return false; + } + + return true; + } + + /// + /// Utility to create a tree hierarchy + /// + /// + /// + /// + /// + /// + /// A callback that returns a tuple of Content and another callback to produce a Container. + /// For media, a container will be another folder, for content the container will be the Content itself. + /// + /// + private IEnumerable CreateHierarchy( + T parent, int count, int depth, + Func container)> create) + where T: class, IContentBase + { + yield return parent.GetUdi(); + + // This will not calculate a balanced tree but it will ensure that there will be enough nodes deep enough to not fill up the tree. + var totalDescendants = count - 1; + var perLevel = Math.Ceiling(totalDescendants / (double)depth); + var perBranch = Math.Ceiling(perLevel / depth); + + var tracked = new Stack<(T parent, int childCount)>(); + + var currChildCount = 0; + + for (int i = 0; i < count; i++) + { + var created = create(parent); + var contentItem = created.content; + + yield return contentItem.GetUdi(); + + currChildCount++; + + if (currChildCount == perBranch) + { + // move back up... + + var prev = tracked.Pop(); + + // restore child count + currChildCount = prev.childCount; + // restore the parent + parent = prev.parent; + + } + else if (contentItem.Level < depth) + { + // track the current parent and it's current child count + tracked.Push((parent, currChildCount)); + + // not at max depth, create below + parent = created.container(); + + currChildCount = 0; + } + + } + } + + /// + /// Creates the media tree hiearachy + /// + /// + /// + /// + /// + /// + private IEnumerable CreateMediaTree(string company, Faker faker, int count, int depth) + { + var parent = Services.MediaService.CreateMediaWithIdentity(company, -1, Constants.Conventions.MediaTypes.Folder); + + return CreateHierarchy(parent, count, depth, currParent => + { + var imageUrl = faker.Image.PicsumUrl(); + + // we are appending a &ext=.jpg to the end of this for a reason. The result of this url will be something like: + // https://picsum.photos/640/480/?image=106 + // and due to the way that we detect images there must be an extension so we'll change it to + // https://picsum.photos/640/480/?image=106&ext=.jpg + // which will trick our app into parsing this and thinking it's an image ... which it is so that's good. + // if we don't do this we don't get thumbnails in the back office. + imageUrl += "&ext=.jpg"; + + var media = Services.MediaService.CreateMedia(faker.Commerce.ProductName(), currParent, Constants.Conventions.MediaTypes.Image); + media.SetValue(Constants.Conventions.Media.File, imageUrl); + Services.MediaService.Save(media); + return (media, () => + { + // create a folder to contain child media + var container = Services.MediaService.CreateMediaWithIdentity(faker.Commerce.Department(), currParent, Constants.Conventions.MediaTypes.Folder); + return container; + }); + }); + } + + /// + /// Creates the content tree hiearachy + /// + /// + /// + /// + /// + /// + /// + private IEnumerable CreateContentTree(string company, Faker faker, int count, int depth, List imageIds, out IContent root) + { + var random = new Random(company.GetHashCode()); + + var docType = GetOrCreateContentType(); + + var parent = Services.ContentService.Create(company, -1, docType.Alias); + parent.SetValue("review", faker.Rant.Review()); + parent.SetValue("desc", company); + parent.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]); + Services.ContentService.Save(parent); + + root = parent; + + return CreateHierarchy(parent, count, depth, currParent => + { + var content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias); + content.SetValue("review", faker.Rant.Review()); + content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective()))); ; + content.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]); + + Services.ContentService.Save(content); + return (content, () => content); + }); + + } + + private IContentType GetOrCreateContentType() + { + var docType = Services.ContentTypeService.Get(TestDataContentTypeAlias); + if (docType != null) + return docType; + + docType = new ContentType(-1) + { + Alias = TestDataContentTypeAlias, + Name = "Umbraco Test Data Content", + Icon = "icon-science color-green" + }; + docType.AddPropertyGroup("Content"); + docType.AddPropertyType(new PropertyType(GetOrCreateRichText(), "review") + { + Name = "Review" + }); + docType.AddPropertyType(new PropertyType(GetOrCreateMediaPicker(), "media") + { + Name = "Media" + }); + docType.AddPropertyType(new PropertyType(GetOrCreateText(), "desc") + { + Name = "Description" + }); + Services.ContentTypeService.Save(docType); + docType.AllowedContentTypes = new[] { new ContentTypeSort(docType.Id, 0) }; + Services.ContentTypeService.Save(docType); + return docType; + } + + private IDataType GetOrCreateRichText() => GetOrCreateDataType(RichTextDataTypeName, Constants.PropertyEditors.Aliases.TinyMce); + + private IDataType GetOrCreateMediaPicker() => GetOrCreateDataType(MediaPickerDataTypeName, Constants.PropertyEditors.Aliases.MediaPicker); + + private IDataType GetOrCreateText() => GetOrCreateDataType(TextDataTypeName, Constants.PropertyEditors.Aliases.TextBox); + + private IDataType GetOrCreateDataType(string name, string editorAlias) + { + var dt = Services.DataTypeService.GetDataType(name); + if (dt != null) return dt; + + var editor = _propertyEditors.FirstOrDefault(x => x.Alias == editorAlias); + if (editor == null) + throw new InvalidOperationException($"No {editorAlias} editor found"); + + dt = new DataType(editor) + { + Name = name, + Configuration = editor.GetConfigurationEditor().DefaultConfigurationObject, + DatabaseType = ValueStorageType.Ntext + }; + + Services.DataTypeService.Save(dt); + return dt; + } + } +} diff --git a/src/Umbraco.TestData/readme.md b/src/Umbraco.TestData/readme.md new file mode 100644 index 0000000000..f943326303 --- /dev/null +++ b/src/Umbraco.TestData/readme.md @@ -0,0 +1,51 @@ +## Umbraco Test Data + +This project is a utility to be able to generate large amounts of content and media in an +Umbraco installation for testing. + +Currently this project is referenced in the Umbraco.Web.UI project but only when it's being built +in Debug mode (i.e. when testing within Visual Studio). + +## Usage + +You must use SQL Server for this, using SQLCE will die if you try to bulk create huge amounts of data. + +It has to be enabled by an appSetting: + +```xml + +``` + +Once this is enabled this endpoint can be executed: + +`/umbraco/surface/umbracotestdata/CreateTree?count=100&depth=5` + +The query string options are: + +* `count` = the number of content and media nodes to create +* `depth` = how deep the trees created will be +* `locale` (optional, default = "en") = the language that the data will be generated in + +This creates a content and associated media tree (hierarchy). Each content item created is associated +to a media item via a media picker and therefore a relation is created between the two. Each content and +media tree created have the same root node name so it's easy to know which content branch relates to +which media branch. + +All values are generated using the very handy `Bogus` package. + +## Schema + +This will install some schema items: + +* `umbTestDataContent` Document Type. __TIP__: If you want to delete all of the content data generated with this tool, just delete this content type +* `UmbracoTestDataContent.RTE` Data Type +* `UmbracoTestDataContent.MediaPicker` Data Type +* `UmbracoTestDataContent.Text` Data Type + +For media, the normal folder and image is used + +## Media + +This does not upload physical files, it just uses a randomized online image as the `umbracoFile` value. +This works when viewing the media item in the media section and the image will show up and with recent changes this will also work +when editing content to view the thumbnail for the picked media. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index a2dfa0ffd2..96701c1c4e 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -126,6 +126,10 @@ {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c} Umbraco.ModelsBuilder.Embedded + + {fb5676ed-7a69-492c-b802-e7b24144c0fc} + Umbraco.TestData + {651e1350-91b6-44b7-bd60-7207006d7003} Umbraco.Web diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index ff42f098f7..0026f23514 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -21,6 +21,11 @@ + + + + + diff --git a/src/umbraco.sln b/src/umbraco.sln index ba9df633bb..a747f21d19 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -104,6 +104,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.ModelsBuilder.Embedded", "Umbraco.ModelsBuilder.Embedded\Umbraco.ModelsBuilder.Embedded.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.TestData", "Umbraco.TestData\Umbraco.TestData.csproj", "{FB5676ED-7A69-492C-B802-E7B24144C0FC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -140,6 +142,10 @@ Global {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Release|Any CPU.Build.0 = Release|Any CPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -152,6 +158,7 @@ Global {53594E5B-64A2-4545-8367-E3627D266AE8} = {FD962632-184C-4005-A5F3-E705D92FC645} {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} {C7311C00-2184-409B-B506-52A5FAEA8736} = {FD962632-184C-4005-A5F3-E705D92FC645} + {FB5676ED-7A69-492C-B802-E7B24144C0FC} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7A0F2E34-D2AF-4DAB-86A0-7D7764B3D0EC} From be38b9639188689765632ea71bb9c83c3d22103c Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 21 Jan 2020 13:52:53 +1100 Subject: [PATCH 196/224] fixes up the AddSegmentData endpoint --- src/Umbraco.TestData/SegmentTestController.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.TestData/SegmentTestController.cs b/src/Umbraco.TestData/SegmentTestController.cs index bcfdfc4ac4..33badbbb55 100644 --- a/src/Umbraco.TestData/SegmentTestController.cs +++ b/src/Umbraco.TestData/SegmentTestController.cs @@ -62,12 +62,18 @@ namespace Umbraco.TestData if (content == null) return Content($"No content found by id {contentId}"); - if (!content.HasProperty(propertyAlias)) - return Content($"The content by id {contentId} does not contain a property with alias {propertyAlias}"); + if (propertyAlias.IsNullOrWhiteSpace() || !content.HasProperty(propertyAlias)) + return Content($"The content by id {contentId} does not contain a property with alias {propertyAlias ?? "null"}"); if (content.ContentType.VariesByCulture() && culture.IsNullOrWhiteSpace()) return Content($"The content by id {contentId} varies by culture but no culture was specified"); + if (value.IsNullOrWhiteSpace()) + return Content("'value' cannot be null"); + + if (segment.IsNullOrWhiteSpace()) + return Content("'segment' cannot be null"); + content.SetValue(propertyAlias, value, culture, segment); Services.ContentService.Save(content); From 466f8ca18584f1ed4c02eb4c93b0d1604927cbc8 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 21 Jan 2020 07:56:51 +0000 Subject: [PATCH 197/224] V8: Email Marketing Opt In (#7366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enable BackOfficeTours to have a bool to hide them in the help drawer * New hidden tour to display the email marketing option on login to backoffice * Update to tourService to use the new bool property of hidden to show/hide the tour in the help drawer * AngularJS Resource to call the Azure Function EmailService proxy code - currently set to DEV * New method on userService.addUserToEmailMarketing that in turn calls the new emailMarketingResource * New AngularJS view & controller in the tour step to deal with user clicking yes/accept to the email opt-in * Modifies the init script to auto launch the hidden email marketing tour at login If it has been accepted or dismissed before we then try to launch the original intro tour * Only show the email marketing tour when the intro tour has been dismissed or completed and will appear for one time only the next time you login * When using X to close email tour, it does not disable and never show it again but just closes it, similar to intro tour * Adds new localStorageService key for 'emailMarketingTourShown' to prevent the tour being shown again in the same logged in session, if you refresh the backoffice in your browser * Update URL to email function * Adding new COMA copy for email marketing tour - needs fine tuning pixel pushing from Niels L * Prettified layout of e-mail marketing promotion tour * fixing whitespace * text=auto * adding xml to gitattributes * Ensures the email tour is not shown if you dismiss the intro tour and manually refresh the page Co-authored-by: Niels Lyngsø --- .gitattributes | 16 ++++--- .../resources/emailmarketing.resource.js | 34 ++++++++++++++ .../src/common/services/tour.service.js | 12 +++-- .../src/common/services/user.service.js | 7 ++- src/Umbraco.Web.UI.Client/src/init.js | 30 +++++++++++-- src/Umbraco.Web.UI.Client/src/less/belle.less | 2 + .../less/components/application/umb-tour.less | 14 ++++++ .../less/components/umbemailmarketing.less | 44 +++++++++++++++++++ .../src/main.controller.js | 7 ++- .../emails/emails.controller.js | 24 ++++++++++ .../umbEmailMarketing/emails/emails.html | 26 +++++++++++ .../components/application/umb-tour.html | 2 +- .../Umbraco/js/main.controller.js | 7 ++- .../BackOfficeTours/getting-started.json | 18 ++++++++ src/Umbraco.Web/Models/BackOfficeTour.cs | 9 ++++ 15 files changed, 236 insertions(+), 16 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/resources/emailmarketing.resource.js create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/umbemailmarketing.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.html diff --git a/.gitattributes b/.gitattributes index a664be3a85..c8987ade67 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,7 +13,7 @@ *.png binary *.gif binary -*.cs text=auto diff=csharp +*.cs text=auto diff=csharp *.vb text=auto *.c text=auto *.cpp text=auto @@ -41,9 +41,13 @@ *.fs text=auto *.fsx text=auto *.hs text=auto +*.json text=auto +*.xml text=auto -*.csproj text=auto merge=union -*.vbproj text=auto merge=union -*.fsproj text=auto merge=union -*.dbproj text=auto merge=union -*.sln text=auto eol=crlf merge=union +*.csproj text=auto merge=union +*.vbproj text=auto merge=union +*.fsproj text=auto merge=union +*.dbproj text=auto merge=union +*.sln text=auto eol=crlf merge=union + +*.gitattributes text=auto diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/emailmarketing.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/emailmarketing.resource.js new file mode 100644 index 0000000000..4ac56ad13b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/emailmarketing.resource.js @@ -0,0 +1,34 @@ +/** + * @ngdoc service + * @name umbraco.resources.emailMarketingResource + * @description Used to add a backoffice user to Umbraco's email marketing system, if user opts in + * + * + **/ +function emailMarketingResource($http, umbRequestHelper) { + + // LOCAL + // http://localhost:7071/api/EmailProxy + + // LIVE + // https://emailcollector.umbraco.io/api/EmailProxy + + const emailApiUrl = 'https://emailcollector.umbraco.io/api/EmailProxy'; + + //the factory object returned + return { + + postAddUserToEmailMarketing: (user) => { + return umbRequestHelper.resourcePromise( + $http.post(emailApiUrl, + { + name: user.name, + email: user.email, + usergroup: user.userGroups // [ "admin", "sensitiveData" ] + }), + 'Failed to add user to email marketing list'); + } + }; +} + +angular.module('umbraco.resources').factory('emailMarketingResource', emailMarketingResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js index e102da5d34..62af17146c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js @@ -147,7 +147,10 @@ group.groupOrder = item.groupOrder } groupExists = true; - group.tours.push(item) + + if(item.hidden === false){ + group.tours.push(item); + } } }); @@ -157,8 +160,11 @@ if(item.groupOrder) { newGroup.groupOrder = item.groupOrder } - newGroup.tours.push(item); - groupedTours.push(newGroup); + + if(item.hidden === false){ + newGroup.tours.push(item); + groupedTours.push(newGroup); + } } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 7723c8f4bb..afd7b606e7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -1,5 +1,5 @@ angular.module('umbraco.services') - .factory('userService', function ($rootScope, eventsService, $q, $location, requestRetryQueue, authResource, $timeout, angularHelper) { + .factory('userService', function ($rootScope, eventsService, $q, $location, requestRetryQueue, authResource, emailMarketingResource, $timeout, angularHelper) { var currentUser = null; var lastUserId = null; @@ -262,6 +262,11 @@ angular.module('umbraco.services') /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ setUserTimeout: function (newTimeout) { setUserTimeoutInternal(newTimeout); + }, + + /** Calls out to a Remote Azure Function to deal with email marketing service */ + addUserToEmailMarketing: (user) => { + return emailMarketingResource.postAddUserToEmailMarketing(user); } }; diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index 7d199c5c4f..d5c5166d21 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -1,6 +1,6 @@ /** Executed when the application starts, binds to events and set global state */ -app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService', - function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService) { +app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService', 'localStorageService', + function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService, localStorageService) { //This sets the default jquery ajax headers to include our csrf token, we // need to user the beforeSend method because our token changes per user/login so @@ -23,11 +23,35 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', appReady(data); tourService.registerAllTours().then(function () { - // Auto start intro tour + + // Start intro tour tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) { // start intro tour if it hasn't been completed or disabled if (introTour && introTour.disabled !== true && introTour.completed !== true) { tourService.startTour(introTour); + localStorageService.set("introTourShown", true); + } + else { + + const introTourShown = localStorageService.get("introTourShown"); + if(!introTourShown){ + // Go & show email marketing tour (ONLY when intro tour is completed or been dismissed) + tourService.getTourByAlias("umbEmailMarketing").then(function (emailMarketingTour) { + // Only show the email marketing tour one time - dismissing it or saying no will make sure it never appears again + // Unless invoked from tourService JS Client code explicitly. + // Accepted mails = Completed and Declicned mails = Disabled + if (emailMarketingTour && emailMarketingTour.disabled !== true && emailMarketingTour.completed !== true) { + + // Only show the email tour once per logged in session + // The localstorage key is removed on logout or user session timeout + const emailMarketingTourShown = localStorageService.get("emailMarketingTourShown"); + if(!emailMarketingTourShown){ + tourService.startTour(emailMarketingTour); + localStorageService.set("emailMarketingTourShown", true); + } + } + }); + } } }); }); diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index b5e032f9fb..0921f46aac 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -194,6 +194,8 @@ @import "components/contextdialogs/umb-dialog-datatype-delete.less"; +@import "components/umbemailmarketing.less"; + // Utilities @import "utilities/layout/_display.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less index 42403c65b1..bf2f030cea 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less @@ -119,3 +119,17 @@ border: none; padding: 0; } + +.umb-tour__popover--promotion { + width: 800px; + min-height: 400px; + padding: 40px; + border-radius: @baseBorderRadius * 2; + .umb-tour-step__close { + top: 40px; + right: 40px; + } + a { + text-decoration: underline; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umbemailmarketing.less b/src/Umbraco.Web.UI.Client/src/less/components/umbemailmarketing.less new file mode 100644 index 0000000000..f4b3183045 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umbemailmarketing.less @@ -0,0 +1,44 @@ +.umb-email-marketing { + + h2 { + font-weight: 800; + max-width: 26ex; + margin-top: 20px; + } + + .layout { + display: flex; + align-items: center; + align-content: stretch; + + .primary { + flex-basis: 50%; + padding-right: 40px; + padding-top: 20px; + padding-bottom: 20px; + .notice { + color: @gray-5; + font-style: italic; + a { + color: @gray-5; + &:hover { + color: @ui-action-type-hover; + } + } + } + } + + .secondary { + flex-basis: 50%; + svg { + height: 200px; + width: 100%; + margin-top: -60px; + } + } + } + + .cta { + text-align: right; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 93870f8a56..883907d1dc 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -67,13 +67,18 @@ function MainController($scope, $location, appState, treeService, notificationsS }; var evts = []; - + //when a user logs out or timesout evts.push(eventsService.on("app.notAuthenticated", function (evt, data) { $scope.authenticated = null; $scope.user = null; const isTimedOut = data && data.isTimedOut ? true : false; $scope.showLoginScreen(isTimedOut); + + // Remove the localstorage items for tours shown + // Means that when next logged in they can be re-shown if not already dismissed etc + localStorageService.remove("emailMarketingTourShown"); + localStorageService.remove("introTourShown"); })); evts.push(eventsService.on("app.userRefresh", function(evt) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.controller.js new file mode 100644 index 0000000000..8ecc737278 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.controller.js @@ -0,0 +1,24 @@ +(function () { + "use strict"; + + function EmailsController($scope, userService) { + + var vm = this; + + vm.optIn = function() { + // Get the current user in backoffice + userService.getCurrentUser().then(function(user){ + // Send this user along to opt in + // It's a fire & forget - not sure we need to check the response + userService.addUserToEmailMarketing(user); + }); + + // Mark Tour as complete + // This is also can help us indicate that the user accepted + // Where disabled is set if user closes modal or chooses NO + $scope.model.completeTour(); + } + } + + angular.module("umbraco").controller("Umbraco.Tours.UmbEmailMarketing.EmailsController", EmailsController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.html b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.html new file mode 100644 index 0000000000..887624ed05 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.html @@ -0,0 +1,26 @@ +
+ + + +

{{ model.currentStep.title }}

+ +
+ +
+
+
+ + +
+ paperplane +
+
+ +
+ + +
+ +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html index 5dd56941a9..e358d75b9e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html @@ -4,7 +4,7 @@
-
+
diff --git a/src/Umbraco.Web.UI/Umbraco/js/main.controller.js b/src/Umbraco.Web.UI/Umbraco/js/main.controller.js index 93870f8a56..883907d1dc 100644 --- a/src/Umbraco.Web.UI/Umbraco/js/main.controller.js +++ b/src/Umbraco.Web.UI/Umbraco/js/main.controller.js @@ -67,13 +67,18 @@ function MainController($scope, $location, appState, treeService, notificationsS }; var evts = []; - + //when a user logs out or timesout evts.push(eventsService.on("app.notAuthenticated", function (evt, data) { $scope.authenticated = null; $scope.user = null; const isTimedOut = data && data.isTimedOut ? true : false; $scope.showLoginScreen(isTimedOut); + + // Remove the localstorage items for tours shown + // Means that when next logged in they can be re-shown if not already dismissed etc + localStorageService.remove("emailMarketingTourShown"); + localStorageService.remove("introTourShown"); })); evts.push(eventsService.on("app.userRefresh", function(evt) { diff --git a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json index e300e6562e..7b3f2a2184 100644 --- a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json +++ b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json @@ -1,4 +1,22 @@ [ + { + "name": "Email Marketing", + "alias": "umbEmailMarketing", + "group": "Email Marketing", + "groupOrder": 10, + "hidden": true, + "requiredSections": [ + "content" + ], + "steps": [ + { + "title": "Do you want to stay updated on everything Umbraco?", + "content": "

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

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

", + "view": "emails", + "type": "promotion" + } + ] + }, { "name": "Introduction", "alias": "umbIntroIntroduction", diff --git a/src/Umbraco.Web/Models/BackOfficeTour.cs b/src/Umbraco.Web/Models/BackOfficeTour.cs index d5987ec5bc..7391765193 100644 --- a/src/Umbraco.Web/Models/BackOfficeTour.cs +++ b/src/Umbraco.Web/Models/BackOfficeTour.cs @@ -16,16 +16,25 @@ namespace Umbraco.Web.Models [DataMember(Name = "name")] public string Name { get; set; } + [DataMember(Name = "alias")] public string Alias { get; set; } + [DataMember(Name = "group")] public string Group { get; set; } + [DataMember(Name = "groupOrder")] public int GroupOrder { get; set; } + + [DataMember(Name = "hidden")] + public bool Hidden { get; set; } + [DataMember(Name = "allowDisable")] public bool AllowDisable { get; set; } + [DataMember(Name = "requiredSections")] public List RequiredSections { get; set; } + [DataMember(Name = "steps")] public BackOfficeTourStep[] Steps { get; set; } From 0d12852cd8520dd21c4e33136a93d4747506d816 Mon Sep 17 00:00:00 2001 From: abi Date: Tue, 21 Jan 2020 09:07:10 +0000 Subject: [PATCH 198/224] Address Shannon comments Add code documentation, return empty enumerable instead of allocate empty list --- .../Search/IUmbracoTreeSearcherFields.cs | 16 +++++++++++++++- .../Search/UmbracoTreeSearcherFields.cs | 7 +++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Search/IUmbracoTreeSearcherFields.cs b/src/Umbraco.Web/Search/IUmbracoTreeSearcherFields.cs index eaa5d743a1..c5a6c53d19 100644 --- a/src/Umbraco.Web/Search/IUmbracoTreeSearcherFields.cs +++ b/src/Umbraco.Web/Search/IUmbracoTreeSearcherFields.cs @@ -2,12 +2,26 @@ using System.Collections.Generic; namespace Umbraco.Web.Search { + /// + /// Used to propagate hardcoded internal Field lists + /// public interface IUmbracoTreeSearcherFields { + /// + /// Propagate list of searchable fields for all node types + /// IEnumerable GetBackOfficeFields(); + /// + /// Propagate list of searchable fields for Members + /// IEnumerable GetBackOfficeMembersFields(); - + /// + /// Propagate list of searchable fields for Media + /// IEnumerable GetBackOfficeMediaFields(); + /// + /// Propagate list of searchable fields for Documents + /// IEnumerable GetBackOfficeDocumentFields(); } } diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcherFields.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcherFields.cs index d1c663024b..f90d7bc6b6 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcherFields.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcherFields.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; +using System.Linq; using Umbraco.Examine; -using Umbraco.Web.Search; -namespace Umbraco.Web +namespace Umbraco.Web.Search { public class UmbracoTreeSearcherFields : IUmbracoTreeSearcherFields { @@ -23,10 +23,9 @@ namespace Umbraco.Web { return _backOfficeMediaFields; } - private IReadOnlyList _backOfficeDocumentFields = new List (); public IEnumerable GetBackOfficeDocumentFields() { - return _backOfficeDocumentFields; + return Enumerable.Empty(); } } } From 204e0841afde1cdca8d9d8b4f79b3bb127fad6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Dessureault?= Date: Tue, 21 Jan 2020 10:48:21 -0500 Subject: [PATCH 199/224] Fix typo in contentTypeSavedHeader French label "documet" should read "document" --- src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index 18a5fe9c5a..e3cfa32d62 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -1180,7 +1180,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Type de propriété créé Type de données : %1%]]> Type de propriété supprimé - Type de documet sauvegardé + Type de document sauvegardé Onglet créé Onglet supprimé Onglet avec l'ID : %0% supprimé From 163c1f7028eb419eda33e8a0c0548f66fd1ee8e2 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 21 Jan 2020 19:16:07 +0100 Subject: [PATCH 200/224] Fix automatic merge gone wrong --- src/Umbraco.Web.UI.Client/src/common/services/tour.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js index 91b41cc68d..1c2da43814 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js @@ -168,12 +168,12 @@ } } - }); + } deferred.resolve(groupedTours); }); return deferred.promise; - } + }); /** * @ngdoc method From 80309011847af5664d3765d824dd2ec3e93985c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 22 Jan 2020 08:36:52 +0100 Subject: [PATCH 201/224] Fix mistakes made in commit a4a6b77 --- .../src/common/services/tour.service.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js index 1c2da43814..8fcab445b3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js @@ -162,18 +162,19 @@ newGroup.groupOrder = item.groupOrder; } - if(item.hidden === false){ - newGroup.tours.push(item); - groupedTours.push(newGroup); + if(item.hidden === false){ + newGroup.tours.push(item); + groupedTours.push(newGroup); + } } } - } + }); deferred.resolve(groupedTours); }); return deferred.promise; - }); + } /** * @ngdoc method From 10f15a7648c939c0469892bf04f9ae6e5930ef6f Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 22 Jan 2020 11:10:28 +0100 Subject: [PATCH 202/224] Remove duplicate VariesBySegment overload after merge --- src/Umbraco.Core/ContentVariationExtensions.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index 8939f7133b..6430c75686 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -27,11 +27,6 @@ namespace Umbraco.Core /// public static bool VariesByNothing(this IContentTypeBase contentType) => contentType.Variations.VariesByNothing(); - /// - /// 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 6c2e1b29fd7221a259be97e39eb0ee3ae0f657cd Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 22 Jan 2020 11:10:56 +0100 Subject: [PATCH 203/224] Use SetVariesBy extension methods instead of SetFlag/UnsetFlag --- src/Umbraco.TestData/SegmentTestController.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.TestData/SegmentTestController.cs b/src/Umbraco.TestData/SegmentTestController.cs index 33badbbb55..650820760e 100644 --- a/src/Umbraco.TestData/SegmentTestController.cs +++ b/src/Umbraco.TestData/SegmentTestController.cs @@ -30,9 +30,8 @@ namespace Umbraco.TestData if (ct.Variations.VariesBySegment()) return Content($"The document type {alias} already allows segments, nothing has been changed"); - ct.Variations = ct.Variations.SetFlag(ContentVariation.Segment); - - propType.Variations = propType.Variations.SetFlag(ContentVariation.Segment); + ct.SetVariesBy(ContentVariation.Segment); + propType.SetVariesBy(ContentVariation.Segment); Services.ContentTypeService.Save(ct); return Content($"The document type {alias} and property type {propertyTypeAlias} now allows segments"); @@ -50,7 +49,7 @@ namespace Umbraco.TestData if (!ct.VariesBySegment()) return Content($"The document type {alias} does not allow segments, nothing has been changed"); - ct.Variations = ct.Variations.UnsetFlag(ContentVariation.Segment); + ct.SetVariesBy(ContentVariation.Segment, false); Services.ContentTypeService.Save(ct); return Content($"The document type {alias} no longer allows segments"); From ac675d14e952788881e4980e0f20c57aae967d9c Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 22 Jan 2020 11:13:49 +0100 Subject: [PATCH 204/224] Fixes build errors --- src/Umbraco.Core/ContentVariationExtensions.cs | 13 ++++--------- src/Umbraco.TestData/SegmentTestController.cs | 8 ++++---- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index 8939f7133b..4fc6f418aa 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -30,6 +30,10 @@ namespace Umbraco.Core /// /// Determines whether the content type varies by segment. /// + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment(); /// @@ -122,15 +126,6 @@ namespace Umbraco.Core /// public static bool VariesByCulture(this ContentVariation variation) => (variation & ContentVariation.Culture) > 0; - /// - /// Determines whether the content type varies by segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by segment. - /// - public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment(); - /// /// Determines whether the content type varies by segment. /// diff --git a/src/Umbraco.TestData/SegmentTestController.cs b/src/Umbraco.TestData/SegmentTestController.cs index 33badbbb55..c78d12ab0e 100644 --- a/src/Umbraco.TestData/SegmentTestController.cs +++ b/src/Umbraco.TestData/SegmentTestController.cs @@ -30,9 +30,9 @@ namespace Umbraco.TestData if (ct.Variations.VariesBySegment()) return Content($"The document type {alias} already allows segments, nothing has been changed"); - ct.Variations = ct.Variations.SetFlag(ContentVariation.Segment); - - propType.Variations = propType.Variations.SetFlag(ContentVariation.Segment); + ct.Variations = ct.Variations.SetVariesBy(ContentVariation.Segment); + + propType.Variations = propType.Variations.SetVariesBy(ContentVariation.Segment); Services.ContentTypeService.Save(ct); return Content($"The document type {alias} and property type {propertyTypeAlias} now allows segments"); @@ -50,7 +50,7 @@ namespace Umbraco.TestData if (!ct.VariesBySegment()) return Content($"The document type {alias} does not allow segments, nothing has been changed"); - ct.Variations = ct.Variations.UnsetFlag(ContentVariation.Segment); + ct.Variations = ct.Variations.SetVariesBy(ContentVariation.Nothing); Services.ContentTypeService.Save(ct); return Content($"The document type {alias} no longer allows segments"); From 1cc6a8cb4511c74ad5d5a96fed9ca1805bf7831a Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 22 Jan 2020 11:14:31 +0100 Subject: [PATCH 205/224] Rename SetVariesBy to SetFlag on the enum overload --- src/Umbraco.Core/ContentVariationExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index 6430c75686..29442b78e6 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -234,7 +234,7 @@ namespace Umbraco.Core /// /// This method does not support setting the variation to nothing. /// - public static void SetVariesBy(this IContentTypeBase contentType, ContentVariation variation, bool value = true) => contentType.Variations = contentType.Variations.SetVariesBy(variation, value); + public static void SetVariesBy(this IContentTypeBase contentType, ContentVariation variation, bool value = true) => contentType.Variations = contentType.Variations.SetFlag(variation, value); /// /// Sets or removes the property type variation depending on the specified value. @@ -245,7 +245,7 @@ namespace Umbraco.Core /// /// This method does not support setting the variation to nothing. /// - public static void SetVariesBy(this PropertyType propertyType, ContentVariation variation, bool value = true) => propertyType.Variations = propertyType.Variations.SetVariesBy(variation, value); + public static void SetVariesBy(this PropertyType propertyType, ContentVariation variation, bool value = true) => propertyType.Variations = propertyType.Variations.SetFlag(variation, value); /// /// Returns the variations with the variation set or removed depending on the specified value. @@ -259,7 +259,7 @@ namespace Umbraco.Core /// /// This method does not support setting the variation to nothing. /// - public static ContentVariation SetVariesBy(this ContentVariation variations, ContentVariation variation, bool value = true) + public static ContentVariation SetFlag(this ContentVariation variations, ContentVariation variation, bool value = true) { return value ? variations | variation // Set flag using bitwise logical OR From d61090a225c9e99606f096c18971e5beb5c6c9a7 Mon Sep 17 00:00:00 2001 From: Dave Woestenborghs Date: Tue, 14 Jan 2020 14:02:10 +0100 Subject: [PATCH 206/224] #7452 set save state correctly for variant when setting schedule publishing --- .../src/common/directives/components/content/edit.controller.js | 1 + 1 file changed, 1 insertion(+) 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..7429f60e0d 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 @@ -791,6 +791,7 @@ $scope.content.variants[i].expireDate = model.variants[i].expireDate; $scope.content.variants[i].releaseDateFormatted = model.variants[i].releaseDateFormatted; $scope.content.variants[i].expireDateFormatted = model.variants[i].expireDateFormatted; + $scope.content.variants[i].save = model.variants[i].save; } model.submitButtonState = "busy"; From 201927580c299b223c5eb17f798e1aaa263944aa Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 23 Jan 2020 16:23:27 +1100 Subject: [PATCH 207/224] Fixing accidental api signature breaking change --- .../Persistence/SqlSyntax/SqlServerSyntaxProvider.cs | 6 +++--- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 11 ++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 6dda49cd5e..bb50fa98a1 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -251,10 +251,10 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName) public override void WriteLock(IDatabase db, params int[] lockIds) { - WriteLock(db, 1800, lockIds); + WriteLock(db, TimeSpan.FromMilliseconds(1800), lockIds); } - public void WriteLock(IDatabase db, int millisecondsTimeout, params int[] lockIds) + public void WriteLock(IDatabase db, TimeSpan timeout, params int[] lockIds) { // soon as we get Database, a transaction is started @@ -265,7 +265,7 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName) // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks foreach (var lockId in lockIds) { - db.Execute($"SET LOCK_TIMEOUT {millisecondsTimeout};"); + db.Execute($"SET LOCK_TIMEOUT {timeout.TotalMilliseconds};"); var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId }); if (i == 0) // ensure we are actually locking! throw new ArgumentException($"LockObject with id={lockId} does not exist."); diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index 31f8b36ed0..418e4c13fc 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -39,6 +39,11 @@ namespace Umbraco.Core.Runtime public async Task AcquireLockAsync(int millisecondsTimeout) { + if (!(_dbFactory.SqlContext.SqlSyntax is SqlServerSyntaxProvider sqlServerSyntaxProvider)) + throw new NotSupportedException("SqlMainDomLock is only supported for Sql Server"); + + _sqlServerSyntax = sqlServerSyntaxProvider; + _logger.Debug("Acquiring lock..."); var db = GetDatabase(); @@ -52,7 +57,7 @@ namespace Umbraco.Core.Runtime try { // wait to get a write lock - _sqlServerSyntax.WriteLock(db, millisecondsTimeout, Constants.Locks.MainDom); + _sqlServerSyntax.WriteLock(db, TimeSpan.FromMilliseconds(millisecondsTimeout), Constants.Locks.MainDom); } catch (Exception ex) { @@ -136,7 +141,7 @@ namespace Umbraco.Core.Runtime db.BeginTransaction(IsolationLevel.ReadCommitted); // get a read lock - _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); + _dbFactory.SqlContext.SqlSyntax.ReadLock(db, Constants.Locks.MainDom); // TODO: We could in theory just check if the main dom row doesn't exist, that could indicate that // we are still the maindom. An empty value might be better because then we won't have any orphan rows @@ -209,7 +214,7 @@ namespace Umbraco.Core.Runtime db.BeginTransaction(IsolationLevel.ReadCommitted); // get a read lock - _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); + _dbFactory.SqlContext.SqlSyntax.ReadLock(db, Constants.Locks.MainDom); // the row var mainDomRows = db.Fetch("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); From 8d7ed7dd32b6ff0bb415d126b56f4c0a57b49491 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 23 Jan 2020 16:26:13 +1100 Subject: [PATCH 208/224] adds const fixing naming --- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index 418e4c13fc..37db2899c6 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -17,6 +17,7 @@ namespace Umbraco.Core.Runtime { private string _lockId; private const string MainDomKey = "Umbraco.Core.Runtime.SqlMainDom"; + private const string UpdatedSuffix = "_updated"; private readonly ILogger _logger; private IUmbracoDatabase _db; private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); @@ -197,7 +198,7 @@ namespace Umbraco.Core.Runtime /// private Task WaitForExistingAsync(string tempId, int millisecondsTimeout) { - var updatedTempId = tempId + "_updated"; + var updatedTempId = tempId + UpdatedSuffix; return Task.Run(() => { @@ -343,11 +344,11 @@ namespace Umbraco.Core.Runtime private bool IsLockTimeoutException(Exception exception) => exception is SqlException sqlException && sqlException.Number == 1222; #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls + private bool _disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { @@ -374,7 +375,7 @@ namespace Umbraco.Core.Runtime if (_mainDomChanging) { _logger.Debug("Releasing MainDom, updating row, new application is booting."); - db.Execute("UPDATE umbracoKeyValue SET [value] = [value] + '_updated' WHERE [key] = @key", new { key = MainDomKey }); + db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey }); } else { @@ -396,7 +397,7 @@ namespace Umbraco.Core.Runtime } } - disposedValue = true; + _disposedValue = true; } } From 52b93bfc9c0c11a1225383681de5f6a716b67730 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 23 Jan 2020 18:33:55 +1100 Subject: [PATCH 209/224] ensures only _sqlServerSyntax is used --- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index 37db2899c6..4433a8e307 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -142,7 +142,7 @@ namespace Umbraco.Core.Runtime db.BeginTransaction(IsolationLevel.ReadCommitted); // get a read lock - _dbFactory.SqlContext.SqlSyntax.ReadLock(db, Constants.Locks.MainDom); + _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); // TODO: We could in theory just check if the main dom row doesn't exist, that could indicate that // we are still the maindom. An empty value might be better because then we won't have any orphan rows @@ -215,7 +215,7 @@ namespace Umbraco.Core.Runtime db.BeginTransaction(IsolationLevel.ReadCommitted); // get a read lock - _dbFactory.SqlContext.SqlSyntax.ReadLock(db, Constants.Locks.MainDom); + _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); // the row var mainDomRows = db.Fetch("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); From aaf53921eb1a09aa6297599432e79ab37d16119d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 23 Jan 2020 15:15:48 +0100 Subject: [PATCH 210/224] upgrade to Angular 1.7.9 + npm 6.13.6 --- src/Umbraco.Web.UI.Client/package-lock.json | 143 +++++++++++--------- src/Umbraco.Web.UI.Client/package.json | 4 +- 2 files changed, 78 insertions(+), 69 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index d69ef62f16..dd58b5ca18 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1099,9 +1099,9 @@ "dev": true }, "angular": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.5.tgz", - "integrity": "sha512-760183yxtGzni740IBTieNuWLtPNAoMqvmC0Z62UoU0I3nqk+VJuO3JbQAXOyvo3Oy/ZsdNQwrSTh/B0OQZjNw==" + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.9.tgz", + "integrity": "sha512-5se7ZpcOtu0MBFlzGv5dsM1quQDoDeUTwZrWjGtTNA7O88cD8TEk5IEKCTDa3uECV9XnvKREVUr7du1ACiWGFQ==" }, "angular-animate": { "version": "1.7.5", @@ -9324,9 +9324,9 @@ "dev": true }, "nouislider": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.0.2.tgz", - "integrity": "sha512-N4AQStV4frh+XcLUwMI/hZpBP6tRboDE/4LZ7gzfxMVXFi/2J9URphnm40Ff4KEyrAVGSGaWApvljoMzTNWBlA==" + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.1.1.tgz", + "integrity": "sha512-3/+Z/pTBoWoJf2YXSEWRmS27LW2XxOBmGEzkPyRzB/J6QvL+0mS3QwcQp0SmWhgO5CMzbSxPmb1lDDD4HP12bg==" }, "now-and-later": { "version": "2.0.1", @@ -9338,9 +9338,9 @@ } }, "npm": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.12.0.tgz", - "integrity": "sha512-juj5VkB3/k+PWbJUnXD7A/8oc8zLusDnK/sV9PybSalsbOVOTIp5vSE0rz5rQ7BsmUgQS47f/L2GYQnWXaKgnQ==", + "version": "6.13.6", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.13.6.tgz", + "integrity": "sha512-NomC08kv7HIl1FOyLOe9Hp89kYsOsvx52huVIJ7i8hFW8Xp65lDwe/8wTIrh9q9SaQhA8hTrfXPh3BEL3TmMpw==", "requires": { "JSONStream": "^1.3.5", "abbrev": "~1.1.1", @@ -9348,12 +9348,12 @@ "ansistyles": "~0.1.3", "aproba": "^2.0.0", "archy": "~1.0.0", - "bin-links": "^1.1.3", + "bin-links": "^1.1.6", "bluebird": "^3.5.5", "byte-size": "^5.0.1", "cacache": "^12.0.3", "call-limit": "^1.1.1", - "chownr": "^1.1.2", + "chownr": "^1.1.3", "ci-info": "^2.0.0", "cli-columns": "^3.1.2", "cli-table3": "^0.5.1", @@ -9369,9 +9369,9 @@ "find-npm-prefix": "^1.0.2", "fs-vacuum": "~1.2.10", "fs-write-stream-atomic": "~1.0.10", - "gentle-fs": "^2.2.1", + "gentle-fs": "^2.3.0", "glob": "^7.1.4", - "graceful-fs": "^4.2.2", + "graceful-fs": "^4.2.3", "has-unicode": "~2.0.1", "hosted-git-info": "^2.8.5", "iferr": "^1.0.2", @@ -9384,7 +9384,7 @@ "is-cidr": "^3.0.0", "json-parse-better-errors": "^1.0.2", "lazy-property": "~1.0.0", - "libcipm": "^4.0.4", + "libcipm": "^4.0.7", "libnpm": "^3.0.1", "libnpmaccess": "^3.0.2", "libnpmhook": "^5.0.3", @@ -9418,25 +9418,25 @@ "npm-install-checks": "^3.0.2", "npm-lifecycle": "^3.1.4", "npm-package-arg": "^6.1.1", - "npm-packlist": "^1.4.4", + "npm-packlist": "^1.4.7", "npm-pick-manifest": "^3.0.2", "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.0", + "npm-registry-fetch": "^4.0.2", "npm-user-validate": "~1.0.0", "npmlog": "~4.1.2", "once": "~1.4.0", "opener": "^1.5.1", "osenv": "^0.1.5", - "pacote": "^9.5.8", + "pacote": "^9.5.12", "path-is-inside": "~1.0.2", "promise-inflight": "~1.0.1", "qrcode-terminal": "^0.12.0", "query-string": "^6.8.2", "qw": "~1.0.1", "read": "~1.0.7", - "read-cmd-shim": "^1.0.4", + "read-cmd-shim": "^1.0.5", "read-installed": "~4.0.3", - "read-package-json": "^2.1.0", + "read-package-json": "^2.1.1", "read-package-tree": "^5.3.1", "readable-stream": "^3.4.0", "readdir-scoped-modules": "^1.1.0", @@ -9451,7 +9451,7 @@ "sorted-union-stream": "~2.1.3", "ssri": "^6.0.1", "stringify-package": "^1.0.1", - "tar": "^4.4.12", + "tar": "^4.4.13", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", "uid-number": "0.0.6", @@ -9459,7 +9459,7 @@ "unique-filename": "^1.1.1", "unpipe": "~1.0.0", "update-notifier": "^2.5.0", - "uuid": "^3.3.2", + "uuid": "^3.3.3", "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "~3.0.0", "which": "^1.3.1", @@ -9607,13 +9607,14 @@ } }, "bin-links": { - "version": "1.1.3", + "version": "1.1.6", "bundled": true, "requires": { "bluebird": "^3.5.3", "cmd-shim": "^3.0.0", - "gentle-fs": "^2.0.1", + "gentle-fs": "^2.3.0", "graceful-fs": "^4.1.15", + "npm-normalize-package-bin": "^1.0.0", "write-file-atomic": "^2.3.0" } }, @@ -9705,7 +9706,7 @@ } }, "chownr": { - "version": "1.1.2", + "version": "1.1.3", "bundled": true }, "ci-info": { @@ -10266,7 +10267,7 @@ }, "dependencies": { "minipass": { - "version": "2.8.6", + "version": "2.9.0", "bundled": true, "requires": { "safe-buffer": "^5.1.2", @@ -10362,11 +10363,12 @@ "bundled": true }, "gentle-fs": { - "version": "2.2.1", + "version": "2.3.0", "bundled": true, "requires": { "aproba": "^1.1.2", "chownr": "^1.1.2", + "cmd-shim": "^3.0.3", "fs-vacuum": "^1.2.10", "graceful-fs": "^4.1.11", "iferr": "^0.1.5", @@ -10448,7 +10450,7 @@ } }, "graceful-fs": { - "version": "4.2.2", + "version": "4.2.3", "bundled": true }, "har-schema": { @@ -10508,7 +10510,7 @@ } }, "https-proxy-agent": { - "version": "2.2.2", + "version": "2.2.4", "bundled": true, "requires": { "agent-base": "^4.3.0", @@ -10534,7 +10536,7 @@ "bundled": true }, "ignore-walk": { - "version": "3.0.1", + "version": "3.0.3", "bundled": true, "requires": { "minimatch": "^3.0.4" @@ -10748,7 +10750,7 @@ } }, "libcipm": { - "version": "4.0.4", + "version": "4.0.7", "bundled": true, "requires": { "bin-links": "^1.1.2", @@ -11017,14 +11019,14 @@ } }, "make-fetch-happen": { - "version": "5.0.0", + "version": "5.0.2", "bundled": true, "requires": { "agentkeepalive": "^3.4.1", "cacache": "^12.0.0", "http-cache-semantics": "^3.8.1", "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1", + "https-proxy-agent": "^2.2.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "node-fetch-npm": "^2.0.2", @@ -11070,27 +11072,23 @@ "version": "0.0.8", "bundled": true }, - "minipass": { - "version": "2.3.3", + "minizlib": { + "version": "1.3.3", "bundled": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "minipass": "^2.9.0" }, "dependencies": { - "yallist": { - "version": "3.0.2", - "bundled": true + "minipass": { + "version": "2.9.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } } } }, - "minizlib": { - "version": "1.2.2", - "bundled": true, - "requires": { - "minipass": "^2.2.1" - } - }, "mississippi": { "version": "3.0.0", "bundled": true, @@ -11215,8 +11213,11 @@ } }, "npm-bundled": { - "version": "1.0.6", - "bundled": true + "version": "1.1.1", + "bundled": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } }, "npm-cache-filename": { "version": "1.0.2", @@ -11247,6 +11248,10 @@ "version": "1.2.1", "bundled": true }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true + }, "npm-package-arg": { "version": "6.1.1", "bundled": true, @@ -11258,7 +11263,7 @@ } }, "npm-packlist": { - "version": "1.4.4", + "version": "1.4.7", "bundled": true, "requires": { "ignore-walk": "^3.0.1", @@ -11284,7 +11289,7 @@ } }, "npm-registry-fetch": { - "version": "4.0.0", + "version": "4.0.2", "bundled": true, "requires": { "JSONStream": "^1.3.4", @@ -11292,7 +11297,14 @@ "figgy-pudding": "^3.4.1", "lru-cache": "^5.1.1", "make-fetch-happen": "^5.0.0", - "npm-package-arg": "^6.1.0" + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true + } } }, "npm-run-path": { @@ -11409,7 +11421,7 @@ } }, "pacote": { - "version": "9.5.8", + "version": "9.5.12", "bundled": true, "requires": { "bluebird": "^3.5.3", @@ -11426,6 +11438,7 @@ "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", "npm-package-arg": "^6.1.0", "npm-packlist": "^1.1.12", "npm-pick-manifest": "^3.0.0", @@ -11444,7 +11457,7 @@ }, "dependencies": { "minipass": { - "version": "2.3.5", + "version": "2.9.0", "bundled": true, "requires": { "safe-buffer": "^5.1.2", @@ -11644,7 +11657,7 @@ } }, "read-cmd-shim": { - "version": "1.0.4", + "version": "1.0.5", "bundled": true, "requires": { "graceful-fs": "^4.1.2" @@ -11664,14 +11677,14 @@ } }, "read-package-json": { - "version": "2.1.0", + "version": "2.1.1", "bundled": true, "requires": { "glob": "^7.1.1", "graceful-fs": "^4.1.2", "json-parse-better-errors": "^1.0.1", "normalize-package-data": "^2.0.0", - "slash": "^1.0.0" + "npm-normalize-package-bin": "^1.0.0" } }, "read-package-tree": { @@ -11824,24 +11837,20 @@ "version": "3.0.2", "bundled": true }, - "slash": { - "version": "1.0.0", - "bundled": true - }, "slide": { "version": "1.1.6", "bundled": true }, "smart-buffer": { - "version": "4.0.2", + "version": "4.1.0", "bundled": true }, "socks": { - "version": "2.3.2", + "version": "2.3.3", "bundled": true, "requires": { - "ip": "^1.1.5", - "smart-buffer": "4.0.2" + "ip": "1.1.5", + "smart-buffer": "^4.1.0" } }, "socks-proxy-agent": { @@ -12056,7 +12065,7 @@ } }, "tar": { - "version": "4.4.12", + "version": "4.4.13", "bundled": true, "requires": { "chownr": "^1.1.1", @@ -12069,7 +12078,7 @@ }, "dependencies": { "minipass": { - "version": "2.8.6", + "version": "2.9.0", "bundled": true, "requires": { "safe-buffer": "^5.1.2", @@ -12231,7 +12240,7 @@ } }, "uuid": { - "version": "3.3.2", + "version": "3.3.3", "bundled": true }, "validate-npm-package-license": { diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 0f02aba5e2..d60da6e790 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "ace-builds": "1.4.2", - "angular": "1.7.5", + "angular": "^1.7.9", "angular-animate": "1.7.5", "angular-aria": "1.7.5", "angular-chart.js": "^1.1.1", @@ -39,7 +39,7 @@ "moment": "2.22.2", "ng-file-upload": "12.2.13", "nouislider": "14.1.1", - "npm": "6.12.0", + "npm": "^6.13.6", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", "tinymce": "4.9.2", From 7fc0f01887cf57b3a6e393155939a56e15bf6314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 23 Jan 2020 15:23:32 +0100 Subject: [PATCH 211/224] fixed versions for angular + npm --- src/Umbraco.Web.UI.Client/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index d60da6e790..beff2b45d1 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "ace-builds": "1.4.2", - "angular": "^1.7.9", + "angular": "1.7.9", "angular-animate": "1.7.5", "angular-aria": "1.7.5", "angular-chart.js": "^1.1.1", @@ -39,7 +39,7 @@ "moment": "2.22.2", "ng-file-upload": "12.2.13", "nouislider": "14.1.1", - "npm": "^6.13.6", + "npm": "6.13.6", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", "tinymce": "4.9.2", From 384ecbe22a217fd9487ee48d04a18a130280ab70 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 24 Jan 2020 08:25:03 +0100 Subject: [PATCH 212/224] Removed reference from Umbraco.Web.UI to Umbraco.TestData --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index ead0cd1592..8f0db76c1d 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -113,12 +113,6 @@ - - - {fb5676ed-7a69-492c-b802-e7b24144c0fc} - Umbraco.TestData - - {31785bc3-256c-4613-b2f5-a1b0bdded8c1} @@ -132,10 +126,6 @@ {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c} Umbraco.ModelsBuilder.Embedded - - {fb5676ed-7a69-492c-b802-e7b24144c0fc} - Umbraco.TestData - {651e1350-91b6-44b7-bd60-7207006d7003} Umbraco.Web @@ -439,4 +429,4 @@ - + \ No newline at end of file From 05fe604e221b9eae4c0b63bc5538347b5988fbbf Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 24 Jan 2020 09:19:29 +0100 Subject: [PATCH 213/224] https://github.com/umbraco/Umbraco-CMS/issues/7469 Renamed extension method to avoid ambiguous signature --- .../PublishedElementExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs index 29429ba74f..8a0a688942 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web /// /// Gets the value of a property. /// - public static TValue Value(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) + public static TValue ValueFor(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) where TModel : IPublishedElement { var alias = GetAlias(model, property); @@ -45,7 +45,7 @@ namespace Umbraco.Web var attribute = member.GetCustomAttribute(); if (attribute == null) throw new InvalidOperationException("Property is not marked with ImplementPropertyType attribute."); - + return attribute.Alias; } } From 7bdf97c4313aca414e243553cdd9b48434b9b037 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 24 Jan 2020 09:20:16 +0100 Subject: [PATCH 214/224] https://github.com/umbraco/Umbraco-CMS/issues/7469 Unsealed class to allow override --- .../ImplementPropertyTypeAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs index 0359c49654..b7b2695a08 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs @@ -7,7 +7,7 @@ namespace Umbraco.ModelsBuilder.Embedded /// /// And therefore it should not be generated. [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] - public sealed class ImplementPropertyTypeAttribute : Attribute + public class ImplementPropertyTypeAttribute : Attribute { public ImplementPropertyTypeAttribute(string alias) { From 4cf78d6ef049b58e513da4bf9ccc95180b36eebb Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 24 Jan 2020 09:22:34 +0100 Subject: [PATCH 215/224] Bump version to 8.5.3 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 9dba3adef1..841e986f15 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.5.2")] -[assembly: AssemblyInformationalVersion("8.5.2")] +[assembly: AssemblyFileVersion("8.5.3")] +[assembly: AssemblyInformationalVersion("8.5.3")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 0c80ccb70b..e12eb8d19b 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -345,9 +345,9 @@ False True - 8520 + 8530 / - http://localhost:8520 + http://localhost:8530 False False From 8bf457a88d3e3ca632339af9b727e93f38d544de Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 24 Jan 2020 09:10:54 +0000 Subject: [PATCH 216/224] Set v8/dev to 8.7.0 with powershell .\build setumbracoversion 8.7.0 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 363677b826..a0d969c1b8 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.6.0")] -[assembly: AssemblyInformationalVersion("8.6.0")] +[assembly: AssemblyFileVersion("8.7.0")] +[assembly: AssemblyInformationalVersion("8.7.0")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index a2dfa0ffd2..9914528e58 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -345,9 +345,9 @@ False True - 8600 + 8700 / - http://localhost:8600 + http://localhost:8700 False False @@ -429,4 +429,4 @@ - + \ No newline at end of file From 8e736f87d03785601331ac270758091eb4baa416 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 24 Jan 2020 12:38:21 +0100 Subject: [PATCH 217/224] Hide the "blob:" URL for images uploaded via the RTE (#6926) --- .../mediapicker/mediapicker.controller.js | 14 ++++++++++++++ .../infiniteeditors/mediapicker/mediapicker.html | 4 ++-- 2 files changed, 16 insertions(+), 2 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 01f61a16ae..4419805194 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 @@ -23,6 +23,7 @@ angular.module("umbraco") vm.clickHandler = clickHandler; vm.clickItemName = clickItemName; vm.gotoFolder = gotoFolder; + vm.shouldShowUrl = shouldShowUrl; var dialogOptions = $scope.model; @@ -531,6 +532,19 @@ angular.module("umbraco") } } + function shouldShowUrl() { + if (!$scope.target) { + return false; + } + if ($scope.target.id) { + return false; + } + if ($scope.target.url && $scope.target.url.toLower().indexOf("blob:") === 0) { + return false; + } + return true; + } + function submit() { if ($scope.model && $scope.model.submit) { $scope.model.submit($scope.model); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html index 917010dbb6..82612d5e19 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html @@ -138,8 +138,8 @@
- -
+ +
From ebed80791db2d388a7e1d987ac8eace39826eba1 Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Mon, 27 Jan 2020 05:12:01 +0000 Subject: [PATCH 218/224] Changed delete button style on media type folder delete to danger style (#7393) --- src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html index c059710ebb..fb0c4c3a52 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html @@ -9,7 +9,7 @@
+ on-cancel="cancel" confirm-button-style="danger">
From 902d4196cf2637809a6a36d94f9f7f6087afd8f1 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Mon, 27 Jan 2020 15:29:04 +1000 Subject: [PATCH 219/224] =?UTF-8?q?add=20dialog=20for=20change=20password,?= =?UTF-8?q?=20shift=20form=20from=20user=20details=20view=20int=E2=80=A6?= =?UTF-8?q?=20(#7450)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/common/resources/users.resource.js | 38 ++++++++++ .../services/umbdataformatter.service.js | 3 +- .../changepassword/changepassword.html | 6 ++ .../src/views/users/user.controller.js | 75 ++++++++++++++----- .../src/views/users/views/user/details.html | 17 +---- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 4 +- .../Umbraco/config/lang/en_us.xml | 2 + src/Umbraco.Web/Editors/UsersController.cs | 70 +++++++++++------ .../Models/ChangingPasswordModel.cs | 6 ++ 9 files changed, 161 insertions(+), 60 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/overlays/changepassword/changepassword.html diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 0fd308ffd0..2b9e0c0fd5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -405,6 +405,43 @@ formattedSaveData), "Failed to save user"); } + + /** + * @ngdoc method + * @name umbraco.resources.usersResource#changePassword + * @methodOf umbraco.resources.usersResource + * + * @description + * Changes a user's password + * + * ##usage + *
+          * usersResource.changePassword(changePasswordModel)
+          *    .then(function() {
+          *        // password changed
+          *    });
+          * 
+ * + * @param {Object} model object to save + * @returns {Promise} resourcePromise object containing the updated user. + * + */ + function changePassword(changePasswordModel) { + if (!changePasswordModel) { + throw "password model not specified"; + } + + //need to convert the password data into the correctly formatted save data - it is *not* the same and we don't want to over-post + var formattedPasswordData = umbDataFormatter.formatChangePasswordModel(changePasswordModel); + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "PostChangePassword"), + formattedPasswordData), + "Failed to save user"); + } /** * @ngdoc method @@ -447,6 +484,7 @@ createUser: createUser, inviteUser: inviteUser, saveUser: saveUser, + changePassword: changePassword, deleteNonLoggedInUser: deleteNonLoggedInUser, clearAvatar: clearAvatar }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index a03a71febe..d9aef1ddba 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -152,8 +152,7 @@ formatUserPostData: function (displayModel) { //create the save model from the display model - var saveModel = _.pick(displayModel, 'id', 'parentId', 'name', 'username', 'culture', 'email', 'startContentIds', 'startMediaIds', 'userGroups', 'message', 'changePassword'); - saveModel.changePassword = this.formatChangePasswordModel(saveModel.changePassword); + var saveModel = _.pick(displayModel, 'id', 'parentId', 'name', 'username', 'culture', 'email', 'startContentIds', 'startMediaIds', 'userGroups', 'message'); //make sure the userGroups are just a string array var currGroups = saveModel.userGroups; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/changepassword/changepassword.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/changepassword/changepassword.html new file mode 100644 index 0000000000..db69a32f7f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/changepassword/changepassword.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index c935852bf8..9534eea9fd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -21,7 +21,8 @@ //create the initial model for change password vm.changePasswordModel = { config: {}, - isChanging: false + isChanging: false, + value: {} }; vm.goToPage = goToPage; @@ -37,7 +38,8 @@ vm.changeAvatar = changeAvatar; vm.clearAvatar = clearAvatar; vm.save = save; - + + vm.changePassword = changePassword; vm.toggleChangePassword = toggleChangePassword; function init() { @@ -125,23 +127,34 @@ } function toggleChangePassword() { - vm.changePasswordModel.isChanging = !vm.changePasswordModel.isChanging; - //reset it - vm.user.changePassword = null; + //reset it + vm.user.changePassword = null; + + localizationService.localizeMany(["general_cancel", "general_confirm", "general_changePassword"]) + .then(function (data) { + const overlay = { + view: "changepassword", + title: data[2], + changePassword: vm.user.changePassword, + config: vm.changePasswordModel.config, + closeButtonLabel: data[0], + submitButtonLabel: data[1], + submitButtonStyle: 'success', + close: () => overlayService.close(), + submit: model => { + overlayService.close(); + vm.changePasswordModel.value = model.changePassword; + changePassword(); + } + }; + overlayService.open(overlay); + }); } function save() { if (formHelper.submitForm({ scope: $scope })) { - //anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here - if (vm.user.changePassword) { - //NOTE: the check for allowManuallyChangingPassword is due to this legacy user membership provider setting, if that is true, then the current user - //can change their own password without entering their current one (this is a legacy setting since that is a security issue but we need to maintain compat). - //if allowManuallyChangingPassword=false, then we are using default settings and the user will need to enter their old password to change their own password. - vm.user.changePassword.reset = (!vm.user.changePassword.oldPassword && !vm.user.isCurrentUser) || vm.changePasswordModel.config.allowManuallyChangingPassword; - } - vm.page.saveButtonState = "busy"; vm.user.resetPasswordValue = null; @@ -161,11 +174,7 @@ //restore vm.user.navigation = currentNav; setUserDisplayState(); - formatDatesToLocal(vm.user); - - vm.changePasswordModel.isChanging = false; - //the user has a password if they are not states: Invited, NoCredentials - vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4; + formatDatesToLocal(vm.user); vm.page.saveButtonState = "success"; @@ -181,6 +190,36 @@ } } + /** + * + */ + function changePassword() { + //anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here + if (vm.changePasswordModel.value) { + //NOTE: the check for allowManuallyChangingPassword is due to this legacy user membership provider setting, if that is true, then the current user + //can change their own password without entering their current one (this is a legacy setting since that is a security issue but we need to maintain compat). + //if allowManuallyChangingPassword=false, then we are using default settings and the user will need to enter their old password to change their own password. + vm.changePasswordModel.value.reset = (!vm.changePasswordModel.value.oldPassword && !vm.user.isCurrentUser) || vm.changePasswordModel.config.allowManuallyChangingPassword; + } + + // since we don't send the entire user model, the id is required + vm.changePasswordModel.value.id = vm.user.id; + + usersResource.changePassword(vm.changePasswordModel.value) + .then(() => { + vm.changePasswordModel.isChanging = false; + vm.changePasswordModel.value = {}; + + //the user has a password if they are not states: Invited, NoCredentials + vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4; + }, err => { + contentEditingHelper.handleSaveError({ + err: err, + showNotifications: true + }); + }); + } + /** * Used to emit the save event and await any async operations being performed by editor extensions * @param {any} savedUser diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index a1dcafd421..e0cf09da50 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 @@ -258,7 +258,7 @@
-
- - - - - - - - - -


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

diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 85b23f53ed..93a97414c9 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -737,6 +737,7 @@ Sort Status Submit + Success Type Type to search... under @@ -1843,7 +1844,8 @@ To manage your website, simply open the Umbraco back office and start adding con Password Reset password Your password has been changed! - Please confirm the new password + Password changed + Please confirm the new password Enter your new password Your new password cannot be blank! Current password 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 fb1ed6fa63..91fad336c7 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -738,6 +738,7 @@ Sort Status Submit + Success Type Type to search... under @@ -1852,6 +1853,7 @@ To manage your website, simply open the Umbraco back office and start adding con Password Reset password Your password has been changed! + Password changed Please confirm the new password Enter your new password Your new password cannot be blank! diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 15e5b9404f..c27d7c9f48 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -26,6 +26,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Editors.Filters; +using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -549,29 +550,7 @@ namespace Umbraco.Web.Editors if (Current.Configs.Settings().Security.UsernameIsEmail && found.Username == found.Email && userSave.Username != userSave.Email) { userSave.Username = userSave.Email; - } - - if (userSave.ChangePassword != null) - { - var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext); - - //this will change the password and raise appropriate events - var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, userSave.ChangePassword, UserManager); - if (passwordChangeResult.Success) - { - //need to re-get the user - found = Services.UserService.GetUserById(intId.Result); - } - else - { - hasErrors = true; - - foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames) - { - ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage); - } - } - } + } if (hasErrors) throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); @@ -587,6 +566,51 @@ namespace Umbraco.Web.Editors return display; } + /// + /// + /// + /// + /// + public async Task> PostChangePassword(ChangingPasswordModel changingPasswordModel) + { + changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel)); + + if (ModelState.IsValid == false) + { + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + var intId = changingPasswordModel.Id.TryConvertTo(); + if (intId.Success == false) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var found = Services.UserService.GetUserById(intId.Result); + if (found == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext); + var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, changingPasswordModel, UserManager); + + if (passwordChangeResult.Success) + { + var result = new ModelWithNotifications(passwordChangeResult.Result.ResetPassword); + result.AddSuccessNotification(Services.TextService.Localize("general/success"), Services.TextService.Localize("user/passwordChangedGeneric")); + return result; + } + + foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames) + { + ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage); + } + + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + + /// /// Disables the users with the given user ids /// diff --git a/src/Umbraco.Web/Models/ChangingPasswordModel.cs b/src/Umbraco.Web/Models/ChangingPasswordModel.cs index 355b358049..633c42a056 100644 --- a/src/Umbraco.Web/Models/ChangingPasswordModel.cs +++ b/src/Umbraco.Web/Models/ChangingPasswordModel.cs @@ -49,5 +49,11 @@ namespace Umbraco.Web.Models ///
[DataMember(Name = "generatedPassword")] public string GeneratedPassword { get; set; } + + /// + /// The id of the user - required to allow changing password without the entire UserSave model + /// + [DataMember(Name = "id")] + public int Id { get; set; } } } From af7f03e1f7e37bbd8ecd7812e5b3548484033c51 Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Tue, 28 Jan 2020 22:28:54 +0100 Subject: [PATCH 220/224] Don't downcast `IPublishedSnapshot` unnecessarily in `PublishedContent` --- src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index bf4975714d..3c3c3ac54f 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -326,7 +326,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // beware what you use that one for - you don't want to cache its result private IAppCache GetAppropriateCache() { - var publishedSnapshot = (PublishedSnapshot)_publishedSnapshotAccessor.PublishedSnapshot; + var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; var cache = publishedSnapshot == null ? null : ((IsPreviewing == false || PublishedSnapshotService.FullCacheWhenPreviewing) && (ContentType.ItemType != PublishedItemType.Member) @@ -337,7 +337,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private IAppCache GetCurrentSnapshotCache() { - var publishedSnapshot = (PublishedSnapshot)_publishedSnapshotAccessor.PublishedSnapshot; + var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; return publishedSnapshot?.SnapshotCache; } From 537674a0b392d4a035beae1bbd858ba1d96fd838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandr=20=C5=A0mailov?= Date: Wed, 29 Jan 2020 09:53:08 +0200 Subject: [PATCH 221/224] Improve RTE performance when using base64 images (#7530) --- .../Templates/HtmlImageSourceParserTests.cs | 84 +++++++++++++++++-- .../Templates/HtmlImageSourceParser.cs | 33 +++++--- 2 files changed, 97 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs b/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs index 3bef495507..bce9bd4155 100644 --- a/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs +++ b/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs @@ -1,7 +1,6 @@ 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; @@ -13,12 +12,10 @@ using System; using System.Linq; using Umbraco.Core.Models; using Umbraco.Core; -using Umbraco.Web.PropertyEditors; +using System.Diagnostics; namespace Umbraco.Tests.Templates { - - [TestFixture] public class HtmlImageSourceParserTests { @@ -31,10 +28,9 @@ namespace Umbraco.Tests.Templates

"; - var logger = Mock.Of(); var umbracoContextAccessor = new TestUmbracoContextAccessor(); var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor); - + var result = imageSourceParser.FindUdisFromDataAttributes(input).ToList(); Assert.AreEqual(2, result.Count); Assert.AreEqual(Udi.Parse("umb://media/D4B18427A1544721B09AC7692F35C264"), result[0]); @@ -44,7 +40,6 @@ namespace Umbraco.Tests.Templates [Test] public void Remove_Image_Sources() { - var logger = Mock.Of(); var umbracoContextAccessor = new TestUmbracoContextAccessor(); var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor); @@ -111,10 +106,83 @@ namespace Umbraco.Tests.Templates

", result); - } + } + [TestCase( + @"
", + ExpectedResult = @"
", + TestName = "Empty source is not updated with no data-udi set" + )] + [TestCase( + @"
", + ExpectedResult = @"
", + TestName = "Empty source is updated with data-udi set" + )] + [TestCase( + @"
", + ExpectedResult = @"
", + TestName = "Filled source is overwritten with data-udi set" + )] + [TestCase( + @"
", + ExpectedResult = @"
", + TestName = "Attributes are persisted" + )] + [TestCase( + @"
", + ExpectedResult = @"
", + TestName = "Source is trimmed and parameters are prefixed" + )] + [TestCase( + @"
", + ExpectedResult = @"
", + TestName = "Parameters are prefixed" + )] + [TestCase( + @"
+ + + +
", + ExpectedResult = + @"
+ + + +
", + TestName = "Multiple img tags are handled" + )] + [Category("Ensure image sources")] + public string Ensure_ImageSources_Processing(string sourceHtml) + { + var fakeMediaUrl = "/media/1001/image.jpg"; + var parser = new HtmlImageSourceParser((guid) => fakeMediaUrl); + var actual = parser.EnsureImageSources(sourceHtml); + + return actual; + } + + [Category("Ensure image sources")] + [Test] + public void Ensure_Large_Html_Is_Processed_Quickly() + { + int symbolCount = 25000; + int maxMsToRun = 200; + + var longText = new string('*', symbolCount); + var text = $@""; + + var fakeMediaUrl = "/media/1001/image.jpg"; + var parser = new HtmlImageSourceParser((guid) => fakeMediaUrl); + + var timer = new Stopwatch(); + timer.Start(); + var actual = parser.EnsureImageSources(text); + timer.Stop(); + + Assert.IsTrue(timer.ElapsedMilliseconds <= maxMsToRun); } } } diff --git a/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs b/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs index b0d6980ef3..dcc5318b89 100644 --- a/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs +++ b/src/Umbraco.Web/Templates/HtmlImageSourceParser.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using Umbraco.Core; @@ -7,18 +8,29 @@ namespace Umbraco.Web.Templates public sealed class HtmlImageSourceParser { - public HtmlImageSourceParser(IUmbracoContextAccessor umbracoContextAccessor) + public HtmlImageSourceParser(Func getMediaUrl) { - _umbracoContextAccessor = umbracoContextAccessor; + this._getMediaUrl = getMediaUrl; } - private static readonly Regex ResolveImgPattern = new Regex(@"(]*src="")([^""\?]*)([^""]*""[^>]*data-udi="")([^""]*)(""[^>]*>)", + public HtmlImageSourceParser(IUmbracoContextAccessor umbracoContextAccessor) + { + if (umbracoContextAccessor?.UmbracoContext?.UrlProvider == null) + { + return; + } + + _getMediaUrl = (guid) => umbracoContextAccessor.UmbracoContext.UrlProvider.GetMediaUrl(guid); + } + + private static readonly Regex ResolveImgPattern = new Regex(@"(]*src="")([^""\?]*)((?:\?[^""]*)?""[^>]*data-udi="")([^""]*)(""[^>]*>)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - 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); + private readonly Func _getMediaUrl; + /// /// Parses out media UDIs from an html string based on 'data-udi' html attributes /// @@ -45,14 +57,13 @@ namespace Umbraco.Web.Templates /// 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?.UrlProvider == null) + // no point in doing any processing if we don't have + // a function to retrieve Urls + if (_getMediaUrl == null) { return text; } - var urlProvider = _umbracoContextAccessor.UmbracoContext.UrlProvider; - return ResolveImgPattern.Replace(text, match => { // match groups: @@ -66,7 +77,7 @@ namespace Umbraco.Web.Templates { return match.Value; } - var mediaUrl = urlProvider.GetMediaUrl(guidUdi.Guid); + var mediaUrl = _getMediaUrl(guidUdi.Guid); if (mediaUrl == null) { // image does not exist - we could choose to remove the image entirely here (return empty string), @@ -86,7 +97,5 @@ namespace Umbraco.Web.Templates public string RemoveImageSources(string text) // see comment in ResolveMediaFromTextString for group reference => ResolveImgPattern.Replace(text, "$1$3$4$5"); - - } } From 42fe20a11ebf7cd0fe93089ad3cf293ef7db9a25 Mon Sep 17 00:00:00 2001 From: jmcaveney Date: Wed, 29 Jan 2020 07:16:17 -0500 Subject: [PATCH 222/224] Uses AppSettings.Path constant for SystemDirectories.Umbraco (#7440) --- src/Umbraco.Core/Constants-AppSettings.cs | 15 +++++++++++++++ src/Umbraco.Core/IO/SystemDirectories.cs | 8 ++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs index 85d6b24ae0..704617d90c 100644 --- a/src/Umbraco.Core/Constants-AppSettings.cs +++ b/src/Umbraco.Core/Constants-AppSettings.cs @@ -43,6 +43,21 @@ namespace Umbraco.Core ///
public const string Path = "Umbraco.Core.Path"; + /// + /// Gets the path to the css directory (/css by default). + /// + public const string CssPath = "umbracoCssPath"; + + /// + /// Gets the path to the scripts directory (/scripts by default). + /// + public const string ScriptsPath = "umbracoScriptsPath"; + + /// + /// Gets the path to media directory (/media by default). + /// + public const string MediaPath = "umbracoMediaPath"; + /// /// The reserved urls from web.config. /// diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index d6fb63b0a1..b4688d2e9f 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -29,13 +29,13 @@ namespace Umbraco.Core.IO public static string MacroPartials => MvcViews + "/MacroPartials/"; - public static string Media => IOHelper.ReturnPath("umbracoMediaPath", "~/media"); + public static string Media => IOHelper.ReturnPath(Constants.AppSettings.MediaPath, "~/media"); - public static string Scripts => IOHelper.ReturnPath("umbracoScriptsPath", "~/scripts"); + public static string Scripts => IOHelper.ReturnPath(Constants.AppSettings.ScriptsPath, "~/scripts"); - public static string Css => IOHelper.ReturnPath("umbracoCssPath", "~/css"); + public static string Css => IOHelper.ReturnPath(Constants.AppSettings.CssPath, "~/css"); - public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco"); + public static string Umbraco => IOHelper.ReturnPath(Constants.AppSettings.Path, "~/umbraco"); public static string Packages => Data + "/packages"; From a945b0c511febd4617a6c9cbd327e9cca5d59069 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Thu, 30 Jan 2020 02:52:20 +1000 Subject: [PATCH 223/224] use default color when previous selection removed (#7448) Thanks Nathan! --- .../iconpicker/iconpicker.controller.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js index 471d23ae84..5bfee22c4e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js @@ -16,7 +16,7 @@ function IconPickerController($scope, iconHelper, localizationService) { vm.close = close; vm.colors = [ - { name: "Black", value: "color-black" }, + { name: "Black", value: "color-black", default: true }, { name: "Blue Grey", value: "color-blue-grey" }, { name: "Grey", value: "color-grey" }, { name: "Brown", value: "color-brown" }, @@ -49,7 +49,7 @@ function IconPickerController($scope, iconHelper, localizationService) { }); // set a default color if nothing is passed in - vm.color = $scope.model.color ? findColor($scope.model.color) : vm.colors[0]; + vm.color = $scope.model.color ? findColor($scope.model.color) : vm.colors.find(x => x.default); // if an icon is passed in - preselect it vm.icon = $scope.model.icon ? $scope.model.icon : undefined; @@ -71,12 +71,13 @@ function IconPickerController($scope, iconHelper, localizationService) { } function findColor(value) { - return _.findWhere(vm.colors, {value: value}); + return vm.colors.find(x => x.value === value); } - function selectColor(color, $index, $event) { - $scope.model.color = color.value; - vm.color = color; + function selectColor(color) { + let newColor = (color || vm.colors.find(x => x.default)); + $scope.model.color = newColor.value; + vm.color = newColor; } function close() { From b6a073a12e5484f3acf7bdef9301d95fb9ddd650 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 29 Jan 2020 17:54:56 +0100 Subject: [PATCH 224/224] Add missing documentation for web.routing in umbracoSettings.config (#7468) Cheers Kenn :) --- src/Umbraco.Web.UI/config/umbracoSettings.config | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index 9e60a9499c..c3df6cfb41 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -231,6 +231,14 @@ By default you can call any content Id in the url and show the content with that id, for example: http://mysite.com/1092 or http://mysite.com/1092.aspx would render the content with id 1092. Setting this setting to true stops that behavior + @disableRedirectUrlTracking + When the URL changes for content, redirects are automatically created for redirect handling within the + request pipeline. Setting this setting to true stops the automatic creation of redirects. Note that this + does not stop the request pipeline from handling any previously created redirects. + @urlProviderMode + By default Umbraco automatically figures out if internal URLs should be rendered as relative or absolute, + depending on the current request and the configured domains. By setting this setting to "Relative" or + "Absolute" you can force Umbraco to always render URLs as either relative or absolute. @umbracoApplicationUrl The url of the Umbraco application. By default, Umbraco will figure it out from the first request. Configure it here if you need anything specific. Needs to be a complete url with scheme and umbraco