Browse Source

New Scripting methods and S3 improvements.

pull/978/head
Sebastian 3 years ago
parent
commit
3634d29f2e
  1. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptDescriptor.cs
  2. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintObjectConverter.cs
  3. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs
  4. 6
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingValue.cs
  5. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  6. 75
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs
  7. 66
      backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs
  8. 281
      backend/src/Squidex.Domain.Apps.Entities/Properties/Resources.Designer.cs
  9. 12
      backend/src/Squidex.Domain.Apps.Entities/Properties/Resources.resx
  10. 2
      backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  11. 12
      backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  12. 24
      backend/src/Squidex/Squidex.csproj
  13. 5
      backend/src/Squidex/appsettings.json
  14. 21
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs
  15. 21
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesJintExtensionTests.cs
  16. 4
      frontend/src/app/features/rules/shared/actions/formattable-input.component.ts
  17. 4
      frontend/src/app/features/rules/shared/actions/generic-action.component.ts
  18. 4
      frontend/src/app/features/rules/shared/triggers/completions-cache.ts
  19. 4
      frontend/src/app/features/rules/shared/triggers/content-changed-schema.component.ts
  20. 4
      frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.ts
  21. 4
      frontend/src/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.ts
  22. 4
      frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts-form.component.ts
  23. 4
      frontend/src/app/features/settings/pages/asset-scripts/asset-scripts-page.component.ts
  24. 10
      frontend/src/app/framework/angular/forms/editors/code-editor.component.ts
  25. 1
      frontend/src/app/framework/internal.ts
  26. 25
      frontend/src/app/framework/utils/completion.ts
  27. 4
      frontend/src/app/shared/services/assets.service.spec.ts
  28. 17
      frontend/src/app/shared/services/assets.service.ts
  29. 5
      frontend/src/app/shared/services/rules.service.spec.ts
  30. 17
      frontend/src/app/shared/services/rules.service.ts
  31. 11
      frontend/src/app/shared/services/schemas.service.spec.ts
  32. 32
      frontend/src/app/shared/services/schemas.service.ts

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptDescriptor.cs

@ -9,7 +9,7 @@
namespace Squidex.Domain.Apps.Core.Scripting; namespace Squidex.Domain.Apps.Core.Scripting;
public delegate void AddDescription(JsonType type, string name, string description, string[]? allowedValues = null); public delegate void AddDescription(JsonType type, string name, string description, string[]? allowedValues = null, string? deprecationReason = null);
public interface IScriptDescriptor public interface IScriptDescriptor
{ {

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintObjectConverter.cs

@ -51,7 +51,7 @@ public sealed class JintObjectConverter : IObjectConverter
result = guid.ToString(); result = guid.ToString();
return true; return true;
case Instant instant: case Instant instant:
result = JsValue.FromObject(engine, instant.ToDateTimeUtc()); result = new JsDate(engine, instant.ToDateTimeUtc());
return true; return true;
case Status status: case Status status:
result = status.ToString(); result = status.ToString();

8
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs

@ -474,7 +474,7 @@ public sealed class ScriptingCompleter
Add(JsonType.String, name, description); Add(JsonType.String, name, description);
} }
private void Add(JsonType type, string? name, string? description, string[]? allowedValues = null) private void Add(JsonType type, string? name, string? description, string[]? allowedValues = null, string? decprecationReason = null)
{ {
var parts = name?.Split('.') ?? Array.Empty<string>(); var parts = name?.Split('.') ?? Array.Empty<string>();
@ -490,9 +490,11 @@ public sealed class ScriptingCompleter
var path = string.Concat(prefixes.Reverse()); var path = string.Concat(prefixes.Reverse());
result[path] = new ScriptingValue(path, type, description) result[path] = new ScriptingValue(path, type)
{ {
AllowedValues = allowedValues AllowedValues = allowedValues,
Description = description,
DeprecationReason = decprecationReason
}; };
for (int i = 0; i < parts.Length; i++) for (int i = 0; i < parts.Length; i++)

6
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingValue.cs

@ -9,7 +9,11 @@
namespace Squidex.Domain.Apps.Core.Scripting; namespace Squidex.Domain.Apps.Core.Scripting;
public sealed record ScriptingValue(string Path, JsonType Type, string? Description) public sealed record ScriptingValue(string Path, JsonType Type)
{ {
public string? Description { get; init; }
public string? DeprecationReason { get; init; }
public string[]? AllowedValues { get; init; } public string[]? AllowedValues { get; init; }
} }

2
backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -28,7 +28,7 @@
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="NJsonSchema" Version="10.8.0" /> <PackageReference Include="NJsonSchema" Version="10.8.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.Messaging.Subscriptions" Version="5.3.0" /> <PackageReference Include="Squidex.Messaging.Subscriptions" Version="5.4.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="7.0.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" /> <PackageReference Include="System.Linq.Async" Version="6.0.1" />

75
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs

@ -24,6 +24,8 @@ namespace Squidex.Domain.Apps.Entities.Assets;
public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
{ {
private static readonly JsString ErrorNoAsset = new JsString(nameof(ErrorNoAsset));
private static readonly JsString ErrorTooBig = new JsString(nameof(ErrorTooBig));
private delegate void GetAssetsDelegate(JsValue references, Action<JsValue> callback); private delegate void GetAssetsDelegate(JsValue references, Action<JsValue> callback);
private delegate void GetAssetTextDelegate(JsValue asset, Action<JsValue> callback, JsValue? encoding); private delegate void GetAssetTextDelegate(JsValue asset, Action<JsValue> callback, JsValue? encoding);
private delegate void GetBlurHashDelegate(JsValue asset, Action<JsValue> callback, JsValue? componentX, JsValue? componentY); private delegate void GetBlurHashDelegate(JsValue asset, Action<JsValue> callback, JsValue? componentX, JsValue? componentY);
@ -48,12 +50,16 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
return; return;
} }
describe(JsonType.Function, "getAsset(ids, callback)",
Resources.ScriptingGetAsset,
deprecationReason: Resources.ScriptingGetAssetDeprecated);
describe(JsonType.Function, "getAssetV2(ids, callback)",
Resources.ScriptingGetAssetV2);
describe(JsonType.Function, "getAssets(ids, callback)", describe(JsonType.Function, "getAssets(ids, callback)",
Resources.ScriptingGetAssets); Resources.ScriptingGetAssets);
describe(JsonType.Function, "getAsset(ids, callback)",
Resources.ScriptingGetAsset);
describe(JsonType.Function, "getAssetText(asset, callback, encoding?)", describe(JsonType.Function, "getAssetText(asset, callback, encoding?)",
Resources.ScriptingGetAssetText); Resources.ScriptingGetAssetText);
@ -78,7 +84,13 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
GetAssets(context, appId, user, references, callback); GetAssets(context, appId, user, references, callback);
}); });
var getAsset = new GetAssetsDelegate((references, callback) =>
{
GetAsset(context, appId, user, references, callback);
});
context.Engine.SetValue("getAsset", getAssets); context.Engine.SetValue("getAsset", getAssets);
context.Engine.SetValue("getAssetV2", getAsset);
context.Engine.SetValue("getAssets", getAssets); context.Engine.SetValue("getAssets", getAssets);
} }
@ -110,7 +122,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
{ {
if (input is not ObjectWrapper objectWrapper) if (input is not ObjectWrapper objectWrapper)
{ {
scheduler.Run(callback, JsValue.FromObject(context.Engine, "ErrorNoAsset")); scheduler.Run(callback, ErrorNoAsset);
return; return;
} }
@ -118,7 +130,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
{ {
if (asset.FileSize > 256_000) if (asset.FileSize > 256_000)
{ {
scheduler.Run(callback, JsValue.FromObject(context.Engine, "ErrorTooBig")); scheduler.Run(callback, ErrorTooBig);
return; return;
} }
@ -127,7 +139,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
{ {
var text = await asset.GetTextAsync(encoding?.ToString(), assetFileStore, ct); var text = await asset.GetTextAsync(encoding?.ToString(), assetFileStore, ct);
scheduler.Run(callback, JsValue.FromObject(context.Engine, text)); scheduler.Run(callback, text);
} }
catch catch
{ {
@ -146,7 +158,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
break; break;
default: default:
scheduler.Run(callback, JsValue.FromObject(context.Engine, "ErrorNoAsset")); scheduler.Run(callback, ErrorNoAsset);
break; break;
} }
}); });
@ -160,7 +172,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
{ {
if (input is not ObjectWrapper objectWrapper) if (input is not ObjectWrapper objectWrapper)
{ {
scheduler.Run(callback, JsValue.FromObject(context.Engine, "ErrorNoAsset")); scheduler.Run(callback, ErrorNoAsset);
return; return;
} }
@ -190,7 +202,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
{ {
var hash = await asset.GetBlurHashAsync(options, assetFileStore, assetGenerator, ct); var hash = await asset.GetBlurHashAsync(options, assetFileStore, assetGenerator, ct);
scheduler.Run(callback, JsValue.FromObject(context.Engine, hash)); scheduler.Run(callback, hash);
} }
catch catch
{ {
@ -209,7 +221,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
break; break;
default: default:
scheduler.Run(callback, JsValue.FromObject(context.Engine, "ErrorNoAsset")); scheduler.Run(callback,ErrorNoAsset);
break; break;
} }
}); });
@ -225,9 +237,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
if (ids.Count == 0) if (ids.Count == 0)
{ {
var emptyAssets = Array.Empty<IEnrichedAssetEntity>(); scheduler.Run(callback, new JsArray(context.Engine));
scheduler.Run(callback, JsValue.FromObject(context.Engine, emptyAssets));
return; return;
} }
@ -235,9 +245,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
if (app == null) if (app == null)
{ {
var emptyAssets = Array.Empty<IEnrichedAssetEntity>(); scheduler.Run(callback, new JsArray(context.Engine));
scheduler.Run(callback, JsValue.FromObject(context.Engine, emptyAssets));
return; return;
} }
@ -254,6 +262,41 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
}); });
} }
private void GetAsset(ScriptExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback)
{
Guard.NotNull(callback);
context.Schedule(async (scheduler, ct) =>
{
var ids = references.ToIds();
if (ids.Count == 0)
{
scheduler.Run(callback, JsValue.Null);
return;
}
var app = await GetAppAsync(appId, ct);
if (app == null)
{
scheduler.Run(callback, JsValue.Null);
return;
}
var assetQuery = serviceProvider.GetRequiredService<IAssetQueryService>();
var requestContext =
new Context(user, app).Clone(b => b
.WithoutTotal());
var assets = await assetQuery.QueryAsync(requestContext, null, Q.Empty.WithIds(ids), ct);
scheduler.Run(callback, JsValue.FromObject(context.Engine, assets.FirstOrDefault()));
return;
});
}
private async Task<IAppEntity> GetAppAsync(DomainId appId, private async Task<IAppEntity> GetAppAsync(DomainId appId,
CancellationToken ct) CancellationToken ct)
{ {

66
backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs

@ -39,13 +39,19 @@ public sealed class ReferencesJintExtension : IJintExtension, IScriptDescriptor
return; return;
} }
var action = new GetReferencesDelegate((references, callback) => var getReference = new GetReferencesDelegate((references, callback) =>
{
GetReference(context, appId, user, references, callback);
});
var getReferences = new GetReferencesDelegate((references, callback) =>
{ {
GetReferences(context, appId, user, references, callback); GetReferences(context, appId, user, references, callback);
}); });
context.Engine.SetValue("getReference", action); context.Engine.SetValue("getReference", getReferences);
context.Engine.SetValue("getReferences", action); context.Engine.SetValue("getReferenceV2", getReference);
context.Engine.SetValue("getReferences", getReferences);
} }
public void Describe(AddDescription describe, ScriptScope scope) public void Describe(AddDescription describe, ScriptScope scope)
@ -55,11 +61,15 @@ public sealed class ReferencesJintExtension : IJintExtension, IScriptDescriptor
return; return;
} }
describe(JsonType.Function, "getReference(id, callback)",
Resources.ScriptingGetReference,
deprecationReason: Resources.ScriptingGetReferenceDeprecated);
describe(JsonType.Function, "getReferenceV2(id, callback)",
Resources.ScriptingGetReferenceV2);
describe(JsonType.Function, "getReferences(ids, callback)", describe(JsonType.Function, "getReferences(ids, callback)",
Resources.ScriptingGetReferences); Resources.ScriptingGetReferences);
describe(JsonType.Function, "getReference(ids, callback)",
Resources.ScriptingGetReference);
} }
private void GetReferences(ScriptExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback) private void GetReferences(ScriptExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback)
@ -72,9 +82,7 @@ public sealed class ReferencesJintExtension : IJintExtension, IScriptDescriptor
if (ids.Count == 0) if (ids.Count == 0)
{ {
var emptyContents = Array.Empty<IEnrichedContentEntity>(); scheduler.Run(callback, new JsArray(context.Engine));
scheduler.Run(callback, JsValue.FromObject(context.Engine, emptyContents));
return; return;
} }
@ -82,9 +90,7 @@ public sealed class ReferencesJintExtension : IJintExtension, IScriptDescriptor
if (app == null) if (app == null)
{ {
var emptyContents = Array.Empty<IEnrichedContentEntity>(); scheduler.Run(callback, new JsArray(context.Engine));
scheduler.Run(callback, JsValue.FromObject(context.Engine, emptyContents));
return; return;
} }
@ -102,6 +108,42 @@ public sealed class ReferencesJintExtension : IJintExtension, IScriptDescriptor
}); });
} }
private void GetReference(ScriptExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback)
{
Guard.NotNull(callback);
context.Schedule(async (scheduler, ct) =>
{
var ids = references.ToIds();
if (ids.Count == 0)
{
scheduler.Run(callback, JsValue.Null);
return;
}
var app = await GetAppAsync(appId);
if (app == null)
{
scheduler.Run(callback, JsValue.Null);
return;
}
var contentQuery = serviceProvider.GetRequiredService<IContentQueryService>();
var requestContext =
new Context(user, app).Clone(b => b
.WithoutContentEnrichment()
.WithUnpublished()
.WithoutTotal());
var contents = await contentQuery.QueryAsync(requestContext, Q.Empty.WithIds(ids), ct);
scheduler.Run(callback, JsValue.FromObject(context.Engine, contents.FirstOrDefault()));
});
}
private async Task<IAppEntity> GetAppAsync(DomainId appId) private async Task<IAppEntity> GetAppAsync(DomainId appId)
{ {
var appProvider = serviceProvider.GetRequiredService<IAppProvider>(); var appProvider = serviceProvider.GetRequiredService<IAppProvider>();

281
backend/src/Squidex.Domain.Apps.Entities/Properties/Resources.Designer.cs

@ -8,145 +8,182 @@
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace Squidex.Domain.Apps.Entities.Properties; namespace Squidex.Domain.Apps.Entities.Properties {
using System; using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary> /// <summary>
/// Returns the cached ResourceManager instance used by this class. /// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] // This class was auto-generated by the StronglyTypedResourceBuilder
internal static global::System.Resources.ResourceManager ResourceManager { // class via a tool like ResGen or Visual Studio.
get { // To add or remove a member, edit your .ResX file then rerun ResGen
if (object.ReferenceEquals(resourceMan, null)) { // with the /str option, or rebuild your VS project.
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Squidex.Domain.Apps.Entities.Properties.Resources", typeof(Resources).Assembly); [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
resourceMan = temp; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Squidex.Domain.Apps.Entities.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
} }
return resourceMan;
} }
}
/// <summary>
/// <summary> /// Overrides the current thread's CurrentUICulture property for all
/// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class.
/// resource lookups using this strongly typed resource class. /// </summary>
/// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture {
internal static global::System.Globalization.CultureInfo Culture { get {
get { return resourceCulture;
return resourceCulture; }
set {
resourceCulture = value;
}
} }
set {
resourceCulture = value; /// <summary>
/// Looks up a localized string similar to Queries the asset with the specified ID and invokes the callback with an array of assets..
/// </summary>
internal static string ScriptingGetAsset {
get {
return ResourceManager.GetString("ScriptingGetAsset", resourceCulture);
}
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Please use getAssetV2, which returns a single asset and not an array..
/// Looks up a localized string similar to Queries the asset with the specified ID and invokes the callback with an array of assets.. /// </summary>
/// </summary> internal static string ScriptingGetAssetDeprecated {
internal static string ScriptingGetAsset { get {
get { return ResourceManager.GetString("ScriptingGetAssetDeprecated", resourceCulture);
return ResourceManager.GetString("ScriptingGetAsset", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Queries the assets with the specified IDs and invokes the callback with an array of assets..
/// Looks up a localized string similar to Queries the assets with the specified IDs and invokes the callback with an array of assets.. /// </summary>
/// </summary> internal static string ScriptingGetAssets {
internal static string ScriptingGetAssets { get {
get { return ResourceManager.GetString("ScriptingGetAssets", resourceCulture);
return ResourceManager.GetString("ScriptingGetAssets", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Get the text of an asset. Encodings: base64,ascii,unicode,utf8.
/// Looks up a localized string similar to Get the text of an asset. Encodings: base64,ascii,unicode,utf8. /// </summary>
/// </summary> internal static string ScriptingGetAssetText {
internal static string ScriptingGetAssetText { get {
get { return ResourceManager.GetString("ScriptingGetAssetText", resourceCulture);
return ResourceManager.GetString("ScriptingGetAssetText", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Queries the asset with the specified ID and invokes the callback with the found asset or null otherwise..
/// Looks up a localized string similar to Gets the blur hash of an asset if it is an image or null otherwise.. /// </summary>
/// </summary> internal static string ScriptingGetAssetV2 {
internal static string ScriptingGetBlurHash { get {
get { return ResourceManager.GetString("ScriptingGetAssetV2", resourceCulture);
return ResourceManager.GetString("ScriptingGetBlurHash", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Gets the blur hash of an asset if it is an image or null otherwise..
/// Looks up a localized string similar to Queries the content item with the specified ID and invokes the callback with an array of contents.. /// </summary>
/// </summary> internal static string ScriptingGetBlurHash {
internal static string ScriptingGetReference { get {
get { return ResourceManager.GetString("ScriptingGetBlurHash", resourceCulture);
return ResourceManager.GetString("ScriptingGetReference", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Queries the content item with the specified ID and invokes the callback with an array of contents..
/// Looks up a localized string similar to Queries the content items with the specified IDs and invokes the callback with an array of contents.. /// </summary>
/// </summary> internal static string ScriptingGetReference {
internal static string ScriptingGetReferences { get {
get { return ResourceManager.GetString("ScriptingGetReference", resourceCulture);
return ResourceManager.GetString("ScriptingGetReferences", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Please use getReferenceV2, which returns a single content item and not an array..
/// Looks up a localized string similar to Increments the counter with the given name and returns the value (OBSOLETE).. /// </summary>
/// </summary> internal static string ScriptingGetReferenceDeprecated {
internal static string ScriptingIncrementCounter { get {
get { return ResourceManager.GetString("ScriptingGetReferenceDeprecated", resourceCulture);
return ResourceManager.GetString("ScriptingIncrementCounter", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Queries the content items with the specified IDs and invokes the callback with an array of contents..
/// Looks up a localized string similar to Increments the counter with the given name and returns the value.. /// </summary>
/// </summary> internal static string ScriptingGetReferences {
internal static string ScriptingIncrementCounterV2 { get {
get { return ResourceManager.GetString("ScriptingGetReferences", resourceCulture);
return ResourceManager.GetString("ScriptingIncrementCounterV2", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Queries the content item with the specified ID and invokes the callback with the found content item or null otherwise..
/// Looks up a localized string similar to Resets the counter with the given name to zero (OBSOLETE).. /// </summary>
/// </summary> internal static string ScriptingGetReferenceV2 {
internal static string ScriptingResetCounter { get {
get { return ResourceManager.GetString("ScriptingGetReferenceV2", resourceCulture);
return ResourceManager.GetString("ScriptingResetCounter", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Increments the counter with the given name and returns the value (OBSOLETE)..
/// Looks up a localized string similar to Resets the counter with the given name to zero.. /// </summary>
/// </summary> internal static string ScriptingIncrementCounter {
internal static string ScriptingResetCounterV2 { get {
get { return ResourceManager.GetString("ScriptingIncrementCounter", resourceCulture);
return ResourceManager.GetString("ScriptingResetCounterV2", resourceCulture); }
}
/// <summary>
/// Looks up a localized string similar to Increments the counter with the given name and returns the value..
/// </summary>
internal static string ScriptingIncrementCounterV2 {
get {
return ResourceManager.GetString("ScriptingIncrementCounterV2", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Resets the counter with the given name to zero (OBSOLETE)..
/// </summary>
internal static string ScriptingResetCounter {
get {
return ResourceManager.GetString("ScriptingResetCounter", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Resets the counter with the given name to zero..
/// </summary>
internal static string ScriptingResetCounterV2 {
get {
return ResourceManager.GetString("ScriptingResetCounterV2", resourceCulture);
}
} }
} }
} }

12
backend/src/Squidex.Domain.Apps.Entities/Properties/Resources.resx

@ -120,21 +120,33 @@
<data name="ScriptingGetAsset" xml:space="preserve"> <data name="ScriptingGetAsset" xml:space="preserve">
<value>Queries the asset with the specified ID and invokes the callback with an array of assets.</value> <value>Queries the asset with the specified ID and invokes the callback with an array of assets.</value>
</data> </data>
<data name="ScriptingGetAssetDeprecated" xml:space="preserve">
<value>Please use getAssetV2, which returns a single asset and not an array.</value>
</data>
<data name="ScriptingGetAssets" xml:space="preserve"> <data name="ScriptingGetAssets" xml:space="preserve">
<value>Queries the assets with the specified IDs and invokes the callback with an array of assets.</value> <value>Queries the assets with the specified IDs and invokes the callback with an array of assets.</value>
</data> </data>
<data name="ScriptingGetAssetText" xml:space="preserve"> <data name="ScriptingGetAssetText" xml:space="preserve">
<value>Get the text of an asset. Encodings: base64,ascii,unicode,utf8</value> <value>Get the text of an asset. Encodings: base64,ascii,unicode,utf8</value>
</data> </data>
<data name="ScriptingGetAssetV2" xml:space="preserve">
<value>Queries the asset with the specified ID and invokes the callback with the found asset or null otherwise.</value>
</data>
<data name="ScriptingGetBlurHash" xml:space="preserve"> <data name="ScriptingGetBlurHash" xml:space="preserve">
<value>Gets the blur hash of an asset if it is an image or null otherwise.</value> <value>Gets the blur hash of an asset if it is an image or null otherwise.</value>
</data> </data>
<data name="ScriptingGetReference" xml:space="preserve"> <data name="ScriptingGetReference" xml:space="preserve">
<value>Queries the content item with the specified ID and invokes the callback with an array of contents.</value> <value>Queries the content item with the specified ID and invokes the callback with an array of contents.</value>
</data> </data>
<data name="ScriptingGetReferenceDeprecated" xml:space="preserve">
<value>Please use getReferenceV2, which returns a single content item and not an array.</value>
</data>
<data name="ScriptingGetReferences" xml:space="preserve"> <data name="ScriptingGetReferences" xml:space="preserve">
<value>Queries the content items with the specified IDs and invokes the callback with an array of contents.</value> <value>Queries the content items with the specified IDs and invokes the callback with an array of contents.</value>
</data> </data>
<data name="ScriptingGetReferenceV2" xml:space="preserve">
<value>Queries the content item with the specified ID and invokes the callback with the found content item or null otherwise.</value>
</data>
<data name="ScriptingIncrementCounter" xml:space="preserve"> <data name="ScriptingIncrementCounter" xml:space="preserve">
<value>Increments the counter with the given name and returns the value (OBSOLETE).</value> <value>Increments the counter with the given name and returns the value (OBSOLETE).</value>
</data> </data>

2
backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj

@ -29,7 +29,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Notifo.SDK" Version="1.5.1" /> <PackageReference Include="Notifo.SDK" Version="1.5.1" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.CLI.Core" Version="9.2.0" /> <PackageReference Include="Squidex.CLI.Core" Version="9.5.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="7.0.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />

12
backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -24,12 +24,12 @@
<PackageReference Include="NodaTime" Version="3.1.6" /> <PackageReference Include="NodaTime" Version="3.1.6" />
<PackageReference Include="OpenTelemetry.Api" Version="1.3.1" /> <PackageReference Include="OpenTelemetry.Api" Version="1.3.1" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.Assets" Version="5.3.0" /> <PackageReference Include="Squidex.Assets" Version="5.4.0" />
<PackageReference Include="Squidex.Caching" Version="5.3.0" /> <PackageReference Include="Squidex.Caching" Version="5.4.0" />
<PackageReference Include="Squidex.Hosting.Abstractions" Version="5.3.0" /> <PackageReference Include="Squidex.Hosting.Abstractions" Version="5.4.0" />
<PackageReference Include="Squidex.Log" Version="5.3.0" /> <PackageReference Include="Squidex.Log" Version="5.4.0" />
<PackageReference Include="Squidex.Messaging" Version="5.3.0" /> <PackageReference Include="Squidex.Messaging" Version="5.4.0" />
<PackageReference Include="Squidex.Text" Version="5.3.0" /> <PackageReference Include="Squidex.Text" Version="5.4.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="7.0.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />

24
backend/src/Squidex/Squidex.csproj

@ -63,18 +63,18 @@
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc7" /> <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc7" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="ReportGenerator" Version="5.1.12" PrivateAssets="all" /> <PackageReference Include="ReportGenerator" Version="5.1.12" PrivateAssets="all" />
<PackageReference Include="Squidex.Assets.Azure" Version="5.3.0" /> <PackageReference Include="Squidex.Assets.Azure" Version="5.4.0" />
<PackageReference Include="Squidex.Assets.GoogleCloud" Version="5.3.0" /> <PackageReference Include="Squidex.Assets.GoogleCloud" Version="5.4.0" />
<PackageReference Include="Squidex.Assets.FTP" Version="5.3.0" /> <PackageReference Include="Squidex.Assets.FTP" Version="5.4.0" />
<PackageReference Include="Squidex.Assets.ImageMagick" Version="5.3.0" /> <PackageReference Include="Squidex.Assets.ImageMagick" Version="5.4.0" />
<PackageReference Include="Squidex.Assets.ImageSharp" Version="5.3.0" /> <PackageReference Include="Squidex.Assets.ImageSharp" Version="5.4.0" />
<PackageReference Include="Squidex.Assets.Mongo" Version="5.3.0" /> <PackageReference Include="Squidex.Assets.Mongo" Version="5.4.0" />
<PackageReference Include="Squidex.Assets.S3" Version="5.3.0" /> <PackageReference Include="Squidex.Assets.S3" Version="5.4.0" />
<PackageReference Include="Squidex.Assets.TusAdapter" Version="5.3.0" /> <PackageReference Include="Squidex.Assets.TusAdapter" Version="5.4.0" />
<PackageReference Include="Squidex.ClientLibrary" Version="12.6.0" /> <PackageReference Include="Squidex.ClientLibrary" Version="14.1.0" />
<PackageReference Include="Squidex.Hosting" Version="5.3.0" /> <PackageReference Include="Squidex.Hosting" Version="5.4.0" />
<PackageReference Include="Squidex.Messaging.All" Version="5.3.0" /> <PackageReference Include="Squidex.Messaging.All" Version="5.4.0" />
<PackageReference Include="Squidex.Messaging.Subscriptions" Version="5.3.0" /> <PackageReference Include="Squidex.Messaging.Subscriptions" Version="5.4.0" />
<PackageReference Include="Squidex.OpenIddict.MongoDb" Version="4.0.1-dev" /> <PackageReference Include="Squidex.OpenIddict.MongoDb" Version="4.0.1-dev" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
</ItemGroup> </ItemGroup>

5
backend/src/Squidex/appsettings.json

@ -439,6 +439,11 @@
// Read More: https://supsystic.com/documentation/id-secret-access-key-amazon-s3/ // Read More: https://supsystic.com/documentation/id-secret-access-key-amazon-s3/
"secretKey": "<MY_SECRET>", "secretKey": "<MY_SECRET>",
// True, to disable the SigV4 payload signing.
//
// This might be needed for some S3-compatible storage solutions, for example Cloudflare R2.
"disablePayloadSigning": false,
// Force path style property for AmazonS3Config // Force path style property for AmazonS3Config
"forcePathStyle": false "forcePathStyle": false
}, },

21
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs

@ -97,6 +97,27 @@ public class AssetsJintExtensionTests : GivenContext, IClassFixture<Translations
Assert.Equal(Cleanup(expected), Cleanup(actual)); Assert.Equal(Cleanup(expected), Cleanup(actual));
} }
[Fact]
public async Task Should_resolve_asset_v2()
{
var (vars, assets) = SetupAssetsVars(1);
var expected = $@"
Text: {assets[0].FileName} {assets[0].Id}
";
var script = @"
getAssetV2(data.assets.iv[0], function (asset) {
var actual1 = `Text: ${asset.fileName} ${asset.id}`;
complete(`${actual1}`);
});";
var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(actual));
}
[Fact] [Fact]
public async Task Should_resolve_assets() public async Task Should_resolve_assets()
{ {

21
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesJintExtensionTests.cs

@ -66,6 +66,27 @@ public class ReferencesJintExtensionTests : GivenContext, IClassFixture<Translat
Assert.Equal(Cleanup(expected), Cleanup(actual)); Assert.Equal(Cleanup(expected), Cleanup(actual));
} }
[Fact]
public async Task Should_resolve_reference_v2()
{
var (vars, _) = SetupReferenceVars(1);
var expected = @"
Text: Hello 1 World 1
";
var script = @"
getReferenceV2(data.references.iv[0], function (reference) {
var actual1 = `Text: ${reference.data.field1.iv} ${reference.data.field2.iv}`;
complete(`${actual1}`);
})";
var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(actual));
}
[Fact] [Fact]
public async Task Should_resolve_references() public async Task Should_resolve_references()
{ {

4
frontend/src/app/features/rules/shared/actions/formattable-input.component.ts

@ -7,7 +7,7 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, forwardRef, Input, ViewChild } from '@angular/core'; import { AfterViewInit, ChangeDetectionStrategy, Component, forwardRef, Input, ViewChild } from '@angular/core';
import { ControlValueAccessor, DefaultValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, DefaultValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { CodeEditorComponent, Types } from '@app/framework'; import { CodeEditorComponent, ScriptCompletions, Types } from '@app/framework';
export const SQX_FORMATTABLE_INPUT_CONTROL_VALUE_ACCESSOR: any = { export const SQX_FORMATTABLE_INPUT_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => FormattableInputComponent), multi: true, provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => FormattableInputComponent), multi: true,
@ -35,7 +35,7 @@ export class FormattableInputComponent implements ControlValueAccessor, AfterVie
public type: 'Text' | 'Code' = 'Text'; public type: 'Text' | 'Code' = 'Text';
@Input() @Input()
public completion: ReadonlyArray<{ path: string; description: string; type: string }> | undefined | null; public completion: ScriptCompletions | undefined | null;
@ViewChild(DefaultValueAccessor) @ViewChild(DefaultValueAccessor)
public inputEditor!: DefaultValueAccessor; public inputEditor!: DefaultValueAccessor;

4
frontend/src/app/features/rules/shared/actions/generic-action.component.ts

@ -7,7 +7,7 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { EMPTY, Observable, shareReplay } from 'rxjs'; import { EMPTY, Observable, shareReplay } from 'rxjs';
import { ActionForm, RuleCompletions, RulesService, TypedSimpleChanges } from '@app/shared'; import { ActionForm, RulesService, ScriptCompletions, TypedSimpleChanges } from '@app/shared';
@Component({ @Component({
selector: 'sqx-generic-action[actionForm][appName][trigger][triggerType]', selector: 'sqx-generic-action[actionForm][appName][trigger][triggerType]',
@ -28,7 +28,7 @@ export class GenericActionComponent {
@Input() @Input()
public triggerType: string | undefined | null; public triggerType: string | undefined | null;
public ruleCompletions: Observable<RuleCompletions> = EMPTY; public ruleCompletions: Observable<ScriptCompletions> = EMPTY;
constructor( constructor(
private readonly rulesService: RulesService, private readonly rulesService: RulesService,

4
frontend/src/app/features/rules/shared/triggers/completions-cache.ts

@ -7,11 +7,11 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { EMPTY, map, Observable, shareReplay } from 'rxjs'; import { EMPTY, map, Observable, shareReplay } from 'rxjs';
import { SchemaCompletions, SchemasService, SchemasState } from '@app/shared'; import { SchemasService, SchemasState, ScriptCompletions } from '@app/shared';
@Injectable() @Injectable()
export class CompletionsCache { export class CompletionsCache {
private readonly cache: { [schema: string]: Observable<SchemaCompletions> } = {}; private readonly cache: { [schema: string]: Observable<ScriptCompletions> } = {};
constructor( constructor(
private readonly schemasService: SchemasService, private readonly schemasService: SchemasService,

4
frontend/src/app/features/rules/shared/triggers/content-changed-schema.component.ts

@ -8,7 +8,7 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { EMPTY, Observable, switchMap } from 'rxjs'; import { EMPTY, Observable, switchMap } from 'rxjs';
import { SchemaCompletions, SchemaDto, TypedSimpleChanges, value$ } from '@app/shared'; import { SchemaDto, ScriptCompletions, TypedSimpleChanges, value$ } from '@app/shared';
import { CompletionsCache } from './completions-cache'; import { CompletionsCache } from './completions-cache';
@Component({ @Component({
@ -26,7 +26,7 @@ export class ContentChangedSchemaComponent {
@Output() @Output()
public remove = new EventEmitter<any>(); public remove = new EventEmitter<any>();
public completions: Observable<SchemaCompletions> = EMPTY; public completions: Observable<ScriptCompletions> = EMPTY;
constructor( constructor(
private readonly completionsCache: CompletionsCache, private readonly completionsCache: CompletionsCache,

4
frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.ts

@ -7,7 +7,7 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { EMPTY, Observable, shareReplay } from 'rxjs'; import { EMPTY, Observable, shareReplay } from 'rxjs';
import { ConfigurePreviewUrlsForm, SchemaCompletions, SchemaDto, SchemasService, SchemasState } from '@app/shared'; import { ConfigurePreviewUrlsForm, SchemaDto, SchemasService, SchemasState, ScriptCompletions } from '@app/shared';
@Component({ @Component({
selector: 'sqx-schema-preview-urls-form', selector: 'sqx-schema-preview-urls-form',
@ -20,7 +20,7 @@ export class SchemaPreviewUrlsFormComponent implements OnInit {
public editForm = new ConfigurePreviewUrlsForm(); public editForm = new ConfigurePreviewUrlsForm();
public fieldCompletions: Observable<SchemaCompletions> = EMPTY; public fieldCompletions: Observable<ScriptCompletions> = EMPTY;
public isEditable = false; public isEditable = false;

4
frontend/src/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.ts

@ -7,7 +7,7 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { EMPTY, Observable, shareReplay } from 'rxjs'; import { EMPTY, Observable, shareReplay } from 'rxjs';
import { ConfigureFieldRulesForm, FIELD_RULE_ACTIONS, SchemaCompletions, SchemaDto, SchemasService, SchemasState } from '@app/shared'; import { ConfigureFieldRulesForm, FIELD_RULE_ACTIONS, SchemaDto, SchemasService, SchemasState, ScriptCompletions } from '@app/shared';
@Component({ @Component({
selector: 'sqx-schema-field-rules-form', selector: 'sqx-schema-field-rules-form',
@ -22,7 +22,7 @@ export class SchemaFieldRulesFormComponent implements OnInit {
public fieldNames!: ReadonlyArray<string>; public fieldNames!: ReadonlyArray<string>;
public fieldActions = FIELD_RULE_ACTIONS; public fieldActions = FIELD_RULE_ACTIONS;
public fieldCompletions: Observable<SchemaCompletions> = EMPTY; public fieldCompletions: Observable<ScriptCompletions> = EMPTY;
public isEditable = false; public isEditable = false;

4
frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts-form.component.ts

@ -7,7 +7,7 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { EMPTY, Observable, shareReplay } from 'rxjs'; import { EMPTY, Observable, shareReplay } from 'rxjs';
import { AppsState, EditSchemaScriptsForm, SchemaCompletions, SchemaDto, SchemasService, SchemasState } from '@app/shared'; import { AppsState, EditSchemaScriptsForm, ScriptCompletions, SchemaDto, SchemasService, SchemasState } from '@app/shared';
@Component({ @Component({
selector: 'sqx-schema-scripts-form', selector: 'sqx-schema-scripts-form',
@ -19,7 +19,7 @@ export class SchemaScriptsFormComponent {
public schema!: SchemaDto; public schema!: SchemaDto;
public schemaScript = 'query'; public schemaScript = 'query';
public schemaCompletions: Observable<SchemaCompletions> = EMPTY; public schemaCompletions: Observable<ScriptCompletions> = EMPTY;
public editForm = new EditSchemaScriptsForm(); public editForm = new EditSchemaScriptsForm();

4
frontend/src/app/features/settings/pages/asset-scripts/asset-scripts-page.component.ts

@ -7,7 +7,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { EMPTY, Observable, shareReplay } from 'rxjs'; import { EMPTY, Observable, shareReplay } from 'rxjs';
import { AppsState, AssetCompletions, AssetScriptsState, AssetsService, EditAssetScriptsForm, ResourceOwner } from '@app/shared'; import { AppsState, AssetScriptsState, AssetsService, EditAssetScriptsForm, ResourceOwner, ScriptCompletions } from '@app/shared';
@Component({ @Component({
selector: 'sqx-asset-scripts-page', selector: 'sqx-asset-scripts-page',
@ -16,7 +16,7 @@ import { AppsState, AssetCompletions, AssetScriptsState, AssetsService, EditAsse
}) })
export class AssetScriptsPageComponent extends ResourceOwner implements OnInit { export class AssetScriptsPageComponent extends ResourceOwner implements OnInit {
public assetScript = 'query'; public assetScript = 'query';
public assetCompletions: Observable<AssetCompletions> = EMPTY; public assetCompletions: Observable<ScriptCompletions> = EMPTY;
public editForm = new EditAssetScriptsForm(); public editForm = new EditAssetScriptsForm();

10
frontend/src/app/framework/angular/forms/editors/code-editor.component.ts

@ -8,7 +8,7 @@
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, ViewChild } from '@angular/core'; import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, ViewChild } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { debounceTime, Subject } from 'rxjs'; import { debounceTime, Subject } from 'rxjs';
import { ResourceLoaderService, StatefulControlComponent, Types } from '@app/framework/internal'; import { ResourceLoaderService, ScriptCompletions, StatefulControlComponent, Types } from '@app/framework/internal';
import { TypedSimpleChanges } from './../../helpers'; import { TypedSimpleChanges } from './../../helpers';
import { FocusComponent } from './../forms-helper'; import { FocusComponent } from './../forms-helper';
@ -68,9 +68,9 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
} }
@Input() @Input()
public set completion(value: ReadonlyArray<{ path: string; description: string; type: string; allowedValues?: string[] }> | undefined | null) { public set completion(value: ScriptCompletions | undefined | null) {
if (value) { if (value) {
this.completions = value.map(({ path, description, type, allowedValues }) => ({ value: path, name: path, description, meta: type?.toLowerCase(), path, allowedValues })); this.completions = value.map(({ path, description, type, ...other }) => ({ value: path, name: path, description, meta: type?.toLowerCase(), path, ...other }));
} else { } else {
this.completions = []; this.completions = [];
} }
@ -178,6 +178,10 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
item.docHTML += '</ul></div>'; item.docHTML += '</ul></div>';
} }
if (item.deprecationReason) {
item.docHTML += `<div class="mt-2 mb-2"><strong>Deprecated</strong>: ${item.deprecationReason}</div>`;
}
} }
}, },
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape

1
frontend/src/app/framework/internal.ts

@ -25,6 +25,7 @@ export * from './services/title.service';
export * from './services/toolbar.service'; export * from './services/toolbar.service';
export * from './utils/array-helper'; export * from './utils/array-helper';
export * from './utils/cookies'; export * from './utils/cookies';
export * from './utils/completion';
export * from './utils/date-helper'; export * from './utils/date-helper';
export * from './utils/date-time'; export * from './utils/date-time';
export * from './utils/duration'; export * from './utils/duration';

25
frontend/src/app/framework/utils/completion.ts

@ -0,0 +1,25 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export type ScriptCompletion = {
// The autocompletion path.
path: string;
// The description of the autocompletion field.
description: string;
// The type of the autocompletion field.
type: 'Any' | 'Array' | 'Boolean' | 'Function' | 'Object' | 'String';
// The allowed values if the property is a string enum.
allowedValues?: string[];
// If the property is deprecated, a description is given.
deprecationReason?: string;
};
export type ScriptCompletions = ReadonlyArray<ScriptCompletion>;

4
frontend/src/app/shared/services/assets.service.spec.ts

@ -7,7 +7,7 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing'; import { inject, TestBed } from '@angular/core/testing';
import { ApiUrlConfig, AssetCompletions, AssetDto, AssetFolderDto, AssetFoldersDto, AssetsDto, AssetsService, DateTime, ErrorDto, MathHelper, Resource, ResourceLinks, sanitize, Version } from '@app/shared/internal'; import { ApiUrlConfig, AssetDto, AssetFolderDto, AssetFoldersDto, AssetsDto, AssetsService, DateTime, ErrorDto, MathHelper, Resource, ResourceLinks, sanitize, ScriptCompletions, Version } from '@app/shared/internal';
describe('AssetsService', () => { describe('AssetsService', () => {
const version = new Version('1'); const version = new Version('1');
@ -472,7 +472,7 @@ describe('AssetsService', () => {
it('should make get request to get completions', it('should make get request to get completions',
inject([AssetsService, HttpTestingController], (assetsService: AssetsService, httpMock: HttpTestingController) => { inject([AssetsService, HttpTestingController], (assetsService: AssetsService, httpMock: HttpTestingController) => {
let completions: AssetCompletions; let completions: ScriptCompletions;
assetsService.getCompletions('my-app').subscribe(result => { assetsService.getCompletions('my-app').subscribe(result => {
completions = result; completions = result;

17
frontend/src/app/shared/services/assets.service.ts

@ -9,7 +9,7 @@ import { HttpClient, HttpErrorResponse, HttpEventType, HttpResponse } from '@ang
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs'; import { Observable, throwError } from 'rxjs';
import { catchError, filter, map } from 'rxjs/operators'; import { catchError, filter, map } from 'rxjs/operators';
import { ApiUrlConfig, DateTime, ErrorDto, getLinkUrl, hasAnyLink, HTTP, Metadata, pretifyError, Resource, ResourceLinks, StringHelper, Types, Version, Versioned } from '@app/framework'; import { ApiUrlConfig, DateTime, ErrorDto, getLinkUrl, hasAnyLink, HTTP, Metadata, pretifyError, Resource, ResourceLinks, ScriptCompletions, StringHelper, Types, Version, Versioned } from '@app/framework';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { Query, sanitize } from './query'; import { Query, sanitize } from './query';
@ -141,17 +141,6 @@ export type AssetFoldersDto = Readonly<{
canCreate?: boolean; canCreate?: boolean;
}>; }>;
export type AssetCompletions = ReadonlyArray<{
// The autocompletion path.
path: string;
// The description of the autocompletion field.
description: string;
// The type of the autocompletion field.
type: string;
}>;
export type AnnotateAssetDto = Readonly<{ export type AnnotateAssetDto = Readonly<{
// The optional file name. // The optional file name.
fileName?: string; fileName?: string;
@ -407,10 +396,10 @@ export class AssetsService {
pretifyError('i18n:assets.deleteFailed')); pretifyError('i18n:assets.deleteFailed'));
} }
public getCompletions(appName: string): Observable<AssetCompletions> { public getCompletions(appName: string): Observable<ScriptCompletions> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/completion`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/completion`);
return this.http.get<AssetCompletions>(url); return this.http.get<ScriptCompletions>(url);
} }
} }

5
frontend/src/app/shared/services/rules.service.spec.ts

@ -7,8 +7,7 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing'; import { inject, TestBed } from '@angular/core/testing';
import { ApiUrlConfig, DateTime, Resource, ResourceLinks, RuleDto, RuleElementDto, RuleElementPropertyDto, RuleEventDto, RuleEventsDto, RulesDto, RulesService, Version } from '@app/shared/internal'; import { ApiUrlConfig, DateTime, Resource, ResourceLinks, RuleDto, RuleElementDto, RuleElementPropertyDto, RuleEventDto, RuleEventsDto, RulesDto, RulesService, ScriptCompletions, Version } from '@app/shared/internal';
import { RuleCompletions } from './..';
import { SimulatedRuleEventDto, SimulatedRuleEventsDto } from './rules.service'; import { SimulatedRuleEventDto, SimulatedRuleEventsDto } from './rules.service';
describe('RulesService', () => { describe('RulesService', () => {
@ -408,7 +407,7 @@ describe('RulesService', () => {
it('should make get request to get completions', it('should make get request to get completions',
inject([RulesService, HttpTestingController], (rulesService: RulesService, httpMock: HttpTestingController) => { inject([RulesService, HttpTestingController], (rulesService: RulesService, httpMock: HttpTestingController) => {
let completions: RuleCompletions; let completions: ScriptCompletions;
rulesService.getCompletions('my-app', 'TriggerType').subscribe(result => { rulesService.getCompletions('my-app', 'TriggerType').subscribe(result => {
completions = result; completions = result;

17
frontend/src/app/shared/services/rules.service.ts

@ -9,7 +9,7 @@ import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { ApiUrlConfig, DateTime, hasAnyLink, HTTP, Model, pretifyError, Resource, ResourceLinks, Version } from '@app/framework'; import { ApiUrlConfig, DateTime, hasAnyLink, HTTP, Model, pretifyError, Resource, ResourceLinks, ScriptCompletions, Version } from '@app/framework';
export type RuleElementMetadataDto = Readonly<{ export type RuleElementMetadataDto = Readonly<{
description: string; description: string;
@ -186,17 +186,6 @@ export class SimulatedRuleEventDto {
} }
} }
export type RuleCompletions = ReadonlyArray<Readonly<{
// The autocompletion path.
path: string;
// The description of the autocompletion field.
description: string;
// The type of the autocompletion field.
type: string;
}>>;
export type RulesDto = Readonly<{ export type RulesDto = Readonly<{
// The list of rules. // The list of rules.
items: ReadonlyArray<RuleDto>; items: ReadonlyArray<RuleDto>;
@ -409,10 +398,10 @@ export class RulesService {
pretifyError('i18n:rules.ruleEvents.cancelFailed')); pretifyError('i18n:rules.ruleEvents.cancelFailed'));
} }
public getCompletions(appName: string, actionType: string): Observable<RuleCompletions> { public getCompletions(appName: string, actionType: string): Observable<ScriptCompletions> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/completion/${actionType}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/completion/${actionType}`);
return this.http.get<RuleCompletions>(url); return this.http.get<ScriptCompletions>(url);
} }
} }

11
frontend/src/app/shared/services/schemas.service.spec.ts

@ -7,8 +7,7 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing'; import { inject, TestBed } from '@angular/core/testing';
import { ApiUrlConfig, createProperties, DateTime, FieldRule, NestedFieldDto, Resource, ResourceLinks, RootFieldDto, SchemaDto, SchemaPropertiesDto, SchemasDto, SchemasService, Version } from '@app/shared/internal'; import { ApiUrlConfig, createProperties, DateTime, FieldRule, NestedFieldDto, Resource, ResourceLinks, RootFieldDto, SchemaDto, SchemaPropertiesDto, SchemasDto, SchemasService, ScriptCompletions, Version } from '@app/shared/internal';
import { SchemaCompletions } from './..';
describe('SchemasService', () => { describe('SchemasService', () => {
const version = new Version('1'); const version = new Version('1');
@ -577,7 +576,7 @@ describe('SchemasService', () => {
it('should make get request to get content scripts completions', it('should make get request to get content scripts completions',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
let completions: SchemaCompletions; let completions: ScriptCompletions;
schemasService.getContentScriptsCompletion('my-app', 'my-schema').subscribe(result => { schemasService.getContentScriptsCompletion('my-app', 'my-schema').subscribe(result => {
completions = result; completions = result;
@ -595,7 +594,7 @@ describe('SchemasService', () => {
it('should make get request to get content trigger completions', it('should make get request to get content trigger completions',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
let completions: SchemaCompletions; let completions: ScriptCompletions;
schemasService.getContentTriggerCompletion('my-app', 'my-schema').subscribe(result => { schemasService.getContentTriggerCompletion('my-app', 'my-schema').subscribe(result => {
completions = result; completions = result;
@ -613,7 +612,7 @@ describe('SchemasService', () => {
it('should make get request to get field rules completions', it('should make get request to get field rules completions',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
let completions: SchemaCompletions; let completions: ScriptCompletions;
schemasService.getFieldRulesCompletion('my-app', 'my-schema').subscribe(result => { schemasService.getFieldRulesCompletion('my-app', 'my-schema').subscribe(result => {
completions = result; completions = result;
@ -631,7 +630,7 @@ describe('SchemasService', () => {
it('should make get request to get preview urls completions', it('should make get request to get preview urls completions',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
let completions: SchemaCompletions; let completions: ScriptCompletions;
schemasService.getPreviewUrlsCompletion('my-app', 'my-schema').subscribe(result => { schemasService.getPreviewUrlsCompletion('my-app', 'my-schema').subscribe(result => {
completions = result; completions = result;

32
frontend/src/app/shared/services/schemas.service.ts

@ -9,7 +9,7 @@ import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { ApiUrlConfig, DateTime, hasAnyLink, HTTP, pretifyError, Resource, ResourceLinks, StringHelper, Types, Version, Versioned } from '@app/framework'; import { ApiUrlConfig, DateTime, hasAnyLink, HTTP, pretifyError, Resource, ResourceLinks, ScriptCompletions, StringHelper, Types, Version, Versioned } from '@app/framework';
import { QueryModel } from './query'; import { QueryModel } from './query';
import { createProperties, FieldPropertiesDto } from './schemas.types'; import { createProperties, FieldPropertiesDto } from './schemas.types';
@ -378,20 +378,6 @@ export type FieldRule = Readonly<{
condition: string; condition: string;
}>; }>;
export type SchemaCompletions = ReadonlyArray<{
// The autocompletion path.
path: string;
// The description of the autocompletion field.
description: string;
// The type of the autocompletion field.
type: 'Any' | 'Array' | 'Boolean' | 'Function' | 'Object' | 'String';
// The allowed values if the property is a string enum.
allowedValues?: string[];
}>;
export type SchemasDto = Readonly<{ export type SchemasDto = Readonly<{
// The list of schemas. // The list of schemas.
items: ReadonlyArray<SchemaDto>; items: ReadonlyArray<SchemaDto>;
@ -746,28 +732,28 @@ export class SchemasService {
pretifyError('i18n:schemas.deleteFailed')); pretifyError('i18n:schemas.deleteFailed'));
} }
public getContentScriptsCompletion(appName: string, schemaName: string): Observable<SchemaCompletions> { public getContentScriptsCompletion(appName: string, schemaName: string): Observable<ScriptCompletions> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/completion/content-scripts`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/completion/content-scripts`);
return this.http.get<SchemaCompletions>(url); return this.http.get<ScriptCompletions>(url);
} }
public getContentTriggerCompletion(appName: string, schemaName: string): Observable<SchemaCompletions> { public getContentTriggerCompletion(appName: string, schemaName: string): Observable<ScriptCompletions> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/completion/content-triggers`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/completion/content-triggers`);
return this.http.get<SchemaCompletions>(url); return this.http.get<ScriptCompletions>(url);
} }
public getFieldRulesCompletion(appName: string, schemaName: string): Observable<SchemaCompletions> { public getFieldRulesCompletion(appName: string, schemaName: string): Observable<ScriptCompletions> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/completion/field-rules`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/completion/field-rules`);
return this.http.get<SchemaCompletions>(url); return this.http.get<ScriptCompletions>(url);
} }
public getPreviewUrlsCompletion(appName: string, schemaName: string): Observable<SchemaCompletions> { public getPreviewUrlsCompletion(appName: string, schemaName: string): Observable<ScriptCompletions> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/completion/preview-urls`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/completion/preview-urls`);
return this.http.get<SchemaCompletions>(url); return this.http.get<ScriptCompletions>(url);
} }
public getFilters(appName: string, schemaName: string): Observable<QueryModel> { public getFilters(appName: string, schemaName: string): Observable<QueryModel> {

Loading…
Cancel
Save