Merge remote-tracking branch 'origin/v8/dev' into v9/dev

# Conflicts:
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs
#	src/Umbraco.Core/Extensions/PublishedContentExtensions.cs
#	src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs
#	src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
#	src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs
#	src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml
#	src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs
This commit is contained in:
Bjarke Berg
2021-05-31 14:39:54 +02:00
21 changed files with 134 additions and 110 deletions

View File

@@ -3,6 +3,7 @@ using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters; using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Serialization;
@@ -62,6 +63,11 @@ namespace Umbraco.Cms.Core.Cache
foreach (var payload in payloads) foreach (var payload in payloads)
{ {
_idKeyMap.ClearCache(payload.Id); _idKeyMap.ClearCache(payload.Id);
if (dataTypeCache.Success)
{
dataTypeCache.Result.Clear(RepositoryCacheKeys.GetKey<IDataType, int>(payload.Id));
}
} }
// TODO: not sure I like these? // TODO: not sure I like these?

View File

@@ -262,7 +262,7 @@ namespace Umbraco.Extensions
// else... if we have a property, at least let the converter return its own // else... if we have a property, at least let the converter return its own
// vision of 'no value' (could be an empty enumerable) - otherwise, default // vision of 'no value' (could be an empty enumerable) - otherwise, default
return property == null ? default : property.Value<T>(publishedValueFallback, culture, segment, fallback, defaultValue); return property == null ? default : property.Value<T>(publishedValueFallback, culture, segment);
} }
#endregion #endregion

View File

@@ -32,16 +32,9 @@ namespace Umbraco.Extensions
// we have a value // we have a value
// try to cast or convert it // try to cast or convert it
var value = property.GetValue(culture, segment); var value = property.GetValue(culture, segment);
if (value is T valueAsT) if (value is T valueAsT) return valueAsT;
{
return valueAsT;
}
var valueConverted = value.TryConvertTo<T>(); var valueConverted = value.TryConvertTo<T>();
if (valueConverted) if (valueConverted) return valueConverted.Result;
{
return valueConverted.Result;
}
// cannot cast nor convert the value, nothing we can return but 'default' // cannot cast nor convert the value, nothing we can return but 'default'
// note: we don't want to fallback in that case - would make little sense // note: we don't want to fallback in that case - would make little sense
@@ -50,28 +43,14 @@ namespace Umbraco.Extensions
// we don't have a value, try fallback // we don't have a value, try fallback
if (publishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var fallbackValue)) if (publishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var fallbackValue))
{
return fallbackValue; return fallbackValue;
}
// we don't have a value - neither direct nor fallback // we don't have a value - neither direct nor fallback
// give a chance to the converter to return something (eg empty enumerable) // give a chance to the converter to return something (eg empty enumerable)
var noValue = property.GetValue(culture, segment); var noValue = property.GetValue(culture, segment);
if (noValue == null) if (noValue is T noValueAsT) return noValueAsT;
{
return default;
}
if (noValue is T noValueAsT)
{
return noValueAsT;
}
var noValueConverted = noValue.TryConvertTo<T>(); var noValueConverted = noValue.TryConvertTo<T>();
if (noValueConverted) if (noValueConverted) return noValueConverted.Result;
{
return noValueConverted.Result;
}
// cannot cast noValue nor convert it, nothing we can return but 'default' // cannot cast noValue nor convert it, nothing we can return but 'default'
return default; return default;

View File

@@ -5,6 +5,9 @@
/// </summary> /// </summary>
public class MediaPickerConfiguration : IIgnoreUserStartNodesConfig public class MediaPickerConfiguration : IIgnoreUserStartNodesConfig
{ {
[ConfigurationField("notice", "You can NOT change the property editor", "obsoletemediapickernotice")]
public bool Notice { get; set; }
[ConfigurationField("multiPicker", "Pick multiple items", "boolean")] [ConfigurationField("multiPicker", "Pick multiple items", "boolean")]
public bool Multiple { get; set; } public bool Multiple { get; set; }

View File

@@ -151,14 +151,14 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
//New UDI pickers with newer Ids //New UDI pickers with newer Ids
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = new Guid("FD1E0DA5-5606-4862-B679-5D0CF3A52A59"), Text = "Content Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = new Guid("FD1E0DA5-5606-4862-B679-5D0CF3A52A59"), Text = "Content Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "(Obsolete) Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker (old)", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "(Obsolete) Multiple Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Multi URL Picker (old)", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Multi URL Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1051, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1051", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3Guid, Text = "Media Picker 3", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1051, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1051", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3Guid, Text = "Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1052, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1052", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3MultipleGuid, Text = "Multiple Media Picker 3", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1052, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1052", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3MultipleGuid, Text = "Multiple Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1053, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1053", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3SingleImageGuid, Text = "Image Media Picker 3", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1053, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1053", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3SingleImageGuid, Text = "Image Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1054, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1054", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3MultipleImagesGuid, Text = "Multiple Image Media Picker 3", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1054, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1054", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3MultipleImagesGuid, Text = "Multiple Image Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
} }

View File

@@ -17,7 +17,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
[DataEditor( [DataEditor(
Constants.PropertyEditors.Aliases.MediaPicker3, Constants.PropertyEditors.Aliases.MediaPicker3,
EditorType.PropertyValue, EditorType.PropertyValue,
"Media Picker v3", "Media Picker",
"mediapicker3", "mediapicker3",
ValueType = ValueTypes.Json, ValueType = ValueTypes.Json,
Group = Constants.PropertyEditors.Groups.Media, Group = Constants.PropertyEditors.Groups.Media,

View File

@@ -19,11 +19,12 @@ namespace Umbraco.Cms.Core.PropertyEditors
[DataEditor( [DataEditor(
Constants.PropertyEditors.Aliases.MediaPicker, Constants.PropertyEditors.Aliases.MediaPicker,
EditorType.PropertyValue | EditorType.MacroParameter, EditorType.PropertyValue | EditorType.MacroParameter,
"Media Picker", "(Obsolete)Media Picker",
"mediapicker", "mediapicker",
ValueType = ValueTypes.Text, ValueType = ValueTypes.Text,
Group = Constants.PropertyEditors.Groups.Media, Group = Constants.PropertyEditors.Groups.Media,
Icon = Constants.Icons.MediaImage)] Icon = Constants.Icons.MediaImage,
IsDeprecated = true)]
public class MediaPickerPropertyEditor : DataEditor public class MediaPickerPropertyEditor : DataEditor
{ {
private readonly IIOHelper _ioHelper; private readonly IIOHelper _ioHelper;

View File

@@ -129,7 +129,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
content.SetValue("ddl", "1234"); content.SetValue("ddl", "1234");
content.SetValue("chklist", "randomc"); content.SetValue("chklist", "randomc");
content.SetValue("contentPicker", Udi.Create(Constants.UdiEntityType.Document, new Guid("74ECA1D4-934E-436A-A7C7-36CC16D4095C")).ToString()); content.SetValue("contentPicker", Udi.Create(Constants.UdiEntityType.Document, new Guid("74ECA1D4-934E-436A-A7C7-36CC16D4095C")).ToString());
content.SetValue("mediaPicker", Udi.Create(Constants.UdiEntityType.Media, new Guid("44CB39C8-01E5-45EB-9CF8-E70AAF2D1691")).ToString()); content.SetValue("mediaPicker3", "[{\"key\": \"8f78ce9e-8fe0-4500-a52d-4c4f35566ba9\",\"mediaKey\": \"44CB39C8-01E5-45EB-9CF8-E70AAF2D1691\",\"crops\": [],\"focalPoint\": {\"left\": 0.5,\"top\": 0.5}}]");
content.SetValue("memberPicker", Udi.Create(Constants.UdiEntityType.Member, new Guid("9A50A448-59C0-4D42-8F93-4F1D55B0F47D")).ToString()); content.SetValue("memberPicker", Udi.Create(Constants.UdiEntityType.Member, new Guid("9A50A448-59C0-4D42-8F93-4F1D55B0F47D")).ToString());
content.SetValue("multiUrlPicker", "[{\"name\":\"https://test.com\",\"url\":\"https://test.com\"}]"); content.SetValue("multiUrlPicker", "[{\"name\":\"https://test.com\",\"url\":\"https://test.com\"}]");
content.SetValue("tags", "this,is,tags"); content.SetValue("tags", "this,is,tags");

View File

@@ -355,7 +355,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.DropDownListFlexible, ValueStorageType.Integer) { Alias = "ddl", Name = "Dropdown List", Mandatory = false, SortOrder = 14, DataTypeId = -42 }); contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.DropDownListFlexible, ValueStorageType.Integer) { Alias = "ddl", Name = "Dropdown List", Mandatory = false, SortOrder = 14, DataTypeId = -42 });
contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.CheckBoxList, ValueStorageType.Nvarchar) { Alias = "chklist", Name = "Checkbox List", Mandatory = false, SortOrder = 15, DataTypeId = -43 }); contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.CheckBoxList, ValueStorageType.Nvarchar) { Alias = "chklist", Name = "Checkbox List", Mandatory = false, SortOrder = 15, DataTypeId = -43 });
contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.ContentPicker, ValueStorageType.Integer) { Alias = "contentPicker", Name = "Content Picker", Mandatory = false, SortOrder = 16, DataTypeId = 1046 }); contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.ContentPicker, ValueStorageType.Integer) { Alias = "contentPicker", Name = "Content Picker", Mandatory = false, SortOrder = 16, DataTypeId = 1046 });
contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.MediaPicker, ValueStorageType.Integer) { Alias = "mediaPicker", Name = "Media Picker", Mandatory = false, SortOrder = 17, DataTypeId = 1048 }); contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.MediaPicker3, ValueStorageType.Integer) { Alias = "mediapicker3", Name = "Media Picker", Mandatory = false, SortOrder = 17, DataTypeId = 1051 });
contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.MemberPicker, ValueStorageType.Integer) { Alias = "memberPicker", Name = "Member Picker", Mandatory = false, SortOrder = 18, DataTypeId = 1047 }); contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.MemberPicker, ValueStorageType.Integer) { Alias = "memberPicker", Name = "Member Picker", Mandatory = false, SortOrder = 18, DataTypeId = 1047 });
contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.MultiUrlPicker, ValueStorageType.Nvarchar) { Alias = "multiUrlPicker", Name = "Multi URL Picker", Mandatory = false, SortOrder = 21, DataTypeId = 1050 }); contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.MultiUrlPicker, ValueStorageType.Nvarchar) { Alias = "multiUrlPicker", Name = "Multi URL Picker", Mandatory = false, SortOrder = 21, DataTypeId = 1050 });
contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Tags, ValueStorageType.Ntext) { Alias = "tags", Name = "Tags", Mandatory = false, SortOrder = 22, DataTypeId = 1041 }); contentCollection.Add(new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.Tags, ValueStorageType.Ntext) { Alias = "tags", Name = "Tags", Mandatory = false, SortOrder = 22, DataTypeId = 1041 });

View File

@@ -12,6 +12,11 @@
var isLeftColumnAbove = false; var isLeftColumnAbove = false;
scope.editors = []; scope.editors = [];
/* we need to keep a count of open editors because the length of the editors array is first changed when animations are done
we do this because some infinite editors close more than one editor at the time and we get the wrong count from editors.length
because of the animation */
let editorCount = 0;
function addEditor(editor) { function addEditor(editor) {
editor.inFront = true; editor.inFront = true;
editor.moveRight = true; editor.moveRight = true;
@@ -51,13 +56,16 @@
updateEditors(-1); updateEditors(-1);
if(scope.editors.length === 1){ if(scope.editors.length === 1) {
if(isLeftColumnAbove){ if(isLeftColumnAbove){
$('#leftcolumn').addClass(aboveBackDropCssClass); $('#leftcolumn').addClass(aboveBackDropCssClass);
} }
isLeftColumnAbove = false; isLeftColumnAbove = false;
}
// when the last editor is closed remove the focus lock
if (editorCount === 0) {
// Remove the inert attribute from the #mainwrapper // Remove the inert attribute from the #mainwrapper
focusLockService.removeInertAttribute(); focusLockService.removeInertAttribute();
} }
@@ -105,16 +113,19 @@
} }
evts.push(eventsService.on("appState.editors.open", function (name, args) { evts.push(eventsService.on("appState.editors.open", function (name, args) {
editorCount = editorCount + 1;
addEditor(args.editor); addEditor(args.editor);
})); }));
evts.push(eventsService.on("appState.editors.close", function (name, args) { evts.push(eventsService.on("appState.editors.close", function (name, args) {
// remove the closed editor // remove the closed editor
if (args && args.editor) { if (args && args.editor) {
editorCount = editorCount - 1;
removeEditor(args.editor); removeEditor(args.editor);
} }
// close all editors // close all editors
if (args && !args.editor && args.editors.length === 0) { if (args && !args.editor && args.editors.length === 0) {
editorCount = 0;
scope.editors = []; scope.editors = [];
} }
})); }));

View File

@@ -40,7 +40,9 @@
function getDomNodes(){ function getDomNodes(){
infiniteEditorsWrapper = document.querySelector('.umb-editors'); infiniteEditorsWrapper = document.querySelector('.umb-editors');
infiniteEditors = Array.from(infiniteEditorsWrapper.querySelectorAll('.umb-editor')); if(infiniteEditorsWrapper) {
infiniteEditors = Array.from(infiniteEditorsWrapper.querySelectorAll('.umb-editor') || []);
}
} }
function getFocusableElements(targetElm) { function getFocusableElements(targetElm) {
@@ -84,22 +86,24 @@
var defaultFocusedElement = getAutoFocusElement(focusableElements); var defaultFocusedElement = getAutoFocusElement(focusableElements);
var lastKnownElement; var lastKnownElement;
// If an inifite editor is being closed then we reset the focus to the element that triggered the the overlay // If an infinite editor is being closed then we reset the focus to the element that triggered the the overlay
if(closingEditor){ if(closingEditor){
var lastItemIndex = $rootScope.lastKnownFocusableElements.length - 1;
var editorInfo = infiniteEditors[0].querySelector('.editor-info');
// If there is only one editor open, search for the "editor-info" inside it and set focus on it // If there is only one editor open, search for the "editor-info" inside it and set focus on it
// This is relevant when a property editor has been selected and the editor where we selected it from // This is relevant when a property editor has been selected and the editor where we selected it from
// is closed taking us back to the first layer // is closed taking us back to the first layer
// Otherwise set it to the last element in the lastKnownFocusedElements array // Otherwise set it to the last element in the lastKnownFocusedElements array
if(infiniteEditors.length === 1 && editorInfo !== null){ if(infiniteEditors && infiniteEditors.length === 1){
lastKnownElement = editorInfo; var editorInfo = infiniteEditors[0].querySelector('.editor-info');
if(infiniteEditors && infiniteEditors.length === 1 && editorInfo !== null) {
lastKnownElement = editorInfo;
// Clear the array // Clear the array
clearLastKnownFocusedElements(); clearLastKnownFocusedElements();
}
} }
else { else {
var lastItemIndex = $rootScope.lastKnownFocusableElements.length - 1;
lastKnownElement = $rootScope.lastKnownFocusableElements[lastItemIndex]; lastKnownElement = $rootScope.lastKnownFocusableElements[lastItemIndex];
// Remove the last item from the array so we always set the correct lastKnowFocus for each layer // Remove the last item from the array so we always set the correct lastKnowFocus for each layer
@@ -149,20 +153,24 @@
} }
function cleanupEventHandlers() { function cleanupEventHandlers() {
var activeEditor = infiniteEditors[infiniteEditors.length - 1]; //if we're in infinite editing mode
var inactiveEditors = infiniteEditors.filter(editor => editor !== activeEditor); if(infiniteEditors.length > 0) {
var activeEditor = infiniteEditors[infiniteEditors.length - 1];
var inactiveEditors = infiniteEditors.filter(editor => editor !== activeEditor);
if(inactiveEditors.length > 0) { if(inactiveEditors.length > 0) {
for (var index = 0; index < inactiveEditors.length; index++) { for (var index = 0; index < inactiveEditors.length; index++) {
var inactiveEditor = inactiveEditors[index]; var inactiveEditor = inactiveEditors[index];
// Remove event handlers from inactive editors // Remove event handlers from inactive editors
inactiveEditor.removeEventListener('keydown', handleKeydown); inactiveEditor.removeEventListener('keydown', handleKeydown);
}
}
else {
// Why is this one only begin called if there is no other infinite editors, wouldn't it make sense always to clean this up?
// Remove event handlers from the active editor
activeEditor.removeEventListener('keydown', handleKeydown);
} }
}
else {
// Remove event handlers from the active editor
activeEditor.removeEventListener('keydown', handleKeydown);
} }
} }
@@ -173,10 +181,7 @@
// Fetch the DOM nodes we need // Fetch the DOM nodes we need
getDomNodes(); getDomNodes();
// Cleanup event handlers if we're in infinite editing mode cleanupEventHandlers();
if(infiniteEditors.length > 0){
cleanupEventHandlers();
}
getFocusableElements(targetElm); getFocusableElements(targetElm);
@@ -204,17 +209,19 @@
// Make sure to disconnect the observer so we potentially don't end up with having many active ones // Make sure to disconnect the observer so we potentially don't end up with having many active ones
disconnectObserver = true; disconnectObserver = true;
// Pass the correct editor in order to find the focusable elements if(infiniteEditors && infiniteEditors.length > 1) {
var newTarget = infiniteEditors[infiniteEditors.length - 2]; // Pass the correct editor in order to find the focusable elements
var newTarget = infiniteEditors[infiniteEditors.length - 2];
if(infiniteEditors.length > 1){ if(infiniteEditors.length > 1) {
// Setting closing till true will let us re-apply the last known focus to then opened layer that then becomes // Setting closing till true will let us re-apply the last known focus to then opened layer that then becomes
// active // active
closingEditor = true; closingEditor = true;
onInit(newTarget); onInit(newTarget);
return; return;
}
} }
// Clear lastKnownFocusableElements // Clear lastKnownFocusableElements

View File

@@ -26,7 +26,7 @@ angular.module("umbraco.directives")
forceUpdate: '@?' forceUpdate: '@?'
}, },
link: function (scope, element, attrs) { link: function (scope, element, attrs, windowResizeListener) {
var unsubscribe = []; var unsubscribe = [];
let sliderRef = null; let sliderRef = null;

View File

@@ -48,8 +48,6 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
}); });
} }
else if (value[0]) { else if (value[0]) {
//notificationsService.error("Validation", value[0]);
console.log({type:messageType, header:"Validation", message:value[0]})
notificationsService.showNotification({type:messageType, header:"Validation", message:value[0]}) notificationsService.showNotification({type:messageType, header:"Validation", message:value[0]})
} }
} }

View File

@@ -441,7 +441,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
// We are not ready to limit the pasted elements further than default, we will return to this feature. ( TODO: Make this feature an option. ) // We are not ready to limit the pasted elements further than default, we will return to this feature. ( TODO: Make this feature an option. )
// We keep spans here, cause removing spans here also removes b-tags inside of them, instead we strip them out later. (TODO: move this definition to the config file... ) // We keep spans here, cause removing spans here also removes b-tags inside of them, instead we strip them out later. (TODO: move this definition to the config file... )
var validPasteElements = "-strong/b,-em/i,-u,-span,-p,-ol,-ul,-li,-p/div,-a[href|name],sub,sup,strike,br,del,table[width],tr,td[colspan|rowspan|width],th[colspan|rowspan|width],thead,tfoot,tbody,img[src|alt|width|height],ul,ol,li,hr,pre,dl,dt,figure,figcaption,wbr" var validPasteElements = "-strong/b,-em/i,-u,-span,-p,-ol,-ul,-li,-p/div,-a[href|name],sub,sup,strike,br,del,table[width],tr,td[colspan|rowspan|width],th[colspan|rowspan|width],thead,tfoot,tbody,img[src|alt|width|height],ul,ol,li,hr,pre,dl,dt,figure,figcaption,wbr"
// add elements from user configurated styleFormats to our list of validPasteElements. // add elements from user configurated styleFormats to our list of validPasteElements.
// (This means that we only allow H3-element if its configured as a styleFormat on this specific propertyEditor.) // (This means that we only allow H3-element if its configured as a styleFormat on this specific propertyEditor.)
var style, i = 0; var style, i = 0;
@@ -610,7 +610,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
'contenteditable': false 'contenteditable': false
}, },
embed.preview); embed.preview);
// Only replace if activeElement is an Embed element. // Only replace if activeElement is an Embed element.
if (activeElement && activeElement.nodeName.toUpperCase() === "DIV" && activeElement.classList.contains("embeditem")){ if (activeElement && activeElement.nodeName.toUpperCase() === "DIV" && activeElement.classList.contains("embeditem")){
activeElement.replaceWith(wrapper); // directly replaces the html node activeElement.replaceWith(wrapper); // directly replaces the html node
@@ -738,9 +738,9 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
id: "__mcenew", id: "__mcenew",
"data-udi": img.udi "data-udi": img.udi
}; };
editor.selection.setContent(editor.dom.createHTML('img', data)); editor.selection.setContent(editor.dom.createHTML('img', data));
// Using settimeout to wait for a DoM-render, so we can find the new element by ID. // Using settimeout to wait for a DoM-render, so we can find the new element by ID.
$timeout(function () { $timeout(function () {
@@ -761,7 +761,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
} }
}); });
} }
} }
}, },
@@ -1401,11 +1401,26 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
function syncContent() { function syncContent() {
if(args.model.value === args.editor.getContent()) {
return;
}
//stop watching before we update the value //stop watching before we update the value
stopWatch(); stopWatch();
angularHelper.safeApply($rootScope, function () { angularHelper.safeApply($rootScope, function () {
args.model.value = args.editor.getContent(); args.model.value = args.editor.getContent();
//make the form dirty manually so that the track changes works, setting our model doesn't trigger
// the angular bits because tinymce replaces the textarea.
if (args.currentForm) {
args.currentForm.$setDirty();
}
// With complex validation we need to set a input field to dirty, not the form. but we will keep the old code for backwards compatibility.
if (args.currentFormInput) {
args.currentFormInput.$setDirty();
}
}); });
//re-watch the value //re-watch the value
startWatch(); startWatch();
} }
@@ -1430,7 +1445,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
// Upload BLOB images (dragged/pasted ones) // Upload BLOB images (dragged/pasted ones)
// find src attribute where value starts with `blob:` // find src attribute where value starts with `blob:`
// search is case-insensitive and allows single or double quotes // search is case-insensitive and allows single or double quotes
if(content.search(/src=["']blob:.*?["']/gi) !== -1){ if(content.search(/src=["']blob:.*?["']/gi) !== -1){
args.editor.uploadImages(function(data) { args.editor.uploadImages(function(data) {
// Once all images have been uploaded // Once all images have been uploaded
@@ -1496,6 +1511,9 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
args.editor.on('Change', function (e) { args.editor.on('Change', function (e) {
syncContent(); syncContent();
}); });
args.editor.on('Keyup', function (e) {
syncContent();
});
//when we leave the editor (maybe) //when we leave the editor (maybe)
args.editor.on('blur', function (e) { args.editor.on('blur', function (e) {
@@ -1520,12 +1538,6 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
args.editor.on('Dirty', function (e) { args.editor.on('Dirty', function (e) {
syncContent(); // Set model.value to the RTE's content syncContent(); // Set model.value to the RTE's content
//make the form dirty manually so that the track changes works, setting our model doesn't trigger
// the angular bits because tinymce replaces the textarea.
if (args.currentForm) {
args.currentForm.$setDirty();
}
}); });
let self = this; let self = this;

View File

@@ -19,7 +19,6 @@
padding: 2px 6px; padding: 2px 6px;
} }
.umb-range-slider .noUi-handle { .umb-range-slider .noUi-handle {
outline: none;
cursor: grab; cursor: grab;
border-radius: 100px; border-radius: 100px;
border: none; border: none;

View File

@@ -35,7 +35,7 @@
width="{{crop.width}}" width="{{crop.width}}"
max-size="75"> max-size="75">
</umb-image-thumbnail> </umb-image-thumbnail>
<span class="__text">{{crop.alias}}</span> <span class="__text">{{crop.label}}</span>
</button> </button>
</div> </div>

View File

@@ -0,0 +1 @@
<p style="background-color: #fee4e1; padding: 8px;"><strong>Important:</strong> switching from the (Obsolete) Media Picker to Media Picker will mean all data (references to previously selected media items) will be deleted and no longer available.</p>

View File

@@ -5,12 +5,12 @@ angular.module("umbraco")
// TODO: A lot of the code below should be shared between the grid rte and the normal rte // TODO: A lot of the code below should be shared between the grid rte and the normal rte
$scope.isLoading = true; $scope.isLoading = true;
//To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias
// because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because
// we have this mini content editor panel that can be launched with MNTP. // we have this mini content editor panel that can be launched with MNTP.
$scope.textAreaHtmlId = $scope.model.alias + "_" + String.CreateGuid(); $scope.textAreaHtmlId = $scope.model.alias + "_" + String.CreateGuid();
var editorConfig = $scope.model.config ? $scope.model.config.editor : null; var editorConfig = $scope.model.config ? $scope.model.config.editor : null;
if (!editorConfig || Utilities.isString(editorConfig)) { if (!editorConfig || Utilities.isString(editorConfig)) {
editorConfig = tinyMceService.defaultPrevalues(); editorConfig = tinyMceService.defaultPrevalues();
@@ -28,14 +28,14 @@ angular.module("umbraco")
$scope.containerOverflow = editorConfig.mode === "distraction-free" ? (height ? "auto" : "inherit") : "inherit"; $scope.containerOverflow = editorConfig.mode === "distraction-free" ? (height ? "auto" : "inherit") : "inherit";
var promises = []; var promises = [];
// we need to make sure that the element is initialized before we can init TinyMCE, because we find the placeholder by ID, so it needs to be appended to document before. // we need to make sure that the element is initialized before we can init TinyMCE, because we find the placeholder by ID, so it needs to be appended to document before.
var initPromise = $q((resolve, reject) => { var initPromise = $q((resolve, reject) => {
this.$onInit = resolve; this.$onInit = resolve;
}); });
promises.push(initPromise); promises.push(initPromise);
//queue file loading //queue file loading
tinyMceAssets.forEach(function (tinyJsAsset) { tinyMceAssets.forEach(function (tinyJsAsset) {
promises.push(assetsService.loadJs(tinyJsAsset, $scope)); promises.push(assetsService.loadJs(tinyJsAsset, $scope));
@@ -50,50 +50,50 @@ angular.module("umbraco")
toolbar: editorConfig.toolbar, toolbar: editorConfig.toolbar,
mode: editorConfig.mode mode: editorConfig.mode
})); }));
//wait for queue to end //wait for queue to end
$q.all(promises).then(function (result) { $q.all(promises).then(function (result) {
var standardConfig = result[promises.length - 1]; var standardConfig = result[promises.length - 1];
if (height !== null) { if (height !== null) {
standardConfig.plugins.splice(standardConfig.plugins.indexOf("autoresize"), 1); standardConfig.plugins.splice(standardConfig.plugins.indexOf("autoresize"), 1);
} }
//create a baseline Config to extend upon //create a baseline Config to extend upon
var baseLineConfigObj = { var baseLineConfigObj = {
maxImageSize: editorConfig.maxImageSize, maxImageSize: editorConfig.maxImageSize,
width: width, width: width,
height: height height: height
}; };
baseLineConfigObj.setup = function (editor) { baseLineConfigObj.setup = function (editor) {
//set the reference //set the reference
tinyMceEditor = editor; tinyMceEditor = editor;
tinyMceEditor.on('init', function (e) { tinyMceEditor.on('init', function (e) {
$timeout(function () { $timeout(function () {
$scope.isLoading = false; $scope.isLoading = false;
}); });
}); });
//initialize the standard editor functionality for Umbraco //initialize the standard editor functionality for Umbraco
tinyMceService.initializeEditor({ tinyMceService.initializeEditor({
editor: editor, editor: editor,
model: $scope.model, model: $scope.model,
currentForm: angularHelper.getCurrentForm($scope) currentFormInput: $scope.rteForm.modelValue
}); });
}; };
angular.extend(baseLineConfigObj, standardConfig); angular.extend(baseLineConfigObj, standardConfig);
// We need to wait for DOM to have rendered before we can find the element by ID. // We need to wait for DOM to have rendered before we can find the element by ID.
$timeout(function () { $timeout(function () {
tinymce.init(baseLineConfigObj); tinymce.init(baseLineConfigObj);
}, 150); }, 150);
//listen for formSubmitting event (the result is callback used to remove the event subscription) //listen for formSubmitting event (the result is callback used to remove the event subscription)
var unsubscribe = $scope.$on("formSubmitting", function () { var unsubscribe = $scope.$on("formSubmitting", function () {
if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) { if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) {

View File

@@ -1,8 +1,10 @@
<div ng-controller="Umbraco.PropertyEditors.RTEController" class="umb-property-editor umb-rte" ng-class="{'--initialized': !isLoading}"> <div ng-controller="Umbraco.PropertyEditors.RTEController" class="umb-property-editor umb-rte" ng-class="{'--initialized': !isLoading}">
<umb-load-indicator ng-if="isLoading"></umb-load-indicator> <umb-load-indicator ng-if="isLoading"></umb-load-indicator>
<div class="umb-rte-editor-con"> <ng-form name="rteForm">
<input type="text" id="{{model.alias}}" ng-focus="focus()" style="position:absolute;top:0;width:0;height:0;" /> <div class="umb-rte-editor-con">
<div disable-hotkeys id="{{textAreaHtmlId}}" class="umb-rte-editor" ng-style="{ width: containerWidth, height: containerHeight, overflow: containerOverflow}"></div> <input type="text" id="{{model.alias}}" ng-focus="focus()" name="modelValue" ng-model="model.value" style="position:absolute;top:0;width:0;height:0;" />
</div> <div disable-hotkeys id="{{textAreaHtmlId}}" class="umb-rte-editor" ng-style="{ width: containerWidth, height: containerHeight, overflow: containerOverflow}"></div>
</div>
</ng-form>
</div> </div>

View File

@@ -235,8 +235,12 @@
<DevelopmentServerVPath>/</DevelopmentServerVPath> <DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:8121</IISUrl> <IISUrl>http://localhost:8121</IISUrl>
<DevelopmentServerPort>8130</DevelopmentServerPort> <DevelopmentServerPort>8130</DevelopmentServerPort>
<DevelopmentServerPort>8140</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath> <DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:8130</IISUrl> <IISUrl>http://localhost:8140</IISUrl>
<DevelopmentServerPort>8131</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:8131</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication> <NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer> <UseCustomServer>False</UseCustomServer>
<CustomServerUrl> <CustomServerUrl>

View File

@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EdotCover_002EIde_002ECore_002EFilterManagement_002EModel_002ESolutionFilterSettingsManagerMigrateSettings/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters /&gt;&lt;/data&gt;</s:String> <s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters /&gt;&lt;/data&gt;</s:String>
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data /&gt;</s:String> <s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data /&gt;</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=2DA32DA040A7D74599ABE288C7224CF0/Comment/@EntryValue">Disposable construction</s:String> <s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=2DA32DA040A7D74599ABE288C7224CF0/Comment/@EntryValue">Disposable construction</s:String>