Browse Source

Merge branch 'master' into feature-json2

# Conflicts:
#	src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaConverter.cs
#	tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs
#	tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs
pull/335/head
Sebastian Stehle 7 years ago
parent
commit
a6750fef1e
  1. 2
      src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs
  2. 2
      src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs
  3. 46
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs
  4. 15
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs
  5. 51
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs
  6. 10
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs
  7. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  8. 10
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs
  9. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  10. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  11. 50
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs
  12. 60
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs
  13. 13
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  14. 13
      src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs
  15. 2
      src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs
  16. 7
      src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs
  17. 5
      src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/NumberFieldPropertiesDto.cs
  18. 5
      src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/StringFieldPropertiesDto.cs
  19. 22
      src/Squidex/Config/Domain/StoreServices.cs
  20. 8
      src/Squidex/app/features/content/shared/array-editor.component.html
  21. 21
      src/Squidex/app/features/content/shared/array-editor.component.ts
  22. 30
      src/Squidex/app/features/content/shared/array-item.component.html
  23. 17
      src/Squidex/app/features/content/shared/array-item.component.scss
  24. 34
      src/Squidex/app/features/content/shared/array-item.component.ts
  25. 11
      src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.html
  26. 16
      src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.ts
  27. 11
      src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html
  28. 13
      src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts
  29. 2
      src/Squidex/app/shared/services/schemas.types.ts
  30. 19
      src/Squidex/app/shared/state/contents.forms.ts
  31. 8
      src/Squidex/app/theme/icomoon/demo-files/demo.css
  32. 1240
      src/Squidex/app/theme/icomoon/demo.html
  33. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.eot
  34. 9
      src/Squidex/app/theme/icomoon/fonts/icomoon.svg
  35. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.ttf
  36. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.woff
  37. 66
      src/Squidex/app/theme/icomoon/icons/caret-bottom.svg
  38. 66
      src/Squidex/app/theme/icomoon/icons/caret-top.svg
  39. 70
      src/Squidex/app/theme/icomoon/icons/hide-all.svg
  40. 62
      src/Squidex/app/theme/icomoon/icons/hide.svg
  41. 70
      src/Squidex/app/theme/icomoon/icons/show-all.svg
  42. 62
      src/Squidex/app/theme/icomoon/icons/show.svg
  43. 2
      src/Squidex/app/theme/icomoon/selection.json
  44. 217
      src/Squidex/app/theme/icomoon/style.css
  45. 10
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs
  46. 12
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs
  47. 4
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs
  48. 10
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs
  49. 8
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs
  50. 2
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs
  51. 22
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs
  52. 26
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs
  53. 24
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs
  54. 12
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs
  55. 14
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs
  56. 93
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValidatorTests.cs

2
src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs

@ -21,6 +21,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
public double? DefaultValue { get; set; }
public bool IsUnique { get; set; }
public bool InlineEditable { get; set; }
public NumberFieldEditor Editor { get; set; }

2
src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs

@ -19,6 +19,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
public int? MaxLength { get; set; }
public bool IsUnique { get; set; }
public bool InlineEditable { get; set; }
public string DefaultValue { get; set; }

46
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs

@ -10,13 +10,20 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Core.ValidateContent
{
public delegate Task<IReadOnlyList<Guid>> CheckContents(Guid schemaId, FilterNode filter);
public delegate Task<IReadOnlyList<IAssetInfo>> CheckAssets(IEnumerable<Guid> ids);
public sealed class ValidationContext
{
private readonly Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent;
private readonly Func<IEnumerable<Guid>, Task<IReadOnlyList<IAssetInfo>>> checkAsset;
private readonly Guid contentId;
private readonly Guid schemaId;
private readonly CheckContents checkContent;
private readonly CheckAssets checkAsset;
private readonly ImmutableQueue<string> propertyPath;
public ImmutableQueue<string> Path
@ -24,18 +31,32 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
get { return propertyPath; }
}
public Guid ContentId
{
get { return contentId; }
}
public Guid SchemaId
{
get { return schemaId; }
}
public bool IsOptional { get; }
public ValidationContext(
Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent,
Func<IEnumerable<Guid>, Task<IReadOnlyList<IAssetInfo>>> checkAsset)
: this(checkContent, checkAsset, ImmutableQueue<string>.Empty, false)
Guid contentId,
Guid schemaId,
CheckContents checkContent,
CheckAssets checkAsset)
: this(contentId, schemaId, checkContent, checkAsset, ImmutableQueue<string>.Empty, false)
{
}
private ValidationContext(
Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent,
Func<IEnumerable<Guid>, Task<IReadOnlyList<IAssetInfo>>> checkAsset,
Guid contentId,
Guid schemaId,
CheckContents checkContent,
CheckAssets checkAsset,
ImmutableQueue<string> propertyPath,
bool isOptional)
{
@ -46,23 +67,26 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
this.checkContent = checkContent;
this.checkAsset = checkAsset;
this.contentId = contentId;
this.schemaId = schemaId;
IsOptional = isOptional;
}
public ValidationContext Optional(bool isOptional)
{
return isOptional == IsOptional ? this : new ValidationContext(checkContent, checkAsset, propertyPath, isOptional);
return isOptional == IsOptional ? this : new ValidationContext(contentId, schemaId, checkContent, checkAsset, propertyPath, isOptional);
}
public ValidationContext Nested(string property)
{
return new ValidationContext(checkContent, checkAsset, propertyPath.Enqueue(property), IsOptional);
return new ValidationContext(contentId, schemaId, checkContent, checkAsset, propertyPath.Enqueue(property), IsOptional);
}
public Task<IReadOnlyList<Guid>> GetInvalidContentIdsAsync(IEnumerable<Guid> contentIds, Guid schemaId)
public Task<IReadOnlyList<Guid>> GetContentIdsAsync(Guid schemaId, FilterNode filter)
{
return checkContent(contentIds, schemaId);
return checkContent(schemaId, filter);
}
public Task<IReadOnlyList<IAssetInfo>> GetAssetInfosAsync(IEnumerable<Guid> assetId)

15
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs

@ -7,12 +7,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
public sealed class ReferencesValidator : IValidator
{
private static readonly IReadOnlyList<string> Path = new List<string> { "Id" };
private readonly Guid schemaId;
public ReferencesValidator(Guid schemaId)
@ -24,11 +28,16 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
if (value is ICollection<Guid> contentIds)
{
var invalidIds = await context.GetInvalidContentIdsAsync(contentIds, schemaId);
var filter = new FilterComparison(Path, FilterOperator.In, new FilterValue(contentIds.ToList()));
var foundIds = await context.GetContentIdsAsync(schemaId, filter);
foreach (var invalidId in invalidIds)
foreach (var id in contentIds)
{
addError(context.Path, $"Contains invalid reference '{invalidId}'.");
if (!foundIds.Contains(id))
{
addError(context.Path, $"Contains invalid reference '{id}'.");
}
}
}
}

51
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs

@ -0,0 +1,51 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
public sealed class UniqueValidator : IValidator
{
public async Task ValidateAsync(object value, ValidationContext context, AddError addError)
{
var count = context.Path.Count();
if (value != null && (count == 0 || (count == 2 && context.Path.Last() == InvariantPartitioning.Instance.Master.Key)))
{
FilterNode filter = null;
if (value is string s)
{
filter = new FilterComparison(Path(context), FilterOperator.Equals, new FilterValue(s));
}
else if (value is double d)
{
filter = new FilterComparison(Path(context), FilterOperator.Equals, new FilterValue(d));
}
if (filter != null)
{
var found = await context.GetContentIdsAsync(context.SchemaId, filter);
if (found.Any(x => x != context.ContentId))
{
addError(context.Path, "Another content with the same value exists.");
}
}
}
}
private static List<string> Path(ValidationContext context)
{
return Enumerable.Repeat("Data", 1).Union(context.Path).ToList();
}
}
}

10
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs

@ -111,6 +111,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
{
yield return new AllowedValuesValidator<double>(field.Properties.AllowedValues);
}
if (field.Properties.IsUnique)
{
yield return new UniqueValidator();
}
}
public IEnumerable<IValidator> Visit(IField<ReferencesFieldProperties> field)
@ -147,6 +152,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
{
yield return new AllowedValuesValidator<string>(field.Properties.AllowedValues);
}
if (field.Properties.IsUnique)
{
yield return new UniqueValidator();
}
}
public IEnumerable<IValidator> Visit(IField<TagsFieldProperties> field)

2
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs

@ -54,7 +54,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
query = query.AdjustToModel(schema.SchemaDef, useDraft);
var filter = FindExtensions.BuildQuery(query, schema.Id, status);
var filter = query.ToFilter(schema.Id, status);
var contentCount = Collection.Find(filter).CountDocumentsAsync();
var contentItems =

10
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs

@ -16,10 +16,12 @@ using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
@ -53,13 +55,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
await base.SetupCollectionAsync(collection, ct);
}
public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> ids)
public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId, ISchemaEntity schema, FilterNode filterNode)
{
var filter = filterNode.AdjustToModel(schema.SchemaDef, true).ToFilter(schema.Id);
var contentEntities =
await Collection.Find(x => x.IndexedSchemaId == schemaId && ids.Contains(x.Id) && x.IsDeleted != true).Only(x => x.Id)
await Collection.Find(filter).Only(x => x.Id)
.ToListAsync();
return ids.Except(contentEntities.Select(x => Guid.Parse(x["_id"].AsString))).ToList();
return contentEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
}
public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId)

2
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs

@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
private NamedContentData dataDraft;
[BsonId]
[BsonElement]
[BsonElement("_id")]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; }

4
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -96,11 +96,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
}
}
public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> ids)
public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode filterNode)
{
using (Profiler.TraceMethod<MongoContentRepository>())
{
return await contentsDraft.QueryNotFoundAsync(appId, schemaId, ids);
return await contentsDraft.QueryIdsAsync(appId, await appProvider.GetSchemaAsync(appId, schemaId), filterNode);
}
}

50
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs

@ -0,0 +1,50 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using NodaTime;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
internal sealed class AdaptionVisitor : TransformVisitor
{
private readonly Func<IReadOnlyList<string>, IReadOnlyList<string>> pathConverter;
public AdaptionVisitor(Func<IReadOnlyList<string>, IReadOnlyList<string>> pathConverter)
{
this.pathConverter = pathConverter;
}
public override FilterNode Visit(FilterComparison nodeIn)
{
FilterComparison result;
var value = nodeIn.Rhs.Value;
if (value is Instant &&
!string.Equals(nodeIn.Lhs[0], "mt", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(nodeIn.Lhs[0], "ct", StringComparison.OrdinalIgnoreCase))
{
result = new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, new FilterValue(value.ToString()));
}
else
{
result = new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, nodeIn.Rhs);
}
if (result.Lhs.Count == 1 && result.Lhs[0] == "_id" && result.Rhs.Value is List<Guid> guidList)
{
result = new FilterComparison(nodeIn.Lhs, nodeIn.Operator, new FilterValue(guidList.Select(x => x.ToString()).ToList()));
}
return result;
}
}
}

60
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs

@ -11,7 +11,6 @@ using System.Linq;
using System.Reflection;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.GenerateEdmSchema;
using Squidex.Domain.Apps.Core.Schemas;
@ -28,33 +27,30 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
typeof(MongoContentEntity).GetProperties()
.ToDictionary(x => x.Name, x => x.GetCustomAttribute<BsonElementAttribute>()?.ElementName ?? x.Name, StringComparer.OrdinalIgnoreCase);
private sealed class AdaptionVisitor : TransformVisitor
public static Query AdjustToModel(this Query query, Schema schema, bool useDraft)
{
private readonly Func<IReadOnlyList<string>, IReadOnlyList<string>> pathConverter;
var pathConverter = PathConverter(schema, useDraft);
public AdaptionVisitor(Func<IReadOnlyList<string>, IReadOnlyList<string>> pathConverter)
if (query.Filter != null)
{
this.pathConverter = pathConverter;
query.Filter = query.Filter.Accept(new AdaptionVisitor(pathConverter));
}
public override FilterNode Visit(FilterComparison nodeIn)
{
var value = nodeIn.Rhs.Value;
query.Sort = query.Sort.Select(x => new SortNode(pathConverter(x.Path), x.SortOrder)).ToList();
if (value is Instant &&
!string.Equals(nodeIn.Lhs[0], "mt", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(nodeIn.Lhs[0], "ct", StringComparison.OrdinalIgnoreCase))
{
return new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, new FilterValue(value.ToString()));
return query;
}
return new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, nodeIn.Rhs);
}
public static FilterNode AdjustToModel(this FilterNode filterNode, Schema schema, bool useDraft)
{
var pathConverter = PathConverter(schema, useDraft);
return filterNode.Accept(new AdaptionVisitor(pathConverter));
}
public static Query AdjustToModel(this Query query, Schema schema, bool useDraft)
private static Func<IReadOnlyList<string>, IReadOnlyList<string>> PathConverter(Schema schema, bool useDraft)
{
var pathConverter = new Func<IReadOnlyList<string>, IReadOnlyList<string>>(propertyNames =>
return new Func<IReadOnlyList<string>, IReadOnlyList<string>>(propertyNames =>
{
var result = new List<string>(propertyNames);
@ -96,15 +92,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
return result;
});
if (query.Filter != null)
{
query.Filter = query.Filter.Accept(new AdaptionVisitor(pathConverter));
}
query.Sort = query.Sort.Select(x => new SortNode(pathConverter(x.Path), x.SortOrder)).ToList();
return query;
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentSort(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, Query query)
@ -122,16 +109,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
return cursor.Skip(query);
}
public static FilterDefinition<MongoContentEntity> BuildQuery(Query query, Guid schemaId, Status[] status)
public static FilterDefinition<MongoContentEntity> ToFilter(this Query query, Guid schemaId, Status[] status)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Eq(x => x.IndexedSchemaId, schemaId)
Filter.Eq(x => x.IndexedSchemaId, schemaId),
Filter.Ne(x => x.IsDeleted, true)
};
if (status != null)
{
filters.Add(Filter.Ne(x => x.IsDeleted, true));
filters.Add(Filter.In(x => x.Status, status));
}
@ -149,14 +136,19 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
}
}
if (filters.Count == 1)
{
return filters[0];
return Filter.And(filters);
}
else
public static FilterDefinition<MongoContentEntity> ToFilter(this FilterNode filterNode, Guid schemaId)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Eq(x => x.IndexedSchemaId, schemaId),
Filter.Ne(x => x.IsDeleted, true),
filterNode.BuildFilter<MongoContentEntity>()
};
return Filter.And(filters);
}
}
}
}

13
src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs

@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
case CreateContent createContent:
return CreateReturnAsync(createContent, async c =>
{
var ctx = await CreateContext(c.AppId.Id, c.SchemaId.Id, () => "Failed to create content.");
var ctx = await CreateContext(c.AppId.Id, c.SchemaId.Id, Guid.Empty, () => "Failed to create content.");
GuardContent.CanCreate(ctx.Schema, c);
@ -105,7 +105,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
try
{
var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, () => "Failed to change content.");
var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, Snapshot.Id, () => "Failed to change content.");
GuardContent.CanChangeContentStatus(ctx.Schema, Snapshot.IsPending, Snapshot.Status, c);
@ -162,7 +162,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
case DeleteContent deleteContent:
return UpdateAsync(deleteContent, async c =>
{
var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, () => "Failed to delete content.");
var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, Snapshot.Id, () => "Failed to delete content.");
GuardContent.CanDelete(ctx.Schema, c);
@ -197,7 +197,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (!currentData.Equals(newData))
{
var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, () => "Failed to update content.");
var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, Snapshot.Id, () => "Failed to update content.");
if (partial)
{
@ -301,11 +301,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
return Snapshot.Apply(@event);
}
private async Task<ContentOperationContext> CreateContext(Guid appId, Guid schemaId, Func<string> message)
private async Task<ContentOperationContext> CreateContext(Guid appId, Guid schemaId, Guid contentId, Func<string> message)
{
var operationContext =
await ContentOperationContext.CreateAsync(
appId, schemaId,
await ContentOperationContext.CreateAsync(appId, schemaId, contentId,
appProvider, assetRepository, contentRepository, scriptEngine, message);
return operationContext;

13
src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs

@ -7,7 +7,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.EnrichContent;
@ -18,6 +17,7 @@ using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents
@ -29,6 +29,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
private IScriptEngine scriptEngine;
private ISchemaEntity schemaEntity;
private IAppEntity appEntity;
private Guid contentId;
private Guid schemaId;
private Func<string> message;
public ISchemaEntity Schema
@ -39,6 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public static async Task<ContentOperationContext> CreateAsync(
Guid appId,
Guid schemaId,
Guid contentId,
IAppProvider appProvider,
IAssetRepository assetRepository,
IContentRepository contentRepository,
@ -51,8 +54,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
appEntity = appEntity,
assetRepository = assetRepository,
contentId = contentId,
contentRepository = contentRepository,
message = message,
schemaId = schemaId,
schemaEntity = schemaEntity,
scriptEngine = scriptEngine
};
@ -106,7 +111,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
private ValidationContext CreateValidationContext()
{
return new ValidationContext((contentIds, schemaId) => QueryContentsAsync(schemaId, contentIds), QueryAssetsAsync);
return new ValidationContext(contentId, schemaId, (sid, filterNode) => QueryContentsAsync(sid, filterNode), QueryAssetsAsync);
}
private async Task<IReadOnlyList<IAssetInfo>> QueryAssetsAsync(IEnumerable<Guid> assetIds)
@ -114,9 +119,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
return await assetRepository.QueryAsync(appEntity.Id, new HashSet<Guid>(assetIds));
}
private async Task<IReadOnlyList<Guid>> QueryContentsAsync(Guid schemaId, IEnumerable<Guid> contentIds)
private async Task<IReadOnlyList<Guid>> QueryContentsAsync(Guid filterSchemaId, FilterNode filterNode)
{
return await contentRepository.QueryNotFoundAsync(appEntity.Id, schemaId, contentIds.ToList());
return await contentRepository.QueryIdsAsync(appEntity.Id, filterSchemaId, filterNode);
}
}
}

2
src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs

@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories
Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Query query);
Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> ids);
Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode filterNode);
Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Guid id);

7
src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs

@ -26,10 +26,15 @@ namespace Squidex.Infrastructure.MongoDb.Queries
if (query.Filter != null)
{
return (FilterVisitor<T>.Visit(query.Filter), true);
return (query.Filter.BuildFilter<T>(), true);
}
return (null, false);
}
public static FilterDefinition<T> BuildFilter<T>(this FilterNode filterNode)
{
return FilterVisitor<T>.Visit(filterNode);
}
}
}

5
src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/NumberFieldPropertiesDto.cs

@ -35,6 +35,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
/// </summary>
public double[] AllowedValues { get; set; }
/// <summary>
/// Indicates if the field value must be unique. Ignored for nested fields and localized fields.
/// </summary>
public bool IsUnique { get; set; }
/// <summary>
/// Indicates that the inline editor is enabled for this field.
/// </summary>

5
src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/StringFieldPropertiesDto.cs

@ -45,6 +45,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
/// </summary>
public string[] AllowedValues { get; set; }
/// <summary>
/// Indicates if the field value must be unique. Ignored for nested fields and localized fields.
/// </summary>
public bool IsUnique { get; set; }
/// <summary>
/// Indicates that the inline editor is enabled for this field.
/// </summary>

22
src/Squidex/Config/Domain/StoreServices.cs

@ -66,8 +66,11 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<MongoMigrationStatus>()
.As<IMigrationStatus>();
services.AddSingletonAs<MongoPersistedGrantStore>()
.As<IPersistedGrantStore>();
services.AddTransientAs<ConvertOldSnapshotStores>()
.As<IMigration>();
services.AddTransientAs(c => new DeleteContentCollections(mongoContentDatabase))
.As<IMigration>();
services.AddSingletonAs<MongoUsageRepository>()
.As<IUsageRepository>();
@ -75,12 +78,15 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<MongoRuleEventRepository>()
.As<IRuleEventRepository>();
services.AddSingletonAs<MongoRoleStore>()
.As<IRoleStore<IdentityRole>>();
services.AddSingletonAs<MongoHistoryEventRepository>()
.As<IHistoryEventRepository>();
services.AddSingletonAs<MongoPersistedGrantStore>()
.As<IPersistedGrantStore>();
services.AddSingletonAs<MongoRoleStore>()
.As<IRoleStore<IdentityRole>>();
services.AddSingletonAs<MongoUserStore>()
.As<IUserStore<IdentityUser>>()
.As<IUserFactory>();
@ -93,12 +99,6 @@ namespace Squidex.Config.Domain
.As<IContentRepository>()
.As<ISnapshotStore<ContentState, Guid>>()
.As<IEventConsumer>();
services.AddTransientAs<ConvertOldSnapshotStores>()
.As<IMigration>();
services.AddTransientAs(c => new DeleteContentCollections(mongoContentDatabase))
.As<IMigration>();
}
});

8
src/Squidex/app/features/content/shared/array-editor.component.html

@ -5,15 +5,21 @@
<sqx-array-item
[form]="form"
[field]="field"
[isHidden]="isHidden"
[isFirst]="i === 0"
[isLast]="i === arrayControl.controls.length - 1"
[index]="i"
[itemForm]="itemForm"
[language]="language"
[languages]="languages"
(toggle)="hide($event)"
(moving)="move(itemForm, $event)"
(cloning)="addItem(itemForm)"
(removing)="removeItem(i)">
</sqx-array-item>
</div>
</div>
<button class="btn btn-success" (click)="addItem(); $event.preventDefault()">
<button class="btn btn-success" (click)="addItem(undefined); $event.preventDefault()">
Add Item
</button>

21
src/Squidex/app/features/content/shared/array-editor.component.ts

@ -6,7 +6,7 @@
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { AbstractControl, FormArray } from '@angular/forms';
import { AbstractControl, FormArray, FormGroup } from '@angular/forms';
import {
AppLanguageDto,
@ -37,12 +37,18 @@ export class ArrayEditorComponent {
@Input()
public arrayControl: FormArray;
public isHidden = false;
public hide(hide: boolean) {
this.isHidden = hide;
}
public removeItem(index: number) {
this.form.removeArrayItem(this.field, this.language, index);
}
public addItem() {
this.form.insertArrayItem(this.field, this.language);
public addItem(value?: FormGroup) {
this.form.insertArrayItem(this.field, this.language, value);
}
public sort(controls: AbstractControl[]) {
@ -50,4 +56,13 @@ export class ArrayEditorComponent {
this.arrayControl.setControl(i, controls[i]);
}
}
public move(control: AbstractControl, index: number) {
let controls = [...this.arrayControl.controls];
controls.splice(controls.indexOf(control), 1);
controls.splice(index, 0, control);
this.sort(controls);
}
}

30
src/Squidex/app/features/content/shared/array-item.component.html

@ -4,14 +4,40 @@
<i class="icon-drag2 mr-1"></i>
<span class="header-text text-decent">Item #{{index + 1}}</span>
<button class="btn btn-secondary btn-link" [disabled]="isFirst" (click)="moveTop(); $event.preventDefault()">
<i class="icon-caret-top"></i>
</button>
<button class="btn btn-secondary btn-link" [disabled]="isFirst" (click)="moveUp(); $event.preventDefault()">
<i class="icon-caret-up"></i>
</button>
<button class="btn btn-secondary btn-link" [disabled]="isLast" (click)="moveDown(); $event.preventDefault()">
<i class="icon-caret-down"></i>
</button>
<button class="btn btn-secondary btn-link" [disabled]="isLast" (click)="moveBottom(); $event.preventDefault()">
<i class="icon-caret-bottom"></i>
</button>
<button class="btn btn-secondary btn-link" [class.hidden]="!isHidden" (click)="toggle.emit(false); $event.preventDefault()" title="Open all items">
<i class="icon-plus-square"></i>
</button>
<button class="btn btn-secondary btn-link" [class.hidden]="isHidden" (click)="toggle.emit(true); $event.preventDefault()" title="Close all items">
<i class="icon-minus-square"></i>
</button>
</span>
<button type="button" class="btn btn-link btn-danger float-right" (click)="removing.emit(); $event.preventDefault()">
<span class="float-right">
<button type="button" class="btn btn-link btn-secondary" (click)="cloning.emit(); $event.preventDefault()">
<i class="icon-clone"></i>
</button>
<button type="button" class="btn btn-link btn-danger" (click)="removing.emit(); $event.preventDefault()">
<i class="icon-bin2"></i>
</button>
</span>
</div>
<div class="card-body">
<div class="card-body" [class.hidden]="isHidden">
<div class="form-group" *ngFor="let fieldControl of fieldControls">
<sqx-field-editor
[form]="form"

17
src/Squidex/app/features/content/shared/array-item.component.scss

@ -10,6 +10,23 @@
&-header {
line-height: 2.2rem;
}
.header-text {
display: inline-block;
min-width: 70px;
max-width: 100px;
}
}
.btn-link {
& {
padding: .375rem;
}
&:disabled,
&.disabled {
background: transparent;
}
}
.remove {

34
src/Squidex/app/features/content/shared/array-item.component.ts

@ -28,12 +28,30 @@ export class ArrayItemComponent implements OnChanges {
@Output()
public removing = new EventEmitter();
@Output()
public moving = new EventEmitter<number>();
@Output()
public cloning = new EventEmitter();
@Output()
public toggle = new EventEmitter<boolean>();
@Input()
public form: EditContentForm;
@Input()
public field: RootFieldDto;
@Input()
public isHidden = false;
@Input()
public isFirst = false;
@Input()
public isLast = false;
@Input()
public index: number;
@ -59,4 +77,20 @@ export class ArrayItemComponent implements OnChanges {
this.fieldControls = this.field.nested.map(field => ({ field, control: this.itemForm.get(field.name)! })).filter(x => !!x.control);
}
}
public moveTop() {
this.moving.emit(0);
}
public moveUp() {
this.moving.emit(this.index - 1);
}
public moveDown() {
this.moving.emit(this.index + 1);
}
public moveBottom() {
this.moving.emit(99999);
}
}

11
src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.html

@ -1,4 +1,15 @@
<div [formGroup]="editForm">
<div class="form-group row" *ngIf="showUnique">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldUnique" formControlName="isUnique" />
<label class="form-check-label" for="{{field.fieldId}}_fieldUnique">
Unique
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">

16
src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.ts

@ -10,7 +10,12 @@ import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { FieldDto, NumberFieldPropertiesDto } from '@app/shared';
import {
FieldDto,
NumberFieldPropertiesDto,
RootFieldDto,
Types
} from '@app/shared';
@Component({
selector: 'sqx-number-validation',
@ -27,9 +32,18 @@ export class NumberValidationComponent implements OnInit {
@Input()
public properties: NumberFieldPropertiesDto;
public showUnique: boolean;
public showDefaultValue: Observable<boolean>;
public ngOnInit() {
this.showUnique = Types.is(this.field, RootFieldDto) && !this.field.isLocalizable;
if (this.showUnique) {
this.editForm.setControl('isUnique',
new FormControl(this.properties.isUnique));
}
this.editForm.setControl('maxValue',
new FormControl(this.properties.maxValue));

11
src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html

@ -1,4 +1,15 @@
<div [formGroup]="editForm">
<div class="form-group row" *ngIf="showUnique">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldUnique" formControlName="isUnique" />
<label class="form-check-label" for="{{field.fieldId}}_fieldUnique">
Unique
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">

13
src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts

@ -15,7 +15,9 @@ import {
FieldDto,
ImmutableArray,
ModalModel,
StringFieldPropertiesDto
RootFieldDto,
StringFieldPropertiesDto,
Types
} from '@app/shared';
@Component({
@ -45,11 +47,20 @@ export class StringValidationComponent implements OnDestroy, OnInit {
public patternName: string;
public patternsModal = new ModalModel();
public showUnique: boolean;
public ngOnDestroy() {
this.patternSubscription.unsubscribe();
}
public ngOnInit() {
this.showUnique = Types.is(this.field, RootFieldDto) && !this.field.isLocalizable;
if (this.showUnique) {
this.editForm.setControl('isUnique',
new FormControl(this.properties.isUnique));
}
this.editForm.setControl('maxLength',
new FormControl(this.properties.maxLength));

2
src/Squidex/app/shared/services/schemas.types.ts

@ -241,6 +241,7 @@ export class NumberFieldPropertiesDto extends FieldPropertiesDto {
public readonly fieldType = 'Number';
public readonly inlineEditable: boolean = false;
public readonly isUnique: boolean = false;
public readonly defaultValue?: number;
public readonly maxValue?: number;
public readonly minValue?: number;
@ -279,6 +280,7 @@ export class StringFieldPropertiesDto extends FieldPropertiesDto {
public readonly fieldType = 'String';
public readonly inlineEditable = false;
public readonly isUnique: boolean = false;
public readonly defaultValue?: string;
public readonly pattern?: string;
public readonly patternMessage?: string;

19
src/Squidex/app/shared/state/contents.forms.ts

@ -374,24 +374,33 @@ export class EditContentForm extends Form<FormGroup> {
this.findArrayItemForm(field, language).removeAt(index);
}
public insertArrayItem(field: RootFieldDto, language: AppLanguageDto) {
public insertArrayItem(field: RootFieldDto, language: AppLanguageDto, source?: FormGroup) {
if (field.nested.length > 0) {
const formControl = this.findArrayItemForm(field, language);
this.addArrayItem(field, language, formControl);
this.addArrayItem(field, language, formControl, source);
}
}
private addArrayItem(field: RootFieldDto, language: AppLanguageDto | null, partitionForm: FormArray) {
private addArrayItem(field: RootFieldDto, language: AppLanguageDto | null, partitionForm: FormArray, source?: FormGroup) {
const itemForm = new FormGroup({});
let isOptional = field.isLocalizable && !!language && language.isOptional;
for (let nested of field.nested) {
const nestedValidators = FieldValidatorsFactory.createValidators(nested, isOptional);
const nestedDefault = FieldDefaultValue.get(nested);
itemForm.setControl(nested.name, new FormControl(nestedDefault, nestedValidators));
let value = FieldDefaultValue.get(nested);
if (source) {
const sourceField = source.get(nested.name);
if (sourceField) {
value = sourceField.value;
}
}
itemForm.setControl(nested.name, new FormControl(value, nestedValidators));
}
partitionForm.push(itemForm);

8
src/Squidex/app/theme/icomoon/demo-files/demo.css

@ -147,16 +147,16 @@ p {
font-size: 16px;
}
.fs1 {
font-size: 24px;
font-size: 28px;
}
.fs2 {
font-size: 32px;
font-size: 24px;
}
.fs3 {
font-size: 20px;
font-size: 24px;
}
.fs4 {
font-size: 32px;
font-size: 20px;
}
.fs5 {
font-size: 32px;

1240
src/Squidex/app/theme/icomoon/demo.html

File diff suppressed because it is too large

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.eot

Binary file not shown.

9
src/Squidex/app/theme/icomoon/fonts/icomoon.svg

@ -107,6 +107,15 @@
<glyph unicode="&#xe961;" glyph-name="drag2" d="M170 298.667v86h684v-86h-684zM854 554.667v-86h-684v86h684z" />
<glyph unicode="&#xe962;" glyph-name="control-Checkboxes" d="M384 771.657h614.4c14.157 0 25.6 11.443 25.6 25.6s-11.443 25.6-25.6 25.6h-614.4c-14.131 0-25.6-11.443-25.6-25.6s11.469-25.6 25.6-25.6zM998.4 464.457h-614.4c-14.131 0-25.6-11.443-25.6-25.6s11.469-25.6 25.6-25.6h614.4c14.157 0 25.6 11.443 25.6 25.6s-11.443 25.6-25.6 25.6zM998.4 106.057h-614.4c-38.406-15.539-22.811-37.543 0-51.2h614.4c14.157 0 25.6 11.443 25.6 25.6s-11.443 25.6-25.6 25.6zM0 950.857v-307.2h307.2v307.2zM47.4 903.457h212.4v-212.4h-212.4zM0 234.057v-307.2h307.2v307.2zM47.4 186.657h212.4v-212.4h-212.4zM0 592.457v-307.2h307.2v307.2zM47.4 545.057h212.4v-212.4h-212.4zM89.6 861.257h128v-128h-128v128z" />
<glyph unicode="&#xe963;" glyph-name="control-Tags" horiz-adv-x="1140" d="M498.787 620.534v49.548h112.31v66.065c0 49.548-39.639 89.187-89.187 89.187s-89.187-39.639-89.187-89.187v-541.729l89.187-161.858 89.187 161.858v426.116zM360.052 234.057h-66.065c-59.458 0-105.703 46.245-105.703 105.703v254.348c0 59.458 46.245 105.703 105.703 105.703h66.065v42.942h-66.065c-82.581 0-148.645-66.065-148.645-148.645v-254.348c0-82.581 66.065-148.645 148.645-148.645h66.065zM852.232 689.902c-26.426 33.032-66.065 52.852-109.006 52.852h-59.458v-42.942h39.639c42.942 0 82.581-19.819 109.006-52.852l145.342-181.677-142.039-178.374c-26.426-33.032-69.368-52.852-112.31-52.852h-36.335v-42.942h56.155c42.942 0 85.884 19.819 112.31 52.852l178.374 221.316z" />
<glyph unicode="&#xe964;" glyph-name="show" d="M256 857.6c-28.314 0-51.2-22.886-51.2-51.2v-256h51.2v256h307.2v-153.6c0-28.314 22.886-51.2 51.2-51.2h153.6v-512h-512v460.8h-51.2v-460.8c0-28.314 22.886-51.2 51.2-51.2h512c28.314 0 51.2 22.886 51.2 51.2v548.2l-219.8 219.8h-292.2zM614.4 770.2l117.4-117.4h-117.4zM408.906 372.28l-35.3 37 138.1 131.9 138-131.9-35.3-37-102.7 98.1zM511.706 186.88l-138.1 131.9 35.3 37 102.8-98.1 102.7 98.1 35.3-37z" />
<glyph unicode="&#xe965;" glyph-name="show-all" d="M256 857.6c-28.314 0-51.2-22.886-51.2-51.2v-256h51.2v256h307.2v-153.6c0-28.314 22.886-51.2 51.2-51.2h153.6v-512h-512v460.8h-51.2v-460.8c0-28.314 22.886-51.2 51.2-51.2h512c28.314 0 51.2 22.886 51.2 51.2v548.2l-219.8 219.8h-292.2zM614.4 770.2l117.4-117.4h-117.4zM348.394 944.012c-28.314 0-51.2-22.886-51.2-51.2v-23.7h51.2v23.7h307.2l204.8-204.8v-512h-23.8v-51.2h23.8c28.314 0 51.2 22.886 51.2 51.2v548.2l-219.8 219.8h-292.2zM408.906 372.28l-35.3 37 138.1 131.9 138-131.9-35.3-37-102.7 98.1zM511.706 186.88l-138.1 131.9 35.3 37 102.8-98.1 102.7 98.1 35.3-37z" />
<glyph unicode="&#xe966;" glyph-name="hide" d="M256 857.6c-28.314 0-51.2-22.886-51.2-51.2v-256h51.2v256h307.2v-153.6c0-28.314 22.886-51.2 51.2-51.2h153.6v-512h-512v460.8h-51.2v-460.8c0-28.314 22.886-51.2 51.2-51.2h512c28.314 0 51.2 22.886 51.2 51.2v548.2l-219.8 219.8h-292.2zM614.4 770.2l117.4-117.4h-117.4zM408.9 541.2l-35.3-37 138-131.9 138.1 131.9-35.3 37-102.8-98.1zM511.6 355.8l-138-131.9 35.3-37 102.7 98.1 102.8-98.1 35.3 37z" />
<glyph unicode="&#xe967;" glyph-name="hide-all" d="M408.9 541.2l-35.3-37 138.1-131.9 138 131.9-35.3 37-102.7-98.1zM511.7 355.8l-138.1-131.9 35.3-37 102.8 98.1 102.7-98.1 35.3 37zM256 857.6c-28.314 0-51.2-22.886-51.2-51.2v-256h51.2v256h307.2v-153.6c0-28.314 22.886-51.2 51.2-51.2h153.6v-512h-512v460.8h-51.2v-460.8c0-28.314 22.886-51.2 51.2-51.2h512c28.314 0 51.2 22.886 51.2 51.2v548.2l-219.8 219.8h-292.2zM614.4 770.2l117.4-117.4h-117.4zM348.394 944.012c-28.314 0-51.2-22.886-51.2-51.2v-23.7h51.2v23.7h307.2l204.8-204.8v-512h-23.8v-51.2h23.8c28.314 0 51.2 22.886 51.2 51.2v548.2l-219.8 219.8h-292.2z" />
<glyph unicode="&#xe968;" glyph-name="plus-square" d="M810.667 853.334h-597.333c-72.533 0-128-55.467-128-128v-597.333c0-72.533 55.467-128 128-128h597.333c72.533 0 128 55.467 128 128v597.333c0 72.533-55.467 128-128 128zM853.333 128c0-25.6-17.067-42.667-42.667-42.667h-597.333c-25.6 0-42.667 17.067-42.667 42.667v597.333c0 25.6 17.067 42.667 42.667 42.667h597.333c25.6 0 42.667-17.067 42.667-42.667v-597.333zM682.667 469.334h-128v128c0 25.6-17.067 42.667-42.667 42.667s-42.667-17.067-42.667-42.667v-128h-128c-25.6 0-42.667-17.067-42.667-42.667s17.067-42.667 42.667-42.667h128v-128c0-25.6 17.067-42.667 42.667-42.667s42.667 17.067 42.667 42.667v128h128c25.6 0 42.667 17.067 42.667 42.667s-17.067 42.667-42.667 42.667z" />
<glyph unicode="&#xe969;" glyph-name="minus-square" d="M810.667 853.334h-597.333c-72.533 0-128-55.467-128-128v-597.333c0-72.533 55.467-128 128-128h597.333c72.533 0 128 55.467 128 128v597.333c0 72.533-55.467 128-128 128zM853.333 128c0-25.6-17.067-42.667-42.667-42.667h-597.333c-25.6 0-42.667 17.067-42.667 42.667v597.333c0 25.6 17.067 42.667 42.667 42.667h597.333c25.6 0 42.667-17.067 42.667-42.667v-597.333zM682.667 469.334h-341.333c-25.6 0-42.667-17.067-42.667-42.667s17.067-42.667 42.667-42.667h341.333c25.6 0 42.667 17.067 42.667 42.667s-17.067 42.667-42.667 42.667z" />
<glyph unicode="&#xe96a;" glyph-name="clone" d="M950.857 18.286v621.714c0 9.714-8.571 18.286-18.286 18.286h-621.714c-9.714 0-18.286-8.571-18.286-18.286v-621.714c0-9.714 8.571-18.286 18.286-18.286h621.714c9.714 0 18.286 8.571 18.286 18.286zM1024 640v-621.714c0-50.286-41.143-91.429-91.429-91.429h-621.714c-50.286 0-91.429 41.143-91.429 91.429v621.714c0 50.286 41.143 91.429 91.429 91.429h621.714c50.286 0 91.429-41.143 91.429-91.429zM804.571 859.428v-91.429h-73.143v91.429c0 9.714-8.571 18.286-18.286 18.286h-621.714c-9.714 0-18.286-8.571-18.286-18.286v-621.714c0-9.714 8.571-18.286 18.286-18.286h91.429v-73.143h-91.429c-50.286 0-91.429 41.143-91.429 91.429v621.714c0 50.286 41.143 91.429 91.429 91.429h621.714c50.286 0 91.429-41.143 91.429-91.429z" />
<glyph unicode="&#xe96b;" glyph-name="caret-bottom" horiz-adv-x="585" d="M585.143 411.443c0-9.728-3.986-18.871-10.862-25.71l-256-256c-6.839-6.839-16.018-10.862-25.71-10.862s-18.871 3.986-25.71 10.862l-256 256c-6.839 6.839-10.862 16.018-10.862 25.71 0 20.005 16.567 36.571 36.571 36.571h512c20.005 0 36.571-16.567 36.571-36.571zM585.143 740.557c0-9.728-3.986-18.871-10.862-25.71l-256-256c-6.839-6.839-16.018-10.862-25.71-10.862s-18.871 3.986-25.71 10.862l-256 256c-6.839 6.839-10.862 16.018-10.862 25.71 0 20.005 16.567 36.571 36.571 36.571h512c20.005 0 36.571-16.567 36.571-36.571z" />
<glyph unicode="&#xe96c;" glyph-name="caret-top" horiz-adv-x="585" d="M585.143 155.423c0-20.005-16.567-36.571-36.571-36.571h-512c-20.005 0-36.571 16.567-36.571 36.571 0 9.728 3.986 18.871 10.862 25.71l256 256c6.839 6.839 16.018 10.862 25.71 10.862s18.871-3.986 25.71-10.862l256-256c6.839-6.839 10.862-16.018 10.862-25.71zM585.143 484.577c0-20.005-16.567-36.571-36.571-36.571h-512c-20.005 0-36.571 16.567-36.571 36.571 0 9.728 3.986 18.871 10.862 25.71l256 256c6.839 6.839 16.018 10.862 25.71 10.862s18.871-3.986 25.71-10.862l256-256c6.839-6.839 10.862-16.018 10.862-25.71z" />
<glyph unicode="&#xe9ca;" glyph-name="earth" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512-0.002c-62.958 0-122.872 13.012-177.23 36.452l233.148 262.29c5.206 5.858 8.082 13.422 8.082 21.26v96c0 17.674-14.326 32-32 32-112.99 0-232.204 117.462-233.374 118.626-6 6.002-14.14 9.374-22.626 9.374h-128c-17.672 0-32-14.328-32-32v-192c0-12.122 6.848-23.202 17.69-28.622l110.31-55.156v-187.886c-116.052 80.956-192 215.432-192 367.664 0 68.714 15.49 133.806 43.138 192h116.862c8.488 0 16.626 3.372 22.628 9.372l128 128c6 6.002 9.372 14.14 9.372 22.628v77.412c40.562 12.074 83.518 18.588 128 18.588 70.406 0 137.004-16.26 196.282-45.2-4.144-3.502-8.176-7.164-12.046-11.036-36.266-36.264-56.236-84.478-56.236-135.764s19.97-99.5 56.236-135.764c36.434-36.432 85.218-56.264 135.634-56.26 3.166 0 6.342 0.080 9.518 0.236 13.814-51.802 38.752-186.656-8.404-372.334-0.444-1.744-0.696-3.488-0.842-5.224-81.324-83.080-194.7-134.656-320.142-134.656z" />
<glyph unicode="&#xf00a;" glyph-name="grid" d="M292.571 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf0c9;" glyph-name="list1" horiz-adv-x="878" d="M877.714 182.857v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 475.428v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 768v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571z" />

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 87 KiB

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.ttf

Binary file not shown.

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.woff

Binary file not shown.

66
src/Squidex/app/theme/icomoon/icons/caret-bottom.svg

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generated by IcoMoon.io -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="16"
height="28"
viewBox="0 0 16 28"
id="svg6"
sodipodi:docname="caret-down.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1278"
inkscape:window-height="1008"
id="namedview8"
showgrid="false"
inkscape:zoom="2"
inkscape:cx="-140.31126"
inkscape:cy="32.033384"
inkscape:window-x="1913"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg6" />
<title
id="title2">caret-down</title>
<g
id="g3749"
transform="translate(0,-0.0039063)">
<path
inkscape:connector-curvature="0"
id="path4"
d="m 16,15.003503 c 0,0.266 -0.109,0.516 -0.297,0.703 l -7,7 c -0.187,0.187 -0.438,0.297 -0.703,0.297 -0.265,0 -0.516,-0.109 -0.703,-0.297 l -7,-7 C 0.11,15.519503 0,15.268503 0,15.003503 c 0,-0.547 0.453,-1 0.99999997,-1 H 15 c 0.547,0 1,0.453 1,1 z" />
<path
id="path4-7"
d="m 15.999999,6.0043096 c 0,0.266 -0.109,0.516 -0.297,0.703 L 8.7029995,13.707309 c -0.1869999,0.187 -0.438,0.297001 -0.7029999,0.297001 -0.2650001,0 -0.5160001,-0.109 -0.7030001,-0.297001 L 0.297,6.7073096 C 0.11,6.5203096 0,6.2693096 0,6.0043096 c 0,-0.547 0.453,-1 0.99999986,-1 H 14.999999 c 0.547,0 1,0.453 1,1 z"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

66
src/Squidex/app/theme/icomoon/icons/caret-top.svg

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generated by IcoMoon.io -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="16"
height="28"
viewBox="0 0 16 28"
id="svg6"
sodipodi:docname="caret-up.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1278"
inkscape:window-height="1008"
id="namedview8"
showgrid="false"
inkscape:zoom="16"
inkscape:cx="9.6379344"
inkscape:cy="17.322397"
inkscape:window-x="3193"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg6" />
<title
id="title2">caret-up</title>
<g
id="g3734"
transform="translate(0.06567746,3.0040716)">
<path
inkscape:connector-curvature="0"
id="path4"
d="m 15.934323,18.996094 c 0,0.547 -0.453,1 -1,1 H 0.93432208 c -0.547,0 -1,-0.453 -1,-1 0,-0.266 0.109,-0.516 0.297,-0.703 l 7.00000002,-7 c 0.187,-0.187 0.438,-0.297 0.703,-0.297 0.265,0 0.516,0.109 0.703,0.297 l 6.9999999,7 c 0.187001,0.187 0.297001,0.438 0.297001,0.703 z" />
<path
id="path4-0"
d="m 15.934323,9.9957628 c 0,0.5470002 -0.453,1.0000002 -1,1.0000002 H 0.93432253 c -0.54699995,0 -0.99999995,-0.453 -0.99999995,-1.0000002 0,-0.266 0.109,-0.516 0.297,-0.703 l 7.00000002,-7 c 0.187,-0.187 0.438,-0.297 0.7030002,-0.297 0.265,0 0.516,0.109 0.703,0.297 l 7.0000002,7 c 0.186999,0.187 0.297,0.438 0.297,0.703 z"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

70
src/Squidex/app/theme/icomoon/icons/hide-all.svg

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width="20px"
height="20px"
viewBox="0 0 20 20"
enable-background="new 0 0 20 20"
xml:space="preserve"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="hide-all.svg"><metadata
id="metadata17"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs15" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1017"
id="namedview13"
showgrid="false"
inkscape:zoom="22.627418"
inkscape:cx="13.001263"
inkscape:cy="11.856095"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1"
inkscape:snap-to-guides="true"
inkscape:snap-grids="false" /><path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 7.9863281,8.1796875 7.296875,8.9023438 9.9941406,11.478516 12.689453,8.9023438 12,8.1796875 9.9941406,10.095703 Z"
id="path4553-8"
inkscape:connector-curvature="0" /><path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 9.9941406,11.800781 7.296875,14.376953 7.9863281,15.099609 9.9941406,13.183594 12,15.099609 12.689453,14.376953 Z"
id="path4553-9-7"
inkscape:connector-curvature="0" /><g
id="g4728"
transform="translate(-1.4278535,-0.91235)"><path
clip-path="none"
mask="none"
sodipodi:nodetypes="ssccccsscccccccsscccssccccccscccc"
inkscape:connector-curvature="0"
id="path7-9"
d="m 6.4278534,2.912349 c -0.553,0 -1,0.447 -1,1 V 8.91235 h 1 V 3.912349 H 12.427853 V 6.91235 c 0,0.553 0.447,1 1,1 h 3 v 10 h -4 -2.9999996 -3 v -9 h -1 v 9 c 0,0.553 0.447,1 1,1 h 1 2 2.9999996 4 c 0.553,0 1,-0.447 1,-1 V 9.9123499 7.91235 7.205319 L 13.134884,2.912349 H 12.427853 7.4278534 Z m 6.9999996,1.707032 2.292969,2.292969 h -2.292969 z"
style="display:inline;fill:#000000" /><path
id="path7-9-8"
d="m 8.2324219,1.2246094 c -0.553,0 -1,0.447 -1,1 V 2.6875 h 1 V 2.2246094 h 6.0000001 l 4,4 v 9.9140626 0.08594 h -0.464844 v 1 h 0.464844 c 0.553,0 1,-0.447 1,-1 v -7.9999996 -2 -0.7070313 L 14.939453,1.2246094 H 14.232422 9.2324219 Z"
clip-path="none"
mask="none"
style="display:inline;fill:#000000"
inkscape:connector-curvature="0" /></g></svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

62
src/Squidex/app/theme/icomoon/icons/hide.svg

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width="20px"
height="20px"
viewBox="0 0 20 20"
enable-background="new 0 0 20 20"
xml:space="preserve"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="hide.svg"><metadata
id="metadata17"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs15" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1017"
id="namedview13"
showgrid="false"
inkscape:zoom="22.627417"
inkscape:cx="5.5837903"
inkscape:cy="15.957992"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1"
inkscape:snap-to-guides="true"
inkscape:snap-grids="false" /><path
style="display:inline;fill:#000000"
d="m 4.9999999,1.9999998 c -0.553,0 -1,0.447 -1,1 v 5.0000004 h 1 V 2.9999998 H 11 v 3.0000004 c 0,0.553 0.447,1 1,1 h 3 V 17 h -4 -3.0000001 -3 V 8.0000002 h -1 V 17 c 0,0.553 0.447,1 1,1 h 1 2 H 11 15 c 0.553,0 1,-0.447 1,-1 v -7.9999998 -2 V 6.292969 L 11.707031,1.9999998 H 11 5.9999999 Z M 12,3.7070315 14.292969,6.0000002 H 12 Z"
id="path7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ssccccsscccccccsscccssccccccscccc"
mask="none"
clip-path="none" /><path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 7.9863281,8.1796875 7.296875,8.9023438 9.9921875,11.478516 12.689453,8.9023438 12,8.1796875 9.9921875,10.095703 Z"
id="path4553-8"
inkscape:connector-curvature="0" /><path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 9.9921875,11.800781 7.296875,14.376953 7.9863281,15.099609 9.9921875,13.183594 12,15.099609 12.689453,14.376953 Z"
id="path4553-9-7"
inkscape:connector-curvature="0" /></svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

70
src/Squidex/app/theme/icomoon/icons/show-all.svg

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width="20px"
height="20px"
viewBox="0 0 20 20"
enable-background="new 0 0 20 20"
xml:space="preserve"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="show-all.svg"><metadata
id="metadata17"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs15" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1017"
id="namedview13"
showgrid="false"
inkscape:zoom="11.313709"
inkscape:cx="9.0602094"
inkscape:cy="14.082047"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1"
inkscape:snap-to-guides="true"
inkscape:snap-grids="false" /><g
id="g4728"
transform="translate(-1.4278535,-0.91235)"><path
clip-path="none"
mask="none"
sodipodi:nodetypes="ssccccsscccccccsscccssccccccscccc"
inkscape:connector-curvature="0"
id="path7-9"
d="m 6.4278534,2.912349 c -0.553,0 -1,0.447 -1,1 V 8.91235 h 1 V 3.912349 H 12.427853 V 6.91235 c 0,0.553 0.447,1 1,1 h 3 v 10 h -4 -2.9999996 -3 v -9 h -1 v 9 c 0,0.553 0.447,1 1,1 h 1 2 2.9999996 4 c 0.553,0 1,-0.447 1,-1 V 9.9123499 7.91235 7.205319 L 13.134884,2.912349 H 12.427853 7.4278534 Z m 6.9999996,1.707032 2.292969,2.292969 h -2.292969 z"
style="display:inline;fill:#000000" /><path
id="path7-9-8"
d="m 8.2324219,1.2246094 c -0.553,0 -1,0.447 -1,1 V 2.6875 h 1 V 2.2246094 h 6.0000001 l 4,4 v 9.9140626 0.08594 h -0.464844 v 1 h 0.464844 c 0.553,0 1,-0.447 1,-1 v -7.9999996 -2 -0.7070313 L 14.939453,1.2246094 H 14.232422 9.2324219 Z"
clip-path="none"
mask="none"
style="display:inline;fill:#000000"
inkscape:connector-curvature="0" /></g><path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 7.9864531,11.478907 7.297,10.756251 9.9942654,8.1800781 12.689578,10.756251 12.000125,11.478907 9.9942654,9.5628911 Z"
id="path4553-8"
inkscape:connector-curvature="0" /><path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 9.9942654,15.1 7.297,12.523828 l 0.6894531,-0.722656 2.0078123,1.916015 2.0058596,-1.916015 0.689453,0.722656 z"
id="path4553-9-7"
inkscape:connector-curvature="0" /></svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

62
src/Squidex/app/theme/icomoon/icons/show.svg

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width="20px"
height="20px"
viewBox="0 0 20 20"
enable-background="new 0 0 20 20"
xml:space="preserve"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="show.svg"><metadata
id="metadata17"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs15" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1017"
id="namedview13"
showgrid="false"
inkscape:zoom="22.627417"
inkscape:cx="5.5837903"
inkscape:cy="14.190225"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1"
inkscape:snap-to-guides="true"
inkscape:snap-grids="false" /><path
style="display:inline;fill:#000000"
d="m 4.9999999,1.9999998 c -0.553,0 -1,0.447 -1,1 v 5.0000004 h 1 V 2.9999998 H 11 v 3.0000004 c 0,0.553 0.447,1 1,1 h 3 V 17 h -4 -3.0000001 -3 V 8.0000002 h -1 V 17 c 0,0.553 0.447,1 1,1 h 1 2 H 11 15 c 0.553,0 1,-0.447 1,-1 v -7.9999998 -2 V 6.292969 L 11.707031,1.9999998 H 11 5.9999999 Z M 12,3.7070315 14.292969,6.0000002 H 12 Z"
id="path7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ssccccsscccccccsscccssccccccscccc"
mask="none"
clip-path="none" /><path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 7.9864527,11.478907 7.297,10.756251 9.9942657,8.1800785 12.689578,10.756251 12.000125,11.478907 9.9942657,9.5628915 Z"
id="path4553-8"
inkscape:connector-curvature="0" /><path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 9.9942657,15.1 7.297,12.523828 l 0.6894527,-0.722656 2.007813,1.916015 2.0058593,-1.916015 0.689453,0.722656 z"
id="path4553-9-7"
inkscape:connector-curvature="0" /></svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

2
src/Squidex/app/theme/icomoon/selection.json

File diff suppressed because one or more lines are too long

217
src/Squidex/app/theme/icomoon/style.css

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?2tmnw3');
src: url('fonts/icomoon.eot?2tmnw3#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?2tmnw3') format('truetype'),
url('fonts/icomoon.woff?2tmnw3') format('woff'),
url('fonts/icomoon.svg?2tmnw3#icomoon') format('svg');
src: url('fonts/icomoon.eot?vexzhv');
src: url('fonts/icomoon.eot?vexzhv#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?vexzhv') format('truetype'),
url('fonts/icomoon.woff?vexzhv') format('woff'),
url('fonts/icomoon.svg?vexzhv#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@ -24,6 +24,105 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-clone:before {
content: "\e96a";
}
.icon-control-Tags:before {
content: "\e963";
}
.icon-control-Checkboxes:before {
content: "\e962";
}
.icon-control-Html:before {
content: "\e960";
}
.icon-single-content:before {
content: "\e958";
}
.icon-multiple-content:before {
content: "\e957";
}
.icon-type-Array:before {
content: "\e956";
}
.icon-exclamation:before {
content: "\e955";
}
.icon-orleans:before {
content: "\e94b";
}
.icon-document-lock:before {
content: "\e949";
}
.icon-document-unpublish:before {
content: "\e93f";
}
.icon-angle-down:before {
content: "\e900";
}
.icon-angle-left:before {
content: "\e901";
}
.icon-angle-right:before {
content: "\e931";
}
.icon-angle-up:before {
content: "\e903";
}
.icon-api:before {
content: "\e945";
}
.icon-assets:before {
content: "\e948";
}
.icon-bug:before {
content: "\e93d";
}
.icon-caret-down:before {
content: "\e92c";
}
.icon-caret-left:before {
content: "\e92a";
}
.icon-caret-right:before {
content: "\e929";
}
.icon-caret-up:before {
content: "\e92b";
}
.icon-contents:before {
content: "\e946";
}
.icon-trigger-ContentChanged:before {
content: "\e946";
}
.icon-control-Date:before {
content: "\e936";
}
.icon-control-DateTime:before {
content: "\e937";
}
.icon-control-Markdown:before {
content: "\e938";
}
.icon-grid:before {
content: "\f00a";
}
.icon-list1:before {
content: "\f0c9";
}
.icon-user-o:before {
content: "\e932";
}
.icon-rules:before {
content: "\e947";
}
.icon-minus-square:before {
content: "\e969";
}
.icon-plus-square:before {
content: "\e968";
}
.icon-drag2:before {
content: "\e961";
}
@ -42,6 +141,24 @@
.icon-download:before {
content: "\e93e";
}
.icon-caret-bottom:before {
content: "\e96b";
}
.icon-caret-top:before {
content: "\e96c";
}
.icon-show:before {
content: "\e964";
}
.icon-show-all:before {
content: "\e965";
}
.icon-hide:before {
content: "\e966";
}
.icon-hide-all:before {
content: "\e967";
}
.icon-spinner2:before {
content: "\e959";
}
@ -276,93 +393,3 @@
.icon-user:before {
content: "\e928";
}
.icon-control-Tags:before {
content: "\e963";
}
.icon-control-Checkboxes:before {
content: "\e962";
}
.icon-control-Html:before {
content: "\e960";
}
.icon-single-content:before {
content: "\e958";
}
.icon-multiple-content:before {
content: "\e957";
}
.icon-type-Array:before {
content: "\e956";
}
.icon-exclamation:before {
content: "\e955";
}
.icon-orleans:before {
content: "\e94b";
}
.icon-document-lock:before {
content: "\e949";
}
.icon-document-unpublish:before {
content: "\e93f";
}
.icon-angle-down:before {
content: "\e900";
}
.icon-angle-left:before {
content: "\e901";
}
.icon-angle-right:before {
content: "\e931";
}
.icon-angle-up:before {
content: "\e903";
}
.icon-api:before {
content: "\e945";
}
.icon-assets:before {
content: "\e948";
}
.icon-bug:before {
content: "\e93d";
}
.icon-caret-down:before {
content: "\e92c";
}
.icon-caret-left:before {
content: "\e92a";
}
.icon-caret-right:before {
content: "\e929";
}
.icon-caret-up:before {
content: "\e92b";
}
.icon-contents:before {
content: "\e946";
}
.icon-trigger-ContentChanged:before {
content: "\e946";
}
.icon-control-Date:before {
content: "\e936";
}
.icon-control-DateTime:before {
content: "\e937";
}
.icon-control-Markdown:before {
content: "\e938";
}
.icon-grid:before {
content: "\f00a";
}
.icon-list1:before {
content: "\f0c9";
}
.icon-user-o:before {
content: "\e932";
}
.icon-rules:before {
content: "\e947";
}

10
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs

@ -48,7 +48,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_tags_are_required_and_null()
public async Task Should_add_error_if_tags_are_required_and_null()
{
var sut = Field(new ArrayFieldProperties { IsRequired = true });
@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_tags_are_required_and_empty()
public async Task Should_add_error_if_tags_are_required_and_empty()
{
var sut = Field(new ArrayFieldProperties { IsRequired = true });
@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_is_not_valid()
public async Task Should_add_error_if_value_is_not_valid()
{
var sut = Field(new ArrayFieldProperties());
@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_has_not_enough_items()
public async Task Should_add_error_if_value_has_not_enough_items()
{
var sut = Field(new ArrayFieldProperties { MinItems = 3 });
@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_has_too_much_items()
public async Task Should_add_error_if_value_has_too_much_items()
{
var sut = Field(new ArrayFieldProperties { MaxItems = 1 });

12
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs

@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_assets_are_required_and_null()
public async Task Should_add_error_if_assets_are_required_and_null()
{
var sut = Field(new AssetsFieldProperties { IsRequired = true });
@ -104,7 +104,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_assets_are_required_and_empty()
public async Task Should_add_error_if_assets_are_required_and_empty()
{
var sut = Field(new AssetsFieldProperties { IsRequired = true });
@ -115,7 +115,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_is_not_valid()
public async Task Should_add_error_if_value_is_not_valid()
{
var sut = Field(new AssetsFieldProperties());
@ -126,7 +126,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_has_not_enough_items()
public async Task Should_add_error_if_value_has_not_enough_items()
{
var sut = Field(new AssetsFieldProperties { MinItems = 3 });
@ -137,7 +137,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_has_too_much_items()
public async Task Should_add_error_if_value_has_too_much_items()
{
var sut = Field(new AssetsFieldProperties { MaxItems = 1 });
@ -148,7 +148,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_asset_are_not_valid()
public async Task Should_add_error_if_asset_are_not_valid()
{
var assetId = Guid.NewGuid();

4
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs

@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_boolean_is_required()
public async Task Should_add_error_if_boolean_is_required()
{
var sut = Field(new BooleanFieldProperties { IsRequired = true });
@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_is_not_valid()
public async Task Should_add_error_if_value_is_not_valid()
{
var sut = Field(new BooleanFieldProperties());

10
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs

@ -39,7 +39,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_datetime_is_required()
public async Task Should_add_error_if_datetime_is_required()
{
var sut = Field(new DateTimeFieldProperties { IsRequired = true });
@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_datetime_is_less_than_min()
public async Task Should_add_error_if_datetime_is_less_than_min()
{
var sut = Field(new DateTimeFieldProperties { MinValue = FutureDays(10) });
@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_datetime_is_greater_than_max()
public async Task Should_add_error_if_datetime_is_greater_than_max()
{
var sut = Field(new DateTimeFieldProperties { MaxValue = FutureDays(10) });
@ -72,7 +72,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_is_not_valid()
public async Task Should_add_error_if_value_is_not_valid()
{
var sut = Field(new DateTimeFieldProperties());
@ -83,7 +83,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_is_another_type()
public async Task Should_add_error_if_value_is_another_type()
{
var sut = Field(new DateTimeFieldProperties());

8
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs

@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_geolocation_has_invalid_latitude()
public async Task Should_add_error_if_geolocation_has_invalid_latitude()
{
var sut = Field(new GeolocationFieldProperties { IsRequired = true });
@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_geolocation_has_invalid_longitude()
public async Task Should_add_error_if_geolocation_has_invalid_longitude()
{
var sut = Field(new GeolocationFieldProperties { IsRequired = true });
@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_geolocation_has_too_many_properties()
public async Task Should_add_error_if_geolocation_has_too_many_properties()
{
var sut = Field(new GeolocationFieldProperties { IsRequired = true });
@ -97,7 +97,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_geolocation_is_required()
public async Task Should_add_error_if_geolocation_is_required()
{
var sut = Field(new GeolocationFieldProperties { IsRequired = true });

2
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs

@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_json_is_required()
public async Task Should_add_error_if_json_is_required()
{
var sut = Field(new JsonFieldProperties { IsRequired = true });

22
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
@ -38,7 +39,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_number_is_required()
public async Task Should_add_error_if_number_is_required()
{
var sut = Field(new NumberFieldProperties { IsRequired = true });
@ -49,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_number_is_less_than_min()
public async Task Should_add_error_if_number_is_less_than_min()
{
var sut = Field(new NumberFieldProperties { MinValue = 10 });
@ -60,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_number_is_greater_than_max()
public async Task Should_add_error_if_number_is_greater_than_max()
{
var sut = Field(new NumberFieldProperties { MaxValue = 10 });
@ -71,7 +72,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_number_is_not_allowed()
public async Task Should_add_error_if_number_is_not_allowed()
{
var sut = Field(new NumberFieldProperties { AllowedValues = ReadOnlyCollection.Create(10d) });
@ -82,7 +83,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_is_not_valid()
public async Task Should_add_error_if_value_is_not_valid()
{
var sut = Field(new NumberFieldProperties());
@ -92,6 +93,17 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
new[] { "Not a valid value." });
}
[Fact]
public async Task Should_add_error_if_unique_constraint_failed()
{
var sut = Field(new NumberFieldProperties { IsUnique = true });
await sut.ValidateAsync(CreateValue(12.5), errors, ValidationTestExtensions.References(Guid.NewGuid()));
errors.Should().BeEquivalentTo(
new[] { "Another content with the same value exists." });
}
private static IJsonValue CreateValue(double v)
{
return JsonValue.Create(v);

26
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs

@ -20,6 +20,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
private readonly List<string> errors = new List<string>();
private readonly Guid schemaId = Guid.NewGuid();
private readonly Guid ref1 = Guid.NewGuid();
private readonly Guid ref2 = Guid.NewGuid();
[Fact]
public void Should_instantiate_field()
@ -34,7 +36,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new ReferencesFieldProperties());
await sut.ValidateAsync(CreateValue(Guid.NewGuid()), errors, ValidationTestExtensions.ValidContext);
await sut.ValidateAsync(CreateValue(ref1), errors, ValidationTestExtensions.References(ref1));
Assert.Empty(errors);
}
@ -50,7 +52,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_references_are_required_and_null()
public async Task Should_add_error_if_references_are_required_and_null()
{
var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, IsRequired = true });
@ -61,7 +63,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_references_are_required_and_empty()
public async Task Should_add_error_if_references_are_required_and_empty()
{
var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, IsRequired = true });
@ -72,7 +74,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_is_not_valid()
public async Task Should_add_error_if_value_is_not_valid()
{
var sut = Field(new ReferencesFieldProperties());
@ -83,38 +85,36 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_has_not_enough_items()
public async Task Should_add_error_if_value_has_not_enough_items()
{
var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, MinItems = 3 });
await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors);
await sut.ValidateAsync(CreateValue(ref1, ref2), errors, ValidationTestExtensions.References(ref1, ref2));
errors.Should().BeEquivalentTo(
new[] { "Must have at least 3 item(s)." });
}
[Fact]
public async Task Should_add_errors_if_value_has_too_much_items()
public async Task Should_add_error_if_value_has_too_much_items()
{
var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, MaxItems = 1 });
await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors);
await sut.ValidateAsync(CreateValue(ref1, ref2), errors, ValidationTestExtensions.References(ref1, ref2));
errors.Should().BeEquivalentTo(
new[] { "Must have not more than 1 item(s)." });
}
[Fact]
public async Task Should_add_errors_if_reference_are_not_valid()
public async Task Should_add_error_if_reference_are_not_valid()
{
var referenceId = Guid.NewGuid();
var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId });
await sut.ValidateAsync(CreateValue(referenceId), errors, ValidationTestExtensions.InvalidReferences(referenceId));
await sut.ValidateAsync(CreateValue(ref1), errors, ValidationTestExtensions.References());
errors.Should().BeEquivalentTo(
new[] { $"Contains invalid reference '{referenceId}'." });
new[] { $"Contains invalid reference '{ref1}'." });
}
private static IJsonValue CreateValue(params Guid[] ids)

24
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
@ -38,7 +39,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_string_is_required()
public async Task Should_add_error_if_string_is_required()
{
var sut = Field(new StringFieldProperties { IsRequired = true });
@ -49,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_string_is_shorter_than_min_length()
public async Task Should_add_error_if_string_is_shorter_than_min_length()
{
var sut = Field(new StringFieldProperties { MinLength = 10 });
@ -60,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_string_is_longer_than_max_length()
public async Task Should_add_error_if_string_is_longer_than_max_length()
{
var sut = Field(new StringFieldProperties { MaxLength = 5 });
@ -71,7 +72,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_string_not_allowed()
public async Task Should_add_error_if_string_not_allowed()
{
var sut = Field(new StringFieldProperties { AllowedValues = ReadOnlyCollection.Create("Foo") });
@ -82,7 +83,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_number_is_not_valid_pattern()
public async Task Should_add_error_if_number_is_not_valid_pattern()
{
var sut = Field(new StringFieldProperties { Pattern = "[0-9]{3}" });
@ -93,7 +94,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_number_is_not_valid_pattern_with_message()
public async Task Should_add_error_if_number_is_not_valid_pattern_with_message()
{
var sut = Field(new StringFieldProperties { Pattern = "[0-9]{3}", PatternMessage = "Custom Error Message." });
@ -103,6 +104,17 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
new[] { "Custom Error Message." });
}
[Fact]
public async Task Should_add_error_if_unique_constraint_failed()
{
var sut = Field(new StringFieldProperties { IsUnique = true });
await sut.ValidateAsync(CreateValue("abc"), errors, ValidationTestExtensions.References(Guid.NewGuid()));
errors.Should().BeEquivalentTo(
new[] { "Another content with the same value exists." });
}
private static IJsonValue CreateValue(string v)
{
return JsonValue.Create(v);

12
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs

@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_tags_are_required_and_null()
public async Task Should_add_error_if_tags_are_required_and_null()
{
var sut = Field(new TagsFieldProperties { IsRequired = true });
@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_tags_are_required_and_empty()
public async Task Should_add_error_if_tags_are_required_and_empty()
{
var sut = Field(new TagsFieldProperties { IsRequired = true });
@ -71,7 +71,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_is_not_valid()
public async Task Should_add_error_if_value_is_not_valid()
{
var sut = Field(new TagsFieldProperties());
@ -82,7 +82,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_has_not_enough_items()
public async Task Should_add_error_if_value_has_not_enough_items()
{
var sut = Field(new TagsFieldProperties { MinItems = 3 });
@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_has_too_much_items()
public async Task Should_add_error_if_value_has_too_much_items()
{
var sut = Field(new TagsFieldProperties { MaxItems = 1 });
@ -104,7 +104,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
}
[Fact]
public async Task Should_add_errors_if_value_contains_an_not_allowed_values()
public async Task Should_add_error_if_value_contains_an_not_allowed_values()
{
var sut = Field(new TagsFieldProperties { AllowedValues = ReadOnlyCollection.Create("tag-2", "tag-3") });

14
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs

@ -18,10 +18,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
public static class ValidationTestExtensions
{
private static readonly Task<IReadOnlyList<Guid>> ValidReferences = Task.FromResult<IReadOnlyList<Guid>>(new List<Guid>());
private static readonly Task<IReadOnlyList<IAssetInfo>> ValidAssets = Task.FromResult<IReadOnlyList<IAssetInfo>>(new List<IAssetInfo>());
private static readonly Task<IReadOnlyList<Guid>> EmptyReferences = Task.FromResult<IReadOnlyList<Guid>>(new List<Guid>());
private static readonly Task<IReadOnlyList<IAssetInfo>> EmptyAssets = Task.FromResult<IReadOnlyList<IAssetInfo>>(new List<IAssetInfo>());
public static readonly ValidationContext ValidContext = new ValidationContext((x, y) => ValidReferences, x => ValidAssets);
public static readonly ValidationContext ValidContext = new ValidationContext(Guid.NewGuid(), Guid.NewGuid(), (x, y) => EmptyReferences, x => EmptyAssets);
public static Task ValidateAsync(this IValidator validator, object value, IList<string> errors, ValidationContext context = null)
{
@ -75,14 +75,14 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var actual = Task.FromResult<IReadOnlyList<IAssetInfo>>(assets.ToList());
return new ValidationContext((x, y) => ValidReferences, x => actual);
return new ValidationContext(Guid.NewGuid(), Guid.NewGuid(), (x, y) => EmptyReferences, x => actual);
}
public static ValidationContext InvalidReferences(Guid referencesIds)
public static ValidationContext References(params Guid[] referencesIds)
{
var actual = Task.FromResult<IReadOnlyList<Guid>>(new List<Guid> { referencesIds });
var actual = Task.FromResult<IReadOnlyList<Guid>>(referencesIds.ToList());
return new ValidationContext((x, y) => actual, x => ValidAssets);
return new ValidationContext(Guid.NewGuid(), Guid.NewGuid(), (x, y) => actual, x => EmptyAssets);
}
}
}

93
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValidatorTests.cs

@ -0,0 +1,93 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Xunit;
namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators
{
public class UniqueValidatorTests
{
private readonly List<string> errors = new List<string>();
private readonly Guid contentId = Guid.NewGuid();
private readonly Guid schemaId = Guid.NewGuid();
[Fact]
public async Task Should_add_error_if_string_value_not_found()
{
var sut = new UniqueValidator();
var filter = string.Empty;
await sut.ValidateAsync("hi", errors, Context(Guid.NewGuid(), f => filter = f));
errors.Should().BeEquivalentTo(
new[] { "property: Another content with the same value exists." });
Assert.Equal("Data.property.iv == 'hi'", filter);
}
[Fact]
public async Task Should_add_error_if_double_value_not_found()
{
var sut = new UniqueValidator();
var filter = string.Empty;
await sut.ValidateAsync(12.5, errors, Context(Guid.NewGuid(), f => filter = f));
errors.Should().BeEquivalentTo(
new[] { "property: Another content with the same value exists." });
Assert.Equal("Data.property.iv == 12.5", filter);
}
[Fact]
public async Task Should_not_add_error_if_string_value_found()
{
var sut = new UniqueValidator();
var filter = string.Empty;
await sut.ValidateAsync("hi", errors, Context(contentId, f => filter = f));
Assert.Empty(errors);
}
[Fact]
public async Task Should_not_add_error_if_double_value_found()
{
var sut = new UniqueValidator();
var filter = string.Empty;
await sut.ValidateAsync(12.5, errors, Context(contentId, f => filter = f));
Assert.Empty(errors);
}
private ValidationContext Context(Guid id, Action<string> filter)
{
return new ValidationContext(contentId, schemaId,
(schema, filterNode) =>
{
filter(filterNode.ToString());
return Task.FromResult<IReadOnlyList<Guid>>(new List<Guid> { id });
},
ids =>
{
return Task.FromResult<IReadOnlyList<IAssetInfo>>(new List<IAssetInfo>());
}).Nested("property").Nested("iv");
}
}
}
Loading…
Cancel
Save