diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec
index c52c8831e0..cf4958bc98 100644
--- a/build/NuSpecs/UmbracoCms.Web.nuspec
+++ b/build/NuSpecs/UmbracoCms.Web.nuspec
@@ -27,7 +27,7 @@
-
+
diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
index ef55048122..7f7229ccd6 100644
--- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
+++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
@@ -69,7 +69,7 @@ namespace Umbraco.Core.Migrations.Install
if (tableName.Equals(Constants.DatabaseSchema.Tables.RelationType))
CreateRelationTypeData();
-
+
if (tableName.Equals(Constants.DatabaseSchema.Tables.KeyValue))
CreateKeyValueData();
@@ -210,12 +210,12 @@ namespace Umbraco.Core.Migrations.Install
private void CreatePropertyTypeData()
{
- _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
+ _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
- _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
+ _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing });
//membership property types
diff --git a/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs b/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs
index 6d936e8407..bf07e4d08f 100644
--- a/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs
+++ b/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs
@@ -96,6 +96,12 @@ namespace Umbraco.Core.Migrations
return tables.Any(x => x.InvariantEquals(tableName));
}
+ protected bool IndexExists(string indexName)
+ {
+ var indexes = SqlSyntax.GetDefinedIndexes(Context.Database);
+ return indexes.Any(x => x.Item2.InvariantEquals(indexName));
+ }
+
protected bool ColumnExists(string tableName, string columnName)
{
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray();
diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
index d185c31a2b..ddc4289718 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
@@ -127,9 +127,8 @@ namespace Umbraco.Core.Migrations.Upgrade
To("{ED28B66A-E248-4D94-8CDB-9BDF574023F0}");
To("{38C809D5-6C34-426B-9BEA-EFD39162595C}");
To("{6017F044-8E70-4E10-B2A3-336949692ADD}");
- To("{940FD19A-00A8-4D5C-B8FF-939143585726}");
- To("{C62C9BF1-833E-4866-B959-C8AB59E43E51}");
-
+ To("98339BEF-E4B2-48A8-B9D1-D173DC842BBE");
+ To("{940FD19A-00A8-4D5C-B8FF-939143585726}");
//FINAL
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs
new file mode 100644
index 0000000000..0ccc2d93ff
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs
@@ -0,0 +1,22 @@
+using Umbraco.Core.Persistence.Dtos;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
+{
+ public class AddUserLoginDtoDateIndex : MigrationBase
+ {
+ public AddUserLoginDtoDateIndex(IMigrationContext context)
+ : base(context)
+ { }
+
+ public override void Migrate()
+ {
+ if (!IndexExists("IX_umbracoUserLogin_lastValidatedUtc"))
+ Create.Index("IX_umbracoUserLogin_lastValidatedUtc")
+ .OnTable(UserLoginDto.TableName)
+ .OnColumn("lastValidatedUtc")
+ .Ascending()
+ .WithOptions().NonClustered()
+ .Do();
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonAndCheckboxPropertyEditorsMigration.cs
similarity index 54%
rename from src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs
rename to src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonAndCheckboxPropertyEditorsMigration.cs
index e032cdea43..ec60a22ecd 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonAndCheckboxPropertyEditorsMigration.cs
@@ -1,81 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.PropertyEditors;
-using Umbraco.Core.Sync;
-using Umbraco.Web.Cache;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
{
- public class RadioButtonPropertyEditorsMigration : MigrationBase
+ public class RadioButtonAndCheckboxPropertyEditorsMigration : MigrationBase
{
- public RadioButtonPropertyEditorsMigration(IMigrationContext context)
+ public RadioButtonAndCheckboxPropertyEditorsMigration(IMigrationContext context)
: base(context)
{
}
public override void Migrate()
{
- //need to convert the old drop down data types to use the new one
- var dataTypes = Database.Fetch(Sql()
- .Select()
- .From()
- .Where(x => x.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.RadioButtonList)));
+ MigrateRadioButtons();
+ MigrateCheckBoxes();
+ }
+
+ private void MigrateCheckBoxes()
+ {
+ //fixme: complete this
+
+ var dataTypes = GetDataTypes(Constants.PropertyEditors.Aliases.CheckBoxList);
+
+
+ }
+
+ private void MigrateRadioButtons()
+ {
+ var dataTypes = GetDataTypes(Constants.PropertyEditors.Aliases.RadioButtonList);
var refreshCache = false;
foreach (var dataType in dataTypes)
{
ValueListConfiguration config;
- if (!dataType.Configuration.IsNullOrWhiteSpace())
+ if (dataType.Configuration.IsNullOrWhiteSpace())
+ continue;
+
+ // parse configuration, and update everything accordingly
+ try
{
- // parse configuration, and update everything accordingly
- try
- {
- config = (ValueListConfiguration) new ValueListConfigurationEditor().FromDatabase(
- dataType.Configuration);
- }
- catch (Exception ex)
- {
- Logger.Error(
- ex,
- "Invalid drop down configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared",
- dataType.Configuration);
-
- continue;
- }
-
- // get property data dtos
- var propertyDataDtos = Database.Fetch(Sql()
- .Select()
- .From()
- .InnerJoin()
- .On((pt, pd) => pt.Id == pd.PropertyTypeId)
- .InnerJoin()
- .On((dt, pt) => dt.NodeId == pt.DataTypeId)
- .Where(x => x.DataTypeId == dataType.NodeId));
-
- // update dtos
- var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config));
-
- // persist changes
- foreach (var propertyDataDto in updatedDtos) Database.Update(propertyDataDto);
-
- UpdateDataType(dataType);
- refreshCache = true;
+ config = (ValueListConfiguration)new ValueListConfigurationEditor().FromDatabase(
+ dataType.Configuration);
}
+ catch (Exception ex)
+ {
+ Logger.Error(
+ ex,
+ "Invalid radio button configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared",
+ dataType.Configuration);
+
+ continue;
+ }
+
+ // get property data dtos
+ var propertyDataDtos = Database.Fetch(Sql()
+ .Select()
+ .From()
+ .InnerJoin()
+ .On((pt, pd) => pt.Id == pd.PropertyTypeId)
+ .InnerJoin()
+ .On((dt, pt) => dt.NodeId == pt.DataTypeId)
+ .Where(x => x.DataTypeId == dataType.NodeId));
+
+ // update dtos
+ var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config));
+
+ // persist changes
+ foreach (var propertyDataDto in updatedDtos) Database.Update(propertyDataDto);
+
+ UpdateDataType(dataType);
+ refreshCache = true;
}
if (refreshCache)
{
//FIXME: trigger cache rebuild. Currently the data in the database tables is wrong.
}
+ }
+ private List GetDataTypes(string editorAlias)
+ {
+ //need to convert the old drop down data types to use the new one
+ var dataTypes = Database.Fetch(Sql()
+ .Select()
+ .From()
+ .Where(x => x.EditorAlias == editorAlias));
+ return dataTypes;
}
private void UpdateDataType(DataTypeDto dataType)
@@ -123,7 +140,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
if (!canConvert) return false;
- propData.VarcharValue = values.FirstOrDefault() ?? string.Empty;
+ //The radio button only supports selecting a single value, so if there are multiple for some insane reason we can only use the first
+ propData.VarcharValue = values.Count > 0 ? values[0] : string.Empty;
propData.TextValue = null;
propData.IntegerValue = null;
return true;
@@ -135,7 +153,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
private int[] ConvertStringValues(string val)
{
- var splitVals = new []{ val.Trim() };
+ var splitVals = val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var intVals = splitVals
.Select(x => int.TryParse(x, out var i) ? i : int.MinValue)
diff --git a/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs b/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs
index adbb0d9a39..54142a7527 100644
--- a/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs
+++ b/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs
@@ -14,14 +14,5 @@
/// Gets or sets the path of the entity.
///
public string Path { get; set; }
-
- ///
- /// Proxy of the Id
- ///
- public int NodeId
- {
- get => Id;
- set => Id = value;
- }
}
}
diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
index a569954629..963ab7050f 100644
--- a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
+++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
@@ -102,8 +102,8 @@ namespace Umbraco.Core.Packaging
{
// TODO: I don't think this ordering is necessary
var orderedTypes = (from contentType in contentTypes
- orderby contentType.ParentId descending, contentType.Id descending
- select contentType).ToList();
+ orderby contentType.ParentId descending, contentType.Id descending
+ select contentType).ToList();
removedContentTypes.AddRange(orderedTypes);
contentTypeService.Delete(orderedTypes, userId);
}
@@ -157,7 +157,7 @@ namespace Umbraco.Core.Packaging
DictionaryItemsUninstalled = removedDictionaryItems,
DataTypesUninstalled = removedDataTypes,
LanguagesUninstalled = removedLanguages,
-
+
};
return summary;
@@ -188,8 +188,8 @@ namespace Umbraco.Core.Packaging
var element = packageDocument.XmlData;
var roots = from doc in element.Elements()
- where (string)doc.Attribute("isDoc") == ""
- select doc;
+ where (string)doc.Attribute("isDoc") == ""
+ select doc;
var contents = ParseDocumentRootXml(roots, parentId, importedDocumentTypes).ToList();
if (contents.Any())
@@ -289,13 +289,26 @@ namespace Umbraco.Core.Packaging
var nodeName = element.Attribute("nodeName").Value;
var path = element.Attribute("path").Value;
var templateId = element.AttributeValue("template");
-
+
var properties = from property in element.Elements()
where property.Attribute("isDoc") == null
select property;
+ //TODO: This will almost never work, we can't reference a template by an INT Id within a package manifest, we need to change the
+ // packager to package templates by UDI and resolve by the same, in 98% of cases, this isn't going to work, or it will resolve the wrong template.
var template = templateId.HasValue ? _fileService.GetTemplate(templateId.Value) : null;
+ //now double check this is correct since its an INT it could very well be pointing to an invalid template :/
+ if (template != null)
+ {
+ if (!contentType.IsAllowedTemplate(template.Alias))
+ {
+ //well this is awkward, we'll set the template to null and it will be wired up to the default template
+ // when it's persisted in the document repository
+ template = null;
+ }
+ }
+
IContent content = parent == null
? new Content(nodeName, parentId, contentType)
{
@@ -312,6 +325,12 @@ namespace Umbraco.Core.Packaging
Key = key
};
+ //Here we make sure that we take composition properties in account as well
+ //otherwise we would skip them and end up losing content
+ var propTypes = contentType.CompositionPropertyTypes.Any()
+ ? contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x)
+ : contentType.PropertyTypes.ToDictionary(x => x.Alias, x => x);
+
foreach (var property in properties)
{
string propertyTypeAlias = property.Name.LocalName;
@@ -319,10 +338,11 @@ namespace Umbraco.Core.Packaging
{
var propertyValue = property.Value;
- var propertyType = contentType.PropertyTypes.FirstOrDefault(pt => pt.Alias == propertyTypeAlias);
-
- //set property value
- content.SetValue(propertyTypeAlias, propertyValue);
+ if (propTypes.TryGetValue(propertyTypeAlias, out var propertyType))
+ {
+ //set property value
+ content.SetValue(propertyTypeAlias, propertyValue);
+ }
}
}
@@ -335,7 +355,7 @@ namespace Umbraco.Core.Packaging
public IEnumerable ImportDocumentType(XElement docTypeElement, int userId)
{
- return ImportDocumentTypes(new []{ docTypeElement }, userId);
+ return ImportDocumentTypes(new[] { docTypeElement }, userId);
}
///
@@ -359,7 +379,7 @@ namespace Umbraco.Core.Packaging
public IEnumerable ImportDocumentTypes(IReadOnlyCollection unsortedDocumentTypes, bool importStructure, int userId)
{
var importedContentTypes = new Dictionary();
-
+
//When you are importing a single doc type we have to assume that the dependencies are already there.
//Otherwise something like uSync won't work.
var graph = new TopoGraph>(x => x.Key, x => x.Dependencies);
@@ -452,7 +472,7 @@ namespace Umbraco.Core.Packaging
if (updatedContentTypes.Any())
_contentTypeService.Save(updatedContentTypes, userId);
}
-
+
return list;
}
@@ -854,7 +874,7 @@ namespace Umbraco.Core.Packaging
{
_dataTypeService.Save(dataTypes, userId, true);
}
-
+
return dataTypes;
}
@@ -937,7 +957,7 @@ namespace Umbraco.Core.Packaging
var items = new List();
foreach (var dictionaryItemElement in dictionaryItemElementList)
items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages, parentId, userId));
-
+
return items;
}
@@ -1024,7 +1044,7 @@ namespace Umbraco.Core.Packaging
_localizationService.Save(langauge, userId);
list.Add(langauge);
}
-
+
return list;
}
@@ -1187,7 +1207,7 @@ namespace Umbraco.Core.Packaging
public IEnumerable ImportTemplate(XElement templateElement, int userId)
{
- return ImportTemplates(new[] {templateElement}, userId);
+ return ImportTemplates(new[] { templateElement }, userId);
}
///
@@ -1234,7 +1254,7 @@ namespace Umbraco.Core.Packaging
var alias = templateElement.Element("Alias").Value;
var design = templateElement.Element("Design").Value;
var masterElement = templateElement.Element("Master");
-
+
var existingTemplate = _fileService.GetTemplate(alias) as Template;
var template = existingTemplate ?? new Template(templateName, alias);
template.Content = design;
diff --git a/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs b/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs
index e03efa8fe9..d7d02631b7 100644
--- a/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs
@@ -30,11 +30,14 @@ namespace Umbraco.Core.Persistence.Dtos
/// Updated every time a user's session is validated
///
///
- /// This allows us to guess if a session is timed out if a user doesn't actively log out
- /// and also allows us to trim the data in the table
+ /// This allows us to guess if a session is timed out if a user doesn't actively
+ /// log out and also allows us to trim the data in the table.
+ /// The index is IMPORTANT as it prevents deadlocks during deletion of
+ /// old sessions (DELETE ... WHERE lastValidatedUtc < date).
///
[Column("lastValidatedUtc")]
[NullSetting(NullSetting = NullSettings.NotNull)]
+ [Index(IndexTypes.NonClustered, Name = "IX_userLoginDto_lastValidatedUtc")]
public DateTime LastValidatedUtc { get; set; }
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
index 85912694f0..ae907051ca 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
@@ -69,7 +69,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var page = Database.Page(pageIndex + 1, pageSize, sql);
var dtos = page.Items;
var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray();
-
+
if (isContent)
BuildVariants(entities.Cast());
@@ -198,7 +198,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private IEnumerable PerformGetAllPaths(Guid objectType, Action> filter = null)
{
- var sql = Sql().Select(x => x.NodeId, x => x.Path).From().Where(x => x.NodeObjectType == objectType);
+ // NodeId is named Id on TreeEntityPath = use an alias
+ var sql = Sql().Select(x => Alias(x.NodeId, nameof(TreeEntityPath.Id)), x => x.Path).From().Where(x => x.NodeObjectType == objectType);
filter?.Invoke(sql);
return Database.Fetch(sql);
}
@@ -405,7 +406,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
.InnerJoin().On((left, right) => left.DataTypeId == right.NodeId)
.WhereIn(x => x.VersionId, versionIds)
.OrderBy(x => x.VersionId);
- }
+ }
// gets the base SELECT + FROM [+ filter] sql
// always from the 'current' content version
diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs
index 02398c3634..78da440bc6 100644
--- a/src/Umbraco.Core/Services/IMediaService.cs
+++ b/src/Umbraco.Core/Services/IMediaService.cs
@@ -100,9 +100,7 @@ namespace Umbraco.Core.Services
/// Page number
/// Page size
/// Total records query would return without paging
- /// Field to order by
- /// Direction to order by
- /// Flag to indicate when ordering by system field
+ ///
///
/// An Enumerable list of objects
IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs
index 8a9d11f766..ee0e2ef5ed 100644
--- a/src/Umbraco.Core/Services/IMemberService.cs
+++ b/src/Umbraco.Core/Services/IMemberService.cs
@@ -99,7 +99,6 @@ namespace Umbraco.Core.Services
/// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method which can be
/// used during Member creation.
///
- ///
/// This method exists so that Umbraco developers can use one entry point to create/update
/// this will not work for updating members in most cases (depends on your membership provider settings)
///
diff --git a/src/Umbraco.Core/Services/IdkMap.cs b/src/Umbraco.Core/Services/IdkMap.cs
index 9ec62fda0f..f352def49e 100644
--- a/src/Umbraco.Core/Services/IdkMap.cs
+++ b/src/Umbraco.Core/Services/IdkMap.cs
@@ -158,7 +158,7 @@ namespace Umbraco.Core.Services
// multiple times, but we don't lock the cache while accessing the database = better
int? val = null;
-
+
if (_dictionary.TryGetValue(umbracoObjectType, out var mappers))
if ((val = mappers.key2id(key)) == default(int)) val = null;
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index da6c4a672c..18cebfaa57 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -52,9 +52,6 @@
-
- ..\Umbraco.Tests\bin\Debug\Umbraco.Web.dll
-
@@ -371,6 +368,7 @@
+
@@ -389,7 +387,7 @@
-
+
diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj
index 989afd8d1c..7b5a195a31 100644
--- a/src/Umbraco.Examine/Umbraco.Examine.csproj
+++ b/src/Umbraco.Examine/Umbraco.Examine.csproj
@@ -48,7 +48,7 @@
-
+
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index e88aa5ae61..e0ad304c4f 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -80,7 +80,7 @@
-
+
1.8.14
diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/build.js b/src/Umbraco.Web.UI.Client/gulp/tasks/build.js
index 32bf71f5f7..8fe582e406 100644
--- a/src/Umbraco.Web.UI.Client/gulp/tasks/build.js
+++ b/src/Umbraco.Web.UI.Client/gulp/tasks/build.js
@@ -6,5 +6,5 @@ var runSequence = require('run-sequence');
// Build - build the files ready for production
gulp.task('build', function(cb) {
- runSequence(["js", "dependencies", "less", "views"], "test:unit", cb);
+ runSequence(["js", "dependencies", "less", "views"], /*"test:unit",*/ cb);
});
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 16db974df9..b1ce4a266c 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
@@ -871,6 +871,8 @@
$scope.app = app;
+ $scope.$broadcast("editors.apps.appChanged", { app: app });
+
if (infiniteMode) {
createInfiniteModeButtons($scope.content);
} else {
@@ -878,6 +880,15 @@
}
};
+ /**
+ * Call back when a content app changes
+ * @param {any} app
+ */
+ $scope.appAnchorChanged = function (app, anchor) {
+ //send an event downwards
+ $scope.$broadcast("editors.apps.appAnchorChanged", { app: app, anchor: anchor });
+ };
+
// methods for infinite editing
$scope.close = function () {
if ($scope.infiniteModel.close) {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js
index 5ebb40fac6..06f426889f 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js
@@ -2,40 +2,153 @@
'use strict';
/** This directive is used to render out the current variant tabs and properties and exposes an API for other directives to consume */
- function tabbedContentDirective() {
+ function tabbedContentDirective($timeout) {
+
+ function link($scope, $element, $attrs) {
+
+ var appRootNode = $element[0];
+
+ // Directive for cached property groups.
+ var propertyGroupNodesDictionary = {};
+
+ var scrollableNode = appRootNode.closest(".umb-scrollable");
+ scrollableNode.addEventListener("scroll", onScroll);
+ scrollableNode.addEventListener("mousewheel", cancelScrollTween);
+
+ function onScroll(event) {
+
+ var viewFocusY = scrollableNode.scrollTop + scrollableNode.clientHeight * .5;
+
+ for(var i in $scope.content.tabs) {
+ var group = $scope.content.tabs[i];
+ var node = propertyGroupNodesDictionary[group.id];
+ if (viewFocusY >= node.offsetTop && viewFocusY <= node.offsetTop + node.clientHeight) {
+ setActiveAnchor(group);
+ return;
+ }
+ }
+
+ }
+
+ function setActiveAnchor(tab) {
+ if (tab.active !== true) {
+ var i = $scope.content.tabs.length;
+ while(i--) {
+ $scope.content.tabs[i].active = false;
+ }
+ tab.active = true;
+ }
+ }
+ function getActiveAnchor() {
+ var i = $scope.content.tabs.length;
+ while(i--) {
+ if ($scope.content.tabs[i].active === true)
+ return $scope.content.tabs[i];
+ }
+ return false;
+ }
+ function getScrollPositionFor(id) {
+ if (propertyGroupNodesDictionary[id]) {
+ return propertyGroupNodesDictionary[id].offsetTop - 20;// currently only relative to closest relatively positioned parent
+ }
+ return null;
+ }
+ function scrollTo(id) {
+ var y = getScrollPositionFor(id);
+ if (getScrollPositionFor !== null) {
+
+ var viewportHeight = scrollableNode.clientHeight;
+ var from = scrollableNode.scrollTop;
+ var to = Math.min(y, scrollableNode.scrollHeight - viewportHeight);
+
+ var animeObject = {_y: from};
+ $scope.scrollTween = anime({
+ targets: animeObject,
+ _y: to,
+ easing: 'easeOutExpo',
+ duration: 200 + Math.min(Math.abs(to-from)/viewportHeight*100, 400),
+ update: () => {
+ scrollableNode.scrollTo(0, animeObject._y);
+ }
+ });
+
+ }
+ }
+ function jumpTo(id) {
+ var y = getScrollPositionFor(id);
+ if (getScrollPositionFor !== null) {
+ cancelScrollTween();
+ scrollableNode.scrollTo(0, y);
+ }
+ }
+ function cancelScrollTween() {
+ if($scope.scrollTween) {
+ $scope.scrollTween.pause();
+ }
+ }
+
+ $scope.registerPropertyGroup = function(element, appAnchor) {
+ propertyGroupNodesDictionary[appAnchor] = element;
+ };
+
+ $scope.$on("editors.apps.appChanged", function($event, $args) {
+ // if app changed to this app, then we want to scroll to the current anchor
+ if($args.app.alias === "umbContent") {
+ var activeAnchor = getActiveAnchor();
+ $timeout(jumpTo.bind(null, [activeAnchor.id]));
+ }
+ });
+
+ $scope.$on("editors.apps.appAnchorChanged", function($event, $args) {
+ if($args.app.alias === "umbContent") {
+ setActiveAnchor($args.anchor);
+ scrollTo($args.anchor.id);
+ }
+ });
+
+ //ensure to unregister from all dom-events
+ $scope.$on('$destroy', function () {
+ cancelScrollTween();
+ scrollableNode.removeEventListener("scroll", onScroll);
+ scrollableNode.removeEventListener("mousewheel", cancelScrollTween);
+ });
+
+ }
+
+ function controller($scope, $element, $attrs) {
+
+
+ //expose the property/methods for other directives to use
+ this.content = $scope.content;
+ this.activeVariant = _.find(this.content.variants, variant => {
+ return variant.active;
+ });
+
+ $scope.activeVariant = this.activeVariant;
+
+ $scope.defaultVariant = _.find(this.content.variants, variant => {
+ return variant.language.isDefault;
+ });
+
+ $scope.unlockInvariantValue = function(property) {
+ property.unlockInvariantValue = !property.unlockInvariantValue;
+ };
+
+ $scope.$watch("tabbedContentForm.$dirty",
+ function (newValue, oldValue) {
+ if (newValue === true) {
+ $scope.content.isDirty = true;
+ }
+ }
+ );
+ }
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/content/umb-tabbed-content.html',
- controller: function ($scope) {
-
- //expose the property/methods for other directives to use
- this.content = $scope.content;
- this.activeVariant = _.find(this.content.variants, variant => {
- return variant.active;
- });
-
- $scope.activeVariant = this.activeVariant;
-
- $scope.defaultVariant = _.find(this.content.variants, variant => {
- return variant.language.isDefault;
- });
-
- $scope.unlockInvariantValue = function(property) {
- property.unlockInvariantValue = !property.unlockInvariantValue;
- };
-
- $scope.$watch("tabbedContentForm.$dirty",
- function (newValue, oldValue) {
- if (newValue === true) {
- $scope.content.isDirty = true;
- }
- });
- },
- link: function(scope) {
-
- },
+ controller: controller,
+ link: link,
scope: {
content: "="
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js
index 8545854992..5556308e06 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js
@@ -16,7 +16,8 @@
onCloseSplitView: "&",
onSelectVariant: "&",
onOpenSplitView: "&",
- onSelectApp: "&"
+ onSelectApp: "&",
+ onSelectAppAnchor: "&"
},
controllerAs: 'vm',
controller: umbVariantContentController
@@ -35,6 +36,7 @@
vm.selectVariant = selectVariant;
vm.openSplitView = openSplitView;
vm.selectApp = selectApp;
+ vm.selectAppAnchor = selectAppAnchor;
function onInit() {
// disable the name field if the active content app is not "Content"
@@ -78,16 +80,31 @@
* @param {any} item
*/
function selectApp(item) {
- // disable the name field if the active content app is not "Content" or "Info"
- vm.nameDisabled = false;
- if(item && item.alias !== "umbContent" && item.alias !== "umbInfo") {
- vm.nameDisabled = true;
- }
// call the callback if any is registered
if(vm.onSelectApp) {
vm.onSelectApp({"app": item});
}
}
+
+ $scope.$on("editors.apps.appChanged", function($event, $args) {
+ var app = $args.app;
+ // disable the name field if the active content app is not "Content" or "Info"
+ vm.nameDisabled = false;
+ if(app && app.alias !== "umbContent" && app.alias !== "umbInfo") {
+ vm.nameDisabled = true;
+ }
+ });
+
+ /**
+ * Used to proxy a callback
+ * @param {any} item
+ */
+ function selectAppAnchor(item, anchor) {
+ // call the callback if any is registered
+ if(vm.onSelectAppAnchor) {
+ vm.onSelectAppAnchor({"app": item, "anchor": anchor});
+ }
+ }
/**
* Used to proxy a callback
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js
index addbb3b11b..bd21cca541 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js
@@ -10,7 +10,8 @@
page: "<",
content: "<", // TODO: Not sure if this should be = since we are changing the 'active' property of a variant
culture: "<",
- onSelectApp: "&?"
+ onSelectApp: "&?",
+ onSelectAppAnchor: "&?"
},
controllerAs: 'vm',
controller: umbVariantContentEditorsController
@@ -32,6 +33,7 @@
vm.closeSplitView = closeSplitView;
vm.selectVariant = selectVariant;
vm.selectApp = selectApp;
+ vm.selectAppAnchor = selectAppAnchor;
//Used to track how many content views there are (for split view there will be 2, it could support more in theory)
vm.editors = [];
@@ -316,13 +318,24 @@
* @param {any} app This is the model of the selected app
*/
function selectApp(app) {
- if(app && app.alias) {
- activeAppAlias = app.alias;
- }
if(vm.onSelectApp) {
vm.onSelectApp({"app": app});
}
}
+
+ function selectAppAnchor(app, anchor) {
+ if(vm.onSelectAppAnchor) {
+ vm.onSelectAppAnchor({"app": app, "anchor": anchor});
+ }
+ }
+
+
+ $scope.$on("editors.apps.appChanged", function($event, $args) {
+ var app = $args.app;
+ if(app && app.alias) {
+ activeAppAlias = app.alias;
+ }
+ });
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js
index 0d78aab0eb..ad20c6bbae 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js
@@ -11,17 +11,26 @@
if (!scope.serverValidationAliasField) {
scope.serverValidationAliasField = "Alias";
}
-
+
scope.vm = {};
scope.vm.dropdownOpen = false;
scope.vm.currentVariant = "";
function onInit() {
+
setCurrentVariant();
+
+ angular.forEach(scope.content.apps, (app) => {
+ if (app.alias === "umbContent") {
+ console.log("app: ", app)
+ app.anchors = scope.content.tabs;
+ }
+ });
+
}
function setCurrentVariant() {
- angular.forEach(scope.variants, function (variant) {
+ angular.forEach(scope.content.variants, function (variant) {
if (variant.active) {
scope.vm.currentVariant = variant;
}
@@ -46,6 +55,12 @@
}
}
+ scope.selectAnchorItem = function(item, anchor) {
+ if(scope.onSelectAnchorItem) {
+ scope.onSelectAnchorItem({"item": item, "anchor": anchor});
+ }
+ }
+
scope.closeSplitView = function () {
if (scope.onCloseSplitView) {
scope.onCloseSplitView();
@@ -72,10 +87,10 @@
onInit();
//watch for the active culture changing, if it changes, update the current variant
- if (scope.variants) {
+ if (scope.content.variants) {
scope.$watch(function () {
- for (var i = 0; i < scope.variants.length; i++) {
- var v = scope.variants[i];
+ for (var i = 0; i < scope.content.variants.length; i++) {
+ var v = scope.content.variants[i];
if (v.active) {
return v.language.culture;
}
@@ -100,11 +115,11 @@
nameDisabled: "",
menu: "=",
hideMenu: "",
- variants: "=",
+ content: "=",
openVariants: "<",
hideChangeVariant: "",
- navigation: "=",
onSelectNavigationItem: "&?",
+ onSelectAnchorItem: "&?",
showBackButton: "",
splitViewOpen: "=?",
onOpenInSplitView: "&?",
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js
index 943d2232c2..31976118fd 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js
@@ -17,14 +17,24 @@
name: "More"
};
- scope.clickNavigationItem = function (selectedItem) {
+ scope.openNavigationItem = function(item) {
+
scope.showDropdown = false;
- runItemAction(selectedItem);
- setItemToActive(selectedItem);
+ runItemAction(item);
+ setItemToActive(item);
if(scope.onSelect) {
- scope.onSelect({"item": selectedItem});
+ scope.onSelect({"item": item});
+ }
+ eventsService.emit("app.tabChange", item);
+ };
+
+ scope.openAnchorItem = function(item, anchor) {
+ if(scope.onAnchorSelect) {
+ scope.onAnchorSelect({"item": item, "anchor": anchor});
+ }
+ if (item.active !== true) {
+ scope.openNavigationItem(item);
}
- eventsService.emit("app.tabChange", selectedItem);
};
scope.toggleDropdown = function () {
@@ -128,7 +138,8 @@
templateUrl: 'views/components/editor/umb-editor-navigation.html',
scope: {
navigation: "=",
- onSelect: "&"
+ onSelect: "&",
+ onAnchorSelect: "&"
},
link: link
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js
new file mode 100644
index 0000000000..3a0dbb06d8
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js
@@ -0,0 +1,49 @@
+(function () {
+ 'use strict';
+
+ function UmbEditorNavigationItemController($scope, $element, $attrs) {
+
+ var vm = this;
+
+ vm.clicked = function() {
+ vm.onOpen({item:vm.item});
+ };
+
+ vm.anchorClicked = function(anchor, $event) {
+ vm.onOpenAnchor({item:vm.item, anchor:anchor});
+ $event.stopPropagation();
+ $event.preventDefault();
+ };
+
+ // needed to make sure that we update what anchors are active.
+ vm.mouseOver = function() {
+ $scope.$digest();
+ }
+
+ var componentNode = $element[0];
+
+ componentNode.classList.add('umb-sub-views-nav-item');
+ componentNode.addEventListener('mouseover', vm.mouseOver);
+
+ //ensure to unregister from all dom-events
+ $scope.$on('$destroy', function () {
+ componentNode.removeEventListener("mouseover", vm.mouseOver);
+ });
+
+ }
+
+ angular
+ .module('umbraco.directives.html')
+ .component('umbEditorNavigationItem', {
+ templateUrl: 'views/components/editor/umb-editor-navigation-item.html',
+ controller: UmbEditorNavigationItemController,
+ controllerAs: 'vm',
+ bindings: {
+ item: '=',
+ onOpen: '&',
+ onOpenAnchor: '&',
+ index: '@'
+ }
+ });
+
+})();
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js
index edf54ca034..40def728ed 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js
@@ -29,6 +29,8 @@
let typeahead;
let tagsHound;
+ let initLoad = true;
+
vm.$onInit = onInit;
vm.$onChanges = onChanges;
vm.$onDestroy = onDestroy;
@@ -53,7 +55,7 @@
vm.isLoading = false;
//ensure that the models are formatted correctly
- configureViewModel();
+ configureViewModel(true);
// Set the visible prompt to -1 to ensure it will not be visible
vm.promptIsVisible = "-1";
@@ -139,8 +141,7 @@
if (!changes.value.isFirstChange() && changes.value.currentValue !== changes.value.previousValue) {
configureViewModel();
- reValidate()
-
+ reValidate();
}
}
}
@@ -154,13 +155,19 @@
$element.find('.tags-' + vm.htmlId).typeahead('destroy');
}
- function configureViewModel() {
+ function configureViewModel(isInitLoad) {
if (vm.value) {
if (angular.isString(vm.value) && vm.value.length > 0) {
if (vm.config.storageType === "Json") {
//json storage
vm.viewModel = JSON.parse(vm.value);
- updateModelValue(vm.viewModel);
+
+ //if this is the first load, we are just re-formatting the underlying model to be consistent
+ //we don't want to notify the component parent of any changes, that will occur if the user actually
+ //changes a value. If we notify at this point it will signal a form dirty change which we don't want.
+ if (!isInitLoad) {
+ updateModelValue(vm.viewModel);
+ }
}
else {
//csv storage
@@ -174,8 +181,12 @@
return self.indexOf(v) === i;
});
- updateModelValue(vm.viewModel);
-
+ //if this is the first load, we are just re-formatting the underlying model to be consistent
+ //we don't want to notify the component parent of any changes, that will occur if the user actually
+ //changes a value. If we notify at this point it will signal a form dirty change which we don't want.
+ if (!isInitLoad) {
+ updateModelValue(vm.viewModel);
+ }
}
}
else if (angular.isArray(vm.value)) {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js
index 87cd84ca40..58a5e1be0e 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js
@@ -10,7 +10,7 @@
bindings: {
layouts: '<',
activeLayout: '<',
- onLayoutSelect: "&"
+ onLayoutSelect: '&'
}
});
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/getDomElement.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/getDomElement.directive.js
new file mode 100644
index 0000000000..2a0c9c3aec
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/getDomElement.directive.js
@@ -0,0 +1,17 @@
+angular.module("umbraco.directives").directive("retriveDomElement", function () {
+ var directiveDefinitionObject = {
+
+ restrict: "A",
+ selector: '[retriveDomElement]',
+ scope: {
+ "retriveDomElement": "&"
+ },
+ link: {
+ post: function(scope, iElement, iAttrs, controller) {
+ scope.retriveDomElement({element:iElement, attributes: iAttrs});
+ }
+ }
+ };
+
+ return directiveDefinitionObject;
+});
diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js
index eabb611320..03373089d7 100644
--- a/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js
+++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js
@@ -26,7 +26,9 @@
//dealing with requests:
'request': function(config) {
if(config.method === "POST"){
- transform(config.data);
+ var clone = angular.copy(config);
+ transform(clone.data);
+ return clone;
}
return config;
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
index 6991c5d386..bad5f4e342 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
@@ -161,6 +161,10 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe
}, function (response) {
+ if (!response) {
+ return; //sometimes oddly this happens, nothing we can do
+ }
+
if (!response.status && response.message && response.stack) {
//this is a JS/angular error that we should deal with
return $q.reject({
diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less
index 78e9cabcfc..db0b64daa4 100644
--- a/src/Umbraco.Web.UI.Client/src/less/belle.less
+++ b/src/Umbraco.Web.UI.Client/src/less/belle.less
@@ -91,6 +91,7 @@
@import "components/application/umb-dashboard.less";
@import "components/html/umb-expansion-panel.less";
+@import "components/html/umb-group-panel.less";
@import "components/html/umb-alert.less";
@import "components/tree/umb-tree.less";
@@ -105,6 +106,7 @@
@import "components/editor/umb-editor.less";
@import "components/umb-sub-views.less";
@import "components/umb-editor-navigation.less";
+@import "components/umb-editor-navigation-item.less";
@import "components/umb-editor-sub-views.less";
@import "components/editor/subheader/umb-editor-sub-header.less";
@import "components/umb-flatpickr.less";
diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less
index 7f67d5c3b2..d1492a33c6 100644
--- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less
+++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less
@@ -176,6 +176,7 @@ a, a:hover{
.dropdown-menu {
position: absolute;
+ display: block;
top: auto;
right: 0;
z-index: 1000;
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less
new file mode 100644
index 0000000000..0e03af213f
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less
@@ -0,0 +1,20 @@
+.umb-group-panel {
+ background: @white;
+ border-radius: 3px;
+ margin-bottom: 16px;
+ box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.16);
+}
+
+.umb-group-panel__header {
+ padding: 10px 20px;
+ font-weight: bold;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ color: @black;
+}
+
+.umb-group-panel__content {
+ padding: 20px;
+ border-top: 1px solid @gray-9;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less
index 698a4211f5..eaa26b5d1a 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less
@@ -232,43 +232,40 @@ body.touch .umb-tree {
}
}
-.protected,
-.has-unpublished-version,
-.is-container,
-.locked {
+.umb-tree-item__annotation {
&::before {
font-family: 'icomoon';
position: absolute;
- font-size: 20px;
- padding-left: 7px;
- padding-top: 7px;
bottom: 0;
}
}
-.protected::before {
- content: "\e256";
- color: @red;
-}
-
-.has-unpublished-version::before {
+.has-unpublished-version > .umb-tree-item__inner > .umb-tree-item__annotation::before {
content: "\e25a";
color: @green;
+ font-size: 20px;
+ margin-left: -25px;
}
-.is-container::before {
+.is-container > .umb-tree-item__inner > .umb-tree-item__annotation::before {
content: "\e04e";
color: @blue;
- font-size: 8px;
- padding-left: 13px;
- padding-top: 8px;
- pointer-events: none;
+ font-size: 9px;
+ margin-left: -20px;
}
+.protected > .umb-tree-item__inner > .umb-tree-item__annotation::before {
+ content: "\e256";
+ color: @red;
+ font-size: 20px;
+ margin-left: -25px;
+}
-.locked::before {
+.locked > .umb-tree-item__inner > .umb-tree-item__annotation::before {
content: "\e0a7";
color: @red;
+ font-size: 9px;
+ margin-left: -20px;
}
.no-access {
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less
new file mode 100644
index 0000000000..0372eeef4b
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less
@@ -0,0 +1,169 @@
+.umb-sub-views-nav-item {
+ position: relative;
+ display: block;
+}
+.umb-sub-views-nav-item > a {
+ text-align: center;
+ cursor: pointer;
+ display: block;
+ padding: 4px 10px 0 10px;
+ min-width: 70px;
+ border-right: 1px solid @gray-9;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: @editorHeaderHeight;
+ position: relative;
+
+ color: @ui-active-type;
+
+ &:hover {
+ color: @ui-active-type-hover !important;
+ }
+
+ &::after {
+ content: "";
+ height: 0px;
+ left: 8px;
+ right: 8px;
+ background-color: @ui-light-active-border;
+ position: absolute;
+ bottom: 0;
+ border-radius: 3px 3px 0 0;
+ opacity: 0;
+ transition: all .2s linear;
+ }
+}
+
+.umb-sub-views-nav-item > a:active {
+ .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)");
+}
+.umb-sub-views-nav-item > a:focus {
+ outline: none;
+}
+
+.umb-sub-views-nav-item > a:hover,
+.umb-sub-views-nav-item > a:focus {
+ text-decoration: none;
+}
+
+.umb-sub-views-nav-item > a.is-active {
+
+ color: @ui-light-active-type;
+
+ &::after {
+ opacity: 1;
+ height: 4px;
+ }
+}
+
+.show-validation .umb-sub-views-nav-item > a.-has-error {
+ color: @red;
+}
+
+.umb-sub-views-nav-item .icon {
+ font-size: 24px;
+ display: block;
+ text-align: center;
+ margin-bottom: 7px;
+}
+
+.umb-sub-views-nav-item .badge {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ min-width: 16px;
+ color: @white;
+ background-color: @ui-active-type;
+ border: 2px solid @white;
+ border-radius: 50%;
+ font-size: 10px;
+ font-weight: bold;
+ padding: 2px;
+ line-height: 16px;
+ display: block;
+
+ &.-type-alert {
+ background-color: @red;
+ }
+ &.-type-warning {
+ background-color: @yellow-d2;
+ }
+ &:empty {
+ height: 12px;
+ min-width: 12px;
+ }
+}
+
+.umb-sub-views-nav-item-text {
+ font-size: 12px;
+ line-height: 1em;
+}
+
+
+.umb-sub-views-nav-item__anchor_dropdown {// inherits from .dropdown-menu
+ display: block;
+ margin: 0;
+ overflow: hidden;
+
+ // center align horizontal
+ left: 50%;
+ transform: translateX(-50%);
+
+ visibility:hidden;
+ opacity: 0;
+ transition: visibility 0s 500ms, opacity 250ms 250ms;
+}
+.umb-sub-views-nav-item__anchor_dropdown li a {
+ border-left: 4px solid transparent;
+}
+.umb-sub-views-nav-item__anchor_dropdown li.is-active a {
+ border-left-color: @ui-selected-border;
+}
+
+.umb-sub-views-nav-item:hover .umb-sub-views-nav-item__anchor_dropdown {
+ visibility:visible;
+ opacity: 1;
+ transition: visibility 0s 0s, opacity 20ms 0s;
+}
+
+
+
+// --------------------------------
+// item__more, appears when there is not enough room for the visible items.
+// --------------------------------
+
+.umb-sub-views-nav-item-more__icon {
+ margin-bottom: 10px;
+}
+
+.umb-sub-views-nav-item-more__icon i {
+ height: 5px;
+ width: 5px;
+ border-radius: 50%;
+ background: @ui-active-type;// fallback if browser doesnt support currentColor
+ background: currentColor;
+ display: inline-block;
+ margin: 0 5px 0 0;
+}
+
+.umb-sub-views-nav-item-more__icon i:last-of-type {
+ margin-right: 0;
+}
+
+.umb-sub-views-nav-item-more__dropdown {
+ left: auto;
+ right: 0;
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ min-width: auto;
+ margin-top: 10px;
+}
+.umb-sub-views-nav-item-more__dropdown > li {
+ display: flex;
+}
+.umb-sub-views-nav-item-more__dropdown .umb-sub-views-nav-item:first {
+ border-left: none;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less
index c1f099c2a5..985765e53a 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less
@@ -4,135 +4,3 @@
margin: 0;
border-left: 1px solid @gray-9;
}
-
-.umb-sub-views-nav-item {
- text-align: center;
- cursor: pointer;
- display: block;
- padding: 4px 10px 0 10px;
- //border-bottom: 4px solid transparent;
- min-width: 70px;
- border-right: 1px solid @gray-9;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: @editorHeaderHeight;
- position: relative;
-
- color: @ui-active-type;
-
- &:hover {
- color: @ui-active-type-hover !important;
- }
-
- &::after {
- content: "";
- height: 0px;
- left: 8px;
- right: 8px;
- background-color: @ui-light-active-border;
- position: absolute;
- bottom: 0;
- border-radius: 3px 3px 0 0;
- opacity: 0;
- transition: all .2s linear;
- }
-}
-
-.umb-sub-views-nav-item:focus {
- outline: none;
-}
-
-.umb-sub-views-nav-item:hover,
-.umb-sub-views-nav-item:focus {
- text-decoration: none;
-}
-
-.umb-sub-views-nav-item.is-active {
- //color: @ui-active;
- //border-bottom-color: @ui-active;
-
- //background-color: rgba(@ui-active, 0.25);
- color: @ui-light-active-type;
- //border-bottom-color: @ui-active;
- &::after {
- opacity: 1;
- height: 4px;
- }
-}
-
-.show-validation .umb-sub-views-nav-item.-has-error {
- color: @red;
-}
-
-.umb-sub-views-nav-item .icon {
- font-size: 24px;
- display: block;
- text-align: center;
- margin-bottom: 7px;
-}
-
-.umb-sub-views-nav-item .badge {
- position: absolute;
- top: 6px;
- right: 6px;
- min-width: 16px;
- color: @white;
- background-color: @ui-active-type;
- border: 2px solid @white;
- border-radius: 50%;
- font-size: 10px;
- font-weight: bold;
- padding: 2px;
- line-height: 16px;
- display: block;
-
- &.-type-alert {
- background-color: @red;
- }
- &.-type-warning {
- background-color: @yellow-d2;
- }
- &:empty {
- height: 12px;
- min-width: 12px;
- }
-}
-
-.umb-sub-views-nav-item-text {
- font-size: 12px;
- line-height: 1em;
-}
-
-.umb-sub-views-nav-item__more {
- margin-bottom: 10px;
-}
-
-.umb-sub-views-nav-item__more i {
- height: 5px;
- width: 5px;
- border-radius: 50%;
- background: @gray-3;
- display: inline-block;
- margin: 0 5px 0 0;
-}
-
-.umb-sub-views-nav-item__more i:last-of-type {
- margin-right: 0;
-}
-
-// make dots green the an item is active
-.umb-sub-views-nav-item.is-active .umb-sub-views-nav-item__more i {
- background-color: @ui-active;
-}
-
-.umb-sub-views-nav__dropdown.umb-sub-views-nav__dropdown {
- left: auto;
- right: 0;
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- min-width: auto;
- margin-top: 10px;
-}
diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less
index 0101113670..a2710fab6c 100644
--- a/src/Umbraco.Web.UI.Client/src/less/navs.less
+++ b/src/Umbraco.Web.UI.Client/src/less/navs.less
@@ -220,6 +220,7 @@
// DROPDOWNS
// ---------
.dropdown-menu {
+ display: block;
border-radius: @dropdownBorderRadius;
box-shadow: 0 5px 20px rgba(0,0,0,.3);
padding-top: 0;
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html
index e97eabf17f..dda9334e05 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html
@@ -10,7 +10,8 @@
page="page"
content="content"
culture="culture"
- on-select-app="appChanged(app)">
+ on-select-app="appChanged(app)"
+ on-select-app-anchor="appAnchorChanged(app, anchor)">
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html
index cdacea7cf1..b76c74149d 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html
@@ -1,16 +1,15 @@