Browse Source

Merge branch 'master' of https://github.com/Squidex/squidex into fix-markdown

pull/870/head
Sebastian 4 years ago
parent
commit
b2ed0ea9ec
  1. 47
      CHANGELOG.md
  2. 9
      backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs
  3. 17
      backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs
  4. 5
      backend/extensions/Squidex.Extensions/Actions/RuleHelper.cs
  5. 5
      backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs
  6. 1
      backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs
  7. 48
      backend/extensions/Squidex.Extensions/Samples/Middleware/TemplateInstance.cs
  8. 63
      backend/extensions/Squidex.Extensions/Samples/Middleware/TemplateMiddleware.cs
  9. 25
      backend/extensions/Squidex.Extensions/Samples/Middleware/TemplatePlugin.cs
  10. 4
      backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj
  11. 40
      backend/i18n/frontend_en.json
  12. 28
      backend/i18n/frontend_it.json
  13. 24
      backend/i18n/frontend_nl.json
  14. 28
      backend/i18n/frontend_zh.json
  15. 6
      backend/i18n/source/backend_en.json
  16. 3
      backend/i18n/source/backend_it.json
  17. 3
      backend/i18n/source/backend_nl.json
  18. 7
      backend/i18n/source/backend_zh.json
  19. 40
      backend/i18n/source/frontend_en.json
  20. 15
      backend/i18n/source/frontend_it.json
  21. 15
      backend/i18n/source/frontend_nl.json
  22. 15
      backend/i18n/source/frontend_zh.json
  23. 14
      backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptionAttribute.cs
  24. 156
      backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs
  25. 60
      backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx
  26. 29
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs
  27. 5
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs
  28. 10
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs
  29. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedEvent.cs
  30. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs
  31. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventBase.cs
  32. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUsageExceededEvent.cs
  33. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUserEventBase.cs
  34. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs
  35. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs
  36. 33
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  37. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.Designer.cs
  38. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.resx
  39. 82
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetCommandScriptVars.cs
  40. 83
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetEntityScriptVars.cs
  41. 57
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetScriptVars.cs
  42. 101
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentScriptVars.cs
  43. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs
  44. 20
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/DataScriptVars.cs
  45. 36
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/EventScriptVars.cs
  46. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs
  47. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs
  48. 125
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/AssetMetadataWrapper.cs
  49. 15
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  50. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContext.cs
  51. 15
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptScope.cs
  52. 18
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs
  53. 331
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs
  54. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  55. 11
      backend/src/Squidex.Domain.Apps.Entities/Apps/AlwaysCreateClientCommandMiddleware.cs
  56. 3
      backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs
  57. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs
  58. 6
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.State.cs
  59. 6
      backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs
  60. 3
      backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/IAppsIndex.cs
  61. 144
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/ArrayFieldBuilder.cs
  62. 20
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/AssetFieldBuilder.cs
  63. 20
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/BooleanFieldBuilder.cs
  64. 20
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/ComponentFieldBuilder.cs
  65. 20
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/ComponentsFieldBuilder.cs
  66. 20
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/DateTimeFieldBuilder.cs
  67. 97
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/FieldBuilder.cs
  68. 20
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/JsonFieldBuilder.cs
  69. 20
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/NumberFieldBuilder.cs
  70. 20
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/ReferencesFieldBuilder.cs
  71. 203
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs
  72. 20
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs
  73. 20
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/TagsFieldBuilder.cs
  74. 127
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlog.cs
  75. 269
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateProfile.cs
  76. 29
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs
  77. 121
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/StringLogger.cs
  78. 15
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Template.cs
  79. 113
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs
  80. 6
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateRepository.cs
  81. 107
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplatesClient.cs
  82. 16
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplatesOptions.cs
  83. 9
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs
  84. 5
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs
  85. 198
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ScriptingExtensions.cs
  86. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/FileTagAssetMetadataSource.cs
  87. 2
      backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupHandler.cs
  88. 2
      backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs
  89. 5
      backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs
  90. 5
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs
  91. 8
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs
  92. 115
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ScriptingExtensions.cs
  93. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs
  94. 39
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs
  95. 5
      backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs
  96. 18
      backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs
  97. 4
      backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/IRuleRunnerService.cs
  98. 3
      backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/SimulatedRuleEvent.cs
  99. 3
      backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs
  100. 1
      backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj

47
CHANGELOG.md

@ -3,6 +3,53 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [6.6.0] - 2022-03-25
There was actually a 6.5.0, but it was not documented and due to a problem with the Github CI never published as a Release to Github.
This version contains a lot of small improvements, but also one breaking change. Therefore it was not easy to decide whether the major version should be increased or not. As part of a refactoring the redirect URIs for external authentication providers have been changed. You have to change the following paths when you use Google, Github, Microsoft or OIDC authentication.
* `/identity-server/signin-github` -> `/signin-github`
* `/identity-server/signin-google` -> `/signin-google`
* `/identity-server/signin-microsoft` -> `/signin-microsoft`
* `/identity-server/signin-oidc` -> `/signin-oidc`
### Fixed
* **Assets**: Fixed a bug when image metadata was resolved.
* **Assets**: Several fixes to the calculation of etags.
* **Contents**: Several fixes to the calculation of etags.
* **OpenAPI**: Several fixes to OpenAPI.
* **Rules**: Fixed the authentication for the Medium action.
* **Translations**: Fixed the selection of the chinese localization.
* **UI**: Fixed the default values for component fields.
* **UI**: Fixes for the field rules forms.
### Changed
* **Filters**: Better model for filters to simplify the UI and autocompletion in scripts.
* **Identity**: Migration to a new authentication structure.
* **Logs**: Easier logs with reabable messages.
* **UI**: Better unsplash field editor.
* **UI**: Migration to Angular CLI.
* **UI**: New library for virtualization to fix several bugs with the array editor and large lists.
* **UI**: New profile page.
* **UI**: New style for content table to support resizable columns.
### Added
* **Assets**: New function to calculate the blur hash of an image.
* **Assets**: Resumable file uploads with tus.
* **GraphQL**: Memory caching directive (only self hosting).
* **GraphQL**: New resolver to query references.
* **GraphQL**: Support to define the allowed values for string fields as GraphQL enum.
* **Templates**: New template system based on Github repositories.
* **UI**: Fullscreen button for all fields.
* **UI**: New embed SDK to allow inline editing to your website.
* **UI**: New gallery page to explain how to install templates.
* **UI**: Option to word wrap table columns.
* **UI**: Resizable table columns.
## [6.4.0] - 2021-12-20 ## [6.4.0] - 2021-12-20
### Fixed ### Fixed

9
backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs

@ -51,6 +51,8 @@ namespace Squidex.Extensions.Actions.Discourse
var ruleJob = new DiscourseJob var ruleJob = new DiscourseJob
{ {
ApiKey = action.ApiKey,
ApiUserName = action.ApiUsername,
RequestUrl = url, RequestUrl = url,
RequestBody = requestBody RequestBody = requestBody
}; };
@ -73,6 +75,9 @@ namespace Squidex.Extensions.Actions.Discourse
Content = new StringContent(job.RequestBody, Encoding.UTF8, "application/json") Content = new StringContent(job.RequestBody, Encoding.UTF8, "application/json")
}) })
{ {
request.Headers.TryAddWithoutValidation("Api-Key", job.ApiKey);
request.Headers.TryAddWithoutValidation("Api-Username", job.ApiUserName);
return await httpClient.OneWayRequestAsync(request, job.RequestBody, ct); return await httpClient.OneWayRequestAsync(request, job.RequestBody, ct);
} }
} }
@ -81,6 +86,10 @@ namespace Squidex.Extensions.Actions.Discourse
public sealed class DiscourseJob public sealed class DiscourseJob
{ {
public string ApiKey { get; set; }
public string ApiUserName { get; set; }
public string RequestUrl { get; set; } public string RequestUrl { get; set; }
public string RequestBody { get; set; } public string RequestBody { get; set; }

17
backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs

@ -10,6 +10,7 @@ using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Infrastructure;
#pragma warning disable IDE0059 // Value assigned to symbol is never used #pragma warning disable IDE0059 // Value assigned to symbol is never used
#pragma warning disable MA0048 // File name must match type name #pragma warning disable MA0048 // File name must match type name
@ -40,12 +41,19 @@ namespace Squidex.Extensions.Actions.ElasticSearch
} }
protected override async Task<(string Description, ElasticSearchJob Data)> CreateJobAsync(EnrichedEvent @event, ElasticSearchAction action) protected override async Task<(string Description, ElasticSearchJob Data)> CreateJobAsync(EnrichedEvent @event, ElasticSearchAction action)
{
if (@event is IEnrichedEntityEvent entityEvent)
{ {
var delete = @event.ShouldDelete(scriptEngine, action.Delete); var delete = @event.ShouldDelete(scriptEngine, action.Delete);
var contentId = entityEvent.Id.ToString(); string contentId;
if (@event is IEnrichedEntityEvent enrichedEntityEvent)
{
contentId = enrichedEntityEvent.Id.ToString();
}
else
{
contentId = DomainId.NewGuid().ToString();
}
var ruleDescription = string.Empty; var ruleDescription = string.Empty;
var ruleJob = new ElasticSearchJob var ruleJob = new ElasticSearchJob
@ -95,9 +103,6 @@ namespace Squidex.Extensions.Actions.ElasticSearch
return (ruleDescription, ruleJob); return (ruleDescription, ruleJob);
} }
return ("Ignore", new ElasticSearchJob());
}
protected override async Task<Result> ExecuteJobAsync(ElasticSearchJob job, protected override async Task<Result> ExecuteJobAsync(ElasticSearchJob job,
CancellationToken ct = default) CancellationToken ct = default)
{ {

5
backend/extensions/Squidex.Extensions/Actions/RuleHelper.cs

@ -18,9 +18,10 @@ namespace Squidex.Extensions.Actions
{ {
if (!string.IsNullOrWhiteSpace(expression)) if (!string.IsNullOrWhiteSpace(expression))
{ {
var vars = new ScriptVars // Script vars are just wrappers over dictionaries for better performance.
var vars = new EventScriptVars
{ {
["event"] = @event Event = @event
}; };
return scriptEngine.Evaluate(vars, expression); return scriptEngine.Evaluate(vars, expression);

5
backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs

@ -33,9 +33,10 @@ namespace Squidex.Extensions.Actions.Script
protected override async Task<Result> ExecuteJobAsync(ScriptJob job, protected override async Task<Result> ExecuteJobAsync(ScriptJob job,
CancellationToken ct = default) CancellationToken ct = default)
{ {
var vars = new ScriptVars // Script vars are just wrappers over dictionaries for better performance.
var vars = new EventScriptVars
{ {
["event"] = job.Event Event = job.Event
}; };
var result = await scriptEngine.ExecuteAsync(vars, job.Script, ct: ct); var result = await scriptEngine.ExecuteAsync(vars, job.Script, ct: ct);

1
backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs

@ -44,7 +44,6 @@ namespace Squidex.Extensions.Actions.Webhook
[Display(Name = "Headers (Optional)", Description = "The message headers in the format '[Key]=[Value]', one entry per line.")] [Display(Name = "Headers (Optional)", Description = "The message headers in the format '[Key]=[Value]', one entry per line.")]
[Editor(RuleFieldEditor.TextArea)] [Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Headers { get; set; } public string Headers { get; set; }
[Display(Name = "Shared Secret", Description = "The shared secret that is used to calculate the payload signature.")] [Display(Name = "Shared Secret", Description = "The shared secret that is used to calculate the payload signature.")]

48
backend/extensions/Squidex.Extensions/Samples/Middleware/TemplateInstance.cs

@ -1,48 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps.Templates;
using Squidex.Domain.Apps.Entities.Apps.Templates.Builders;
namespace Squidex.Extensions.Samples.Middleware
{
public class TemplateInstance : ITemplate
{
public string Name { get; } = "custom2";
public Task RunAsync(PublishTemplate publish)
{
var schema =
SchemaBuilder.Create("Blogs")
.AddString("Title", f => f
.Properties(p => p with
{
MaxLength = 100
})
.Required())
.AddString("Slug", f => f
.Properties(p => p with
{
MaxLength = 100
})
.Required()
.Disabled())
.AddString("Text", f => f
.Properties(p => p with
{
Editor = StringFieldEditor.RichText,
MaxLength = 1000,
MinLength = 200
})
.Required())
.Build();
return publish(schema);
}
}
}

63
backend/extensions/Squidex.Extensions/Samples/Middleware/TemplateMiddleware.cs

@ -1,63 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Templates.Builders;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Extensions.Samples.Middleware
{
public sealed class TemplateMiddleware : ICustomCommandMiddleware
{
public async Task HandleAsync(CommandContext context, NextDelegate next)
{
await next(context);
if (context.Command is CreateApp createApp && context.IsCompleted && createApp.Template == "custom")
{
var appId = NamedId.Of(createApp.AppId, createApp.Name);
var publish = new Func<IAppCommand, Task>(command =>
{
command.AppId = appId;
return context.CommandBus.PublishAsync(command);
});
var schema =
SchemaBuilder.Create("Pages")
.AddString("Title", f => f
.Properties(p => p with
{
MaxLength = 100
})
.Required())
.AddString("Slug", f => f
.Properties(p => p with
{
MaxLength = 100
})
.Required()
.Disabled())
.AddString("Text", f => f
.Properties(p => p with
{
Editor = StringFieldEditor.RichText,
MaxLength = 1000,
MinLength = 200
})
.Required())
.Build();
await publish(schema);
}
}
}
}

25
backend/extensions/Squidex.Extensions/Samples/Middleware/TemplatePlugin.cs

@ -1,25 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Entities.Apps.Templates;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Plugins;
namespace Squidex.Extensions.Samples.Middleware
{
public sealed class TemplatePlugin : IPlugin
{
public void ConfigureServices(IServiceCollection services, IConfiguration config)
{
services.AddSingleton<ICustomCommandMiddleware, TemplateMiddleware>();
services.AddSingleton<ITemplate, TemplateInstance>();
}
}
}

4
backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj

@ -10,12 +10,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Algolia.Search" Version="6.12.0" /> <PackageReference Include="Algolia.Search" Version="6.12.0" />
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.0.0-beta.3" />
<PackageReference Include="Azure.Search.Documents" Version="11.3.0" /> <PackageReference Include="Azure.Search.Documents" Version="11.3.0" />
<PackageReference Include="Confluent.Apache.Avro" Version="1.7.7.7" /> <PackageReference Include="Confluent.Apache.Avro" Version="1.7.7.7" />
<PackageReference Include="Confluent.Kafka" Version="1.8.2" /> <PackageReference Include="Confluent.Kafka" Version="1.8.2" />
<PackageReference Include="Confluent.SchemaRegistry.Serdes" Version="1.3.0" /> <PackageReference Include="Confluent.SchemaRegistry.Serdes" Version="1.3.0" />
<PackageReference Include="CoreTweet" Version="1.0.0.483" /> <PackageReference Include="CoreTweet" Version="1.0.0.483" />
<PackageReference Include="Elasticsearch.Net" Version="7.17.0" /> <PackageReference Include="Elasticsearch.Net" Version="7.13.0" />
<PackageReference Include="Google.Cloud.Diagnostics.Common" Version="4.4.0" /> <PackageReference Include="Google.Cloud.Diagnostics.Common" Version="4.4.0" />
<PackageReference Include="Google.Cloud.Logging.V2" Version="3.4.0" /> <PackageReference Include="Google.Cloud.Logging.V2" Version="3.4.0" />
<PackageReference Include="Meziantou.Analyzer" Version="1.0.694"> <PackageReference Include="Meziantou.Analyzer" Version="1.0.694">
@ -29,7 +30,6 @@
<PackageReference Include="Microsoft.OData.Core" Version="7.10.0" /> <PackageReference Include="Microsoft.OData.Core" Version="7.10.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NodaTime" Version="3.0.9" /> <PackageReference Include="NodaTime" Version="3.0.9" />
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.0.0-beta.3" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.1.0" /> <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.1.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.2.0-beta1" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.2.0-beta1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.0.0-rc8" /> <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.0.0-rc8" />

40
backend/i18n/frontend_en.json

@ -16,16 +16,11 @@
"apps.create": "Create App", "apps.create": "Create App",
"apps.createBlankApp": "New App", "apps.createBlankApp": "New App",
"apps.createBlankAppDescription": "Create a new blank app without content and schemas.", "apps.createBlankAppDescription": "Create a new blank app without content and schemas.",
"apps.createBlogApp": "New Blog Sample",
"apps.createBlogAppDescription": "Start with our ready to use blog.",
"apps.createFailed": "Failed to create app. Please reload.", "apps.createFailed": "Failed to create app. Please reload.",
"apps.createProfileApp": "New Profile Sample",
"apps.createProfileAppDescription": "Create your profile page.",
"apps.createWithTemplate": "Create {template} Sample", "apps.createWithTemplate": "Create {template} Sample",
"apps.delete": "Delete App", "apps.delete": "Delete App",
"apps.deleteConfirmText": "Do you really want to delete this app?", "apps.deleteConfirmText": "Do you really want to delete this app?",
"apps.deleteConfirmTitle": "I understand. Delete my App", "apps.deleteConfirmTitle": "I understand. Delete my App",
"apps.deleteFailed": "Failed to delete app. Please reload.",
"apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.", "apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.",
"apps.empty": "You are not collaborating on any apps yet", "apps.empty": "You are not collaborating on any apps yet",
"apps.generalSettings": "General", "apps.generalSettings": "General",
@ -266,6 +261,7 @@
"common.disable": "Disable", "common.disable": "Disable",
"common.disabled": "Disabled", "common.disabled": "Disabled",
"common.displayName": "Display Name", "common.displayName": "Display Name",
"common.documentation": "Documentation",
"common.edit": "Edit", "common.edit": "Edit",
"common.editing": "Editing", "common.editing": "Editing",
"common.email": "Email", "common.email": "Email",
@ -335,14 +331,14 @@
"common.queryOperators.contains": "contains", "common.queryOperators.contains": "contains",
"common.queryOperators.empty": "is empty", "common.queryOperators.empty": "is empty",
"common.queryOperators.endsWith": "ends with", "common.queryOperators.endsWith": "ends with",
"common.queryOperators.eq": "is equals to", "common.queryOperators.eq": "is equal to",
"common.queryOperators.exists": "exists", "common.queryOperators.exists": "exists",
"common.queryOperators.ge": "is greater than or equals to", "common.queryOperators.ge": "is greater than or equal to",
"common.queryOperators.gt": "is greater than", "common.queryOperators.gt": "is greater than",
"common.queryOperators.le": "is less than or equals to", "common.queryOperators.le": "is less than or equal to",
"common.queryOperators.lt": "is less than", "common.queryOperators.lt": "is less than",
"common.queryOperators.matchs": "matchs", "common.queryOperators.matchs": "matchs",
"common.queryOperators.ne": "is not equals to", "common.queryOperators.ne": "is not equal to",
"common.queryOperators.startsWith": "starts with", "common.queryOperators.startsWith": "starts with",
"common.refresh": "Refresh", "common.refresh": "Refresh",
"common.remember": "Don't ask again", "common.remember": "Don't ask again",
@ -380,6 +376,7 @@
"common.tagAddSchema": ", to add schema", "common.tagAddSchema": ", to add schema",
"common.tags": "Tags", "common.tags": "Tags",
"common.tagsAll": "All tags", "common.tagsAll": "All tags",
"common.templates": "Templates",
"common.time": "Time", "common.time": "Time",
"common.to": "To", "common.to": "To",
"common.update": "Update", "common.update": "Update",
@ -428,9 +425,7 @@
"contents.create": "New", "contents.create": "New",
"contents.createContentTooltip": "New Content", "contents.createContentTooltip": "New Content",
"contents.created": "Content created successfully.", "contents.created": "Content created successfully.",
"contents.createdByFieldDescription": "The user who created the content item.",
"contents.createFailed": "Failed to create content. Please reload.", "contents.createFailed": "Failed to create content. Please reload.",
"contents.createFieldDescription": "The date time when the content item was created.",
"contents.createPageTitle": "Create Content", "contents.createPageTitle": "Create Content",
"contents.createTitle": "New Content", "contents.createTitle": "New Content",
"contents.currentStatusLabel": "Current Version", "contents.currentStatusLabel": "Current Version",
@ -444,23 +439,19 @@
"contents.draftNew": "New Draft", "contents.draftNew": "New Draft",
"contents.draftStatus": "New Version", "contents.draftStatus": "New Version",
"contents.editPageTitle": "Edit Content", "contents.editPageTitle": "Edit Content",
"contents.fieldFullscreen": "Focus on this field and toggle fullscreen mode.",
"contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.", "contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.",
"contents.inspectContent": "Content", "contents.inspectContent": "Content",
"contents.inspectData": "Data", "contents.inspectData": "Data",
"contents.inspectFlatData": "Flat Data", "contents.inspectFlatData": "Flat Data",
"contents.invariantFieldDescription": "The '{fieldName}' field of the content item.",
"contents.languageModeAll": "All Languages", "contents.languageModeAll": "All Languages",
"contents.languageModeSingle": "Single Language", "contents.languageModeSingle": "Single Language",
"contents.lastModifiedByFieldDescription": "The user who modified the content item the last time.",
"contents.lastModifiedFieldDescription": "The date time when the content item was modified the last time.",
"contents.lastUpdatedLabel": "Last Updated", "contents.lastUpdatedLabel": "Last Updated",
"contents.loadContent": "Load", "contents.loadContent": "Load",
"contents.loadContentFailed": "Failed to load content. Please reload.", "contents.loadContentFailed": "Failed to load content. Please reload.",
"contents.loadDataFailed": "Failed to load data. Please reload.", "contents.loadDataFailed": "Failed to load data. Please reload.",
"contents.loadFailed": "Failed to load contents. Please reload.", "contents.loadFailed": "Failed to load contents. Please reload.",
"contents.loadVersionFailed": "Failed to load a new version. Please reload.", "contents.loadVersionFailed": "Failed to load a new version. Please reload.",
"contents.localizedFieldDescription": "The '{fieldName}' field of the content item (localized).",
"contents.newStatusFieldDescription": "The new status of the content item.",
"contents.noReference": "- No Reference -", "contents.noReference": "- No Reference -",
"contents.noReferences": "This content has no references.", "contents.noReferences": "This content has no references.",
"contents.noReferencing": "This content is not referenced by another item.", "contents.noReferencing": "This content is not referenced by another item.",
@ -483,13 +474,12 @@
"contents.scheduledBy": "by", "contents.scheduledBy": "by",
"contents.scheduledTo": "to", "contents.scheduledTo": "to",
"contents.scheduledToLabel": "Scheduled to", "contents.scheduledToLabel": "Scheduled to",
"contents.scheduledTooltip": "Will be set to '{status}' at {time}.",
"contents.schemasPageTitle": "Contents", "contents.schemasPageTitle": "Contents",
"contents.searchPlaceholder": "Fulltext search", "contents.searchPlaceholder": "Fulltext search",
"contents.searchSchemasPlaceholder": "Search", "contents.searchSchemasPlaceholder": "Search",
"contents.selectionCount": "{count} items selected", "contents.selectionCount": "{count} items selected",
"contents.statusFieldDescription": "The status of the content item.",
"contents.statusQueries": "Status Queries", "contents.statusQueries": "Status Queries",
"contents.stockPhotoEmpty": "Nothing selected",
"contents.stockPhotoSearch": "Search for Photos by Unsplash", "contents.stockPhotoSearch": "Search for Photos by Unsplash",
"contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.", "contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.",
"contents.tableHeaders.created": "Created", "contents.tableHeaders.created": "Created",
@ -507,7 +497,7 @@
"contents.unsavedChangesText": "You have unsaved changes. Do you want to load them now?", "contents.unsavedChangesText": "You have unsaved changes. Do you want to load them now?",
"contents.unsavedChangesTitle": "Unsaved changes", "contents.unsavedChangesTitle": "Unsaved changes",
"contents.unsetValue": "Unset value", "contents.unsetValue": "Unset value",
"contents.unsetValueConfirmText": "If you unset the value you might loose your changes.\n\nDo you really want to do it?", "contents.unsetValueConfirmText": "If you unset the value you might lose your changes.\n\nDo you really want to do it?",
"contents.unsetValueConfirmTitle": "Do you want to unset the value?", "contents.unsetValueConfirmTitle": "Do you want to unset the value?",
"contents.updated": "Content updated successfully.", "contents.updated": "Content updated successfully.",
"contents.updateFailed": "Failed to update content. Please reload.", "contents.updateFailed": "Failed to update content. Please reload.",
@ -515,7 +505,6 @@
"contents.validationHint": "Please remember to check all languages when you see validation errors.", "contents.validationHint": "Please remember to check all languages when you see validation errors.",
"contents.versionCompare": "Compare", "contents.versionCompare": "Compare",
"contents.versionDelete": "Delete this Version", "contents.versionDelete": "Delete this Version",
"contents.versionFieldDescription": "The version of the content item",
"contents.versionViewing": "Viewing version **{version}**.", "contents.versionViewing": "Viewing version **{version}**.",
"contents.viewLatest": "View latest", "contents.viewLatest": "View latest",
"contents.viewReset": "Reset Default View", "contents.viewReset": "Reset Default View",
@ -671,6 +660,7 @@
"rules.actionData": "Action Data", "rules.actionData": "Action Data",
"rules.actionHint": "The selection of the action type cannot be changed later.", "rules.actionHint": "The selection of the action type cannot be changed later.",
"rules.addSchema": "Add Schema", "rules.addSchema": "Add Schema",
"rules.advancedFormattingHint": "You can use advanced formatting",
"rules.cancelFailed": "Failed to cancel rule. Please reload.", "rules.cancelFailed": "Failed to cancel rule. Please reload.",
"rules.conditionHint": "Optional condition as javascript expression", "rules.conditionHint": "Optional condition as javascript expression",
"rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example", "rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example",
@ -731,7 +721,7 @@
"rules.simulateTooltip": "Simulate this rules using the last 100 events.", "rules.simulateTooltip": "Simulate this rules using the last 100 events.",
"rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.", "rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.",
"rules.simulation.actionExecuted": "Job is taken from the queue and executed.", "rules.simulation.actionExecuted": "Job is taken from the queue and executed.",
"rules.simulation.errorConditionDoesNotMatch": "STOP: Javescript expressions in trigger do not match to the event.", "rules.simulation.errorConditionDoesNotMatch": "STOP: Javascript expressions in trigger do not match to the event.",
"rules.simulation.errorConditionPrecheckDoesNotMatch": "STOP: Condition in trigger does not match to the event.", "rules.simulation.errorConditionPrecheckDoesNotMatch": "STOP: Condition in trigger does not match to the event.",
"rules.simulation.errorDisabled": "STOP: Rule is disabled.", "rules.simulation.errorDisabled": "STOP: Rule is disabled.",
"rules.simulation.errorFailed": "Internal Error.", "rules.simulation.errorFailed": "Internal Error.",
@ -741,7 +731,7 @@
"rules.simulation.errorTooOld": "STOP: Event is too old.", "rules.simulation.errorTooOld": "STOP: Event is too old.",
"rules.simulation.errorWrongEvent": "STOP: Event does not match to the trigger.", "rules.simulation.errorWrongEvent": "STOP: Event does not match to the trigger.",
"rules.simulation.errorWrongEventForTrigger": "STOP: Event does not match to the trigger.", "rules.simulation.errorWrongEventForTrigger": "STOP: Event does not match to the trigger.",
"rules.simulation.eventConditionEvaluated": "Enriched event is evaluated, whether it matches to the conditions and javascript expressions in the tigger.", "rules.simulation.eventConditionEvaluated": "Enriched event is evaluated, whether it matchs to the conditions and javascript expressions in the trigger.",
"rules.simulation.eventEnriched": "Event is enriched with additional data", "rules.simulation.eventEnriched": "Event is enriched with additional data",
"rules.simulation.eventQueried": "Event is queried from the database", "rules.simulation.eventQueried": "Event is queried from the database",
"rules.simulation.eventTriggerChecked": "Event is tested to see if it matchs to the trigger and the basic conditions.", "rules.simulation.eventTriggerChecked": "Event is tested to see if it matchs to the trigger and the basic conditions.",
@ -978,6 +968,10 @@
"start.loginHint": "The login button will open a new popup. Once you are logged in successful we will redirect you to the Squidex management portal.", "start.loginHint": "The login button will open a new popup. Once you are logged in successful we will redirect you to the Squidex management portal.",
"start.madeBy": "Proudly made by", "start.madeBy": "Proudly made by",
"start.madeByCopyright": "Sebastian Stehle and Contributors, 2016-2021", "start.madeByCopyright": "Sebastian Stehle and Contributors, 2016-2021",
"templates.cliHint": "Download the CLI at https://github.com/squidex/squidex-samples to use the templates.",
"templates.loadFailed": "Failed to load templates. Please reload.",
"templates.refreshTooltip": "Refresh Templates",
"templates.reloaded": "Templates reloaded.",
"tour.joinForum": "Join our Forum", "tour.joinForum": "Join our Forum",
"tour.joinGithub": "Join us on Github", "tour.joinGithub": "Join us on Github",
"tour.skip": "Skip Tour", "tour.skip": "Skip Tour",
@ -991,7 +985,7 @@
"tour.step3Text": "Content is the actual data in your app which is grouped by the schema.\n\nSelect a published schema first, then add content for this schema.", "tour.step3Text": "Content is the actual data in your app which is grouped by the schema.\n\nSelect a published schema first, then add content for this schema.",
"tour.step4Next": "Got It!", "tour.step4Next": "Got It!",
"tour.step4Text": "The assets contains all files that can also be linked to your content. For example images, videos or documents.\n\nYou can upload the assets here and use them later or also upload them directly when you create a new content item with an asset field.", "tour.step4Text": "The assets contains all files that can also be linked to your content. For example images, videos or documents.\n\nYou can upload the assets here and use them later or also upload them directly when you create a new content item with an asset field.",
"tour.step5Text": "But that's not all of the support we can provide.\n\nYou can got to https://docs.squidex.io to read more.\n\nDo you want to join our community?", "tour.step5Text": "But that's not all of the support we can provide.\n\nYou can go to https://docs.squidex.io to read more.\n\nDo you want to join our community?",
"tour.step5Title": "Awesome, now you know the basics!", "tour.step5Title": "Awesome, now you know the basics!",
"tour.tooltipConfirm": "Got It", "tour.tooltipConfirm": "Got It",
"tour.tooltipStop": "Stop Tour", "tour.tooltipStop": "Stop Tour",

28
backend/i18n/frontend_it.json

@ -16,16 +16,11 @@
"apps.create": "Crea un'App", "apps.create": "Crea un'App",
"apps.createBlankApp": "Nuova App.", "apps.createBlankApp": "Nuova App.",
"apps.createBlankAppDescription": "Crea una app vuota senza contenuti o schema.", "apps.createBlankAppDescription": "Crea una app vuota senza contenuti o schema.",
"apps.createBlogApp": "Nuovo blog",
"apps.createBlogAppDescription": "Inizia con un blog.",
"apps.createFailed": "Non è stato possibile creare l'app. Per favore ricarica.", "apps.createFailed": "Non è stato possibile creare l'app. Per favore ricarica.",
"apps.createProfileApp": "Nuovo Profilo",
"apps.createProfileAppDescription": "Crea la tua pagina del profilo.",
"apps.createWithTemplate": "Create un esempio di {template}", "apps.createWithTemplate": "Create un esempio di {template}",
"apps.delete": "Delete App", "apps.delete": "Delete App",
"apps.deleteConfirmText": "Do you really want to delete this app?", "apps.deleteConfirmText": "Do you really want to delete this app?",
"apps.deleteConfirmTitle": "I understand. Delete my App", "apps.deleteConfirmTitle": "I understand. Delete my App",
"apps.deleteFailed": "Failed to delete app. Please reload.",
"apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.", "apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.",
"apps.empty": "Non stai ancora collaborando su nessuna app", "apps.empty": "Non stai ancora collaborando su nessuna app",
"apps.generalSettings": "Generale", "apps.generalSettings": "Generale",
@ -266,6 +261,7 @@
"common.disable": "Disable", "common.disable": "Disable",
"common.disabled": "Disabled", "common.disabled": "Disabled",
"common.displayName": "Nome visualizzato", "common.displayName": "Nome visualizzato",
"common.documentation": "Documentation",
"common.edit": "Modifica", "common.edit": "Modifica",
"common.editing": "Editing", "common.editing": "Editing",
"common.email": "Email", "common.email": "Email",
@ -380,6 +376,7 @@
"common.tagAddSchema": ", aggiungi schema", "common.tagAddSchema": ", aggiungi schema",
"common.tags": "Tag", "common.tags": "Tag",
"common.tagsAll": "Tutti i tag", "common.tagsAll": "Tutti i tag",
"common.templates": "Templates",
"common.time": "Ora", "common.time": "Ora",
"common.to": "To", "common.to": "To",
"common.update": "Aggiorna", "common.update": "Aggiorna",
@ -428,9 +425,7 @@
"contents.create": "Nuovo", "contents.create": "Nuovo",
"contents.createContentTooltip": "Nuovo contenuto", "contents.createContentTooltip": "Nuovo contenuto",
"contents.created": "Contenuto creato con successo.", "contents.created": "Contenuto creato con successo.",
"contents.createdByFieldDescription": "L'utente che ha creato l'elemento del contenuto.",
"contents.createFailed": "Non è stato possibile creare il contenuto. Per favore ricarica.", "contents.createFailed": "Non è stato possibile creare il contenuto. Per favore ricarica.",
"contents.createFieldDescription": "La data e l'ora di creazione del contenuto.",
"contents.createPageTitle": "Crea un contenuto", "contents.createPageTitle": "Crea un contenuto",
"contents.createTitle": "Nuovo Contenuto", "contents.createTitle": "Nuovo Contenuto",
"contents.currentStatusLabel": "Versione corrente", "contents.currentStatusLabel": "Versione corrente",
@ -444,23 +439,19 @@
"contents.draftNew": "Nuova bozza", "contents.draftNew": "Nuova bozza",
"contents.draftStatus": "Nuova versione", "contents.draftStatus": "Nuova versione",
"contents.editPageTitle": "Modifica contenuto", "contents.editPageTitle": "Modifica contenuto",
"contents.fieldFullscreen": "Focus on this field and toggle fullscreen mode.",
"contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.", "contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.",
"contents.inspectContent": "Content", "contents.inspectContent": "Content",
"contents.inspectData": "Data", "contents.inspectData": "Data",
"contents.inspectFlatData": "Flat Data", "contents.inspectFlatData": "Flat Data",
"contents.invariantFieldDescription": "Il campo '{fieldName}' del contenuto.",
"contents.languageModeAll": "Tutte le lingue", "contents.languageModeAll": "Tutte le lingue",
"contents.languageModeSingle": "Una sola lingua", "contents.languageModeSingle": "Una sola lingua",
"contents.lastModifiedByFieldDescription": "L'utente che ha modificato l'elemento l'ultima volta.",
"contents.lastModifiedFieldDescription": "La data e l'ora dell'ultima modifica del contenuto.",
"contents.lastUpdatedLabel": "Ultimo aggiornamento", "contents.lastUpdatedLabel": "Ultimo aggiornamento",
"contents.loadContent": "Carica", "contents.loadContent": "Carica",
"contents.loadContentFailed": "Non è stato possibile caricare il contenuto. Per favore ricarica.", "contents.loadContentFailed": "Non è stato possibile caricare il contenuto. Per favore ricarica.",
"contents.loadDataFailed": "Non è stato possibile caricare i dati. Per favore ricarica.", "contents.loadDataFailed": "Non è stato possibile caricare i dati. Per favore ricarica.",
"contents.loadFailed": "Non è stato possibile caricare i contenuti. Per favore ricarica.", "contents.loadFailed": "Non è stato possibile caricare i contenuti. Per favore ricarica.",
"contents.loadVersionFailed": "Non è stato possibile creare una nuova versione. Per favore ricarica.", "contents.loadVersionFailed": "Non è stato possibile creare una nuova versione. Per favore ricarica.",
"contents.localizedFieldDescription": "Il campo '{fieldName}' del contenuto (localizzato).",
"contents.newStatusFieldDescription": "Nuovo stato per l'elemento del contenuto.",
"contents.noReference": "- Nessun riferimento collegato -", "contents.noReference": "- Nessun riferimento collegato -",
"contents.noReferences": "Questo contenuto non ha collegamenti.", "contents.noReferences": "Questo contenuto non ha collegamenti.",
"contents.noReferencing": "Questo contenuto non è collegato da altri contenuti.", "contents.noReferencing": "Questo contenuto non è collegato da altri contenuti.",
@ -483,13 +474,12 @@
"contents.scheduledBy": "by", "contents.scheduledBy": "by",
"contents.scheduledTo": "a", "contents.scheduledTo": "a",
"contents.scheduledToLabel": "Scheduled to", "contents.scheduledToLabel": "Scheduled to",
"contents.scheduledTooltip": "Will be set to '{status}' at {time}.",
"contents.schemasPageTitle": "Contenuti", "contents.schemasPageTitle": "Contenuti",
"contents.searchPlaceholder": "Ricerca testuale", "contents.searchPlaceholder": "Ricerca testuale",
"contents.searchSchemasPlaceholder": "Cerca schemi...", "contents.searchSchemasPlaceholder": "Cerca schemi...",
"contents.selectionCount": "{count} elementi selezionati", "contents.selectionCount": "{count} elementi selezionati",
"contents.statusFieldDescription": "Stato dell'elemento del contenuto.",
"contents.statusQueries": "Stato Query", "contents.statusQueries": "Stato Query",
"contents.stockPhotoEmpty": "Nessuna selezione",
"contents.stockPhotoSearch": "Cerca foto su Unsplash", "contents.stockPhotoSearch": "Cerca foto su Unsplash",
"contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.", "contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.",
"contents.tableHeaders.created": "Creato", "contents.tableHeaders.created": "Creato",
@ -515,7 +505,6 @@
"contents.validationHint": "Ricorda di verificare tutte le lingue quando vedi errori di validazione.", "contents.validationHint": "Ricorda di verificare tutte le lingue quando vedi errori di validazione.",
"contents.versionCompare": "Confronta", "contents.versionCompare": "Confronta",
"contents.versionDelete": "Cancella questa Versione", "contents.versionDelete": "Cancella questa Versione",
"contents.versionFieldDescription": "La versione dell'elemento del contenuto",
"contents.versionViewing": "Stai guardando la versione **{version}**.", "contents.versionViewing": "Stai guardando la versione **{version}**.",
"contents.viewLatest": "Visualizza l'ultima", "contents.viewLatest": "Visualizza l'ultima",
"contents.viewReset": "Imposta la visualizzazione predefinita", "contents.viewReset": "Imposta la visualizzazione predefinita",
@ -671,6 +660,7 @@
"rules.actionData": "Action Data", "rules.actionData": "Action Data",
"rules.actionHint": "The selection of the action type cannot be changed later.", "rules.actionHint": "The selection of the action type cannot be changed later.",
"rules.addSchema": "Add Schema", "rules.addSchema": "Add Schema",
"rules.advancedFormattingHint": "You can use advanced formatting",
"rules.cancelFailed": "Non è stato possibile eliminare la regola. Per favore ricarica.", "rules.cancelFailed": "Non è stato possibile eliminare la regola. Per favore ricarica.",
"rules.conditionHint": "Optional condition as javascript expression", "rules.conditionHint": "Optional condition as javascript expression",
"rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example", "rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example",
@ -731,7 +721,7 @@
"rules.simulateTooltip": "Simulate this rules using the last 100 events.", "rules.simulateTooltip": "Simulate this rules using the last 100 events.",
"rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.", "rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.",
"rules.simulation.actionExecuted": "Job is taken from the queue and executed.", "rules.simulation.actionExecuted": "Job is taken from the queue and executed.",
"rules.simulation.errorConditionDoesNotMatch": "STOP: Javescript expressions in trigger do not match to the event.", "rules.simulation.errorConditionDoesNotMatch": "STOP: Javascript expressions in trigger do not match to the event.",
"rules.simulation.errorConditionPrecheckDoesNotMatch": "STOP: Condition in trigger does not match to the event.", "rules.simulation.errorConditionPrecheckDoesNotMatch": "STOP: Condition in trigger does not match to the event.",
"rules.simulation.errorDisabled": "STOP: Rule is disabled.", "rules.simulation.errorDisabled": "STOP: Rule is disabled.",
"rules.simulation.errorFailed": "Internal Error.", "rules.simulation.errorFailed": "Internal Error.",
@ -741,7 +731,7 @@
"rules.simulation.errorTooOld": "STOP: Event is too old.", "rules.simulation.errorTooOld": "STOP: Event is too old.",
"rules.simulation.errorWrongEvent": "STOP: Event does not match to the trigger.", "rules.simulation.errorWrongEvent": "STOP: Event does not match to the trigger.",
"rules.simulation.errorWrongEventForTrigger": "STOP: Event does not match to the trigger.", "rules.simulation.errorWrongEventForTrigger": "STOP: Event does not match to the trigger.",
"rules.simulation.eventConditionEvaluated": "Enriched event is evaluated, whether it matches to the conditions and javascript expressions in the tigger.", "rules.simulation.eventConditionEvaluated": "Enriched event is evaluated, whether it matchs to the conditions and javascript expressions in the trigger.",
"rules.simulation.eventEnriched": "Event is enriched with additional data", "rules.simulation.eventEnriched": "Event is enriched with additional data",
"rules.simulation.eventQueried": "Event is queried from the database", "rules.simulation.eventQueried": "Event is queried from the database",
"rules.simulation.eventTriggerChecked": "Event is tested to see if it matchs to the trigger and the basic conditions.", "rules.simulation.eventTriggerChecked": "Event is tested to see if it matchs to the trigger and the basic conditions.",
@ -978,6 +968,10 @@
"start.loginHint": "Il pulsante per accedere aprirà un popup. Una volta effettuato l'accesso sarai indirizzato al portale per la gestione di Squidex.", "start.loginHint": "Il pulsante per accedere aprirà un popup. Una volta effettuato l'accesso sarai indirizzato al portale per la gestione di Squidex.",
"start.madeBy": "Realizzato con orgoglio da", "start.madeBy": "Realizzato con orgoglio da",
"start.madeByCopyright": "Sebastian Stehle e Collaboratori, 2016-2020", "start.madeByCopyright": "Sebastian Stehle e Collaboratori, 2016-2020",
"templates.cliHint": "Download the CLI at https://github.com/squidex/squidex-samples to use the templates.",
"templates.loadFailed": "Failed to load templates. Please reload.",
"templates.refreshTooltip": "Refresh Templates",
"templates.reloaded": "Templates reloaded.",
"tour.joinForum": "Unisciti al nostro Forum", "tour.joinForum": "Unisciti al nostro Forum",
"tour.joinGithub": "Unisciti a Github", "tour.joinGithub": "Unisciti a Github",
"tour.skip": "Salta il Tour", "tour.skip": "Salta il Tour",

24
backend/i18n/frontend_nl.json

@ -16,16 +16,11 @@
"apps.create": "App maken", "apps.create": "App maken",
"apps.createBlankApp": "Nieuwe app.", "apps.createBlankApp": "Nieuwe app.",
"apps.createBlankAppDescription": "Maak een nieuwe lege app zonder inhoud en schema's.", "apps.createBlankAppDescription": "Maak een nieuwe lege app zonder inhoud en schema's.",
"apps.createBlogApp": "Nieuw blogvoorbeeld",
"apps.createBlogAppDescription": "Begin met onze gebruiksklare blog.",
"apps.createFailed": "Maken van app is mislukt. Laad opnieuw.", "apps.createFailed": "Maken van app is mislukt. Laad opnieuw.",
"apps.createProfileApp": "Nieuw profielvoorbeeld",
"apps.createProfileAppDescription": "Maak uw profielpagina.",
"apps.createWithTemplate": "Maak {template} voorbeeld", "apps.createWithTemplate": "Maak {template} voorbeeld",
"apps.delete": "Verwijder App", "apps.delete": "Verwijder App",
"apps.deleteConfirmText": "Weet je zeker dat je de app wilt verwijderen?", "apps.deleteConfirmText": "Weet je zeker dat je de app wilt verwijderen?",
"apps.deleteConfirmTitle": "I begrijp het, verwijder mijn app", "apps.deleteConfirmTitle": "I begrijp het, verwijder mijn app",
"apps.deleteFailed": "App verwijderen mislukt. Herlaad de pagina.",
"apps.deleteWarning": "Wanneer je de app verwijderd, kan je niet meer terug. Al je gegevens worden in de achtergrond verwijderd.", "apps.deleteWarning": "Wanneer je de app verwijderd, kan je niet meer terug. Al je gegevens worden in de achtergrond verwijderd.",
"apps.empty": "Je werkt nog niet samen aan een app", "apps.empty": "Je werkt nog niet samen aan een app",
"apps.generalSettings": "Algemeen", "apps.generalSettings": "Algemeen",
@ -266,6 +261,7 @@
"common.disable": "Uitzetten", "common.disable": "Uitzetten",
"common.disabled": "Uitgezet", "common.disabled": "Uitgezet",
"common.displayName": "Weergavenaam", "common.displayName": "Weergavenaam",
"common.documentation": "Documentation",
"common.edit": "Bewerken", "common.edit": "Bewerken",
"common.editing": "Bewerken", "common.editing": "Bewerken",
"common.email": "E-mail", "common.email": "E-mail",
@ -380,6 +376,7 @@
"common.tagAddSchema": ", om schema toe te voegen", "common.tagAddSchema": ", om schema toe te voegen",
"common.tags": "Tags", "common.tags": "Tags",
"common.tagsAll": "Alle tags", "common.tagsAll": "Alle tags",
"common.templates": "Templates",
"common.time": "Tijd", "common.time": "Tijd",
"common.to": "Naar", "common.to": "Naar",
"common.update": "Update", "common.update": "Update",
@ -428,9 +425,7 @@
"contents.create": "Nieuw", "contents.create": "Nieuw",
"contents.createContentTooltip": "Nieuwe inhoud", "contents.createContentTooltip": "Nieuwe inhoud",
"contents.created": "Inhoud succesvol aangemaakt.", "contents.created": "Inhoud succesvol aangemaakt.",
"contents.createdByFieldDescription": "De gebruiker die het inhoudsitem heeft gemaakt.",
"contents.createFailed": "Maken van inhoud is mislukt. Laad opnieuw.", "contents.createFailed": "Maken van inhoud is mislukt. Laad opnieuw.",
"contents.createFieldDescription": "De datum en tijd waarop het inhoudsitem is gemaakt.",
"contents.createPageTitle": "Inhoud maken", "contents.createPageTitle": "Inhoud maken",
"contents.createTitle": "Nieuwe inhoud", "contents.createTitle": "Nieuwe inhoud",
"contents.currentStatusLabel": "Huidige versie", "contents.currentStatusLabel": "Huidige versie",
@ -444,23 +439,19 @@
"contents.draftNew": "Nieuw concept", "contents.draftNew": "Nieuw concept",
"contents.draftStatus": "Nieuwe versie", "contents.draftStatus": "Nieuwe versie",
"contents.editPageTitle": "Inhoud bewerken", "contents.editPageTitle": "Inhoud bewerken",
"contents.fieldFullscreen": "Focus on this field and toggle fullscreen mode.",
"contents.idPlaceholder": "Definieer een aangepaste ID of laat leeg om Squidex er een te laten genereren.", "contents.idPlaceholder": "Definieer een aangepaste ID of laat leeg om Squidex er een te laten genereren.",
"contents.inspectContent": "Inhoud", "contents.inspectContent": "Inhoud",
"contents.inspectData": "Data", "contents.inspectData": "Data",
"contents.inspectFlatData": "Platte Data", "contents.inspectFlatData": "Platte Data",
"contents.invariantFieldDescription": "Het veld '{fieldName}' van het inhoudsitem.",
"contents.languageModeAll": "Alle talen", "contents.languageModeAll": "Alle talen",
"contents.languageModeSingle": "Enkele taal", "contents.languageModeSingle": "Enkele taal",
"contents.lastModifiedByFieldDescription": "De gebruiker die het inhoudsitem de laatste keer heeft gewijzigd.",
"contents.lastModifiedFieldDescription": "De datum en tijd waarop het inhoudsitem de laatste keer is gewijzigd.",
"contents.lastUpdatedLabel": "Laatst bijgewerkt", "contents.lastUpdatedLabel": "Laatst bijgewerkt",
"contents.loadContent": "Laden", "contents.loadContent": "Laden",
"contents.loadContentFailed": "Kan inhoud niet laden. Laad opnieuw.", "contents.loadContentFailed": "Kan inhoud niet laden. Laad opnieuw.",
"contents.loadDataFailed": "Laden van gegevens is mislukt. Laad opnieuw.", "contents.loadDataFailed": "Laden van gegevens is mislukt. Laad opnieuw.",
"contents.loadFailed": "Laden van inhoud is mislukt. Laad opnieuw.", "contents.loadFailed": "Laden van inhoud is mislukt. Laad opnieuw.",
"contents.loadVersionFailed": "Versie van een nieuwe versie is mislukt. Laad opnieuw.", "contents.loadVersionFailed": "Versie van een nieuwe versie is mislukt. Laad opnieuw.",
"contents.localizedFieldDescription": "Het veld '{fieldName}' van het inhoudsitem (gelokaliseerd).",
"contents.newStatusFieldDescription": "De nieuwe status van het item.",
"contents.noReference": "- Geen referentie -", "contents.noReference": "- Geen referentie -",
"contents.noReferences": "Deze inhoud heeft geen referenties.", "contents.noReferences": "Deze inhoud heeft geen referenties.",
"contents.noReferencing": "Naar deze inhoud wordt niet verwezen door een ander item.", "contents.noReferencing": "Naar deze inhoud wordt niet verwezen door een ander item.",
@ -483,13 +474,12 @@
"contents.scheduledBy": "door", "contents.scheduledBy": "door",
"contents.scheduledTo": "naar", "contents.scheduledTo": "naar",
"contents.scheduledToLabel": "Ingepland tot", "contents.scheduledToLabel": "Ingepland tot",
"contents.scheduledTooltip": "Will be set to '{status}' at {time}.",
"contents.schemasPageTitle": "Inhoud", "contents.schemasPageTitle": "Inhoud",
"contents.searchPlaceholder": "Zoeken in volledige tekst", "contents.searchPlaceholder": "Zoeken in volledige tekst",
"contents.searchSchemasPlaceholder": "Zoek schema's ...", "contents.searchSchemasPlaceholder": "Zoek schema's ...",
"contents.selectionCount": "{count} items geselecteerd", "contents.selectionCount": "{count} items geselecteerd",
"contents.statusFieldDescription": "De status van het inhoudsitem.",
"contents.statusQueries": "Statusquery's", "contents.statusQueries": "Statusquery's",
"contents.stockPhotoEmpty": "Niets geselecteerd",
"contents.stockPhotoSearch": "Zoeken naar foto's op Unsplash", "contents.stockPhotoSearch": "Zoeken naar foto's op Unsplash",
"contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.", "contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.",
"contents.tableHeaders.created": "Gemaakt", "contents.tableHeaders.created": "Gemaakt",
@ -515,7 +505,6 @@
"contents.validationHint": "Denk eraan om alle talen te controleren wanneer je validatiefouten ziet.", "contents.validationHint": "Denk eraan om alle talen te controleren wanneer je validatiefouten ziet.",
"contents.versionCompare": "Vergelijk", "contents.versionCompare": "Vergelijk",
"contents.versionDelete": "Verwijder deze versie", "contents.versionDelete": "Verwijder deze versie",
"contents.versionFieldDescription": "De versie van het inhoudsitem",
"contents.versionViewing": "Bekijk versie **{version}**.", "contents.versionViewing": "Bekijk versie **{version}**.",
"contents.viewLatest": "Bekijk laatste", "contents.viewLatest": "Bekijk laatste",
"contents.viewReset": "Standaardweergave herstellen", "contents.viewReset": "Standaardweergave herstellen",
@ -671,6 +660,7 @@
"rules.actionData": "Actiegegevens", "rules.actionData": "Actiegegevens",
"rules.actionHint": "De selectie van het actietype kan later niet worden gewijzigd.", "rules.actionHint": "De selectie van het actietype kan later niet worden gewijzigd.",
"rules.addSchema": "Add Schema", "rules.addSchema": "Add Schema",
"rules.advancedFormattingHint": "You can use advanced formatting",
"rules.cancelFailed": "Annuleren van regel is mislukt. Laad opnieuw.", "rules.cancelFailed": "Annuleren van regel is mislukt. Laad opnieuw.",
"rules.conditionHint": "Optional condition as javascript expression", "rules.conditionHint": "Optional condition as javascript expression",
"rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example", "rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example",
@ -978,6 +968,10 @@
"start.loginHint": "De login-knop opent een nieuwe pop-up. Zodra je succesvol bent ingelogd, zullen we je doorverwijzen naar het Squidex beheerportaal.", "start.loginHint": "De login-knop opent een nieuwe pop-up. Zodra je succesvol bent ingelogd, zullen we je doorverwijzen naar het Squidex beheerportaal.",
"start.madeBy": "Met trots gemaakt door", "start.madeBy": "Met trots gemaakt door",
"start.madeByCopyright": "Sebastian Stehle en medewerkers, 2016-2020", "start.madeByCopyright": "Sebastian Stehle en medewerkers, 2016-2020",
"templates.cliHint": "Download the CLI at https://github.com/squidex/squidex-samples to use the templates.",
"templates.loadFailed": "Failed to load templates. Please reload.",
"templates.refreshTooltip": "Refresh Templates",
"templates.reloaded": "Templates reloaded.",
"tour.joinForum": "Word lid van ons forum", "tour.joinForum": "Word lid van ons forum",
"tour.joinGithub": "Bezoek ons ​​op Github", "tour.joinGithub": "Bezoek ons ​​op Github",
"tour.skip": "Tour overslaan", "tour.skip": "Tour overslaan",

28
backend/i18n/frontend_zh.json

@ -16,16 +16,11 @@
"apps.create": "创建应用程序", "apps.create": "创建应用程序",
"apps.createBlankApp": "新应用程序", "apps.createBlankApp": "新应用程序",
"apps.createBlankAppDescription": "创建一个没有内容和Schemas的新空白应用程序。", "apps.createBlankAppDescription": "创建一个没有内容和Schemas的新空白应用程序。",
"apps.createBlogApp": "新博客示例",
"apps.createBlogAppDescription": "从我们准备使用的博客开始。",
"apps.createFailed": "创建应用失败。请重新加载。", "apps.createFailed": "创建应用失败。请重新加载。",
"apps.createProfileApp": "新配置文件示例",
"apps.createProfileAppDescription": "创建您的个人资料页面。",
"apps.createWithTemplate": "创建 {template} 示例", "apps.createWithTemplate": "创建 {template} 示例",
"apps.delete": "Delete App", "apps.delete": "Delete App",
"apps.deleteConfirmText": "Do you really want to delete this app?", "apps.deleteConfirmText": "Do you really want to delete this app?",
"apps.deleteConfirmTitle": "I understand. Delete my App", "apps.deleteConfirmTitle": "I understand. Delete my App",
"apps.deleteFailed": "Failed to delete app. Please reload.",
"apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.", "apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.",
"apps.empty": "您还没有与任何应用协作", "apps.empty": "您还没有与任何应用协作",
"apps.generalSettings": "通用", "apps.generalSettings": "通用",
@ -266,6 +261,7 @@
"common.disable": "Disable", "common.disable": "Disable",
"common.disabled": "已禁用", "common.disabled": "已禁用",
"common.displayName": "显示名称", "common.displayName": "显示名称",
"common.documentation": "Documentation",
"common.edit": "编辑", "common.edit": "编辑",
"common.editing": "Editing", "common.editing": "Editing",
"common.email": "电子邮件", "common.email": "电子邮件",
@ -380,6 +376,7 @@
"common.tagAddSchema": ", 添加Schemas", "common.tagAddSchema": ", 添加Schemas",
"common.tags": "标签", "common.tags": "标签",
"common.tagsAll": "所有标签", "common.tagsAll": "所有标签",
"common.templates": "Templates",
"common.time": "时间", "common.time": "时间",
"common.to": "To", "common.to": "To",
"common.update": "更新", "common.update": "更新",
@ -428,9 +425,7 @@
"contents.create": "新建", "contents.create": "新建",
"contents.createContentTooltip": "新建内容", "contents.createContentTooltip": "新建内容",
"contents.created": "内容创建成功。", "contents.created": "内容创建成功。",
"contents.createdByFieldDescription": "创建内容项的用户。",
"contents.createFailed": "创建内容失败,请重新加载。", "contents.createFailed": "创建内容失败,请重新加载。",
"contents.createFieldDescription": "创建内容项的日期时间。",
"contents.createPageTitle": "创建内容", "contents.createPageTitle": "创建内容",
"contents.createTitle": "新内容", "contents.createTitle": "新内容",
"contents.currentStatusLabel": "当前版本", "contents.currentStatusLabel": "当前版本",
@ -444,23 +439,19 @@
"contents.draftNew": "新草稿", "contents.draftNew": "新草稿",
"contents.draftStatus": "新版本", "contents.draftStatus": "新版本",
"contents.editPageTitle": "编辑内容", "contents.editPageTitle": "编辑内容",
"contents.fieldFullscreen": "Focus on this field and toggle fullscreen mode.",
"contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.", "contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.",
"contents.inspectContent": "Content", "contents.inspectContent": "Content",
"contents.inspectData": "Data", "contents.inspectData": "Data",
"contents.inspectFlatData": "Flat Data", "contents.inspectFlatData": "Flat Data",
"contents.invariantFieldDescription": "内容项的 '{fieldName}' 字段。",
"contents.languageModeAll": "所有语言", "contents.languageModeAll": "所有语言",
"contents.languageModeSingle": "单一语言", "contents.languageModeSingle": "单一语言",
"contents.lastModifiedByFieldDescription": "上次修改内容项的用户。",
"contents.lastModifiedFieldDescription": "上次修改内容项的日期时间。",
"contents.lastUpdatedLabel": "上次更新", "contents.lastUpdatedLabel": "上次更新",
"contents.loadContent": "加载", "contents.loadContent": "加载",
"contents.loadContentFailed": "加载内容失败,请重新加载。", "contents.loadContentFailed": "加载内容失败,请重新加载。",
"contents.loadDataFailed": "加载数据失败,请重新加载。", "contents.loadDataFailed": "加载数据失败,请重新加载。",
"contents.loadFailed": "加载内容失败,请重新加载。", "contents.loadFailed": "加载内容失败,请重新加载。",
"contents.loadVersionFailed": "加载新版本失败。请重新加载。", "contents.loadVersionFailed": "加载新版本失败。请重新加载。",
"contents.localizedFieldDescription": "内容项的 '{fieldName}' 字段(本地化)。",
"contents.newStatusFieldDescription": "内容项的新状态。",
"contents.noReference": "- 无引用 -", "contents.noReference": "- 无引用 -",
"contents.noReferences": "此内容没有引用。", "contents.noReferences": "此内容没有引用。",
"contents.noReferencing": "此内容未被其他项目引用。", "contents.noReferencing": "此内容未被其他项目引用。",
@ -483,13 +474,12 @@
"contents.scheduledBy": "by", "contents.scheduledBy": "by",
"contents.scheduledTo": "to", "contents.scheduledTo": "to",
"contents.scheduledToLabel": "Scheduled to", "contents.scheduledToLabel": "Scheduled to",
"contents.scheduledTooltip": "Will be set to '{status}' at {time}.",
"contents.schemasPageTitle": "内容", "contents.schemasPageTitle": "内容",
"contents.searchPlaceholder": "全文搜索", "contents.searchPlaceholder": "全文搜索",
"contents.searchSchemasPlaceholder": "搜索Schemas...", "contents.searchSchemasPlaceholder": "搜索Schemas...",
"contents.selectionCount": "{count} 个选定的项目", "contents.selectionCount": "{count} 个选定的项目",
"contents.statusFieldDescription": "内容项的状态。",
"contents.statusQueries": "状态查询", "contents.statusQueries": "状态查询",
"contents.stockPhotoEmpty": "未选择任何内容",
"contents.stockPhotoSearch": "通过 Unsplash 搜索照片", "contents.stockPhotoSearch": "通过 Unsplash 搜索照片",
"contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.", "contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.",
"contents.tableHeaders.created": "创建", "contents.tableHeaders.created": "创建",
@ -515,7 +505,6 @@
"contents.validationHint": "当您看到验证错误时,请记住检查所有语言。", "contents.validationHint": "当您看到验证错误时,请记住检查所有语言。",
"contents.versionCompare": "比较", "contents.versionCompare": "比较",
"contents.versionDelete": "删除此版本", "contents.versionDelete": "删除此版本",
"contents.versionFieldDescription": "内容项的版本",
"contents.versionViewing": "查看版本**{version}**。", "contents.versionViewing": "查看版本**{version}**。",
"contents.viewLatest": "查看最新", "contents.viewLatest": "查看最新",
"contents.viewReset": "重置默认视图", "contents.viewReset": "重置默认视图",
@ -671,6 +660,7 @@
"rules.actionData": "动作数据", "rules.actionData": "动作数据",
"rules.actionHint": "动作类型的选择以后不能更改。", "rules.actionHint": "动作类型的选择以后不能更改。",
"rules.addSchema": "Add Schema", "rules.addSchema": "Add Schema",
"rules.advancedFormattingHint": "You can use advanced formatting",
"rules.cancelFailed": "取消规则失败,请重新加载。", "rules.cancelFailed": "取消规则失败,请重新加载。",
"rules.conditionHint": "Optional condition as javascript expression", "rules.conditionHint": "Optional condition as javascript expression",
"rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example", "rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example",
@ -731,7 +721,7 @@
"rules.simulateTooltip": "使用最近 100 个事件模拟此规则。", "rules.simulateTooltip": "使用最近 100 个事件模拟此规则。",
"rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.", "rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.",
"rules.simulation.actionExecuted": "Job is taken from the queue and executed.", "rules.simulation.actionExecuted": "Job is taken from the queue and executed.",
"rules.simulation.errorConditionDoesNotMatch": "STOP: Javescript expressions in trigger do not match to the event.", "rules.simulation.errorConditionDoesNotMatch": "STOP: Javascript expressions in trigger do not match to the event.",
"rules.simulation.errorConditionPrecheckDoesNotMatch": "STOP: Condition in trigger does not match to the event.", "rules.simulation.errorConditionPrecheckDoesNotMatch": "STOP: Condition in trigger does not match to the event.",
"rules.simulation.errorDisabled": "STOP: Rule is disabled.", "rules.simulation.errorDisabled": "STOP: Rule is disabled.",
"rules.simulation.errorFailed": "Internal Error.", "rules.simulation.errorFailed": "Internal Error.",
@ -741,7 +731,7 @@
"rules.simulation.errorTooOld": "STOP: Event is too old.", "rules.simulation.errorTooOld": "STOP: Event is too old.",
"rules.simulation.errorWrongEvent": "STOP: Event does not match to the trigger.", "rules.simulation.errorWrongEvent": "STOP: Event does not match to the trigger.",
"rules.simulation.errorWrongEventForTrigger": "STOP: Event does not match to the trigger.", "rules.simulation.errorWrongEventForTrigger": "STOP: Event does not match to the trigger.",
"rules.simulation.eventConditionEvaluated": "Enriched event is evaluated, whether it matches to the conditions and javascript expressions in the tigger.", "rules.simulation.eventConditionEvaluated": "Enriched event is evaluated, whether it matchs to the conditions and javascript expressions in the trigger.",
"rules.simulation.eventEnriched": "Event is enriched with additional data", "rules.simulation.eventEnriched": "Event is enriched with additional data",
"rules.simulation.eventQueried": "Event is queried from the database", "rules.simulation.eventQueried": "Event is queried from the database",
"rules.simulation.eventTriggerChecked": "Event is tested to see if it matchs to the trigger and the basic conditions.", "rules.simulation.eventTriggerChecked": "Event is tested to see if it matchs to the trigger and the basic conditions.",
@ -978,6 +968,10 @@
"start.loginHint": "登录按钮将打开一个新的弹出窗口。一旦您登录成功,我们会将您重定向到 Squidex 管理门户。", "start.loginHint": "登录按钮将打开一个新的弹出窗口。一旦您登录成功,我们会将您重定向到 Squidex 管理门户。",
"start.madeBy": "自豪地制作", "start.madeBy": "自豪地制作",
"start.madeByCopyright": "Sebastian Stehle 和贡献者,2016-2021", "start.madeByCopyright": "Sebastian Stehle 和贡献者,2016-2021",
"templates.cliHint": "Download the CLI at https://github.com/squidex/squidex-samples to use the templates.",
"templates.loadFailed": "Failed to load templates. Please reload.",
"templates.refreshTooltip": "Refresh Templates",
"templates.reloaded": "Templates reloaded.",
"tour.joinForum": "加入我们的论坛", "tour.joinForum": "加入我们的论坛",
"tour.joinGithub": "加入我们的 Github", "tour.joinGithub": "加入我们的 Github",
"tour.skip": "跳过游览", "tour.skip": "跳过游览",

6
backend/i18n/source/backend_en.json

@ -203,8 +203,6 @@
"dotnet_identity_PasswordTooShort": "Passwords is too short.", "dotnet_identity_PasswordTooShort": "Passwords is too short.",
"dotnet_identity_PwnedError": "This password has previously appeared in a data breach and should never be used. If you have ever used it anywhere before, change it!", "dotnet_identity_PwnedError": "This password has previously appeared in a data breach and should never be used. If you have ever used it anywhere before, change it!",
"dotnet_identity_UserLockedOut": "User is locked out.", "dotnet_identity_UserLockedOut": "User is locked out.",
"exception.invalidJsonQuery": "Json query not valid: {message}",
"exception.invalidJsonQueryJson": "Json query not valid json: {message}",
"exceptions.domainObjectConflict": "Entity ({id}) already exists.", "exceptions.domainObjectConflict": "Entity ({id}) already exists.",
"exceptions.domainObjectDeleted": "Entity ({id}) has been deleted.", "exceptions.domainObjectDeleted": "Entity ({id}) has been deleted.",
"exceptions.domainObjectNotFound": "Entity ({id}) does not exist.", "exceptions.domainObjectNotFound": "Entity ({id}) does not exist.",
@ -275,7 +273,6 @@
"search.contentResult": "{name} Content", "search.contentResult": "{name} Content",
"search.contentsResult": "{name} Contents", "search.contentsResult": "{name} Contents",
"search.schemaResult": "{name} Schema", "search.schemaResult": "{name} Schema",
"security.passwordStolen": "This password has previously appeared in a data breach and should never be used. If you have ever used it anywhere before, change it!",
"setup.createUser.button": "Create User", "setup.createUser.button": "Create User",
"setup.createUser.confirmPassword": "Confirm", "setup.createUser.confirmPassword": "Confirm",
"setup.createUser.failure": "Neither password authentication nor an external authentication provider such as Google is configured. Please check your settings and logs.", "setup.createUser.failure": "Neither password authentication nor an external authentication provider such as Google is configured. Please check your settings and logs.",
@ -298,9 +295,6 @@
"setup.ruleUrl.failure": "You should access Squidex only over one canonical URL and configure this URL with the <code>URLS__BASEURL</code> environment variable. The current base URL <code>{actual}</code> does not match to the base url <code>{configured}</code>. This variable must point to the public URL under which your Squidex instance is available.", "setup.ruleUrl.failure": "You should access Squidex only over one canonical URL and configure this URL with the <code>URLS__BASEURL</code> environment variable. The current base URL <code>{actual}</code> does not match to the base url <code>{configured}</code>. This variable must point to the public URL under which your Squidex instance is available.",
"setup.ruleUrl.success": "Congratulations, the <code>URLS__BASEURL</code> environment variable is configured properly. This variable must point to the public URL under which your Squidex instance is available.", "setup.ruleUrl.success": "Congratulations, the <code>URLS__BASEURL</code> environment variable is configured properly. This variable must point to the public URL under which your Squidex instance is available.",
"setup.title": "Installation", "setup.title": "Installation",
"setup.webpack.headline": "webpack error",
"setup.webpack.text": "You have to run webpack dev server to run the frontend in Development mode. More information in the documenation: ",
"setup.webpack.title": "webpack error",
"users.accessDenied.text": "This operation is not allowed, your account might be locked.", "users.accessDenied.text": "This operation is not allowed, your account might be locked.",
"users.accessDenied.title": "Access denied", "users.accessDenied.title": "Access denied",
"users.consent.agree": "I agree!", "users.consent.agree": "I agree!",

3
backend/i18n/source/backend_it.json

@ -193,8 +193,6 @@
"dotnet_identity_PasswordTooShort": "La password è troppo corta.", "dotnet_identity_PasswordTooShort": "La password è troppo corta.",
"dotnet_identity_PwnedError": "Questa password è apparsa in un database di password compromesso e non dovrebbe più essere utilizzata. Se l'hai usata, cambiala!", "dotnet_identity_PwnedError": "Questa password è apparsa in un database di password compromesso e non dovrebbe più essere utilizzata. Se l'hai usata, cambiala!",
"dotnet_identity_UserLockedOut": "L'utente è bloccato.", "dotnet_identity_UserLockedOut": "L'utente è bloccato.",
"exception.invalidJsonQuery": "La query Json non è valida: {message}",
"exception.invalidJsonQueryJson": "La query Json non è valida: {message}",
"exceptions.domainObjectConflict": "L'entità ({id}) esiste già.", "exceptions.domainObjectConflict": "L'entità ({id}) esiste già.",
"exceptions.domainObjectDeleted": "L'entità ({id}) è stata cancellata.", "exceptions.domainObjectDeleted": "L'entità ({id}) è stata cancellata.",
"exceptions.domainObjectNotFound": "L'entità ({id}) non esiste.", "exceptions.domainObjectNotFound": "L'entità ({id}) non esiste.",
@ -264,7 +262,6 @@
"search.contentResult": "Contenuto {name}", "search.contentResult": "Contenuto {name}",
"search.contentsResult": "Contenuti {name} ", "search.contentsResult": "Contenuti {name} ",
"search.schemaResult": "Schema {name}", "search.schemaResult": "Schema {name}",
"security.passwordStolen": "Questa password risulta essere stata compromessa e non dovrebbe essere mai utilizzata. Se l'hai utilizzata in precedenza, cambiala!",
"setup.createUser.button": "Nuovo utente", "setup.createUser.button": "Nuovo utente",
"setup.createUser.confirmPassword": "Conferma password", "setup.createUser.confirmPassword": "Conferma password",
"setup.createUser.failure": "Non è stata configurata la password di autenticazione e nessun provider esterno di autenticazione come Google è stato configurato. Verifica per favore le tue impostazioni e i log.", "setup.createUser.failure": "Non è stata configurata la password di autenticazione e nessun provider esterno di autenticazione come Google è stato configurato. Verifica per favore le tue impostazioni e i log.",

3
backend/i18n/source/backend_nl.json

@ -203,8 +203,6 @@
"dotnet_identity_PasswordTooShort": "Wachtwoorden zijn te kort.", "dotnet_identity_PasswordTooShort": "Wachtwoorden zijn te kort.",
"dotnet_identity_PwnedError": "Dit wachtwoord is eerder verschenen in een datalek en mag nooit worden gebruikt. Als je het ooit eerder ergens hebt gebruikt, verander het dan!", "dotnet_identity_PwnedError": "Dit wachtwoord is eerder verschenen in een datalek en mag nooit worden gebruikt. Als je het ooit eerder ergens hebt gebruikt, verander het dan!",
"dotnet_identity_UserLockedOut": "Gebruiker is uitgesloten.", "dotnet_identity_UserLockedOut": "Gebruiker is uitgesloten.",
"exception.invalidJsonQuery": "Json-query niet geldig: {message}",
"exception.invalidJsonQueryJson": "Json-query is niet geldig json: {message}",
"exceptions.domainObjectConflict": "Entiteit ({id}) bestaat al.", "exceptions.domainObjectConflict": "Entiteit ({id}) bestaat al.",
"exceptions.domainObjectDeleted": "Entiteit ({id}) is verwijderd.", "exceptions.domainObjectDeleted": "Entiteit ({id}) is verwijderd.",
"exceptions.domainObjectNotFound": "Entiteit ({id}) bestaat niet.", "exceptions.domainObjectNotFound": "Entiteit ({id}) bestaat niet.",
@ -275,7 +273,6 @@
"search.contentResult": "{name} Inhoud", "search.contentResult": "{name} Inhoud",
"search.contentsResult": "{name} Inhoud", "search.contentsResult": "{name} Inhoud",
"search.schemaResult": "{name} Schema", "search.schemaResult": "{name} Schema",
"security.passwordStolen": "Dit wachtwoord is eerder verschenen bij een datalek en mag nooit worden gebruikt. Als je het ooit eerder hebt gebruikt, verander het dan!",
"users.accessDenied.text": "Deze bewerking is niet toegestaan, je account is mogelijk vergrendeld.", "users.accessDenied.text": "Deze bewerking is niet toegestaan, je account is mogelijk vergrendeld.",
"users.accessDenied.title": "Toegang geweigerd", "users.accessDenied.title": "Toegang geweigerd",
"users.consent.agree": "Akkoord!", "users.consent.agree": "Akkoord!",

7
backend/i18n/source/backend_zh.json

@ -197,8 +197,6 @@
"dotnet_identity_PasswordRequiresUpper": "密码必须至少有一个大写字母 ('A'-'Z')。", "dotnet_identity_PasswordRequiresUpper": "密码必须至少有一个大写字母 ('A'-'Z')。",
"dotnet_identity_PasswordTooShort": "密码太短。", "dotnet_identity_PasswordTooShort": "密码太短。",
"dotnet_identity_PwnedError": "此密码以前曾出现在数据泄露事件中,永远不应使用。如果您以前曾在任何地方使用过它,请更改它!", "dotnet_identity_PwnedError": "此密码以前曾出现在数据泄露事件中,永远不应使用。如果您以前曾在任何地方使用过它,请更改它!",
"exception.invalidJsonQuery": "Json 查询无效:{message}",
"exception.invalidJsonQueryJson": "Json 查询无效 json: {message}",
"exceptions.domainObjectConflict": "实体 ({id}) 已经存在。", "exceptions.domainObjectConflict": "实体 ({id}) 已经存在。",
"exceptions.domainObjectDeleted": "实体 ({id}) 已被删除。", "exceptions.domainObjectDeleted": "实体 ({id}) 已被删除。",
"exceptions.domainObjectNotFound": "实体 ({id}) 不存在。", "exceptions.domainObjectNotFound": "实体 ({id}) 不存在。",
@ -245,10 +243,6 @@
"history.statusChanged": "已将 {[Schema]} 内容的状态更改为 {[Status]}。", "history.statusChanged": "已将 {[Schema]} 内容的状态更改为 {[Status]}。",
"login.githubPrivateEmail": "您的邮箱在 Github 中设置为私有。请设置为公开以使用 Github 登录。", "login.githubPrivateEmail": "您的邮箱在 Github 中设置为私有。请设置为公开以使用 Github 登录。",
"rules.ruleAlreadyRunning": "另一个规则已经在运行。", "rules.ruleAlreadyRunning": "另一个规则已经在运行。",
"schema.fieldIsLocked": "Schemas字段被锁定。",
"schema.fieldNotInSchema": "字段不是Schemas的一部分。",
"schema.noPermission": "您没有此Schemas的权限。",
"schema.notFoundId": "Schemas {id} 不存在。",
"schemas.dateTimeCalculatedDefaultAndDefaultError": "计算出的默认值和默认值不能一起使用。", "schemas.dateTimeCalculatedDefaultAndDefaultError": "计算出的默认值和默认值不能一起使用。",
"schemas.duplicateFieldName": "字段 '{field}' 已添加两次。", "schemas.duplicateFieldName": "字段 '{field}' 已添加两次。",
"schemas.fieldCannotBeUIField": "字段不能是 UI 字段。", "schemas.fieldCannotBeUIField": "字段不能是 UI 字段。",
@ -269,7 +263,6 @@
"search.contentResult": "{name} 内容", "search.contentResult": "{name} 内容",
"search.contentsResult": "{name} 内容", "search.contentsResult": "{name} 内容",
"search.schemaResult": "{name} Schemas", "search.schemaResult": "{name} Schemas",
"security.passwordStolen": "此密码以前曾出现在数据泄露事件中,永远不应使用。如果您以前曾在任何地方使用过它,请更改它!",
"setup.createUser.button": "创建用户", "setup.createUser.button": "创建用户",
"setup.createUser.confirmPassword": "确认", "setup.createUser.confirmPassword": "确认",
"setup.createUser.failure": "既未配置密码认证,也未配置 Google 等外部认证提供商。请检查您的设置和日志。", "setup.createUser.failure": "既未配置密码认证,也未配置 Google 等外部认证提供商。请检查您的设置和日志。",

40
backend/i18n/source/frontend_en.json

@ -16,16 +16,11 @@
"apps.create": "Create App", "apps.create": "Create App",
"apps.createBlankApp": "New App", "apps.createBlankApp": "New App",
"apps.createBlankAppDescription": "Create a new blank app without content and schemas.", "apps.createBlankAppDescription": "Create a new blank app without content and schemas.",
"apps.createBlogApp": "New Blog Sample",
"apps.createBlogAppDescription": "Start with our ready to use blog.",
"apps.createFailed": "Failed to create app. Please reload.", "apps.createFailed": "Failed to create app. Please reload.",
"apps.createProfileApp": "New Profile Sample",
"apps.createProfileAppDescription": "Create your profile page.",
"apps.createWithTemplate": "Create {template} Sample", "apps.createWithTemplate": "Create {template} Sample",
"apps.delete": "Delete App", "apps.delete": "Delete App",
"apps.deleteConfirmText": "Do you really want to delete this app?", "apps.deleteConfirmText": "Do you really want to delete this app?",
"apps.deleteConfirmTitle": "I understand. Delete my App", "apps.deleteConfirmTitle": "I understand. Delete my App",
"apps.deleteFailed": "Failed to delete app. Please reload.",
"apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.", "apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.",
"apps.empty": "You are not collaborating on any apps yet", "apps.empty": "You are not collaborating on any apps yet",
"apps.generalSettings": "General", "apps.generalSettings": "General",
@ -266,6 +261,7 @@
"common.disable": "Disable", "common.disable": "Disable",
"common.disabled": "Disabled", "common.disabled": "Disabled",
"common.displayName": "Display Name", "common.displayName": "Display Name",
"common.documentation": "Documentation",
"common.edit": "Edit", "common.edit": "Edit",
"common.editing": "Editing", "common.editing": "Editing",
"common.email": "Email", "common.email": "Email",
@ -335,14 +331,14 @@
"common.queryOperators.contains": "contains", "common.queryOperators.contains": "contains",
"common.queryOperators.empty": "is empty", "common.queryOperators.empty": "is empty",
"common.queryOperators.endsWith": "ends with", "common.queryOperators.endsWith": "ends with",
"common.queryOperators.eq": "is equals to", "common.queryOperators.eq": "is equal to",
"common.queryOperators.exists": "exists", "common.queryOperators.exists": "exists",
"common.queryOperators.ge": "is greater than or equals to", "common.queryOperators.ge": "is greater than or equal to",
"common.queryOperators.gt": "is greater than", "common.queryOperators.gt": "is greater than",
"common.queryOperators.le": "is less than or equals to", "common.queryOperators.le": "is less than or equal to",
"common.queryOperators.lt": "is less than", "common.queryOperators.lt": "is less than",
"common.queryOperators.matchs": "matchs", "common.queryOperators.matchs": "matchs",
"common.queryOperators.ne": "is not equals to", "common.queryOperators.ne": "is not equal to",
"common.queryOperators.startsWith": "starts with", "common.queryOperators.startsWith": "starts with",
"common.refresh": "Refresh", "common.refresh": "Refresh",
"common.remember": "Don't ask again", "common.remember": "Don't ask again",
@ -380,6 +376,7 @@
"common.tagAddSchema": ", to add schema", "common.tagAddSchema": ", to add schema",
"common.tags": "Tags", "common.tags": "Tags",
"common.tagsAll": "All tags", "common.tagsAll": "All tags",
"common.templates": "Templates",
"common.time": "Time", "common.time": "Time",
"common.to": "To", "common.to": "To",
"common.update": "Update", "common.update": "Update",
@ -428,9 +425,7 @@
"contents.create": "New", "contents.create": "New",
"contents.createContentTooltip": "New Content", "contents.createContentTooltip": "New Content",
"contents.created": "Content created successfully.", "contents.created": "Content created successfully.",
"contents.createdByFieldDescription": "The user who created the content item.",
"contents.createFailed": "Failed to create content. Please reload.", "contents.createFailed": "Failed to create content. Please reload.",
"contents.createFieldDescription": "The date time when the content item was created.",
"contents.createPageTitle": "Create Content", "contents.createPageTitle": "Create Content",
"contents.createTitle": "New Content", "contents.createTitle": "New Content",
"contents.currentStatusLabel": "Current Version", "contents.currentStatusLabel": "Current Version",
@ -444,23 +439,19 @@
"contents.draftNew": "New Draft", "contents.draftNew": "New Draft",
"contents.draftStatus": "New Version", "contents.draftStatus": "New Version",
"contents.editPageTitle": "Edit Content", "contents.editPageTitle": "Edit Content",
"contents.fieldFullscreen": "Focus on this field and toggle fullscreen mode.",
"contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.", "contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.",
"contents.inspectContent": "Content", "contents.inspectContent": "Content",
"contents.inspectData": "Data", "contents.inspectData": "Data",
"contents.inspectFlatData": "Flat Data", "contents.inspectFlatData": "Flat Data",
"contents.invariantFieldDescription": "The '{fieldName}' field of the content item.",
"contents.languageModeAll": "All Languages", "contents.languageModeAll": "All Languages",
"contents.languageModeSingle": "Single Language", "contents.languageModeSingle": "Single Language",
"contents.lastModifiedByFieldDescription": "The user who modified the content item the last time.",
"contents.lastModifiedFieldDescription": "The date time when the content item was modified the last time.",
"contents.lastUpdatedLabel": "Last Updated", "contents.lastUpdatedLabel": "Last Updated",
"contents.loadContent": "Load", "contents.loadContent": "Load",
"contents.loadContentFailed": "Failed to load content. Please reload.", "contents.loadContentFailed": "Failed to load content. Please reload.",
"contents.loadDataFailed": "Failed to load data. Please reload.", "contents.loadDataFailed": "Failed to load data. Please reload.",
"contents.loadFailed": "Failed to load contents. Please reload.", "contents.loadFailed": "Failed to load contents. Please reload.",
"contents.loadVersionFailed": "Failed to load a new version. Please reload.", "contents.loadVersionFailed": "Failed to load a new version. Please reload.",
"contents.localizedFieldDescription": "The '{fieldName}' field of the content item (localized).",
"contents.newStatusFieldDescription": "The new status of the content item.",
"contents.noReference": "- No Reference -", "contents.noReference": "- No Reference -",
"contents.noReferences": "This content has no references.", "contents.noReferences": "This content has no references.",
"contents.noReferencing": "This content is not referenced by another item.", "contents.noReferencing": "This content is not referenced by another item.",
@ -483,13 +474,12 @@
"contents.scheduledBy": "by", "contents.scheduledBy": "by",
"contents.scheduledTo": "to", "contents.scheduledTo": "to",
"contents.scheduledToLabel": "Scheduled to", "contents.scheduledToLabel": "Scheduled to",
"contents.scheduledTooltip": "Will be set to '{status}' at {time}.",
"contents.schemasPageTitle": "Contents", "contents.schemasPageTitle": "Contents",
"contents.searchPlaceholder": "Fulltext search", "contents.searchPlaceholder": "Fulltext search",
"contents.searchSchemasPlaceholder": "Search", "contents.searchSchemasPlaceholder": "Search",
"contents.selectionCount": "{count} items selected", "contents.selectionCount": "{count} items selected",
"contents.statusFieldDescription": "The status of the content item.",
"contents.statusQueries": "Status Queries", "contents.statusQueries": "Status Queries",
"contents.stockPhotoEmpty": "Nothing selected",
"contents.stockPhotoSearch": "Search for Photos by Unsplash", "contents.stockPhotoSearch": "Search for Photos by Unsplash",
"contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.", "contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.",
"contents.tableHeaders.created": "Created", "contents.tableHeaders.created": "Created",
@ -507,7 +497,7 @@
"contents.unsavedChangesText": "You have unsaved changes. Do you want to load them now?", "contents.unsavedChangesText": "You have unsaved changes. Do you want to load them now?",
"contents.unsavedChangesTitle": "Unsaved changes", "contents.unsavedChangesTitle": "Unsaved changes",
"contents.unsetValue": "Unset value", "contents.unsetValue": "Unset value",
"contents.unsetValueConfirmText": "If you unset the value you might loose your changes.\n\nDo you really want to do it?", "contents.unsetValueConfirmText": "If you unset the value you might lose your changes.\n\nDo you really want to do it?",
"contents.unsetValueConfirmTitle": "Do you want to unset the value?", "contents.unsetValueConfirmTitle": "Do you want to unset the value?",
"contents.updated": "Content updated successfully.", "contents.updated": "Content updated successfully.",
"contents.updateFailed": "Failed to update content. Please reload.", "contents.updateFailed": "Failed to update content. Please reload.",
@ -515,7 +505,6 @@
"contents.validationHint": "Please remember to check all languages when you see validation errors.", "contents.validationHint": "Please remember to check all languages when you see validation errors.",
"contents.versionCompare": "Compare", "contents.versionCompare": "Compare",
"contents.versionDelete": "Delete this Version", "contents.versionDelete": "Delete this Version",
"contents.versionFieldDescription": "The version of the content item",
"contents.versionViewing": "Viewing version **{version}**.", "contents.versionViewing": "Viewing version **{version}**.",
"contents.viewLatest": "View latest", "contents.viewLatest": "View latest",
"contents.viewReset": "Reset Default View", "contents.viewReset": "Reset Default View",
@ -671,6 +660,7 @@
"rules.actionData": "Action Data", "rules.actionData": "Action Data",
"rules.actionHint": "The selection of the action type cannot be changed later.", "rules.actionHint": "The selection of the action type cannot be changed later.",
"rules.addSchema": "Add Schema", "rules.addSchema": "Add Schema",
"rules.advancedFormattingHint": "You can use advanced formatting",
"rules.cancelFailed": "Failed to cancel rule. Please reload.", "rules.cancelFailed": "Failed to cancel rule. Please reload.",
"rules.conditionHint": "Optional condition as javascript expression", "rules.conditionHint": "Optional condition as javascript expression",
"rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example", "rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example",
@ -731,7 +721,7 @@
"rules.simulateTooltip": "Simulate this rules using the last 100 events.", "rules.simulateTooltip": "Simulate this rules using the last 100 events.",
"rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.", "rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.",
"rules.simulation.actionExecuted": "Job is taken from the queue and executed.", "rules.simulation.actionExecuted": "Job is taken from the queue and executed.",
"rules.simulation.errorConditionDoesNotMatch": "STOP: Javescript expressions in trigger do not match to the event.", "rules.simulation.errorConditionDoesNotMatch": "STOP: Javascript expressions in trigger do not match to the event.",
"rules.simulation.errorConditionPrecheckDoesNotMatch": "STOP: Condition in trigger does not match to the event.", "rules.simulation.errorConditionPrecheckDoesNotMatch": "STOP: Condition in trigger does not match to the event.",
"rules.simulation.errorDisabled": "STOP: Rule is disabled.", "rules.simulation.errorDisabled": "STOP: Rule is disabled.",
"rules.simulation.errorFailed": "Internal Error.", "rules.simulation.errorFailed": "Internal Error.",
@ -741,7 +731,7 @@
"rules.simulation.errorTooOld": "STOP: Event is too old.", "rules.simulation.errorTooOld": "STOP: Event is too old.",
"rules.simulation.errorWrongEvent": "STOP: Event does not match to the trigger.", "rules.simulation.errorWrongEvent": "STOP: Event does not match to the trigger.",
"rules.simulation.errorWrongEventForTrigger": "STOP: Event does not match to the trigger.", "rules.simulation.errorWrongEventForTrigger": "STOP: Event does not match to the trigger.",
"rules.simulation.eventConditionEvaluated": "Enriched event is evaluated, whether it matches to the conditions and javascript expressions in the tigger.", "rules.simulation.eventConditionEvaluated": "Enriched event is evaluated, whether it matchs to the conditions and javascript expressions in the trigger.",
"rules.simulation.eventEnriched": "Event is enriched with additional data", "rules.simulation.eventEnriched": "Event is enriched with additional data",
"rules.simulation.eventQueried": "Event is queried from the database", "rules.simulation.eventQueried": "Event is queried from the database",
"rules.simulation.eventTriggerChecked": "Event is tested to see if it matchs to the trigger and the basic conditions.", "rules.simulation.eventTriggerChecked": "Event is tested to see if it matchs to the trigger and the basic conditions.",
@ -978,6 +968,10 @@
"start.loginHint": "The login button will open a new popup. Once you are logged in successful we will redirect you to the Squidex management portal.", "start.loginHint": "The login button will open a new popup. Once you are logged in successful we will redirect you to the Squidex management portal.",
"start.madeBy": "Proudly made by", "start.madeBy": "Proudly made by",
"start.madeByCopyright": "Sebastian Stehle and Contributors, 2016-2021", "start.madeByCopyright": "Sebastian Stehle and Contributors, 2016-2021",
"templates.cliHint": "Download the CLI at https://github.com/squidex/squidex-samples to use the templates.",
"templates.loadFailed": "Failed to load templates. Please reload.",
"templates.refreshTooltip": "Refresh Templates",
"templates.reloaded": "Templates reloaded.",
"tour.joinForum": "Join our Forum", "tour.joinForum": "Join our Forum",
"tour.joinGithub": "Join us on Github", "tour.joinGithub": "Join us on Github",
"tour.skip": "Skip Tour", "tour.skip": "Skip Tour",
@ -991,7 +985,7 @@
"tour.step3Text": "Content is the actual data in your app which is grouped by the schema.\n\nSelect a published schema first, then add content for this schema.", "tour.step3Text": "Content is the actual data in your app which is grouped by the schema.\n\nSelect a published schema first, then add content for this schema.",
"tour.step4Next": "Got It!", "tour.step4Next": "Got It!",
"tour.step4Text": "The assets contains all files that can also be linked to your content. For example images, videos or documents.\n\nYou can upload the assets here and use them later or also upload them directly when you create a new content item with an asset field.", "tour.step4Text": "The assets contains all files that can also be linked to your content. For example images, videos or documents.\n\nYou can upload the assets here and use them later or also upload them directly when you create a new content item with an asset field.",
"tour.step5Text": "But that's not all of the support we can provide.\n\nYou can got to https://docs.squidex.io to read more.\n\nDo you want to join our community?", "tour.step5Text": "But that's not all of the support we can provide.\n\nYou can go to https://docs.squidex.io to read more.\n\nDo you want to join our community?",
"tour.step5Title": "Awesome, now you know the basics!", "tour.step5Title": "Awesome, now you know the basics!",
"tour.tooltipConfirm": "Got It", "tour.tooltipConfirm": "Got It",
"tour.tooltipStop": "Stop Tour", "tour.tooltipStop": "Stop Tour",

15
backend/i18n/source/frontend_it.json

@ -15,11 +15,7 @@
"apps.create": "Crea un'App", "apps.create": "Crea un'App",
"apps.createBlankApp": "Nuova App.", "apps.createBlankApp": "Nuova App.",
"apps.createBlankAppDescription": "Crea una app vuota senza contenuti o schema.", "apps.createBlankAppDescription": "Crea una app vuota senza contenuti o schema.",
"apps.createBlogApp": "Nuovo blog",
"apps.createBlogAppDescription": "Inizia con un blog.",
"apps.createFailed": "Non è stato possibile creare l'app. Per favore ricarica.", "apps.createFailed": "Non è stato possibile creare l'app. Per favore ricarica.",
"apps.createProfileApp": "Nuovo Profilo",
"apps.createProfileAppDescription": "Crea la tua pagina del profilo.",
"apps.createWithTemplate": "Create un esempio di {template}", "apps.createWithTemplate": "Create un esempio di {template}",
"apps.empty": "Non stai ancora collaborando su nessuna app", "apps.empty": "Non stai ancora collaborando su nessuna app",
"apps.generalSettings": "Generale", "apps.generalSettings": "Generale",
@ -357,9 +353,7 @@
"contents.create": "Nuovo", "contents.create": "Nuovo",
"contents.createContentTooltip": "Nuovo contenuto", "contents.createContentTooltip": "Nuovo contenuto",
"contents.created": "Contenuto creato con successo.", "contents.created": "Contenuto creato con successo.",
"contents.createdByFieldDescription": "L'utente che ha creato l'elemento del contenuto.",
"contents.createFailed": "Non è stato possibile creare il contenuto. Per favore ricarica.", "contents.createFailed": "Non è stato possibile creare il contenuto. Per favore ricarica.",
"contents.createFieldDescription": "La data e l'ora di creazione del contenuto.",
"contents.createPageTitle": "Crea un contenuto", "contents.createPageTitle": "Crea un contenuto",
"contents.createTitle": "Nuovo Contenuto", "contents.createTitle": "Nuovo Contenuto",
"contents.currentStatusLabel": "Versione corrente", "contents.currentStatusLabel": "Versione corrente",
@ -373,19 +367,14 @@
"contents.draftNew": "Nuova bozza", "contents.draftNew": "Nuova bozza",
"contents.draftStatus": "Nuova versione", "contents.draftStatus": "Nuova versione",
"contents.editPageTitle": "Modifica contenuto", "contents.editPageTitle": "Modifica contenuto",
"contents.invariantFieldDescription": "Il campo '{fieldName}' del contenuto.",
"contents.languageModeAll": "Tutte le lingue", "contents.languageModeAll": "Tutte le lingue",
"contents.languageModeSingle": "Una sola lingua", "contents.languageModeSingle": "Una sola lingua",
"contents.lastModifiedByFieldDescription": "L'utente che ha modificato l'elemento l'ultima volta.",
"contents.lastModifiedFieldDescription": "La data e l'ora dell'ultima modifica del contenuto.",
"contents.lastUpdatedLabel": "Ultimo aggiornamento", "contents.lastUpdatedLabel": "Ultimo aggiornamento",
"contents.loadContent": "Carica", "contents.loadContent": "Carica",
"contents.loadContentFailed": "Non è stato possibile caricare il contenuto. Per favore ricarica.", "contents.loadContentFailed": "Non è stato possibile caricare il contenuto. Per favore ricarica.",
"contents.loadDataFailed": "Non è stato possibile caricare i dati. Per favore ricarica.", "contents.loadDataFailed": "Non è stato possibile caricare i dati. Per favore ricarica.",
"contents.loadFailed": "Non è stato possibile caricare i contenuti. Per favore ricarica.", "contents.loadFailed": "Non è stato possibile caricare i contenuti. Per favore ricarica.",
"contents.loadVersionFailed": "Non è stato possibile creare una nuova versione. Per favore ricarica.", "contents.loadVersionFailed": "Non è stato possibile creare una nuova versione. Per favore ricarica.",
"contents.localizedFieldDescription": "Il campo '{fieldName}' del contenuto (localizzato).",
"contents.newStatusFieldDescription": "Nuovo stato per l'elemento del contenuto.",
"contents.noReference": "- Nessun riferimento collegato -", "contents.noReference": "- Nessun riferimento collegato -",
"contents.noReferences": "Questo contenuto non ha collegamenti.", "contents.noReferences": "Questo contenuto non ha collegamenti.",
"contents.noReferencing": "Questo contenuto non è collegato da altri contenuti.", "contents.noReferencing": "Questo contenuto non è collegato da altri contenuti.",
@ -405,15 +394,12 @@
"contents.removeConfirmTitle": "Cancella il contenuto", "contents.removeConfirmTitle": "Cancella il contenuto",
"contents.saveAndPublish": "Salva e pubblica", "contents.saveAndPublish": "Salva e pubblica",
"contents.scheduledAt": "alle", "contents.scheduledAt": "alle",
"contents.scheduledAtLabel": "alle",
"contents.scheduledTo": "a", "contents.scheduledTo": "a",
"contents.schemasPageTitle": "Contenuti", "contents.schemasPageTitle": "Contenuti",
"contents.searchPlaceholder": "Ricerca testuale", "contents.searchPlaceholder": "Ricerca testuale",
"contents.searchSchemasPlaceholder": "Cerca schemi...", "contents.searchSchemasPlaceholder": "Cerca schemi...",
"contents.selectionCount": "{count} elementi selezionati", "contents.selectionCount": "{count} elementi selezionati",
"contents.statusFieldDescription": "Stato dell'elemento del contenuto.",
"contents.statusQueries": "Stato Query", "contents.statusQueries": "Stato Query",
"contents.stockPhotoEmpty": "Nessuna selezione",
"contents.stockPhotoSearch": "Cerca foto su Unsplash", "contents.stockPhotoSearch": "Cerca foto su Unsplash",
"contents.tableHeaders.created": "Creato", "contents.tableHeaders.created": "Creato",
"contents.tableHeaders.createdBy": "Creato da", "contents.tableHeaders.createdBy": "Creato da",
@ -438,7 +424,6 @@
"contents.validationHint": "Ricorda di verificare tutte le lingue quando vedi errori di validazione.", "contents.validationHint": "Ricorda di verificare tutte le lingue quando vedi errori di validazione.",
"contents.versionCompare": "Confronta", "contents.versionCompare": "Confronta",
"contents.versionDelete": "Cancella questa Versione", "contents.versionDelete": "Cancella questa Versione",
"contents.versionFieldDescription": "La versione dell'elemento del contenuto",
"contents.versionViewing": "Stai guardando la versione **{version}**.", "contents.versionViewing": "Stai guardando la versione **{version}**.",
"contents.viewLatest": "Visualizza l'ultima", "contents.viewLatest": "Visualizza l'ultima",
"contents.viewReset": "Imposta la visualizzazione predefinita", "contents.viewReset": "Imposta la visualizzazione predefinita",

15
backend/i18n/source/frontend_nl.json

@ -15,16 +15,11 @@
"apps.create": "App maken", "apps.create": "App maken",
"apps.createBlankApp": "Nieuwe app.", "apps.createBlankApp": "Nieuwe app.",
"apps.createBlankAppDescription": "Maak een nieuwe lege app zonder inhoud en schema's.", "apps.createBlankAppDescription": "Maak een nieuwe lege app zonder inhoud en schema's.",
"apps.createBlogApp": "Nieuw blogvoorbeeld",
"apps.createBlogAppDescription": "Begin met onze gebruiksklare blog.",
"apps.createFailed": "Maken van app is mislukt. Laad opnieuw.", "apps.createFailed": "Maken van app is mislukt. Laad opnieuw.",
"apps.createProfileApp": "Nieuw profielvoorbeeld",
"apps.createProfileAppDescription": "Maak uw profielpagina.",
"apps.createWithTemplate": "Maak {template} voorbeeld", "apps.createWithTemplate": "Maak {template} voorbeeld",
"apps.delete": "Verwijder App", "apps.delete": "Verwijder App",
"apps.deleteConfirmText": "Weet je zeker dat je de app wilt verwijderen?", "apps.deleteConfirmText": "Weet je zeker dat je de app wilt verwijderen?",
"apps.deleteConfirmTitle": "I begrijp het, verwijder mijn app", "apps.deleteConfirmTitle": "I begrijp het, verwijder mijn app",
"apps.deleteFailed": "App verwijderen mislukt. Herlaad de pagina.",
"apps.deleteWarning": "Wanneer je de app verwijderd, kan je niet meer terug. Al je gegevens worden in de achtergrond verwijderd.", "apps.deleteWarning": "Wanneer je de app verwijderd, kan je niet meer terug. Al je gegevens worden in de achtergrond verwijderd.",
"apps.empty": "Je werkt nog niet samen aan een app", "apps.empty": "Je werkt nog niet samen aan een app",
"apps.generalSettings": "Algemeen", "apps.generalSettings": "Algemeen",
@ -420,9 +415,7 @@
"contents.create": "Nieuw", "contents.create": "Nieuw",
"contents.createContentTooltip": "Nieuwe inhoud", "contents.createContentTooltip": "Nieuwe inhoud",
"contents.created": "Inhoud succesvol aangemaakt.", "contents.created": "Inhoud succesvol aangemaakt.",
"contents.createdByFieldDescription": "De gebruiker die het inhoudsitem heeft gemaakt.",
"contents.createFailed": "Maken van inhoud is mislukt. Laad opnieuw.", "contents.createFailed": "Maken van inhoud is mislukt. Laad opnieuw.",
"contents.createFieldDescription": "De datum en tijd waarop het inhoudsitem is gemaakt.",
"contents.createPageTitle": "Inhoud maken", "contents.createPageTitle": "Inhoud maken",
"contents.createTitle": "Nieuwe inhoud", "contents.createTitle": "Nieuwe inhoud",
"contents.currentStatusLabel": "Huidige versie", "contents.currentStatusLabel": "Huidige versie",
@ -440,19 +433,14 @@
"contents.inspectContent": "Inhoud", "contents.inspectContent": "Inhoud",
"contents.inspectData": "Data", "contents.inspectData": "Data",
"contents.inspectFlatData": "Platte Data", "contents.inspectFlatData": "Platte Data",
"contents.invariantFieldDescription": "Het veld '{fieldName}' van het inhoudsitem.",
"contents.languageModeAll": "Alle talen", "contents.languageModeAll": "Alle talen",
"contents.languageModeSingle": "Enkele taal", "contents.languageModeSingle": "Enkele taal",
"contents.lastModifiedByFieldDescription": "De gebruiker die het inhoudsitem de laatste keer heeft gewijzigd.",
"contents.lastModifiedFieldDescription": "De datum en tijd waarop het inhoudsitem de laatste keer is gewijzigd.",
"contents.lastUpdatedLabel": "Laatst bijgewerkt", "contents.lastUpdatedLabel": "Laatst bijgewerkt",
"contents.loadContent": "Laden", "contents.loadContent": "Laden",
"contents.loadContentFailed": "Kan inhoud niet laden. Laad opnieuw.", "contents.loadContentFailed": "Kan inhoud niet laden. Laad opnieuw.",
"contents.loadDataFailed": "Laden van gegevens is mislukt. Laad opnieuw.", "contents.loadDataFailed": "Laden van gegevens is mislukt. Laad opnieuw.",
"contents.loadFailed": "Laden van inhoud is mislukt. Laad opnieuw.", "contents.loadFailed": "Laden van inhoud is mislukt. Laad opnieuw.",
"contents.loadVersionFailed": "Versie van een nieuwe versie is mislukt. Laad opnieuw.", "contents.loadVersionFailed": "Versie van een nieuwe versie is mislukt. Laad opnieuw.",
"contents.localizedFieldDescription": "Het veld '{fieldName}' van het inhoudsitem (gelokaliseerd).",
"contents.newStatusFieldDescription": "De nieuwe status van het item.",
"contents.noReference": "- Geen referentie -", "contents.noReference": "- Geen referentie -",
"contents.noReferences": "Deze inhoud heeft geen referenties.", "contents.noReferences": "Deze inhoud heeft geen referenties.",
"contents.noReferencing": "Naar deze inhoud wordt niet verwezen door een ander item.", "contents.noReferencing": "Naar deze inhoud wordt niet verwezen door een ander item.",
@ -479,9 +467,7 @@
"contents.searchPlaceholder": "Zoeken in volledige tekst", "contents.searchPlaceholder": "Zoeken in volledige tekst",
"contents.searchSchemasPlaceholder": "Zoek schema's ...", "contents.searchSchemasPlaceholder": "Zoek schema's ...",
"contents.selectionCount": "{count} items geselecteerd", "contents.selectionCount": "{count} items geselecteerd",
"contents.statusFieldDescription": "De status van het inhoudsitem.",
"contents.statusQueries": "Statusquery's", "contents.statusQueries": "Statusquery's",
"contents.stockPhotoEmpty": "Niets geselecteerd",
"contents.stockPhotoSearch": "Zoeken naar foto's op Unsplash", "contents.stockPhotoSearch": "Zoeken naar foto's op Unsplash",
"contents.tableHeaders.created": "Gemaakt", "contents.tableHeaders.created": "Gemaakt",
"contents.tableHeaders.createdBy": "Gemaakt door", "contents.tableHeaders.createdBy": "Gemaakt door",
@ -506,7 +492,6 @@
"contents.validationHint": "Denk eraan om alle talen te controleren wanneer je validatiefouten ziet.", "contents.validationHint": "Denk eraan om alle talen te controleren wanneer je validatiefouten ziet.",
"contents.versionCompare": "Vergelijk", "contents.versionCompare": "Vergelijk",
"contents.versionDelete": "Verwijder deze versie", "contents.versionDelete": "Verwijder deze versie",
"contents.versionFieldDescription": "De versie van het inhoudsitem",
"contents.versionViewing": "Bekijk versie **{version}**.", "contents.versionViewing": "Bekijk versie **{version}**.",
"contents.viewLatest": "Bekijk laatste", "contents.viewLatest": "Bekijk laatste",
"contents.viewReset": "Standaardweergave herstellen", "contents.viewReset": "Standaardweergave herstellen",

15
backend/i18n/source/frontend_zh.json

@ -15,11 +15,7 @@
"apps.create": "创建应用程序", "apps.create": "创建应用程序",
"apps.createBlankApp": "新应用程序", "apps.createBlankApp": "新应用程序",
"apps.createBlankAppDescription": "创建一个没有内容和Schemas的新空白应用程序。", "apps.createBlankAppDescription": "创建一个没有内容和Schemas的新空白应用程序。",
"apps.createBlogApp": "新博客示例",
"apps.createBlogAppDescription": "从我们准备使用的博客开始。",
"apps.createFailed": "创建应用失败。请重新加载。", "apps.createFailed": "创建应用失败。请重新加载。",
"apps.createProfileApp": "新配置文件示例",
"apps.createProfileAppDescription": "创建您的个人资料页面。",
"apps.createWithTemplate": "创建 {template} 示例", "apps.createWithTemplate": "创建 {template} 示例",
"apps.empty": "您还没有与任何应用协作", "apps.empty": "您还没有与任何应用协作",
"apps.generalSettings": "通用", "apps.generalSettings": "通用",
@ -379,9 +375,7 @@
"contents.create": "新建", "contents.create": "新建",
"contents.createContentTooltip": "新建内容", "contents.createContentTooltip": "新建内容",
"contents.created": "内容创建成功。", "contents.created": "内容创建成功。",
"contents.createdByFieldDescription": "创建内容项的用户。",
"contents.createFailed": "创建内容失败,请重新加载。", "contents.createFailed": "创建内容失败,请重新加载。",
"contents.createFieldDescription": "创建内容项的日期时间。",
"contents.createPageTitle": "创建内容", "contents.createPageTitle": "创建内容",
"contents.createTitle": "新内容", "contents.createTitle": "新内容",
"contents.currentStatusLabel": "当前版本", "contents.currentStatusLabel": "当前版本",
@ -395,19 +389,14 @@
"contents.draftNew": "新草稿", "contents.draftNew": "新草稿",
"contents.draftStatus": "新版本", "contents.draftStatus": "新版本",
"contents.editPageTitle": "编辑内容", "contents.editPageTitle": "编辑内容",
"contents.invariantFieldDescription": "内容项的 '{fieldName}' 字段。",
"contents.languageModeAll": "所有语言", "contents.languageModeAll": "所有语言",
"contents.languageModeSingle": "单一语言", "contents.languageModeSingle": "单一语言",
"contents.lastModifiedByFieldDescription": "上次修改内容项的用户。",
"contents.lastModifiedFieldDescription": "上次修改内容项的日期时间。",
"contents.lastUpdatedLabel": "上次更新", "contents.lastUpdatedLabel": "上次更新",
"contents.loadContent": "加载", "contents.loadContent": "加载",
"contents.loadContentFailed": "加载内容失败,请重新加载。", "contents.loadContentFailed": "加载内容失败,请重新加载。",
"contents.loadDataFailed": "加载数据失败,请重新加载。", "contents.loadDataFailed": "加载数据失败,请重新加载。",
"contents.loadFailed": "加载内容失败,请重新加载。", "contents.loadFailed": "加载内容失败,请重新加载。",
"contents.loadVersionFailed": "加载新版本失败。请重新加载。", "contents.loadVersionFailed": "加载新版本失败。请重新加载。",
"contents.localizedFieldDescription": "内容项的 '{fieldName}' 字段(本地化)。",
"contents.newStatusFieldDescription": "内容项的新状态。",
"contents.noReference": "- 无引用 -", "contents.noReference": "- 无引用 -",
"contents.noReferences": "此内容没有引用。", "contents.noReferences": "此内容没有引用。",
"contents.noReferencing": "此内容未被其他项目引用。", "contents.noReferencing": "此内容未被其他项目引用。",
@ -427,15 +416,12 @@
"contents.removeConfirmTitle": "删除内容", "contents.removeConfirmTitle": "删除内容",
"contents.saveAndPublish": "保存并发布", "contents.saveAndPublish": "保存并发布",
"contents.scheduledAt": "at", "contents.scheduledAt": "at",
"contents.scheduledAtLabel": "at",
"contents.scheduledTo": "to", "contents.scheduledTo": "to",
"contents.schemasPageTitle": "内容", "contents.schemasPageTitle": "内容",
"contents.searchPlaceholder": "全文搜索", "contents.searchPlaceholder": "全文搜索",
"contents.searchSchemasPlaceholder": "搜索Schemas...", "contents.searchSchemasPlaceholder": "搜索Schemas...",
"contents.selectionCount": "{count} 个选定的项目", "contents.selectionCount": "{count} 个选定的项目",
"contents.statusFieldDescription": "内容项的状态。",
"contents.statusQueries": "状态查询", "contents.statusQueries": "状态查询",
"contents.stockPhotoEmpty": "未选择任何内容",
"contents.stockPhotoSearch": "通过 Unsplash 搜索照片", "contents.stockPhotoSearch": "通过 Unsplash 搜索照片",
"contents.tableHeaders.created": "创建", "contents.tableHeaders.created": "创建",
"contents.tableHeaders.createdBy": "创建者", "contents.tableHeaders.createdBy": "创建者",
@ -460,7 +446,6 @@
"contents.validationHint": "当您看到验证错误时,请记住检查所有语言。", "contents.validationHint": "当您看到验证错误时,请记住检查所有语言。",
"contents.versionCompare": "比较", "contents.versionCompare": "比较",
"contents.versionDelete": "删除此版本", "contents.versionDelete": "删除此版本",
"contents.versionFieldDescription": "内容项的版本",
"contents.versionViewing": "查看版本**{version}**。", "contents.versionViewing": "查看版本**{version}**。",
"contents.viewLatest": "查看最新", "contents.viewLatest": "查看最新",
"contents.viewReset": "重置默认视图", "contents.viewReset": "重置默认视图",

14
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/UIFieldBuilder.cs → backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptionAttribute.cs

@ -5,16 +5,16 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Core
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{ {
public sealed class UIFieldBuilder : FieldBuilder<UIFieldBuilder, UIFieldProperties> [AttributeUsage(AttributeTargets.Property)]
public sealed class FieldDescriptionAttribute : Attribute
{ {
public UIFieldBuilder(UpsertSchemaFieldBase field, UIFieldProperties properties, CreateSchema schema) public string Name { get; }
: base(field, properties, schema)
public FieldDescriptionAttribute(string name)
{ {
Name = name;
} }
} }
} }

156
backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs

@ -60,6 +60,33 @@ namespace Squidex.Domain.Apps.Core {
} }
} }
/// <summary>
/// Looks up a localized string similar to The user or client that triggered the event or command..
/// </summary>
public static string Actor {
get {
return ResourceManager.GetString("Actor", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The ID of this actor..
/// </summary>
public static string ActorIdentifier {
get {
return ResourceManager.GetString("ActorIdentifier", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The type of this actor..
/// </summary>
public static string ActorType {
get {
return ResourceManager.GetString("ActorType", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to The ID of the current app.. /// Looks up a localized string similar to The ID of the current app..
/// </summary> /// </summary>
@ -303,6 +330,33 @@ namespace Squidex.Domain.Apps.Core {
} }
} }
/// <summary>
/// Looks up a localized string similar to The user when someone is mentioned with &apos;@&apos;..
/// </summary>
public static string CommentMentionedUser {
get {
return ResourceManager.GetString("CommentMentionedUser", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The text of the comment..
/// </summary>
public static string CommentText {
get {
return ResourceManager.GetString("CommentText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The URL pointing to the source of the comment..
/// </summary>
public static string CommentUrl {
get {
return ResourceManager.GetString("CommentUrl", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to {0} field ({1} component).. /// Looks up a localized string similar to {0} field ({1} component)..
/// </summary> /// </summary>
@ -321,6 +375,15 @@ namespace Squidex.Domain.Apps.Core {
} }
} }
/// <summary>
/// Looks up a localized string similar to The content..
/// </summary>
public static string Content {
get {
return ResourceManager.GetString("Content", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to {0} nested field.. /// Looks up a localized string similar to {0} nested field..
/// </summary> /// </summary>
@ -627,6 +690,42 @@ namespace Squidex.Domain.Apps.Core {
} }
} }
/// <summary>
/// Looks up a localized string similar to The actual event..
/// </summary>
public static string Event {
get {
return ResourceManager.GetString("Event", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The name of the event..
/// </summary>
public static string EventName {
get {
return ResourceManager.GetString("EventName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The event timestamp..
/// </summary>
public static string EventTimestamp {
get {
return ResourceManager.GetString("EventTimestamp", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The type of the event, e.g. &apos;Created&apos; or &apos;Updated&apos;..
/// </summary>
public static string EventType {
get {
return ResourceManager.GetString("EventType", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to The path to the json value.. /// Looks up a localized string similar to The path to the json value..
/// </summary> /// </summary>
@ -636,6 +735,24 @@ namespace Squidex.Domain.Apps.Core {
} }
} }
/// <summary>
/// Looks up a localized string similar to The ID part of this ID..
/// </summary>
public static string NamedId {
get {
return ResourceManager.GetString("NamedId", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The name part of this ID..
/// </summary>
public static string NamedName {
get {
return ResourceManager.GetString("NamedName", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to The current operation.. /// Looks up a localized string similar to The current operation..
/// </summary> /// </summary>
@ -717,6 +834,33 @@ namespace Squidex.Domain.Apps.Core {
} }
} }
/// <summary>
/// Looks up a localized string similar to The ID of the schema..
/// </summary>
public static string SchemaId {
get {
return ResourceManager.GetString("SchemaId", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The current number of calls..
/// </summary>
public static string UsageCallsCurrent {
get {
return ResourceManager.GetString("UsageCallsCurrent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The configured usage limit..
/// </summary>
public static string UsageCallsLimit {
get {
return ResourceManager.GetString("UsageCallsLimit", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Information about the current user.. /// Looks up a localized string similar to Information about the current user..
/// </summary> /// </summary>
@ -727,7 +871,7 @@ namespace Squidex.Domain.Apps.Core {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to The additional properties of the user.. /// Looks up a localized string similar to The additional properties of this user..
/// </summary> /// </summary>
public static string UserClaims { public static string UserClaims {
get { get {
@ -736,7 +880,7 @@ namespace Squidex.Domain.Apps.Core {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to The display name of the user.. /// Looks up a localized string similar to The display name of this user..
/// </summary> /// </summary>
public static string UserDisplayName { public static string UserDisplayName {
get { get {
@ -745,7 +889,7 @@ namespace Squidex.Domain.Apps.Core {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to The email address of the current user.. /// Looks up a localized string similar to The email address ofthis user..
/// </summary> /// </summary>
public static string UserEmail { public static string UserEmail {
get { get {
@ -754,7 +898,7 @@ namespace Squidex.Domain.Apps.Core {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to The ID of the user.. /// Looks up a localized string similar to The ID of this user..
/// </summary> /// </summary>
public static string UserId { public static string UserId {
get { get {
@ -763,7 +907,7 @@ namespace Squidex.Domain.Apps.Core {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to True when the current user is a client, which is typically the case when the request is made from the API.. /// Looks up a localized string similar to True when this user is a client, which is typically the case when the request is made from the API..
/// </summary> /// </summary>
public static string UserIsClient { public static string UserIsClient {
get { get {
@ -772,7 +916,7 @@ namespace Squidex.Domain.Apps.Core {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to True when the current user is a user, which is typically the case when the request is made in the UI.. /// Looks up a localized string similar to True when this user is a user, which is typically the case when the request is made in the UI..
/// </summary> /// </summary>
public static string UserIsUser { public static string UserIsUser {
get { get {

60
backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx

@ -117,6 +117,15 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Actor" xml:space="preserve">
<value>The user or client that triggered the event or command.</value>
</data>
<data name="ActorIdentifier" xml:space="preserve">
<value>The ID of this actor.</value>
</data>
<data name="ActorType" xml:space="preserve">
<value>The type of this actor.</value>
</data>
<data name="AppId" xml:space="preserve"> <data name="AppId" xml:space="preserve">
<value>The ID of the current app.</value> <value>The ID of the current app.</value>
</data> </data>
@ -198,12 +207,24 @@
<data name="Command" xml:space="preserve"> <data name="Command" xml:space="preserve">
<value>The executed command.</value> <value>The executed command.</value>
</data> </data>
<data name="CommentMentionedUser" xml:space="preserve">
<value>The user when someone is mentioned with '@'.</value>
</data>
<data name="CommentText" xml:space="preserve">
<value>The text of the comment.</value>
</data>
<data name="CommentUrl" xml:space="preserve">
<value>The URL pointing to the source of the comment.</value>
</data>
<data name="ComponentField" xml:space="preserve"> <data name="ComponentField" xml:space="preserve">
<value>{0} field ({1} component).</value> <value>{0} field ({1} component).</value>
</data> </data>
<data name="ComponentSchemaId" xml:space="preserve"> <data name="ComponentSchemaId" xml:space="preserve">
<value>The schema id to identity the component type.</value> <value>The schema id to identity the component type.</value>
</data> </data>
<data name="Content" xml:space="preserve">
<value>The content.</value>
</data>
<data name="ContentArrayField" xml:space="preserve"> <data name="ContentArrayField" xml:space="preserve">
<value>{0} nested field.</value> <value>{0} nested field.</value>
</data> </data>
@ -306,9 +327,27 @@
<data name="EntityVersion" xml:space="preserve"> <data name="EntityVersion" xml:space="preserve">
<value>The version of the objec.</value> <value>The version of the objec.</value>
</data> </data>
<data name="Event" xml:space="preserve">
<value>The actual event.</value>
</data>
<data name="EventName" xml:space="preserve">
<value>The name of the event.</value>
</data>
<data name="EventTimestamp" xml:space="preserve">
<value>The event timestamp.</value>
</data>
<data name="EventType" xml:space="preserve">
<value>The type of the event, e.g. 'Created' or 'Updated'.</value>
</data>
<data name="JsonPath" xml:space="preserve"> <data name="JsonPath" xml:space="preserve">
<value>The path to the json value.</value> <value>The path to the json value.</value>
</data> </data>
<data name="NamedId" xml:space="preserve">
<value>The ID part of this ID.</value>
</data>
<data name="NamedName" xml:space="preserve">
<value>The name part of this ID.</value>
</data>
<data name="Operation" xml:space="preserve"> <data name="Operation" xml:space="preserve">
<value>The current operation.</value> <value>The current operation.</value>
</data> </data>
@ -336,26 +375,35 @@
<data name="QueryVersion" xml:space="preserve"> <data name="QueryVersion" xml:space="preserve">
<value>The optional version of the content to retrieve an older instance (not cached).</value> <value>The optional version of the content to retrieve an older instance (not cached).</value>
</data> </data>
<data name="SchemaId" xml:space="preserve">
<value>The ID of the schema.</value>
</data>
<data name="UsageCallsCurrent" xml:space="preserve">
<value>The current number of calls.</value>
</data>
<data name="UsageCallsLimit" xml:space="preserve">
<value>The configured usage limit.</value>
</data>
<data name="User" xml:space="preserve"> <data name="User" xml:space="preserve">
<value>Information about the current user.</value> <value>Information about the current user.</value>
</data> </data>
<data name="UserClaims" xml:space="preserve"> <data name="UserClaims" xml:space="preserve">
<value>The additional properties of the user.</value> <value>The additional properties of this user.</value>
</data> </data>
<data name="UserDisplayName" xml:space="preserve"> <data name="UserDisplayName" xml:space="preserve">
<value>The display name of the user.</value> <value>The display name of this user.</value>
</data> </data>
<data name="UserEmail" xml:space="preserve"> <data name="UserEmail" xml:space="preserve">
<value>The email address of the current user.</value> <value>The email address ofthis user.</value>
</data> </data>
<data name="UserId" xml:space="preserve"> <data name="UserId" xml:space="preserve">
<value>The ID of the user.</value> <value>The ID of this user.</value>
</data> </data>
<data name="UserIsClient" xml:space="preserve"> <data name="UserIsClient" xml:space="preserve">
<value>True when the current user is a client, which is typically the case when the request is made from the API.</value> <value>True when this user is a client, which is typically the case when the request is made from the API.</value>
</data> </data>
<data name="UserIsUser" xml:space="preserve"> <data name="UserIsUser" xml:space="preserve">
<value>True when the current user is a user, which is typically the case when the request is made in the UI.</value> <value>True when this user is a user, which is typically the case when the request is made in the UI.</value>
</data> </data>
<data name="UsersClaimsValue" xml:space="preserve"> <data name="UsersClaimsValue" xml:space="preserve">
<value>The list of additional properties that have the name 'name'.</value> <value>The list of additional properties that have the name 'name'.</value>

29
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs

@ -13,32 +13,61 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public sealed class EnrichedAssetEvent : EnrichedUserEventBase, IEnrichedEntityEvent public sealed class EnrichedAssetEvent : EnrichedUserEventBase, IEnrichedEntityEvent
{ {
[FieldDescription(nameof(FieldDescriptions.EventType))]
public EnrichedAssetEventType Type { get; set; } public EnrichedAssetEventType Type { get; set; }
[FieldDescription(nameof(FieldDescriptions.EntityId))]
public DomainId Id { get; set; } public DomainId Id { get; set; }
[FieldDescription(nameof(FieldDescriptions.EntityCreated))]
public Instant Created { get; set; } public Instant Created { get; set; }
[FieldDescription(nameof(FieldDescriptions.EntityLastModified))]
public Instant LastModified { get; set; } public Instant LastModified { get; set; }
[FieldDescription(nameof(FieldDescriptions.EntityCreatedBy))]
public RefToken CreatedBy { get; set; } public RefToken CreatedBy { get; set; }
[FieldDescription(nameof(FieldDescriptions.EntityLastModifiedBy))]
public RefToken LastModifiedBy { get; set; } public RefToken LastModifiedBy { get; set; }
[FieldDescription(nameof(FieldDescriptions.AssetParentId))]
public DomainId ParentId { get; }
[FieldDescription(nameof(FieldDescriptions.AssetMimeType))]
public string MimeType { get; set; } public string MimeType { get; set; }
[FieldDescription(nameof(FieldDescriptions.AssetFileName))]
public string FileName { get; set; } public string FileName { get; set; }
[FieldDescription(nameof(FieldDescriptions.AssetFileHash))]
public string FileHash { get; set; }
[FieldDescription(nameof(FieldDescriptions.AssetSlug))]
public string Slug { get; set; }
[FieldDescription(nameof(FieldDescriptions.AssetFileVersion))]
public long FileVersion { get; set; } public long FileVersion { get; set; }
[FieldDescription(nameof(FieldDescriptions.AssetFileSize))]
public long FileSize { get; set; } public long FileSize { get; set; }
[FieldDescription(nameof(FieldDescriptions.AssetIsProtected))]
public bool IsProtected { get; set; }
[FieldDescription(nameof(FieldDescriptions.AssetPixelWidth))]
public int? PixelWidth { get; set; } public int? PixelWidth { get; set; }
[FieldDescription(nameof(FieldDescriptions.AssetPixelHeight))]
public int? PixelHeight { get; set; } public int? PixelHeight { get; set; }
[FieldDescription(nameof(FieldDescriptions.AssetType))]
public AssetType AssetType { get; set; } public AssetType AssetType { get; set; }
[FieldDescription(nameof(FieldDescriptions.AssetMetadata))]
public AssetMetadata Metadata { get; }
[FieldDescription(nameof(FieldDescriptions.AssetIsImage))]
public bool IsImage public bool IsImage
{ {
get => AssetType == AssetType.Image; get => AssetType == AssetType.Image;

5
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs

@ -9,16 +9,19 @@ using System.Runtime.Serialization;
using Squidex.Shared.Users; using Squidex.Shared.Users;
#pragma warning disable CA1822 // Mark members as static #pragma warning disable CA1822 // Mark members as static
#pragma warning disable SA1133 // Do not combine attributes
namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public sealed class EnrichedCommentEvent : EnrichedUserEventBase public sealed class EnrichedCommentEvent : EnrichedUserEventBase
{ {
[FieldDescription(nameof(FieldDescriptions.CommentText))]
public string Text { get; set; } public string Text { get; set; }
[FieldDescription(nameof(FieldDescriptions.CommentUrl))]
public Uri? Url { get; set; } public Uri? Url { get; set; }
[IgnoreDataMember] [FieldDescription(nameof(FieldDescriptions.CommentMentionedUser)), IgnoreDataMember]
public IUser MentionedUser { get; set; } public IUser MentionedUser { get; set; }
[IgnoreDataMember] [IgnoreDataMember]

10
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs

@ -13,24 +13,34 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public sealed class EnrichedContentEvent : EnrichedSchemaEventBase, IEnrichedEntityEvent public sealed class EnrichedContentEvent : EnrichedSchemaEventBase, IEnrichedEntityEvent
{ {
[FieldDescription(nameof(FieldDescriptions.EventType))]
public EnrichedContentEventType Type { get; set; } public EnrichedContentEventType Type { get; set; }
[FieldDescription(nameof(FieldDescriptions.EntityId))]
public DomainId Id { get; set; } public DomainId Id { get; set; }
[FieldDescription(nameof(FieldDescriptions.EntityCreated))]
public Instant Created { get; set; } public Instant Created { get; set; }
[FieldDescription(nameof(FieldDescriptions.EntityLastModified))]
public Instant LastModified { get; set; } public Instant LastModified { get; set; }
[FieldDescription(nameof(FieldDescriptions.EntityCreatedBy))]
public RefToken CreatedBy { get; set; } public RefToken CreatedBy { get; set; }
[FieldDescription(nameof(FieldDescriptions.EntityLastModifiedBy))]
public RefToken LastModifiedBy { get; set; } public RefToken LastModifiedBy { get; set; }
[FieldDescription(nameof(FieldDescriptions.ContentData))]
public ContentData Data { get; set; } public ContentData Data { get; set; }
[FieldDescription(nameof(FieldDescriptions.ContentDataOld))]
public ContentData? DataOld { get; set; } public ContentData? DataOld { get; set; }
[FieldDescription(nameof(FieldDescriptions.ContentStatus))]
public Status Status { get; set; } public Status Status { get; set; }
[FieldDescription(nameof(FieldDescriptions.ContentNewStatus))]
public Status? NewStatus { get; set; } public Status? NewStatus { get; set; }
public override long Partition public override long Partition

4
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedEvent.cs

@ -12,12 +12,16 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public abstract class EnrichedEvent public abstract class EnrichedEvent
{ {
[FieldDescription(nameof(FieldDescriptions.AppId))]
public NamedId<DomainId> AppId { get; set; } public NamedId<DomainId> AppId { get; set; }
[FieldDescription(nameof(FieldDescriptions.EventTimestamp))]
public Instant Timestamp { get; set; } public Instant Timestamp { get; set; }
[FieldDescription(nameof(FieldDescriptions.EventName))]
public string Name { get; set; } public string Name { get; set; }
[FieldDescription(nameof(FieldDescriptions.EntityVersion))]
public long Version { get; set; } public long Version { get; set; }
public abstract long Partition { get; } public abstract long Partition { get; }

1
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs

@ -11,6 +11,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public sealed class EnrichedSchemaEvent : EnrichedSchemaEventBase, IEnrichedEntityEvent public sealed class EnrichedSchemaEvent : EnrichedSchemaEventBase, IEnrichedEntityEvent
{ {
[FieldDescription(nameof(FieldDescriptions.EventType))]
public EnrichedSchemaEventType Type { get; set; } public EnrichedSchemaEventType Type { get; set; }
public DomainId Id public DomainId Id

1
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventBase.cs

@ -11,6 +11,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public abstract class EnrichedSchemaEventBase : EnrichedUserEventBase public abstract class EnrichedSchemaEventBase : EnrichedUserEventBase
{ {
[FieldDescription(nameof(FieldDescriptions.EntityVersion))]
public NamedId<DomainId> SchemaId { get; set; } public NamedId<DomainId> SchemaId { get; set; }
} }
} }

2
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUsageExceededEvent.cs

@ -9,8 +9,10 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public sealed class EnrichedUsageExceededEvent : EnrichedEvent public sealed class EnrichedUsageExceededEvent : EnrichedEvent
{ {
[FieldDescription(nameof(FieldDescriptions.UsageCallsCurrent))]
public long CallsCurrent { get; set; } public long CallsCurrent { get; set; }
[FieldDescription(nameof(FieldDescriptions.UsageCallsLimit))]
public long CallsLimit { get; set; } public long CallsLimit { get; set; }
public override long Partition public override long Partition

4
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUserEventBase.cs

@ -10,14 +10,16 @@ using Squidex.Infrastructure;
using Squidex.Shared.Users; using Squidex.Shared.Users;
#pragma warning disable CA1822 // Mark members as static #pragma warning disable CA1822 // Mark members as static
#pragma warning disable SA1133 // Do not combine attributes
namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public abstract class EnrichedUserEventBase : EnrichedEvent public abstract class EnrichedUserEventBase : EnrichedEvent
{ {
[FieldDescription(nameof(FieldDescriptions.Actor))]
public RefToken Actor { get; set; } public RefToken Actor { get; set; }
[IgnoreDataMember] [FieldDescription(nameof(FieldDescriptions.User)), IgnoreDataMember]
public IUser? User { get; set; } public IUser? User { get; set; }
public bool ShouldSerializeUser() public bool ShouldSerializeUser()

2
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs

@ -29,7 +29,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
public FieldNames FieldsInReferences { get; private set; } = FieldNames.Empty; public FieldNames FieldsInReferences { get; private set; } = FieldNames.Empty;
public SchemaScripts Scripts { get; private set; } = SchemaScripts.Empty; public SchemaScripts Scripts { get; private set; } = new SchemaScripts();
public SchemaProperties Properties { get; private set; } = new SchemaProperties(); public SchemaProperties Properties { get; private set; } = new SchemaProperties();

2
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs

@ -9,8 +9,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
{ {
public sealed record SchemaScripts public sealed record SchemaScripts
{ {
public static readonly SchemaScripts Empty = new SchemaScripts();
public string? Change { get; init; } public string? Change { get; init; }
public string? Create { get; init; } public string? Create { get; init; }

33
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System.Globalization; using System.Globalization;
using System.Security.Claims;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -14,6 +15,8 @@ using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.Templates; using Squidex.Domain.Apps.Core.Templates;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Shared;
using Squidex.Shared.Identity;
using Squidex.Text; using Squidex.Text;
using ValueTaskSupplement; using ValueTaskSupplement;
@ -76,12 +79,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public virtual string ToPayload<T>(T @event) public virtual string ToPayload<T>(T @event)
{ {
return jsonSerializer.Serialize(@event); var payload = @event;
return jsonSerializer.Serialize(payload);
} }
public virtual string ToEnvelope(EnrichedEvent @event) public virtual string ToEnvelope(EnrichedEvent @event)
{ {
return jsonSerializer.Serialize(new { type = @event.Name, payload = @event, timestamp = @event.Timestamp }); var payload = new { type = @event.Name, payload = @event, timestamp = @event.Timestamp };
return jsonSerializer.Serialize(payload);
} }
public async ValueTask<string?> FormatAsync(string text, EnrichedEvent @event) public async ValueTask<string?> FormatAsync(string text, EnrichedEvent @event)
@ -103,14 +110,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules
if (TryGetScript(text.Trim(), out var script)) if (TryGetScript(text.Trim(), out var script))
{ {
var vars = new ScriptVars // Script vars are just wrappers over dictionaries for better performance.
var vars = new EventScriptVars
{ {
["event"] = @event Event = @event,
AppId = @event.AppId.Id,
AppName = @event.AppId.Name,
User = Admin()
}; };
#pragma warning disable MA0042 // Do not use blocking calls in an async method var result = (await scriptEngine.ExecuteAsync(vars, script)).ToString();
var result = scriptEngine.Execute(vars, script).ToString();
#pragma warning restore MA0042 // Do not use blocking calls in an async method
if (result == "undefined") if (result == "undefined")
{ {
@ -130,6 +139,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return CombineParts(text, parts); return CombineParts(text, parts);
} }
private static ClaimsPrincipal Admin()
{
var claimsIdentity = new ClaimsIdentity();
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, Permissions.All));
return claimsPrincipal;
}
private static string CombineParts(string text, List<TextPart> parts) private static string CombineParts(string text, List<TextPart> parts)
{ {
var span = text.AsSpan(); var span = text.AsSpan();

2
backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.Designer.cs

@ -133,7 +133,7 @@ namespace Squidex.Domain.Apps.Core.Properties {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Tell Squidex to not allow the current operation and to return a 403 (Forbidden).. /// Looks up a localized string similar to Tell Squidex to not allow the current operation and to return a 400 (BadRequest)..
/// </summary> /// </summary>
internal static string ScriptingDisallow { internal static string ScriptingDisallow {
get { get {

2
backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.resx

@ -142,7 +142,7 @@
<value>Makes a DELETE request to the defined URL and parses the result as JSON. Headers are optional.</value> <value>Makes a DELETE request to the defined URL and parses the result as JSON. Headers are optional.</value>
</data> </data>
<data name="ScriptingDisallow" xml:space="preserve"> <data name="ScriptingDisallow" xml:space="preserve">
<value>Tell Squidex to not allow the current operation and to return a 403 (Forbidden).</value> <value>Tell Squidex to not allow the current operation and to return a 400 (BadRequest).</value>
</data> </data>
<data name="ScriptingFormatDate" xml:space="preserve"> <data name="ScriptingFormatDate" xml:space="preserve">
<value>Formats a JavaScript date object using the specified pattern.</value> <value>Formats a JavaScript date object using the specified pattern.</value>

82
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetCommandScriptVars.cs

@ -0,0 +1,82 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Scripting.Internal;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Scripting
{
public sealed class AssetCommandScriptVars : ScriptVars
{
[FieldDescription(nameof(FieldDescriptions.AssetParentId))]
public DomainId ParentId
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetFileHash))]
public string? FileHash
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetFileName))]
public string? FileName
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetSlug))]
public string? FileSlug
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetMimeType))]
public string? MimeType
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetParentPath))]
public Array? ParentPath
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetMetadata))]
public AssetMetadata? Metadata
{
set => SetValue(value != null ? new AssetMetadataWrapper(value) : null);
}
[FieldDescription(nameof(FieldDescriptions.AssetTags))]
public HashSet<string>? Tags
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetFileSize))]
public long FileSize
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetIsProtected))]
public bool? IsProtected
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.EntityRequestDeletePermanent))]
public bool? Permanent
{
set => SetValue(value);
}
}
}

83
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetEntityScriptVars.cs

@ -0,0 +1,83 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.ObjectModel;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Scripting
{
public sealed class AssetEntityScriptVars : ScriptVars
{
[FieldDescription(nameof(FieldDescriptions.AssetParentId))]
public DomainId ParentId
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetFileHash))]
public string? FileHash
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetFileName))]
public string? FileName
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetSlug))]
public string? FileSlug
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetMimeType))]
public string? MimeType
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetParentPath))]
public Array? ParentPath
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetMetadata))]
public AssetMetadata? Metadata
{
set => SetValue(value != null ? new ReadOnlyDictionary<string, IJsonValue>(value) : null);
}
[FieldDescription(nameof(FieldDescriptions.AssetTags))]
public HashSet<string>? Tags
{
set => SetValue(value != null ? new ReadOnlyCollection<string>(value.ToList()) : null);
}
[FieldDescription(nameof(FieldDescriptions.AssetFileSize))]
public long FileSize
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetFileVersion))]
public long FileVersion
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AssetIsProtected))]
public bool? IsProtected
{
set => SetValue(value);
}
}
}

57
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetScriptVars.cs

@ -0,0 +1,57 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Security.Claims;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Scripting
{
public sealed class AssetScriptVars : ScriptVars
{
[FieldDescription(nameof(FieldDescriptions.AppId))]
public DomainId AppId
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.EntityId))]
public DomainId AssetId
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AppName))]
public string AppName
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.Operation))]
public string Operation
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.Command))]
public AssetCommandScriptVars Command
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.Asset))]
public AssetEntityScriptVars Asset
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.User))]
public ClaimsPrincipal? User
{
set => SetValue(value);
}
}
}

101
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentScriptVars.cs

@ -0,0 +1,101 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Security.Claims;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Scripting
{
public sealed class ContentScriptVars : DataScriptVars
{
[FieldDescription(nameof(FieldDescriptions.AppId))]
public DomainId AppId
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.SchemaId))]
public DomainId SchemaId
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.EntityId))]
public DomainId ContentId
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.AppName))]
public string AppName
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.ContentSchemaName))]
public string SchemaName
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.Operation))]
public string Operation
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.EntityRequestDeletePermanent))]
public bool Permanent
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.User))]
public ClaimsPrincipal? User
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.ContentStatus))]
public Status Status
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.ContentStatusOld))]
public Status StatusOld
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.ContentStatusOld))]
public Status OldStatus
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.ContentData))]
public ContentData? DataOld
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.ContentDataOld))]
public ContentData? OldData
{
set => SetValue(value);
}
[FieldDescription(nameof(FieldDescriptions.ContentData))]
public override ContentData? Data
{
get => GetValue<ContentData?>();
set => SetValue(value);
}
}
}

4
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs

@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
var propertyName = property.AsString(); var propertyName = property.AsString();
fieldProperties.GetOrAdd(propertyName, this, (k, c) => new ContentDataProperty(c)).Value = value; fieldProperties.GetOrAdd(propertyName, (k, c) => new ContentDataProperty(c), this).Value = value;
return true; return true;
} }
@ -114,7 +114,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
return PropertyDescriptor.Undefined; return PropertyDescriptor.Undefined;
} }
return fieldProperties.GetOrAdd(propertyName, this, (k, c) => new ContentDataProperty(c, new ContentFieldObject(c, new ContentFieldData(), false))); return fieldProperties.GetOrAdd(propertyName, (k, c) => new ContentDataProperty(c, new ContentFieldObject(c, new ContentFieldData(), false)), this);
} }
public override IEnumerable<KeyValuePair<JsValue, PropertyDescriptor>> GetOwnProperties() public override IEnumerable<KeyValuePair<JsValue, PropertyDescriptor>> GetOwnProperties()

20
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/DataScriptVars.cs

@ -0,0 +1,20 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Core.Scripting
{
public class DataScriptVars : ScriptVars
{
public virtual ContentData? Data
{
get => GetValue<ContentData?>();
set => SetValue(value);
}
}
}

36
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/EventScriptVars.cs

@ -0,0 +1,36 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Security.Claims;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Scripting
{
public sealed class EventScriptVars : ScriptVars
{
public DomainId AppId
{
set => SetValue(value);
}
public string AppName
{
set => SetValue(value);
}
public ClaimsPrincipal User
{
set => SetValue(value);
}
public EnrichedEvent Event
{
set => SetValue(value);
}
}
}

5
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs

@ -36,6 +36,11 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
public void Describe(AddDescription describe, ScriptScope scope) public void Describe(AddDescription describe, ScriptScope scope)
{ {
if (!scope.HasFlag(ScriptScope.Async))
{
return;
}
describe(JsonType.Function, "getJSON(url, callback, headers?)", describe(JsonType.Function, "getJSON(url, callback, headers?)",
Resources.ScriptingGetJSON); Resources.ScriptingGetJSON);

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

@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
Task<IJsonValue> ExecuteAsync(ScriptVars vars, string script, ScriptOptions options = default, Task<IJsonValue> ExecuteAsync(ScriptVars vars, string script, ScriptOptions options = default,
CancellationToken ct = default); CancellationToken ct = default);
Task<ContentData> TransformAsync(ScriptVars vars, string script, ScriptOptions options = default, Task<ContentData> TransformAsync(DataScriptVars vars, string script, ScriptOptions options = default,
CancellationToken ct = default); CancellationToken ct = default);
IJsonValue Execute(ScriptVars vars, string script, ScriptOptions options = default); IJsonValue Execute(ScriptVars vars, string script, ScriptOptions options = default);

125
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/AssetMetadataWrapper.cs

@ -0,0 +1,125 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Scripting.Internal
{
internal sealed class AssetMetadataWrapper : IDictionary<string, object?>
{
private readonly AssetMetadata metadata;
public int Count
{
get => metadata.Count;
}
public ICollection<string> Keys
{
get => metadata.Keys;
}
public ICollection<object?> Values
{
get => metadata.Values.Cast<object?>().ToList();
}
public object? this[string key]
{
get => metadata[key];
set => metadata[key] = JsonValue.Create(value);
}
public bool IsReadOnly
{
get => false;
}
public AssetMetadataWrapper(AssetMetadata metadata)
{
this.metadata = metadata;
}
public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value)
{
if (metadata.TryGetValue(key, out var temp))
{
value = temp;
return true;
}
else
{
value = null;
return false;
}
}
public void Add(string key, object? value)
{
metadata.Add(key, JsonValue.Create(value));
}
public void Add(KeyValuePair<string, object?> item)
{
Add(item.Key, item.Value);
}
public bool Remove(string key)
{
return metadata.Remove(key);
}
public bool Remove(KeyValuePair<string, object?> item)
{
return false;
}
public void Clear()
{
metadata.Clear();
}
public bool Contains(KeyValuePair<string, object?> item)
{
return false;
}
public bool ContainsKey(string key)
{
return metadata.ContainsKey(key);
}
public void CopyTo(KeyValuePair<string, object?>[] array, int arrayIndex)
{
var i = arrayIndex;
foreach (var item in metadata)
{
if (i >= array.Length)
{
break;
}
array[i] = new KeyValuePair<string, object?>(item.Key, item.Value);
i++;
}
}
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator()
{
return metadata.Select(x => new KeyValuePair<string, object?>(x.Key, x.Value)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)metadata).GetEnumerator();
}
}
}

15
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs

@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
} }
} }
public async Task<ContentData> TransformAsync(ScriptVars vars, string script, ScriptOptions options = default, public async Task<ContentData> TransformAsync(DataScriptVars vars, string script, ScriptOptions options = default,
CancellationToken ct = default) CancellationToken ct = default)
{ {
Guard.NotNull(vars); Guard.NotNull(vars);
@ -194,20 +194,25 @@ namespace Squidex.Domain.Apps.Core.Scripting
public void Describe(AddDescription describe, ScriptScope scope) public void Describe(AddDescription describe, ScriptScope scope)
{ {
if (scope.HasFlag(ScriptScope.ContentTrigger) || scope.HasFlag(ScriptScope.AssetTrigger))
{
return;
}
if (scope.HasFlag(ScriptScope.Transform) || scope.HasFlag(ScriptScope.ContentScript)) if (scope.HasFlag(ScriptScope.Transform) || scope.HasFlag(ScriptScope.ContentScript))
{ {
describe(JsonType.Function, "replace()", describe(JsonType.Function, "replace()",
Resources.ScriptingReplace); Resources.ScriptingReplace);
} }
describe(JsonType.Function, "disallow()", describe(JsonType.Function, "disallow(reason)",
Resources.ScriptingDisallow); Resources.ScriptingDisallow);
describe(JsonType.Function, "complete()",
Resources.ScriptingComplete);
describe(JsonType.Function, "reject(reason)", describe(JsonType.Function, "reject(reason)",
Resources.ScriptingReject); Resources.ScriptingReject);
describe(JsonType.Function, "complete()",
Resources.ScriptingComplete);
} }
} }
} }

5
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContext.cs

@ -17,11 +17,6 @@ namespace Squidex.Domain.Apps.Core.Scripting
{ {
} }
public ScriptContext(ScriptContext source)
: base(source, StringComparer.OrdinalIgnoreCase)
{
}
public bool TryGetValue<T>(string key, [MaybeNullWhen(false)] out T value) public bool TryGetValue<T>(string key, [MaybeNullWhen(false)] out T value)
{ {
Guard.NotNull(key); Guard.NotNull(key);

15
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptScope.cs

@ -10,10 +10,15 @@ namespace Squidex.Domain.Apps.Core.Scripting
[Flags] [Flags]
public enum ScriptScope public enum ScriptScope
{ {
AssetScript, None = 0,
AssetTrigger, AssetScript = 1,
ContentScript, AssetTrigger = 2,
ContentTrigger, Async = 4,
Transform CommentTrigger = 8,
ContentScript = 16,
ContentTrigger = 32,
SchemaTrigger = 128,
Transform = 256,
UsageTrigger = 512,
} }
} }

18
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs

@ -6,27 +6,11 @@
// ========================================================================== // ==========================================================================
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Core.Scripting namespace Squidex.Domain.Apps.Core.Scripting
{ {
public sealed class ScriptVars : ScriptContext public class ScriptVars : ScriptContext
{ {
public ScriptVars()
{
}
public ScriptVars(ScriptVars source)
: base(source)
{
}
public ContentData? Data
{
get => GetValue<ContentData?>();
set => SetValue(value);
}
public void SetValue(object? value, [CallerMemberName] string? key = null) public void SetValue(object? value, [CallerMemberName] string? key = null)
{ {
if (key != null) if (key != null)

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

@ -5,170 +5,269 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections;
using System.Globalization;
using System.Reflection;
using System.Security.Claims;
using System.Text.RegularExpressions;
using NodaTime;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Reflection.Internal;
using Squidex.Shared.Users;
using Squidex.Text;
namespace Squidex.Domain.Apps.Core.Scripting namespace Squidex.Domain.Apps.Core.Scripting
{ {
public sealed class ScriptingCompleter public sealed class ScriptingCompleter
{ {
private readonly IEnumerable<IScriptDescriptor> descriptors; private readonly IEnumerable<IScriptDescriptor> descriptors;
private static readonly FilterSchema DynamicData = new FilterSchema(FilterSchemaType.Object)
{
Fields = ReadonlyList.Create(
new FilterField(new FilterSchema(FilterSchemaType.Object), "my-field"),
new FilterField(FilterSchema.String, "my-field.iv"))
};
public ScriptingCompleter(IEnumerable<IScriptDescriptor> descriptors) public ScriptingCompleter(IEnumerable<IScriptDescriptor> descriptors)
{ {
this.descriptors = descriptors; this.descriptors = descriptors;
} }
public IReadOnlyList<ScriptingValue> Trigger(string type)
{
Guard.NotNull(type);
switch (type)
{
case "AssetChanged":
return AssetTrigger();
case "Comment":
return CommentTrigger();
case "ContentChanged":
return ContentTrigger(DynamicData);
case "SchemaChanged":
return SchemaTrigger();
case "Usage":
return UsageTrigger();
default:
return new List<ScriptingValue>();
}
}
public IReadOnlyList<ScriptingValue> ContentScript(FilterSchema dataSchema) public IReadOnlyList<ScriptingValue> ContentScript(FilterSchema dataSchema)
{ {
Guard.NotNull(dataSchema); Guard.NotNull(dataSchema);
return new Process(descriptors).Content(dataSchema, ScriptScope.ContentScript | ScriptScope.Transform); return new Process(descriptors, dataSchema.Flatten()).ContentScript();
} }
public IReadOnlyList<ScriptingValue> ContentTrigger(FilterSchema dataSchema) public IReadOnlyList<ScriptingValue> ContentTrigger(FilterSchema dataSchema)
{ {
Guard.NotNull(dataSchema); Guard.NotNull(dataSchema);
return new Process(descriptors).Content(dataSchema, ScriptScope.ContentTrigger); return new Process(descriptors, dataSchema.Flatten()).ContentTrigger();
} }
public IReadOnlyList<ScriptingValue> AssetScript() public IReadOnlyList<ScriptingValue> AssetScript()
{ {
return new Process(descriptors).Asset(ScriptScope.AssetScript); return new Process(descriptors).AssetScript();
} }
public IReadOnlyList<ScriptingValue> AssetTrigger() public IReadOnlyList<ScriptingValue> AssetTrigger()
{ {
return new Process(descriptors).Asset(ScriptScope.AssetTrigger); return new Process(descriptors).AssetTrigger();
}
public IReadOnlyList<ScriptingValue> CommentTrigger()
{
return new Process(descriptors).CommentTrigger();
}
public IReadOnlyList<ScriptingValue> SchemaTrigger()
{
return new Process(descriptors).SchemaTrigger();
}
public IReadOnlyList<ScriptingValue> UsageTrigger()
{
return new Process(descriptors).UsageTrigger();
} }
private sealed class Process private sealed class Process
{ {
private static readonly Regex PropertyRegex = new Regex(@"^(?!\d)[\w$]+$", RegexOptions.Compiled);
private readonly Stack<string> prefixes = new Stack<string>(); private readonly Stack<string> prefixes = new Stack<string>();
private readonly HashSet<ScriptingValue> result = new HashSet<ScriptingValue>(); private readonly Dictionary<string, ScriptingValue> result = new Dictionary<string, ScriptingValue>();
private readonly IEnumerable<IScriptDescriptor> descriptors; private readonly IEnumerable<IScriptDescriptor> descriptors;
private readonly FilterSchema? dataSchema;
public Process(IEnumerable<IScriptDescriptor> descriptors) public Process(IEnumerable<IScriptDescriptor> descriptors, FilterSchema? dataSchema = null)
{ {
this.descriptors = descriptors; this.descriptors = descriptors;
this.dataSchema = dataSchema;
} }
public IReadOnlyList<ScriptingValue> Content(FilterSchema dataSchema, ScriptScope scope) private IReadOnlyList<ScriptingValue> Build()
{ {
AddShared(scope); return result.Values.OrderBy(x => x.Path).ToList();
}
AddObject("ctx", FieldDescriptions.Context, () => public IReadOnlyList<ScriptingValue> SchemaTrigger()
{ {
AddString("contentId", AddHelpers(ScriptScope.SchemaTrigger | ScriptScope.Async);
FieldDescriptions.EntityId);
AddString("status", AddObject("event", FieldDescriptions.Event, () =>
FieldDescriptions.ContentStatus); {
AddType(typeof(EnrichedSchemaEvent));
});
AddString("statusOld", return Build();
FieldDescriptions.ContentStatusOld); }
AddObject("data", FieldDescriptions.ContentData, () => public IReadOnlyList<ScriptingValue> CommentTrigger()
{ {
AddData(dataSchema); AddHelpers(ScriptScope.CommentTrigger | ScriptScope.Async);
});
AddObject("dataOld", FieldDescriptions.ContentDataOld, () => AddObject("event", FieldDescriptions.Event, () =>
{ {
AddData(dataSchema); AddType(typeof(EnrichedCommentEvent));
});
}); });
return result.OrderBy(x => x.Path).ToList(); return Build();
} }
public IReadOnlyList<ScriptingValue> Asset(ScriptScope scope) public IReadOnlyList<ScriptingValue> UsageTrigger()
{ {
AddShared(scope); AddHelpers(ScriptScope.UsageTrigger | ScriptScope.Async);
AddObject("ctx", FieldDescriptions.Context, () => AddObject("event", FieldDescriptions.Event, () =>
{ {
AddString("assetId", AddType(typeof(EnrichedUsageExceededEvent));
FieldDescriptions.EntityId); });
AddObject("asset", return Build();
FieldDescriptions.Asset, () => }
public IReadOnlyList<ScriptingValue> ContentScript()
{ {
AddSharedAsset(); var scope = ScriptScope.ContentScript | ScriptScope.Transform | ScriptScope.Async;
AddNumber("fileVersion", AddHelpers(scope);
FieldDescriptions.AssetFileVersion);
});
AddObject("command", AddObject("ctx", FieldDescriptions.Context, () =>
FieldDescriptions.Command, () =>
{ {
AddSharedAsset(); AddType(typeof(ContentScriptVars));
AddBoolean("permanent",
FieldDescriptions.EntityRequestDeletePermanent);
});
}); });
return result.OrderBy(x => x.Path).ToList(); return Build();
} }
private void AddSharedAsset() public IReadOnlyList<ScriptingValue> ContentTrigger()
{ {
AddString("fileHash", var scope = ScriptScope.ContentTrigger | ScriptScope.Async;
FieldDescriptions.AssetFileHash);
AddString("fileName",
FieldDescriptions.AssetFileName);
AddString("fileSize", AddHelpers(scope);
FieldDescriptions.AssetFileSize);
AddString("fileSlug", AddObject("event", FieldDescriptions.Event, () =>
FieldDescriptions.AssetSlug); {
AddType(typeof(EnrichedContentEvent));
});
AddString("mimeType", return Build();
FieldDescriptions.AssetMimeType); }
AddBoolean("isProtected", public IReadOnlyList<ScriptingValue> AssetTrigger()
FieldDescriptions.AssetIsProtected); {
AddHelpers(ScriptScope.AssetTrigger | ScriptScope.Async);
AddString("parentId", AddObject("event", FieldDescriptions.Event, () =>
FieldDescriptions.AssetParentId); {
AddType(typeof(EnrichedAssetEvent));
});
AddArray("parentPath", return Build();
FieldDescriptions.AssetParentPath); }
AddArray("tags", public IReadOnlyList<ScriptingValue> AssetScript()
FieldDescriptions.AssetTags); {
AddHelpers(ScriptScope.AssetScript | ScriptScope.Async);
AddObject("metadata", AddObject("ctx", FieldDescriptions.Event, () =>
FieldDescriptions.AssetMetadata, () =>
{ {
AddArray("name", AddType(typeof(AssetScriptVars));
FieldDescriptions.AssetMetadataValue);
}); });
return Build();
} }
private void AddShared(ScriptScope scope) private void AddHelpers(ScriptScope scope)
{ {
foreach (var descriptor in descriptors) foreach (var descriptor in descriptors)
{ {
descriptor.Describe(Add, scope); descriptor.Describe(Add, scope);
} }
}
AddString("appId", private void AddType(Type type)
FieldDescriptions.AppId); {
foreach (var (name, description, propertyTypeOrNullable) in GetFields(type))
{
var propertyType = Nullable.GetUnderlyingType(propertyTypeOrNullable) ?? propertyTypeOrNullable;
AddString("appName", if (propertyType.IsEnum ||
FieldDescriptions.AppName); propertyType == typeof(string) ||
propertyType == typeof(DomainId) ||
propertyType == typeof(Instant) ||
propertyType == typeof(Status))
{
AddString(name, description);
}
else if (propertyType == typeof(int) || propertyType == typeof(long))
{
AddNumber(name, description);
}
else if (propertyType == typeof(bool))
{
AddBoolean(name, description);
}
else if (propertyType == typeof(AssetMetadata))
{
AddObject(name, description, () =>
{
AddString("my-name",
FieldDescriptions.AssetMetadataValue);
});
}
else if (propertyType == typeof(NamedId<DomainId>))
{
AddObject(name, description, () =>
{
AddString("id",
FieldDescriptions.NamedId);
AddString("operation", AddString("name",
FieldDescriptions.Operation); FieldDescriptions.NamedName);
});
}
else if (propertyType == typeof(RefToken))
{
AddObject(name, description, () =>
{
AddString("identifier",
FieldDescriptions.ActorIdentifier);
AddObject("user", AddString("type",
FieldDescriptions.User, () => FieldDescriptions.ActorType);
});
}
else if (propertyType == typeof(IUser) || propertyType == typeof(ClaimsPrincipal))
{
AddObject(name, description, () =>
{ {
AddString("id", AddString("id",
FieldDescriptions.UserId); FieldDescriptions.UserId);
@ -182,18 +281,54 @@ namespace Squidex.Domain.Apps.Core.Scripting
AddBoolean("isUser", AddBoolean("isUser",
FieldDescriptions.UserIsUser); FieldDescriptions.UserIsUser);
AddObject("claims", AddObject("claims", FieldDescriptions.UserClaims, () =>
FieldDescriptions.UserClaims, () =>
{ {
AddArray("name", AddArray("name",
FieldDescriptions.UsersClaimsValue); FieldDescriptions.UsersClaimsValue);
}); });
}); });
} }
else if (propertyType == typeof(ContentData) && dataSchema != null)
{
AddObject(name, description, () =>
{
AddData();
});
}
else if (GetFields(propertyType).Any())
{
AddObject(name, description, () =>
{
AddType(propertyType);
});
}
else if (propertyType.GetInterfaces().Contains(typeof(IEnumerable)))
{
AddArray(name, description);
}
}
}
private void AddData(FilterSchema dataSchema) private static IEnumerable<(string Name, string Description, Type Type)> GetFields(Type type)
{
foreach (var property in type.GetPublicProperties())
{ {
if (dataSchema.Fields == null) var descriptionKey = property.GetCustomAttribute<FieldDescriptionAttribute>()?.Name;
if (descriptionKey == null)
{
continue;
}
var description = FieldDescriptions.ResourceManager.GetString(descriptionKey, CultureInfo.InvariantCulture)!;
yield return (property.Name.ToCamelCase(), description, property.PropertyType);
}
}
private void AddData()
{
if (dataSchema?.Fields == null)
{ {
return; return;
} }
@ -268,9 +403,11 @@ namespace Squidex.Domain.Apps.Core.Scripting
private void Add(JsonType type, string? name, string? description) private void Add(JsonType type, string? name, string? description)
{ {
if (name != null) var parts = name?.Split('.') ?? Array.Empty<string>();
foreach (var part in parts)
{ {
prefixes.Push(name); PushPrefix(part);
} }
if (prefixes.Count == 0) if (prefixes.Count == 0)
@ -278,11 +415,11 @@ namespace Squidex.Domain.Apps.Core.Scripting
return; return;
} }
var path = string.Join('.', prefixes.Reverse()); var path = string.Concat(prefixes.Reverse());
result.Add(new ScriptingValue(path, type, description)); result[path] = new ScriptingValue(path, type, description);
if (name != null) for (int i = 0; i < parts.Length; i++)
{ {
prefixes.Pop(); prefixes.Pop();
} }
@ -292,16 +429,36 @@ namespace Squidex.Domain.Apps.Core.Scripting
{ {
Add(JsonType.Object, name, description); Add(JsonType.Object, name, description);
prefixes.Push(name); var parts = name.Split('.');
try
foreach (var part in parts)
{ {
inner(); PushPrefix(part);
} }
finally
inner();
for (int i = 0; i < parts.Length; i++)
{ {
prefixes.Pop(); prefixes.Pop();
} }
} }
private void PushPrefix(string name)
{
if (prefixes.Count == 0)
{
prefixes.Push(name);
}
else if (PropertyRegex.IsMatch(name))
{
prefixes.Push($".{name}");
}
else
{
prefixes.Push($"['{name}']");
}
}
} }
} }
} }

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

@ -26,7 +26,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="NJsonSchema" Version="10.6.7" /> <PackageReference Include="NJsonSchema" Version="10.6.10" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<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="6.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="6.0.0" />

11
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/AlwaysCreateClientCommandMiddleware.cs → backend/src/Squidex.Domain.Apps.Entities/Apps/AlwaysCreateClientCommandMiddleware.cs

@ -5,11 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates namespace Squidex.Domain.Apps.Entities.Apps
{ {
public sealed class AlwaysCreateClientCommandMiddleware : ICommandMiddleware public sealed class AlwaysCreateClientCommandMiddleware : ICommandMiddleware
{ {
@ -21,14 +22,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
{ {
var appId = NamedId.Of(createApp.AppId, createApp.Name); var appId = NamedId.Of(createApp.AppId, createApp.Name);
var publish = new Func<IAppCommand, Task>(command => var publish = new Func<IAppCommand, Task>(async command =>
{ {
command.AppId = appId; command.AppId = appId;
return context.CommandBus.PublishAsync(command); var newContext = await context.CommandBus.PublishAsync(command);
context.Complete(newContext.PlainResult);
}); });
await publish(new AttachClient { Id = "default" }); await publish(new AttachClient { Id = "default", Role = Role.Owner });
} }
} }
} }

3
backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs

@ -135,10 +135,11 @@ namespace Squidex.Domain.Apps.Entities.Apps
await appsIndex.RemoveReservationAsync(appReservation); await appsIndex.RemoveReservationAsync(appReservation);
} }
public async Task CompleteRestoreAsync(RestoreContext context) public async Task CompleteRestoreAsync(RestoreContext context, string appName)
{ {
await rebuilder.InsertManyAsync<AppDomainObject, AppDomainObject.State>(Enumerable.Repeat(context.AppId, 1), 1, default); await rebuilder.InsertManyAsync<AppDomainObject, AppDomainObject.State>(Enumerable.Repeat(context.AppId, 1), 1, default);
await appsIndex.RegisterAsync(context.AppId, appName);
await appsIndex.RemoveReservationAsync(appReservation); await appsIndex.RemoveReservationAsync(appReservation);
} }

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs

@ -15,6 +15,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands
public string Secret { get; set; } public string Secret { get; set; }
public string? Role { get; set; }
public AttachClient() public AttachClient()
{ {
Secret = RandomHash.New(); Secret = RandomHash.New();

6
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.State.cs

@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
public AppContributors Contributors { get; set; } = AppContributors.Empty; public AppContributors Contributors { get; set; } = AppContributors.Empty;
public AssetScripts AssetScripts { get; set; } = AssetScripts.Empty; public AssetScripts AssetScripts { get; set; } = new AssetScripts();
public LanguagesConfig Languages { get; set; } = LanguagesConfig.English; public LanguagesConfig Languages { get; set; } = LanguagesConfig.English;
@ -100,7 +100,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
return UpdateContributors(e, (e, c) => c.Remove(e.ContributorId)); return UpdateContributors(e, (e, c) => c.Remove(e.ContributorId));
case AppClientAttached e: case AppClientAttached e:
return UpdateClients(e, (e, c) => c.Add(e.Id, e.Secret)); return UpdateClients(e, (e, c) => c.Add(e.Id, e.Secret, e.Role));
case AppClientUpdated e: case AppClientUpdated e:
return UpdateClients(e, (e, c) => c.Update(e.Id, e.Name, e.Role, e.ApiCallsLimit, e.ApiTrafficLimit, e.AllowAnonymous)); return UpdateClients(e, (e, c) => c.Update(e.Id, e.Name, e.Role, e.ApiCallsLimit, e.ApiTrafficLimit, e.AllowAnonymous));
@ -212,7 +212,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
private bool UpdateAssetScripts(AssetScripts? scripts) private bool UpdateAssetScripts(AssetScripts? scripts)
{ {
AssetScripts = scripts ?? AssetScripts.Empty; AssetScripts = scripts ?? new AssetScripts();
return true; return true;
} }

6
backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs

@ -34,6 +34,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
this.grainCache = grainCache; this.grainCache = grainCache;
} }
public Task RegisterAsync(DomainId id, string name,
CancellationToken ct = default)
{
return Cache().AddAsync(id, name);
}
public Task RemoveReservationAsync(string? token, public Task RemoveReservationAsync(string? token,
CancellationToken ct = default) CancellationToken ct = default)
{ {

3
backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/IAppsIndex.cs

@ -24,6 +24,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
Task<string?> ReserveAsync(DomainId id, string name, Task<string?> ReserveAsync(DomainId id, string name,
CancellationToken ct = default); CancellationToken ct = default);
Task RegisterAsync(DomainId id, string name,
CancellationToken ct = default);
Task RemoveReservationAsync(string? token, Task RemoveReservationAsync(string? token,
CancellationToken ct = default); CancellationToken ct = default);
} }

144
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/ArrayFieldBuilder.cs

@ -1,144 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Text;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public sealed class ArrayFieldBuilder : FieldBuilder<ArrayFieldBuilder, ArrayFieldProperties>
{
private UpsertSchemaField TypedField
{
get => (UpsertSchemaField)Field;
}
public ArrayFieldBuilder(UpsertSchemaField field, ArrayFieldProperties properties, CreateSchema schema)
: base(field, properties, schema)
{
}
public ArrayFieldBuilder AddAssets(string name, Action<AssetFieldBuilder> configure)
{
var (field, properties) = AddField<AssetsFieldProperties>(name);
configure(new AssetFieldBuilder(field, properties, Schema));
return this;
}
public ArrayFieldBuilder AddBoolean(string name, Action<BooleanFieldBuilder> configure)
{
var (field, properties) = AddField<BooleanFieldProperties>(name);
configure(new BooleanFieldBuilder(field, properties, Schema));
return this;
}
public ArrayFieldBuilder AddDateTime(string name, Action<DateTimeFieldBuilder> configure)
{
var (field, properties) = AddField<DateTimeFieldProperties>(name);
configure(new DateTimeFieldBuilder(field, properties, Schema));
return this;
}
public ArrayFieldBuilder AddComponent(string name, Action<ComponentFieldBuilder> configure)
{
var (field, properties) = AddField<ComponentFieldProperties>(name);
configure(new ComponentFieldBuilder(field, properties, Schema));
return this;
}
public ArrayFieldBuilder AddComponents(string name, Action<ComponentsFieldBuilder> configure)
{
var (field, properties) = AddField<ComponentsFieldProperties>(name);
configure(new ComponentsFieldBuilder(field, properties, Schema));
return this;
}
public ArrayFieldBuilder AddJson(string name, Action<JsonFieldBuilder> configure)
{
var (field, properties) = AddField<JsonFieldProperties>(name);
configure(new JsonFieldBuilder(field, properties, Schema));
return this;
}
public ArrayFieldBuilder AddNumber(string name, Action<NumberFieldBuilder> configure)
{
var (field, properties) = AddField<NumberFieldProperties>(name);
configure(new NumberFieldBuilder(field, properties, Schema));
return this;
}
public ArrayFieldBuilder AddReferences(string name, Action<ReferencesFieldBuilder> configure)
{
var (field, properties) = AddField<ReferencesFieldProperties>(name);
configure(new ReferencesFieldBuilder(field, properties, Schema));
return this;
}
public ArrayFieldBuilder AddString(string name, Action<StringFieldBuilder> configure)
{
var (field, properties) = AddField<StringFieldProperties>(name);
configure(new StringFieldBuilder(field, properties, Schema));
return this;
}
public ArrayFieldBuilder AddTags(string name, Action<TagsFieldBuilder> configure)
{
var (field, properties) = AddField<TagsFieldProperties>(name);
configure(new TagsFieldBuilder(field, properties, Schema));
return this;
}
public ArrayFieldBuilder AddUI(string name, Action<UIFieldBuilder> configure)
{
var (field, properties) = AddField<UIFieldProperties>(name);
configure(new UIFieldBuilder(field, properties, Schema));
return this;
}
private (UpsertSchemaNestedField, T) AddField<T>(string name) where T : FieldProperties, new()
{
var properties = new T
{
Label = name
};
var nestedField = new UpsertSchemaNestedField
{
Name = name.ToCamelCase(),
Properties = properties
};
TypedField.Nested ??= Array.Empty<UpsertSchemaNestedField>();
TypedField.Nested = TypedField.Nested.Union(new[] { nestedField }).ToArray();
return (nestedField, properties);
}
}
}

20
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/AssetFieldBuilder.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public sealed class AssetFieldBuilder : FieldBuilder<AssetFieldBuilder, AssetsFieldProperties>
{
public AssetFieldBuilder(UpsertSchemaFieldBase field, AssetsFieldProperties properties, CreateSchema schema)
: base(field, properties, schema)
{
}
}
}

20
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/BooleanFieldBuilder.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public sealed class BooleanFieldBuilder : FieldBuilder<BooleanFieldBuilder, BooleanFieldProperties>
{
public BooleanFieldBuilder(UpsertSchemaFieldBase field, BooleanFieldProperties properties, CreateSchema schema)
: base(field, properties, schema)
{
}
}
}

20
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/ComponentFieldBuilder.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public sealed class ComponentFieldBuilder : FieldBuilder<ComponentFieldBuilder, ComponentFieldProperties>
{
public ComponentFieldBuilder(UpsertSchemaFieldBase field, ComponentFieldProperties properties, CreateSchema schema)
: base(field, properties, schema)
{
}
}
}

20
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/ComponentsFieldBuilder.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public sealed class ComponentsFieldBuilder : FieldBuilder<ComponentsFieldBuilder, ComponentsFieldProperties>
{
public ComponentsFieldBuilder(UpsertSchemaFieldBase field, ComponentsFieldProperties properties, CreateSchema schema)
: base(field, properties, schema)
{
}
}
}

20
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/DateTimeFieldBuilder.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public sealed class DateTimeFieldBuilder : FieldBuilder<DateTimeFieldBuilder, DateTimeFieldProperties>
{
public DateTimeFieldBuilder(UpsertSchemaFieldBase field, DateTimeFieldProperties properties, CreateSchema schema)
: base(field, properties, schema)
{
}
}
}

97
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/FieldBuilder.cs

@ -1,97 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public abstract class FieldBuilder
{
protected UpsertSchemaFieldBase Field { get; init; }
protected CreateSchema Schema { get; init; }
}
public abstract class FieldBuilder<T, TProperties> : FieldBuilder where T : FieldBuilder where TProperties : FieldProperties
{
private TProperties properties;
protected FieldBuilder(UpsertSchemaFieldBase field, TProperties properties, CreateSchema schema)
{
this.properties = properties;
Field = field;
Schema = schema;
}
public T Localizable()
{
if (Field is UpsertSchemaField localizableField)
{
localizableField.Partitioning = Partitioning.Language.Key;
}
return (T)(object)this;
}
public T Disabled(bool isDisabled = true)
{
Field.IsDisabled = isDisabled;
return (T)(object)this;
}
public T Hidden(bool isHidden = true)
{
Field.IsHidden = isHidden;
return (T)(object)this;
}
public T Label(string? label)
{
return Properties(x => x with { Label = label });
}
public T Hints(string? hints)
{
return Properties(x => x with { Hints = hints });
}
public T Required(bool isRequired = true)
{
return Properties(x => x with { IsRequired = isRequired });
}
public T Properties(Func<TProperties, TProperties> updater)
{
properties = updater(properties);
Field.Properties = properties;
return (T)(object)this;
}
public T ShowInList()
{
Schema.FieldsInLists ??= new FieldNames();
Schema.FieldsInLists.Add(Field.Name);
return (T)(object)this;
}
public T ShowInReferences()
{
Schema.FieldsInReferences ??= new FieldNames();
Schema.FieldsInReferences.Add(Field.Name);
return (T)(object)this;
}
}
}

20
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/JsonFieldBuilder.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public sealed class JsonFieldBuilder : FieldBuilder<JsonFieldBuilder, JsonFieldProperties>
{
public JsonFieldBuilder(UpsertSchemaFieldBase field, JsonFieldProperties properties, CreateSchema schema)
: base(field, properties, schema)
{
}
}
}

20
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/NumberFieldBuilder.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public sealed class NumberFieldBuilder : FieldBuilder<NumberFieldBuilder, NumberFieldProperties>
{
public NumberFieldBuilder(UpsertSchemaFieldBase field, NumberFieldProperties properties, CreateSchema schema)
: base(field, properties, schema)
{
}
}
}

20
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/ReferencesFieldBuilder.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public sealed class ReferencesFieldBuilder : FieldBuilder<ReferencesFieldBuilder, ReferencesFieldProperties>
{
public ReferencesFieldBuilder(UpsertSchemaFieldBase field, ReferencesFieldProperties properties, CreateSchema schema)
: base(field, properties, schema)
{
}
}
}

203
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs

@ -1,203 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Text;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public sealed class SchemaBuilder
{
private readonly CreateSchema command;
public SchemaBuilder(CreateSchema command)
{
this.command = command;
}
public static SchemaBuilder Create(string name)
{
var schemaName = name.ToKebabCase();
return new SchemaBuilder(new CreateSchema
{
Name = schemaName
}).Published().WithLabel(name);
}
public SchemaBuilder WithLabel(string? label)
{
if (command.Properties == null)
{
command.Properties = new SchemaProperties { Label = label };
}
else
{
command.Properties = command.Properties with { Label = label };
}
return this;
}
public SchemaBuilder WithScripts(SchemaScripts scripts)
{
command.Scripts = scripts;
return this;
}
public SchemaBuilder Published()
{
command.IsPublished = true;
return this;
}
public SchemaBuilder Singleton()
{
command.Type = SchemaType.Singleton;
return this;
}
public SchemaBuilder AddArray(string name, Action<ArrayFieldBuilder> configure)
{
var (field, properties) = AddField<ArrayFieldProperties>(name);
configure(new ArrayFieldBuilder(field, properties, command));
return this;
}
public SchemaBuilder AddAssets(string name, Action<AssetFieldBuilder> configure)
{
var (field, properties) = AddField<AssetsFieldProperties>(name);
configure(new AssetFieldBuilder(field, properties, command));
return this;
}
public SchemaBuilder AddBoolean(string name, Action<BooleanFieldBuilder> configure)
{
var (field, properties) = AddField<BooleanFieldProperties>(name);
configure(new BooleanFieldBuilder(field, properties, command));
return this;
}
public SchemaBuilder AddComponent(string name, Action<ComponentFieldBuilder> configure)
{
var (field, properties) = AddField<ComponentFieldProperties>(name);
configure(new ComponentFieldBuilder(field, properties, command));
return this;
}
public SchemaBuilder AddComponents(string name, Action<ComponentsFieldBuilder> configure)
{
var (field, properties) = AddField<ComponentsFieldProperties>(name);
configure(new ComponentsFieldBuilder(field, properties, command));
return this;
}
public SchemaBuilder AddDateTime(string name, Action<DateTimeFieldBuilder> configure)
{
var (field, properties) = AddField<DateTimeFieldProperties>(name);
configure(new DateTimeFieldBuilder(field, properties, command));
return this;
}
public SchemaBuilder AddJson(string name, Action<JsonFieldBuilder> configure)
{
var (field, properties) = AddField<JsonFieldProperties>(name);
configure(new JsonFieldBuilder(field, properties, command));
return this;
}
public SchemaBuilder AddNumber(string name, Action<NumberFieldBuilder> configure)
{
var (field, properties) = AddField<NumberFieldProperties>(name);
configure(new NumberFieldBuilder(field, properties, command));
return this;
}
public SchemaBuilder AddReferences(string name, Action<ReferencesFieldBuilder> configure)
{
var (field, properties) = AddField<ReferencesFieldProperties>(name);
configure(new ReferencesFieldBuilder(field, properties, command));
return this;
}
public SchemaBuilder AddString(string name, Action<StringFieldBuilder> configure)
{
var (field, properties) = AddField<StringFieldProperties>(name);
configure(new StringFieldBuilder(field, properties, command));
return this;
}
public SchemaBuilder AddTags(string name, Action<TagsFieldBuilder> configure)
{
var (field, properties) = AddField<TagsFieldProperties>(name);
configure(new TagsFieldBuilder(field, properties, command));
return this;
}
public SchemaBuilder AddUI(string name, Action<UIFieldBuilder> configure)
{
var (field, properties) = AddField<UIFieldProperties>(name);
configure(new UIFieldBuilder(field, properties, command));
return this;
}
private (UpsertSchemaField, T) AddField<T>(string name) where T : FieldProperties, new()
{
var properties = new T { Label = name };
var field = new UpsertSchemaField
{
Name = name.ToCamelCase(),
Properties = properties,
};
if (command.Fields == null)
{
command.Fields = new[] { field };
}
else
{
command.Fields = command.Fields.Union(Enumerable.Repeat(field, 1)).ToArray();
}
return (field, properties);
}
public CreateSchema Build()
{
return command;
}
}
}

20
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public sealed class StringFieldBuilder : FieldBuilder<StringFieldBuilder, StringFieldProperties>
{
public StringFieldBuilder(UpsertSchemaFieldBase field, StringFieldProperties properties, CreateSchema schema)
: base(field, properties, schema)
{
}
}
}

20
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/TagsFieldBuilder.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public sealed class TagsFieldBuilder : FieldBuilder<TagsFieldBuilder, TagsFieldProperties>
{
public TagsFieldBuilder(UpsertSchemaFieldBase field, TagsFieldProperties properties, CreateSchema schema)
: base(field, properties, schema)
{
}
}
}

127
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlog.cs

@ -1,127 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps.Templates.Builders;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Templates
{
public sealed class CreateBlog : ITemplate
{
public string Name { get; } = "blog";
public Task RunAsync(PublishTemplate publish)
{
return Task.WhenAll(
CreatePagesAsync(publish),
CreatePostsAsync(publish));
}
private static async Task CreatePostsAsync(PublishTemplate publish)
{
var postsId = await CreatePostsSchemaAsync(publish);
await publish(new CreateContent
{
SchemaId = postsId,
Data =
new ContentData()
.AddField("title",
new ContentFieldData()
.AddInvariant("My first post with Squidex"))
.AddField("text",
new ContentFieldData()
.AddInvariant("Just created a blog with Squidex. I love it!")),
Status = Status.Published
});
}
private static async Task CreatePagesAsync(PublishTemplate publish)
{
var pagesId = await CreatePagesSchemaAsync(publish);
await publish(new CreateContent
{
SchemaId = pagesId,
Data =
new ContentData()
.AddField("title",
new ContentFieldData()
.AddInvariant("About Me"))
.AddField("text",
new ContentFieldData()
.AddInvariant("I love Squidex and SciFi!")),
Status = Status.Published
});
}
private static async Task<NamedId<DomainId>> CreatePostsSchemaAsync(PublishTemplate publish)
{
var schema =
SchemaBuilder.Create("Posts")
.AddString("Title", f => f
.Properties(p => p with
{
MaxLength = 100
})
.Required()
.ShowInList()
.Hints("The title of the post."))
.AddString("Text", f => f
.Properties(p => p with
{
Editor = StringFieldEditor.RichText
})
.Required()
.Hints("The text of the post."))
.AddString("Slug", f => f
.Disabled()
.Label("Slug (Autogenerated)")
.Hints("Autogenerated slug that can be used to identity the post."))
.WithScripts(DefaultScripts.GenerateSlug)
.Build();
await publish(schema);
return NamedId.Of(schema.SchemaId, schema.Name);
}
private static async Task<NamedId<DomainId>> CreatePagesSchemaAsync(PublishTemplate publish)
{
var schema =
SchemaBuilder.Create("Pages")
.AddString("Title", f => f
.Properties(p => p with
{
MaxLength = 100
})
.Required()
.ShowInList()
.Hints("The title of the page."))
.AddString("Text", f => f
.Properties(p => p with
{
Editor = StringFieldEditor.RichText
})
.Required()
.Hints("The text of the page."))
.AddString("Slug", f => f
.Disabled()
.Label("Slug (Autogenerated)")
.Hints("Autogenerated slug that can be used to identity the page."))
.WithScripts(DefaultScripts.GenerateSlug)
.Build();
await publish(schema);
return NamedId.Of(schema.SchemaId, schema.Name);
}
}
}

269
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateProfile.cs

@ -1,269 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps.Templates.Builders;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Entities.Apps.Templates
{
public sealed class CreateProfile : ITemplate
{
public string Name { get; } = "profile";
public Task RunAsync(PublishTemplate publish)
{
return Task.WhenAll(
CreateBasicsAsync(publish),
CreateEducationSchemaAsync(publish),
CreateExperienceSchemaAsync(publish),
CreateProjectsSchemaAsync(publish),
CreatePublicationsSchemaAsync(publish),
CreateSkillsSchemaAsync(publish));
}
private static async Task CreateBasicsAsync(PublishTemplate publish)
{
var postsId = await CreateBasicsSchemaAsync(publish);
await publish(new UpdateContent
{
ContentId = postsId.Id,
Data =
new ContentData()
.AddField("firstName",
new ContentFieldData()
.AddInvariant("John"))
.AddField("lastName",
new ContentFieldData()
.AddInvariant("Doe"))
.AddField("profession",
new ContentFieldData()
.AddInvariant("Software Developer")),
SchemaId = postsId
});
}
private static async Task<NamedId<DomainId>> CreateBasicsSchemaAsync(PublishTemplate publish)
{
var command =
SchemaBuilder.Create("Basics")
.Singleton()
.AddString("First Name", f => f
.Required()
.ShowInList()
.Hints("Your first name."))
.AddString("Last Name", f => f
.Required()
.ShowInList()
.Hints("Your last name."))
.AddAssets("Image", f => f
.Properties(p => p with
{
ExpectedType = AssetType.Image,
MaxItems = 1,
MinItems = 1
})
.Hints("Your profile image."))
.AddString("Profession", f => f
.Properties(p => p with
{
Editor = StringFieldEditor.TextArea
})
.Required()
.Hints("Describe your profession."))
.AddString("Summary", f => f
.Properties(p => p with
{
Editor = StringFieldEditor.TextArea
})
.Hints("Write a short summary about yourself."))
.AddString("Legal Terms", f => f
.Properties(p => p with
{
Editor = StringFieldEditor.TextArea
})
.Hints("The terms to fulfill legal requirements."))
.AddString("Github Link", f => f
.Hints("An optional link to your Github account."))
.AddString("Blog Link", f => f
.Hints("An optional link to your Blog."))
.AddString("Twitter Link", f => f
.Hints("An optional link to your Twitter account."))
.AddString("LinkedIn Link", f => f
.Hints("An optional link to your LinkedIn account."))
.AddString("Email Address", f => f
.Hints("An optional email address to contact you."))
.Build();
await publish(command);
return NamedId.Of(command.SchemaId, command.Name);
}
private static async Task<NamedId<DomainId>> CreateProjectsSchemaAsync(PublishTemplate publish)
{
var schema =
SchemaBuilder.Create("Projects")
.AddString("Name", f => f
.Required()
.ShowInList()
.Hints("The name of your project."))
.AddString("Description", f => f
.Properties(p => p with
{
Editor = StringFieldEditor.TextArea
})
.Required()
.Hints("Describe your project."))
.AddAssets("Image", f => f
.Properties(p => p with
{
ExpectedType = AssetType.Image,
MaxItems = 1,
MinItems = 1
})
.Required()
.Hints("An image or screenshot for your project."))
.AddString("Label", f => f
.Properties(p => p with { Editor = StringFieldEditor.TextArea })
.Hints("An optional label to categorize your project, e.g. 'Open Source'."))
.AddString("Link", f => f
.Hints("An optional link to your project."))
.AddNumber("Year", f => f
.Hints("The year, when you realized the project, used for sorting only."))
.Build();
await publish(schema);
return NamedId.Of(schema.SchemaId, schema.Name);
}
private static async Task<NamedId<DomainId>> CreateExperienceSchemaAsync(PublishTemplate publish)
{
var schema =
SchemaBuilder.Create("Experience")
.AddString("Position", f => f
.Required()
.ShowInList()
.Hints("Your position in this job."))
.AddString("Company", f => f
.Required()
.ShowInList()
.Hints("The company or organization you worked for."))
.AddAssets("Logo", f => f
.Properties(p => p with
{
ExpectedType = AssetType.Image,
MaxItems = 1,
MinItems = 1
})
.Hints("The logo of the company or organization you worked for."))
.AddDateTime("From", f => f
.Required()
.Hints("The start date."))
.AddDateTime("To", f => f
.Hints("The end date, keep empty if you still work there."))
.Build();
await publish(schema);
return NamedId.Of(schema.SchemaId, schema.Name);
}
private static async Task<NamedId<DomainId>> CreateEducationSchemaAsync(PublishTemplate publish)
{
var schema =
SchemaBuilder.Create("Education")
.AddString("Degree", f => f
.Required()
.ShowInList()
.Hints("The degree you got or achieved."))
.AddString("School", f => f
.Required()
.ShowInList()
.Hints("The school or university."))
.AddAssets("Logo", f => f
.Properties(p => p with
{
ExpectedType = AssetType.Image,
MaxItems = 1,
MinItems = 1
})
.Hints("The logo of the school or university."))
.AddDateTime("From", f => f
.Required()
.Hints("The start date."))
.AddDateTime("To", f => f
.Hints("The end date, keep empty if you still study there."))
.Build();
await publish(schema);
return NamedId.Of(schema.SchemaId, schema.Name);
}
private static async Task<NamedId<DomainId>> CreatePublicationsSchemaAsync(PublishTemplate publish)
{
var command =
SchemaBuilder.Create("Publications")
.AddString("Name", f => f
.Required()
.ShowInList()
.Hints("The name or title of your publication."))
.AddAssets("Cover", f => f
.Properties(p => p with
{
ExpectedType = AssetType.Image,
MaxItems = 1,
MinItems = 1
})
.Hints("The cover of your publication."))
.AddString("Description", f => f
.Hints("Describe the content of your publication."))
.AddString("Link", f => f
.Hints("Optional link to your publication."))
.Build();
await publish(command);
return NamedId.Of(command.SchemaId, command.Name);
}
private static async Task<NamedId<DomainId>> CreateSkillsSchemaAsync(PublishTemplate publish)
{
var command =
SchemaBuilder.Create("Skills")
.AddString("Name", f => f
.Required()
.ShowInList()
.Hints("The name of the skill."))
.AddString("Experience", f => f
.Properties(p => p with
{
AllowedValues = ReadonlyList.Create(
"Beginner",
"Advanced",
"Professional",
"Expert"),
Editor = StringFieldEditor.Dropdown,
})
.Required()
.ShowInList()
.Hints("The level of experience."))
.Build();
await publish(command);
return NamedId.Of(command.SchemaId, command.Name);
}
}
}

29
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs

@ -1,29 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Apps.Templates
{
public static class DefaultScripts
{
private const string ScriptToGenerateSlug = @"
var data = ctx.data;
if (data.title && data.title.iv) {
data.slug = { iv: slugify(data.title.iv) };
replace(data);
}";
public static readonly SchemaScripts GenerateSlug = new SchemaScripts
{
Create = ScriptToGenerateSlug,
Update = ScriptToGenerateSlug
};
}
}

121
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/StringLogger.cs

@ -0,0 +1,121 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Globalization;
using Squidex.CLI.Commands.Implementation;
using Squidex.Infrastructure;
using Squidex.Log;
namespace Squidex.Domain.Apps.Entities.Apps.Templates
{
public sealed class StringLogger : ILogger, ILogLine
{
private const int MaxActionLength = 40;
private readonly ISemanticLog log;
private readonly string template;
private readonly List<string> lines = new List<string>();
private readonly List<string> errors = new List<string>();
private string startedLine = string.Empty;
public StringLogger(string template, ISemanticLog log)
{
this.template = template;
this.log = log;
}
public void Dispose()
{
var mesage = string.Join('\n', lines);
log.LogInformation(w => w
.WriteProperty("message", $"CLI executed or template {template}.")
.WriteProperty("template", template)
.WriteArray("steps", a =>
{
foreach (var line in lines)
{
a.WriteValue(line);
}
}));
if (errors.Count > 0)
{
throw new DomainException($"Template failed with {errors[0]}");
}
}
public void StepStart(string message)
{
if (message.Length > MaxActionLength - 3)
{
var length = MaxActionLength - 3;
message = message[..length];
}
startedLine = $"{message.PadRight(MaxActionLength, '.')}...";
}
public void StepSuccess(string? details = null)
{
if (!string.IsNullOrWhiteSpace(details))
{
AddToLine($"succeeded ({details}).");
}
else
{
AddToLine("succeeded");
}
}
public void StepFailed(string reason)
{
AddToErrors(reason);
AddToLine($"failed: {reason.TrimEnd('.')}.");
}
public void StepSkipped(string reason)
{
AddToLine($"skipped: {reason.TrimEnd('.')}.");
}
public void WriteLine()
{
lines.Add(string.Empty);
}
public void WriteLine(string message)
{
lines.Add(message);
}
public void WriteLine(string message, params object?[] args)
{
lines.Add(string.Format(CultureInfo.InvariantCulture, message, args));
}
private void AddToErrors(string reason)
{
errors.Add(reason);
}
private void AddToLine(string message)
{
startedLine += message;
lines.Add(startedLine);
startedLine = string.Empty;
}
public ILogLine WriteSameLine()
{
return this;
}
}
}

15
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Template.cs

@ -0,0 +1,15 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Entities.Apps.Templates
{
public sealed record Template(string Name, string Title, string Description, bool IsStarter)
{
}
}

113
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs

@ -5,45 +5,124 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Squidex.CLI.Commands.Implementation;
using Squidex.CLI.Commands.Implementation.FileSystem;
using Squidex.CLI.Commands.Implementation.Sync;
using Squidex.CLI.Commands.Implementation.Sync.App;
using Squidex.CLI.Commands.Implementation.Sync.AssertFolders;
using Squidex.CLI.Commands.Implementation.Sync.Assets;
using Squidex.CLI.Commands.Implementation.Sync.Rules;
using Squidex.CLI.Commands.Implementation.Sync.Schemas;
using Squidex.CLI.Commands.Implementation.Sync.Workflows;
using Squidex.CLI.Configuration;
using Squidex.ClientLibrary;
using Squidex.ClientLibrary.Configuration;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Log;
#pragma warning disable MA0048 // File name must match type name
namespace Squidex.Domain.Apps.Entities.Apps.Templates namespace Squidex.Domain.Apps.Entities.Apps.Templates
{ {
public delegate Task PublishTemplate(IAppCommand command);
public sealed class TemplateCommandMiddleware : ICommandMiddleware public sealed class TemplateCommandMiddleware : ICommandMiddleware
{ {
private readonly Dictionary<string, ITemplate> templates; private readonly TemplatesClient templatesClient;
private readonly TemplatesOptions templateOptions;
private readonly IUrlGenerator urlGenerator;
private readonly ISemanticLog log;
public TemplateCommandMiddleware(IEnumerable<ITemplate> templates) public TemplateCommandMiddleware(TemplatesClient templatesClient, IOptions<TemplatesOptions> templateOptions, IUrlGenerator urlGenerator,
ISemanticLog log)
{ {
this.templates = templates.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); this.templatesClient = templatesClient;
this.templateOptions = templateOptions.Value;
this.urlGenerator = urlGenerator;
this.log = log;
} }
public async Task HandleAsync(CommandContext context, NextDelegate next) public async Task HandleAsync(CommandContext context, NextDelegate next)
{ {
if (context.IsCompleted && context.Command is CreateApp createApp && !string.IsNullOrWhiteSpace(createApp.Template)) await next(context);
if (context.IsCompleted && context.Command is CreateApp createApp)
{
await ApplyTemplateAsync(context.Result<IAppEntity>(), createApp.Template);
}
}
private async Task ApplyTemplateAsync(IAppEntity app, string? template)
{
if (string.IsNullOrWhiteSpace(template))
{
return;
}
var repository = await templatesClient.GetRepositoryUrl(template);
if (string.IsNullOrEmpty(repository))
{
log.LogWarning(w => w
.WriteProperty("message", "Template not found.")
.WriteProperty("template", template));
return;
}
using (var cliLog = new StringLogger(template, log))
{ {
if (templates.TryGetValue(createApp.Template, out var template)) var session = CreateSession(app);
var syncService = await CreateSyncServiceAsync(repository, session);
var syncOptions = new SyncOptions();
var targets = new ISynchronizer[]
{ {
var appId = NamedId.Of(createApp.AppId, createApp.Name); new AppSynchronizer(cliLog),
new AssetFoldersSynchronizer(cliLog),
new AssetsSynchronizer(cliLog),
new RulesSynchronizer(cliLog),
new SchemasSynchronizer(cliLog),
new WorkflowsSynchronizer(cliLog),
};
var publish = new PublishTemplate(command => foreach (var target in targets.OrderBy(x => x.Name))
{ {
command.AppId = appId; await target.ImportAsync(syncService, syncOptions, session);
}
}
}
return context.CommandBus.PublishAsync(command); private static async Task<ISyncService> CreateSyncServiceAsync(string repository, ISession session)
}); {
var fs = await FileSystems.CreateAsync(repository, session.WorkingDirectory);
await template.RunAsync(publish); return new SyncService(fs, session);
} }
private ISession CreateSession(IAppEntity app)
{
var client = app.Clients.First();
var url = templateOptions.LocalUrl;
if (string.IsNullOrEmpty(url))
{
url = urlGenerator.Root();
} }
await next(context); return new Session(
app.Name,
new DirectoryInfo(Path.GetTempPath()),
new SquidexClientManager(new SquidexOptions
{
Configurator = AcceptAllCertificatesConfigurator.Instance,
AppName = app.Name,
ClientId = $"{app.Name}:{client.Key}",
ClientSecret = client.Value.Secret,
Url = url
}));
} }
} }
} }

6
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/ITemplate.cs → backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateRepository.cs

@ -7,10 +7,10 @@
namespace Squidex.Domain.Apps.Entities.Apps.Templates namespace Squidex.Domain.Apps.Entities.Apps.Templates
{ {
public interface ITemplate public sealed class TemplateRepository
{ {
string Name { get; } public string ContentUrl { get; set; }
Task RunAsync(PublishTemplate publish); public string GitUrl { get; set; }
} }
} }

107
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplatesClient.cs

@ -0,0 +1,107 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Text.RegularExpressions;
using Microsoft.Extensions.Options;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Templates
{
public sealed class TemplatesClient
{
private static readonly Regex Regex = new Regex("\\* \\[(?<Title>.*)\\]\\((?<Name>.*)\\/README\\.md\\): (?<Description>.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
private readonly IHttpClientFactory httpClientFactory;
private readonly TemplatesOptions options;
public TemplatesClient(IHttpClientFactory httpClientFactory, IOptions<TemplatesOptions> options)
{
this.httpClientFactory = httpClientFactory;
this.options = options.Value;
}
public async Task<string?> GetRepositoryUrl(string name,
CancellationToken ct = default)
{
using (var httpClient = httpClientFactory.CreateClient())
{
var result = new List<Template>();
foreach (var repository in options.Repositories.OrEmpty())
{
var url = $"{repository.ContentUrl}/README.md";
var text = await httpClient.GetStringAsync(url, ct);
foreach (Match match in Regex.Matches(text))
{
var currentName = match.Groups["Name"].Value;
if (currentName == name)
{
return $"{repository.GitUrl ?? repository.ContentUrl}?folder={name}";
}
}
}
return null;
}
}
public async Task<List<Template>> GetTemplatesAsync(
CancellationToken ct = default)
{
using (var httpClient = httpClientFactory.CreateClient())
{
var result = new List<Template>();
foreach (var repository in options.Repositories.OrEmpty())
{
var url = $"{repository.ContentUrl}/README.md";
var text = await httpClient.GetStringAsync(url, ct);
foreach (Match match in Regex.Matches(text))
{
var title = match.Groups["Title"].Value;
result.Add(new Template(
match.Groups["Name"].Value,
title,
match.Groups["Description"].Value,
title.StartsWith("Starter ", StringComparison.OrdinalIgnoreCase)));
}
}
return result;
}
}
public async Task<string?> GetDetailAsync(string name,
CancellationToken ct = default)
{
Guard.NotNullOrEmpty(name);
using (var httpClient = httpClientFactory.CreateClient())
{
foreach (var repository in options.Repositories.OrEmpty())
{
var url = $"{repository.ContentUrl}/{name}/README.md";
var response = await httpClient.GetAsync(url, ct);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync(ct);
}
}
}
return null;
}
}
}

16
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplatesOptions.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Apps.Templates
{
public sealed class TemplatesOptions
{
public string? LocalUrl { get; set; }
public TemplateRepository[] Repositories { get; set; }
}
}

9
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs

@ -56,6 +56,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
SimpleMapper.Map(asset, result); SimpleMapper.Map(asset, result);
result.Actor = asset.LastModifiedBy; result.Actor = asset.LastModifiedBy;
result.PixelHeight = asset.Metadata?.GetPixelHeight();
result.PixelWidth = asset.Metadata?.GetPixelWidth();
result.Name = "AssetQueried"; result.Name = "AssetQueried";
yield return result; yield return result;
@ -78,6 +80,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
SimpleMapper.Map(asset, result); SimpleMapper.Map(asset, result);
result.PixelHeight = asset.Metadata?.GetPixelHeight();
result.PixelWidth = asset.Metadata?.GetPixelWidth();
result.AssetType = asset.Type; result.AssetType = asset.Type;
} }
@ -109,9 +113,10 @@ namespace Squidex.Domain.Apps.Entities.Assets
return true; return true;
} }
var vars = new ScriptVars // Script vars are just wrappers over dictionaries for better performance.
var vars = new EventScriptVars
{ {
["event"] = @event Event = @event
}; };
return scriptEngine.Evaluate(vars, trigger.Condition); return scriptEngine.Evaluate(vars, trigger.Condition);

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

@ -42,6 +42,11 @@ namespace Squidex.Domain.Apps.Entities.Assets
public void Describe(AddDescription describe, ScriptScope scope) public void Describe(AddDescription describe, ScriptScope scope)
{ {
if (!scope.HasFlag(ScriptScope.Async))
{
return;
}
describe(JsonType.Function, "getAssets(ids, callback)", describe(JsonType.Function, "getAssets(ids, callback)",
Resources.ScriptingGetAssets); Resources.ScriptingGetAssets);

198
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ScriptingExtensions.cs

@ -5,12 +5,9 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.ObjectModel;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Text; using Squidex.Text;
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards
@ -24,29 +21,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards
CanReject = true CanReject = true
}; };
private static class ScriptKeys
{
public const string AppId = "appId";
public const string AppName = "appName";
public const string Asset = "asset";
public const string AssetId = "assetId";
public const string Command = "command";
public const string FileHash = "fileHash";
public const string FileName = "fileName";
public const string FileSize = "fileSize";
public const string FileSlug = "fileSlug";
public const string FileVersion = "fileVersion";
public const string IsProtected = "isProtected";
public const string Metadata = "metadata";
public const string MimeType = "mimeType";
public const string Operation = "operation";
public const string ParentId = "parentId";
public const string ParentPath = "parentPath";
public const string Permanent = "permanent";
public const string Tags = "tags";
public const string User = "User";
}
public static async Task ExecuteCreateScriptAsync(this AssetOperation operation, CreateAsset create) public static async Task ExecuteCreateScriptAsync(this AssetOperation operation, CreateAsset create)
{ {
var script = operation.App.AssetScripts?.Create; var script = operation.App.AssetScripts?.Create;
@ -58,23 +32,23 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards
var parentPath = await GetPathAsync(operation, create.ParentId); var parentPath = await GetPathAsync(operation, create.ParentId);
// Tags and metadata are mutable and can be changed from the scripts, but not replaced. // Script vars are just wrappers over dictionaries for better performance.
var vars = new ScriptVars var vars = new AssetScriptVars
{ {
// Use a dictionary for better performance, because no reflection is involved. // Tags and metadata are mutable and can be changed from the scripts, but not replaced.
[ScriptKeys.Command] = new Dictionary<string, object?> Command = new AssetCommandScriptVars
{ {
[ScriptKeys.Metadata] = create.Metadata.Mutable(), FileHash = create.FileHash,
[ScriptKeys.FileHash] = create.FileHash, FileName = create.File.FileName,
[ScriptKeys.FileName] = create.File.FileName, FileSlug = create.File.FileName.Slugify(),
[ScriptKeys.FileSize] = create.File.FileSize, FileSize = create.File.FileSize,
[ScriptKeys.FileSlug] = create.File.FileName.Slugify(), Metadata = create.Metadata,
[ScriptKeys.MimeType] = create.File.MimeType, MimeType = create.File.MimeType,
[ScriptKeys.ParentId] = create.ParentId, ParentId = create.ParentId,
[ScriptKeys.ParentPath] = parentPath, ParentPath = parentPath,
[ScriptKeys.Tags] = create.Tags Tags = create.Tags
}, },
[ScriptKeys.Operation] = "Create" Operation = "Create"
}; };
await ExecuteScriptAsync(operation, script, vars); await ExecuteScriptAsync(operation, script, vars);
@ -89,20 +63,20 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards
return Task.CompletedTask; return Task.CompletedTask;
} }
// Tags and metadata are mutable and can be changed from the scripts, but not replaced. // Script vars are just wrappers over dictionaries for better performance.
var vars = new ScriptVars var vars = new AssetScriptVars
{
// Use a dictionary for better performance, because no reflection is involved.
[ScriptKeys.Command] = new Dictionary<string, object?>
{ {
[ScriptKeys.Metadata] = update.Metadata.Mutable(), // Tags and metadata are mutable and can be changed from the scripts, but not replaced.
[ScriptKeys.FileHash] = update.FileHash, Command = new AssetCommandScriptVars
[ScriptKeys.FileName] = update.File.FileName, {
[ScriptKeys.FileSize] = update.File.FileSize, Metadata = update.Metadata,
[ScriptKeys.MimeType] = update.File.MimeType, FileHash = update.FileHash,
[ScriptKeys.Tags] = update.Tags FileName = update.File.FileName,
FileSize = update.File.FileSize,
MimeType = update.File.MimeType,
Tags = update.Tags
}, },
[ScriptKeys.Operation] = "Update" Operation = "Update"
}; };
return ExecuteScriptAsync(operation, script, vars); return ExecuteScriptAsync(operation, script, vars);
@ -117,18 +91,19 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards
return Task.CompletedTask; return Task.CompletedTask;
} }
// Tags are mutable and can be changed from the scripts, but not replaced. // Script vars are just wrappers over dictionaries for better performance.
var vars = new ScriptVars var vars = new AssetScriptVars
{ {
// Use a dictionary for better performance, because no reflection is involved. // Tags are mutable and can be changed from the scripts, but not replaced.
[ScriptKeys.Command] = new Dictionary<string, object?> Command = new AssetCommandScriptVars
{ {
[ScriptKeys.Metadata] = annotate.Metadata?.Mutable(), IsProtected = annotate.IsProtected,
[ScriptKeys.FileName] = annotate.FileName, Metadata = annotate.Metadata,
[ScriptKeys.FileSlug] = annotate.Slug, FileName = annotate.FileName,
[ScriptKeys.Tags] = annotate.Tags FileSlug = annotate.Slug,
Tags = annotate.Tags
}, },
[ScriptKeys.Operation] = "Annotate" Operation = "Annotate"
}; };
return ExecuteScriptAsync(operation, script, vars); return ExecuteScriptAsync(operation, script, vars);
@ -145,15 +120,15 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards
var parentPath = await GetPathAsync(operation, move.ParentId); var parentPath = await GetPathAsync(operation, move.ParentId);
var vars = new ScriptVars // Script vars are just wrappers over dictionaries for better performance.
var vars = new AssetScriptVars
{ {
// Use a dictionary for better performance, because no reflection is involved. Command = new AssetCommandScriptVars
[ScriptKeys.Command] = new Dictionary<string, object?>
{ {
[ScriptKeys.ParentId] = move.ParentId, ParentId = move.ParentId,
[ScriptKeys.ParentPath] = parentPath ParentPath = parentPath
}, },
[ScriptKeys.Operation] = "Move" Operation = "Move"
}; };
await ExecuteScriptAsync(operation, script, vars); await ExecuteScriptAsync(operation, script, vars);
@ -168,53 +143,53 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards
return Task.CompletedTask; return Task.CompletedTask;
} }
var vars = new ScriptVars // Script vars are just wrappers over dictionaries for better performance.
var vars = new AssetScriptVars
{ {
// Use a dictionary for better performance, because no reflection is involved. Command = new AssetCommandScriptVars
[ScriptKeys.Command] = new Dictionary<string, object?>
{ {
[ScriptKeys.Permanent] = delete.Permanent Permanent = delete.Permanent
}, },
[ScriptKeys.Operation] = "Delete" Operation = "Delete"
}; };
return ExecuteScriptAsync(operation, script, vars); return ExecuteScriptAsync(operation, script, vars);
} }
private static async Task ExecuteScriptAsync(AssetOperation operation, string script, ScriptVars vars) private static async Task ExecuteScriptAsync(AssetOperation operation, string script, AssetScriptVars vars)
{ {
var snapshot = operation.Snapshot; var snapshot = operation.Snapshot;
var parentPath = await GetPathAsync(operation, snapshot.ParentId); var parentPath = await GetPathAsync(operation, snapshot.ParentId);
// Use a dictionary for better performance, because no reflection is involved. // Script vars are just wrappers over dictionaries for better performance.
var asset = new Dictionary<string, object?> var asset = new AssetEntityScriptVars
{ {
[ScriptKeys.Metadata] = snapshot.ReadonlyMetadata(), Metadata = snapshot.Metadata,
[ScriptKeys.FileHash] = snapshot.FileHash, FileHash = snapshot.FileHash,
[ScriptKeys.FileName] = snapshot.FileName, FileName = snapshot.FileName,
[ScriptKeys.FileSize] = snapshot.FileSize, FileSize = snapshot.FileSize,
[ScriptKeys.FileSlug] = snapshot.Slug, FileSlug = snapshot.Slug,
[ScriptKeys.FileVersion] = snapshot.FileVersion, FileVersion = snapshot.FileVersion,
[ScriptKeys.IsProtected] = snapshot.IsProtected, IsProtected = snapshot.IsProtected,
[ScriptKeys.MimeType] = snapshot.MimeType, MimeType = snapshot.MimeType,
[ScriptKeys.ParentId] = snapshot.ParentId, ParentId = snapshot.ParentId,
[ScriptKeys.ParentPath] = parentPath, ParentPath = parentPath,
[ScriptKeys.Tags] = snapshot.ReadonlyTags() Tags = snapshot.Tags
}; };
vars[ScriptKeys.AppId] = operation.App.Id; vars.AppId = operation.App.Id;
vars[ScriptKeys.AppName] = operation.App.Name; vars.AppName = operation.App.Name;
vars[ScriptKeys.AssetId] = operation.CommandId; vars.AssetId = operation.CommandId;
vars[ScriptKeys.Asset] = asset; vars.Asset = asset;
vars[ScriptKeys.User] = operation.User; vars.User = operation.User;
var scriptEngine = operation.Resolve<IScriptEngine>(); var scriptEngine = operation.Resolve<IScriptEngine>();
await scriptEngine.ExecuteAsync(vars, script, Options); await scriptEngine.ExecuteAsync(vars, script, Options);
} }
private static async Task<object> GetPathAsync(AssetOperation operation, DomainId parentId) private static async Task<Array> GetPathAsync(AssetOperation operation, DomainId parentId)
{ {
if (parentId == default) if (parentId == default)
{ {
@ -222,40 +197,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards
} }
var assetQuery = operation.Resolve<IAssetQueryService>(); var assetQuery = operation.Resolve<IAssetQueryService>();
var assetPath = await assetQuery.FindAssetFolderAsync(operation.App.Id, parentId);
var path = await assetQuery.FindAssetFolderAsync(operation.App.Id, parentId); return assetPath.Select(x => new { id = x.Id, folderName = x.FolderName }).ToArray();
return path.Select(x => new { id = x.Id, folderName = x.FolderName }).ToList();
}
private static object? Mutable(this AssetMetadata metadata)
{
if (metadata == null)
{
return null;
}
return new ScriptMetadataWrapper(metadata);
}
private static object? ReadonlyMetadata(this IAssetEntity asset)
{
if (asset.Metadata == null)
{
return null;
}
return new ReadOnlyDictionary<string, IJsonValue>(asset.Metadata);
}
private static object? ReadonlyTags(this IAssetEntity asset)
{
if (asset.Tags == null)
{
return null;
}
return new ReadOnlyCollection<string>(asset.Tags.ToList());
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Assets/FileTagAssetMetadataSource.cs

@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var pw = file.Properties.PhotoWidth; var pw = file.Properties.PhotoWidth;
var ph = file.Properties.PhotoHeight; var ph = file.Properties.PhotoHeight;
if (pw > 0 && pw > 0) if (pw > 0 && ph > 0)
{ {
command.Metadata.SetPixelWidth(pw); command.Metadata.SetPixelWidth(pw);
command.Metadata.SetPixelHeight(ph); command.Metadata.SetPixelHeight(ph);

2
backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupHandler.cs

@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task CompleteRestoreAsync(RestoreContext context) public Task CompleteRestoreAsync(RestoreContext context, string appName)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }

2
backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs

@ -168,7 +168,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{ {
using (Telemetry.Activities.StartActivity($"{handler.GetType().Name}/CompleteRestoreAsync")) using (Telemetry.Activities.StartActivity($"{handler.GetType().Name}/CompleteRestoreAsync"))
{ {
await handler.CompleteRestoreAsync(runningContext); await handler.CompleteRestoreAsync(runningContext, CurrentJob.NewAppName!);
} }
Log($"Completed {handler.Name}"); Log($"Completed {handler.Name}");

5
backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs

@ -74,9 +74,10 @@ namespace Squidex.Domain.Apps.Entities.Comments
return true; return true;
} }
var vars = new ScriptVars // Script vars are just wrappers over dictionaries for better performance.
var vars = new EventScriptVars
{ {
["event"] = @event Event = @event
}; };
return scriptEngine.Evaluate(vars, trigger.Condition); return scriptEngine.Evaluate(vars, trigger.Condition);

5
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs

@ -215,9 +215,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
return true; return true;
} }
var vars = new ScriptVars // Script vars are just wrappers over dictionaries for better performance.
var vars = new EventScriptVars
{ {
["event"] = @event Event = @event
}; };
return scriptEngine.Evaluate(vars, schema.Condition); return scriptEngine.Evaluate(vars, schema.Condition);

8
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs

@ -250,7 +250,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
private async Task ChangeCore(ChangeContentStatus c, ContentOperation operation) private async Task ChangeCore(ChangeContentStatus c, ContentOperation operation)
{ {
operation.MustHavePermission(Permissions.AppContentsChangeStatusOwn); operation.MustHavePermission(Permissions.AppContentsChangeStatus);
operation.MustNotChangeSingleton(c.Status); operation.MustNotChangeSingleton(c.Status);
if (c.Status == Snapshot.EditingStatus()) if (c.Status == Snapshot.EditingStatus())
@ -383,7 +383,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
private async Task ValidateCore(ContentOperation operation) private async Task ValidateCore(ContentOperation operation)
{ {
operation.MustHavePermission(Permissions.AppContentsReadOwn); operation.MustHavePermission(Permissions.AppContentsRead);
await operation.ValidateContentAndInputAsync(Snapshot.Data, false, Snapshot.IsPublished()); await operation.ValidateContentAndInputAsync(Snapshot.Data, false, Snapshot.IsPublished());
} }
@ -408,12 +408,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
private async Task DeleteCore(DeleteContent c, ContentOperation operation) private async Task DeleteCore(DeleteContent c, ContentOperation operation)
{ {
operation.MustHavePermission(Permissions.AppContentsDeleteOwn); operation.MustHavePermission(Permissions.AppContentsDelete);
operation.MustNotDeleteSingleton(); operation.MustNotDeleteSingleton();
if (!c.DoNotScript) if (!c.DoNotScript)
{ {
await operation.ExecuteDeleteScriptAsync(); await operation.ExecuteDeleteScriptAsync(c.Permanent);
} }
if (c.CheckReferrers) if (c.CheckReferrers)

115
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ScriptingExtensions.cs

@ -19,25 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards
CanReject = true CanReject = true
}; };
private static class ScriptKeys
{
public const string AppId = "appId";
public const string AppName = "appName";
public const string Command = "command";
public const string Content = "content";
public const string ContentId = "contentId";
public const string Data = "data";
public const string DataOld = "dataOld";
public const string OldData = "oldData";
public const string OldStatus = "oldStatus";
public const string Operation = "operation";
public const string SchemaId = "achemaId";
public const string SchemaName = "achemaName";
public const string Status = "status";
public const string StatusOld = "statusOld";
public const string User = "user";
}
public static Task<ContentData> ExecuteCreateScriptAsync(this ContentOperation operation, ContentData data, Status status) public static Task<ContentData> ExecuteCreateScriptAsync(this ContentOperation operation, ContentData data, Status status)
{ {
var script = operation.SchemaDef.Scripts.Create; var script = operation.SchemaDef.Scripts.Create;
@ -47,15 +28,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards
return Task.FromResult(data); return Task.FromResult(data);
} }
var vars = Enrich(operation, new ScriptVars // Script vars are just wrappers over dictionaries for better performance.
var vars = Enrich(operation, new ContentScriptVars
{ {
[ScriptKeys.Data] = data, Data = data,
[ScriptKeys.DataOld] = null, DataOld = null,
[ScriptKeys.OldData] = null, OldData = null,
[ScriptKeys.OldStatus] = default(Status), OldStatus = default,
[ScriptKeys.Operation] = "Create", Operation = "Create",
[ScriptKeys.Status] = status, Status = status,
[ScriptKeys.StatusOld] = default(Status) StatusOld = default
}); });
return TransformAsync(operation, script, vars); return TransformAsync(operation, script, vars);
@ -70,15 +52,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards
return Task.FromResult(data); return Task.FromResult(data);
} }
var vars = Enrich(operation, new ScriptVars // Script vars are just wrappers over dictionaries for better performance.
var vars = Enrich(operation, new ContentScriptVars
{ {
[ScriptKeys.Data] = data, Data = data,
[ScriptKeys.DataOld] = operation.Snapshot.Data, DataOld = operation.Snapshot.Data,
[ScriptKeys.OldData] = operation.Snapshot.Data, OldData = operation.Snapshot.Data,
[ScriptKeys.OldStatus] = data, OldStatus = operation.Snapshot.Status,
[ScriptKeys.Operation] = "Update", Operation = "Update",
[ScriptKeys.Status] = operation.Snapshot.EditingStatus(), Status = operation.Snapshot.EditingStatus(),
[ScriptKeys.StatusOld] = default(Status) StatusOld = default
}); });
return TransformAsync(operation, script, vars); return TransformAsync(operation, script, vars);
@ -93,24 +76,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards
return Task.FromResult(operation.Snapshot.Data); return Task.FromResult(operation.Snapshot.Data);
} }
// Clone the data so we do not change it by accident. // Script vars are just wrappers over dictionaries for better performance.
var data = operation.Snapshot.Data.Clone(); var vars = Enrich(operation, new ContentScriptVars
var vars = Enrich(operation, new ScriptVars
{ {
[ScriptKeys.Data] = data, Data = operation.Snapshot.Data.Clone(),
[ScriptKeys.DataOld] = null, DataOld = null,
[ScriptKeys.OldData] = null, OldData = null,
[ScriptKeys.OldStatus] = operation.Snapshot.EditingStatus(), OldStatus = operation.Snapshot.EditingStatus(),
[ScriptKeys.Operation] = change.ToString(), Operation = change.ToString(),
[ScriptKeys.Status] = status, Status = status,
[ScriptKeys.StatusOld] = operation.Snapshot.EditingStatus() StatusOld = operation.Snapshot.EditingStatus()
}); });
return TransformAsync(operation, script, vars); return TransformAsync(operation, script, vars);
} }
public static Task ExecuteDeleteScriptAsync(this ContentOperation operation) public static Task ExecuteDeleteScriptAsync(this ContentOperation operation, bool permanent)
{ {
var script = operation.SchemaDef.Scripts.Delete; var script = operation.SchemaDef.Scripts.Delete;
@ -119,40 +100,40 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards
return Task.CompletedTask; return Task.CompletedTask;
} }
var vars = Enrich(operation, new ScriptVars // Script vars are just wrappers over dictionaries for better performance.
{ var vars = Enrich(operation, new ContentScriptVars
[ScriptKeys.Data] = operation.Snapshot.Data, {
[ScriptKeys.DataOld] = null, Data = operation.Snapshot.Data,
[ScriptKeys.OldData] = null, DataOld = null,
[ScriptKeys.OldStatus] = operation.Snapshot.EditingStatus(), OldData = null,
[ScriptKeys.Operation] = "Delete", OldStatus = operation.Snapshot.EditingStatus(),
[ScriptKeys.Status] = operation.Snapshot.EditingStatus(), Operation = "Delete",
[ScriptKeys.StatusOld] = default(Status) Permanent = permanent,
Status = operation.Snapshot.EditingStatus(),
StatusOld = default
}); });
return ExecuteAsync(operation, script, vars); return ExecuteAsync(operation, script, vars);
} }
private static async Task<ContentData> TransformAsync(ContentOperation operation, string script, ScriptVars vars) private static async Task<ContentData> TransformAsync(ContentOperation operation, string script, ContentScriptVars vars)
{ {
return await operation.Resolve<IScriptEngine>().TransformAsync(vars, script, Options); return await operation.Resolve<IScriptEngine>().TransformAsync(vars, script, Options);
} }
private static async Task ExecuteAsync(ContentOperation operation, string script, ScriptVars vars) private static async Task ExecuteAsync(ContentOperation operation, string script, ContentScriptVars vars)
{ {
await operation.Resolve<IScriptEngine>().ExecuteAsync(vars, script, Options); await operation.Resolve<IScriptEngine>().ExecuteAsync(vars, script, Options);
} }
private static ScriptVars Enrich(ContentOperation operation, ScriptVars vars) private static ContentScriptVars Enrich(ContentOperation operation, ContentScriptVars vars)
{ {
vars[ScriptKeys.AppId] = operation.App.Id; vars.AppId = operation.App.Id;
vars[ScriptKeys.AppName] = operation.App.Name; vars.AppName = operation.App.Name;
vars[ScriptKeys.Command] = operation.Command; vars.ContentId = operation.CommandId;
vars[ScriptKeys.Content] = operation.Snapshot; vars.SchemaId = operation.Schema.Id;
vars[ScriptKeys.ContentId] = operation.CommandId; vars.SchemaName = operation.Schema.SchemaDef.Name;
vars[ScriptKeys.SchemaId] = operation.Schema.Id; vars.User = operation.User;
vars[ScriptKeys.SchemaName] = operation.Schema.SchemaDef.Name;
vars[ScriptKeys.User] = operation.User;
return vars; return vars;
} }

4
backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs

@ -115,9 +115,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (!string.IsNullOrWhiteSpace(condition?.Expression) && data != null) if (!string.IsNullOrWhiteSpace(condition?.Expression) && data != null)
{ {
var vars = new ScriptVars var vars = new DataScriptVars
{ {
["data"] = data Data = data
}; };
return scriptEngine.Evaluate(vars, condition.Expression); return scriptEngine.Evaluate(vars, condition.Expression);

39
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs

@ -14,16 +14,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{ {
private readonly IScriptEngine scriptEngine; private readonly IScriptEngine scriptEngine;
private static class ScriptKeys
{
public const string AppId = "appId";
public const string AppName = "appName";
public const string ContentId = "contentId";
public const string Data = "data";
public const string Operation = "operation";
public const string User = "user";
}
public ScriptContent(IScriptEngine scriptEngine) public ScriptContent(IScriptEngine scriptEngine)
{ {
this.scriptEngine = scriptEngine; this.scriptEngine = scriptEngine;
@ -48,11 +38,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
continue; continue;
} }
var vars = new ScriptVars var vars = new ContentScriptVars
{ {
[ScriptKeys.AppId] = context.App.Id, AppId = schema.AppId.Id,
[ScriptKeys.AppName] = context.App.Name, AppName = schema.AppId.Name,
[ScriptKeys.User] = context.User SchemaId = schema.Id,
SchemaName = schema.SchemaDef.Name,
User = context.User
}; };
var preScript = schema.SchemaDef.Scripts.QueryPre; var preScript = schema.SchemaDef.Scripts.QueryPre;
@ -74,15 +66,26 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
} }
} }
private async Task TransformAsync(ScriptVars rootVars, string script, ContentEntity content, private async Task TransformAsync(ContentScriptVars sharedVars, string script, ContentEntity content,
CancellationToken ct) CancellationToken ct)
{ {
var vars = new ScriptVars(rootVars) var vars = new ContentScriptVars
{ {
[ScriptKeys.ContentId] = content.Id, ContentId = content.Id,
[ScriptKeys.Data] = content.Data, Data = content.Data,
DataOld = default,
Status = content.Status,
StatusOld = default
}; };
foreach (var (key, value) in sharedVars)
{
if (!vars.ContainsKey(key))
{
vars[key] = value;
}
}
var options = new ScriptOptions var options = new ScriptOptions
{ {
AsContext = true AsContext = true

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

@ -49,6 +49,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
public void Describe(AddDescription describe, ScriptScope scope) public void Describe(AddDescription describe, ScriptScope scope)
{ {
if (!scope.HasFlag(ScriptScope.Async))
{
return;
}
describe(JsonType.Function, "getReferences(ids, callback)", describe(JsonType.Function, "getReferences(ids, callback)",
Resources.ScriptingGetReferences); Resources.ScriptingGetReferences);

18
backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs

@ -8,6 +8,7 @@
using NodaTime; using NodaTime;
using Orleans; using Orleans;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -34,16 +35,22 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
this.ruleService = ruleService; this.ruleService = ruleService;
} }
public async Task<List<SimulatedRuleEvent>> SimulateAsync(IRuleEntity rule, public Task<List<SimulatedRuleEvent>> SimulateAsync(IRuleEntity rule,
CancellationToken ct = default)
{
return SimulateAsync(rule.AppId, rule.Id, rule.RuleDef, ct);
}
public async Task<List<SimulatedRuleEvent>> SimulateAsync(NamedId<DomainId> appId, DomainId ruleId, Rule rule,
CancellationToken ct = default) CancellationToken ct = default)
{ {
Guard.NotNull(rule); Guard.NotNull(rule);
var context = new RuleContext var context = new RuleContext
{ {
AppId = rule.AppId, AppId = appId,
Rule = rule.RuleDef, Rule = rule,
RuleId = rule.Id, RuleId = ruleId,
IncludeSkipped = true, IncludeSkipped = true,
IncludeStale = true IncludeStale = true
}; };
@ -52,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
var fromNow = SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromDays(7)); var fromNow = SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromDays(7));
await foreach (var storedEvent in eventStore.QueryAllReverseAsync($"^([a-zA-Z0-9]+)\\-{rule.AppId.Id}", fromNow, MaxSimulatedEvents, ct)) await foreach (var storedEvent in eventStore.QueryAllReverseAsync($"^([a-zA-Z0-9]+)\\-{appId.Id}", fromNow, MaxSimulatedEvents, ct))
{ {
var @event = eventDataFormatter.ParseIfKnown(storedEvent); var @event = eventDataFormatter.ParseIfKnown(storedEvent);
@ -75,6 +82,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
EnrichedEvent = result.EnrichedEvent, EnrichedEvent = result.EnrichedEvent,
Error = result.EnrichmentError?.Message, Error = result.EnrichmentError?.Message,
Event = @event.Payload, Event = @event.Payload,
EventId = @event.Headers.EventId(),
EventName = eventName, EventName = eventName,
SkipReason = result.SkipReason SkipReason = result.SkipReason
}); });

4
backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/IRuleRunnerService.cs

@ -5,12 +5,16 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Rules.Runner namespace Squidex.Domain.Apps.Entities.Rules.Runner
{ {
public interface IRuleRunnerService public interface IRuleRunnerService
{ {
Task<List<SimulatedRuleEvent>> SimulateAsync(NamedId<DomainId> appId, DomainId ruleId, Rule rule,
CancellationToken ct = default);
Task<List<SimulatedRuleEvent>> SimulateAsync(IRuleEntity rule, Task<List<SimulatedRuleEvent>> SimulateAsync(IRuleEntity rule,
CancellationToken ct = default); CancellationToken ct = default);

3
backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/SimulatedRuleEvent.cs

@ -6,11 +6,14 @@
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Rules.Runner namespace Squidex.Domain.Apps.Entities.Rules.Runner
{ {
public sealed record SimulatedRuleEvent public sealed record SimulatedRuleEvent
{ {
public Guid EventId { get; init; }
public string EventName { get; init; } public string EventName { get; init; }
public object Event { get; init; } public object Event { get; init; }

3
backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs

@ -79,7 +79,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas
return true; return true;
} }
var vars = new ScriptVars // Script vars are just wrappers over dictionaries for better performance.
var vars = new EventScriptVars
{ {
["event"] = @event ["event"] = @event
}; };

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

@ -34,6 +34,7 @@
<PackageReference Include="Microsoft.Orleans.Core" Version="3.6.0" /> <PackageReference Include="Microsoft.Orleans.Core" Version="3.6.0" />
<PackageReference Include="Notifo.SDK" Version="1.0.1" /> <PackageReference Include="Notifo.SDK" Version="1.0.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="8.8.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="6.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="6.0.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save