Merge remote-tracking branch 'origin/temp8-4477-radiobutton-must-store-text-value' into temp8-4477-radiobutton-must-store-text-value

# Conflicts:
#	src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
#	src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonAndCheckboxPropertyEditorsMigration.cs
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js
This commit is contained in:
Bjarke Berg
2019-02-12 08:17:38 +01:00
67 changed files with 852 additions and 464 deletions

View File

@@ -27,7 +27,7 @@
<dependency id="ClientDependency" version="[1.9.7,1.999999)" />
<dependency id="ClientDependency-Mvc5" version="[1.8.0,1.999999)" />
<dependency id="CSharpTest.Net.Collections" version="[14.906.1403.1082,14.999999)" />
<dependency id="Examine" version="[1.0.0-beta078,1.999999)" />
<dependency id="Examine" version="[1.0.0-beta079,1.999999)" />
<dependency id="HtmlAgilityPack" version="[1.8.14,1.999999)" />
<dependency id="ImageProcessor" version="[2.6.2.25,2.999999)" />
<dependency id="LightInject.Mvc" version="[2.0.0,2.999999)" />

View File

@@ -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

View File

@@ -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();

View File

@@ -127,9 +127,8 @@ namespace Umbraco.Core.Migrations.Upgrade
To<ConvertRelatedLinksToMultiUrlPicker>("{ED28B66A-E248-4D94-8CDB-9BDF574023F0}");
To<UpdatePickerIntegerValuesToUdi>("{38C809D5-6C34-426B-9BEA-EFD39162595C}");
To<RenameUmbracoDomainsTable>("{6017F044-8E70-4E10-B2A3-336949692ADD}");
To<RadioButtonPropertyEditorsMigration>("{940FD19A-00A8-4D5C-B8FF-939143585726}");
To<CheckBoxListPropertyEditorsMigration>("{C62C9BF1-833E-4866-B959-C8AB59E43E51}");
To<AddUserLoginDtoDateIndex>("98339BEF-E4B2-48A8-B9D1-D173DC842BBE");
To<RadioButtonAndCheckboxPropertyEditorsMigration>("{940FD19A-00A8-4D5C-B8FF-939143585726}");
//FINAL

View File

@@ -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();
}
}
}

View File

@@ -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<DataTypeDto>(Sql()
.Select<DataTypeDto>()
.From<DataTypeDto>()
.Where<DataTypeDto>(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<DropDownPropertyEditorsMigration>(
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<PropertyDataDto>(Sql()
.Select<PropertyDataDto>()
.From<PropertyDataDto>()
.InnerJoin<PropertyTypeDto>()
.On<PropertyTypeDto, PropertyDataDto>((pt, pd) => pt.Id == pd.PropertyTypeId)
.InnerJoin<DataTypeDto>()
.On<DataTypeDto, PropertyTypeDto>((dt, pt) => dt.NodeId == pt.DataTypeId)
.Where<PropertyTypeDto>(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<DropDownPropertyEditorsMigration>(
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<PropertyDataDto>(Sql()
.Select<PropertyDataDto>()
.From<PropertyDataDto>()
.InnerJoin<PropertyTypeDto>()
.On<PropertyTypeDto, PropertyDataDto>((pt, pd) => pt.Id == pd.PropertyTypeId)
.InnerJoin<DataTypeDto>()
.On<DataTypeDto, PropertyTypeDto>((dt, pt) => dt.NodeId == pt.DataTypeId)
.Where<PropertyTypeDto>(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<DataTypeDto> GetDataTypes(string editorAlias)
{
//need to convert the old drop down data types to use the new one
var dataTypes = Database.Fetch<DataTypeDto>(Sql()
.Select<DataTypeDto>()
.From<DataTypeDto>()
.Where<DataTypeDto>(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)

View File

@@ -14,14 +14,5 @@
/// Gets or sets the path of the entity.
/// </summary>
public string Path { get; set; }
/// <summary>
/// Proxy of the Id
/// </summary>
public int NodeId
{
get => Id;
set => Id = value;
}
}
}

View File

@@ -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<int?>("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<IContentType> ImportDocumentType(XElement docTypeElement, int userId)
{
return ImportDocumentTypes(new []{ docTypeElement }, userId);
return ImportDocumentTypes(new[] { docTypeElement }, userId);
}
/// <summary>
@@ -359,7 +379,7 @@ namespace Umbraco.Core.Packaging
public IEnumerable<IContentType> ImportDocumentTypes(IReadOnlyCollection<XElement> unsortedDocumentTypes, bool importStructure, int userId)
{
var importedContentTypes = new Dictionary<string, IContentType>();
//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<string, TopoGraph.Node<string, XElement>>(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<IDictionaryItem>();
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<ITemplate> ImportTemplate(XElement templateElement, int userId)
{
return ImportTemplates(new[] {templateElement}, userId);
return ImportTemplates(new[] { templateElement }, userId);
}
/// <summary>
@@ -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;

View File

@@ -30,11 +30,14 @@ namespace Umbraco.Core.Persistence.Dtos
/// Updated every time a user's session is validated
/// </summary>
/// <remarks>
/// 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
/// <para>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.</para>
/// <para>The index is IMPORTANT as it prevents deadlocks during deletion of
/// old sessions (DELETE ... WHERE lastValidatedUtc &lt; date).</para>
/// </remarks>
[Column("lastValidatedUtc")]
[NullSetting(NullSetting = NullSettings.NotNull)]
[Index(IndexTypes.NonClustered, Name = "IX_userLoginDto_lastValidatedUtc")]
public DateTime LastValidatedUtc { get; set; }
/// <summary>

View File

@@ -69,7 +69,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var page = Database.Page<BaseDto>(pageIndex + 1, pageSize, sql);
var dtos = page.Items;
var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray();
if (isContent)
BuildVariants(entities.Cast<DocumentEntitySlim>());
@@ -198,7 +198,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private IEnumerable<TreeEntityPath> PerformGetAllPaths(Guid objectType, Action<Sql<ISqlContext>> filter = null)
{
var sql = Sql().Select<NodeDto>(x => x.NodeId, x => x.Path).From<NodeDto>().Where<NodeDto>(x => x.NodeObjectType == objectType);
// NodeId is named Id on TreeEntityPath = use an alias
var sql = Sql().Select<NodeDto>(x => Alias(x.NodeId, nameof(TreeEntityPath.Id)), x => x.Path).From<NodeDto>().Where<NodeDto>(x => x.NodeObjectType == objectType);
filter?.Invoke(sql);
return Database.Fetch<TreeEntityPath>(sql);
}
@@ -405,7 +406,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
.InnerJoin<DataTypeDto>().On<PropertyTypeDto, DataTypeDto>((left, right) => left.DataTypeId == right.NodeId)
.WhereIn<PropertyDataDto>(x => x.VersionId, versionIds)
.OrderBy<PropertyDataDto>(x => x.VersionId);
}
}
// gets the base SELECT + FROM [+ filter] sql
// always from the 'current' content version

View File

@@ -100,9 +100,7 @@ namespace Umbraco.Core.Services
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records query would return without paging</param>
/// <param name="orderBy">Field to order by</param>
/// <param name="orderDirection">Direction to order by</param>
/// <param name="orderBySystemField">Flag to indicate when ordering by system field</param>
/// <param name="ordering"></param>
/// <param name="filter"></param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
IEnumerable<IMedia> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,

View File

@@ -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.
/// </summary>
/// <remarks>
/// <remarks>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)
///

View File

@@ -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;

View File

@@ -52,9 +52,6 @@
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Umbraco.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\Umbraco.Tests\bin\Debug\Umbraco.Web.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<!-- note: NuGet deals with transitive references now -->
@@ -371,6 +368,7 @@
<Compile Include="Migrations\Upgrade\V_7_9_0\AddUmbracoConsentTable.cs" />
<Compile Include="Migrations\Upgrade\V_7_9_0\CreateSensitiveDataUserGroup.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\CheckBoxListPropertyEditorsMigration.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\AddUserLoginDtoDateIndex.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\ConvertRelatedLinksToMultiUrlPicker.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\AddContentTypeIsElementColumn.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\AddLogTableColumns.cs" />
@@ -389,7 +387,7 @@
<Compile Include="Migrations\Upgrade\V_8_0_0\PropertyEditorsMigration.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\RefactorMacroColumns.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\RefactorVariantsModel.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\RadioButtonPropertyEditorsMigration.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\RadioButtonAndCheckboxPropertyEditorsMigration.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\RenameUmbracoDomainsTable.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\SuperZero.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\TablesForScheduledPublishing.cs" />

View File

@@ -48,7 +48,7 @@
</ItemGroup>
<ItemGroup>
<!-- note: NuGet deals with transitive references now -->
<PackageReference Include="Examine" Version="1.0.0-beta078" />
<PackageReference Include="Examine" Version="1.0.0-beta079" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="NPoco" Version="3.9.4" />
</ItemGroup>

View File

@@ -80,7 +80,7 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="8.0.0" />
<PackageReference Include="Castle.Core" Version="4.3.1" />
<PackageReference Include="Examine" Version="1.0.0-beta078" />
<PackageReference Include="Examine" Version="1.0.0-beta079" />
<PackageReference Include="HtmlAgilityPack">
<Version>1.8.14</Version>
</PackageReference>

View File

@@ -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);
});

View File

@@ -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) {

View File

@@ -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: "="
}

View File

@@ -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

View File

@@ -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;
}
});
}

View File

@@ -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: "&?",

View File

@@ -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
};

View File

@@ -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: '@'
}
});
})();

View File

@@ -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)) {

View File

@@ -10,7 +10,7 @@
bindings: {
layouts: '<',
activeLayout: '<',
onLayoutSelect: "&"
onLayoutSelect: '&'
}
});

View File

@@ -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;
});

View File

@@ -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;

View File

@@ -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({

View File

@@ -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";

View File

@@ -176,6 +176,7 @@ a, a:hover{
.dropdown-menu {
position: absolute;
display: block;
top: auto;
right: 0;
z-index: 1000;

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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)">
</umb-variant-content-editors>
<umb-editor-footer>

View File

@@ -1,16 +1,15 @@
<div>
<ng-form name="tabbedContentForm">
<div class="umb-expansion-panel" data-element="group-{{group.alias}}" ng-repeat="group in content.tabs track by group.label">
<div class="umb-group-panel" retrive-dom-element="registerPropertyGroup(element[0], attributes.appAnchor)" data-app-anchor="{{group.id}}" data-element="group-{{group.alias}}" ng-repeat="group in content.tabs track by group.label">
<div class="umb-expansion-panel__header" ng-click="group.open = !group.open">
<div class="umb-group-panel__header">
<div>{{ group.label }}</div>
<ins class="umb-expansion-panel__expand" ng-class="{'icon-navigation-down': !group.open, 'icon-navigation-up': group.open}">&nbsp;</ins>
</div>
<div class="umb-expansion-panel__content" ng-show="group.open">
<umb-property
data-element="property-{{property.alias}}"
ng-repeat="property in group.properties track by property.alias"
<div class="umb-group-panel__content">
<umb-property
data-element="property-{{property.alias}}"
ng-repeat="property in group.properties track by property.alias"
property="property"
show-inherit="content.variants.length > 1 && !property.culture && !activeVariant.language.isDefault"
inherits-from="defaultVariant.language.name">

View File

@@ -13,7 +13,8 @@
on-open-split-view="vm.openSplitView(variant)"
on-close-split-view="vm.closeSplitView($index)"
on-select-variant="vm.selectVariant(variant, $index)"
on-select-app="vm.selectApp(app)">
on-select-app="vm.selectApp(app)"
on-select-app-anchor="vm.selectAppAnchor(app, anchor)">
</umb-variant-content>
</div>

View File

@@ -1,19 +1,19 @@
<div>
<umb-load-indicator
<umb-load-indicator
ng-if="vm.editor.loading">
</umb-load-indicator>
<div class="umb-split-view__content" ng-show="!vm.editor.loading">
<ng-form name="contentHeaderForm">
<umb-editor-content-header
<umb-editor-content-header
menu="vm.page.menu"
hide-menu="vm.page.hideActionsMenu"
name="vm.editor.content.name"
name-disabled="vm.nameDisabled"
navigation="vm.editor.content.apps"
content="vm.editor.content"
on-select-navigation-item="vm.selectApp(item)"
variants="vm.editor.content.variants"
on-select-anchor-item="vm.selectAppAnchor(item, anchor)"
open-variants="vm.openVariants"
hide-change-variant="vm.page.hideChangeVariant"
show-back-button="vm.page.listViewPath !== null"

View File

@@ -29,7 +29,7 @@
autocomplete="off" />
</ng-form>
<a ng-if="variants.length > 0 && hideChangeVariant !== true" class="umb-variant-switcher__toggle" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen">
<a ng-if="content.variants.length > 0 && hideChangeVariant !== true" class="umb-variant-switcher__toggle" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen">
<span>{{vm.currentVariant.language.name}}</span>
<ins class="umb-variant-switcher__expand" ng-class="{'icon-navigation-down': !vm.dropdownOpen, 'icon-navigation-up': vm.dropdownOpen}">&nbsp;</ins>
</a>
@@ -39,7 +39,7 @@
</span>
<umb-dropdown ng-if="vm.dropdownOpen" style="width: 100%; max-height: 250px; overflow-y: scroll; margin-top: 5px;" on-close="vm.dropdownOpen = false" umb-keyboard-list>
<umb-dropdown-item class="umb-variant-switcher__item" ng-class="{'umb-variant-switcher_item--current': variant.active, 'umb-variant-switcher_item--not-allowed': variantIsOpen(variant.language.culture)}" ng-repeat="variant in variants">
<umb-dropdown-item class="umb-variant-switcher__item" ng-class="{'umb-variant-switcher_item--current': variant.active, 'umb-variant-switcher_item--not-allowed': variantIsOpen(variant.language.culture)}" ng-repeat="variant in content.variants">
<a href="" class="umb-variant-switcher__name-wrapper" ng-click="selectVariant($event, variant)" prevent-default>
<span class="umb-variant-switcher__name" ng-class="{'bold': variant.language.isDefault}">{{variant.language.name}}</span>
<umb-variant-state variant="variant" class="umb-variant-switcher__state"></umb-variant-state>
@@ -60,11 +60,12 @@
</a>
</div>
<div ng-if="navigation && splitViewOpen !== true" style="margin-left: 20px;">
<div ng-if="content.apps && splitViewOpen !== true" style="margin-left: 20px;">
<umb-editor-navigation
data-element="editor-sub-views"
navigation="navigation"
on-select="selectNavigationItem(item)">
navigation="content.apps"
on-select="selectNavigationItem(item)"
on-anchor-select="selectAnchorItem(item, anchor)">
</umb-editor-navigation>
</div>

View File

@@ -0,0 +1,19 @@
<a data-element="sub-view-{{vm.item.alias}}"
tabindex="-1"
ng-href=""
ng-click="vm.clicked()"
hotkey="{{vm.index+1}}"
hotkey-when-hidden="true"
ng-class="{'is-active': vm.item.active, '-has-error': vm.item.hasError}">
<i class="icon {{ vm.item.icon }}"></i>
<span class="umb-sub-views-nav-item-text">{{ vm.item.name }}</span>
<div ng-show="item.badge" class="badge -type-{{vm.item.badge.type}}">{{vm.item.badge.count}}</div>
</a>
<ul class="dropdown-menu umb-sub-views-nav-item__anchor_dropdown" ng-class="{'show': vm.showDropdown}">
<li ng-repeat="anchor in vm.item.anchors" ng-class="{'is-active': vm.item.active && anchor.active}">
<a href="" ng-click="vm.anchorClicked(anchor, $event)">
{{anchor.label}}
</a>
</li>
</ul>

View File

@@ -1,46 +1,41 @@
<ul class="umb-sub-views-nav" ng-show="showNavigation">
<li ng-repeat="item in navigation | limitTo: itemsLimit ">
<div ng-show="item.alias !== 'more'"
ng-class="item.errorClass">
<a data-element="sub-view-{{item.alias}}"
tabindex="-1"
class="umb-sub-views-nav-item js-umb-sub-views-nav-item"
href=""
ng-click="clickNavigationItem(item, $index)"
hotkey="{{$index+1}}"
ng-class="{'is-active': item.active, '-has-error': item.hasError}">
<i class="icon {{ item.icon }}"></i>
<span class="umb-sub-views-nav-item-text">{{ item.name }}</span>
<div ng-show="item.badge" class="badge -type-{{item.badge.type}}">{{item.badge.count}}</div>
</a>
<li ng-repeat="navItem in navigation | limitTo: itemsLimit ">
<div ng-show="navItem.alias !== 'more'"
ng-class="navItem.errorClass">
<umb-editor-navigation-item
item="navItem"
on-open="openNavigationItem(item)"
on-open-anchor="openAnchorItem(item, anchor)"
index="{{$index}}">
</umb-editor-navigation-item>
</div>
</li>
<li ng-show="showMoreButton" style="position: relative;">
<a data-element="sub-view-{{moreButton.alias}}"
class="umb-sub-views-nav-item"
href=""
ng-click="toggleDropdown()"
ng-class="{'is-active': moreButton.active}">
<div class="umb-sub-views-nav-item__more"><i></i><i></i><i></i></div>
<span class="umb-sub-views-nav-item-text">{{ moreButton.name }}</span>
</a>
<div class="umb-sub-views-nav-item umb-sub-views-nav-item-more">
<umb-dropdown ng-show="showDropdown" on-close="hideDropdown()" class="umb-sub-views-nav__dropdown">
<umb-dropdown-item ng-repeat="item in navigation | limitTo: overflowingItems">
<a data-element="sub-view-{{item.alias}}"
style="display: flex; border: none;"
class="umb-sub-views-nav-item"
ng-href=""
ng-click="clickNavigationItem(item, $index)"
ng-class="{'is-active': item.active, '-has-error': item.hasError}">
<i class="icon {{ item.icon }}"></i>
<span class="umb-sub-views-nav-item-text">{{ item.name }}</span>
</a>
</umb-dropdown-item>
</umb-dropdown>
<a data-element="sub-view-{{moreButton.alias}}"
href=""
ng-click="toggleDropdown()"
ng-class="{'is-active': moreButton.active}">
<div class="umb-sub-views-nav-item-more__icon"><i></i><i></i><i></i></div>
<span class="umb-sub-views-nav-item-text">{{ moreButton.name }}</span>
</a>
<umb-dropdown ng-show="showDropdown" on-close="hideDropdown()" class="umb-sub-views-nav-item-more__dropdown">
<umb-dropdown-item ng-repeat="navItem in navigation | limitTo: overflowingItems">
<umb-editor-navigation-item
item="navItem"
on-open="openNavigationItem(item)"
on-open-anchor="openAnchorItem(item, anchor)"
index="{{$index}}">
</umb-editor-navigation-item>
</umb-dropdown-item>
</umb-dropdown>
</div>
</li>

View File

@@ -1,11 +1,12 @@
<li class="umb-tree-item" data-element="tree-item-{{::node.dataElement}}" ng-class="getNodeCssClass(node)" on-right-click="altSelect(node, $event)">
<div class="umb-tree-item__inner" ng-swipe-right="options(node, $event)" ng-dblclick="load(node)" >
<ins data-element="tree-item-expand"
ng-class="{'icon-navigation-right': !node.expanded || node.metaData.isContainer, 'icon-navigation-down': node.expanded && !node.metaData.isContainer}"
ng-style="{'visibility': (scope.enablelistviewexpand === 'true' || node.hasChildren && (!node.metaData.isContainer || isDialog)) ? 'visible' : 'hidden'}"
ng-click="load(node)">&nbsp;</ins>
ng-class="{'icon-navigation-right': !node.expanded || node.metaData.isContainer, 'icon-navigation-down': node.expanded && !node.metaData.isContainer}"
ng-style="{'visibility': (scope.enablelistviewexpand === 'true' || node.hasChildren && (!node.metaData.isContainer || isDialog)) ? 'visible' : 'hidden'}"
ng-click="load(node)">&nbsp;</ins>
<i class="icon umb-tree-icon sprTree" ng-class="::node.cssClass" title="{{::node.routePath}}" ng-click="select(node, $event)" ng-style="::node.style"></i>
<span class="umb-tree-item__annotation"></span>
<a class="umb-tree-item__label" ng-href="#/{{::node.routePath}}" ng-click="select(node, $event)">{{node.name}}</a>
<!-- NOTE: These are the 'option' elipses -->

View File

@@ -1 +1 @@
<ul class="dropdown-menu db" on-outside-click="close()" ng-transclude></ul>
<ul class="dropdown-menu" on-outside-click="close()" ng-transclude></ul>

View File

@@ -8,7 +8,7 @@
class="umb-layout-selector__dropdown shadow-depth-3 animated -half-second fadeIn"
on-outside-click="vm.closeLayoutDropdown()">
<div ng-repeat="layout in vm.layouts | filter:{selected:true} track by layout.name"
<div ng-repeat="layout in vm.layouts | filter:{selected:true} track by $id(layout)"
class="umb-layout-selector__dropdown-item"
ng-click="vm.pickLayout(layout)"
ng-class="{'-active': layout.active }"

View File

@@ -194,7 +194,7 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource,
contentEditingHelper.handleSaveError({
err: err,
redirectOnError: !infiniteMode,
redirectOnFailure: !infiniteMode,
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
});

View File

@@ -42,7 +42,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListContro
return f.checked;
}),
function(m) {
return m.key;
return m.value;
});
//get all of the same values between the arrays
var same = _.intersection($scope.model.value, selectedVals);
@@ -66,13 +66,13 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListContro
function changed(item) {
var index = _.findIndex($scope.model.value,
function (v) {
return v === item.key;
return v === item.value;
});
if (item.checked) {
//if it doesn't exist in the model, then add it
if (index < 0) {
$scope.model.value.push(item.val);
$scope.model.value.push(item.value);
}
}
else {

View File

@@ -4,7 +4,7 @@
<li ng-repeat="item in selectedItems track by item.key">
<label class="checkbox">
<input type="checkbox" name="checkboxlist"
value="{{item.key}}"
value="{{item.value}}"
ng-model="item.checked"
ng-change="changed(item)"
ng-required="model.validation.mandatory && !model.value.length" />

View File

@@ -105,6 +105,10 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
};
if ($scope.model.config) {
//special case, if the `startNode` is falsy on the server config delete it entirely so the default value is merged in
if (!$scope.model.config.startNode) {
delete $scope.model.config.startNode;
}
//merge the server config on top of the default config, then set the server config to use the result
$scope.model.config = angular.extend(defaultConfig, $scope.model.config);
}

View File

@@ -59,13 +59,10 @@
}
];
vm.activeLayout = {
"icon": "icon-thumbnails-small",
"path": "1",
"selected": true
};
// Set card layout to active by default
vm.activeLayout = vm.layouts[0];
//don't show the invite button if no email is configured
// Don't show the invite button if no email is configured
if (Umbraco.Sys.ServerVariables.umbracoSettings.showUserInvite) {
vm.defaultButton = {
labelKey: "user_inviteUser",

View File

@@ -23,21 +23,20 @@
ng-if="vm.layouts"
layouts="vm.layouts"
active-layout="vm.activeLayout"
on-layout-select="vm.selectLayout">
on-layout-select="vm.selectLayout(layout)">
</umb-layout-selector>
</umb-editor-sub-header-section>
<umb-editor-sub-header-section>
<div class="form-search -no-margin-bottom pull-right">
<div class="inner-addon left-addon">
<i class="icon icon-search"></i>
<input
class="form-control search-input"
type="text" localize="placeholder"
placeholder="@general_typeToSearch"
ng-model="vm.usersOptions.filter"
ng-change="vm.searchUsers()"
prevent-enter-submit
no-dirty-check>
<input class="form-control search-input"
type="text" localize="placeholder"
placeholder="@general_typeToSearch"
ng-model="vm.usersOptions.filter"
ng-change="vm.searchUsers()"
prevent-enter-submit
no-dirty-check>
</div>
</div>
</umb-editor-sub-header-section>
@@ -229,7 +228,7 @@
<!-- Layout: Table -->
<div ng-if="vm.activeLayout.path === '2'">
<table class="table table-hover">
<table class="table table-hover">
<thead>
<tr>
<th style="padding-left: 10px; width: 10px;">

View File

@@ -87,7 +87,7 @@
<PackageReference Include="CSharpTest.Net.Collections" Version="14.906.1403.1082" />
<PackageReference Include="ClientDependency" Version="1.9.7" />
<PackageReference Include="ClientDependency-Mvc5" Version="1.8.0.0" />
<PackageReference Include="Examine" Version="1.0.0-beta078" />
<PackageReference Include="Examine" Version="1.0.0-beta079" />
<PackageReference Include="ImageProcessor.Web" Version="4.9.3.25" />
<PackageReference Include="ImageProcessor.Web.Config" Version="2.4.1.19" />
<PackageReference Include="Microsoft.AspNet.Identity.Owin" Version="2.2.2" />

View File

@@ -45,7 +45,7 @@ namespace Umbraco.Web.Editors.Filters
{
//now do each validation step
if (contentItemValidator.ValidateExistingContent(model, actionContext))
if (!contentItemValidator.ValidateProperties(model, model, actionContext))
if (contentItemValidator.ValidateProperties(model, model, actionContext))
contentItemValidator.ValidatePropertyData(model, model, model.PropertyCollectionDto, actionContext.ModelState);
}
}

View File

@@ -124,7 +124,7 @@ namespace Umbraco.Web.Editors
// When rendering the macro in the backoffice the default setting would be to use the Culture of the logged in user.
// Since a Macro might contain thing thats related to the culture of the "IPublishedContent" (ie Dictionary keys) we want
// to set the current culture to the culture related to the content item. This is hacky but it works.
var culture = publishedContent.GetCulture();
_variationContextAccessor.VariationContext = new VariationContext(); //must have an active variation context!
if (culture != null)
@@ -156,7 +156,8 @@ namespace Umbraco.Web.Editors
{
Alias = macroName.ToSafeAlias(),
Name = macroName,
MacroSource = model.VirtualPath.EnsureStartsWith("~")
MacroSource = model.VirtualPath.EnsureStartsWith("~"),
MacroType = MacroTypes.PartialView
};
_macroService.Save(macro); // may throw

View File

@@ -45,9 +45,15 @@ namespace Umbraco.Web.Models.Mapping
}
parameter.View = paramEditor.GetValueEditor().View;
var paramConfig = paramEditor.GetConfigurationEditor().ToValueEditor(paramEditor.DefaultConfiguration);
//set the config
parameter.Configuration = paramConfig;
// sets the parameter configuration to be the default configuration editor's configuration,
// ie configurationEditor.DefaultConfigurationObject, prepared for the value editor, ie
// after ToValueEditor - important to use DefaultConfigurationObject here, because depending
// on editors, ToValueEditor expects the actual strongly typed configuration - not the
// dictionary thing returned by DefaultConfiguration
var configurationEditor = paramEditor.GetConfigurationEditor();
parameter.Configuration = configurationEditor.ToValueEditor(configurationEditor.DefaultConfigurationObject);
});
}
}

View File

@@ -8,11 +8,6 @@ namespace Umbraco.Web.PropertyEditors
/// <summary>
/// A property editor to allow multiple checkbox selection of pre-defined items.
/// </summary>
/// <remarks>
/// Due to remaining backwards compatible, this stores the id of the checkbox items in the database
/// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the string value is published
/// in cache and not the int ID.
/// </remarks>
[DataEditor(Constants.PropertyEditors.Aliases.CheckBoxList, "Checkbox list", "checkboxlist", Icon="icon-bulleted-list", Group="lists")]
public class CheckBoxListPropertyEditor : DataEditor
{
@@ -31,6 +26,6 @@ namespace Umbraco.Web.PropertyEditors
protected override IConfigurationEditor CreateConfigurationEditor() => new ValueListConfigurationEditor(_textService);
/// <inheritdoc />
protected override IDataValueEditor CreateValueEditor() => new PublishValuesMultipleValueEditor(Logger, Attribute);
protected override IDataValueEditor CreateValueEditor() => new MultipleValueEditor(Logger, Attribute);
}
}

View File

@@ -8,24 +8,23 @@ namespace Umbraco.Web.PropertyEditors
public ContentPickerConfigurationEditor()
{
// configure fields
// this is not part of ContentPickerConfiguration,
// but is required to configure the UI editor (when editing the configuration)
Field(nameof(ContentPickerConfiguration.StartNodeId))
.Config = new Dictionary<string, object> { { "idType", "udi" } };
}
public override IDictionary<string, object> DefaultConfiguration => new Dictionary<string, object>
{
["idType"] = "udi",
["showEditButton"] = false,
["showPathOnHover"] = false,
};
public override IDictionary<string, object> ToValueEditor(object configuration)
{
// these are not configuration fields, but constants required by the value editor
// get the configuration fields
var d = base.ToValueEditor(configuration);
// add extra fields
// not part of ContentPickerConfiguration but used to configure the UI editor
d["showEditButton"] = false;
d["showPathOnHover"] = false;
d["idType"] = "udi";
d["idType"] = "udi";
return d;
}
}

View File

@@ -8,11 +8,6 @@ namespace Umbraco.Web.PropertyEditors
/// </summary>
public class DateConfigurationEditor : ConfigurationEditor<DateConfiguration>
{
public override IDictionary<string, object> DefaultConfiguration => new Dictionary<string, object>
{
["pickTime"] = false,
};
public override IDictionary<string, object> ToValueEditor(object configuration)
{
var d = base.ToValueEditor(configuration);

View File

@@ -8,11 +8,6 @@ namespace Umbraco.Web.PropertyEditors
/// </summary>
public class DateTimeConfigurationEditor : ConfigurationEditor<DateTimeConfiguration>
{
public override IDictionary<string, object> DefaultConfiguration => new Dictionary<string, object>
{
["pickTime"] = true,
};
public override IDictionary<string, object> ToValueEditor(object configuration)
{
var d = base.ToValueEditor(configuration);

View File

@@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors
protected override IDataValueEditor CreateValueEditor()
{
return new PublishValuesMultipleValueEditor(Logger, Attribute);
return new MultipleValueEditor(Logger, Attribute);
}
protected override IConfigurationEditor CreateConfigurationEditor() => new DropDownFlexibleConfigurationEditor(_textService);

View File

@@ -13,22 +13,22 @@ namespace Umbraco.Web.PropertyEditors
/// </summary>
public MediaPickerConfigurationEditor()
{
// configure fields
// this is not part of ContentPickerConfiguration,
// but is required to configure the UI editor (when editing the configuration)
Field(nameof(MediaPickerConfiguration.StartNodeId))
.Config = new Dictionary<string, object>
{
{ "idType", "udi" }
};
.Config = new Dictionary<string, object> { { "idType", "udi" } };
}
public override IDictionary<string, object> DefaultConfiguration => new Dictionary<string, object>
{
["idType"] = "udi"
};
public override IDictionary<string, object> ToValueEditor(object configuration)
{
// get the configuration fields
var d = base.ToValueEditor(configuration);
// add extra fields
// not part of ContentPickerConfiguration but used to configure the UI editor
d["idType"] = "udi";
return d;
}
}

View File

@@ -11,10 +11,7 @@ namespace Umbraco.Web.PropertyEditors
public MultiNodePickerConfigurationEditor()
{
Field(nameof(MultiNodePickerConfiguration.TreeSource))
.Config = new Dictionary<string, object>
{
{ "idType", "udi" }
};
.Config = new Dictionary<string, object> { { "idType", "udi" } };
}
/// <inheritdoc />
@@ -23,19 +20,11 @@ namespace Umbraco.Web.PropertyEditors
// sanitize configuration
var output = base.ToConfigurationEditor(configuration);
output["multiPicker"] = configuration.MaxNumber > 1 ? true : false;
output["multiPicker"] = configuration.MaxNumber > 1;
return output;
}
public override IDictionary<string, object> DefaultConfiguration => new Dictionary<string, object>
{
["multiPicker"] = true,
["showEditButton"] = false,
["showPathOnHover"] = false,
["idType"] = "udi"
};
/// <inheritdoc />
public override IDictionary<string, object> ToValueEditor(object configuration)
{

View File

@@ -10,23 +10,23 @@ using Umbraco.Core.Services;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Custom value editor to handle posted json data and to return json data for the multiple selected items
/// A value editor to handle posted json array data and to return array data for the multiple selected csv items
/// </summary>
/// <remarks>
/// This is re-used by editors such as the multiple drop down list or check box list
/// </remarks>
internal class PublishValuesMultipleValueEditor : DataValueEditor
internal class MultipleValueEditor : DataValueEditor
{
private readonly ILogger _logger;
internal PublishValuesMultipleValueEditor(ILogger logger, DataEditorAttribute attribute)
internal MultipleValueEditor(ILogger logger, DataEditorAttribute attribute)
: base(attribute)
{
_logger = logger;
}
/// <summary>
/// Override so that we can return a json array to the editor for multi-select values
/// Override so that we can return an array to the editor for multi-select values
/// </summary>
/// <param name="property"></param>
/// <param name="dataTypeService"></param>

View File

@@ -192,10 +192,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
LoadCaches();
Guid GetUid(ContentStore store, int id) => store.LiveSnapshot.Get(id)?.Uid ?? default;
int GetId(ContentStore store, Guid uid) => store.LiveSnapshot.Get(uid)?.Id ?? default;
if (idkMap != null)
{
idkMap.SetMapper(UmbracoObjectTypes.Document, id => _contentStore.LiveSnapshot.Get(id).Uid, uid => _contentStore.LiveSnapshot.Get(uid).Id);
idkMap.SetMapper(UmbracoObjectTypes.Media, id => _mediaStore.LiveSnapshot.Get(id).Uid, uid => _mediaStore.LiveSnapshot.Get(uid).Id);
idkMap.SetMapper(UmbracoObjectTypes.Document, id => GetUid(_contentStore, id), uid => GetId(_contentStore, uid));
idkMap.SetMapper(UmbracoObjectTypes.Media, id => GetUid(_mediaStore, id), uid => GetId(_mediaStore, uid));
}
}

View File

@@ -38,16 +38,24 @@ namespace Umbraco.Web.Routing
{
get
{
var oldRoutes =
(Dictionary<ContentIdAndCulture, ContentKeyAndOldRoute>) UmbracoContext.Current.HttpContext.Items[
ContextKey3];
var oldRoutes = (Dictionary<ContentIdAndCulture, ContentKeyAndOldRoute>) UmbracoContext.Current.HttpContext.Items[ContextKey3];
if (oldRoutes == null)
UmbracoContext.Current.HttpContext.Items[ContextKey3] =
oldRoutes = new Dictionary<ContentIdAndCulture, ContentKeyAndOldRoute>();
UmbracoContext.Current.HttpContext.Items[ContextKey3] = oldRoutes = new Dictionary<ContentIdAndCulture, ContentKeyAndOldRoute>();
return oldRoutes;
}
}
private static bool HasOldRoutes
{
get
{
if (Current.UmbracoContext == null) return false;
if (Current.UmbracoContext.HttpContext == null) return false;
if (Current.UmbracoContext.HttpContext.Items[ContextKey3] == null) return false;
return true;
}
}
private static bool LockedEvents
{
get => Moving && UmbracoContext.Current.HttpContext.Items[ContextKey2] != null;
@@ -97,8 +105,8 @@ namespace Umbraco.Web.Routing
ContentService.Published += ContentService_Published;
ContentService.Moving += ContentService_Moving;
ContentService.Moved += ContentService_Moved;
ContentCacheRefresher.CacheUpdated += ContentCacheRefresher_CacheUpdated;
ContentCacheRefresher.CacheUpdated += ContentCacheRefresher_CacheUpdated;
// kill all redirects once a content is deleted
//ContentService.Deleted += ContentService_Deleted;
@@ -111,21 +119,26 @@ namespace Umbraco.Web.Routing
public void Terminate()
{ }
private static void ContentCacheRefresher_CacheUpdated(ContentCacheRefresher sender,
CacheRefresherEventArgs args)
private static void ContentCacheRefresher_CacheUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args)
{
// that event is a distributed even that triggers on all nodes
// BUT it should totally NOT run on nodes other that the one that handled the other events
// and besides, it cannot run on a background thread!
if (!HasOldRoutes)
return;
// sanity checks
if (args.MessageType != MessageType.RefreshByPayload)
{
throw new InvalidOperationException("ContentCacheRefresher MessageType should be ByPayload.");
}
if (args.MessageObject == null)
{
return;
}
var payloads = args.MessageObject as ContentCacheRefresher.JsonPayload[];
if (payloads == null)
if (!(args.MessageObject is ContentCacheRefresher.JsonPayload[]))
{
throw new InvalidOperationException("ContentCacheRefresher MessageObject should be JsonPayload[].");
}
@@ -137,8 +150,7 @@ namespace Umbraco.Web.Routing
{
// assuming we cannot have 'CacheUpdated' for only part of the infos else we'd need
// to set a flag in 'Published' to indicate which entities have been refreshed ok
CreateRedirect(oldRoute.Key.ContentId, oldRoute.Key.Culture, oldRoute.Value.ContentKey,
oldRoute.Value.OldRoute);
CreateRedirect(oldRoute.Key.ContentId, oldRoute.Key.Culture, oldRoute.Value.ContentKey, oldRoute.Value.OldRoute);
removeKeys.Add(oldRoute.Key);
}

View File

@@ -63,7 +63,7 @@
<PackageReference Include="AutoMapper" Version="8.0.0" />
<PackageReference Include="ClientDependency" Version="1.9.7" />
<PackageReference Include="CSharpTest.Net.Collections" Version="14.906.1403.1082" />
<PackageReference Include="Examine" Version="1.0.0-beta078" />
<PackageReference Include="Examine" Version="1.0.0-beta079" />
<PackageReference Include="HtmlAgilityPack" Version="1.8.14" />
<PackageReference Include="ImageProcessor">
<Version>2.6.2.25</Version>
@@ -860,7 +860,7 @@
<Compile Include="PropertyEditors\DateTimeValidator.cs" />
<Compile Include="PropertyEditors\IntegerPropertyEditor.cs" />
<Compile Include="PropertyEditors\MultipleTextStringPropertyEditor.cs" />
<Compile Include="PropertyEditors\PublishValuesMultipleValueEditor.cs" />
<Compile Include="PropertyEditors\MultipleValueEditor.cs" />
<Compile Include="PropertyEditors\RadioButtonsPropertyEditor.cs" />
<Compile Include="PropertyEditors\RichTextPreValueController.cs" />
<Compile Include="PropertyEditors\RichTextConfigurationEditor.cs" />