Browse Source

Merge branch 'master' of https://github.com/Squidex/squidex

pull/1132/head
Sebastian 2 years ago
parent
commit
74a0a2e243
  1. 23
      .github/workflows/dev.yml
  2. 12
      .github/workflows/make-screenshots.yml
  3. 21
      .github/workflows/release.yml
  4. 23
      CHANGELOG.md
  5. 4
      Dockerfile
  6. 10
      backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj
  7. 2
      backend/extensions/Squidex.Extensions/Text/Azure/AzureIndexDefinition.cs
  8. 18
      backend/i18n/frontend_en.json
  9. 12
      backend/i18n/frontend_fr.json
  10. 12
      backend/i18n/frontend_it.json
  11. 12
      backend/i18n/frontend_nl.json
  12. 12
      backend/i18n/frontend_pt.json
  13. 12
      backend/i18n/frontend_zh.json
  14. 18
      backend/i18n/source/frontend_en.json
  15. 2
      backend/src/Migrations/Migrations.csproj
  16. 10
      backend/src/Squidex.Domain.Apps.Core.Model/Assets/Asset.cs
  17. 6
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/ContentFieldDataConverter.cs
  18. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs
  19. 3
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldEditor.cs
  20. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
  21. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/CacheParser.cs
  22. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  23. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs
  24. 14
      backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  25. 7
      backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/JsonArrayFluidValue.cs
  26. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs
  27. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs
  28. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  29. 42
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Extensions.cs
  30. 5
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs
  31. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs
  32. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/MongoCountCollection.cs
  33. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs
  34. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs
  35. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs
  36. 4
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
  37. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamRepository.cs
  38. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs
  39. 12
      backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs
  40. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetSlug.cs
  41. 34
      backend/src/Squidex.Domain.Apps.Entities/Assets/SvgAssetMetadataSource.cs
  42. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/BulkUpdateContents.cs
  43. 1
      backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/EnrichContentDefaults.cs
  44. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/UpsertContent.cs
  45. 12
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs
  46. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs
  47. 20
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ValidationExtensions.cs
  48. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  49. 17
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/UserGraphType.cs
  50. 9
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs
  51. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs
  52. 2
      backend/src/Squidex.Domain.Apps.Entities/Jobs/JobWorker.cs
  53. 12
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs
  54. 8
      backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  55. 2
      backend/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj
  56. 6
      backend/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs
  57. 4
      backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
  58. 6
      backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj
  59. 10
      backend/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj
  60. 6
      backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs
  61. 6
      backend/src/Squidex.Infrastructure.MongoDb/Log/MongoRequestLogRepository.cs
  62. 6
      backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
  63. 4
      backend/src/Squidex.Infrastructure/Commands/DefaultDomainObjectFactory.cs
  64. 14
      backend/src/Squidex.Infrastructure/Json/System/PolymorphicConverter.cs
  65. 4
      backend/src/Squidex.Infrastructure/Json/System/StringConverter.cs
  66. 2
      backend/src/Squidex.Infrastructure/Migrations/Migrator.cs
  67. 2
      backend/src/Squidex.Infrastructure/Queries/Json/JsonFilterSurrogate.cs
  68. 20
      backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  69. 2
      backend/src/Squidex.Infrastructure/States/StoreExtensions.cs
  70. 2
      backend/src/Squidex.Infrastructure/Tasks/Scheduler.cs
  71. 5
      backend/src/Squidex.Infrastructure/ThrowHelper.cs
  72. 2
      backend/src/Squidex.Infrastructure/Translations/MissingKeys.cs
  73. 3
      backend/src/Squidex.Shared/PermissionIds.cs
  74. 2
      backend/src/Squidex.Shared/Squidex.Shared.csproj
  75. 1
      backend/src/Squidex.Web/ApiExceptionConverter.cs
  76. 2
      backend/src/Squidex.Web/GraphQL/DynamicUserContextBuilder.cs
  77. 8
      backend/src/Squidex.Web/Squidex.Web.csproj
  78. 2
      backend/src/Squidex/Areas/Api/Config/OpenApi/SecurityProcessor.cs
  79. 5
      backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  80. 35
      backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsSharedController.cs
  81. 2
      backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/OperationBuilder.cs
  82. 5
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/BulkUpdateContentsDto.cs
  83. 8
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  84. 29
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/EnrichContentDefaultsDto.cs
  85. 8
      backend/src/Squidex/Areas/Api/Controllers/UploadModel.cs
  86. 161
      backend/src/Squidex/Areas/IdentityServer/Views/Account/Login.cshtml
  87. 87
      backend/src/Squidex/Areas/IdentityServer/Views/Setup/Setup.cshtml
  88. 2
      backend/src/Squidex/Config/Domain/EventSourcingServices.cs
  89. 4
      backend/src/Squidex/Config/Domain/HealthCheckServices.cs
  90. 5
      backend/src/Squidex/Config/Domain/ResizeServices.cs
  91. 2
      backend/src/Squidex/Config/Domain/StoreServices.cs
  92. 1
      backend/src/Squidex/Config/Domain/TelemetryServices.cs
  93. 70
      backend/src/Squidex/Squidex.csproj
  94. 2
      backend/src/Squidex/appsettings.json
  95. 1
      backend/src/Squidex/wwwroot/editor/squidex-editor.css
  96. 531
      backend/src/Squidex/wwwroot/editor/squidex-editor.js
  97. 7
      backend/src/Squidex/wwwroot/scripts/editor-context.html
  98. 6
      backend/src/Squidex/wwwroot/scripts/editor-log.html
  99. 32
      backend/src/Squidex/wwwroot/scripts/editor-sdk.js
  100. 2
      backend/src/Squidex/wwwroot/scripts/embed-sdk.js

23
.github/workflows/dev.yml

@ -22,27 +22,26 @@ jobs:
uses: rlespinasse/github-slug-action@v4.5.0 uses: rlespinasse/github-slug-action@v4.5.0
- name: Prepare - Setup QEMU - name: Prepare - Setup QEMU
uses: docker/setup-qemu-action@v3.0.0 uses: docker/setup-qemu-action@v3.2.0
- name: Prepare - Setup Docker Buildx - name: Prepare - Setup Docker Buildx
uses: docker/setup-buildx-action@v3.3.0 uses: docker/setup-buildx-action@v3.6.1
- name: Prepare - Setup Node - name: Prepare - Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.4
with: with:
node-version: 18 node-version: 18
- name: Build - BUILD - name: Build - BUILD
uses: docker/build-push-action@v5.4.0 uses: docker/build-push-action@v6.7.0
with: with:
load: true load: true
build-args: "SQUIDEX__RUNTIME__VERSION=7.0.0-dev-${{ env.BUILD_NUMBER }}"
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
tags: squidex-local tags: squidex-local
- name: Test - Start Compose - name: Test - Start Compose
run: docker-compose up -d run: docker compose up -d
working-directory: tools/TestSuite working-directory: tools/TestSuite
- name: Test - RUN - name: Test - RUN
@ -96,14 +95,14 @@ jobs:
working-directory: './tools/e2e' working-directory: './tools/e2e'
- name: Test - Run Playwright Tests - name: Test - Run Playwright Tests
run: npx playwright test run: npx playwright test --retries=3
working-directory: './tools/e2e' working-directory: './tools/e2e'
env: env:
BASE__URL: http://localhost:8080 BASE__URL: http://localhost:8080
- name: Test - Upload Playwright Artifacts - name: Test - Upload Playwright Artifacts
if: always() if: always()
uses: actions/upload-artifact@v4.3.3 uses: actions/upload-artifact@v4.4.0
with: with:
name: playwright-report name: playwright-report
path: tools/e2e/playwright-report/ path: tools/e2e/playwright-report/
@ -111,7 +110,7 @@ jobs:
- name: Test - Upload Screenshots - name: Test - Upload Screenshots
if: failure() if: failure()
uses: actions/upload-artifact@v4.3.3 uses: actions/upload-artifact@v4.4.0
with: with:
path: | path: |
tools/TestSuite/TestSuite.ApiTests/bin/Debug/net8.0/screenshots/ tools/TestSuite/TestSuite.ApiTests/bin/Debug/net8.0/screenshots/
@ -125,7 +124,7 @@ jobs:
- name: Test - Cleanup - name: Test - Cleanup
if: always() if: always()
run: docker-compose down run: docker compose down
working-directory: tools/TestSuite working-directory: tools/TestSuite
- name: Publish - Remove unnecessary files - name: Publish - Remove unnecessary files
@ -142,14 +141,14 @@ jobs:
- name: Publish - Login to Docker Hub - name: Publish - Login to Docker Hub
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Publish - Build & Push for Multi-Platforms - name: Publish - Build & Push for Multi-Platforms
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/build-push-action@v5.4.0 uses: docker/build-push-action@v6.7.0
with: with:
build-args: "SQUIDEX__RUNTIME__VERSION=7.0.0-dev-${{ env.BUILD_NUMBER }}" build-args: "SQUIDEX__RUNTIME__VERSION=7.0.0-dev-${{ env.BUILD_NUMBER }}"
cache-from: type=gha cache-from: type=gha

12
.github/workflows/make-screenshots.yml

@ -12,18 +12,18 @@ jobs:
uses: actions/checkout@v4.1.7 uses: actions/checkout@v4.1.7
- name: Prepare - Setup QEMU - name: Prepare - Setup QEMU
uses: docker/setup-qemu-action@v3.0.0 uses: docker/setup-qemu-action@v3.2.0
- name: Prepare - Setup Docker Buildx - name: Prepare - Setup Docker Buildx
uses: docker/setup-buildx-action@v3.3.0 uses: docker/setup-buildx-action@v3.6.1
- name: Prepare - Setup Node - name: Prepare - Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.4
with: with:
node-version: 18 node-version: 18
- name: Build - BUILD - name: Build - BUILD
uses: docker/build-push-action@v5.4.0 uses: docker/build-push-action@v6.7.0
with: with:
load: true load: true
cache-from: type=gha cache-from: type=gha
@ -31,7 +31,7 @@ jobs:
tags: squidex-local tags: squidex-local
- name: Test - Start Compose - name: Test - Start Compose
run: docker-compose up -d run: docker compose up -d
working-directory: tools/TestSuite working-directory: tools/TestSuite
- name: Test - Install Playwright Dependencies - name: Test - Install Playwright Dependencies
@ -50,7 +50,7 @@ jobs:
- name: Test - Upload Playwright Artifacts - name: Test - Upload Playwright Artifacts
if: always() if: always()
uses: actions/upload-artifact@v4.3.3 uses: actions/upload-artifact@v4.4.0
with: with:
name: snapshots name: snapshots
path: tools/e2e/snapshots/ path: tools/e2e/snapshots/

21
.github/workflows/release.yml

@ -17,18 +17,18 @@ jobs:
uses: rlespinasse/github-slug-action@v4.5.0 uses: rlespinasse/github-slug-action@v4.5.0
- name: Prepare - Setup QEMU - name: Prepare - Setup QEMU
uses: docker/setup-qemu-action@v3.0.0 uses: docker/setup-qemu-action@v3.2.0
- name: Prepare - Setup Docker Buildx - name: Prepare - Setup Docker Buildx
uses: docker/setup-buildx-action@v3.3.0 uses: docker/setup-buildx-action@v3.6.1
- name: Prepare - Setup Node - name: Prepare - Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.4
with: with:
node-version: 18 node-version: 18
- name: Build - BUILD - name: Build - BUILD
uses: docker/build-push-action@v5.4.0 uses: docker/build-push-action@v6.7.0
with: with:
load: true load: true
build-args: "SQUIDEX__BUILD__VERSION=${{ env.GITHUB_REF_SLUG }},SQUIDEX__RUNTIME__VERSION=${{ env.GITHUB_REF_SLUG }}" build-args: "SQUIDEX__BUILD__VERSION=${{ env.GITHUB_REF_SLUG }},SQUIDEX__RUNTIME__VERSION=${{ env.GITHUB_REF_SLUG }}"
@ -37,7 +37,7 @@ jobs:
tags: squidex-local tags: squidex-local
- name: Test - Start Compose - name: Test - Start Compose
run: docker-compose up -d run: docker compose up -d
working-directory: tools/TestSuite working-directory: tools/TestSuite
- name: Test - RUN - name: Test - RUN
@ -91,14 +91,14 @@ jobs:
working-directory: './tools/e2e' working-directory: './tools/e2e'
- name: Test - Run Playwright Tests - name: Test - Run Playwright Tests
run: npx playwright test run: npx playwright test --retries=3
working-directory: './tools/e2e' working-directory: './tools/e2e'
env: env:
BASE__URL: http://localhost:8080 BASE__URL: http://localhost:8080
- name: Test - Upload Playwright Artifacts - name: Test - Upload Playwright Artifacts
if: always() if: always()
uses: actions/upload-artifact@v4.3.3 uses: actions/upload-artifact@v4.4.0
with: with:
name: playwright-report name: playwright-report
path: tools/e2e/playwright-report/ path: tools/e2e/playwright-report/
@ -113,7 +113,7 @@ jobs:
- name: Test - Cleanup - name: Test - Cleanup
if: always() if: always()
run: docker-compose down run: docker compose down
working-directory: tools/TestSuite working-directory: tools/TestSuite
- name: Publish - Remove unnecessary files - name: Publish - Remove unnecessary files
@ -136,19 +136,20 @@ jobs:
fi fi
- name: Publish - Login to Docker Hub - name: Publish - Login to Docker Hub
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Publish - Build & Push for Multi-Platforms - name: Publish - Build & Push for Multi-Platforms
uses: docker/build-push-action@v5.3.0 uses: docker/build-push-action@v6.7.0
with: with:
build-args: "SQUIDEX__BUILD__VERSION=${{ env.GITHUB_REF_SLUG }},SQUIDEX__RUNTIME__VERSION=${{ env.GITHUB_REF_SLUG }}" build-args: "SQUIDEX__BUILD__VERSION=${{ env.GITHUB_REF_SLUG }},SQUIDEX__RUNTIME__VERSION=${{ env.GITHUB_REF_SLUG }}"
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
tags: squidex/squidex:${{ env.GITHUB_REF_SLUG }},squidex/squidex:${{ steps.version.outputs._0 }}${{env.STABLE_VERSION == 'true' && ',squidex/squidex:latest'}} tags: squidex/squidex:${{ env.GITHUB_REF_SLUG }},squidex/squidex:${{ steps.version.outputs._0 }}${{env.STABLE_VERSION == 'true' && ',squidex/squidex:latest'}}
push: true
- name: Release - Make directories - name: Release - Make directories
run: sudo mkdir /build /release run: sudo mkdir /build /release

23
CHANGELOG.md

@ -4,6 +4,29 @@ 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).
## [7.15.0] - 2024-08-23
### Fixed
* **Contents**: Fix to also deliver fields that have the same name as meta fields.
* **Contents**: Never return null statuses for content queries.
* **Migration**: Fix log message in migration.
* **UI**: Correctly remove the filter row, when the delete button is returned.
* **UI**: Fix autocompletion for code editors.
* **UI**: Fix clipboard behavior for rich text editor.
* **UI**: Fix seperator in login screen.
* **UI**: Navigate to the correct page when the back button on the content page is fixed.
* **UI**: Show filters in query dialog when selecting references.
* **UI**: Use correct npm packages to prevent version conflicts.
### Added
* **Contents**: New flag to also enrich required fields.
* **Contents**: New header flag to not enrich queried content items with default values.
* **UI**: Make the code editor editable in the markdown mode of the rich text editor.
* **UI**: New event for editor extensions when the context has been changed.
* **UI**: Support data attributes for Squidex tokens in embed SDK.
## [7.14.0] - 2024-07-03 ## [7.14.0] - 2024-07-03
### Fixed ### Fixed

4
Dockerfile

@ -2,7 +2,7 @@
# Stage 1, Build Backend # Stage 1, Build Backend
# #
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 as backend FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS backend
ARG SQUIDEX__BUILD__VERSION=7.0.0 ARG SQUIDEX__BUILD__VERSION=7.0.0
@ -43,7 +43,7 @@ RUN dotnet tool install --tool-path /tools dotnet-counters \
# #
# Stage 2, Build Frontend # Stage 2, Build Frontend
# #
FROM squidex/frontend-build:20.9 as frontend FROM squidex/frontend-build:20.9 AS frontend
WORKDIR /src WORKDIR /src

10
backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj

@ -12,22 +12,22 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Algolia.Search" Version="6.17.0" /> <PackageReference Include="Algolia.Search" Version="6.17.0" />
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.3.0" /> <PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.3.0" />
<PackageReference Include="Azure.Search.Documents" Version="11.5.1" /> <PackageReference Include="Azure.Search.Documents" Version="11.6.0" />
<PackageReference Include="Confluent.SchemaRegistry.Serdes.Avro" Version="2.4.0" /> <PackageReference Include="Confluent.SchemaRegistry.Serdes.Avro" Version="2.5.2" />
<PackageReference Include="CoreTweet" Version="1.0.0.483" /> <PackageReference Include="CoreTweet" Version="1.0.0.483" />
<PackageReference Include="Elasticsearch.Net" Version="7.17.5" /> <PackageReference Include="Elasticsearch.Net" Version="7.17.5" />
<PackageReference Include="Google.Cloud.Diagnostics.Common" Version="5.2.0" /> <PackageReference Include="Google.Cloud.Diagnostics.Common" Version="5.2.0" />
<PackageReference Include="Google.Cloud.Logging.V2" Version="4.4.0" /> <PackageReference Include="Google.Cloud.Logging.V2" Version="4.4.0" />
<PackageReference Include="Google.Cloud.Monitoring.V3" Version="3.11.0" /> <PackageReference Include="Google.Cloud.Monitoring.V3" Version="3.11.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Azure.CognitiveServices.Vision.ComputerVision" Version="7.0.1" /> <PackageReference Include="Microsoft.Azure.CognitiveServices.Vision.ComputerVision" Version="7.0.1" />
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.25.2" /> <PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.27.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.21.3" /> <PackageReference Include="Microsoft.OData.Core" Version="8.0.1" />
<PackageReference Include="NodaTime" Version="3.1.11" /> <PackageReference Include="NodaTime" Version="3.1.11" />
<PackageReference Include="OpenSearch.Net" Version="1.7.1" /> <PackageReference Include="OpenSearch.Net" Version="1.7.1" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" /> <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />

2
backend/extensions/Squidex.Extensions/Text/Azure/AzureIndexDefinition.cs

@ -25,7 +25,7 @@ public static class AzureIndexDefinition
.GetProperties(BindingFlags.Public | BindingFlags.Static) .GetProperties(BindingFlags.Public | BindingFlags.Static)
.Select(x => x.GetValue(null)!) .Select(x => x.GetValue(null)!)
.Select(x => x.ToString()!) .Select(x => x.ToString()!)
.OrderBy(x => x) .Order()
.ToList(); .ToList();
var addedLanguage = new HashSet<string>(); var addedLanguage = new HashSet<string>();

18
backend/i18n/frontend_en.json

@ -180,6 +180,8 @@
"clients.connectWizard.step0Title": "Setup client", "clients.connectWizard.step0Title": "Setup client",
"clients.connectWizard.step1Title": "Choose connection method", "clients.connectWizard.step1Title": "Choose connection method",
"clients.connectWizard.step2Title": "Connect", "clients.connectWizard.step2Title": "Connect",
"clients.copyClientId": "Copy Client ID",
"clients.copyClientSecret": "Copy Client Secret",
"clients.deleteConfirmText": "Do you really want to revoke the client?", "clients.deleteConfirmText": "Do you really want to revoke the client?",
"clients.deleteConfirmTitle": "Revoke client", "clients.deleteConfirmTitle": "Revoke client",
"clients.empty": "No client created yet.", "clients.empty": "No client created yet.",
@ -194,6 +196,7 @@
"comments.follow": "Follow", "comments.follow": "Follow",
"comments.title": "Comments", "comments.title": "Comments",
"common.actions": "Actions", "common.actions": "Actions",
"common.add": "Add",
"common.administration": "Administration", "common.administration": "Administration",
"common.administrationPageTitle": "Administration", "common.administrationPageTitle": "Administration",
"common.api": "API", "common.api": "API",
@ -251,6 +254,7 @@
"common.disabled": "Disabled", "common.disabled": "Disabled",
"common.displayName": "Display Name", "common.displayName": "Display Name",
"common.documentation": "Documentation", "common.documentation": "Documentation",
"common.download": "Download",
"common.edit": "Edit", "common.edit": "Edit",
"common.editing": "Editing", "common.editing": "Editing",
"common.editInNewTab": "Open in new tab", "common.editInNewTab": "Open in new tab",
@ -310,6 +314,7 @@
"common.more": "More", "common.more": "More",
"common.name": "Name", "common.name": "Name",
"common.next": "Continue", "common.next": "Continue",
"common.nextPage": "Next Page",
"common.no": "No", "common.no": "No",
"common.nothingChanged": "Nothing has been changed.", "common.nothingChanged": "Nothing has been changed.",
"common.notSupported": "Not Supported", "common.notSupported": "Not Supported",
@ -320,6 +325,7 @@
"common.pagerInfo": "{itemFirst}-{itemLast} of {numberOfItems}", "common.pagerInfo": "{itemFirst}-{itemLast} of {numberOfItems}",
"common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?",
"common.pagerReload": "Click to reload view and get total number of items", "common.pagerReload": "Click to reload view and get total number of items",
"common.pageSize": "Page Size",
"common.password": "Password", "common.password": "Password",
"common.passwordConfirm": "Confirm Password", "common.passwordConfirm": "Confirm Password",
"common.pattern": "Pattern", "common.pattern": "Pattern",
@ -328,6 +334,7 @@
"common.permissions": "Permissions", "common.permissions": "Permissions",
"common.prev": "Back", "common.prev": "Back",
"common.preview": "Preview", "common.preview": "Preview",
"common.prevPage": "Previous Page",
"common.product": "Squidex Headless CMS", "common.product": "Squidex Headless CMS",
"common.project": "Project", "common.project": "Project",
"common.queryOperators.contains": "contains", "common.queryOperators.contains": "contains",
@ -359,6 +366,9 @@
"common.rules": "Rules", "common.rules": "Rules",
"common.sampleCodeLabel": "Sample Code at", "common.sampleCodeLabel": "Sample Code at",
"common.save": "Save", "common.save": "Save",
"common.saveAdd": "Save & add another",
"common.saveClose": "Save & close",
"common.saveEdit": "Save & editor",
"common.schema": "Schema", "common.schema": "Schema",
"common.schemas": "Schemas", "common.schemas": "Schemas",
"common.search": "Search", "common.search": "Search",
@ -480,6 +490,8 @@
"contents.removeConfirmText": "Do you really want to remove the content?", "contents.removeConfirmText": "Do you really want to remove the content?",
"contents.removeConfirmTitle": "Remove content", "contents.removeConfirmTitle": "Remove content",
"contents.saveAndPublish": "Save and Publish", "contents.saveAndPublish": "Save and Publish",
"contents.saveAndPublishAdd": "Save and Publish & add another",
"contents.saveAndPublishClose": "Save and Publish & close",
"contents.scheduledAt": "at", "contents.scheduledAt": "at",
"contents.scheduledBy": "by", "contents.scheduledBy": "by",
"contents.scheduledTo": "to", "contents.scheduledTo": "to",
@ -785,9 +797,9 @@
"rules.updateFailed": "Failed to update rule. Please reload.", "rules.updateFailed": "Failed to update rule. Please reload.",
"rules.when": "When", "rules.when": "When",
"schemas.addField": "Add Field", "schemas.addField": "Add Field",
"schemas.addFieldAndClose": "Create and close", "schemas.addFieldAndClose": "Create",
"schemas.addFieldAndCreate": "Create and add field", "schemas.addFieldAndCreate": "Create & add another",
"schemas.addFieldAndEdit": "Create and edit field", "schemas.addFieldAndEdit": "Create & edit properties",
"schemas.addFieldButton": "Add Field", "schemas.addFieldButton": "Add Field",
"schemas.addFieldFailed": "Failed to add field. Please reload.", "schemas.addFieldFailed": "Failed to add field. Please reload.",
"schemas.addNestedField": "Add Nested Field", "schemas.addNestedField": "Add Nested Field",

12
backend/i18n/frontend_fr.json

@ -180,6 +180,8 @@
"clients.connectWizard.step0Title": "Configurer le client", "clients.connectWizard.step0Title": "Configurer le client",
"clients.connectWizard.step1Title": "Choisissez la méthode de connexion", "clients.connectWizard.step1Title": "Choisissez la méthode de connexion",
"clients.connectWizard.step2Title": "Connecter", "clients.connectWizard.step2Title": "Connecter",
"clients.copyClientId": "Copy Client ID",
"clients.copyClientSecret": "Copy Client Secret",
"clients.deleteConfirmText": "Voulez-vous vraiment révoquer le client\u00A0?", "clients.deleteConfirmText": "Voulez-vous vraiment révoquer le client\u00A0?",
"clients.deleteConfirmTitle": "Révoquer le client", "clients.deleteConfirmTitle": "Révoquer le client",
"clients.empty": "Aucun client créé pour le moment.", "clients.empty": "Aucun client créé pour le moment.",
@ -194,6 +196,7 @@
"comments.follow": "Suivre", "comments.follow": "Suivre",
"comments.title": "commentaires", "comments.title": "commentaires",
"common.actions": "Actions", "common.actions": "Actions",
"common.add": "Add",
"common.administration": "Administration", "common.administration": "Administration",
"common.administrationPageTitle": "Administration", "common.administrationPageTitle": "Administration",
"common.api": "API", "common.api": "API",
@ -251,6 +254,7 @@
"common.disabled": "Désactivé", "common.disabled": "Désactivé",
"common.displayName": "Afficher un nom", "common.displayName": "Afficher un nom",
"common.documentation": "Documentation", "common.documentation": "Documentation",
"common.download": "Download",
"common.edit": "Modifier", "common.edit": "Modifier",
"common.editing": "Édition", "common.editing": "Édition",
"common.editInNewTab": "Open in new tab", "common.editInNewTab": "Open in new tab",
@ -310,6 +314,7 @@
"common.more": "Plus", "common.more": "Plus",
"common.name": "Nom", "common.name": "Nom",
"common.next": "Continue", "common.next": "Continue",
"common.nextPage": "Next Page",
"common.no": "Non", "common.no": "Non",
"common.nothingChanged": "Rien n'a été changé.", "common.nothingChanged": "Rien n'a été changé.",
"common.notSupported": "Non supporté", "common.notSupported": "Non supporté",
@ -320,6 +325,7 @@
"common.pagerInfo": "{itemFirst}-{itemLast} de {numberOfItems}", "common.pagerInfo": "{itemFirst}-{itemLast} de {numberOfItems}",
"common.pagerInfoNoTotal": "{itemFirst}-{itemLast} Du total?", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} Du total?",
"common.pagerReload": "Cliquez pour recharger la vue et obtenir le nombre total d'articles", "common.pagerReload": "Cliquez pour recharger la vue et obtenir le nombre total d'articles",
"common.pageSize": "Page Size",
"common.password": "Mot de passe", "common.password": "Mot de passe",
"common.passwordConfirm": "Confirmez le mot de passe", "common.passwordConfirm": "Confirmez le mot de passe",
"common.pattern": "Modèle", "common.pattern": "Modèle",
@ -328,6 +334,7 @@
"common.permissions": "Autorisations", "common.permissions": "Autorisations",
"common.prev": "Back", "common.prev": "Back",
"common.preview": "Aperçu", "common.preview": "Aperçu",
"common.prevPage": "Previous Page",
"common.product": "CMS sans tête Squidex", "common.product": "CMS sans tête Squidex",
"common.project": "Projet", "common.project": "Projet",
"common.queryOperators.contains": "contient", "common.queryOperators.contains": "contient",
@ -359,6 +366,9 @@
"common.rules": "Règles", "common.rules": "Règles",
"common.sampleCodeLabel": "Exemple de code à", "common.sampleCodeLabel": "Exemple de code à",
"common.save": "Sauvegarder", "common.save": "Sauvegarder",
"common.saveAdd": "Save & add another",
"common.saveClose": "Save & close",
"common.saveEdit": "Save & editor",
"common.schema": "Schéma", "common.schema": "Schéma",
"common.schemas": "Schémas", "common.schemas": "Schémas",
"common.search": "Recherche", "common.search": "Recherche",
@ -480,6 +490,8 @@
"contents.removeConfirmText": "Voulez-vous vraiment supprimer le contenu\u00A0?", "contents.removeConfirmText": "Voulez-vous vraiment supprimer le contenu\u00A0?",
"contents.removeConfirmTitle": "Supprimer le contenu", "contents.removeConfirmTitle": "Supprimer le contenu",
"contents.saveAndPublish": "Enregistrer et publier", "contents.saveAndPublish": "Enregistrer et publier",
"contents.saveAndPublishAdd": "Save and Publish & add another",
"contents.saveAndPublishClose": "Save and Publish & close",
"contents.scheduledAt": "à", "contents.scheduledAt": "à",
"contents.scheduledBy": "par", "contents.scheduledBy": "par",
"contents.scheduledTo": "pour", "contents.scheduledTo": "pour",

12
backend/i18n/frontend_it.json

@ -180,6 +180,8 @@
"clients.connectWizard.step0Title": "Setup client", "clients.connectWizard.step0Title": "Setup client",
"clients.connectWizard.step1Title": "Scegli la tipologia di connessione", "clients.connectWizard.step1Title": "Scegli la tipologia di connessione",
"clients.connectWizard.step2Title": "Collega", "clients.connectWizard.step2Title": "Collega",
"clients.copyClientId": "Copy Client ID",
"clients.copyClientSecret": "Copy Client Secret",
"clients.deleteConfirmText": "Sei sicuro di voler rimuovere il client?", "clients.deleteConfirmText": "Sei sicuro di voler rimuovere il client?",
"clients.deleteConfirmTitle": "Rimuovere il client", "clients.deleteConfirmTitle": "Rimuovere il client",
"clients.empty": "Nessun client ancora creato.", "clients.empty": "Nessun client ancora creato.",
@ -194,6 +196,7 @@
"comments.follow": "Segui", "comments.follow": "Segui",
"comments.title": "Commenti", "comments.title": "Commenti",
"common.actions": "Azioni", "common.actions": "Azioni",
"common.add": "Add",
"common.administration": "Amministrazione", "common.administration": "Amministrazione",
"common.administrationPageTitle": "Amministrazione", "common.administrationPageTitle": "Amministrazione",
"common.api": "API", "common.api": "API",
@ -251,6 +254,7 @@
"common.disabled": "Disabled", "common.disabled": "Disabled",
"common.displayName": "Nome visualizzato", "common.displayName": "Nome visualizzato",
"common.documentation": "Documentation", "common.documentation": "Documentation",
"common.download": "Download",
"common.edit": "Modifica", "common.edit": "Modifica",
"common.editing": "Editing", "common.editing": "Editing",
"common.editInNewTab": "Open in new tab", "common.editInNewTab": "Open in new tab",
@ -310,6 +314,7 @@
"common.more": "More", "common.more": "More",
"common.name": "Nome", "common.name": "Nome",
"common.next": "Continue", "common.next": "Continue",
"common.nextPage": "Next Page",
"common.no": "No", "common.no": "No",
"common.nothingChanged": "Non è stato cambiato niente.", "common.nothingChanged": "Non è stato cambiato niente.",
"common.notSupported": "Not Supported", "common.notSupported": "Not Supported",
@ -320,6 +325,7 @@
"common.pagerInfo": "{itemFirst}-{itemLast} of {numberOfItems}", "common.pagerInfo": "{itemFirst}-{itemLast} of {numberOfItems}",
"common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?",
"common.pagerReload": "Click to reload view and get total number of items", "common.pagerReload": "Click to reload view and get total number of items",
"common.pageSize": "Page Size",
"common.password": "Password", "common.password": "Password",
"common.passwordConfirm": "Conferma Password", "common.passwordConfirm": "Conferma Password",
"common.pattern": "Pattern", "common.pattern": "Pattern",
@ -328,6 +334,7 @@
"common.permissions": "Permessi", "common.permissions": "Permessi",
"common.prev": "Back", "common.prev": "Back",
"common.preview": "Anteprima", "common.preview": "Anteprima",
"common.prevPage": "Previous Page",
"common.product": "Squidex Headless CMS", "common.product": "Squidex Headless CMS",
"common.project": "Progetto", "common.project": "Progetto",
"common.queryOperators.contains": "contiene", "common.queryOperators.contains": "contiene",
@ -359,6 +366,9 @@
"common.rules": "Regole", "common.rules": "Regole",
"common.sampleCodeLabel": "Esempio di codice per", "common.sampleCodeLabel": "Esempio di codice per",
"common.save": "Salva", "common.save": "Salva",
"common.saveAdd": "Save & add another",
"common.saveClose": "Save & close",
"common.saveEdit": "Save & editor",
"common.schema": "Schema", "common.schema": "Schema",
"common.schemas": "Schemi", "common.schemas": "Schemi",
"common.search": "Search", "common.search": "Search",
@ -480,6 +490,8 @@
"contents.removeConfirmText": "Sei sicuro di voler rimuovere il contenuto?", "contents.removeConfirmText": "Sei sicuro di voler rimuovere il contenuto?",
"contents.removeConfirmTitle": "Cancella il contenuto", "contents.removeConfirmTitle": "Cancella il contenuto",
"contents.saveAndPublish": "Salva e pubblica", "contents.saveAndPublish": "Salva e pubblica",
"contents.saveAndPublishAdd": "Save and Publish & add another",
"contents.saveAndPublishClose": "Save and Publish & close",
"contents.scheduledAt": "alle", "contents.scheduledAt": "alle",
"contents.scheduledBy": "by", "contents.scheduledBy": "by",
"contents.scheduledTo": "a", "contents.scheduledTo": "a",

12
backend/i18n/frontend_nl.json

@ -180,6 +180,8 @@
"clients.connectWizard.step0Title": "Client instellen", "clients.connectWizard.step0Title": "Client instellen",
"clients.connectWizard.step1Title": "Kies verbindingsmethode", "clients.connectWizard.step1Title": "Kies verbindingsmethode",
"clients.connectWizard.step2Title": "Verbinden", "clients.connectWizard.step2Title": "Verbinden",
"clients.copyClientId": "Copy Client ID",
"clients.copyClientSecret": "Copy Client Secret",
"clients.deleteConfirmText": "Wil je de client echt intrekken?", "clients.deleteConfirmText": "Wil je de client echt intrekken?",
"clients.deleteConfirmTitle": "Client intrekken", "clients.deleteConfirmTitle": "Client intrekken",
"clients.empty": "Nog geen client aangemaakt.", "clients.empty": "Nog geen client aangemaakt.",
@ -194,6 +196,7 @@
"comments.follow": "Volgen", "comments.follow": "Volgen",
"comments.title": "Reacties", "comments.title": "Reacties",
"common.actions": "Acties", "common.actions": "Acties",
"common.add": "Add",
"common.administration": "Administratie", "common.administration": "Administratie",
"common.administrationPageTitle": "Administratie", "common.administrationPageTitle": "Administratie",
"common.api": "API", "common.api": "API",
@ -251,6 +254,7 @@
"common.disabled": "Uitgezet", "common.disabled": "Uitgezet",
"common.displayName": "Weergavenaam", "common.displayName": "Weergavenaam",
"common.documentation": "Documentation", "common.documentation": "Documentation",
"common.download": "Download",
"common.edit": "Bewerken", "common.edit": "Bewerken",
"common.editing": "Bewerken", "common.editing": "Bewerken",
"common.editInNewTab": "Open in new tab", "common.editInNewTab": "Open in new tab",
@ -310,6 +314,7 @@
"common.more": "Meer", "common.more": "Meer",
"common.name": "Naam", "common.name": "Naam",
"common.next": "Continue", "common.next": "Continue",
"common.nextPage": "Next Page",
"common.no": "Nee", "common.no": "Nee",
"common.nothingChanged": "Er is niets veranderd.", "common.nothingChanged": "Er is niets veranderd.",
"common.notSupported": "Not Supported", "common.notSupported": "Not Supported",
@ -320,6 +325,7 @@
"common.pagerInfo": "{itemFirst} - {itemLast} van {numberOfItems}", "common.pagerInfo": "{itemFirst} - {itemLast} van {numberOfItems}",
"common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?",
"common.pagerReload": "Click to reload view and get total number of items", "common.pagerReload": "Click to reload view and get total number of items",
"common.pageSize": "Page Size",
"common.password": "Wachtwoord", "common.password": "Wachtwoord",
"common.passwordConfirm": "Bevestig wachtwoord", "common.passwordConfirm": "Bevestig wachtwoord",
"common.pattern": "Patroon", "common.pattern": "Patroon",
@ -328,6 +334,7 @@
"common.permissions": "Rechten", "common.permissions": "Rechten",
"common.prev": "Back", "common.prev": "Back",
"common.preview": "Preview", "common.preview": "Preview",
"common.prevPage": "Previous Page",
"common.product": "Squidex Headless CMS", "common.product": "Squidex Headless CMS",
"common.project": "Project", "common.project": "Project",
"common.queryOperators.contains": "bevat", "common.queryOperators.contains": "bevat",
@ -359,6 +366,9 @@
"common.rules": "Regels", "common.rules": "Regels",
"common.sampleCodeLabel": "Voorbeeldcode bij", "common.sampleCodeLabel": "Voorbeeldcode bij",
"common.save": "Opslaan", "common.save": "Opslaan",
"common.saveAdd": "Save & add another",
"common.saveClose": "Save & close",
"common.saveEdit": "Save & editor",
"common.schema": "Schema", "common.schema": "Schema",
"common.schemas": "Schema's", "common.schemas": "Schema's",
"common.search": "Zoeken", "common.search": "Zoeken",
@ -480,6 +490,8 @@
"contents.removeConfirmText": "Wil je de inhoud echt verwijderen?", "contents.removeConfirmText": "Wil je de inhoud echt verwijderen?",
"contents.removeConfirmTitle": "Verwijder inhoud", "contents.removeConfirmTitle": "Verwijder inhoud",
"contents.saveAndPublish": "Opslaan en publiceren", "contents.saveAndPublish": "Opslaan en publiceren",
"contents.saveAndPublishAdd": "Save and Publish & add another",
"contents.saveAndPublishClose": "Save and Publish & close",
"contents.scheduledAt": "bij", "contents.scheduledAt": "bij",
"contents.scheduledBy": "door", "contents.scheduledBy": "door",
"contents.scheduledTo": "naar", "contents.scheduledTo": "naar",

12
backend/i18n/frontend_pt.json

@ -180,6 +180,8 @@
"clients.connectWizard.step0Title": "Cliente de configuração", "clients.connectWizard.step0Title": "Cliente de configuração",
"clients.connectWizard.step1Title": "Escolha o método de ligação", "clients.connectWizard.step1Title": "Escolha o método de ligação",
"clients.connectWizard.step2Title": "Ligar", "clients.connectWizard.step2Title": "Ligar",
"clients.copyClientId": "Copy Client ID",
"clients.copyClientSecret": "Copy Client Secret",
"clients.deleteConfirmText": "Quer mesmo revogar o cliente?", "clients.deleteConfirmText": "Quer mesmo revogar o cliente?",
"clients.deleteConfirmTitle": "Revogar cliente", "clients.deleteConfirmTitle": "Revogar cliente",
"clients.empty": "Nenhum cliente foi criado ainda.", "clients.empty": "Nenhum cliente foi criado ainda.",
@ -194,6 +196,7 @@
"comments.follow": "Seguir", "comments.follow": "Seguir",
"comments.title": "Comentários", "comments.title": "Comentários",
"common.actions": "Ações", "common.actions": "Ações",
"common.add": "Add",
"common.administration": "Administração", "common.administration": "Administração",
"common.administrationPageTitle": "Administração", "common.administrationPageTitle": "Administração",
"common.api": "API", "common.api": "API",
@ -251,6 +254,7 @@
"common.disabled": "Deficientes", "common.disabled": "Deficientes",
"common.displayName": "Nome do visor", "common.displayName": "Nome do visor",
"common.documentation": "Documentação", "common.documentation": "Documentação",
"common.download": "Download",
"common.edit": "Editar", "common.edit": "Editar",
"common.editing": "Edição", "common.editing": "Edição",
"common.editInNewTab": "Open in new tab", "common.editInNewTab": "Open in new tab",
@ -310,6 +314,7 @@
"common.more": "Mais", "common.more": "Mais",
"common.name": "Nome", "common.name": "Nome",
"common.next": "Continue", "common.next": "Continue",
"common.nextPage": "Next Page",
"common.no": "Não", "common.no": "Não",
"common.nothingChanged": "Nada foi mudado.", "common.nothingChanged": "Nada foi mudado.",
"common.notSupported": "Não suportado", "common.notSupported": "Não suportado",
@ -320,6 +325,7 @@
"common.pagerInfo": "{itemFirst}-{itemLast} de {numberOfItems}", "common.pagerInfo": "{itemFirst}-{itemLast} de {numberOfItems}",
"common.pagerInfoNoTotal": "{itemFirst}-{itemLast} do total?", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} do total?",
"common.pagerReload": "Clique para recarregar a vista e obter o número total de itens", "common.pagerReload": "Clique para recarregar a vista e obter o número total de itens",
"common.pageSize": "Page Size",
"common.password": "Password", "common.password": "Password",
"common.passwordConfirm": "Confirmar Password", "common.passwordConfirm": "Confirmar Password",
"common.pattern": "Padrão", "common.pattern": "Padrão",
@ -328,6 +334,7 @@
"common.permissions": "Permissões", "common.permissions": "Permissões",
"common.prev": "Back", "common.prev": "Back",
"common.preview": "Previsualizar", "common.preview": "Previsualizar",
"common.prevPage": "Previous Page",
"common.product": "CMS Headless Squidex", "common.product": "CMS Headless Squidex",
"common.project": "Projeto", "common.project": "Projeto",
"common.queryOperators.contains": "contém", "common.queryOperators.contains": "contém",
@ -359,6 +366,9 @@
"common.rules": "Regras", "common.rules": "Regras",
"common.sampleCodeLabel": "Código de amostra em", "common.sampleCodeLabel": "Código de amostra em",
"common.save": "Salvar", "common.save": "Salvar",
"common.saveAdd": "Save & add another",
"common.saveClose": "Save & close",
"common.saveEdit": "Save & editor",
"common.schema": "Esquema", "common.schema": "Esquema",
"common.schemas": "Esquemas", "common.schemas": "Esquemas",
"common.search": "Pesquisar", "common.search": "Pesquisar",
@ -480,6 +490,8 @@
"contents.removeConfirmText": "Quer mesmo remover o conteúdo?", "contents.removeConfirmText": "Quer mesmo remover o conteúdo?",
"contents.removeConfirmTitle": "Remover conteúdo", "contents.removeConfirmTitle": "Remover conteúdo",
"contents.saveAndPublish": "Salvar e Publicar", "contents.saveAndPublish": "Salvar e Publicar",
"contents.saveAndPublishAdd": "Save and Publish & add another",
"contents.saveAndPublishClose": "Save and Publish & close",
"contents.scheduledAt": "em", "contents.scheduledAt": "em",
"contents.scheduledBy": "por", "contents.scheduledBy": "por",
"contents.scheduledTo": "Para", "contents.scheduledTo": "Para",

12
backend/i18n/frontend_zh.json

@ -180,6 +180,8 @@
"clients.connectWizard.step0Title": "设置客户端", "clients.connectWizard.step0Title": "设置客户端",
"clients.connectWizard.step1Title": "选择连接方式", "clients.connectWizard.step1Title": "选择连接方式",
"clients.connectWizard.step2Title": "连接", "clients.connectWizard.step2Title": "连接",
"clients.copyClientId": "Copy Client ID",
"clients.copyClientSecret": "Copy Client Secret",
"clients.deleteConfirmText": "你真的要撤销客户端吗?", "clients.deleteConfirmText": "你真的要撤销客户端吗?",
"clients.deleteConfirmTitle": "撤销客户端", "clients.deleteConfirmTitle": "撤销客户端",
"clients.empty": "尚未创建客户端。", "clients.empty": "尚未创建客户端。",
@ -194,6 +196,7 @@
"comments.follow": "关注", "comments.follow": "关注",
"comments.title": "评论", "comments.title": "评论",
"common.actions": "动作", "common.actions": "动作",
"common.add": "Add",
"common.administration": "管理", "common.administration": "管理",
"common.administrationPageTitle": "管理", "common.administrationPageTitle": "管理",
"common.api": "API", "common.api": "API",
@ -251,6 +254,7 @@
"common.disabled": "已禁用", "common.disabled": "已禁用",
"common.displayName": "显示名称", "common.displayName": "显示名称",
"common.documentation": "Documentation", "common.documentation": "Documentation",
"common.download": "Download",
"common.edit": "编辑", "common.edit": "编辑",
"common.editing": "Editing", "common.editing": "Editing",
"common.editInNewTab": "Open in new tab", "common.editInNewTab": "Open in new tab",
@ -310,6 +314,7 @@
"common.more": "More", "common.more": "More",
"common.name": "名称", "common.name": "名称",
"common.next": "Continue", "common.next": "Continue",
"common.nextPage": "Next Page",
"common.no": "不", "common.no": "不",
"common.nothingChanged": "什么都没有改变。", "common.nothingChanged": "什么都没有改变。",
"common.notSupported": "Not Supported", "common.notSupported": "Not Supported",
@ -320,6 +325,7 @@
"common.pagerInfo": "{itemFirst}-{itemLast} 的 {numberOfItems}", "common.pagerInfo": "{itemFirst}-{itemLast} 的 {numberOfItems}",
"common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?",
"common.pagerReload": "Click to reload view and get total number of items", "common.pagerReload": "Click to reload view and get total number of items",
"common.pageSize": "Page Size",
"common.password": "密码", "common.password": "密码",
"common.passwordConfirm": "确认密码", "common.passwordConfirm": "确认密码",
"common.pattern": "模式", "common.pattern": "模式",
@ -328,6 +334,7 @@
"common.permissions": "权限", "common.permissions": "权限",
"common.prev": "Back", "common.prev": "Back",
"common.preview": "预览", "common.preview": "预览",
"common.prevPage": "Previous Page",
"common.product": "Squidex Headless CMS", "common.product": "Squidex Headless CMS",
"common.project": "项目", "common.project": "项目",
"common.queryOperators.contains": "包含", "common.queryOperators.contains": "包含",
@ -359,6 +366,9 @@
"common.rules": "规则", "common.rules": "规则",
"common.sampleCodeLabel": "示例代码在", "common.sampleCodeLabel": "示例代码在",
"common.save": "保存", "common.save": "保存",
"common.saveAdd": "Save & add another",
"common.saveClose": "Save & close",
"common.saveEdit": "Save & editor",
"common.schema": "Schema", "common.schema": "Schema",
"common.schemas": "Schemas", "common.schemas": "Schemas",
"common.search": "搜索", "common.search": "搜索",
@ -480,6 +490,8 @@
"contents.removeConfirmText": "您真的要删除内容吗?", "contents.removeConfirmText": "您真的要删除内容吗?",
"contents.removeConfirmTitle": "删除内容", "contents.removeConfirmTitle": "删除内容",
"contents.saveAndPublish": "保存并发布", "contents.saveAndPublish": "保存并发布",
"contents.saveAndPublishAdd": "Save and Publish & add another",
"contents.saveAndPublishClose": "Save and Publish & close",
"contents.scheduledAt": "at", "contents.scheduledAt": "at",
"contents.scheduledBy": "by", "contents.scheduledBy": "by",
"contents.scheduledTo": "to", "contents.scheduledTo": "to",

18
backend/i18n/source/frontend_en.json

@ -180,6 +180,8 @@
"clients.connectWizard.step0Title": "Setup client", "clients.connectWizard.step0Title": "Setup client",
"clients.connectWizard.step1Title": "Choose connection method", "clients.connectWizard.step1Title": "Choose connection method",
"clients.connectWizard.step2Title": "Connect", "clients.connectWizard.step2Title": "Connect",
"clients.copyClientId": "Copy Client ID",
"clients.copyClientSecret": "Copy Client Secret",
"clients.deleteConfirmText": "Do you really want to revoke the client?", "clients.deleteConfirmText": "Do you really want to revoke the client?",
"clients.deleteConfirmTitle": "Revoke client", "clients.deleteConfirmTitle": "Revoke client",
"clients.empty": "No client created yet.", "clients.empty": "No client created yet.",
@ -194,6 +196,7 @@
"comments.follow": "Follow", "comments.follow": "Follow",
"comments.title": "Comments", "comments.title": "Comments",
"common.actions": "Actions", "common.actions": "Actions",
"common.add": "Add",
"common.administration": "Administration", "common.administration": "Administration",
"common.administrationPageTitle": "Administration", "common.administrationPageTitle": "Administration",
"common.api": "API", "common.api": "API",
@ -251,6 +254,7 @@
"common.disabled": "Disabled", "common.disabled": "Disabled",
"common.displayName": "Display Name", "common.displayName": "Display Name",
"common.documentation": "Documentation", "common.documentation": "Documentation",
"common.download": "Download",
"common.edit": "Edit", "common.edit": "Edit",
"common.editing": "Editing", "common.editing": "Editing",
"common.editInNewTab": "Open in new tab", "common.editInNewTab": "Open in new tab",
@ -310,6 +314,7 @@
"common.more": "More", "common.more": "More",
"common.name": "Name", "common.name": "Name",
"common.next": "Continue", "common.next": "Continue",
"common.nextPage": "Next Page",
"common.no": "No", "common.no": "No",
"common.nothingChanged": "Nothing has been changed.", "common.nothingChanged": "Nothing has been changed.",
"common.notSupported": "Not Supported", "common.notSupported": "Not Supported",
@ -320,6 +325,7 @@
"common.pagerInfo": "{itemFirst}-{itemLast} of {numberOfItems}", "common.pagerInfo": "{itemFirst}-{itemLast} of {numberOfItems}",
"common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?",
"common.pagerReload": "Click to reload view and get total number of items", "common.pagerReload": "Click to reload view and get total number of items",
"common.pageSize": "Page Size",
"common.password": "Password", "common.password": "Password",
"common.passwordConfirm": "Confirm Password", "common.passwordConfirm": "Confirm Password",
"common.pattern": "Pattern", "common.pattern": "Pattern",
@ -328,6 +334,7 @@
"common.permissions": "Permissions", "common.permissions": "Permissions",
"common.prev": "Back", "common.prev": "Back",
"common.preview": "Preview", "common.preview": "Preview",
"common.prevPage": "Previous Page",
"common.product": "Squidex Headless CMS", "common.product": "Squidex Headless CMS",
"common.project": "Project", "common.project": "Project",
"common.queryOperators.contains": "contains", "common.queryOperators.contains": "contains",
@ -359,6 +366,9 @@
"common.rules": "Rules", "common.rules": "Rules",
"common.sampleCodeLabel": "Sample Code at", "common.sampleCodeLabel": "Sample Code at",
"common.save": "Save", "common.save": "Save",
"common.saveAdd": "Save & add another",
"common.saveClose": "Save & close",
"common.saveEdit": "Save & editor",
"common.schema": "Schema", "common.schema": "Schema",
"common.schemas": "Schemas", "common.schemas": "Schemas",
"common.search": "Search", "common.search": "Search",
@ -480,6 +490,8 @@
"contents.removeConfirmText": "Do you really want to remove the content?", "contents.removeConfirmText": "Do you really want to remove the content?",
"contents.removeConfirmTitle": "Remove content", "contents.removeConfirmTitle": "Remove content",
"contents.saveAndPublish": "Save and Publish", "contents.saveAndPublish": "Save and Publish",
"contents.saveAndPublishAdd": "Save and Publish & add another",
"contents.saveAndPublishClose": "Save and Publish & close",
"contents.scheduledAt": "at", "contents.scheduledAt": "at",
"contents.scheduledBy": "by", "contents.scheduledBy": "by",
"contents.scheduledTo": "to", "contents.scheduledTo": "to",
@ -785,9 +797,9 @@
"rules.updateFailed": "Failed to update rule. Please reload.", "rules.updateFailed": "Failed to update rule. Please reload.",
"rules.when": "When", "rules.when": "When",
"schemas.addField": "Add Field", "schemas.addField": "Add Field",
"schemas.addFieldAndClose": "Create and close", "schemas.addFieldAndClose": "Create",
"schemas.addFieldAndCreate": "Create and add field", "schemas.addFieldAndCreate": "Create & add another",
"schemas.addFieldAndEdit": "Create and edit field", "schemas.addFieldAndEdit": "Create & edit properties",
"schemas.addFieldButton": "Add Field", "schemas.addFieldButton": "Add Field",
"schemas.addFieldFailed": "Failed to add field. Please reload.", "schemas.addFieldFailed": "Failed to add field. Please reload.",
"schemas.addNestedField": "Add Nested Field", "schemas.addNestedField": "Add Nested Field",

2
backend/src/Migrations/Migrations.csproj

@ -6,7 +6,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

10
backend/src/Squidex.Domain.Apps.Core.Model/Assets/Asset.cs

@ -53,27 +53,27 @@ public record Asset : AssetItem
if (fileName != null && !string.Equals(FileName, fileName, StringComparison.OrdinalIgnoreCase)) if (fileName != null && !string.Equals(FileName, fileName, StringComparison.OrdinalIgnoreCase))
{ {
result = this with { FileName = fileName }; result = result with { FileName = fileName };
} }
if (slug != null && !string.Equals(Slug, slug, StringComparison.OrdinalIgnoreCase)) if (slug != null && !string.Equals(Slug, slug, StringComparison.OrdinalIgnoreCase))
{ {
result = this with { Slug = slug }; result = result with { Slug = slug };
} }
if (isProtected != null && IsProtected != isProtected.Value) if (isProtected != null && IsProtected != isProtected.Value)
{ {
result = this with { IsProtected = isProtected.Value }; result = result with { IsProtected = isProtected.Value };
} }
if (tags != null && !Tags.SetEquals(tags)) if (tags != null && !Tags.SetEquals(tags))
{ {
result = this with { Tags = tags }; result = result with { Tags = tags };
} }
if (metadata != null && !Metadata.EqualsDictionary(metadata)) if (metadata != null && !Metadata.EqualsDictionary(metadata))
{ {
result = this with { Metadata = metadata }; result = result with { Metadata = metadata };
} }
return result; return result;

6
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/ContentFieldDataConverter.cs

@ -27,7 +27,8 @@ public sealed class ContentFieldDataConverter : JsonConverter<ContentFieldData>
if (!reader.Read()) if (!reader.Read())
{ {
throw new JsonException("Unexpected end when reading Object."); ThrowHelper.JsonSystemException("Unexpected end when reading Object.");
return result;
} }
var value = JsonSerializer.Deserialize<JsonValue>(ref reader, options)!; var value = JsonSerializer.Deserialize<JsonValue>(ref reader, options)!;
@ -48,7 +49,8 @@ public sealed class ContentFieldDataConverter : JsonConverter<ContentFieldData>
} }
} }
throw new JsonException("Unexpected end when reading Object."); ThrowHelper.JsonSystemException("Unexpected end when reading Object.");
return result;
} }
public override void Write(Utf8JsonWriter writer, ContentFieldData value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, ContentFieldData value, JsonSerializerOptions options)

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

@ -32,7 +32,7 @@ public sealed class FieldNames : ReadonlyList<string>
"translationStatusAverage" "translationStatusAverage"
]; ];
public static readonly new FieldNames Empty = new FieldNames(new List<string>()); public static readonly new FieldNames Empty = new FieldNames([]);
private FieldNames() private FieldNames()
{ {

3
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldEditor.cs

@ -13,5 +13,6 @@ public enum ReferencesFieldEditor
Dropdown, Dropdown,
Tags, Tags,
Checkboxes, Checkboxes,
Input Input,
Radio
} }

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

@ -12,7 +12,7 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

8
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/Parser.cs → backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/CacheParser.cs

@ -5,26 +5,26 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Esprima.Ast; using Acornima.Ast;
using Jint; using Jint;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
namespace Squidex.Domain.Apps.Core.Scripting.Internal; namespace Squidex.Domain.Apps.Core.Scripting.Internal;
internal sealed class Parser internal sealed class CacheParser
{ {
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10); private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10);
private readonly IMemoryCache cache; private readonly IMemoryCache cache;
public Parser(IMemoryCache cache) public CacheParser(IMemoryCache cache)
{ {
this.cache = cache; this.cache = cache;
} }
public Prepared<Script> Parse(string script) public Prepared<Script> Parse(string script)
{ {
var cacheKey = $"{typeof(Parser)}_Script_{script}"; var cacheKey = $"{typeof(CacheParser)}_Script_{script}";
return cache.GetOrCreate(cacheKey, entry => return cache.GetOrCreate(cacheKey, entry =>
{ {

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

@ -6,7 +6,7 @@
// ========================================================================== // ==========================================================================
using System.Diagnostics; using System.Diagnostics;
using Esprima; using Acornima;
using Jint; using Jint;
using Jint.Native; using Jint.Native;
using Jint.Runtime; using Jint.Runtime;
@ -26,13 +26,13 @@ namespace Squidex.Domain.Apps.Core.Scripting;
public sealed class JintScriptEngine : IScriptEngine, IScriptDescriptor public sealed class JintScriptEngine : IScriptEngine, IScriptDescriptor
{ {
private readonly IJintExtension[] extensions; private readonly IJintExtension[] extensions;
private readonly Parser parser; private readonly CacheParser parser;
private readonly TimeSpan timeoutScript; private readonly TimeSpan timeoutScript;
private readonly TimeSpan timeoutExecution; private readonly TimeSpan timeoutExecution;
public JintScriptEngine(IMemoryCache cache, IOptions<JintScriptOptions> options, IEnumerable<IJintExtension>? extensions = null) public JintScriptEngine(IMemoryCache cache, IOptions<JintScriptOptions> options, IEnumerable<IJintExtension>? extensions = null)
{ {
parser = new Parser(cache); parser = new CacheParser(cache);
timeoutScript = options.Value.TimeoutScript; timeoutScript = options.Value.TimeoutScript;
timeoutExecution = options.Value.TimeoutExecution; timeoutExecution = options.Value.TimeoutExecution;
@ -203,7 +203,7 @@ public sealed class JintScriptEngine : IScriptEngine, IScriptDescriptor
return BuildException("common.jsParseError", inner.Message); return BuildException("common.jsParseError", inner.Message);
case JavaScriptException: case JavaScriptException:
return BuildException("common.jsError", inner.Message); return BuildException("common.jsError", inner.Message);
case ParserException: case ParseErrorException:
return BuildException("common.jsError", inner.Message); return BuildException("common.jsError", inner.Message);
case DomainException: case DomainException:
return inner; return inner;

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

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Esprima.Ast; using Acornima.Ast;
using Jint; using Jint;
using Jint.Native; using Jint.Native;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;

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

@ -18,18 +18,18 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" /> <ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Fluid.Core" Version="2.10.0" /> <PackageReference Include="Fluid.Core" Version="2.11.1" />
<PackageReference Include="GeoJSON.Net" Version="1.2.19" /> <PackageReference Include="GeoJSON.Net" Version="1.4.1" />
<PackageReference Include="Jint" Version="3.1.4" /> <PackageReference Include="Jint" Version="4.0.2" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="NJsonSchema" Version="11.0.1" /> <PackageReference Include="NJsonSchema" Version="11.0.2" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.AI" Version="6.18.0" /> <PackageReference Include="Squidex.AI" Version="6.19.0" />
<PackageReference Include="Squidex.Messaging.Subscriptions" Version="6.18.0" /> <PackageReference Include="Squidex.Messaging.Subscriptions" Version="6.19.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="8.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" /> <PackageReference Include="System.Linq.Async" Version="6.0.1" />

7
backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/JsonArrayFluidValue.cs

@ -98,6 +98,13 @@ public sealed class JsonArrayFluidValue : FluidValue
} }
} }
public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
writer.Write(value);
return default;
}
[Obsolete("Made obsolete in base library.")]
public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{ {
writer.Write(value); writer.Write(value);

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs

@ -28,8 +28,8 @@ public sealed class MongoAppRepository : MongoSnapshotStoreBase<App, MongoAppEnt
protected override Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection, protected override Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection,
CancellationToken ct) CancellationToken ct)
{ {
return collection.Indexes.CreateManyAsync(new[] return collection.Indexes.CreateManyAsync(
{ [
new CreateIndexModel<MongoAppEntity>( new CreateIndexModel<MongoAppEntity>(
Index Index
.Ascending(x => x.IndexedName)), .Ascending(x => x.IndexedName)),
@ -39,7 +39,7 @@ public sealed class MongoAppRepository : MongoSnapshotStoreBase<App, MongoAppEnt
new CreateIndexModel<MongoAppEntity>( new CreateIndexModel<MongoAppEntity>(
Index Index
.Ascending(x => x.IndexedTeamId)) .Ascending(x => x.IndexedTeamId))
}, ct); ], ct);
} }
Task IDeleter.DeleteAppAsync(App app, Task IDeleter.DeleteAppAsync(App app,

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs

@ -33,14 +33,14 @@ public sealed partial class MongoAssetFolderRepository : MongoRepositoryBase<Mon
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetFolderEntity> collection, protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetFolderEntity> collection,
CancellationToken ct) CancellationToken ct)
{ {
return collection.Indexes.CreateManyAsync(new[] return collection.Indexes.CreateManyAsync(
{ [
new CreateIndexModel<MongoAssetFolderEntity>( new CreateIndexModel<MongoAssetFolderEntity>(
Index Index
.Ascending(x => x.IndexedAppId) .Ascending(x => x.IndexedAppId)
.Ascending(x => x.ParentId) .Ascending(x => x.ParentId)
.Ascending(x => x.IsDeleted)) .Ascending(x => x.IsDeleted))
}, ct); ], ct);
} }
public async Task<IResultList<AssetFolder>> QueryAsync(DomainId appId, DomainId? parentId, public async Task<IResultList<AssetFolder>> QueryAsync(DomainId appId, DomainId? parentId,

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -49,8 +49,8 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection, protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection,
CancellationToken ct) CancellationToken ct)
{ {
return collection.Indexes.CreateManyAsync(new[] return collection.Indexes.CreateManyAsync(
{ [
new CreateIndexModel<MongoAssetEntity>( new CreateIndexModel<MongoAssetEntity>(
Index Index
.Descending(x => x.LastModified) .Descending(x => x.LastModified)
@ -71,7 +71,7 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
Index Index
.Ascending(x => x.Id) .Ascending(x => x.Id)
.Ascending(x => x.IsDeleted)) .Ascending(x => x.IsDeleted))
}, ct); ], ct);
} }
public async IAsyncEnumerable<Asset> StreamAll(DomainId appId, public async IAsyncEnumerable<Asset> StreamAll(DomainId appId,

42
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Extensions.cs

@ -129,7 +129,7 @@ public static class Extensions
PipelineDefinitionBuilder.For<MongoContentEntity>() PipelineDefinitionBuilder.For<MongoContentEntity>()
.Match(LookupMatch) .Match(LookupMatch)
.Project( .Project(
BuildProjection2<MongoContentEntity>(q.Fields)), BuildProjection<MongoContentEntity>(q.Fields)),
x => x.Joined) x => x.Joined)
.Project<IdOnly>( .Project<IdOnly>(
Builders<IdOnly>.Projection.Include(x => x.Joined)) Builders<IdOnly>.Projection.Include(x => x.Joined))
@ -165,15 +165,15 @@ public static class Extensions
public static IFindFluent<T, T> SelectFields<T>(this IFindFluent<T, T> find, IEnumerable<string>? fields) public static IFindFluent<T, T> SelectFields<T>(this IFindFluent<T, T> find, IEnumerable<string>? fields)
{ {
return find.Project<T>(BuildProjection2<T>(fields)); return find.Project<T>(BuildProjection<T>(fields));
} }
public static IAggregateFluent<T> SelectFields<T>(this IAggregateFluent<T> find, IEnumerable<string>? fields) public static IAggregateFluent<T> SelectFields<T>(this IAggregateFluent<T> find, IEnumerable<string>? fields)
{ {
return find.Project<T>(BuildProjection2<T>(fields)); return find.Project<T>(BuildProjection<T>(fields));
} }
public static ProjectionDefinition<T, T> BuildProjection2<T>(IEnumerable<string>? fields) public static ProjectionDefinition<T, T> BuildProjection<T>(IEnumerable<string>? fields)
{ {
var projector = Builders<T>.Projection; var projector = Builders<T>.Projection;
var projections = new List<ProjectionDefinition<T>>(); var projections = new List<ProjectionDefinition<T>>();
@ -186,30 +186,38 @@ public static class Extensions
foreach (var field in fields) foreach (var field in fields)
{ {
var dataField = var actualFieldName = field;
FieldNames.IsDataField(field, out var fieldName) ? // Only add data fields, because we add all meta fields anyway.
fieldName : if (FieldNames.IsDataField(field, out var dataField))
field; {
actualFieldName = dataField;
yield return $"{dataPrefix}.{dataField}"; }
var fullName = $"{dataPrefix}.{actualFieldName}";
if (!MetaFields.ContainsKey(fullName))
{
yield return fullName;
}
} }
} }
var addedFields = new List<string>(); var addedFields = new List<string>();
// Sort the fields to start with prefixes first. // Sort the fields to start with prefixes first.
var allFields = GetDataFields(fields).Union(MetaFields.Values).OrderBy(x => x); var allFields = GetDataFields(fields).Union(MetaFields.Values).Order();
foreach (var field in allFields) foreach (var field in allFields)
{ {
// If there is at least one field that is a prefix of the current field, we cannot add that. // If there is at least one field that is a prefix of the current field, we cannot add that.
if (addedFields.Exists(x => field.StartsWith(x, StringComparison.OrdinalIgnoreCase))) if (addedFields.Exists(x => IsPrefix(field, x)))
{ {
continue; continue;
} }
projections.Add(projector.Include(field)); projections.Add(projector.Include(field));
// Track added prefixes.
addedFields.Add(field); addedFields.Add(field);
} }
} }
@ -219,5 +227,15 @@ public static class Extensions
} }
return projector.Combine(projections); return projector.Combine(projections);
static bool IsPrefix(string field, string prefix)
{
if (!field.StartsWith(prefix, StringComparison.Ordinal))
{
return false;
}
return field.Length == prefix.Length || field[prefix.Length] == '.';
}
} }
} }

5
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs

@ -45,8 +45,7 @@ internal sealed class QueryInDedicatedCollection : MongoBase<MongoContentEntity>
var schemaCollection = schemaDatabase.GetCollection<MongoContentEntity>($"{prefixCollection}_{schemaId}"); var schemaCollection = schemaDatabase.GetCollection<MongoContentEntity>($"{prefixCollection}_{schemaId}");
await schemaCollection.Indexes.CreateManyAsync( await schemaCollection.Indexes.CreateManyAsync(
new[] [
{
new CreateIndexModel<MongoContentEntity>(Index new CreateIndexModel<MongoContentEntity>(Index
.Descending(x => x.LastModified) .Descending(x => x.LastModified)
.Ascending(x => x.Id) .Ascending(x => x.Id)
@ -56,7 +55,7 @@ internal sealed class QueryInDedicatedCollection : MongoBase<MongoContentEntity>
.Ascending(x => x.IndexedSchemaId) .Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted) .Ascending(x => x.IsDeleted)
.Descending(x => x.LastModified)) .Descending(x => x.LastModified))
}); ]);
return schemaCollection; return schemaCollection;
}); });

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs

@ -44,8 +44,8 @@ public sealed class MongoHistoryEventRepository : MongoRepositoryBase<HistoryEve
protected override Task SetupCollectionAsync(IMongoCollection<HistoryEvent> collection, protected override Task SetupCollectionAsync(IMongoCollection<HistoryEvent> collection,
CancellationToken ct) CancellationToken ct)
{ {
return collection.Indexes.CreateManyAsync(new[] return collection.Indexes.CreateManyAsync(
{ [
new CreateIndexModel<HistoryEvent>( new CreateIndexModel<HistoryEvent>(
Index Index
.Ascending(x => x.OwnerId) .Ascending(x => x.OwnerId)
@ -57,7 +57,7 @@ public sealed class MongoHistoryEventRepository : MongoRepositoryBase<HistoryEve
.Ascending(x => x.OwnerId) .Ascending(x => x.OwnerId)
.Descending(x => x.Created) .Descending(x => x.Created)
.Descending(x => x.Version)) .Descending(x => x.Version))
}, ct); ], ct);
} }
async Task IDeleter.DeleteAppAsync(App app, async Task IDeleter.DeleteAppAsync(App app,

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/MongoCountCollection.cs

@ -78,9 +78,9 @@ internal sealed class MongoCountCollection : MongoRepositoryBase<MongoCountEntit
await Collection.UpdateOneAsync(x => x.Key == key, await Collection.UpdateOneAsync(x => x.Key == key,
Update Update
.Set(x => x.Key, key) .SetOnInsert(x => x.Key, key)
.SetOnInsert(x => x.Count, actualCount) .Set(x => x.Count, actualCount)
.SetOnInsert(x => x.Created, now), .Set(x => x.Created, now),
Upsert, ct); Upsert, ct);
return actualCount; return actualCount;

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs

@ -31,8 +31,8 @@ public sealed class MongoRuleEventRepository : MongoRepositoryBase<MongoRuleEven
protected override async Task SetupCollectionAsync(IMongoCollection<MongoRuleEventEntity> collection, protected override async Task SetupCollectionAsync(IMongoCollection<MongoRuleEventEntity> collection,
CancellationToken ct) CancellationToken ct)
{ {
await collection.Indexes.CreateManyAsync(new[] await collection.Indexes.CreateManyAsync(
{ [
new CreateIndexModel<MongoRuleEventEntity>( new CreateIndexModel<MongoRuleEventEntity>(
Index.Ascending(x => x.NextAttempt)), Index.Ascending(x => x.NextAttempt)),
@ -46,7 +46,7 @@ public sealed class MongoRuleEventRepository : MongoRepositoryBase<MongoRuleEven
{ {
ExpireAfter = TimeSpan.Zero ExpireAfter = TimeSpan.Zero
}) })
}, ct); ], ct);
} }
async Task IDeleter.DeleteAppAsync(App app, async Task IDeleter.DeleteAppAsync(App app,

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs

@ -29,12 +29,12 @@ public sealed class MongoRuleRepository : MongoSnapshotStoreBase<Rule, MongoRule
protected override Task SetupCollectionAsync(IMongoCollection<MongoRuleEntity> collection, protected override Task SetupCollectionAsync(IMongoCollection<MongoRuleEntity> collection,
CancellationToken ct) CancellationToken ct)
{ {
return collection.Indexes.CreateManyAsync(new[] return collection.Indexes.CreateManyAsync(
{ [
new CreateIndexModel<MongoRuleEntity>( new CreateIndexModel<MongoRuleEntity>(
Index Index
.Ascending(x => x.IndexedAppId)) .Ascending(x => x.IndexedAppId))
}, ct); ], ct);
} }
Task IDeleter.DeleteAppAsync(App app, Task IDeleter.DeleteAppAsync(App app,

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs

@ -29,13 +29,13 @@ public sealed class MongoSchemaRepository : MongoSnapshotStoreBase<Schema, Mongo
protected override Task SetupCollectionAsync(IMongoCollection<MongoSchemaEntity> collection, protected override Task SetupCollectionAsync(IMongoCollection<MongoSchemaEntity> collection,
CancellationToken ct) CancellationToken ct)
{ {
return collection.Indexes.CreateManyAsync(new[] return collection.Indexes.CreateManyAsync(
{ [
new CreateIndexModel<MongoSchemaEntity>( new CreateIndexModel<MongoSchemaEntity>(
Index Index
.Ascending(x => x.IndexedAppId) .Ascending(x => x.IndexedAppId)
.Ascending(x => x.IndexedName)) .Ascending(x => x.IndexedName))
}, ct); ], ct);
} }
Task IDeleter.DeleteAppAsync(App app, Task IDeleter.DeleteAppAsync(App app,

4
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj

@ -19,11 +19,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MongoDB.Driver" Version="2.27.0" /> <PackageReference Include="MongoDB.Driver" Version="2.28.0" />
<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.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamRepository.cs

@ -28,12 +28,12 @@ public sealed class MongoTeamRepository : MongoSnapshotStoreBase<Team, MongoTeam
protected override Task SetupCollectionAsync(IMongoCollection<MongoTeamEntity> collection, protected override Task SetupCollectionAsync(IMongoCollection<MongoTeamEntity> collection,
CancellationToken ct) CancellationToken ct)
{ {
return collection.Indexes.CreateManyAsync(new[] return collection.Indexes.CreateManyAsync(
{ [
new CreateIndexModel<MongoTeamEntity>( new CreateIndexModel<MongoTeamEntity>(
Index Index
.Ascending(x => x.IndexedUserIds)) .Ascending(x => x.IndexedUserIds))
}, ct); ], ct);
} }
public async Task<List<Team>> QueryAllAsync(string contributorId, public async Task<List<Team>> QueryAllAsync(string contributorId,

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs

@ -46,8 +46,8 @@ public abstract class MongoTextIndexBase<T> : MongoRepositoryBase<MongoTextIndex
protected override Task SetupCollectionAsync(IMongoCollection<MongoTextIndexEntity<T>> collection, protected override Task SetupCollectionAsync(IMongoCollection<MongoTextIndexEntity<T>> collection,
CancellationToken ct) CancellationToken ct)
{ {
return collection.Indexes.CreateManyAsync(new[] return collection.Indexes.CreateManyAsync(
{ [
new CreateIndexModel<MongoTextIndexEntity<T>>( new CreateIndexModel<MongoTextIndexEntity<T>>(
Index Index
.Ascending(x => x.AppId) .Ascending(x => x.AppId)
@ -59,7 +59,7 @@ public abstract class MongoTextIndexBase<T> : MongoRepositoryBase<MongoTextIndex
.Ascending(x => x.SchemaId) .Ascending(x => x.SchemaId)
.Ascending(x => x.GeoField) .Ascending(x => x.GeoField)
.Geo2DSphere(x => x.GeoObject)) .Geo2DSphere(x => x.GeoObject))
}, ct); ], ct);
} }
protected override string CollectionName() protected override string CollectionName()

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

@ -227,11 +227,11 @@ public sealed class AppsIndex : IAppsIndex, ICommandMiddleware, IInitializable
} }
// Do not use cancellation here as we already so far. // Do not use cancellation here as we already so far.
await appCache.AddAsync(new[] await appCache.AddAsync(
{ [
new KeyValuePair<string, object?>(GetCacheKey(app.Id), app), new KeyValuePair<string, object?>(GetCacheKey(app.Id), app),
new KeyValuePair<string, object?>(GetCacheKey(app.Name), app), new KeyValuePair<string, object?>(GetCacheKey(app.Name), app),
}, options.CacheDuration); ], options.CacheDuration);
return app; return app;
} }
@ -244,10 +244,10 @@ public sealed class AppsIndex : IAppsIndex, ICommandMiddleware, IInitializable
} }
// Do not use cancellation here as we already so far. // Do not use cancellation here as we already so far.
return appCache.RemoveAsync(new[] return appCache.RemoveAsync(
{ [
GetCacheKey(id), GetCacheKey(id),
GetCacheKey(name) GetCacheKey(name)
}); ]);
} }
} }

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

@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Entities.Assets;
public static class AssetSlug public static class AssetSlug
{ {
private static readonly HashSet<char> Dot = new HashSet<char>(new[] { '.' }); private static readonly HashSet<char> Dot = new HashSet<char>(['.']);
public static string ToAssetSlug(this string value) public static string ToAssetSlug(this string value)
{ {

34
backend/src/Squidex.Domain.Apps.Entities/Assets/SvgAssetMetadataSource.cs

@ -52,11 +52,28 @@ public sealed class SvgAssetMetadataSource : IAssetMetadataSource
if (!string.IsNullOrWhiteSpace(width) && !string.IsNullOrWhiteSpace(height)) if (!string.IsNullOrWhiteSpace(width) && !string.IsNullOrWhiteSpace(height))
{ {
command.Metadata[KnownMetadataKeys.SvgWidth] = width; var hasNumericWidth = TryParseInt(width, out var w);
command.Metadata[KnownMetadataKeys.SvgHeight] = height; var hasNumericHeight = TryParseInt(height, out var h);
if (int.TryParse(width, NumberStyles.Integer, CultureInfo.InvariantCulture, out var w) && if (hasNumericWidth)
int.TryParse(height, NumberStyles.Integer, CultureInfo.InvariantCulture, out var h)) {
command.Metadata[KnownMetadataKeys.SvgWidth] = w;
}
else
{
command.Metadata[KnownMetadataKeys.SvgWidth] = width;
}
if (hasNumericWidth)
{
command.Metadata[KnownMetadataKeys.SvgHeight] = h;
}
else
{
command.Metadata[KnownMetadataKeys.SvgHeight] = height;
}
if (hasNumericWidth && hasNumericHeight)
{ {
command.Metadata[KnownMetadataKeys.PixelWidth] = w; command.Metadata[KnownMetadataKeys.PixelWidth] = w;
command.Metadata[KnownMetadataKeys.PixelHeight] = h; command.Metadata[KnownMetadataKeys.PixelHeight] = h;
@ -79,6 +96,11 @@ public sealed class SvgAssetMetadataSource : IAssetMetadataSource
} }
} }
private static bool TryParseInt(string value, out int result)
{
return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
}
public IEnumerable<string> Format(Asset asset) public IEnumerable<string> Format(Asset asset)
{ {
var isSvg = var isSvg =
@ -90,8 +112,8 @@ public sealed class SvgAssetMetadataSource : IAssetMetadataSource
yield break; yield break;
} }
if (asset.Metadata.TryGetString(KnownMetadataKeys.SvgWidth, out var w) && if (asset.Metadata.TryGetValue(KnownMetadataKeys.SvgWidth, out var w) &&
asset.Metadata.TryGetString(KnownMetadataKeys.SvgHeight, out var h)) asset.Metadata.TryGetValue(KnownMetadataKeys.SvgHeight, out var h))
{ {
yield return $"{w}x{h}"; yield return $"{w}x{h}";
} }

2
backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/BulkUpdateContents.cs

@ -29,5 +29,7 @@ public sealed class BulkUpdateContents : SquidexCommand, IAppCommand, ISchemaCom
public bool OptimizeValidation { get; set; } public bool OptimizeValidation { get; set; }
public bool EnrichRequiredFields { get; set; }
public BulkUpdateJob[]? Jobs { get; set; } public BulkUpdateJob[]? Jobs { get; set; }
} }

1
backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/EnrichContentDefaults.cs

@ -9,4 +9,5 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands;
public sealed class EnrichContentDefaults : ContentCommand public sealed class EnrichContentDefaults : ContentCommand
{ {
public bool EnrichRequiredFields { get; set; }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/UpsertContent.cs

@ -21,6 +21,8 @@ public sealed class UpsertContent : ContentDataCommand, ISchemaCommand
public bool EnrichDefaults { get; set; } public bool EnrichDefaults { get; set; }
public bool EnrichRequiredFields { get; set; }
public UpsertContent() public UpsertContent()
{ {
ContentId = DomainId.NewGuid(); ContentId = DomainId.NewGuid();

12
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs

@ -19,6 +19,7 @@ public static class ContentHeaders
public const string KeyLanguages = "X-Languages"; public const string KeyLanguages = "X-Languages";
public const string KeyNoCleanup = "X-NoCleanup"; public const string KeyNoCleanup = "X-NoCleanup";
public const string KeyNoEnrichment = "X-NoEnrichment"; public const string KeyNoEnrichment = "X-NoEnrichment";
public const string KeyNoDefaults = "X-NoDefaults";
public const string KeyNoResolveLanguages = "X-NoResolveLanguages"; public const string KeyNoResolveLanguages = "X-NoResolveLanguages";
public const string KeyResolveFlow = "X-ResolveFlow"; public const string KeyResolveFlow = "X-ResolveFlow";
public const string KeyResolveUrls = "X-ResolveUrls"; public const string KeyResolveUrls = "X-ResolveUrls";
@ -32,6 +33,7 @@ public static class ContentHeaders
cache.AddHeader(KeyLanguages); cache.AddHeader(KeyLanguages);
cache.AddHeader(KeyNoCleanup); cache.AddHeader(KeyNoCleanup);
cache.AddHeader(KeyNoEnrichment); cache.AddHeader(KeyNoEnrichment);
cache.AddHeader(KeyNoDefaults);
cache.AddHeader(KeyNoResolveLanguages); cache.AddHeader(KeyNoResolveLanguages);
cache.AddHeader(KeyResolveFlow); cache.AddHeader(KeyResolveFlow);
cache.AddHeader(KeyResolveUrls); cache.AddHeader(KeyResolveUrls);
@ -63,6 +65,16 @@ public static class ContentHeaders
return builder.WithBoolean(KeyNoEnrichment, value); return builder.WithBoolean(KeyNoEnrichment, value);
} }
public static bool NoDefaults(this Context context)
{
return context.AsBoolean(KeyNoDefaults);
}
public static ICloneBuilder WithNoDefaults(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(KeyNoDefaults, value);
}
public static bool Unpublished(this Context context) public static bool Unpublished(this Context context)
{ {
return context.AsBoolean(KeyUnpublished); return context.AsBoolean(KeyUnpublished);

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

@ -211,7 +211,7 @@ public partial class ContentDomainObject : DomainObject<WriteContent>
{ {
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
var newData = operation.GenerateDefaultValues(Snapshot.EditingData.Clone()); var newData = operation.GenerateDefaultValues(Snapshot.EditingData.Clone(), !c.EnrichRequiredFields);
if (!newData.Equals(Snapshot.EditingData)) if (!newData.Equals(Snapshot.EditingData))
{ {
@ -263,7 +263,7 @@ public partial class ContentDomainObject : DomainObject<WriteContent>
c.Data = await operation.ExecuteCreateScriptAsync(c.Data, status, ct); c.Data = await operation.ExecuteCreateScriptAsync(c.Data, status, ct);
} }
c.Data = operation.GenerateDefaultValues(c.Data); c.Data = operation.GenerateDefaultValues(c.Data, false);
if (!c.DoNotValidate) if (!c.DoNotValidate)
{ {
@ -336,7 +336,7 @@ public partial class ContentDomainObject : DomainObject<WriteContent>
if (c.EnrichDefaults) if (c.EnrichDefaults)
{ {
newData = operation.GenerateDefaultValues(newData); newData = operation.GenerateDefaultValues(newData, true);
} }
if (newData.Equals(Snapshot.EditingData)) if (newData.Equals(Snapshot.EditingData))

20
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ValidationExtensions.cs

@ -47,7 +47,8 @@ public static class ValidationExtensions
operation.ThrowOnErrors(); operation.ThrowOnErrors();
} }
public static async Task ValidateInputAsync(this ContentOperation operation, ContentData data, bool optimize, bool published, public static async Task ValidateInputAsync(this ContentOperation operation,
ContentData data, bool optimize, bool published,
CancellationToken ct) CancellationToken ct)
{ {
var validator = GetValidator(operation, optimize, published); var validator = GetValidator(operation, optimize, published);
@ -57,7 +58,8 @@ public static class ValidationExtensions
operation.AddErrors(validator.Errors).ThrowOnErrors(); operation.AddErrors(validator.Errors).ThrowOnErrors();
} }
public static async Task ValidateInputPartialAsync(this ContentOperation operation, ContentData data, bool optimize, bool published, public static async Task ValidateInputPartialAsync(this ContentOperation operation,
ContentData data, bool optimize, bool published,
CancellationToken ct) CancellationToken ct)
{ {
var validator = GetValidator(operation, optimize, published); var validator = GetValidator(operation, optimize, published);
@ -67,7 +69,8 @@ public static class ValidationExtensions
operation.AddErrors(validator.Errors).ThrowOnErrors(); operation.AddErrors(validator.Errors).ThrowOnErrors();
} }
public static async Task ValidateContentAsync(this ContentOperation operation, ContentData data, bool optimize, bool published, public static async Task ValidateContentAsync(this ContentOperation operation,
ContentData data, bool optimize, bool published,
CancellationToken ct) CancellationToken ct)
{ {
var validator = GetValidator(operation, optimize, published); var validator = GetValidator(operation, optimize, published);
@ -77,7 +80,8 @@ public static class ValidationExtensions
operation.AddErrors(validator.Errors).ThrowOnErrors(); operation.AddErrors(validator.Errors).ThrowOnErrors();
} }
public static async Task ValidateContentAndInputAsync(this ContentOperation operation, ContentData data, bool optimize, bool published, public static async Task ValidateContentAndInputAsync(this ContentOperation operation,
ContentData data, bool optimize, bool published,
CancellationToken ct) CancellationToken ct)
{ {
var validator = GetValidator(operation, optimize, published); var validator = GetValidator(operation, optimize, published);
@ -87,18 +91,20 @@ public static class ValidationExtensions
operation.AddErrors(validator.Errors).ThrowOnErrors(); operation.AddErrors(validator.Errors).ThrowOnErrors();
} }
public static ContentData GenerateDefaultValues(this ContentOperation operation, ContentData data) public static ContentData GenerateDefaultValues(this ContentOperation operation,
ContentData data, bool ignoreRequired)
{ {
var converter = var converter =
new ContentConverter( new ContentConverter(
operation.Components, operation.Components,
operation.Schema); operation.Schema);
converter.Add(new AddDefaultValues(operation.Partition()) { IgnoreRequiredFields = true }); converter.Add(new AddDefaultValues(operation.Partition()) { IgnoreRequiredFields = ignoreRequired });
return converter.Convert(data); return converter.Convert(data);
} }
public static ContentData InvokeUpdates(this ContentOperation operation, ContentData data, ContentData currentData, bool canUnset) public static ContentData InvokeUpdates(this ContentOperation operation, ContentData data,
ContentData currentData, bool canUnset)
{ {
var converter = var converter =
new ContentConverter( new ContentConverter(

6
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs

@ -5,12 +5,14 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using GraphQL;
using GraphQL.DataLoader; using GraphQL.DataLoader;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Cache; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Cache;
using Squidex.Domain.Apps.Entities.Contents.Queries; using Squidex.Domain.Apps.Entities.Contents.Queries;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Shared;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL;
@ -27,6 +29,8 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext
public override Context Context { get; } public override Context Context { get; }
public bool CanExposePII { get; }
public GraphQLExecutionContext( public GraphQLExecutionContext(
IDataLoaderContextAccessor dataLoaders, IDataLoaderContextAccessor dataLoaders,
IAssetQueryService assetQuery, IAssetQueryService assetQuery,
@ -57,6 +61,8 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext
{ {
batchSize = Math.Max(MinBatchSize, Math.Min(MaxBatchSize, batchSize)); batchSize = Math.Max(MinBatchSize, Math.Min(MaxBatchSize, batchSize));
} }
CanExposePII = Context.UserPermissions.Allows(PermissionIds.ForApp(PermissionIds.AppPii, context.App.Name));
} }
public async ValueTask<IUser?> FindUserAsync(RefToken refToken, public async ValueTask<IUser?> FindUserAsync(RefToken refToken,

17
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/UserGraphType.cs

@ -35,7 +35,7 @@ internal sealed class UserGraphType : SharedObjectGraphType<IUser>
AddField(new FieldType AddField(new FieldType
{ {
Name = "displayName", Name = "displayName",
Resolver = Resolve(x => x.Claims.DisplayName()), Resolver = ResolveOrHide(x => x.Claims.DisplayName()),
ResolvedType = Scalars.String, ResolvedType = Scalars.String,
Description = FieldDescriptions.UserDisplayName Description = FieldDescriptions.UserDisplayName
}); });
@ -43,7 +43,7 @@ internal sealed class UserGraphType : SharedObjectGraphType<IUser>
AddField(new FieldType AddField(new FieldType
{ {
Name = "email", Name = "email",
Resolver = Resolve(x => x.Email), Resolver = ResolveOrHide(x => x.Email),
ResolvedType = Scalars.String, ResolvedType = Scalars.String,
Description = FieldDescriptions.UserEmail Description = FieldDescriptions.UserEmail
}); });
@ -55,4 +55,17 @@ internal sealed class UserGraphType : SharedObjectGraphType<IUser>
{ {
return Resolvers.Sync(resolver); return Resolvers.Sync(resolver);
} }
private static IFieldResolver ResolveOrHide(Func<IUser, string?> resolver)
{
return Resolvers.Sync<IUser, string?>((source, _, context) =>
{
if (context.CanExposePII)
{
return resolver(source);
}
return "Hidden";
});
}
} }

9
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs

@ -67,6 +67,7 @@ public sealed class ContentEnricher : IContentEnricher
{ {
var result = SimpleMapper.Map(content, new EnrichedContent()); var result = SimpleMapper.Map(content, new EnrichedContent());
// Clone the data to keep the existing value intact (for example when cached in memory).
if (cloneData) if (cloneData)
{ {
using (Telemetry.Activities.StartActivity("ContentEnricher/CloneData")) using (Telemetry.Activities.StartActivity("ContentEnricher/CloneData"))
@ -86,12 +87,8 @@ public sealed class ContentEnricher : IContentEnricher
{ {
return schemaCache.GetOrAdd(id, async x => return schemaCache.GetOrAdd(id, async x =>
{ {
var schema = await appProvider.GetSchemaAsync(context.App.Id, x, false, ct); var schema = await appProvider.GetSchemaAsync(context.App.Id, x, false, ct)
?? throw new DomainObjectNotFoundException(x.ToString());
if (schema == null)
{
throw new DomainObjectNotFoundException(x.ToString());
}
var components = await appProvider.GetComponentsAsync(schema, ct); var components = await appProvider.GetComponentsAsync(schema, ct);

4
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs

@ -16,8 +16,6 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
#pragma warning disable MA0073 // Avoid comparison with bool constant
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps; namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
public sealed class ConvertData : IContentEnricherStep public sealed class ConvertData : IContentEnricherStep
@ -133,7 +131,7 @@ public sealed class ConvertData : IContentEnricherStep
converter.Add(new ResolveFromPreviousPartitioning(context.App.Languages)); converter.Add(new ResolveFromPreviousPartitioning(context.App.Languages));
if (!context.IsFrontendClient) if (!context.IsFrontendClient && !context.NoDefaults())
{ {
converter.Add(new AddDefaultValues(context.App.PartitionResolver()) converter.Add(new AddDefaultValues(context.App.PartitionResolver())
{ {

2
backend/src/Squidex.Domain.Apps.Entities/Jobs/JobWorker.cs

@ -27,7 +27,7 @@ public sealed class JobWorker :
processorFactory = key => processorFactory = key =>
{ {
return (JobProcessor)objectFactory(serviceProvider, new object[] { key }); return (JobProcessor)objectFactory(serviceProvider, [key]);
}; };
} }

12
backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs

@ -218,11 +218,11 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex
} }
// Do not use cancellation here as we already so far. // Do not use cancellation here as we already so far.
await schemaCache.AddAsync(new[] await schemaCache.AddAsync(
{ [
new KeyValuePair<string, object?>(GetCacheKey(schema.AppId.Id, schema.Id), schema), new KeyValuePair<string, object?>(GetCacheKey(schema.AppId.Id, schema.Id), schema),
new KeyValuePair<string, object?>(GetCacheKey(schema.AppId.Id, schema.Name), schema), new KeyValuePair<string, object?>(GetCacheKey(schema.AppId.Id, schema.Name), schema),
}, options.CacheDuration); ], options.CacheDuration);
return schema; return schema;
} }
@ -235,10 +235,10 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex
} }
// Do not use cancellation here as we already so far. // Do not use cancellation here as we already so far.
return schemaCache.RemoveAsync(new[] return schemaCache.RemoveAsync(
{ [
GetCacheKey(appId, id), GetCacheKey(appId, id),
GetCacheKey(appId, name) GetCacheKey(appId, name)
}); ]);
} }
} }

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

@ -25,16 +25,16 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CsvHelper" Version="33.0.1" /> <PackageReference Include="CsvHelper" Version="33.0.1" />
<PackageReference Include="GraphQL" Version="7.8.0" /> <PackageReference Include="GraphQL" Version="8.0.2" />
<PackageReference Include="GraphQL.DataLoader" Version="7.8.0" /> <PackageReference Include="GraphQL.DataLoader" Version="8.0.2" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="Notifo.SDK" Version="1.7.5" /> <PackageReference Include="Notifo.SDK" Version="1.7.5" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.CLI.Core" Version="13.0.0" /> <PackageReference Include="Squidex.CLI.Core" Version="13.3.0" />
<PackageReference Include="Squidex.YDotNet.Extensions" Version="0.2.9" /> <PackageReference Include="Squidex.YDotNet.Extensions" Version="0.2.9" />
<PackageReference Include="Squidex.YDotNet.Server" Version="0.2.9" /> <PackageReference Include="Squidex.YDotNet.Server" Version="0.2.9" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />

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

@ -14,7 +14,7 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

6
backend/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs

@ -136,8 +136,8 @@ public sealed class MongoUserStore :
protected override Task SetupCollectionAsync(IMongoCollection<MongoUser> collection, protected override Task SetupCollectionAsync(IMongoCollection<MongoUser> collection,
CancellationToken ct) CancellationToken ct)
{ {
return collection.Indexes.CreateManyAsync(new[] return collection.Indexes.CreateManyAsync(
{ [
new CreateIndexModel<MongoUser>( new CreateIndexModel<MongoUser>(
Index Index
.Ascending("Logins.LoginProvider") .Ascending("Logins.LoginProvider")
@ -156,7 +156,7 @@ public sealed class MongoUserStore :
{ {
Unique = true Unique = true
}) })
}, ct); ], ct);
} }
protected override MongoCollectionSettings CollectionSettings() protected override MongoCollectionSettings CollectionSettings()

4
backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj

@ -19,12 +19,12 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" /> <ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="MongoDB.Driver" Version="2.27.0" /> <PackageReference Include="MongoDB.Driver" Version="2.28.0" />
<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.Security.Principal.Windows" Version="5.0.0" /> <PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" />

6
backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj

@ -18,13 +18,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="IdentityModel" Version="7.0.0" /> <PackageReference Include="IdentityModel" Version="7.0.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.6" /> <PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.8" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="OpenIddict.AspNetCore" Version="5.7.0" /> <PackageReference Include="OpenIddict.AspNetCore" Version="5.8.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="SharpPwned.NET" Version="2.0.1" /> <PackageReference Include="SharpPwned.NET" Version="2.0.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />

10
backend/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj

@ -11,11 +11,11 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EventStore.Client.Grpc.PersistentSubscriptions" Version="23.3.3" /> <PackageReference Include="EventStore.Client.Grpc.PersistentSubscriptions" Version="23.3.4" />
<PackageReference Include="EventStore.Client.Grpc.ProjectionManagement" Version="23.3.3" /> <PackageReference Include="EventStore.Client.Grpc.ProjectionManagement" Version="23.3.4" />
<PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.3.3" /> <PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.3.4" />
<PackageReference Include="Grpc.Net.Client" Version="2.63.0" /> <PackageReference Include="Grpc.Net.Client" Version="2.65.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

6
backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs

@ -44,8 +44,8 @@ public partial class MongoEventStore : MongoRepositoryBase<MongoEventCommit>, IE
protected override async Task SetupCollectionAsync(IMongoCollection<MongoEventCommit> collection, protected override async Task SetupCollectionAsync(IMongoCollection<MongoEventCommit> collection,
CancellationToken ct) CancellationToken ct)
{ {
await collection.Indexes.CreateManyAsync(new[] await collection.Indexes.CreateManyAsync(
{ [
new CreateIndexModel<MongoEventCommit>( new CreateIndexModel<MongoEventCommit>(
Index Index
.Ascending(x => x.EventStream) .Ascending(x => x.EventStream)
@ -62,7 +62,7 @@ public partial class MongoEventStore : MongoRepositoryBase<MongoEventCommit>, IE
{ {
Unique = true Unique = true
}) })
}, ct); ], ct);
var clusterVersion = await Database.GetMajorVersionAsync(ct); var clusterVersion = await Database.GetMajorVersionAsync(ct);
var clusteredAsReplica = Database.Client.Cluster.Description.Type == ClusterType.ReplicaSet; var clusteredAsReplica = Database.Client.Cluster.Description.Type == ClusterType.ReplicaSet;

6
backend/src/Squidex.Infrastructure.MongoDb/Log/MongoRequestLogRepository.cs

@ -32,8 +32,8 @@ public sealed class MongoRequestLogRepository : MongoRepositoryBase<MongoRequest
protected override Task SetupCollectionAsync(IMongoCollection<MongoRequest> collection, protected override Task SetupCollectionAsync(IMongoCollection<MongoRequest> collection,
CancellationToken ct) CancellationToken ct)
{ {
return collection.Indexes.CreateManyAsync(new[] return collection.Indexes.CreateManyAsync(
{ [
new CreateIndexModel<MongoRequest>( new CreateIndexModel<MongoRequest>(
Index Index
.Ascending(x => x.Key) .Ascending(x => x.Key)
@ -45,7 +45,7 @@ public sealed class MongoRequestLogRepository : MongoRepositoryBase<MongoRequest
{ {
ExpireAfter = TimeSpan.FromDays(options.StoreRetentionInDays) ExpireAfter = TimeSpan.FromDays(options.StoreRetentionInDays)
}) })
}, ct); ], ct);
} }
public Task InsertManyAsync(IEnumerable<Request> items, public Task InsertManyAsync(IEnumerable<Request> items,

6
backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj

@ -14,12 +14,12 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MongoDB.Driver" Version="2.27.0" /> <PackageReference Include="MongoDB.Driver" Version="2.28.0" />
<PackageReference Include="MongoDB.Driver.GridFS" Version="2.27.0" /> <PackageReference Include="MongoDB.Driver.GridFS" Version="2.28.0" />
<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.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />

4
backend/src/Squidex.Infrastructure/Commands/DefaultDomainObjectFactory.cs

@ -23,7 +23,7 @@ public sealed class DefaultDomainObjectFactory : IDomainObjectFactory
public static T Create(IServiceProvider serviceProvider, DomainId id) public static T Create(IServiceProvider serviceProvider, DomainId id)
{ {
return (T)ObjectFactory(serviceProvider, new object[] { id }); return (T)ObjectFactory(serviceProvider, [id]);
} }
} }
@ -34,7 +34,7 @@ public sealed class DefaultDomainObjectFactory : IDomainObjectFactory
public static T Create(IServiceProvider serviceProvider, DomainId id, IPersistenceFactory<TState> persistenceFactory) public static T Create(IServiceProvider serviceProvider, DomainId id, IPersistenceFactory<TState> persistenceFactory)
{ {
return (T)ObjectFactory(serviceProvider, new object[] { id, persistenceFactory }); return (T)ObjectFactory(serviceProvider, [id, persistenceFactory]);
} }
} }

14
backend/src/Squidex.Infrastructure/Json/System/PolymorphicConverter.cs

@ -10,6 +10,8 @@ using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata; using System.Text.Json.Serialization.Metadata;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
#pragma warning disable MA0084 // Local variables should not hide other symbols
namespace Squidex.Infrastructure.Json.System; namespace Squidex.Infrastructure.Json.System;
public sealed class PolymorphicConverter<T> : JsonConverter<T> where T : class public sealed class PolymorphicConverter<T> : JsonConverter<T> where T : class
@ -38,8 +40,8 @@ public sealed class PolymorphicConverter<T> : JsonConverter<T> where T : class
{ {
if (typeRegistry.TryGetConfig(baseType, out var config) && config.TryGetName(typeInfo.Type, out var typeName)) if (typeRegistry.TryGetConfig(baseType, out var config) && config.TryGetName(typeInfo.Type, out var typeName))
{ {
var discriminiatorName = config.DiscriminatorProperty ?? Constants.DefaultDiscriminatorProperty; var discriminatorName = config.DiscriminatorProperty ?? Constants.DefaultDiscriminatorProperty;
var discriminatorField = typeInfo.CreateJsonPropertyInfo(typeof(string), discriminiatorName); var discriminatorField = typeInfo.CreateJsonPropertyInfo(typeof(string), discriminatorName);
discriminatorField.Get = x => discriminatorField.Get = x =>
{ {
@ -61,7 +63,7 @@ public sealed class PolymorphicConverter<T> : JsonConverter<T> where T : class
if (typeReader.TokenType != JsonTokenType.StartObject) if (typeReader.TokenType != JsonTokenType.StartObject)
{ {
throw new JsonException(); ThrowHelper.JsonSystemException($"Expected Object, got '{reader.TokenType}'");
} }
while (typeReader.Read()) while (typeReader.Read())
@ -73,7 +75,7 @@ public sealed class PolymorphicConverter<T> : JsonConverter<T> where T : class
if (typeReader.TokenType != JsonTokenType.String) if (typeReader.TokenType != JsonTokenType.String)
{ {
ThrowHelper.JsonException($"Expected string discriminator value, got '{reader.TokenType}'"); ThrowHelper.JsonSystemException($"Expected string discriminator value, got '{reader.TokenType}'");
return default!; return default!;
} }
@ -92,7 +94,7 @@ public sealed class PolymorphicConverter<T> : JsonConverter<T> where T : class
} }
} }
ThrowHelper.JsonException($"Object has no discriminator '{discriminatorName}'."); ThrowHelper.JsonSystemException($"Object has no discriminator '{discriminatorName}'.");
return default!; return default!;
} }
@ -112,7 +114,7 @@ public sealed class PolymorphicConverter<T> : JsonConverter<T> where T : class
{ {
if (!typeRegistry.TryGetType<T>(name, out var type)) if (!typeRegistry.TryGetType<T>(name, out var type))
{ {
ThrowHelper.JsonException($"Object has invalid discriminator '{name}'."); ThrowHelper.JsonSystemException($"Object has invalid discriminator '{name}'.");
return default!; return default!;
} }

4
backend/src/Squidex.Infrastructure/Json/System/StringConverter.cs

@ -43,7 +43,7 @@ public sealed class StringConverter<T> : JsonConverter<T> where T : notnull
} }
catch (Exception ex) catch (Exception ex)
{ {
ThrowHelper.JsonException("Error while converting from string.", ex); ThrowHelper.JsonSystemException("Error while converting from string.", ex);
return default; return default;
} }
@ -53,7 +53,7 @@ public sealed class StringConverter<T> : JsonConverter<T> where T : notnull
return JsonSerializer.Deserialize<T>(ref reader, optionsWithoutSelf); return JsonSerializer.Deserialize<T>(ref reader, optionsWithoutSelf);
default: default:
ThrowHelper.JsonException($"Expected string or object, got {reader.TokenType}."); ThrowHelper.JsonSystemException($"Expected string or object, got {reader.TokenType}.");
return default; return default;
} }
} }

2
backend/src/Squidex.Infrastructure/Migrations/Migrator.cs

@ -86,7 +86,7 @@ public sealed class Migrator
{ {
while (!await migrationStatus.TryLockAsync(ct)) while (!await migrationStatus.TryLockAsync(ct))
{ {
log.LogInformation("Could not acquire lock to start migrating. Tryping again in {time}ms.", LockWaitMs); log.LogInformation("Could not acquire lock to start migrating. Trying again in {time}ms.", LockWaitMs);
await Task.Delay(LockWaitMs, ct); await Task.Delay(LockWaitMs, ct);
} }
} }

2
backend/src/Squidex.Infrastructure/Queries/Json/JsonFilterSurrogate.cs

@ -50,7 +50,7 @@ public sealed class JsonFilterSurrogate : ISurrogate<FilterNode<JsonValue>>
return new CompareFilter<JsonValue>(Path, Op ?? CompareOperator.Equals, Value); return new CompareFilter<JsonValue>(Path, Op ?? CompareOperator.Equals, Value);
} }
ThrowHelper.JsonException(Errors.InvalidJsonStructure()); ThrowHelper.JsonSystemException(Errors.InvalidJsonStructure());
return default!; return default!;
} }
} }

20
backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -11,25 +11,25 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MailKit" Version="4.7.0" /> <PackageReference Include="MailKit" Version="4.7.1.1" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" /> <PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="8.0.6" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="8.0.8" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" /> <PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageReference Include="Microsoft.OData.Core" Version="7.21.3" /> <PackageReference Include="Microsoft.OData.Core" Version="8.0.1" />
<PackageReference Include="NodaTime" Version="3.1.11" /> <PackageReference Include="NodaTime" Version="3.1.11" />
<PackageReference Include="OpenTelemetry.Api" Version="1.9.0" /> <PackageReference Include="OpenTelemetry.Api" Version="1.9.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.Assets" Version="6.18.0" /> <PackageReference Include="Squidex.Assets" Version="6.19.0" />
<PackageReference Include="Squidex.Caching" Version="6.18.0" /> <PackageReference Include="Squidex.Caching" Version="6.19.0" />
<PackageReference Include="Squidex.Hosting.Abstractions" Version="6.18.0" /> <PackageReference Include="Squidex.Hosting.Abstractions" Version="6.19.0" />
<PackageReference Include="Squidex.Log" Version="6.18.0" /> <PackageReference Include="Squidex.Log" Version="6.19.0" />
<PackageReference Include="Squidex.Messaging" Version="6.18.0" /> <PackageReference Include="Squidex.Messaging" Version="6.19.0" />
<PackageReference Include="Squidex.Text" Version="6.18.0" /> <PackageReference Include="Squidex.Text" Version="6.19.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="8.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />

2
backend/src/Squidex.Infrastructure/States/StoreExtensions.cs

@ -14,6 +14,6 @@ public static class StoreExtensions
public static Task WriteEventAsync<T>(this IPersistence<T> persistence, Envelope<IEvent> @event, public static Task WriteEventAsync<T>(this IPersistence<T> persistence, Envelope<IEvent> @event,
CancellationToken ct = default) CancellationToken ct = default)
{ {
return persistence.WriteEventsAsync(new[] { @event }, ct); return persistence.WriteEventsAsync([@event], ct);
} }
} }

2
backend/src/Squidex.Infrastructure/Tasks/Scheduler.cs

@ -41,7 +41,7 @@ public sealed class Scheduler
if (pendingTasks >= 1) if (pendingTasks >= 1)
{ {
// If we already in a tasks we just queue it with the semaphore. // If we already in a tasks we just queue it with the semaphore.
ScheduleTasks(new[] { task }, default); ScheduleTasks([task], default);
return; return;
} }

5
backend/src/Squidex.Infrastructure/ThrowHelper.cs

@ -45,4 +45,9 @@ public static class ThrowHelper
{ {
throw new NotSupportedException(message); throw new NotSupportedException(message);
} }
public static void JsonSystemException(string? message = null, Exception? ex = null)
{
throw new System.Text.Json.JsonException(message, ex);
}
} }

2
backend/src/Squidex.Infrastructure/Translations/MissingKeys.cs

@ -33,7 +33,7 @@ public sealed class MissingKeys
{ {
if (!missingTranslations.Add(key)) if (!missingTranslations.Add(key))
{ {
File.AppendAllLines(MissingFileName, new[] { key }); File.AppendAllLines(MissingFileName, [key]);
} }
} }
} }

3
backend/src/Squidex.Shared/PermissionIds.cs

@ -103,6 +103,9 @@ public static class PermissionIds
// App Usage // App Usage
public const string AppUsage = "squidex.apps.{app}.usage"; public const string AppUsage = "squidex.apps.{app}.usage";
// App Expose Users
public const string AppPii = "squidex.apps.{app}.pii";
// App Comments // App Comments
public const string AppComments = "squidex.apps.{app}.comments"; public const string AppComments = "squidex.apps.{app}.comments";
public const string AppCommentsRead = "squidex.apps.{app}.comments.read"; public const string AppCommentsRead = "squidex.apps.{app}.comments.read";

2
backend/src/Squidex.Shared/Squidex.Shared.csproj

@ -10,7 +10,7 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

1
backend/src/Squidex.Web/ApiExceptionConverter.cs

@ -8,6 +8,7 @@
using System.Diagnostics; using System.Diagnostics;
using System.Security; using System.Security;
using System.Text; using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Squidex.Infrastructure; using Squidex.Infrastructure;

2
backend/src/Squidex.Web/GraphQL/DynamicUserContextBuilder.cs

@ -19,7 +19,7 @@ public sealed class DynamicUserContextBuilder : IUserContextBuilder
public ValueTask<IDictionary<string, object?>?> BuildUserContextAsync(HttpContext context, object? payload) public ValueTask<IDictionary<string, object?>?> BuildUserContextAsync(HttpContext context, object? payload)
{ {
var executionContext = (GraphQLExecutionContext)factory(context.RequestServices, new object[] { context.Context() }); var executionContext = (GraphQLExecutionContext)factory(context.RequestServices, [context.Context()]);
return new ValueTask<IDictionary<string, object?>?>(executionContext); return new ValueTask<IDictionary<string, object?>?>(executionContext);
} }

8
backend/src/Squidex.Web/Squidex.Web.csproj

@ -13,10 +13,10 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="GraphQL" Version="7.8.0" /> <PackageReference Include="GraphQL" Version="8.0.2" />
<PackageReference Include="GraphQL.SystemTextJson" Version="7.8.0" /> <PackageReference Include="GraphQL.SystemTextJson" Version="8.0.2" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="7.7.1" /> <PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="8.0.2" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

2
backend/src/Squidex/Areas/Api/Config/OpenApi/SecurityProcessor.cs

@ -15,7 +15,7 @@ namespace Squidex.Areas.Api.Config.OpenApi;
public sealed class SecurityProcessor : SecurityDefinitionAppender public sealed class SecurityProcessor : SecurityDefinitionAppender
{ {
public SecurityProcessor(IUrlGenerator urlGenerator) public SecurityProcessor(IUrlGenerator urlGenerator)
: base(Constants.SecurityDefinition, new[] { Constants.ScopeApi }, CreateOAuthSchema(urlGenerator)) : base(Constants.SecurityDefinition, [Constants.ScopeApi], CreateOAuthSchema(urlGenerator))
{ {
} }

5
backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -452,6 +452,7 @@ public sealed class ContentsController : ApiController
/// <param name="app">The name of the app.</param> /// <param name="app">The name of the app.</param>
/// <param name="schema">The name of the schema.</param> /// <param name="schema">The name of the schema.</param>
/// <param name="id">The ID of the content item to update.</param> /// <param name="id">The ID of the content item to update.</param>
/// <param name="request">The request parameters.</param>
/// <response code="200">Content updated.</response> /// <response code="200">Content updated.</response>
/// <response code="404">Content references, schema or app not found.</response> /// <response code="404">Content references, schema or app not found.</response>
/// <remarks> /// <remarks>
@ -464,9 +465,9 @@ public sealed class ContentsController : ApiController
[AcceptHeader.Unpublished] [AcceptHeader.Unpublished]
[AcceptHeader.Languages] [AcceptHeader.Languages]
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> PutContentDefaults(string app, string schema, DomainId id) public async Task<IActionResult> PutContentDefaults(string app, string schema, DomainId id, EnrichContentDefaultsDto request)
{ {
var command = new EnrichContentDefaults { ContentId = id }; var command = request.ToCommand(id);
var response = await InvokeCommandAsync(command); var response = await InvokeCommandAsync(command);

35
backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsSharedController.cs

@ -24,6 +24,13 @@ namespace Squidex.Areas.Api.Controllers.Contents;
[ApiExplorerSettings(GroupName = nameof(Contents))] [ApiExplorerSettings(GroupName = nameof(Contents))]
public sealed class ContentsSharedController : ApiController public sealed class ContentsSharedController : ApiController
{ {
private static readonly GraphQLHttpMiddlewareOptions GraphQLOptions = new GraphQLHttpMiddlewareOptions
{
DefaultResponseContentType = new MediaTypeHeaderValue("application/json"),
CsrfProtectionEnabled = false,
CsrfProtectionHeaders = []
};
private readonly IContentQueryService contentQuery; private readonly IContentQueryService contentQuery;
private readonly IContentWorkflow contentWorkflow; private readonly IContentWorkflow contentWorkflow;
@ -54,12 +61,7 @@ public sealed class ContentsSharedController : ApiController
[IgnoreCacheFilter] [IgnoreCacheFilter]
public IActionResult GetGraphQL(string app, GraphQLQueryDto request) public IActionResult GetGraphQL(string app, GraphQLQueryDto request)
{ {
var options = new GraphQLHttpMiddlewareOptions return new GraphQLExecutionActionResult<DummySchema>(GraphQLOptions);
{
DefaultResponseContentType = new MediaTypeHeaderValue("application/json")
};
return new GraphQLExecutionActionResult<DummySchema>(options);
} }
/// <summary> /// <summary>
@ -80,12 +82,7 @@ public sealed class ContentsSharedController : ApiController
[IgnoreCacheFilter] [IgnoreCacheFilter]
public IActionResult PostGraphQL(string app) public IActionResult PostGraphQL(string app)
{ {
var options = new GraphQLHttpMiddlewareOptions return new GraphQLExecutionActionResult<DummySchema>(GraphQLOptions);
{
DefaultResponseContentType = new MediaTypeHeaderValue("application/json")
};
return new GraphQLExecutionActionResult<DummySchema>(options);
} }
/// <summary> /// <summary>
@ -106,12 +103,7 @@ public sealed class ContentsSharedController : ApiController
[IgnoreCacheFilter] [IgnoreCacheFilter]
public IActionResult GetGraphQLBatch(string app, GraphQLQueryDto request) public IActionResult GetGraphQLBatch(string app, GraphQLQueryDto request)
{ {
var options = new GraphQLHttpMiddlewareOptions return new GraphQLExecutionActionResult<DummySchema>(GraphQLOptions);
{
DefaultResponseContentType = new MediaTypeHeaderValue("application/json")
};
return new GraphQLExecutionActionResult<DummySchema>(options);
} }
/// <summary> /// <summary>
@ -132,12 +124,7 @@ public sealed class ContentsSharedController : ApiController
[IgnoreCacheFilter] [IgnoreCacheFilter]
public IActionResult PostGraphQLBatch(string app) public IActionResult PostGraphQLBatch(string app)
{ {
var options = new GraphQLHttpMiddlewareOptions return new GraphQLExecutionActionResult<DummySchema>(GraphQLOptions);
{
DefaultResponseContentType = new MediaTypeHeaderValue("application/json")
};
return new GraphQLExecutionActionResult<DummySchema>(options);
} }
/// <summary> /// <summary>

2
backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/OperationBuilder.cs

@ -151,7 +151,7 @@ internal sealed class OperationBuilder
{ {
new OpenApiSecurityRequirement new OpenApiSecurityRequirement
{ {
[Constants.SecurityDefinition] = new[] { fullId } [Constants.SecurityDefinition] = [fullId]
} }
}; };

5
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/BulkUpdateContentsDto.cs

@ -33,6 +33,11 @@ public sealed class BulkUpdateContentsDto
/// </summary> /// </summary>
public bool DoNotScript { get; set; } = true; public bool DoNotScript { get; set; } = true;
/// <summary>
/// True, to also enrich required fields. Default: false.
/// </summary>
public bool EnrichRequiredFields { get; set; }
/// <summary> /// <summary>
/// True to turn off validation for faster inserts. Default: false. /// True to turn off validation for faster inserts. Default: false.
/// </summary> /// </summary>

8
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs

@ -40,14 +40,18 @@ public sealed class ContentsDto : Resource
if (schema != null) if (schema != null)
{ {
await result.AssignStatusesAsync(workflow, schema); await result.CreateStatusesAsync(workflow, schema);
await result.CreateLinksAsync(resources, workflow, schema); await result.CreateLinksAsync(resources, workflow, schema);
} }
else
{
result.Statuses = [];
}
return result; return result;
} }
private async Task AssignStatusesAsync(IContentWorkflow workflow, Schema schema) private async Task CreateStatusesAsync(IContentWorkflow workflow, Schema schema)
{ {
var allStatuses = await workflow.GetAllAsync(schema); var allStatuses = await workflow.GetAllAsync(schema);

29
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/EnrichContentDefaultsDto.cs

@ -0,0 +1,29 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.AspNetCore.Mvc;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Contents.Models;
public class EnrichContentDefaultsDto
{
/// <summary>
/// True, to also enrich required fields. Default: false.
/// </summary>
[FromQuery(Name = "enrichRequiredFields")]
public bool EnrichRequiredFields { get; set; }
public EnrichContentDefaults ToCommand(DomainId id)
{
var command = SimpleMapper.Map(this, new EnrichContentDefaults { ContentId = id });
return command;
}
}

8
backend/src/Squidex/Areas/Api/Controllers/UploadModel.cs

@ -23,14 +23,14 @@ public class UploadModel
/// <summary> /// <summary>
/// The alternative URL to download from. /// The alternative URL to download from.
/// </summary> /// </summary>
[FromForm(Name = "fileUrl")] [FromForm(Name = "url")]
public string? FileUrl { get; set; } public string? Url { get; set; }
/// <summary> /// <summary>
/// The file name if the URL is specified. /// The file name if the URL is specified.
/// </summary> /// </summary>
[FromForm(Name = "fileName")] [FromForm(Name = "name")]
public string? FileName { get; set; } public string? Name { get; set; }
public Task<IAssetFile> ToFileAsync(HttpContext httpContext, App? app) public Task<IAssetFile> ToFileAsync(HttpContext httpContext, App? app)
{ {

161
backend/src/Squidex/Areas/IdentityServer/Views/Account/Login.cshtml

@ -32,101 +32,110 @@
</div> </div>
</div> </div>
<form asp-controller="Account" asp-action="External" asp-route-returnurl="@Model.ReturnUrl" method="post"> <div>
@Html.AntiForgeryToken() @if (Model.HasExternalLogin)
@foreach (var provider in Model.ExternalProviders)
{ {
var schema = provider.AuthenticationScheme.ToLowerInvariant(); <form asp-controller="Account" asp-action="External" asp-route-returnurl="@Model.ReturnUrl" method="post">
@Html.AntiForgeryToken()
<div class="form-group mb-2"> @foreach (var provider in Model.ExternalProviders)
<button class="btn external-button btn-block btn btn-@schema" type="submit" name="provider" value="@provider.AuthenticationScheme"> {
<i class="icon-@schema external-icon"></i> @Html.Raw(T.Get("users.login.loginWith", new { action, provider = provider.DisplayName })) var schema = provider.AuthenticationScheme.ToLowerInvariant();
</button>
</div> <div class="form-group mb-2">
<button class="btn external-button btn-block btn btn-@schema" type="submit" name="provider" value="@provider.AuthenticationScheme">
<i class="icon-@schema external-icon"></i> @Html.Raw(T.Get("users.login.loginWith", new { action, provider = provider.DisplayName }))
</button>
</div>
}
</form>
} }
</form>
@if (Model.HasPasswordAuth)
{
<div class="profile-separator"> <div class="profile-separator">
<div class="profile-separator-text">@T.Get("users.login.separator")</div> <div class="profile-separator-text">@T.Get("users.login.separator")</div>
</div> </div>
if (Model.IsLogin) @if (Model.HasPasswordAuth)
{
@if (Model.RequestType == RequestType.Login)
{
<div asp-validation-summary="ModelOnly" class="form-alert form-alert-error"></div>
}
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl" method="post">
@Html.AntiForgeryToken()
<div class="form-group">
<div error-for="Email"></div>
<input asp-for="Email" type="email" class="form-control" placeholder="@T.Get("users.login.emailPlaceholder")" />
</div>
<div class="form-group">
<div error-for="Password"></div>
<input asp-for="Password" type="password" class="form-control" placeholder="@T.Get("users.login.passwordPlaceholder")" />
</div>
<button type="submit" data-testid="login-button" class="btn btn-block btn-primary">@action</button>
</form>
}
else
{ {
<div class="profile-password-signup text-center">@T.Get("users.login.askAdmin")</div> <div>
@if (Model.IsLogin)
{
@if (Model.RequestType == RequestType.Login)
{
<div asp-validation-summary="ModelOnly" class="form-alert form-alert-error"></div>
}
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl" method="post">
@Html.AntiForgeryToken()
<div class="form-group">
<div error-for="Email"></div>
<input asp-for="Email" type="email" class="form-control" placeholder="@T.Get("users.login.emailPlaceholder")" />
</div>
<div class="form-group">
<div error-for="Password"></div>
<input asp-for="Password" type="password" class="form-control" placeholder="@T.Get("users.login.passwordPlaceholder")" />
</div>
<button type="submit" data-testid="login-button" class="btn btn-block btn-primary">@action</button>
</form>
}
else
{
<div class="profile-password-signup text-center">@T.Get("users.login.askAdmin")</div>
}
</div>
} }
}
@if (Model.HasCustomAuth && Model.IsLogin)
{
<div class="profile-separator"> <div class="profile-separator">
<div class="profile-separator-text">@T.Get("users.login.separator")</div> <div class="profile-separator-text">@T.Get("users.login.separator")</div>
</div> </div>
@if (Model.RequestType == RequestType.LoginCustom) @if (Model.HasCustomAuth && Model.IsLogin)
{ {
<div asp-validation-summary="ModelOnly" class="form-alert form-alert-error"></div> <div>
} @if (Model.RequestType == RequestType.LoginCustom)
{
<div asp-validation-summary="ModelOnly" class="form-alert form-alert-error"></div>
}
<div class="mb-2"> <div class="mb-2">
<small class="text-muted form-text">@T.Get("users.login.custom")</small> <small class="text-muted form-text">@T.Get("users.login.custom")</small>
</div> </div>
<form asp-controller="Account" asp-action="LoginDynamic" asp-route-returnurl="@Model.ReturnUrl" method="post"> <form asp-controller="Account" asp-action="LoginDynamic" asp-route-returnurl="@Model.ReturnUrl" method="post">
@Html.AntiForgeryToken() @Html.AntiForgeryToken()
<div class="form-group"> <div class="form-group">
<div error-for="DynamicEmail"></div> <div error-for="DynamicEmail"></div>
<input asp-for="DynamicEmail" type="email" class="form-control" placeholder="@T.Get("users.login.emailBusinessPlaceholder")" /> <input asp-for="DynamicEmail" type="email" class="form-control" placeholder="@T.Get("users.login.emailBusinessPlaceholder")" />
</div>
<button type="submit" data-testid="dynamic-button" class="btn btn-block btn-primary">@action</button>
</form>
</div> </div>
}
@if (Model.IsLogin)
{
<p class="profile-footer">
@T.Get("users.login.noAccountSignupQuestion")
<a asp-controller="Account" asp-action="Signup" asp-route-returnurl="@Model.ReturnUrl">
@T.Get("users.login.noAccountSignupAction")
</a>
</p>
}
else
{
<p class="profile-footer">
@T.Get("users.login.noAccountLoginQuestion")
<button type="submit" data-testid="dynamic-button" class="btn btn-block btn-primary">@action</button> <a asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl">
</form> @T.Get("users.login.noAccountLoginAction")
} </a>
</p>
@if (Model.IsLogin) }
{ </div>
<p class="profile-footer">
@T.Get("users.login.noAccountSignupQuestion")
<a asp-controller="Account" asp-action="Signup" asp-route-returnurl="@Model.ReturnUrl">
@T.Get("users.login.noAccountSignupAction")
</a>
</p>
}
else
{
<p class="profile-footer">
@T.Get("users.login.noAccountLoginQuestion")
<a asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl">
@T.Get("users.login.noAccountLoginAction")
</a>
</p>
}
</div> </div>

87
backend/src/Squidex/Areas/IdentityServer/Views/Setup/Setup.cshtml

@ -118,66 +118,67 @@
<div class="profile-section"> <div class="profile-section">
<h2 class="mb-3">@T.Get("setup.createUser.headline")</h2> <h2 class="mb-3">@T.Get("setup.createUser.headline")</h2>
@if (Model.HasExternalLogin) <div>
{ @if (Model.HasExternalLogin)
<div> {
<small class="form-text text-muted mt-2 mb-2">@T.Get("setup.createUser.loginHint")</small> <div>
<small class="form-text text-muted mt-2 mb-2">@T.Get("setup.createUser.loginHint")</small>
<div class="mt-3"> <div class="mt-3">
<a class="btn btn-primary force-white" asp-controller="Account" asp-action="Login"> <a class="btn btn-primary force-white" asp-controller="Account" asp-action="Login">
@T.Get("setup.createUser.loginLink") @T.Get("setup.createUser.loginLink")
</a> </a>
</div>
</div> </div>
</div> }
}
@if (Model.HasExternalLogin && Model.HasPasswordAuth)
{
<div class="profile-separator"> <div class="profile-separator">
<div class="profile-separator-text">@T.Get("setup.createUser.separator")</div> <div class="profile-separator-text">@T.Get("setup.createUser.separator")</div>
</div> </div>
}
@if (Model.HasPasswordAuth)
{
<h3>@T.Get("setup.createUser.headlineCreate")</h3>
@if (!string.IsNullOrWhiteSpace(Model.ErrorMessage)) @if (Model.HasPasswordAuth)
{ {
<div class="form-alert form-alert-error"> <div>
<h3>@T.Get("setup.createUser.headlineCreate")</h3>
@if (!string.IsNullOrWhiteSpace(Model.ErrorMessage))
{
<div class="form-alert form-alert-error">
@Model.ErrorMessage @Model.ErrorMessage
</div> </div>
} }
<form class="profile-form" asp-controller="Setup" asp-action="Setup" method="post"> <form class="profile-form" asp-controller="Setup" asp-action="Setup" method="post">
@Html.AntiForgeryToken() @Html.AntiForgeryToken()
<div class="form-group"> <div class="form-group">
<label asp-for="Email">@T.Get("common.email")</label> <label asp-for="Email">@T.Get("common.email")</label>
<div error-for="Email"></div> <div error-for="Email"></div>
<input asp-for="Email" type="text" class="form-control" /> <input asp-for="Email" type="text" class="form-control" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="Password">@T.Get("common.password")</label> <label asp-for="Password">@T.Get("common.password")</label>
<div error-for="Password"></div> <div error-for="Password"></div>
<input asp-for="Password" type="password" class="form-control" /> <input asp-for="Password" type="password" class="form-control" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="PasswordConfirm">@T.Get("setup.createUser.confirmPassword")</label> <label asp-for="PasswordConfirm">@T.Get("setup.createUser.confirmPassword")</label>
<div error-for="PasswordConfirm"></div> <div error-for="PasswordConfirm"></div>
<input asp-for="PasswordConfirm" type="password" class="form-control" /> <input asp-for="PasswordConfirm" type="password" class="form-control" />
</div> </div>
<div class="form-group mb-0"> <div class="form-group mb-0">
<button type="submit" class="btn btn-success">@T.Get("setup.createUser.button")</button> <button type="submit" class="btn btn-success">@T.Get("setup.createUser.button")</button>
</div>
</form>
</div> </div>
</form> }
} </div>
@if (!Model.HasExternalLogin && !Model.HasPasswordAuth) @if (!Model.HasExternalLogin && !Model.HasPasswordAuth)
{ {

2
backend/src/Squidex/Config/Domain/EventSourcingServices.cs

@ -45,7 +45,7 @@ public static class EventSourcingServices
.As<IEventStore>(); .As<IEventStore>();
services.AddHealthChecks() services.AddHealthChecks()
.AddCheck<GetEventStoreHealthCheck>("EventStore", tags: new[] { "node" }); .AddCheck<GetEventStoreHealthCheck>("EventStore", tags: ["node"]);
} }
}); });

4
backend/src/Squidex/Config/Domain/HealthCheckServices.cs

@ -18,7 +18,7 @@ public static class HealthCheckServices
"diagnostics:gc"); "diagnostics:gc");
services.AddHealthChecks() services.AddHealthChecks()
.AddCheck<GCHealthCheck>("GC", tags: new[] { "node" }) .AddCheck<GCHealthCheck>("GC", tags: ["node"])
.AddCheck<EventConsumersHealthCheck>("EventConsumers", tags: new[] { "background" }); .AddCheck<EventConsumersHealthCheck>("EventConsumers", tags: ["background"]);
} }
} }

5
backend/src/Squidex/Config/Domain/ResizeServices.cs

@ -15,11 +15,10 @@ public static class ResizeServices
public static void AddSquidexImageResizing(this IServiceCollection services, IConfiguration config) public static void AddSquidexImageResizing(this IServiceCollection services, IConfiguration config)
{ {
var thumbnailGenerator = new CompositeThumbnailGenerator( var thumbnailGenerator = new CompositeThumbnailGenerator(
new IAssetThumbnailGenerator[] [
{
new ImageSharpThumbnailGenerator(), new ImageSharpThumbnailGenerator(),
new ImageMagickThumbnailGenerator() new ImageMagickThumbnailGenerator()
}); ]);
var resizerUrl = config.GetValue<string>("assets:resizerUrl"); var resizerUrl = config.GetValue<string>("assets:resizerUrl");

2
backend/src/Squidex/Config/Domain/StoreServices.cs

@ -123,7 +123,7 @@ public static class StoreServices
.As<IDistributedCache>(); .As<IDistributedCache>();
services.AddHealthChecks() services.AddHealthChecks()
.AddCheck<MongoHealthCheck>("MongoDB", tags: new[] { "node" }); .AddCheck<MongoHealthCheck>("MongoDB", tags: ["node"]);
services.AddSingletonAs<MongoRequestLogRepository>() services.AddSingletonAs<MongoRequestLogRepository>()
.As<IRequestLogRepository>(); .As<IRequestLogRepository>();

1
backend/src/Squidex/Config/Domain/TelemetryServices.cs

@ -33,7 +33,6 @@ public static class TelemetryServices
builder.AddAspNetCoreInstrumentation(); builder.AddAspNetCoreInstrumentation();
builder.AddHttpClientInstrumentation(); builder.AddHttpClientInstrumentation();
builder.AddMongoDBInstrumentation();
var sampling = config.GetValue<double>("logging:otlp:sampling"); var sampling = config.GetValue<double>("logging:otlp:sampling");

70
backend/src/Squidex/Squidex.csproj

@ -35,56 +35,56 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="8.1.0" /> <PackageReference Include="AspNet.Security.OAuth.GitHub" Version="8.1.0" />
<PackageReference Include="Google.Cloud.Trace.V2" Version="3.6.0" /> <PackageReference Include="Google.Cloud.Trace.V2" Version="3.6.0" />
<PackageReference Include="GraphQL" Version="7.8.0" /> <PackageReference Include="GraphQL" Version="8.0.2" />
<PackageReference Include="GraphQL.MicrosoftDI" Version="7.8.0" /> <PackageReference Include="GraphQL.MicrosoftDI" Version="8.0.2" />
<PackageReference Include="GraphQL.SystemTextJson" Version="7.8.0" /> <PackageReference Include="GraphQL.SystemTextJson" Version="8.0.2" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.159"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.6" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="8.0.6" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.6" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.6" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.6" /> <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.8" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" /> <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis.RulesetToEditorconfigConverter" Version="3.3.3" /> <PackageReference Include="Microsoft.CodeAnalysis.RulesetToEditorconfigConverter" Version="3.3.3" />
<PackageReference Include="Microsoft.Data.Edm" Version="5.8.5" /> <PackageReference Include="Microsoft.Data.Edm" Version="5.8.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" /> <PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="7.6.2" /> <PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.0.2" />
<PackageReference Include="Microsoft.OData.Core" Version="7.21.3" /> <PackageReference Include="Microsoft.OData.Core" Version="8.0.1" />
<PackageReference Include="MongoDB.Driver" Version="2.27.0" /> <PackageReference Include="MongoDB.Driver" Version="2.28.0" />
<PackageReference Include="MongoDB.Driver.Core.Extensions.OpenTelemetry" Version="1.0.0" /> <PackageReference Include="MongoDB.Driver.Core.Extensions.DiagnosticSources" Version="1.5.0" />
<PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="4.0.0" /> <PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="4.0.0" />
<PackageReference Include="NJsonSchema" Version="11.0.1" /> <PackageReference Include="NJsonSchema" Version="11.0.2" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.2.0" /> <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.2.0" />
<PackageReference Include="NSwag.AspNetCore" Version="14.0.8" /> <PackageReference Include="NSwag.AspNetCore" Version="14.1.0" />
<PackageReference Include="OpenCover" Version="4.7.1221" PrivateAssets="all" /> <PackageReference Include="OpenCover" Version="4.7.1221" PrivateAssets="all" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" /> <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" /> <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="ReportGenerator" Version="5.3.7" PrivateAssets="all" /> <PackageReference Include="ReportGenerator" Version="5.3.8" PrivateAssets="all" />
<PackageReference Include="Squidex.Assets.Azure" Version="6.18.0" /> <PackageReference Include="Squidex.Assets.Azure" Version="6.19.0" />
<PackageReference Include="Squidex.Assets.GoogleCloud" Version="6.18.0" /> <PackageReference Include="Squidex.Assets.GoogleCloud" Version="6.19.0" />
<PackageReference Include="Squidex.Assets.FTP" Version="6.18.0" /> <PackageReference Include="Squidex.Assets.FTP" Version="6.19.0" />
<PackageReference Include="Squidex.Assets.ImageMagick" Version="6.18.0" /> <PackageReference Include="Squidex.Assets.ImageMagick" Version="6.19.0" />
<PackageReference Include="Squidex.Assets.ImageSharp" Version="6.18.0" /> <PackageReference Include="Squidex.Assets.ImageSharp" Version="6.19.0" />
<PackageReference Include="Squidex.Assets.Mongo" Version="6.18.0" /> <PackageReference Include="Squidex.Assets.Mongo" Version="6.19.0" />
<PackageReference Include="Squidex.Assets.S3" Version="6.18.0" /> <PackageReference Include="Squidex.Assets.S3" Version="6.19.0" />
<PackageReference Include="Squidex.Assets.TusAdapter" Version="6.18.0" /> <PackageReference Include="Squidex.Assets.TusAdapter" Version="6.19.0" />
<PackageReference Include="Squidex.ClientLibrary" Version="19.2.0" /> <PackageReference Include="Squidex.ClientLibrary" Version="19.7.0" />
<PackageReference Include="Squidex.Hosting" Version="6.18.0" /> <PackageReference Include="Squidex.Hosting" Version="6.19.0" />
<PackageReference Include="Squidex.Messaging.All" Version="6.18.0" /> <PackageReference Include="Squidex.Messaging.All" Version="6.19.0" />
<PackageReference Include="Squidex.Messaging.Subscriptions" Version="6.18.0" /> <PackageReference Include="Squidex.Messaging.Subscriptions" Version="6.19.0" />
<PackageReference Include="Squidex.OpenIddict.MongoDb" Version="5.1.0" /> <PackageReference Include="Squidex.OpenIddict.MongoDb" Version="5.8.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="YDotNet" Version="0.4.0" /> <PackageReference Include="YDotNet" Version="0.4.1" />
<PackageReference Include="YDotNet.Native" Version="0.4.0" /> <PackageReference Include="YDotNet.Native" Version="0.4.1" />
<PackageReference Include="YDotNet.Server" Version="0.4.0" /> <PackageReference Include="YDotNet.Server" Version="0.4.1" />
<PackageReference Include="YDotNet.Server.MongoDB" Version="0.4.0" /> <PackageReference Include="YDotNet.Server.MongoDB" Version="0.4.1" />
<PackageReference Include="YDotNet.Server.Redis" Version="0.4.0" /> <PackageReference Include="YDotNet.Server.Redis" Version="0.4.1" />
<PackageReference Include="YDotNet.Server.WebSockets" Version="0.4.0" /> <PackageReference Include="YDotNet.Server.WebSockets" Version="0.4.1" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

2
backend/src/Squidex/appsettings.json

@ -168,7 +168,7 @@
"hideDateTimeModeButton": false, "hideDateTimeModeButton": false,
// Show the exposed values as information on the apps overview page. // Show the exposed values as information on the apps overview page.
"showInfo": false, "showInfo": true,
// The number of content items for dropdown selector. // The number of content items for dropdown selector.
"referencesDropdownItemCount": 100 "referencesDropdownItemCount": 100

1
backend/src/Squidex/wwwroot/editor/squidex-editor.css

@ -1 +0,0 @@
.remirror-theme{width:100%}.remirror-theme{position:relative}.remirror-theme *{box-sizing:border-box}.remirror-theme .ProseMirror>p{margin-bottom:1rem!important}.remirror-theme .ProseMirror hr{border:0;border-top:1px solid #a7a9b4!important}.remirror-editor{min-height:300px!important;max-height:500px}.MuiStack-root>.MuiBox-root{margin-right:5px}.MuiBox-root>.MuiButtonBase-root{border-color:#dedfe3!important;border-radius:0}.MuiBox-root>.MuiButtonBase-root{border-left-width:0!important}.MuiBox-root>.MuiBox-root:first-child>.MuiButtonBase-root{border-left-width:1px!important}.MuiStack-root>.MuiBox-root>.MuiButtonBase-root:first-child{border-left-width:1px!important}.MuiButtonBase-root.Mui-selected:hover{background-color:#3284f4!important}.remirror-editor-wrapper{padding-top:0!important}.custom-icon path{fill:#0000008a}.remirror-theme div.ProseMirror{border:1px solid #dedfe3!important;border-radius:0!important;box-shadow:none!important}.MuiTooltip-popper>div{background-color:#1a2129;border-radius:0;font-size:85%;font-weight:400;padding:.5rem}.MuiMenu-paper{transform:none!important}.squidex-editor-disabled{pointer-events:none}.squidex-editor-menu{border:1px solid #dedfe3;border-bottom:0;padding:5px}.squidex-editor-menu fieldset{border-radius:0;border:0;display:flex;outline:0;padding:0}.squidex-editor-menu fieldset:disabled,.squidex-editor-menu fieldset.disabled{pointer-events:none}.squidex-editor-menu fieldset:disabled,.squidex-editor-menu fieldset.disabled{opacity:.5}.squidex-editor-counter{border:1px solid #dedfe3;border-top:0;font-size:85%;font-weight:400;opacity:.8;padding:4px 10px 4px 4px;text-align:right}.squidex-editor-image-view{border:1px solid #dedfe3;border-radius:0;display:inline-block;margin-top:15px;margin-bottom:15px;overflow:hidden}.squidex-editor-image-buttons{bottom:10px;left:10px;position:absolute}.squidex-editor-image-element{display:block;max-width:400px;max-height:400px}.squidex-editor-image-info{left:10px;top:10px;position:absolute;background-color:#3284f4;color:#fff;font-size:85%;font-weight:400;padding:2px 6px}.squidex-editor-main{position:relative}.squidex-editor-main .ace_editor{bottom:0;left:0;right:0;top:0;position:absolute;border:1px solid #dedfe3}.squidex-editor-content-link{align-items:center;border-radius:2px;border:1px solid #dedfe3;padding:10px;display:flex;flex-direction:row;flex-wrap:nowrap;margin-top:10px;margin-bottom:10px}.squidex-editor-content-schema{display:block;overflow-x:hidden;overflow-y:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;min-width:0;width:auto;border-right:1px solid #c2c4cc;color:#8b8f9d;flex-shrink:0;padding-left:10px;padding-right:10px;width:200px}.squidex-editor-content-name{display:block;overflow-x:hidden;overflow-y:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;min-width:0;width:auto;padding-left:10px;padding-right:0}.squidex-editor-html{margin-bottom:10px;margin-top:10px;position:relative}.squidex-editor-html-label{left:6px;top:0;position:absolute;color:#8b8f9d;font-size:85%;font-weight:400}.squidex-editor-html textarea{border:1px solid #dedfe3;font-family:monospace;font-size:inherit;height:100px;outline:none;overflow-x:hidden;overflow-y:auto;padding:30px 14px 14px;resize:vertical;width:100%}.squidex-editor-button{align-items:center;background-color:#fff;border-radius:0;border:1px solid #dedfe3;bottom:10px;display:inline-flex;font-size:85%;font-weight:400;margin-right:5px;padding:6px 12px}.squidex-editor-button:hover{background-color:#f5f5f5}.squidex-editor-input{border:1px solid #dedfe3;border-radius:0;box-sizing:border-box;height:30px;margin-left:0;margin-right:5px;outline:none;padding:6px 12px}.squidex-editor-input:active{border-color:#3284f4}.squidex-editor-floating{border:1px solid #dedfe3}.squidex-editor-floating .MuiBox-root{margin-right:0!important}.squidex-editor-floating .MuiButtonBase-root{height:30px}.squidex-editor-modal-wrapper,.squidex-editor-modal-backdrop{bottom:0;left:0;right:0;top:0;position:absolute}.squidex-editor-modal-backdrop{background-color:#00000003}.squidex-editor-modal-window{left:50%;top:50%;background-color:#fff;border:0;border-radius:.25rem;box-sizing:border-box;box-shadow:0 3px 16px #0003;margin-top:-50px;margin-left:-150px;position:absolute;width:350px}.squidex-editor-modal-body{display:flex;flex-direction:row;flex-grow:1;padding-top:0!important}.squidex-editor-modal-body,.squidex-editor-modal-title{padding:15px}.squidex-editor-modal-title{font-size:85%}.squidex-editor-modal-window input{flex-grow:1}:has(.remirror-language-select-positioner)~pre[class*=language-]{padding-top:2.5rem!important}.remirror-language-select-positioner{right:5px;top:5px;position:absolute}.b:before{content:"<a>";font-family:monospace;font-size:90%}.b:after{content:"</a>";font-family:monospace;font-size:90%}

531
backend/src/Squidex/wwwroot/editor/squidex-editor.js

File diff suppressed because one or more lines are too long

7
backend/src/Squidex/wwwroot/scripts/editor-context.html

@ -43,6 +43,13 @@
grow(element); grow(element);
}); });
// Init is called once with a context that contains the app name, schema name and authentication information.
field.onContextChanged(function (context) {
element.innerHTML = JSON.stringify(context, null, 2);
grow(element);
});
</script> </script>
</body> </body>

6
backend/src/Squidex/wwwroot/scripts/editor-log.html

@ -78,7 +78,7 @@
appendLabel('Value of Form'); appendLabel('Value of Form');
appendLine(`<${JSON.stringify(field.getFormValue(), 2)}>`); appendLine(`<${JSON.stringify(field.getFormValue(), 2)}>`);
appendLabel('Disabled: '); appendLabel('Disabled');
appendLine(field.isDisabled()); appendLine(field.isDisabled());
console.log(text); console.log(text);
@ -124,6 +124,10 @@
logState('Field language changed'); logState('Field language changed');
}); });
field.onContextChanged(function () {
logState('Context changed');
});
field.onExpanded(function () { field.onExpanded(function () {
logState('Expanded changed'); logState('Expanded changed');
}); });

32
backend/src/Squidex/wwwroot/scripts/editor-sdk.js

@ -259,6 +259,7 @@ function SquidexWidget(options) {
*/ */
function SquidexFormField(options) { function SquidexFormField(options) {
var context; var context;
var contextHandler;
var currentConfirm; var currentConfirm;
var currentPickAssets; var currentPickAssets;
var currentPickContents; var currentPickContents;
@ -324,6 +325,12 @@ function SquidexFormField(options) {
} }
} }
function raiseContextChanged() {
if (contextHandler && context) {
contextHandler(context);
}
}
function raisedMoved() { function raisedMoved() {
if (movedHandler && isNumber(index)) { if (movedHandler && isNumber(index)) {
movedHandler(index); movedHandler(index);
@ -386,6 +393,10 @@ function SquidexFormField(options) {
context = event.data.context; context = event.data.context;
raiseInit(); raiseInit();
} else if (type === 'contextChanged') {
context = event.data.context;
raiseContextChanged();
} else if (type === 'confirmResult') { } else if (type === 'confirmResult') {
var correlationId = event.data.correlationId; var correlationId = event.data.correlationId;
@ -631,7 +642,6 @@ function SquidexFormField(options) {
} }
initHandler = callback; initHandler = callback;
raiseInit(); raiseInit();
}, },
@ -661,10 +671,23 @@ function SquidexFormField(options) {
} }
disabledHandler = callback; disabledHandler = callback;
raiseDisabled(); raiseDisabled();
}, },
/**
* Register an function that is called whenever the context has been changed.
*
* @param {function} callback: The callback to invoke. Argument 1: New context.
*/
onContextChanged: function (callback) {
if (!isFunction(callback)) {
return;
}
contextHandler = callback;
raiseContextChanged();
},
/** /**
* Register an function that is called whenever the field language is changed. * Register an function that is called whenever the field language is changed.
* *
@ -676,7 +699,6 @@ function SquidexFormField(options) {
} }
languageHandler = callback; languageHandler = callback;
raiseLanguageChanged(); raiseLanguageChanged();
}, },
@ -691,7 +713,6 @@ function SquidexFormField(options) {
} }
valueHandler = callback; valueHandler = callback;
raiseValueChanged(); raiseValueChanged();
}, },
@ -706,7 +727,6 @@ function SquidexFormField(options) {
} }
formValueHandler = callback; formValueHandler = callback;
raiseFormValueChanged(); raiseFormValueChanged();
}, },
@ -721,7 +741,6 @@ function SquidexFormField(options) {
} }
fullscreenHandler = callback; fullscreenHandler = callback;
raiseFullscreen(); raiseFullscreen();
}, },
@ -736,7 +755,6 @@ function SquidexFormField(options) {
} }
expandedHandler = callback; expandedHandler = callback;
raiseExpanded(); raiseExpanded();
}, },

2
backend/src/Squidex/wwwroot/scripts/embed-sdk.js

File diff suppressed because one or more lines are too long

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

Loading…
Cancel
Save