diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs index 3447e9271..88a11c699 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs @@ -7,6 +7,7 @@ using Squidex.Infrastructure; using System.Collections.Generic; +using System.Linq; using NamedIdStatic = Squidex.Infrastructure.NamedId; namespace Squidex.Domain.Apps.Core.Schemas @@ -18,6 +19,26 @@ namespace Squidex.Domain.Apps.Core.Schemas return NamedIdStatic.Of(field.Id, field.Name); } + public static IEnumerable NonHidden(this FieldCollection fields, bool withHidden = false) where T : IField + { + return fields.Ordered.ForApi(withHidden); + } + + public static IEnumerable ForApi(this IEnumerable fields, bool withHidden = false) where T : IField + { + return fields.Where(x => IsForApi(x, withHidden)); + } + + public static bool IsForApi(this T field, bool withHidden = false) where T : IField + { + return (withHidden || !field.IsHidden) && field.RawProperties.IsForApi(); + } + + public static bool IsForApi(this T properties) where T : FieldProperties + { + return !(properties is UIFieldProperties); + } + public static Schema ReorderFields(this Schema schema, List ids, long? parentId = null) { if (parentId != null) diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs index 2ae68dabe..1938ad663 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs @@ -66,6 +66,11 @@ namespace Squidex.Domain.Apps.Core.Schemas return new RootField(id, name, partitioning, properties, settings); } + public static RootField UI(long id, string name, Partitioning partitioning, UIFieldProperties properties = null, IFieldSettings settings = null) + { + return new RootField(id, name, partitioning, properties, settings); + } + public static NestedField Assets(long id, string name, AssetsFieldProperties properties = null, IFieldSettings settings = null) { return new NestedField(id, name, properties, settings); @@ -111,6 +116,11 @@ namespace Squidex.Domain.Apps.Core.Schemas return new NestedField(id, name, properties, settings); } + public static NestedField UI(long id, string name, UIFieldProperties properties = null, IFieldSettings settings = null) + { + return new NestedField(id, name, properties, settings); + } + public static Schema AddArray(this Schema schema, long id, string name, Partitioning partitioning, Func handler = null, ArrayFieldProperties properties = null, IFieldSettings settings = null) { var field = Array(id, name, partitioning, properties, settings); @@ -168,6 +178,11 @@ namespace Squidex.Domain.Apps.Core.Schemas return schema.AddField(Tags(id, name, partitioning, properties, settings)); } + public static Schema AddUI(this Schema schema, long id, string name, Partitioning partitioning, UIFieldProperties properties = null, IFieldSettings settings = null) + { + return schema.AddField(UI(id, name, partitioning, properties, settings)); + } + public static ArrayField AddAssets(this ArrayField field, long id, string name, AssetsFieldProperties properties = null, IFieldSettings settings = null) { return field.AddField(Assets(id, name, properties, settings)); @@ -212,5 +227,10 @@ namespace Squidex.Domain.Apps.Core.Schemas { return field.AddField(Tags(id, name, properties, settings)); } + + public static ArrayField AddUI(this ArrayField field, long id, string name, UIFieldProperties properties = null, IFieldSettings settings = null) + { + return field.AddField(UI(id, name, properties, settings)); + } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldPropertiesVisitor.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldPropertiesVisitor.cs index c4593a450..652d6173b 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldPropertiesVisitor.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldPropertiesVisitor.cs @@ -28,5 +28,7 @@ namespace Squidex.Domain.Apps.Core.Schemas T Visit(StringFieldProperties properties); T Visit(TagsFieldProperties properties); + + T Visit(UIFieldProperties properties); } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldVisitor.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldVisitor.cs index 67142acc4..710560f6d 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldVisitor.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldVisitor.cs @@ -28,5 +28,7 @@ namespace Squidex.Domain.Apps.Core.Schemas T Visit(IField field); T Visit(IField field); + + T Visit(IField field); } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldEditor.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldEditor.cs new file mode 100644 index 000000000..7e52418cd --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldEditor.cs @@ -0,0 +1,14 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.Schemas +{ + public enum UIFieldEditor + { + Separator + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldProperties.cs new file mode 100644 index 000000000..3fd109ce8 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldProperties.cs @@ -0,0 +1,34 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.Schemas +{ + public sealed class UIFieldProperties : FieldProperties + { + public UIFieldEditor Editor { get; set; } + + public override T Accept(IFieldPropertiesVisitor visitor) + { + return visitor.Visit(this); + } + + public override T Accept(IFieldVisitor visitor, IField field) + { + return visitor.Visit((IField)field); + } + + public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) + { + return new NestedField(id, name, this, settings); + } + + public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) + { + return new RootField(id, name, partitioning, this, settings); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs index 1d3b67e74..253af69a7 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs @@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent public static FieldConverter ExcludeHidden() { - return (data, field) => field.IsHidden ? null : data; + return (data, field) => !field.IsForApi() ? null : data; } public static FieldConverter ExcludeChangedTypes() diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs index 2db70c052..583c287a5 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs @@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent public static ValueConverter ExcludeHidden() { - return (value, field) => field.IsHidden ? Value.Unset : value; + return (value, field) => !field.IsForApi() ? Value.Unset : value; } public static ValueConverter ExcludeChangedTypes() diff --git a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs index 22f88bb85..196225e64 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs @@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Core.EnrichContent var defaultValue = DefaultValueFactory.CreateDefaultValue(field, SystemClock.Instance.GetCurrentInstant()); - if (field.RawProperties.IsRequired || defaultValue.Type == JsonValueType.Null) + if (field.RawProperties.IsRequired || defaultValue == null || defaultValue.Type == JsonValueType.Null) { return; } diff --git a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs index 15c3b936b..d7a7a1f21 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs @@ -74,6 +74,11 @@ namespace Squidex.Domain.Apps.Core.EnrichContent return JsonValue.Array(); } + public IJsonValue Visit(IField field) + { + return JsonValue.Null; + } + public IJsonValue Visit(IField field) { if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Now) diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs index a7c459a13..b8c0e33e9 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs @@ -93,6 +93,11 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds return value; } + public IJsonValue Visit(IField field) + { + return value; + } + public IJsonValue Visit(IArrayField field) { return value; diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs index ed00417a0..b4373ac0d 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs @@ -101,5 +101,10 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { return Enumerable.Empty(); } + + public IEnumerable Visit(IField field) + { + return Enumerable.Empty(); + } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs b/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs index 45cbd8fd7..55fa21cb7 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs @@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema foreach (var field in schema.FieldsByName.Values) { - if (!withHidden && field.IsHidden) + if (!field.IsForApi(withHidden)) { continue; } diff --git a/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmTypeVisitor.cs b/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmTypeVisitor.cs index 388448792..1bcabde3a 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmTypeVisitor.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmTypeVisitor.cs @@ -90,6 +90,11 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema return CreatePrimitive(EdmPrimitiveTypeKind.String, field); } + public IEdmTypeReference Visit(IField field) + { + return null; + } + private static IEdmTypeReference CreatePrimitive(EdmPrimitiveTypeKind kind, IField field) { return EdmCoreModel.Instance.GetPrimitive(kind, !field.RawProperties.IsRequired); diff --git a/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/Builder.cs b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/Builder.cs new file mode 100644 index 000000000..203c393f1 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/Builder.cs @@ -0,0 +1,64 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using NJsonSchema; + +namespace Squidex.Domain.Apps.Core.GenerateJsonSchema +{ + public static class Builder + { + public static JsonSchema4 Object() + { + return new JsonSchema4 { Type = JsonObjectType.Object, AllowAdditionalItems = false }; + } + + public static JsonSchema4 Guid() + { + return new JsonSchema4 { Type = JsonObjectType.String, Format = JsonFormatStrings.Guid }; + } + + public static JsonSchema4 String() + { + return new JsonSchema4 { Type = JsonObjectType.String }; + } + + public static JsonProperty ArrayProperty(JsonSchema4 item) + { + return new JsonProperty { Type = JsonObjectType.Array, Item = item }; + } + + public static JsonProperty BooleanProperty() + { + return new JsonProperty { Type = JsonObjectType.Boolean }; + } + + public static JsonProperty DateTimeProperty(string description = null, bool isRequired = false) + { + return new JsonProperty { Type = JsonObjectType.Number, Format = JsonFormatStrings.DateTime, Description = description, IsRequired = isRequired }; + } + + public static JsonProperty GuidProperty(string description = null, bool isRequired = false) + { + return new JsonProperty { Type = JsonObjectType.String, Format = JsonFormatStrings.Guid, Description = description, IsRequired = isRequired }; + } + + public static JsonProperty NumberProperty(string description = null, bool isRequired = false) + { + return new JsonProperty { Type = JsonObjectType.Number, Description = description, IsRequired = isRequired }; + } + + public static JsonProperty ObjectProperty(JsonSchema4 item, string description = null, bool isRequired = false) + { + return new JsonProperty { Type = JsonObjectType.Object, Reference = item, Description = description, IsRequired = isRequired }; + } + + public static JsonProperty StringProperty(string description = null, bool isRequired = false) + { + return new JsonProperty { Type = JsonObjectType.String, Description = description, IsRequired = isRequired }; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/ContentSchemaBuilder.cs b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/ContentSchemaBuilder.cs index 452e0386f..c4743f347 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/ContentSchemaBuilder.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/ContentSchemaBuilder.cs @@ -24,33 +24,19 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema { Properties = { - ["id"] = CreateProperty($"The id of the {schemaName} content."), - ["data"] = CreateProperty($"The data of the {schemaName}.", dataSchema), - ["version"] = CreateProperty($"The version of the {schemaName}.", JsonObjectType.Number), - ["created"] = CreateProperty($"The date and time when the {schemaName} content has been created.", "date-time"), - ["createdBy"] = CreateProperty($"The user that has created the {schemaName} content."), - ["lastModified"] = CreateProperty($"The date and time when the {schemaName} content has been modified last.", "date-time"), - ["lastModifiedBy"] = CreateProperty($"The user that has updated the {schemaName} content last.") + ["id"] = Builder.GuidProperty($"The id of the {schemaName} content.", true), + ["data"] = Builder.ObjectProperty(dataSchema, $"The data of the {schemaName}.", true), + ["dataDraft"] = Builder.ObjectProperty(dataSchema, $"The draft data of the {schemaName}.", false), + ["version"] = Builder.NumberProperty($"The version of the {schemaName}.", true), + ["created"] = Builder.DateTimeProperty($"The date and time when the {schemaName} content has been created.", true), + ["createdBy"] = Builder.StringProperty($"The user that has created the {schemaName} content.", true), + ["lastModified"] = Builder.DateTimeProperty($"The date and time when the {schemaName} content has been modified last.", true), + ["lastModifiedBy"] = Builder.StringProperty($"The user that has updated the {schemaName} content last.", true) }, Type = JsonObjectType.Object }; return contentSchema; } - - private static JsonProperty CreateProperty(string description, JsonSchema4 dataSchema) - { - return new JsonProperty { Description = description, IsRequired = true, Type = JsonObjectType.Object, Reference = dataSchema }; - } - - private static JsonProperty CreateProperty(string description, JsonObjectType type) - { - return new JsonProperty { Description = description, IsRequired = true, Type = type }; - } - - private static JsonProperty CreateProperty(string description, string format = null) - { - return new JsonProperty { Description = description, Format = format, IsRequired = true, Type = JsonObjectType.String }; - } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonSchemaExtensions.cs b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonSchemaExtensions.cs index a11185325..c41f465d2 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonSchemaExtensions.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonSchemaExtensions.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Linq; using NJsonSchema; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; @@ -25,33 +24,38 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema var jsonTypeVisitor = new JsonTypeVisitor(schemaResolver); var jsonSchema = new JsonSchema4 { Type = JsonObjectType.Object }; - foreach (var field in schema.Fields.Where(x => !x.IsHidden)) + foreach (var field in schema.Fields.ForApi()) { - var partitionProperty = CreateProperty(field); - var partitionObject = new JsonSchema4 { Type = JsonObjectType.Object, AllowAdditionalProperties = false }; + var partitionObject = Builder.Object(); var partition = partitionResolver(field.Partitioning); foreach (var partitionItem in partition) { var partitionItemProperty = field.Accept(jsonTypeVisitor); - partitionItemProperty.Description = partitionItem.Name; - partitionItemProperty.IsRequired = field.RawProperties.IsRequired && !partitionItem.IsOptional; + if (partitionItemProperty != null) + { + partitionItemProperty.Description = partitionItem.Name; + partitionItemProperty.IsRequired = field.RawProperties.IsRequired && !partitionItem.IsOptional; - partitionObject.Properties.Add(partitionItem.Key, partitionItemProperty); + partitionObject.Properties.Add(partitionItem.Key, partitionItemProperty); + } } - partitionProperty.Reference = schemaResolver($"{schemaName}{field.Name.ToPascalCase()}Property", partitionObject); + if (partitionObject.Properties.Count > 0) + { + var propertyReference = schemaResolver($"{schemaName}{field.Name.ToPascalCase()}Property", partitionObject); - jsonSchema.Properties.Add(field.Name, partitionProperty); + jsonSchema.Properties.Add(field.Name, CreateProperty(field, propertyReference)); + } } return jsonSchema; } - public static JsonProperty CreateProperty(IField field) + public static JsonProperty CreateProperty(IField field, JsonSchema4 reference) { - var jsonProperty = new JsonProperty { IsRequired = field.RawProperties.IsRequired, Type = JsonObjectType.Object }; + var jsonProperty = Builder.ObjectProperty(reference); if (!string.IsNullOrWhiteSpace(field.RawProperties.Hints)) { @@ -62,6 +66,8 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema jsonProperty.Description = field.Name; } + jsonProperty.IsRequired = field.RawProperties.IsRequired; + return jsonProperty; } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs index 00ad5f60f..124b3eaec 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs @@ -7,7 +7,6 @@ using System; using System.Collections.ObjectModel; -using System.Linq; using NJsonSchema; using Squidex.Domain.Apps.Core.Schemas; @@ -24,164 +23,125 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema public JsonProperty Visit(IArrayField field) { - return CreateProperty(field, jsonProperty => + var item = Builder.Object(); + + foreach (var nestedField in field.Fields.ForApi()) { - var itemSchema = new JsonSchema4 - { - Type = JsonObjectType.Object - }; + var childProperty = nestedField.Accept(this); - foreach (var nestedField in field.Fields.Where(x => !x.IsHidden)) + if (childProperty != null) { - var childProperty = nestedField.Accept(this); - childProperty.Description = nestedField.RawProperties.Hints; childProperty.IsRequired = nestedField.RawProperties.IsRequired; - itemSchema.Properties.Add(nestedField.Name, childProperty); + item.Properties.Add(nestedField.Name, childProperty); } + } - jsonProperty.Type = JsonObjectType.Object; - jsonProperty.Item = itemSchema; - }); + return Builder.ArrayProperty(item); } public JsonProperty Visit(IField field) { - return CreateProperty(field, jsonProperty => - { - var itemSchema = schemaResolver("AssetItem", new JsonSchema4 { Type = JsonObjectType.String }); + var item = schemaResolver("AssetItem", Builder.Guid()); - jsonProperty.Type = JsonObjectType.Array; - jsonProperty.Item = itemSchema; - }); + return Builder.ArrayProperty(item); } public JsonProperty Visit(IField field) { - return CreateProperty(field, jsonProperty => - { - jsonProperty.Type = JsonObjectType.Boolean; - }); + return Builder.BooleanProperty(); } public JsonProperty Visit(IField field) { - return CreateProperty(field, jsonProperty => - { - jsonProperty.Type = JsonObjectType.String; - jsonProperty.Format = JsonFormatStrings.DateTime; - }); + return Builder.DateTimeProperty(); } public JsonProperty Visit(IField field) { - return CreateProperty(field, jsonProperty => - { - var geolocationSchema = new JsonSchema4 - { - AllowAdditionalProperties = false - }; + var geolocationSchema = Builder.Object(); - geolocationSchema.Properties.Add("latitude", new JsonProperty - { - Type = JsonObjectType.Number, - Minimum = -90, - Maximum = 90, - IsRequired = true - }); + geolocationSchema.Properties.Add("latitude", new JsonProperty + { + Type = JsonObjectType.Number, + Minimum = -90, + Maximum = 90, + IsRequired = true + }); - geolocationSchema.Properties.Add("longitude", new JsonProperty - { - Type = JsonObjectType.Number, - Minimum = -180, - Maximum = 180, - IsRequired = true - }); + geolocationSchema.Properties.Add("longitude", new JsonProperty + { + Type = JsonObjectType.Number, + Minimum = -180, + Maximum = 180, + IsRequired = true + }); - var schemaReference = schemaResolver("GeolocationDto", geolocationSchema); + var reference = schemaResolver("GeolocationDto", geolocationSchema); - jsonProperty.Type = JsonObjectType.Object; - jsonProperty.Reference = schemaReference; - }); + return Builder.ObjectProperty(reference); } public JsonProperty Visit(IField field) { - return CreateProperty(field, jsonProperty => - { - jsonProperty.Type = JsonObjectType.Object; - }); + return Builder.StringProperty(); } public JsonProperty Visit(IField field) { - return CreateProperty(field, jsonProperty => + var property = Builder.NumberProperty(); + + if (field.Properties.MinValue.HasValue) { - jsonProperty.Type = JsonObjectType.Number; + property.Minimum = (decimal)field.Properties.MinValue.Value; + } - if (field.Properties.MinValue.HasValue) - { - jsonProperty.Minimum = (decimal)field.Properties.MinValue.Value; - } + if (field.Properties.MaxValue.HasValue) + { + property.Maximum = (decimal)field.Properties.MaxValue.Value; + } - if (field.Properties.MaxValue.HasValue) - { - jsonProperty.Maximum = (decimal)field.Properties.MaxValue.Value; - } - }); + return property; } public JsonProperty Visit(IField field) { - return CreateProperty(field, jsonProperty => - { - var itemSchema = schemaResolver("ReferenceItem", new JsonSchema4 { Type = JsonObjectType.String }); + var item = schemaResolver("ReferenceItem", Builder.Guid()); - jsonProperty.Type = JsonObjectType.Array; - jsonProperty.Item = itemSchema; - }); + return Builder.ArrayProperty(item); } public JsonProperty Visit(IField field) { - return CreateProperty(field, jsonProperty => - { - jsonProperty.Type = JsonObjectType.String; + var property = Builder.StringProperty(); - jsonProperty.MinLength = field.Properties.MinLength; - jsonProperty.MaxLength = field.Properties.MaxLength; + property.MinLength = field.Properties.MinLength; + property.MaxLength = field.Properties.MaxLength; - if (field.Properties.AllowedValues != null) - { - var names = jsonProperty.EnumerationNames = jsonProperty.EnumerationNames ?? new Collection(); + if (field.Properties.AllowedValues != null) + { + var names = property.EnumerationNames = property.EnumerationNames ?? new Collection(); - foreach (var value in field.Properties.AllowedValues) - { - names.Add(value); - } + foreach (var value in field.Properties.AllowedValues) + { + names.Add(value); } - }); + } + + return property; } public JsonProperty Visit(IField field) { - return CreateProperty(field, jsonProperty => - { - var itemSchema = schemaResolver("TagsItem", new JsonSchema4 { Type = JsonObjectType.String }); + var item = schemaResolver("ReferenceItem", Builder.String()); - jsonProperty.Type = JsonObjectType.Array; - jsonProperty.Item = itemSchema; - }); + return Builder.ArrayProperty(item); } - private static JsonProperty CreateProperty(IField field, Action updater) + public JsonProperty Visit(IField field) { - var property = new JsonProperty { IsRequired = field.RawProperties.IsRequired }; - - updater(property); - - return property; + return null; } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs index e67652f9c..c2027c85b 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs @@ -21,7 +21,6 @@ namespace Squidex.Domain.Apps.Core.ValidateContent { public sealed class ContentValidator { - private static readonly ContentFieldData DefaultFieldData = new ContentFieldData(); private readonly Schema schema; private readonly PartitionResolver partitionResolver; private readonly ValidationContext context; @@ -35,6 +34,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent public ContentValidator(Schema schema, PartitionResolver partitionResolver, ValidationContext context) { Guard.NotNull(schema, nameof(schema)); + Guard.NotNull(context, nameof(context)); Guard.NotNull(partitionResolver, nameof(partitionResolver)); this.schema = schema; @@ -71,19 +71,19 @@ namespace Squidex.Domain.Apps.Core.ValidateContent { var fieldsValidators = new Dictionary(schema.Fields.Count); - foreach (var field in schema.FieldsByName) + foreach (var field in schema.Fields) { - fieldsValidators[field.Key] = (!field.Value.RawProperties.IsRequired, CreateFieldValidator(field.Value, isPartial)); + fieldsValidators[field.Name] = (!field.RawProperties.IsRequired, CreateFieldValidator(field, isPartial)); } - return new ObjectValidator(fieldsValidators, isPartial, "field", DefaultFieldData); + return new ObjectValidator(fieldsValidators, isPartial, "field"); } private IValidator CreateFieldValidator(IRootField field, bool isPartial) { var partitioning = partitionResolver(field.Partitioning); - var fieldValidator = new FieldValidator(ValidatorsFactory.CreateValidators(field).ToArray(), field); + var fieldValidator = field.CreateValidator(); var fieldsValidators = new Dictionary(); foreach (var partition in partitioning) @@ -91,11 +91,17 @@ namespace Squidex.Domain.Apps.Core.ValidateContent fieldsValidators[partition.Key] = (partition.IsOptional, fieldValidator); } - var isLanguage = field.Partitioning.Equals(Partitioning.Language); + return new AggregateValidator( + field.CreateBagValidator() + .Union(Enumerable.Repeat( + new ObjectValidator(fieldsValidators, isPartial, TypeName(field)), 1))); + } - var type = isLanguage ? "language" : "invariant value"; + private static string TypeName(IRootField field) + { + var isLanguage = field.Partitioning.Equals(Partitioning.Language); - return new ObjectValidator(fieldsValidators, isPartial, type, JsonValue.Null); + return isLanguage ? "language" : "invariant value"; } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Extensions.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Extensions.cs new file mode 100644 index 000000000..9da804a22 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Extensions.cs @@ -0,0 +1,31 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.ValidateContent.Validators; + +namespace Squidex.Domain.Apps.Core.ValidateContent +{ + public static class Extensions + { + public static FieldValidator CreateValidator(this IField field) + { + return new FieldValidator(CreateValueValidators(field), field); + } + + private static IEnumerable CreateValueValidators(IField field) + { + return FieldValueValidatorsFactory.CreateValidators(field); + } + + public static IEnumerable CreateBagValidator(this IField field) + { + return FieldBagValidatorsFactory.CreateValidators(field); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldBagValidatorsFactory.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldBagValidatorsFactory.cs new file mode 100644 index 000000000..79747c6b8 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldBagValidatorsFactory.cs @@ -0,0 +1,85 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.ValidateContent.Validators; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.ValidateContent +{ + public sealed class FieldBagValidatorsFactory : IFieldVisitor> + { + private static readonly FieldBagValidatorsFactory Instance = new FieldBagValidatorsFactory(); + + private FieldBagValidatorsFactory() + { + } + + public static IEnumerable CreateValidators(IField field) + { + Guard.NotNull(field, nameof(field)); + + return field.Accept(Instance); + } + + public IEnumerable Visit(IArrayField field) + { + yield break; + } + + public IEnumerable Visit(IField field) + { + yield break; + } + + public IEnumerable Visit(IField field) + { + yield break; + } + + public IEnumerable Visit(IField field) + { + yield break; + } + + public IEnumerable Visit(IField field) + { + yield break; + } + + public IEnumerable Visit(IField field) + { + yield break; + } + + public IEnumerable Visit(IField field) + { + yield break; + } + + public IEnumerable Visit(IField field) + { + yield break; + } + + public IEnumerable Visit(IField field) + { + yield break; + } + + public IEnumerable Visit(IField field) + { + yield break; + } + + public IEnumerable Visit(IField field) + { + yield return NoValueValidator.Instance; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldValueValidatorsFactory.cs similarity index 91% rename from src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs rename to src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldValueValidatorsFactory.cs index ae6351a91..a73183637 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/FieldValueValidatorsFactory.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Linq; using NodaTime; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.ValidateContent.Validators; @@ -16,11 +15,11 @@ using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ValidateContent { - public sealed class ValidatorsFactory : IFieldVisitor> + public sealed class FieldValueValidatorsFactory : IFieldVisitor> { - private static readonly ValidatorsFactory Instance = new ValidatorsFactory(); + private static readonly FieldValueValidatorsFactory Instance = new FieldValueValidatorsFactory(); - private ValidatorsFactory() + private FieldValueValidatorsFactory() { } @@ -42,10 +41,10 @@ namespace Squidex.Domain.Apps.Core.ValidateContent foreach (var nestedField in field.Fields) { - nestedSchema[nestedField.Name] = (false, new FieldValidator(nestedField.Accept(this).ToArray(), nestedField)); + nestedSchema[nestedField.Name] = (false, nestedField.CreateValidator()); } - yield return new CollectionItemValidator(new ObjectValidator(nestedSchema, false, "field", JsonValue.Null)); + yield return new CollectionItemValidator(new ObjectValidator(nestedSchema, false, "field")); } public IEnumerable Visit(IField field) @@ -183,5 +182,13 @@ namespace Squidex.Domain.Apps.Core.ValidateContent yield return new CollectionItemValidator(new RequiredStringValidator(true)); } + + public IEnumerable Visit(IField field) + { + if (field is INestedField) + { + yield return NoValueValidator.Instance; + } + } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs index fff4b75e7..867dd79ba 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs @@ -78,6 +78,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent throw new InvalidCastException("Invalid json type, expected string."); } + public object Visit(IField field) + { + return value; + } + public object Visit(IField field) { if (value.Type == JsonValueType.String) diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Undefined.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Undefined.cs new file mode 100644 index 000000000..a15507007 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Undefined.cs @@ -0,0 +1,24 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.ValidateContent +{ + public static class Undefined + { + public static readonly object Value = new object(); + + public static bool IsUndefined(this object other) + { + return ReferenceEquals(other, Value); + } + + public static bool IsNullOrUndefined(this object other) + { + return other == null || other.IsUndefined(); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs new file mode 100644 index 000000000..16b842801 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs @@ -0,0 +1,33 @@ +// ========================================================================== +// 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; + +namespace Squidex.Domain.Apps.Core.ValidateContent.Validators +{ + public sealed class AggregateValidator : IValidator + { + private readonly IValidator[] validators; + + public AggregateValidator(IEnumerable validators) + { + this.validators = validators?.ToArray(); + } + + public Task ValidateAsync(object value, ValidationContext context, AddError addError) + { + if (validators?.Length > 0) + { + return Task.WhenAll(validators.Select(x => x.ValidateAsync(value, context, addError))); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs index cc1fcdfca..6f0d4a63a 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs @@ -31,14 +31,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators public Task ValidateAsync(object value, ValidationContext context, AddError addError) { - if (value == null) - { - return TaskHelper.Done; - } - - var typedValue = (T)value; - - if (!allowedValues.Contains(typedValue)) + if (value != null && value is T typedValue && !allowedValues.Contains(typedValue)) { addError(context.Path, "Not an allowed value."); } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs index 40bf1a89d..eafa95d22 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs @@ -6,8 +6,10 @@ // ========================================================================== using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators @@ -17,9 +19,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators private readonly IValidator[] validators; private readonly IField field; - public FieldValidator(IValidator[] validators, IField field) + public FieldValidator(IEnumerable validators, IField field) { - this.validators = validators; + Guard.NotNull(field, nameof(field)); + + this.validators = validators.ToArray(); + this.field = field; } @@ -27,21 +32,35 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { try { - object typedValue = null; + var typedValue = value; - if (value is IJsonValue jsonValue) + if (value == null) { - typedValue = jsonValue.Type == JsonValueType.Null ? null : JsonValueConverter.ConvertValue(field, jsonValue); + typedValue = Undefined.Value; } - - var tasks = new List(); - - foreach (var validator in validators) + else if (value is IJsonValue jsonValue) { - tasks.Add(validator.ValidateAsync(typedValue, context, addError)); + if (jsonValue.Type == JsonValueType.Null) + { + typedValue = null; + } + else + { + typedValue = JsonValueConverter.ConvertValue(field, jsonValue); + } } - await Task.WhenAll(tasks); + if (validators?.Length > 0) + { + var tasks = new List(); + + foreach (var validator in validators) + { + tasks.Add(validator.ValidateAsync(typedValue, context, addError)); + } + + await Task.WhenAll(tasks); + } } catch { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs new file mode 100644 index 000000000..835a10d31 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs @@ -0,0 +1,30 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Threading.Tasks; + +namespace Squidex.Domain.Apps.Core.ValidateContent.Validators +{ + public sealed class NoValueValidator : IValidator + { + public static readonly NoValueValidator Instance = new NoValueValidator(); + + private NoValueValidator() + { + } + + public Task ValidateAsync(object value, ValidationContext context, AddError addError) + { + if (!value.IsUndefined()) + { + addError(context.Path, "Value must not be defined."); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs index 429962996..6c2b26246 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs @@ -12,21 +12,25 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { public sealed class ObjectValidator : IValidator { + private static readonly IReadOnlyDictionary DefaultValue = new Dictionary(); private readonly IDictionary schema; private readonly bool isPartial; private readonly string fieldType; - private readonly TValue fieldDefault; - public ObjectValidator(IDictionary schema, bool isPartial, string fieldType, TValue fieldDefault) + public ObjectValidator(IDictionary schema, bool isPartial, string fieldType) { this.schema = schema; - this.fieldDefault = fieldDefault; this.fieldType = fieldType; this.isPartial = isPartial; } public async Task ValidateAsync(object value, ValidationContext context, AddError addError) { + if (value == null) + { + value = DefaultValue; + } + if (value is IReadOnlyDictionary values) { foreach (var fieldData in values) @@ -52,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators continue; } - fieldValue = fieldDefault; + fieldValue = default; } var (isOptional, validator) = field.Value; diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs index 52a72ba4c..2209786f7 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs @@ -29,34 +29,30 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators public Task ValidateAsync(object value, ValidationContext context, AddError addError) { - if (value == null) + if (value != null && value is T typedValue) { - return TaskHelper.Done; - } - - var typedValue = (T)value; - - if (min.HasValue && max.HasValue) - { - if (Equals(min, max) && Equals(min.Value, max.Value)) - { - addError(context.Path, $"Must be exactly '{max}'."); - } - else if (typedValue.CompareTo(min.Value) < 0 || typedValue.CompareTo(max.Value) > 0) + if (min.HasValue && max.HasValue) { - addError(context.Path, $"Must be between '{min}' and '{max}'."); + if (Equals(min, max) && Equals(min.Value, max.Value)) + { + addError(context.Path, $"Must be exactly '{max}'."); + } + else if (typedValue.CompareTo(min.Value) < 0 || typedValue.CompareTo(max.Value) > 0) + { + addError(context.Path, $"Must be between '{min}' and '{max}'."); + } } - } - else - { - if (min.HasValue && typedValue.CompareTo(min.Value) < 0) - { - addError(context.Path, $"Must be greater or equal to '{min}'."); - } - - if (max.HasValue && typedValue.CompareTo(max.Value) > 0) + else { - addError(context.Path, $"Must be less or equal to '{max}'."); + if (min.HasValue && typedValue.CompareTo(min.Value) < 0) + { + addError(context.Path, $"Must be greater or equal to '{min}'."); + } + + if (max.HasValue && typedValue.CompareTo(max.Value) > 0) + { + addError(context.Path, $"Must be less or equal to '{max}'."); + } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs index eacbcdb79..129f88dab 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs @@ -21,19 +21,22 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators public Task ValidateAsync(object value, ValidationContext context, AddError addError) { - if (context.IsOptional || (value != null && !(value is string))) + if (context.IsOptional) { return TaskHelper.Done; } - var valueAsString = (string)value; - - if (valueAsString == null || (validateEmptyStrings && string.IsNullOrWhiteSpace(valueAsString))) + if (value.IsNullOrUndefined() || IsEmptyString(value)) { addError(context.Path, "Field is required."); } return TaskHelper.Done; } + + private bool IsEmptyString(object value) + { + return value is string typed && validateEmptyStrings && string.IsNullOrWhiteSpace(typed); + } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs index 6d2d308a6..6a92a3671 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs @@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { public Task ValidateAsync(object value, ValidationContext context, AddError addError) { - if (value == null && !context.IsOptional) + if (value.IsNullOrUndefined() && !context.IsOptional) { addError(context.Path, "Field is required."); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs index 6c930f903..c25cd48bf 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs @@ -6,7 +6,6 @@ // ========================================================================== using System.Collections.Generic; -using System.Linq; using GraphQL.Resolvers; using GraphQL.Types; using Squidex.Domain.Apps.Core.Contents; @@ -26,11 +25,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Name = $"{schemaType}DataDto"; - foreach (var field in schema.SchemaDef.Fields.Where(x => !x.IsHidden)) + foreach (var field in schema.SchemaDef.Fields.ForApi()) { - var fieldInfo = model.GetGraphType(schema, field); + var (resolvedType, valueResolver) = model.GetGraphType(schema, field); - if (fieldInfo.ResolveType != null) + if (valueResolver != null) { var fieldType = field.TypeName(); var fieldName = field.DisplayName(); @@ -46,11 +45,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { var key = partitionItem.Key; - var resolver = new FuncFieldResolver(c => + var partitionResolver = new FuncFieldResolver(c => { if (((ContentFieldData)c.Source).TryGetValue(key, out var value)) { - return fieldInfo.Resolver(value, c); + return valueResolver(value, c); } else { @@ -61,8 +60,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types fieldGraphType.AddField(new FieldType { Name = key.EscapePartition(), - Resolver = resolver, - ResolvedType = fieldInfo.ResolveType, + Resolver = partitionResolver, + ResolvedType = resolvedType, Description = field.RawProperties.Hints }); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs index 35c0a1b2e..afbb92b38 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs @@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Name = $"{schemaType}{fieldName}ChildDto"; - foreach (var nestedField in field.Fields.Where(x => !x.IsHidden)) + foreach (var nestedField in field.Fields.ForApi()) { var fieldInfo = model.GetGraphType(schema, nestedField); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs index 2ed69033c..32a9a308f 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs @@ -81,6 +81,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types return ResolveDefault(AllTypes.NoopTags); } + public (IGraphType ResolveType, ValueResolver Resolver) Visit(IField field) + { + return (null, null); + } + private static (IGraphType ResolveType, ValueResolver Resolver) ResolveDefault(IGraphType type) { return (type, NoopResolver); diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs index fff833c57..7eb970464 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs @@ -21,7 +21,18 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards public static IEnumerable Validate(FieldProperties properties) { - return properties?.Accept(Instance); + if (properties != null) + { + if (!properties.IsForApi() && properties.IsListField) + { + yield return new ValidationError("UI field cannot be a list field.", nameof(properties.IsListField)); + } + + foreach (var error in properties.Accept(Instance)) + { + yield return error; + } + } } public IEnumerable Visit(ArrayFieldProperties properties) @@ -268,5 +279,14 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards nameof(properties.MaxItems)); } } + + public IEnumerable Visit(UIFieldProperties properties) + { + if (!properties.Editor.IsEnumValue()) + { + yield return new ValidationError(Not.Valid("Editor"), + nameof(properties.Editor)); + } + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs index 65b22cbe9..ab4851731 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs @@ -13,6 +13,8 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; +#pragma warning disable IDE0060 // Remove unused parameter + namespace Squidex.Domain.Apps.Entities.Schemas.Guards { public static class GuardSchema @@ -199,6 +201,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards } else { + if (!field.Properties.IsForApi()) + { + if (field.IsHidden) + { + e("UI field cannot be hidden.", $"{prefix}.{nameof(field.IsHidden)}"); + } + + if (field.IsDisabled) + { + e("UI field cannot be disabled.", $"{prefix}.{nameof(field.IsDisabled)}"); + } + } + var errors = FieldPropertiesValidator.Validate(field.Properties); errors.Foreach(x => x.WithPrefix($"{prefix}.{nameof(field.Properties)}").AddTo(e)); diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs index 83853330d..ed93453bc 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs @@ -101,6 +101,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { throw new DomainException("Schema field is already hidden."); } + + if (!field.IsForApi()) + { + throw new DomainException("UI field cannot be hidden."); + } } public static void CanDisable(Schema schema, DisableField command) @@ -113,6 +118,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { throw new DomainException("Schema field is already disabled."); } + + if (!field.IsForApi()) + { + throw new DomainException("UI field cannot be disabled."); + } } public static void CanDelete(Schema schema, DeleteField command) diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs index 31bc2cc01..c2cacdb89 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs @@ -55,6 +55,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Converters return SimpleMapper.Map(properties, new ReferencesFieldPropertiesDto()); } + public FieldPropertiesDto Visit(UIFieldProperties properties) + { + return SimpleMapper.Map(properties, new UIFieldPropertiesDto()); + } + public FieldPropertiesDto Visit(TagsFieldProperties properties) { var result = SimpleMapper.Map(properties, new TagsFieldPropertiesDto()); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/UIFieldPropertiesDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/UIFieldPropertiesDto.cs new file mode 100644 index 000000000..76040518a --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/UIFieldPropertiesDto.cs @@ -0,0 +1,26 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Collections; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields +{ + public sealed class UIFieldPropertiesDto : FieldPropertiesDto + { + /// + /// The editor that is used to manage this field. + /// + public UIFieldEditor Editor { get; set; } + + public override FieldProperties ToProperties() + { + return SimpleMapper.Map(this, new UIFieldProperties()); + } + } +} diff --git a/src/Squidex/app/features/content/pages/content/content-field.component.html b/src/Squidex/app/features/content/pages/content/content-field.component.html index 36cafd558..a6098056f 100644 --- a/src/Squidex/app/features/content/pages/content/content-field.component.html +++ b/src/Squidex/app/features/content/pages/content/content-field.component.html @@ -1,6 +1,6 @@
-
+
+ + + + +
- -
+
+ + + + +
diff --git a/src/Squidex/app/features/content/pages/content/content-field.component.ts b/src/Squidex/app/features/content/pages/content/content-field.component.ts index c949af5de..07be6e356 100644 --- a/src/Squidex/app/features/content/pages/content/content-field.component.ts +++ b/src/Squidex/app/features/content/pages/content/content-field.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { Component, DoCheck, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { AbstractControl, FormGroup } from '@angular/forms'; import { Observable } from 'rxjs'; import { combineLatest } from 'rxjs/operators'; @@ -29,7 +29,7 @@ import { styleUrls: ['./content-field.component.scss'], templateUrl: './content-field.component.html' }) -export class ContentFieldComponent implements OnChanges { +export class ContentFieldComponent implements DoCheck, OnChanges { @Input() public form: EditContentForm; @@ -75,7 +75,7 @@ export class ContentFieldComponent implements OnChanges { this.showAllControls = this.localStore.getBoolean(this.configKey()); } - if (changes['fieldForm']) { + if (changes['fieldForm'] && this.fieldForm) { this.isInvalid = invalid$(this.fieldForm); } @@ -89,26 +89,30 @@ export class ContentFieldComponent implements OnChanges { combineLatest(value$(this.fieldFormCompare), (lhs, rhs) => !Types.jsJsonEquals(lhs, rhs))); } + } + + public ngDoCheck() { + if (this.fieldForm) { + const control = this.findControl(this.fieldForm); - const control = this.findControl(this.fieldForm); + if (this.selectedFormControl !== control) { + if (this.selectedFormControl && Types.isFunction(this.selectedFormControl['_clearChangeFns'])) { + this.selectedFormControl['_clearChangeFns'](); + } - if (this.selectedFormControl !== control) { - if (this.selectedFormControl && Types.isFunction(this.selectedFormControl['_clearChangeFns'])) { - this.selectedFormControl['_clearChangeFns'](); + this.selectedFormControl = control; } - this.selectedFormControl = control; - } + if (this.fieldFormCompare) { + const controlCompare = this.findControl(this.fieldFormCompare); - if (this.fieldFormCompare) { - const controlCompare = this.findControl(this.fieldFormCompare); + if (this.selectedFormControlCompare !== controlCompare) { + if (this.selectedFormControlCompare && Types.isFunction(this.selectedFormControlCompare['_clearChangeFns'])) { + this.selectedFormControlCompare['_clearChangeFns'](); + } - if (this.selectedFormControlCompare !== controlCompare) { - if (this.selectedFormControlCompare && Types.isFunction(this.selectedFormControlCompare['_clearChangeFns'])) { - this.selectedFormControlCompare['_clearChangeFns'](); + this.selectedFormControlCompare = controlCompare; } - - this.selectedFormControlCompare = controlCompare; } } } diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.ts b/src/Squidex/app/features/content/pages/content/content-page.component.ts index b607f2400..0488caf99 100644 --- a/src/Squidex/app/features/content/pages/content/content-page.component.ts +++ b/src/Squidex/app/features/content/pages/content/content-page.component.ts @@ -214,7 +214,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD if (!this.content || version === null || version.eq(this.content.version)) { this.contentFormCompare = null; this.contentVersion = null; - this.contentForm.load(this.content.dataDraft); + this.loadContent(this.content.dataDraft); } else { this.contentsState.loadVersion(this.content, version) .subscribe(dto => { @@ -224,14 +224,16 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD this.contentFormCompare.form.disable(); } - this.contentFormCompare.load(dto.payload); - this.contentForm.load(this.content.dataDraft); + const isArchive = this.content && this.content.status === 'Archived'; + + this.contentFormCompare.loadContent(dto.payload, true); + this.contentForm.loadContent(this.content.dataDraft, isArchive); } else { if (this.contentFormCompare) { this.contentFormCompare = null; } - this.contentForm.load(dto.payload); + this.loadContent(dto.payload); } this.contentVersion = version; diff --git a/src/Squidex/app/features/content/shared/array-editor.component.html b/src/Squidex/app/features/content/shared/array-editor.component.html index 9f94de23f..2632496dd 100644 --- a/src/Squidex/app/features/content/shared/array-editor.component.html +++ b/src/Squidex/app/features/content/shared/array-editor.component.html @@ -6,6 +6,7 @@ [form]="form" [field]="field" [isHidden]="snapshot.isHidden" + [isDisabled]="arrayControl.disabled" [isFirst]="i === 0" [isLast]="i === arrayControl.controls.length - 1" [index]="i" @@ -20,7 +21,7 @@
- diff --git a/src/Squidex/app/features/content/shared/array-item.component.html b/src/Squidex/app/features/content/shared/array-item.component.html index 658f6e19d..5f1b4f23d 100644 --- a/src/Squidex/app/features/content/shared/array-item.component.html +++ b/src/Squidex/app/features/content/shared/array-item.component.html @@ -5,16 +5,16 @@ Item #{{index + 1}} - - - - - -
diff --git a/src/Squidex/app/features/content/shared/array-item.component.scss b/src/Squidex/app/features/content/shared/array-item.component.scss index 11cb6b684..7b54dfb82 100644 --- a/src/Squidex/app/features/content/shared/array-item.component.scss +++ b/src/Squidex/app/features/content/shared/array-item.component.scss @@ -1,6 +1,14 @@ @import '_vars'; @import '_mixins'; +:host /deep/ { + .ui-separator { + border-color: $color-border !important; + font-size: 1.2rem; + font-weight: normal; + } +} + .card { & { border: 1px solid darken($color-border, 5%); diff --git a/src/Squidex/app/features/content/shared/array-item.component.ts b/src/Squidex/app/features/content/shared/array-item.component.ts index d6154dfbc..3a14517c5 100644 --- a/src/Squidex/app/features/content/shared/array-item.component.ts +++ b/src/Squidex/app/features/content/shared/array-item.component.ts @@ -51,6 +51,9 @@ export class ArrayItemComponent implements OnChanges { @Input() public isLast = false; + @Input() + public isDisabled = false; + @Input() public index: number; @@ -73,7 +76,7 @@ export class ArrayItemComponent implements OnChanges { } if (changes['itemForm'] || changes['field']) { - this.fieldControls = this.field.nested.map(field => ({ field, control: this.itemForm.get(field.name)! })).filter(x => !!x.control); + this.fieldControls = this.field.nested.map(field => ({ field, control: this.itemForm.get(field.name)! })).filter(x => !x.field.properties.isContentField || !!x.control); } } diff --git a/src/Squidex/app/features/content/shared/field-editor.component.html b/src/Squidex/app/features/content/shared/field-editor.component.html index 21e87f6f7..506a43f4b 100644 --- a/src/Squidex/app/features/content/shared/field-editor.component.html +++ b/src/Squidex/app/features/content/shared/field-editor.component.html @@ -1,11 +1,13 @@ - - - - Disabled - - +
+ + + + Disabled + + +
@@ -14,6 +16,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -38,6 +71,27 @@ + + + + + + + + + + + + @@ -77,28 +131,6 @@ - - - - - - - - - - - - - - - - - - - - - - @@ -113,37 +145,10 @@ - - - - - - - - - - - - - - - - - + + +

{{field.displayName}}

+
@@ -153,4 +158,4 @@ {{hints}} - +
diff --git a/src/Squidex/app/features/content/shared/field-editor.component.scss b/src/Squidex/app/features/content/shared/field-editor.component.scss index 8cb38413e..206e9cf54 100644 --- a/src/Squidex/app/features/content/shared/field-editor.component.scss +++ b/src/Squidex/app/features/content/shared/field-editor.component.scss @@ -11,4 +11,20 @@ font-size: .8rem; font-weight: normal; } +} + +.ui { + & { + margin-bottom: 1rem; + } + + &-separator { + color: lighten($color-text, 20%); + border-bottom: 1px solid darken($color-border, 5%); + font-size: 1.3rem; + font-weight: normal; + padding: .25rem 0; + margin-top: 1.5rem; + margin-bottom: 0; + } } \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/field-editor.component.ts b/src/Squidex/app/features/content/shared/field-editor.component.ts index d06292a19..69e129549 100644 --- a/src/Squidex/app/features/content/shared/field-editor.component.ts +++ b/src/Squidex/app/features/content/shared/field-editor.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnChanges } from '@angular/core'; import { FormControl } from '@angular/forms'; import { @@ -20,7 +20,7 @@ import { styleUrls: ['./field-editor.component.scss'], templateUrl: './field-editor.component.html' }) -export class FieldEditorComponent { +export class FieldEditorComponent implements OnChanges { @Input() public form: EditContentForm; @@ -43,4 +43,10 @@ export class FieldEditorComponent { public displaySuffix: string; public uniqueId = MathHelper.guid(); + + public ngOnChanges() { + let a = 0; + a++; + console.log(a); + } } \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/references-dropdown.component.ts b/src/Squidex/app/features/content/shared/references-dropdown.component.ts index 721edc3ec..ac9025e7e 100644 --- a/src/Squidex/app/features/content/shared/references-dropdown.component.ts +++ b/src/Squidex/app/features/content/shared/references-dropdown.component.ts @@ -45,8 +45,7 @@ type ContentName = { name: string, id: string }; - `, + `, providers: [SQX_REFERENCES_DROPDOWN_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) diff --git a/src/Squidex/app/features/schemas/declarations.ts b/src/Squidex/app/features/schemas/declarations.ts index 7082aa204..4f7ac0fb9 100644 --- a/src/Squidex/app/features/schemas/declarations.ts +++ b/src/Squidex/app/features/schemas/declarations.ts @@ -5,7 +5,6 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -export * from './pages/schema/types/assets-ui.component'; export * from './pages/schema/types/assets-validation.component'; export * from './pages/schema/types/boolean-ui.component'; export * from './pages/schema/types/boolean-validation.component'; diff --git a/src/Squidex/app/features/schemas/module.ts b/src/Squidex/app/features/schemas/module.ts index 0db0eef25..0207d7049 100644 --- a/src/Squidex/app/features/schemas/module.ts +++ b/src/Squidex/app/features/schemas/module.ts @@ -17,7 +17,6 @@ import { } from '@app/shared'; import { - AssetsUIComponent, AssetsValidationComponent, BooleanUIComponent, BooleanValidationComponent, @@ -81,13 +80,12 @@ const routes: Routes = [ SchemaMustExistGuard ], declarations: [ - FieldComponent, - AssetsUIComponent, AssetsValidationComponent, BooleanUIComponent, BooleanValidationComponent, DateTimeUIComponent, DateTimeValidationComponent, + FieldComponent, FieldFormCommonComponent, FieldFormUIComponent, FieldFormValidationComponent, diff --git a/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html b/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html index 00dfdec04..b47989637 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html @@ -44,7 +44,7 @@ placeholder="Enter field name" sqxFocusOnInit />
-
+
- Locked - Enabled - Disabled + Locked + Enabled + Disabled
@@ -25,22 +25,24 @@ -
+
diff --git a/src/Squidex/app/features/schemas/pages/schema/forms/field-form-ui.component.html b/src/Squidex/app/features/schemas/pages/schema/forms/field-form-ui.component.html deleted file mode 100644 index 469ffb141..000000000 --- a/src/Squidex/app/features/schemas/pages/schema/forms/field-form-ui.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/forms/field-form-ui.component.scss b/src/Squidex/app/features/schemas/pages/schema/forms/field-form-ui.component.scss deleted file mode 100644 index fbb752506..000000000 --- a/src/Squidex/app/features/schemas/pages/schema/forms/field-form-ui.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/forms/field-form-ui.component.ts b/src/Squidex/app/features/schemas/pages/schema/forms/field-form-ui.component.ts index 806c84119..173d51b6e 100644 --- a/src/Squidex/app/features/schemas/pages/schema/forms/field-form-ui.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/forms/field-form-ui.component.ts @@ -12,8 +12,33 @@ import { FieldDto } from '@app/shared'; @Component({ selector: 'sqx-field-form-ui', - styleUrls: ['field-form-ui.component.scss'], - templateUrl: 'field-form-ui.component.html' + template: ` + + + + + + + + + + + + + + + + + + + + + + + + + + ` }) export class FieldFormUIComponent { @Input() diff --git a/src/Squidex/app/features/schemas/pages/schema/forms/field-form-validation.component.html b/src/Squidex/app/features/schemas/pages/schema/forms/field-form-validation.component.html deleted file mode 100644 index 1cd5d2a74..000000000 --- a/src/Squidex/app/features/schemas/pages/schema/forms/field-form-validation.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/forms/field-form-validation.component.scss b/src/Squidex/app/features/schemas/pages/schema/forms/field-form-validation.component.scss deleted file mode 100644 index fbb752506..000000000 --- a/src/Squidex/app/features/schemas/pages/schema/forms/field-form-validation.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/forms/field-form-validation.component.ts b/src/Squidex/app/features/schemas/pages/schema/forms/field-form-validation.component.ts index d02c495a5..886f68411 100644 --- a/src/Squidex/app/features/schemas/pages/schema/forms/field-form-validation.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/forms/field-form-validation.component.ts @@ -16,8 +16,36 @@ import { @Component({ selector: 'sqx-field-form-validation', - styleUrls: ['field-form-validation.component.scss'], - templateUrl: 'field-form-validation.component.html' + template: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ` }) export class FieldFormValidationComponent { @Input() diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html index 7bb111314..69ccaa9fc 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html @@ -70,11 +70,9 @@
- - +
+ +