diff --git a/src/Umbraco.Core/Constants-Sql.cs b/src/Umbraco.Core/Constants-Sql.cs
new file mode 100644
index 0000000000..b57861c92a
--- /dev/null
+++ b/src/Umbraco.Core/Constants-Sql.cs
@@ -0,0 +1,18 @@
+namespace Umbraco.Cms.Core
+{
+ public static partial class Constants
+ {
+ public static class Sql
+ {
+ ///
+ /// The maximum amount of parameters that can be used in a query.
+ ///
+ ///
+ /// The actual limit is 2100
+ /// (https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server),
+ /// but we want to ensure there's room for additional parameters if this value is used to create groups/batches.
+ ///
+ public const int MaxParameterCount = 2000;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
index 2380657180..e4d101ff06 100644
--- a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
+++ b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
@@ -251,7 +251,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
// the entity service due to too many Sql parameters.
var list = new List();
- foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(2000))
+ foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(Constants.Sql.MaxParameterCount))
list.AddRange(_entityService.GetAll(UmbracoObjectTypes.Document, idGroup.ToArray()));
contentEntities = list.ToArray();
}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 734ed2261d..8ed2205f59 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -1,61 +1,61 @@
-
- netstandard2.0
- Umbraco.Cms.Core
- Umbraco CMS
- Umbraco.Cms.Core
- Umbraco CMS Core
- Contains the core assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco
- Umbraco CMS
-
+
+ netstandard2.0
+ Umbraco.Cms.Core
+ Umbraco CMS
+ Umbraco.Cms.Core
+ Umbraco CMS Core
+ Contains the core assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco
+ Umbraco CMS
+
-
- bin\Release\Umbraco.Core.xml
-
+
+ bin\Release\Umbraco.Core.xml
+
-
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
- all
-
-
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ all
+
+
-
-
- <_Parameter1>Umbraco.Tests
-
-
- <_Parameter1>Umbraco.Tests.Common
-
-
- <_Parameter1>Umbraco.Tests.UnitTests
-
-
- <_Parameter1>Umbraco.Tests.Benchmarks
-
-
- <_Parameter1>Umbraco.Tests.Integration
-
-
- <_Parameter1>DynamicProxyGenAssembly2
-
-
+
+
+ <_Parameter1>Umbraco.Tests
+
+
+ <_Parameter1>Umbraco.Tests.Common
+
+
+ <_Parameter1>Umbraco.Tests.UnitTests
+
+
+ <_Parameter1>Umbraco.Tests.Benchmarks
+
+
+ <_Parameter1>Umbraco.Tests.Integration
+
+
+ <_Parameter1>DynamicProxyGenAssembly2
+
+
-
-
-
+
+
+
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs
index 16c411c772..c439c1a80d 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs
@@ -53,7 +53,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
var entries = new List();
- foreach (var group in ids.InGroupsOf(2000))
+ foreach (var group in ids.InGroupsOf(Constants.Sql.MaxParameterCount))
{
var sql = Sql()
.Select()
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index 239f0a89ed..fd4d1c33b9 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -657,7 +657,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
// in the table?
// get all PropertyDataDto for all definitions / versions
- var allPropertyDataDtos = Database.FetchByGroups(versions, 2000, batch =>
+ var allPropertyDataDtos = Database.FetchByGroups(versions, Constants.Sql.MaxParameterCount, batch =>
SqlContext.Sql()
.Select()
.From()
@@ -666,7 +666,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
// get PropertyDataDto distinct PropertyTypeDto
var allPropertyTypeIds = allPropertyDataDtos.Select(x => x.PropertyTypeId).Distinct().ToList();
- var allPropertyTypeDtos = Database.FetchByGroups(allPropertyTypeIds, 2000, batch =>
+ var allPropertyTypeDtos = Database.FetchByGroups(allPropertyTypeIds, Constants.Sql.MaxParameterCount, batch =>
SqlContext.Sql()
.Select(r => r.Select(x => x.DataTypeDto))
.From()
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index d93c2c8322..d7be081fe1 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -795,7 +795,7 @@ AND umbracoNode.id <> @id",
// note: important to use SqlNullableEquals for nullable types, cannot directly compare language identifiers
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
- if (whereInArgsCount > 2000)
+ if (whereInArgsCount > Constants.Sql.MaxParameterCount)
throw new NotSupportedException("Too many property/content types.");
// delete existing relations (for target language)
@@ -933,7 +933,7 @@ AND umbracoNode.id <> @id",
// note: important to use SqlNullableEquals for nullable types, cannot directly compare language identifiers
//
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
- if (whereInArgsCount > 2000)
+ if (whereInArgsCount > Constants.Sql.MaxParameterCount)
throw new NotSupportedException("Too many property/content types.");
//first clear out any existing property data that might already exists under the target language
@@ -1032,7 +1032,7 @@ AND umbracoNode.id <> @id",
//based on the current variance of each item to see if it's 'edited' value should be true/false.
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
- if (whereInArgsCount > 2000)
+ if (whereInArgsCount > Constants.Sql.MaxParameterCount)
throw new NotSupportedException("Too many property/content types.");
var propertySql = Sql()
@@ -1121,14 +1121,20 @@ AND umbracoNode.id <> @id",
}
}
- //lookup all matching rows in umbracoDocumentCultureVariation
- var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000)
- .SelectMany(_ => Database.Fetch(
- Sql().Select().From()
- .WhereIn(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
- .WhereIn(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId))))
- //convert to dictionary with the same key type
- .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x);
+ // lookup all matching rows in umbracoDocumentCultureVariation
+ // fetch in batches to account for maximum parameter count (distinct languages can't exceed 2000)
+ var languageIds = editedLanguageVersions.Keys.Select(x => x.langId).Distinct().ToArray();
+ var nodeIds = editedLanguageVersions.Keys.Select(x => x.nodeId).Distinct();
+ var docCultureVariationsToUpdate = nodeIds.InGroupsOf(Constants.Sql.MaxParameterCount - languageIds.Length)
+ .SelectMany(group =>
+ {
+ var sql = Sql().Select().From()
+ .WhereIn(x => x.LanguageId, languageIds)
+ .WhereIn(x => x.NodeId, group);
+
+ return Database.Fetch(sql);
+ })
+ .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x); //convert to dictionary with the same key type
var toUpdate = new List();
foreach (var ev in editedLanguageVersions)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs
index 0ec31d843f..bc9892b1ee 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs
@@ -263,13 +263,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
Func>> getItemsFromParents = guids =>
{
- //needs to be in groups of 2000 because we are doing an IN clause and there's a max parameter count that can be used.
- return guids.InGroupsOf(2000)
- .Select(@group =>
+ return guids.InGroupsOf(Constants.Sql.MaxParameterCount)
+ .Select(group =>
{
var sqlClause = GetBaseQuery(false)
.Where(x => x.Parent != null)
- .Where($"{SqlSyntax.GetQuotedColumnName("parent")} IN (@parentIds)", new { parentIds = @group });
+ .WhereIn(x => x.Parent, group);
var translator = new SqlTranslator(sqlClause, Query());
var sql = translator.Translate();
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
index 4c9b19f1a9..75d66cf09d 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -1390,7 +1390,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
{
var result = new Dictionary();
- var scheduleDtos = Database.FetchByGroups(contentIds, 2000, batch => Sql()
+ var scheduleDtos = Database.FetchByGroups(contentIds, Constants.Sql.MaxParameterCount, batch => Sql()
.Select()
.From()
.WhereIn(x => x.NodeId, batch));
@@ -1440,7 +1440,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
if (versions.Count == 0)
return new Dictionary>();
- var dtos = Database.FetchByGroups(versions, 2000, batch
+ var dtos = Database.FetchByGroups(versions, Constants.Sql.MaxParameterCount, batch
=> Sql()
.Select()
.From()
@@ -1469,7 +1469,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
{
var ids = temps.Select(x => x.Id);
- var dtos = Database.FetchByGroups(ids, 2000, batch =>
+ var dtos = Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, batch =>
Sql()
.Select()
.From()
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs
index bc1b1b1881..ac69d02587 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs
@@ -62,7 +62,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
{
if (ids.Any())
{
- return Database.FetchByGroups(ids, 2000, batch =>
+ return Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, batch =>
GetBaseQuery(false)
.Where(x => x.NodeObjectType == NodeObjectTypeId)
.WhereIn(x => x.NodeId, batch))
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs
index 4eb4f108ce..f1b9c77d0a 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs
@@ -279,7 +279,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
if (v == null) return entitiesList;
// fetch all variant info dtos
- var dtos = Database.FetchByGroups(v.Select(x => x.Id), 2000, GetVariantInfos);
+ var dtos = Database.FetchByGroups(v.Select(x => x.Id), Constants.Sql.MaxParameterCount, GetVariantInfos);
// group by node id (each group contains all languages)
var xdtos = dtos.GroupBy(x => x.NodeId).ToDictionary(x => x.Key, x => x);
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs
index 25927213e5..1589c9d52e 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs
@@ -209,16 +209,15 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
// can't query more than 2000 ids at a time... but if someone is really querying 2000+ entities,
// the additional overhead of fetching them in groups is minimal compared to the lookup time of each group
- const int maxParams = 2000;
- if (ids.Length <= maxParams)
+ if (ids.Length <= Constants.Sql.MaxParameterCount)
{
return CachePolicy.GetAll(ids, PerformGetAll);
}
var entities = new List();
- foreach (var groupOfIds in ids.InGroupsOf(maxParams))
+ foreach (var group in ids.InGroupsOf(Constants.Sql.MaxParameterCount))
{
- entities.AddRange(CachePolicy.GetAll(groupOfIds.ToArray(), PerformGetAll));
+ entities.AddRange(CachePolicy.GetAll(group.ToArray(), PerformGetAll));
}
return entities;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
index 22083eae30..6485dc286a 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
@@ -530,16 +530,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
var matchedMembers = Get(query).ToArray();
var membersInGroup = new List();
- //then we need to filter the matched members that are in the role
- //since the max sql params are 2100 on sql server, we'll reduce that to be safe for potentially other servers and run the queries in batches
- var inGroups = matchedMembers.InGroupsOf(1000);
- foreach (var batch in inGroups)
- {
- var memberIdBatch = batch.Select(x => x.Id);
+ //then we need to filter the matched members that are in the role
+ foreach (var group in matchedMembers.Select(x => x.Id).InGroupsOf(Constants.Sql.MaxParameterCount))
+ {
var sql = Sql().SelectAll().From()
.Where(dto => dto.MemberGroup == memberGroup.Id)
- .WhereIn(dto => dto.Member, memberIdBatch);
+ .WhereIn(dto => dto.Member, group);
var memberIdsInGroup = Database.Fetch(sql)
.Select(x => x.Member).ToArray();
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs
index 582120992b..be98b7a7f6 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs
@@ -38,44 +38,41 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
///
///
///
- /// This method will not support passing in more than 2000 group Ids
+ /// This method will not support passing in more than 2000 group IDs when also passing in entity IDs.
///
public EntityPermissionCollection GetPermissionsForEntities(int[] groupIds, params int[] entityIds)
{
var result = new EntityPermissionCollection();
- foreach (var groupOfGroupIds in groupIds.InGroupsOf(2000))
+ if (entityIds.Length == 0)
{
- //copy local
- var localIds = groupOfGroupIds.ToArray();
-
- if (entityIds.Length == 0)
+ foreach (var group in groupIds.InGroupsOf(Constants.Sql.MaxParameterCount))
{
var sql = Sql()
.SelectAll()
.From()
- .Where(dto => localIds.Contains(dto.UserGroupId));
+ .Where(dto => group.Contains(dto.UserGroupId));
+
var permissions = AmbientScope.Database.Fetch(sql);
foreach (var permission in ConvertToPermissionList(permissions))
{
result.Add(permission);
}
}
- else
+ }
+ else
+ {
+ foreach (var group in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount - groupIds.Length))
{
- //iterate in groups of 2000 since we don't want to exceed the max SQL param count
- foreach (var groupOfEntityIds in entityIds.InGroupsOf(2000))
+ var sql = Sql()
+ .SelectAll()
+ .From()
+ .Where(dto => groupIds.Contains(dto.UserGroupId) && group.Contains(dto.NodeId));
+
+ var permissions = AmbientScope.Database.Fetch(sql);
+ foreach (var permission in ConvertToPermissionList(permissions))
{
- var ids = groupOfEntityIds;
- var sql = Sql()
- .SelectAll()
- .From()
- .Where(dto => localIds.Contains(dto.UserGroupId) && ids.Contains(dto.NodeId));
- var permissions = AmbientScope.Database.Fetch(sql);
- foreach (var permission in ConvertToPermissionList(permissions))
- {
- result.Add(permission);
- }
+ result.Add(permission);
}
}
}
@@ -133,11 +130,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
var db = AmbientScope.Database;
- //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit
var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND nodeId in (@nodeIds)";
- foreach (var idGroup in entityIds.InGroupsOf(2000))
+ foreach (var group in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount))
{
- db.Execute(sql, new { groupId, nodeIds = idGroup });
+ db.Execute(sql, new { groupId, nodeIds = group });
}
var toInsert = new List();
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs
index 1536768bae..f035ffbd9b 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs
@@ -39,8 +39,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
protected override IEnumerable PerformGetAll(params Guid[] ids)
{
- if (ids.Length > 2000)
- throw new NotSupportedException("This repository does not support more than 2000 ids.");
+ if (ids.Length > Constants.Sql.MaxParameterCount)
+ throw new NotSupportedException($"This repository does not support more than {Constants.Sql.MaxParameterCount} ids.");
+
var sql = GetBaseQuery(false).WhereIn(x => x.Id, ids);
var dtos = Database.Fetch(sql);
return dtos.WhereNotNull().Select(Map);
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs
index 8fbc784576..736d183c85 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs
@@ -38,7 +38,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
{
var dtos = ids.Length == 0
? Database.Fetch(Sql().Select().From())
- : Database.FetchByGroups(ids, 2000, batch => Sql().Select().From().WhereIn(x => x.Id, batch));
+ : Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, batch => Sql().Select().From().WhereIn(x => x.Id, batch));
return dtos.Select(TagFactory.BuildEntity).ToList();
}
diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
index 91af372434..cdb284fdb0 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
@@ -1026,6 +1026,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case UmbracoEntityTypes.Macro:
+ case UmbracoEntityTypes.Template:
+ var template = Services.FileService.GetTemplate(key);
+ if (template is null)
+ {
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ return Mapper.Map(template);
+
default:
throw new NotSupportedException("The " + typeof(EntityController) +
" does not currently support data for the type " + entityType);
@@ -1059,6 +1068,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case UmbracoEntityTypes.Macro:
+ case UmbracoEntityTypes.Template:
+ var template = Services.FileService.GetTemplate(id);
+ if (template is null)
+ {
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ return Mapper.Map(template);
+
default:
throw new NotSupportedException("The " + typeof(EntityController) +
" does not currently support data for the type " + entityType);
diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json
index ec4ead689c..43d7a3cecd 100644
--- a/src/Umbraco.Web.UI.Client/package.json
+++ b/src/Umbraco.Web.UI.Client/package.json
@@ -44,7 +44,7 @@
"lazyload-js": "1.0.0",
"moment": "2.22.2",
"ng-file-upload": "12.2.13",
- "nouislider": "15.4.0",
+ "nouislider": "15.5.0",
"npm": "^6.14.7",
"spectrum-colorpicker2": "2.0.8",
"tinymce": "4.9.11",
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 4a1988cc27..50a32e0b05 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
@@ -200,6 +200,10 @@
}
}));
+ evts.push(eventsService.on("rte.shortcut.saveAndPublish", function () {
+ $scope.saveAndPublish();
+ }));
+
evts.push(eventsService.on("content.saved", function () {
// Clear out localstorage keys that start with tinymce__
// When we save/perist a content node
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js
index bf03749faa..4c628391cb 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js
@@ -1,9 +1,9 @@
(function () {
'use strict';
- function GridSelector($location, overlayService, editorService) {
+ function GridSelector(overlayService, editorService) {
- function link(scope, el, attr, ctrl) {
+ function link(scope) {
var eventBindings = [];
scope.dialogModel = {};
@@ -33,26 +33,30 @@
};
scope.openItemPicker = function ($event) {
- var dialogModel = {
- view: "itempicker",
- title: "Choose " + scope.itemLabel,
- availableItems: scope.availableItems,
- selectedItems: scope.selectedItems,
- position: "target",
- event: $event,
- submit: function (model) {
- scope.selectedItems.push(model.selectedItem);
- // if no default item - set item as default
- if (scope.defaultItem === null) {
- scope.setAsDefaultItem(model.selectedItem);
+ if (scope.itemPicker) {
+ scope.itemPicker();
+ } else {
+ var dialogModel = {
+ view: "itempicker",
+ title: "Choose " + scope.itemLabel,
+ availableItems: scope.availableItems,
+ selectedItems: scope.selectedItems,
+ position: "target",
+ event: $event,
+ submit: function (model) {
+ scope.selectedItems.push(model.selectedItem);
+ // if no default item - set item as default
+ if (scope.defaultItem === null) {
+ scope.setAsDefaultItem(model.selectedItem);
+ }
+ overlayService.close();
+ },
+ close: function () {
+ overlayService.close();
}
- overlayService.close();
- },
- close: function() {
- overlayService.close();
- }
- };
- overlayService.open(dialogModel);
+ };
+ overlayService.open(dialogModel);
+ }
};
scope.openTemplate = function (selectedItem) {
@@ -156,7 +160,8 @@
availableItems: "=",
defaultItem: "=",
itemName: "@",
- updatePlaceholder: "="
+ updatePlaceholder: "=",
+ itemPicker: "="
},
link: link
};
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 fe802a8a28..1f65bd7cea 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
@@ -179,7 +179,7 @@ When building a custom infinite editor view you can use the same components as a
} else {
focus();
}
- });
+ });
/**
* @ngdoc method
@@ -972,6 +972,28 @@ When building a custom infinite editor view you can use the same components as a
open(editor);
}
+ /**
+ * @ngdoc method
+ * @name umbraco.services.editorService#templatePicker
+ * @methodOf umbraco.services.editorService
+ *
+ * @description
+ * Opens a template picker in infinite editing, the submit callback returns an array of selected items.
+ *
+ * @param {object} editor rendering options.
+ * @param {boolean} editor.multiPicker Pick one or multiple items.
+ * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object.
+ * @param {function} editor.close Callback function when the close button is clicked.
+ * @returns {object} editor object.
+ */
+ function templatePicker(editor) {
+ editor.view = "views/common/infiniteeditors/treepicker/treepicker.html";
+ if (!editor.size) editor.size = "small";
+ editor.section = "settings";
+ editor.treeAlias = "templates";
+ open(editor);
+ }
+
/**
* @ngdoc method
* @name umbraco.services.editorService#macroPicker
@@ -1134,6 +1156,7 @@ When building a custom infinite editor view you can use the same components as a
templateSections: templateSections,
userPicker: userPicker,
itemPicker: itemPicker,
+ templatePicker: templatePicker,
macroPicker: macroPicker,
memberGroupPicker: memberGroupPicker,
memberPicker: memberPicker,
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 4c3901e63c..4556641eca 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
@@ -1226,6 +1226,12 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
});
});
+ editor.addShortcut('Ctrl+P', '', function () {
+ angularHelper.safeApply($rootScope, function () {
+ eventsService.emit("rte.shortcut.saveAndPublish");
+ });
+ });
+
},
insertLinkInEditor: function (editor, target, anchorElm) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html
index 7ec69018b6..9ebb9d4e45 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html
@@ -30,21 +30,22 @@
-
+ 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.
-
+ There are no Content Types available to use as a composition.
-
+ This Content Type is used in a composition, and therefore cannot be composed itself.
-
-
+
Where is this composition used?
+
This composition is currently used in the composition of the following Content Types: