Merge remote-tracking branch 'origin/v8/dev' into v8/bugfix/AB10622-be-property-editor-caching
This commit is contained in:
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Cache
|
||||
internal class DefaultRepositoryCachePolicy<TEntity, TId> : RepositoryCachePolicyBase<TEntity, TId>
|
||||
where TEntity : class, IEntity
|
||||
{
|
||||
private static readonly TEntity[] EmptyEntities = new TEntity[0]; // const
|
||||
private static readonly TEntity[] s_emptyEntities = new TEntity[0]; // const
|
||||
private readonly RepositoryCachePolicyOptions _options;
|
||||
|
||||
public DefaultRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options)
|
||||
@@ -29,16 +29,24 @@ namespace Umbraco.Core.Cache
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
protected string GetEntityCacheKey(object id)
|
||||
protected string GetEntityCacheKey(int id) => EntityTypeCacheKey + id;
|
||||
|
||||
protected string GetEntityCacheKey(TId id)
|
||||
{
|
||||
if (id == null) throw new ArgumentNullException(nameof(id));
|
||||
return GetEntityTypeCacheKey() + id;
|
||||
if (EqualityComparer<TId>.Default.Equals(id, default))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (typeof(TId).IsValueType)
|
||||
{
|
||||
return EntityTypeCacheKey + id;
|
||||
}
|
||||
|
||||
return EntityTypeCacheKey + id.ToString().ToUpperInvariant();
|
||||
}
|
||||
|
||||
protected string GetEntityTypeCacheKey()
|
||||
{
|
||||
return $"uRepo_{typeof (TEntity).Name}_";
|
||||
}
|
||||
protected string EntityTypeCacheKey { get; } = $"uRepo_{typeof(TEntity).Name}_";
|
||||
|
||||
protected virtual void InsertEntity(string cacheKey, TEntity entity)
|
||||
{
|
||||
@@ -52,7 +60,7 @@ namespace Umbraco.Core.Cache
|
||||
// getting all of them, and finding nothing.
|
||||
// if we can cache a zero count, cache an empty array,
|
||||
// for as long as the cache is not cleared (no expiration)
|
||||
Cache.Insert(GetEntityTypeCacheKey(), () => EmptyEntities);
|
||||
Cache.Insert(EntityTypeCacheKey, () => s_emptyEntities);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -81,7 +89,7 @@ namespace Umbraco.Core.Cache
|
||||
}
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -91,7 +99,7 @@ namespace Umbraco.Core.Cache
|
||||
Cache.Clear(GetEntityCacheKey(entity.Id));
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -113,7 +121,7 @@ namespace Umbraco.Core.Cache
|
||||
}
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -123,7 +131,7 @@ namespace Umbraco.Core.Cache
|
||||
Cache.Clear(GetEntityCacheKey(entity.Id));
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -144,7 +152,7 @@ namespace Umbraco.Core.Cache
|
||||
var cacheKey = GetEntityCacheKey(entity.Id);
|
||||
Cache.Clear(cacheKey);
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +203,7 @@ namespace Umbraco.Core.Cache
|
||||
else
|
||||
{
|
||||
// get everything we have
|
||||
var entities = Cache.GetCacheItemsByKeySearch<TEntity>(GetEntityTypeCacheKey())
|
||||
var entities = Cache.GetCacheItemsByKeySearch<TEntity>(EntityTypeCacheKey)
|
||||
.ToArray(); // no need for null checks, we are not caching nulls
|
||||
|
||||
if (entities.Length > 0)
|
||||
@@ -218,7 +226,7 @@ namespace Umbraco.Core.Cache
|
||||
{
|
||||
// if none of them were in the cache
|
||||
// and we allow zero count - check for the special (empty) entry
|
||||
var empty = Cache.GetCacheItem<TEntity[]>(GetEntityTypeCacheKey());
|
||||
var empty = Cache.GetCacheItem<TEntity[]>(EntityTypeCacheKey);
|
||||
if (empty != null) return empty;
|
||||
}
|
||||
}
|
||||
@@ -238,7 +246,7 @@ namespace Umbraco.Core.Cache
|
||||
/// <inheritdoc />
|
||||
public override void ClearAll()
|
||||
{
|
||||
Cache.ClearByKey(GetEntityTypeCacheKey());
|
||||
Cache.ClearByKey(EntityTypeCacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.Update(dto);
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IConsent>(entity.Id));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IConsent, int>(entity.Id));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
foreach (var translation in dictionaryItem.Translations)
|
||||
translation.Value = translation.Value.ToValidXmlString();
|
||||
|
||||
|
||||
var dto = DictionaryItemFactory.BuildDto(dictionaryItem);
|
||||
|
||||
var id = Convert.ToInt32(Database.Insert(dto));
|
||||
@@ -152,7 +152,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
foreach (var translation in entity.Translations)
|
||||
translation.Value = translation.Value.ToValidXmlString();
|
||||
|
||||
|
||||
var dto = DictionaryItemFactory.BuildDto(entity);
|
||||
|
||||
Database.Update(dto);
|
||||
@@ -174,8 +174,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
//Clear the cache entries that exist by uniqueid/item key
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.ItemKey));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.Key));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(entity.ItemKey));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(entity.Key));
|
||||
}
|
||||
|
||||
protected override void PersistDeletedItem(IDictionaryItem entity)
|
||||
@@ -186,8 +186,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = entity.Key });
|
||||
|
||||
//Clear the cache entries that exist by uniqueid/item key
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.ItemKey));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.Key));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(entity.ItemKey));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(entity.Key));
|
||||
|
||||
entity.DeleteDate = DateTime.Now;
|
||||
}
|
||||
@@ -203,8 +203,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = dto.UniqueId });
|
||||
|
||||
//Clear the cache entries that exist by uniqueid/item key
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(dto.Key));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(dto.UniqueId));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(dto.Key));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(dto.UniqueId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1163,7 +1163,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (withCache)
|
||||
{
|
||||
// if the cache contains the (proper version of the) item, use it
|
||||
var cached = IsolatedCache.GetCacheItem<IContent>(RepositoryCacheKeys.GetKey<IContent>(dto.NodeId));
|
||||
var cached = IsolatedCache.GetCacheItem<IContent>(RepositoryCacheKeys.GetKey<IContent, int>(dto.NodeId));
|
||||
if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id)
|
||||
{
|
||||
content[i] = (Content)cached;
|
||||
|
||||
@@ -508,7 +508,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (withCache)
|
||||
{
|
||||
// if the cache contains the (proper version of the) item, use it
|
||||
var cached = IsolatedCache.GetCacheItem<IMedia>(RepositoryCacheKeys.GetKey<IMedia>(dto.NodeId));
|
||||
var cached = IsolatedCache.GetCacheItem<IMedia>(RepositoryCacheKeys.GetKey<IMedia, int>(dto.NodeId));
|
||||
if (cached != null && cached.VersionId == dto.ContentVersionDto.Id)
|
||||
{
|
||||
content[i] = (Models.Media)cached;
|
||||
|
||||
@@ -331,7 +331,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
protected override void PersistUpdatedItem(IMember entity)
|
||||
{
|
||||
{
|
||||
// update
|
||||
entity.UpdatingEntity();
|
||||
|
||||
@@ -534,7 +534,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
var sqlSelectTemplateVersion = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin2", s => s
|
||||
.Select<ContentVersionDto>(x => x.Id)
|
||||
.From<ContentVersionDto>()
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<NodeDto>().On<NodeDto, ContentVersionDto>((l, r) => l.NodeId == r.NodeId)
|
||||
.InnerJoin<MemberDto>().On<MemberDto, NodeDto>((l, r) => l.NodeId == r.NodeId)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType"))
|
||||
@@ -606,7 +606,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (withCache)
|
||||
{
|
||||
// if the cache contains the (proper version of the) item, use it
|
||||
var cached = IsolatedCache.GetCacheItem<IMember>(RepositoryCacheKeys.GetKey<IMember>(dto.NodeId));
|
||||
var cached = IsolatedCache.GetCacheItem<IMember>(RepositoryCacheKeys.GetKey<IMember, int>(dto.NodeId));
|
||||
if (cached != null && cached.VersionId == dto.ContentVersionDto.Id)
|
||||
{
|
||||
content[i] = (Member) cached;
|
||||
|
||||
@@ -8,14 +8,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal static class RepositoryCacheKeys
|
||||
{
|
||||
private static readonly Dictionary<Type, string> Keys = new Dictionary<Type, string>();
|
||||
private static readonly Dictionary<Type, string> s_keys = new Dictionary<Type, string>();
|
||||
|
||||
public static string GetKey<T>()
|
||||
{
|
||||
var type = typeof(T);
|
||||
return Keys.TryGetValue(type, out var key) ? key : (Keys[type] = "uRepo_" + type.Name + "_");
|
||||
return s_keys.TryGetValue(type, out var key) ? key : (s_keys[type] = "uRepo_" + type.Name + "_");
|
||||
}
|
||||
|
||||
public static string GetKey<T>(object id) => GetKey<T>() + id;
|
||||
public static string GetKey<T, TId>(TId id)
|
||||
{
|
||||
if (EqualityComparer<TId>.Default.Equals(id, default))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (typeof(TId).IsValueType)
|
||||
{
|
||||
return GetKey<T>() + id;
|
||||
}
|
||||
|
||||
return GetKey<T>() + id.ToString().ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected override IUser PerformGet(int id)
|
||||
{
|
||||
// This will never resolve to a user, yet this is asked
|
||||
// for all of the time (especially in cases of members).
|
||||
// Don't issue a SQL call for this, we know it will not exist.
|
||||
if (id == default || id < -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var sql = SqlContext.Sql()
|
||||
.Select<UserDto>()
|
||||
.From<UserDto>()
|
||||
@@ -168,7 +176,7 @@ ORDER BY colName";
|
||||
}
|
||||
|
||||
public Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true)
|
||||
{
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var dto = new UserLoginDto
|
||||
{
|
||||
|
||||
@@ -442,7 +442,6 @@
|
||||
|
||||
// This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
|
||||
function performSave(args) {
|
||||
|
||||
//Used to check validility of nested form - coming from Content Apps mostly
|
||||
//Set them all to be invalid
|
||||
var fieldsToRollback = checkValidility();
|
||||
@@ -455,7 +454,8 @@
|
||||
create: $scope.page.isNew,
|
||||
action: args.action,
|
||||
showNotifications: args.showNotifications,
|
||||
softRedirect: true
|
||||
softRedirect: true,
|
||||
skipValidation: args.skipValidation
|
||||
}).then(function (data) {
|
||||
//success
|
||||
init();
|
||||
@@ -467,23 +467,24 @@
|
||||
|
||||
eventsService.emit("content.saved", { content: $scope.content, action: args.action, valid: true });
|
||||
|
||||
resetNestedFieldValiation(fieldsToRollback);
|
||||
if($scope.contentForm.$invalid !== true) {
|
||||
resetNestedFieldValiation(fieldsToRollback);
|
||||
}
|
||||
ensureDirtyIsSetIfAnyVariantIsDirty();
|
||||
|
||||
return $q.when(data);
|
||||
},
|
||||
function (err) {
|
||||
|
||||
|
||||
syncTreeNode($scope.content, $scope.content.path);
|
||||
|
||||
if($scope.contentForm.$invalid !== true) {
|
||||
resetNestedFieldValiation(fieldsToRollback);
|
||||
}
|
||||
if (err && err.status === 400 && err.data) {
|
||||
// content was saved but is invalid.
|
||||
eventsService.emit("content.saved", { content: $scope.content, action: args.action, valid: false });
|
||||
}
|
||||
|
||||
resetNestedFieldValiation(fieldsToRollback);
|
||||
|
||||
return $q.reject(err);
|
||||
});
|
||||
}
|
||||
@@ -735,48 +736,48 @@
|
||||
clearNotifications($scope.content);
|
||||
// TODO: Add "..." to save button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant
|
||||
if (hasVariants($scope.content)) {
|
||||
//before we launch the dialog we want to execute all client side validations first
|
||||
if (formHelper.submitForm({ scope: $scope, action: "openSaveDialog" })) {
|
||||
|
||||
var dialog = {
|
||||
parentScope: $scope,
|
||||
view: "views/content/overlays/save.html",
|
||||
variants: $scope.content.variants, //set a model property for the dialog
|
||||
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
|
||||
submitButtonLabelKey: "buttons_save",
|
||||
submit: function (model) {
|
||||
model.submitButtonState = "busy";
|
||||
var dialog = {
|
||||
parentScope: $scope,
|
||||
view: "views/content/overlays/save.html",
|
||||
variants: $scope.content.variants, //set a model property for the dialog
|
||||
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
|
||||
submitButtonLabelKey: "buttons_save",
|
||||
submit: function (model) {
|
||||
model.submitButtonState = "busy";
|
||||
clearNotifications($scope.content);
|
||||
//we need to return this promise so that the dialog can handle the result and wire up the validation response
|
||||
return performSave({
|
||||
saveMethod: $scope.saveMethod(),
|
||||
action: "save",
|
||||
showNotifications: false,
|
||||
skipValidation: true
|
||||
}).then(function (data) {
|
||||
//show all notifications manually here since we disabled showing them automatically in the save method
|
||||
formHelper.showNotifications(data);
|
||||
clearNotifications($scope.content);
|
||||
//we need to return this promise so that the dialog can handle the result and wire up the validation response
|
||||
return performSave({
|
||||
saveMethod: $scope.saveMethod(),
|
||||
action: "save",
|
||||
showNotifications: false
|
||||
}).then(function (data) {
|
||||
//show all notifications manually here since we disabled showing them automatically in the save method
|
||||
formHelper.showNotifications(data);
|
||||
clearNotifications($scope.content);
|
||||
overlayService.close();
|
||||
return $q.when(data);
|
||||
},
|
||||
function (err) {
|
||||
clearDirtyState($scope.content.variants);
|
||||
model.submitButtonState = "error";
|
||||
//re-map the dialog model since we've re-bound the properties
|
||||
dialog.variants = $scope.content.variants;
|
||||
handleHttpException(err);
|
||||
});
|
||||
},
|
||||
close: function (oldModel) {
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
return $q.when(data);
|
||||
},
|
||||
function (err) {
|
||||
clearDirtyState($scope.content.variants);
|
||||
//model.submitButtonState = "error";
|
||||
// Because this is the "save"-action, then we actually save though there was a validation error, therefor we will show success and display the validation errors politely.
|
||||
if(err && err.data && err.data.ModelState && Object.keys(err.data.ModelState).length > 0) {
|
||||
model.submitButtonState = "success";
|
||||
} else {
|
||||
model.submitButtonState = "error";
|
||||
}
|
||||
//re-map the dialog model since we've re-bound the properties
|
||||
dialog.variants = $scope.content.variants;
|
||||
handleHttpException(err);
|
||||
});
|
||||
},
|
||||
close: function (oldModel) {
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
|
||||
overlayService.open(dialog);
|
||||
}
|
||||
else {
|
||||
showValidationNotification();
|
||||
}
|
||||
overlayService.open(dialog);
|
||||
}
|
||||
else {
|
||||
//ensure the flags are set
|
||||
@@ -784,11 +785,17 @@
|
||||
$scope.page.saveButtonState = "busy";
|
||||
return performSave({
|
||||
saveMethod: $scope.saveMethod(),
|
||||
action: "save"
|
||||
action: "save",
|
||||
skipValidation: true
|
||||
}).then(function () {
|
||||
$scope.page.saveButtonState = "success";
|
||||
}, function (err) {
|
||||
$scope.page.saveButtonState = "error";
|
||||
// Because this is the "save"-action, then we actually save though there was a validation error, therefor we will show success and display the validation errors politely.
|
||||
if(err && err.data && err.data.ModelState && Object.keys(err.data.ModelState).length > 0) {
|
||||
$scope.page.saveButtonState = "success";
|
||||
} else {
|
||||
$scope.page.saveButtonState = "error";
|
||||
}
|
||||
handleHttpException(err);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -135,11 +135,11 @@
|
||||
}
|
||||
|
||||
// This directive allows for us to run a custom $compile for the view within the repeater which allows
|
||||
// us to maintain a $scope hierarchy with the rendered view based on the $scope that initiated the
|
||||
// us to maintain a $scope hierarchy with the rendered view based on the $scope that initiated the
|
||||
// infinite editing. The retain the $scope hiearchy a special $parentScope property is passed in to the model.
|
||||
function EditorRepeaterDirective($http, $templateCache, $compile, angularHelper) {
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
function link(scope, el) {
|
||||
|
||||
var editor = scope && scope.$parent ? scope.$parent.model : null;
|
||||
if (!editor) {
|
||||
return;
|
||||
|
||||
@@ -20,7 +20,7 @@ TODO
|
||||
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbFileDropzone',
|
||||
function ($timeout, Upload, localizationService, umbRequestHelper, overlayService) {
|
||||
function ($timeout, Upload, localizationService, umbRequestHelper, overlayService, mediaHelper, mediaTypeHelper) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
@@ -88,21 +88,12 @@ angular.module("umbraco.directives")
|
||||
});
|
||||
scope.queue = [];
|
||||
}
|
||||
// One allowed type
|
||||
if (scope.acceptedMediatypes && scope.acceptedMediatypes.length === 1) {
|
||||
// Standard setup - set alias to auto select to let the server best decide which media type to use
|
||||
if (scope.acceptedMediatypes[0].alias === 'Image') {
|
||||
scope.contentTypeAlias = "umbracoAutoSelect";
|
||||
} else {
|
||||
scope.contentTypeAlias = scope.acceptedMediatypes[0].alias;
|
||||
}
|
||||
// If we have Accepted Media Types, we will ask to choose Media Type, if Choose Media Type returns false, it only had one choice and therefor no reason to
|
||||
if (scope.acceptedMediatypes && _requestChooseMediaTypeDialog() === false) {
|
||||
scope.contentTypeAlias = "umbracoAutoSelect";
|
||||
|
||||
_processQueueItem();
|
||||
}
|
||||
// More than one, open dialog
|
||||
if (scope.acceptedMediatypes && scope.acceptedMediatypes.length > 1) {
|
||||
_chooseMediaType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,8 +137,8 @@ angular.module("umbraco.directives")
|
||||
// set percentage property on file
|
||||
file.uploadProgress = progressPercentage;
|
||||
// set uploading status on file
|
||||
file.uploadStatus = "uploading";
|
||||
}
|
||||
file.uploadStatus = "uploading";
|
||||
}
|
||||
})
|
||||
.success(function(data, status, headers, config) {
|
||||
if (data.notifications && data.notifications.length > 0) {
|
||||
@@ -195,35 +186,61 @@ angular.module("umbraco.directives")
|
||||
});
|
||||
}
|
||||
|
||||
function _chooseMediaType() {
|
||||
function _requestChooseMediaTypeDialog() {
|
||||
|
||||
const dialog = {
|
||||
view: "itempicker",
|
||||
filter: scope.acceptedMediatypes.length > 15,
|
||||
availableItems: scope.acceptedMediatypes,
|
||||
submit: function (model) {
|
||||
scope.contentTypeAlias = model.selectedItem.alias;
|
||||
_processQueueItem();
|
||||
if (scope.acceptedMediatypes.length === 1) {
|
||||
// if only one accepted type, then we wont ask to choose.
|
||||
return false;
|
||||
}
|
||||
|
||||
overlayService.close();
|
||||
},
|
||||
close: function () {
|
||||
var uploadFileExtensions = scope.queue.map(file => mediaHelper.getFileExtension(file.name));
|
||||
|
||||
scope.queue.map(function (file) {
|
||||
file.uploadStatus = "error";
|
||||
file.serverErrorMessage = "Cannot upload this file, no mediatype selected";
|
||||
scope.rejected.push(file);
|
||||
});
|
||||
scope.queue = [];
|
||||
var filteredMediaTypes = mediaTypeHelper.getTypeAcceptingFileExtensions(scope.acceptedMediatypes, uploadFileExtensions);
|
||||
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
var mediaTypesNotFile = filteredMediaTypes.filter(mediaType => mediaType.alias !== "File");
|
||||
|
||||
localizationService.localize("defaultdialogs_selectMediaType").then(value => {
|
||||
dialog.title = value;
|
||||
if (mediaTypesNotFile.length <= 1) {
|
||||
// if only one or less accepted types when we have filtered type 'file' out, then we wont ask to choose.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
localizationService.localizeMany(["defaultdialogs_selectMediaType", "mediaType_autoPickMediaType"]).then(function (translations) {
|
||||
|
||||
filteredMediaTypes.push({
|
||||
alias: "umbracoAutoSelect",
|
||||
name: translations[1],
|
||||
icon: "icon-wand"
|
||||
});
|
||||
|
||||
const dialog = {
|
||||
view: "itempicker",
|
||||
filter: filteredMediaTypes.length > 8,
|
||||
availableItems: filteredMediaTypes,
|
||||
submit: function (model) {
|
||||
scope.contentTypeAlias = model.selectedItem.alias;
|
||||
_processQueueItem();
|
||||
|
||||
overlayService.close();
|
||||
},
|
||||
close: function () {
|
||||
|
||||
scope.queue.map(function (file) {
|
||||
file.uploadStatus = "error";
|
||||
file.serverErrorMessage = "No files uploaded, no mediatype selected";
|
||||
scope.rejected.push(file);
|
||||
});
|
||||
scope.queue = [];
|
||||
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
|
||||
dialog.title = translations[0];
|
||||
overlayService.open(dialog);
|
||||
});
|
||||
|
||||
return true;// yes, we did open the choose-media dialog, therefor we return true.
|
||||
}
|
||||
|
||||
scope.handleFiles = function(files, event) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
function valFormManager(serverValidationManager, $rootScope, $timeout, $location, overlayService, eventsService, $routeParams, navigationService, editorService, localizationService, angularHelper) {
|
||||
|
||||
var SHOW_VALIDATION_CLASS_NAME = "show-validation";
|
||||
var SHOW_VALIDATION_Type_CLASS_NAME = "show-validation-type-";
|
||||
var SAVING_EVENT_NAME = "formSubmitting";
|
||||
var SAVED_EVENT_NAME = "formSubmitted";
|
||||
|
||||
@@ -25,7 +26,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
function ValFormManagerController($scope) {
|
||||
//This exposes an API for direct use with this directive
|
||||
|
||||
// We need this as a way to reference this directive in the scope chain. Since this directive isn't a component and
|
||||
// We need this as a way to reference this directive in the scope chain. Since this directive isn't a component and
|
||||
// because it's an attribute instead of an element, we can't use controllerAs or anything like that. Plus since this is
|
||||
// an attribute an isolated scope doesn't work so it's a bit weird. By doing this we are able to lookup the parent valFormManager
|
||||
// in the scope hierarchy even if the DOM hierarchy doesn't match (i.e. in infinite editing)
|
||||
@@ -44,6 +45,8 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
|
||||
this.isShowingValidation = () => $scope.showValidation === true;
|
||||
|
||||
this.getValidationMessageType = () => $scope.valMsgType;
|
||||
|
||||
this.notify = notify;
|
||||
|
||||
this.isValid = function () {
|
||||
@@ -94,6 +97,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
var parentFormMgr = scope.parentFormMgr = getAncestorValFormManager(scope, ctrls, 1);
|
||||
var subView = ctrls.length > 1 ? ctrls[2] : null;
|
||||
var labels = {};
|
||||
var valMsgType = 2;// error
|
||||
|
||||
var labelKeys = [
|
||||
"prompt_unsavedChanges",
|
||||
@@ -109,9 +113,48 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
labels.stayButton = values[3];
|
||||
});
|
||||
|
||||
//watch the list of validation errors to notify the application of any validation changes
|
||||
// TODO: Wouldn't it be easier/faster to watch formCtrl.$invalid ?
|
||||
scope.$watch(() => angularHelper.countAllFormErrors(formCtrl),
|
||||
var lastValidationMessageType = null;
|
||||
function setValidationMessageType(type) {
|
||||
|
||||
removeValidationMessageType();
|
||||
scope.valMsgType = type;
|
||||
|
||||
// overall a copy of message types from notifications.service:
|
||||
var postfix = "";
|
||||
switch(type) {
|
||||
case 0:
|
||||
//save
|
||||
break;
|
||||
case 1:
|
||||
//info
|
||||
postfix = "info";
|
||||
break;
|
||||
case 2:
|
||||
//error
|
||||
postfix = "error";
|
||||
break;
|
||||
case 3:
|
||||
//success
|
||||
postfix = "success";
|
||||
break;
|
||||
case 4:
|
||||
//warning
|
||||
postfix = "warning";
|
||||
break;
|
||||
}
|
||||
var cssClass = SHOW_VALIDATION_Type_CLASS_NAME+postfix;
|
||||
element.addClass(cssClass);
|
||||
lastValidationMessageType = cssClass;
|
||||
}
|
||||
function removeValidationMessageType() {
|
||||
if(lastValidationMessageType) {
|
||||
element.removeClass(lastValidationMessageType);
|
||||
lastValidationMessageType = null;
|
||||
}
|
||||
}
|
||||
|
||||
// watch the list of validation errors to notify the application of any validation changes
|
||||
scope.$watch(() => formCtrl.$invalid,
|
||||
function (e) {
|
||||
|
||||
notify(scope);
|
||||
@@ -138,6 +181,8 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
if (serverValidationManager.items.length > 0 || (parentFormMgr && parentFormMgr.isShowingValidation())) {
|
||||
element.addClass(SHOW_VALIDATION_CLASS_NAME);
|
||||
scope.showValidation = true;
|
||||
var parentValMsgType = parentFormMgr ? parentFormMgr.getValidationMessageType() : 2;
|
||||
setValidationMessageType(parentValMsgType || 2);
|
||||
notifySubView();
|
||||
}
|
||||
|
||||
@@ -145,8 +190,16 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
|
||||
//listen for the forms saving event
|
||||
unsubscribe.push(scope.$on(SAVING_EVENT_NAME, function (ev, args) {
|
||||
|
||||
var messageType = 2;//error
|
||||
switch (args.action) {
|
||||
case "save":
|
||||
messageType = 4;//warning
|
||||
break;
|
||||
}
|
||||
element.addClass(SHOW_VALIDATION_CLASS_NAME);
|
||||
scope.showValidation = true;
|
||||
setValidationMessageType(messageType);
|
||||
notifySubView();
|
||||
//set the flag so we can check to see if we should display the error.
|
||||
isSavingNewItem = $routeParams.create;
|
||||
@@ -156,6 +209,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
unsubscribe.push(scope.$on(SAVED_EVENT_NAME, function (ev, args) {
|
||||
//remove validation class
|
||||
element.removeClass(SHOW_VALIDATION_CLASS_NAME);
|
||||
removeValidationMessageType();
|
||||
scope.showValidation = false;
|
||||
notifySubView();
|
||||
}));
|
||||
|
||||
@@ -32,7 +32,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
|
||||
return true;
|
||||
}
|
||||
|
||||
function showNotificationsForModelsState(ms) {
|
||||
function showNotificationsForModelsState(ms, messageType) {
|
||||
messageType = messageType || 2;
|
||||
for (const [key, value] of Object.entries(ms)) {
|
||||
|
||||
var errorMsg = value[0];
|
||||
@@ -42,12 +43,14 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
|
||||
var idsToErrors = serverValidationManager.parseComplexEditorError(errorMsg, "");
|
||||
idsToErrors.forEach(x => {
|
||||
if (x.modelState) {
|
||||
showNotificationsForModelsState(x.modelState);
|
||||
showNotificationsForModelsState(x.modelState, messageType);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (value[0]) {
|
||||
notificationsService.error("Validation", 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]})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +96,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
|
||||
//we will use the default one for content if not specified
|
||||
var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback;
|
||||
|
||||
if (formHelper.submitForm({ scope: args.scope, action: args.action })) {
|
||||
var formSubmitOptions = { scope: args.scope, action: args.action };
|
||||
if(args.skipValidation === true) {
|
||||
formSubmitOptions.skipValidation = true;
|
||||
formSubmitOptions.keepServerValidation = true;
|
||||
}
|
||||
if (formHelper.submitForm(formSubmitOptions)) {
|
||||
|
||||
return args.saveMethod(args.content, args.create, fileManager.getFiles(), args.showNotifications)
|
||||
.then(function (data) {
|
||||
@@ -124,6 +132,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
|
||||
showNotifications: args.showNotifications,
|
||||
softRedirect: args.softRedirect,
|
||||
err: err,
|
||||
action: args.action,
|
||||
rebindCallback: function () {
|
||||
// if the error contains data, we want to map that back as we want to continue editing this save. Especially important when the content is new as the returned data will contain ID etc.
|
||||
if(err.data) {
|
||||
@@ -639,9 +648,14 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
|
||||
//wire up the server validation errs
|
||||
formHelper.handleServerValidation(args.err.data.ModelState);
|
||||
|
||||
var messageType = 2;//error
|
||||
if (args.action === "save") {
|
||||
messageType = 4;//warning
|
||||
}
|
||||
|
||||
//add model state errors to notifications
|
||||
if (args.showNotifications) {
|
||||
showNotificationsForModelsState(args.err.data.ModelState);
|
||||
showNotificationsForModelsState(args.err.data.ModelState, messageType);
|
||||
}
|
||||
|
||||
if (!this.redirectToCreatedContent(args.err.data.id, args.softRedirect) || args.softRedirect) {
|
||||
|
||||
@@ -23,15 +23,15 @@ function mediaTypeHelper(mediaTypeResource, $q) {
|
||||
getAllowedImagetypes: function (mediaId){
|
||||
|
||||
// TODO: This is horribly inneficient - why make one request per type!?
|
||||
//This should make a call to c# to get exactly what it's looking for instead of returning every single media type and doing
|
||||
//This should make a call to c# to get exactly what it's looking for instead of returning every single media type and doing
|
||||
//some filtering on the client side.
|
||||
//This is also called multiple times when it's not needed! Example, when launching the media picker, this will be called twice
|
||||
//This is also called multiple times when it's not needed! Example, when launching the media picker, this will be called twice
|
||||
//which means we'll be making at least 6 REST calls to fetch each media type
|
||||
|
||||
// Get All allowedTypes
|
||||
return mediaTypeResource.getAllowedTypes(mediaId)
|
||||
.then(function(types){
|
||||
|
||||
|
||||
var allowedQ = types.map(function(type){
|
||||
return mediaTypeResource.getById(type.id);
|
||||
});
|
||||
@@ -39,16 +39,8 @@ function mediaTypeHelper(mediaTypeResource, $q) {
|
||||
// Get full list
|
||||
return $q.all(allowedQ).then(function(fullTypes){
|
||||
|
||||
// Find all the media types with an Image Cropper property editor
|
||||
var filteredTypes = mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper']);
|
||||
|
||||
// If there is only one media type with an Image Cropper we will return this one
|
||||
if(filteredTypes.length === 1) {
|
||||
return filteredTypes;
|
||||
// If there is more than one Image cropper, custom media types have been added, and we return all media types with and Image cropper or UploadField
|
||||
} else {
|
||||
return mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper', 'Umbraco.UploadField']);
|
||||
}
|
||||
// Find all the media types with an Image Cropper or Upload Field property editor
|
||||
return mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper', 'Umbraco.UploadField']);
|
||||
|
||||
});
|
||||
});
|
||||
@@ -68,6 +60,31 @@ function mediaTypeHelper(mediaTypeResource, $q) {
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
getTypeAcceptingFileExtensions: function (mediaTypes, fileExtensions) {
|
||||
return mediaTypes.filter(mediaType => {
|
||||
var uploadProperty;
|
||||
mediaType.groups.forEach(group => {
|
||||
var foundProperty = group.properties.find(property => property.alias === "umbracoFile");
|
||||
if(foundProperty) {
|
||||
uploadProperty = foundProperty;
|
||||
}
|
||||
});
|
||||
if(uploadProperty) {
|
||||
var acceptedFileExtensions;
|
||||
if(uploadProperty.editor === "Umbraco.ImageCropper") {
|
||||
acceptedFileExtensions = Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes;
|
||||
} else if(uploadProperty.editor === "Umbraco.UploadField") {
|
||||
acceptedFileExtensions = (uploadProperty.config.fileExtensions && uploadProperty.config.fileExtensions.length > 0) ? uploadProperty.config.fileExtensions.map(x => x.value) : null;
|
||||
}
|
||||
if(acceptedFileExtensions && acceptedFileExtensions.length > 0) {
|
||||
return fileExtensions.length === fileExtensions.filter(fileExt => acceptedFileExtensions.includes(fileExt)).length;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -54,6 +54,15 @@
|
||||
border-color: @errorBorder;
|
||||
color: @errorText;
|
||||
}
|
||||
|
||||
.alert-warning() {
|
||||
background-color: @warningBackground;
|
||||
border-color: @warningBorder;
|
||||
color: @warningText;
|
||||
}
|
||||
.alert-warning {
|
||||
.alert-warning()
|
||||
}
|
||||
.alert-danger h4,
|
||||
.alert-error h4 {
|
||||
color: @errorText;
|
||||
@@ -110,6 +119,14 @@
|
||||
padding: 6px 16px 6px 12px;
|
||||
margin-bottom: 6px;
|
||||
|
||||
.show-validation-type-warning & {
|
||||
.alert-warning();
|
||||
font-weight: bold;
|
||||
&.alert-error::after {
|
||||
border-top-color: @warningBackground;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content:'';
|
||||
position: absolute;
|
||||
|
||||
@@ -50,6 +50,10 @@ button.umb-variant-switcher__toggle {
|
||||
font-weight: bold;
|
||||
background-color: @errorBackground;
|
||||
color: @errorText;
|
||||
.show-validation-type-warning & {
|
||||
background-color: @warningBackground;
|
||||
color: @warningText;
|
||||
}
|
||||
|
||||
animation-duration: 1.4s;
|
||||
animation-iteration-count: infinite;
|
||||
@@ -233,7 +237,10 @@ button.umb-variant-switcher__toggle {
|
||||
|
||||
.umb-variant-switcher__item.--error {
|
||||
.umb-variant-switcher__name {
|
||||
color: @red;
|
||||
color: @formErrorText;
|
||||
.show-validation-type-warning & {
|
||||
color: @formWarningText;
|
||||
}
|
||||
&::after {
|
||||
content: '!';
|
||||
position: relative;
|
||||
@@ -250,6 +257,10 @@ button.umb-variant-switcher__toggle {
|
||||
font-weight: bold;
|
||||
background-color: @errorBackground;
|
||||
color: @errorText;
|
||||
.show-validation-type-warning & {
|
||||
background-color: @warningBackground;
|
||||
color: @warningText;
|
||||
}
|
||||
|
||||
animation-duration: 1.4s;
|
||||
animation-iteration-count: infinite;
|
||||
|
||||
@@ -267,6 +267,9 @@
|
||||
.umb-overlay .text-error {
|
||||
color: @formErrorText;
|
||||
}
|
||||
.umb-overlay .text-warning {
|
||||
color: @formWarningText;
|
||||
}
|
||||
|
||||
.umb-overlay .text-success {
|
||||
color: @formSuccessText;
|
||||
|
||||
@@ -86,6 +86,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.show-validation.show-validation-type-warning &.-has-error {
|
||||
color: @yellow-d2;
|
||||
&:hover {
|
||||
color: @yellow-d2 !important;
|
||||
}
|
||||
&::before {
|
||||
background-color: @yellow-d2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__action:active,
|
||||
@@ -122,14 +132,6 @@
|
||||
line-height: 16px;
|
||||
display: block;
|
||||
|
||||
&.-type-alert {
|
||||
background-color: @red;
|
||||
}
|
||||
|
||||
&.-type-warning {
|
||||
background-color: @yellow-d2;
|
||||
}
|
||||
|
||||
&:empty {
|
||||
height: 12px;
|
||||
min-width: 12px;
|
||||
@@ -137,6 +139,11 @@
|
||||
&.--error-badge {
|
||||
display: none;
|
||||
font-weight: 900;
|
||||
background-color: @red;
|
||||
|
||||
.show-validation-type-warning & {
|
||||
background-color: @yellow-d2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,10 @@ a.umb-list-item:focus {
|
||||
}
|
||||
|
||||
.umb-list-item--error {
|
||||
color: @red;
|
||||
color: @formErrorText;
|
||||
}
|
||||
.umb-list-item--warning {
|
||||
color: @formWarningText;
|
||||
}
|
||||
|
||||
.umb-list-item:hover .umb-list-checkbox,
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
&.--error {
|
||||
border-color: @formErrorBorder !important;
|
||||
}
|
||||
|
||||
.show-validation-type-warning &.--error {
|
||||
border-color: @formWarningBorder !important;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-nested-content__item.ui-sortable-placeholder {
|
||||
@@ -292,4 +296,4 @@
|
||||
.umb-textarea, .umb-textstring {
|
||||
width:100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,13 @@
|
||||
background-color: @red !important;
|
||||
border-color: @errorBorder;
|
||||
}
|
||||
.show-validation.show-validation-type-warning .umb-tab--error > .umb-tab-button,
|
||||
.show-validation.show-validation-type-warning .umb-tab--error > .umb-tab-button:hover,
|
||||
.show-validation.show-validation-type-warning .umb-tab--error > .umb-tab-button:focus {
|
||||
color: @white !important;
|
||||
background-color: @yellow-d2 !important;
|
||||
border-color: @warningBorder;
|
||||
}
|
||||
|
||||
.show-validation .umb-tab--error .umb-tab-button:before {
|
||||
content: "\e25d";
|
||||
|
||||
@@ -506,10 +506,20 @@ input[type="checkbox"][readonly] {
|
||||
.formFieldState(@formErrorText, @formErrorText, @formErrorBackground);
|
||||
}
|
||||
|
||||
// ValidationError as a warning
|
||||
.show-validation.show-validation-type-warning.ng-invalid .control-group.error,
|
||||
.show-validation.show-validation-type-warning.ng-invalid .umb-editor-header__name-wrapper {
|
||||
.formFieldState(@formWarningText, @formWarningText, @formWarningBackground);
|
||||
}
|
||||
|
||||
//val-highlight directive styling
|
||||
.highlight-error {
|
||||
color: @formErrorText !important;
|
||||
border-color: @red-l1 !important;
|
||||
.show-validation-type-warning & {
|
||||
color: @formWarningText !important;
|
||||
border-color: @yellow-d2 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// FORM ACTIONS
|
||||
|
||||
@@ -291,8 +291,8 @@
|
||||
@btnSuccessBackground: @ui-btn-positive;// updated 2019
|
||||
@btnSuccessBackgroundHighlight: @ui-btn-positive-hover;// updated 2019
|
||||
|
||||
@btnWarningBackground: @orange;
|
||||
@btnWarningBackgroundHighlight: lighten(@orange, 10%);
|
||||
@btnWarningBackground: @yellow-d2;
|
||||
@btnWarningBackgroundHighlight: lighten(@yellow-d2, 10%);
|
||||
|
||||
@btnDangerBackground: @red;
|
||||
@btnDangerBackgroundHighlight: @red-l1;
|
||||
@@ -480,7 +480,7 @@
|
||||
@formWarningBorder: darken(spin(@warningBackground, -10), 3%);
|
||||
|
||||
@formErrorText: @errorBackground;
|
||||
@formErrorBackground: lighten(@errorBackground, 55%);
|
||||
@formErrorBackground: @errorBackground;
|
||||
@formErrorBorder: @red;
|
||||
|
||||
@formSuccessText: @successBackground;
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
<div class="umb-list-item"
|
||||
ng-repeat="variant in vm.availableVariants track by variant.compositeId">
|
||||
<ng-form name="saveVariantSelectorForm">
|
||||
<div class="umb-variant-selector-entry" ng-class="{'umb-list-item--error': saveVariantSelectorForm.saveVariantSelector.$invalid}">
|
||||
<div class="umb-variant-selector-entry" ng-class="{'umb-list-item--warning': saveVariantSelectorForm.saveVariantSelector.$invalid}">
|
||||
|
||||
<input type="hidden" name="saveInvariant" val-server-field="_content_variant_invariant_null_" ng-model="variant.save"></input>
|
||||
|
||||
<umb-checkbox input-id="{{variant.htmlId}}"
|
||||
name="saveVariantSelector"
|
||||
@@ -24,9 +26,10 @@
|
||||
on-change="vm.changeSelection(variant)"
|
||||
server-validation-field="{{variant.htmlId}}">
|
||||
|
||||
|
||||
|
||||
<span class="umb-variant-selector-entry__title" ng-if="!(variant.segment && variant.language)">
|
||||
<span ng-bind="variant.displayName"></span>
|
||||
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
|
||||
</span>
|
||||
<span class="umb-variant-selector-entry__title" ng-if="variant.segment && variant.language">
|
||||
<span ng-bind="variant.segment"></span>
|
||||
@@ -36,10 +39,13 @@
|
||||
<span class="umb-variant-selector-entry__description" ng-if="!saveVariantSelectorForm.saveVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
|
||||
<umb-variant-state variant="variant"></umb-variant-state>
|
||||
<span ng-if="variant.isMandatory"> - </span>
|
||||
<span ng-if="variant.isMandatory" ng-class="{'text-error': (variant.publish === false) }"><localize key="general_mandatory"></localize></span>
|
||||
<span ng-if="variant.isMandatory"><localize key="general_mandatory"></localize></span>
|
||||
</span>
|
||||
<span class="umb-variant-selector-entry__description" ng-messages="saveVariantSelectorForm.saveVariantSelector.$error" show-validation-on-submit>
|
||||
<span class="text-error" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</span>
|
||||
<span class="text-warning" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</span>
|
||||
</span>
|
||||
<span class="umb-variant-selector-entry__description" ng-messages="saveVariantSelectorForm.saveInvariant.$error" show-validation-on-submit>
|
||||
<span class="text-warning" ng-message="valServerField">{{saveVariantSelectorForm.saveInvariant.errorMsg}}</span>
|
||||
</span>
|
||||
|
||||
</umb-checkbox>
|
||||
|
||||
@@ -60,8 +60,14 @@
|
||||
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block > .umb-block-list__block--content > div > & {
|
||||
> button {
|
||||
color: @formErrorText;
|
||||
.show-validation-type-warning & {
|
||||
color: @formWarningText;
|
||||
}
|
||||
span.caret {
|
||||
border-top-color: @formErrorText;
|
||||
.show-validation-type-warning & {
|
||||
border-top-color: @formWarningText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +90,9 @@
|
||||
padding: 2px;
|
||||
line-height: 10px;
|
||||
background-color: @formErrorText;
|
||||
.show-validation-type-warning & {
|
||||
background-color: @formWarningText;
|
||||
}
|
||||
font-weight: 900;
|
||||
|
||||
animation-duration: 1.4s;
|
||||
|
||||
@@ -42,6 +42,9 @@
|
||||
|
||||
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block > .umb-block-list__block--content > div > & {
|
||||
color: @formErrorText;
|
||||
.show-validation-type-warning & {
|
||||
color: @formWarningText;
|
||||
}
|
||||
}
|
||||
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block:not(.--active) > .umb-block-list__block--content > div > & {
|
||||
> span {
|
||||
@@ -61,6 +64,9 @@
|
||||
padding: 2px;
|
||||
line-height: 10px;
|
||||
background-color: @formErrorText;
|
||||
.show-validation-type-warning & {
|
||||
background-color: @formWarningText;
|
||||
}
|
||||
font-weight: 900;
|
||||
|
||||
animation-duration: 1.4s;
|
||||
|
||||
@@ -23,10 +23,6 @@
|
||||
> .umb-block-list__block--actions {
|
||||
opacity: 0;
|
||||
transition: opacity 120ms;
|
||||
|
||||
.--error {
|
||||
color: @formErrorBorder !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
@@ -100,6 +96,12 @@ ng-form.ng-invalid-val-server-match-settings > .umb-block-list__block > .umb-blo
|
||||
&:hover {
|
||||
color: @ui-action-discreet-type-hover;
|
||||
}
|
||||
&.--error {
|
||||
color: @errorBackground;
|
||||
.show-validation-type-warning & {
|
||||
color: @warningBackground;
|
||||
}
|
||||
}
|
||||
> .__error-badge {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
@@ -113,7 +115,10 @@ ng-form.ng-invalid-val-server-match-settings > .umb-block-list__block > .umb-blo
|
||||
font-weight: bold;
|
||||
padding: 2px;
|
||||
line-height: 8px;
|
||||
background-color: @red;
|
||||
background-color: @errorBackground;
|
||||
.show-validation-type-warning & {
|
||||
background-color: @warningBackground;
|
||||
}
|
||||
display: none;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,12 @@
|
||||
vm.supportCopy = clipboardService.isSupported();
|
||||
vm.clipboardItems = [];
|
||||
unsubscribe.push(eventsService.on("clipboardService.storageUpdate", updateClipboard));
|
||||
unsubscribe.push($scope.$on("editors.content.splitViewChanged", (event, eventData) => {
|
||||
var compositeId = vm.umbVariantContent.editor.compositeId;
|
||||
if(eventData.editors.some(x => x.compositeId === compositeId)) {
|
||||
updateAllBlockObjects();
|
||||
}
|
||||
}));
|
||||
|
||||
vm.layout = []; // The layout object specific to this Block Editor, will be a direct reference from Property Model.
|
||||
vm.availableBlockTypes = []; // Available block entries of this property editor.
|
||||
@@ -66,6 +72,7 @@
|
||||
});
|
||||
|
||||
vm.$onInit = function() {
|
||||
|
||||
if (vm.umbProperty && !vm.umbVariantContent) {// if we dont have vm.umbProperty, it means we are in the DocumentTypeEditor.
|
||||
// not found, then fallback to searching the scope chain, this may be needed when DOM inheritance isn't maintained but scope
|
||||
// inheritance is (i.e.infinite editing)
|
||||
@@ -175,6 +182,8 @@
|
||||
// is invalid for some reason or the data structure has changed.
|
||||
invalidLayoutItems.push(entry);
|
||||
}
|
||||
} else {
|
||||
updateBlockObject(entry.$block);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -197,6 +206,16 @@
|
||||
|
||||
}
|
||||
|
||||
function updateAllBlockObjects() {
|
||||
// Update the blockObjects in our layout.
|
||||
vm.layout.forEach(entry => {
|
||||
// $block must have the data property to be a valid BlockObject, if not its considered as a destroyed blockObject.
|
||||
if (entry.$block) {
|
||||
updateBlockObject(entry.$block);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getDefaultViewForBlock(block) {
|
||||
|
||||
var defaultViewFolderPath = "views/propertyeditors/blocklist/blocklistentryeditors/";
|
||||
@@ -237,9 +256,6 @@
|
||||
|
||||
if (block === null) return null;
|
||||
|
||||
ensureCultureData(block.content);
|
||||
ensureCultureData(block.settings);
|
||||
|
||||
block.view = (block.config.view ? block.config.view : getDefaultViewForBlock(block));
|
||||
block.showValidation = block.config.view ? true : false;
|
||||
|
||||
@@ -254,20 +270,51 @@
|
||||
block.setParentForm = function (parentForm) {
|
||||
this._parentForm = parentForm;
|
||||
};
|
||||
block.activate = activateBlock.bind(null, block);
|
||||
block.edit = function () {
|
||||
|
||||
/** decorator methods, to enable switching out methods without loosing references that would have been made in Block Views codes */
|
||||
block.activate = function() {
|
||||
this._activate();
|
||||
};
|
||||
block.edit = function() {
|
||||
this._edit();
|
||||
};
|
||||
block.editSettings = function() {
|
||||
this._editSettings();
|
||||
};
|
||||
block.requestDelete = function() {
|
||||
this._requestDelete();
|
||||
};
|
||||
block.delete = function() {
|
||||
this._delete();
|
||||
};
|
||||
block.copy = function() {
|
||||
this._copy();
|
||||
};
|
||||
updateBlockObject(block);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
/** As the block object now contains references to this instance of a property editor, we need to ensure that the Block Object contains latest references.
|
||||
* This is a bit hacky but the only way to maintain this reference currently.
|
||||
* Notice this is most relevant for invariant properties on variant documents, specially for the scenario where the scope of the reference we stored is destroyed, therefor we need to ensure we always have references to a current running property editor*/
|
||||
function updateBlockObject(block) {
|
||||
|
||||
ensureCultureData(block.content);
|
||||
ensureCultureData(block.settings);
|
||||
|
||||
block._activate = activateBlock.bind(null, block);
|
||||
block._edit = function () {
|
||||
var blockIndex = vm.layout.indexOf(this.layout);
|
||||
editBlock(this, false, blockIndex, this._parentForm);
|
||||
};
|
||||
block.editSettings = function () {
|
||||
block._editSettings = function () {
|
||||
var blockIndex = vm.layout.indexOf(this.layout);
|
||||
editBlock(this, true, blockIndex, this._parentForm);
|
||||
};
|
||||
block.requestDelete = requestDeleteBlock.bind(null, block);
|
||||
block.delete = deleteBlock.bind(null, block);
|
||||
block.copy = copyBlock.bind(null, block);
|
||||
|
||||
return block;
|
||||
block._requestDelete = requestDeleteBlock.bind(null, block);
|
||||
block._delete = deleteBlock.bind(null, block);
|
||||
block._copy = copyBlock.bind(null, block);
|
||||
}
|
||||
|
||||
function addNewBlock(index, contentElementTypeKey) {
|
||||
|
||||
@@ -343,6 +343,7 @@
|
||||
<area alias="mediaType">
|
||||
<key alias="copyFailed">Kopiering af medietypen fejlede</key>
|
||||
<key alias="moveFailed">Flytning af medietypen fejlede</key>
|
||||
<key alias="autoPickMediaType">Auto vælg</key>
|
||||
</area>
|
||||
<area alias="memberType">
|
||||
<key alias="copyFailed">Kopiering af medlemstypen fejlede</key>
|
||||
|
||||
@@ -362,6 +362,7 @@
|
||||
<area alias="mediaType">
|
||||
<key alias="copyFailed">Failed to copy media type</key>
|
||||
<key alias="moveFailed">Failed to move media type</key>
|
||||
<key alias="autoPickMediaType">Auto pick</key>
|
||||
</area>
|
||||
<area alias="memberType">
|
||||
<key alias="copyFailed">Failed to copy member type</key>
|
||||
|
||||
@@ -369,6 +369,7 @@
|
||||
<area alias="mediaType">
|
||||
<key alias="copyFailed">Failed to copy media type</key>
|
||||
<key alias="moveFailed">Failed to move media type</key>
|
||||
<key alias="autoPickMediaType">Auto pick</key>
|
||||
</area>
|
||||
<area alias="memberType">
|
||||
<key alias="copyFailed">Failed to copy member type</key>
|
||||
|
||||
@@ -54,9 +54,9 @@ namespace Umbraco.Web.Cache
|
||||
foreach (var payload in payloads.Where(x => x.Id != default))
|
||||
{
|
||||
//By INT Id
|
||||
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent>(payload.Id));
|
||||
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent, int>(payload.Id));
|
||||
//By GUID Key
|
||||
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent>(payload.Key));
|
||||
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent, Guid?>(payload.Key));
|
||||
|
||||
_idkMap.ClearCache(payload.Id);
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Umbraco.Web.Cache
|
||||
var macroRepoCache = AppCaches.IsolatedCaches.Get<IMacro>();
|
||||
if (macroRepoCache)
|
||||
{
|
||||
macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey<IMacro>(payload.Id));
|
||||
macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey<IMacro, int>(payload.Id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ namespace Umbraco.Web.Cache
|
||||
// repository cache
|
||||
// it *was* done for each pathId but really that does not make sense
|
||||
// only need to do it for the current media
|
||||
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia>(payload.Id));
|
||||
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia>(payload.Key));
|
||||
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia, int>(payload.Id));
|
||||
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia, Guid?>(payload.Key));
|
||||
|
||||
// remove those that are in the branch
|
||||
if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove))
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Umbraco.Web.Cache
|
||||
_legacyMemberRefresher = new LegacyMemberCacheRefresher(this, appCaches);
|
||||
}
|
||||
|
||||
public class JsonPayload
|
||||
public class JsonPayload
|
||||
{
|
||||
[JsonConstructor]
|
||||
public JsonPayload(int id, string username)
|
||||
@@ -87,11 +87,11 @@ namespace Umbraco.Web.Cache
|
||||
_idkMap.ClearCache(p.Id);
|
||||
if (memberCache)
|
||||
{
|
||||
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember>(p.Id));
|
||||
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember>(p.Username));
|
||||
}
|
||||
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember, int>(p.Id));
|
||||
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember, string>(p.Username));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Umbraco.Web.Cache
|
||||
public override void Refresh(int id)
|
||||
{
|
||||
var cache = AppCaches.IsolatedCaches.Get<IRelationType>();
|
||||
if (cache) cache.Result.Clear(RepositoryCacheKeys.GetKey<IRelationType>(id));
|
||||
if (cache) cache.Result.Clear(RepositoryCacheKeys.GetKey<IRelationType, int>(id));
|
||||
base.Refresh(id);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Umbraco.Web.Cache
|
||||
public override void Remove(int id)
|
||||
{
|
||||
var cache = AppCaches.IsolatedCaches.Get<IRelationType>();
|
||||
if (cache) cache.Result.Clear(RepositoryCacheKeys.GetKey<IRelationType>(id));
|
||||
if (cache) cache.Result.Clear(RepositoryCacheKeys.GetKey<IRelationType, int>(id));
|
||||
base.Remove(id);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,13 +43,13 @@ namespace Umbraco.Web.Cache
|
||||
var userCache = AppCaches.IsolatedCaches.Get<IUser>();
|
||||
if (userCache)
|
||||
{
|
||||
userCache.Result.Clear(RepositoryCacheKeys.GetKey<IUser>(id));
|
||||
userCache.Result.Clear(RepositoryCacheKeys.GetKey<IUser, int>(id));
|
||||
userCache.Result.ClearByKey(CacheKeys.UserContentStartNodePathsPrefix + id);
|
||||
userCache.Result.ClearByKey(CacheKeys.UserMediaStartNodePathsPrefix + id);
|
||||
userCache.Result.ClearByKey(CacheKeys.UserAllContentStartNodesPrefix + id);
|
||||
userCache.Result.ClearByKey(CacheKeys.UserAllMediaStartNodesPrefix + id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
base.Remove(id);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Umbraco.Web.Cache
|
||||
var userGroupCache = AppCaches.IsolatedCaches.Get<IUserGroup>();
|
||||
if (userGroupCache)
|
||||
{
|
||||
userGroupCache.Result.Clear(RepositoryCacheKeys.GetKey<IUserGroup>(id));
|
||||
userGroupCache.Result.Clear(RepositoryCacheKeys.GetKey<IUserGroup, int>(id));
|
||||
userGroupCache.Result.ClearByKey(UserGroupRepository.GetByAliasCacheKeyPrefix);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user