Browse Source

Merge branch 'release/4.x'

# Conflicts:
#	backend/i18n/frontend_en.json
#	backend/i18n/source/frontend_en.json
#	backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs
pull/590/head
Sebastian 5 years ago
parent
commit
ad64a4379b
  1. 8
      backend/i18n/frontend_en.json
  2. 8
      backend/i18n/frontend_it.json
  3. 8
      backend/i18n/frontend_nl.json
  4. 6
      backend/i18n/source/backend_en.json
  5. 6
      backend/i18n/source/backend_it.json
  6. 6
      backend/i18n/source/backend_nl.json
  7. 8
      backend/i18n/source/frontend_en.json
  8. 8
      backend/i18n/source/frontend_it.json
  9. 8
      backend/i18n/source/frontend_nl.json
  10. 10
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs
  11. 5
      backend/src/Squidex.Domain.Users/PwnedPasswordValidator.cs
  12. 2
      backend/src/Squidex.Infrastructure/DomainObjectConflictException.cs
  13. 18
      backend/src/Squidex.Shared/Texts.it.resx
  14. 18
      backend/src/Squidex.Shared/Texts.nl.resx
  15. 18
      backend/src/Squidex.Shared/Texts.resx
  16. 20
      backend/src/Squidex/Areas/Frontend/Middlewares/IndexExtensions.cs
  17. 11
      backend/src/Squidex/Areas/Frontend/Middlewares/IndexMiddleware.cs
  18. 28
      backend/src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs
  19. 6
      backend/src/Squidex/Areas/Frontend/Startup.cs
  20. 4
      frontend/app/features/apps/pages/apps-page.component.html
  21. 2
      frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.html
  22. 10
      frontend/app/features/settings/pages/more/more-page.component.html
  23. 2
      frontend/app/features/settings/pages/roles/role.component.html
  24. 33
      frontend/app/framework/angular/routers/router-2-state.spec.ts
  25. 11
      frontend/app/framework/angular/routers/router-2-state.ts
  26. 1
      frontend/app/shared/components/assets/asset-folder.component.scss
  27. 107
      frontend/app/shared/state/query.spec.ts
  28. 4
      frontend/app/shared/state/query.ts

8
backend/i18n/frontend_en.json

@ -12,11 +12,11 @@
"apps.appNameWarning": "The app name cannot be changed later.",
"apps.appsButtonCreate": "Apps Overview",
"apps.appsButtonFallbackTitle": "Apps Overview",
"apps.archieve": "Archive App",
"apps.archieveConfirmText": "Do you really want to archive this App?",
"apps.archieveConfirmTitle": "Archive App",
"apps.archieveWarning": "Once you archive an app, there is no going back. Please be certain.",
"apps.archive": "Archive App",
"apps.archiveConfirmText": "Do you really want to archive this app?",
"apps.archiveConfirmTitle": "Archive App",
"apps.archiveFailed": "Failed to archive app. Please reload.",
"apps.archiveWarning": "Once you archive an app, there is no going back. Please be certain.",
"apps.create": "Create App",
"apps.createBlankApp": "New App",
"apps.createBlankAppDescription": "Create a new blank app without content and schemas.",

8
backend/i18n/frontend_it.json

@ -12,11 +12,11 @@
"apps.appNameWarning": "Il nome della app non potrà essere cambiato in un secondo momento.",
"apps.appsButtonCreate": "Nuova App",
"apps.appsButtonFallbackTitle": "Lista App",
"apps.archieve": "Archivia l'App",
"apps.archieveConfirmText": "Rimuovi il pattern",
"apps.archieveConfirmTitle": "Sei sicuro di voler archiviare questa app?",
"apps.archieveWarning": "Una volta archiviata una App, non è possibile tornare indietro. Sii certo.",
"apps.archive": "Archivia l'App",
"apps.archiveConfirmText": "Rimuovi il pattern",
"apps.archiveConfirmTitle": "Sei sicuro di voler archiviare questa app?",
"apps.archiveFailed": "Non è stato possibile archiviare l'app. Per favore ricarica.",
"apps.archiveWarning": "Una volta archiviata una App, non è possibile tornare indietro. Sii certo.",
"apps.create": "Crea un'App",
"apps.createBlankApp": "Nuova App.",
"apps.createBlankAppDescription": "Crea una app vuota senza contenuti o schema.",

8
backend/i18n/frontend_nl.json

@ -12,11 +12,11 @@
"apps.appNameWarning": "De app-naam kan later niet worden gewijzigd.",
"apps.appsButtonCreate": "Apps-overzicht",
"apps.appsButtonFallbackTitle": "Apps-overzicht",
"apps.archieve": "App archiveren",
"apps.archieveConfirmText": "Patroon verwijderen",
"apps.archieveConfirmTitle": "Wil je deze app echt archiveren?",
"apps.archieveWarning": "Zodra je een app archiveert, is er geen weg meer terug. Wees alsjeblieft zeker.",
"apps.archive": "App archiveren",
"apps.archiveConfirmText": "Patroon verwijderen",
"apps.archiveConfirmTitle": "Wil je deze app echt archiveren?",
"apps.archiveFailed": "Kan app niet archiveren. Laad opnieuw.",
"apps.archiveWarning": "Zodra je een app archiveert, is er geen weg meer terug. Wees alsjeblieft zeker.",
"apps.create": "App maken",
"apps.createBlankApp": "Nieuwe app.",
"apps.createBlankAppDescription": "Maak een nieuwe lege app zonder inhoud en schema's.",

6
backend/i18n/source/backend_en.json

@ -7,7 +7,6 @@
"annotations_Required": "The field {name|lower} is required.",
"annotations_StringLength": "The field {name|lower} must be a string with a maximum length of {max}.",
"annotations_StringLengthMinimum": "The field {name|lower} must be a string with a minimum length of {min} and a maximum length of {max}.",
"apps.alreadyArchieved": "App has already been archived.",
"apps.clients.idAlreadyExists": "A client with the same id already exists.",
"apps.contributors.cannotChangeYourself": "You cannot change your own role.",
"apps.contributors.maxReached": "You have reached the maximum number of contributors for your plan.",
@ -28,8 +27,6 @@
"apps.roles.nameAlreadyExists": "A role with the same name already exists.",
"apps.roles.usedRoleByClientsNotRemovable": "Cannot remove a role when a client is assigned.",
"apps.roles.usedRoleByContributorsNotRemovable": "Cannot remove a role when a contributor is assigned.",
"assets.assetAlreadyDeleted": "Asset has already been deleted",
"assets.assetFolderAlreadyDeleted": "Asset folder has already been deleted",
"assets.folderNotFound": "Asset folder does not exist.",
"assets.folderRecursion": "Cannot add folder to its own child.",
"assets.maxSizeReached": "You have reached your max asset size.",
@ -126,7 +123,6 @@
"contents.bulkInsertQueryNotUnique": "More than one content matches to the query.",
"contents.draftNotCreateForUnpublished": "You can only create a new version when the content is published.",
"contents.draftToDeleteNotFound": "There is nothing to delete.",
"contents.invalidArrayOfIds": "Invalid json type, expected array of guid strings.",
"contents.invalidArrayOfObjects": "Invalid json type, expected array of objects.",
"contents.invalidArrayOfStrings": "Invalid json type, expected array of strings.",
"contents.invalidBoolean": "Invalid json type, expected boolean.",
@ -249,9 +245,7 @@
"history.schemas.updated": "updated schema {[Name]}.",
"history.statusChanged": "changed status of {[Schema]} content to {[Status]}.",
"login.githubPrivateEmail": "Your email address is set to private in Github. Please set it to public to use Github login.",
"rules.alreadyDeleted": "Rule has already been deleted.",
"rules.ruleAlreadyRunning": "Another rule is already running.",
"schemas.alreadyDeleted": "Schema has already been deleted.",
"schemas.dateTimeCalculatedDefaultAndDefaultError": "Calculated default value and default value cannot be used together.",
"schemas.duplicateFieldName": "Field '{field}' has been added twice.",
"schemas.fieldCannotBeUIField": "Field cannot be an UI field.",

6
backend/i18n/source/backend_it.json

@ -7,7 +7,6 @@
"annotations_Required": "Il campo è {name|lower} obbligatorio.",
"annotations_StringLength": "The field {name|lower} must be a string with a maximum length of {max}.",
"annotations_StringLengthMinimum": "The field {name|lower} must be a string with a minimum length of {min} and a maximum length of {max}.",
"apps.alreadyArchieved": "La App è stata già archiviata.",
"apps.clients.idAlreadyExists": "Un client con lo stesso id esiste già.",
"apps.contributors.cannotChangeYourself": "Non puoi cambiare il tuo ruolo.",
"apps.contributors.maxReached": "Hai raggiunto il numero massimo di contributori previsto per il tuo piano.",
@ -28,8 +27,6 @@
"apps.roles.nameAlreadyExists": "Esiste già un ruolo con lo stesso nome.",
"apps.roles.usedRoleByClientsNotRemovable": "Non è possibile rimuovere un ruolo quando è assegnato ad un ruolo.",
"apps.roles.usedRoleByContributorsNotRemovable": "Non è possibile rimuovere un ruolo quando questo è assegnato ad un collaboratore.",
"assets.assetAlreadyDeleted": "La risorsa è stata già eliminata",
"assets.assetFolderAlreadyDeleted": "La cartella delle risorse è stata già eliminata",
"assets.folderNotFound": "La cartella delle risorse non esiste.",
"assets.folderRecursion": "Non è possibile aggiungere una cartella al proprio figlio.",
"assets.maxSizeReached": "Hai raggiunto la dimensione massima consentito per le risorse.",
@ -125,7 +122,6 @@
"contents.bulkInsertQueryNotUnique": "Ci sono più contenuti che corrispondono alla query.",
"contents.draftNotCreateForUnpublished": "Puoi creare versioni del contenuto solo se questo è pubblicato.",
"contents.draftToDeleteNotFound": "Non c'è niente da eliminare.",
"contents.invalidArrayOfIds": "Errore nel json, atteso un array di string guid.",
"contents.invalidArrayOfObjects": "Errore nel json, attesp un array di objects.",
"contents.invalidArrayOfStrings": "Errore nel json, atteso un array di string.",
"contents.invalidBoolean": "Errore nel json, atteso un boolean.",
@ -245,9 +241,7 @@
"history.schemas.updated": "ha aggiornato lo schema {[Name]}.",
"history.statusChanged": "ha cambiato lo stato del contenuto {[Schema]} in {[Status]}.",
"login.githubPrivateEmail": "Il tuo indirizzo email è impostato su privato in Github. Impostalo come pubblico per poter utilizzare il login Github.",
"rules.alreadyDeleted": "La regola è stata già cancellata.",
"rules.ruleAlreadyRunning": "E' in esecuzione un'altra regola.",
"schemas.alreadyDeleted": "Lo schema è stato già cancellato.",
"schemas.dateTimeCalculatedDefaultAndDefaultError": "Il valore predefinito calcolato e il valore predefinito non possono essere utilizzati insieme.",
"schemas.duplicateFieldName": "Il campo '{field}' è stato aggiunto due volte.",
"schemas.fieldCannotBeUIField": "Il campo non può essere un campo UI.",

6
backend/i18n/source/backend_nl.json

@ -7,7 +7,6 @@
"annotations_Required": "Het veld {name|lower} is verplicht.",
"annotations_StringLength": "Het veld {name|lower} moet een string zijn met een maximale lengte van {max}.",
"annotations_StringLengthMinimum": "Het veld {name|lower} moet een string zijn met een minimum lengte van {min} en een maximum lengte van {max}.",
"apps.alreadyArchieved": "App is al gearchiveerd.",
"apps.clients.idAlreadyExists": "Er bestaat al een client met dezelfde id.",
"apps.contributors.cannotChangeYourself": "Je kunt jouw eigen rol niet wijzigen.",
"apps.contributors.maxReached": "Je heeft het maximale aantal bijdragers voor jouw plan bereikt.",
@ -28,8 +27,6 @@
"apps.roles.nameAlreadyExists": "Er bestaat al een rol met dezelfde naam.",
"apps.roles.usedRoleByClientsNotRemovable": "Kan een rol niet verwijderen wanneer een client is toegewezen.",
"apps.roles.usedRoleByContributorsNotRemovable": "Kan een rol niet verwijderen wanneer een bijdrager is toegewezen.",
"assets.assetAlreadyDeleted": "Asset is al verwijderd",
"assets.assetFolderAlreadyDeleted": "Assetmap is al verwijderd",
"assets.folderNotFound": "Assetmap bestaat niet.",
"assets.folderRecursion": "Kan map niet toevoegen aan zijn eigen kind.",
"assets.maxSizeReached": "Je hebt jouw maximale assetgrootte bereikt.",
@ -126,7 +123,6 @@
"contents.bulkInsertQueryNotUnique": "Meer dan één inhoud komt overeen met de zoekopdracht.",
"contents.draftNotCreateForUnpublished": "Je kunt alleen een nieuwe versie maken wanneer de inhoud is gepubliceerd.",
"contents.draftToDeleteNotFound": "Er is niets te verwijderen.",
"contents.invalidArrayOfIds": "Ongeldig json-type, verwachte array van guid-strings.",
"contents.invalidArrayOfObjects": "Ongeldig json-type, verwachte reeks objecten.",
"contents.invalidArrayOfStrings": "Ongeldig json-type, verwachte reeks strings.",
"contents.invalidBoolean": "Ongeldig json-type, verwachte boolean.",
@ -248,9 +244,7 @@
"history.schemas.updated": "bijgewerkt schema {[Name]}.",
"history.statusChanged": "veranderde status van {[Schema]} inhoud in {[Status]}.",
"login.githubPrivateEmail": "Jouw e-mailadres is ingesteld op privé in Github. Stel het in op openbaar om Github-login te gebruiken.",
"rules.alreadyDeleted": "Regel is al verwijderd.",
"rules.ruleAlreadyRunning": "Er wordt al een andere regel uitgevoerd.",
"schemas.alreadyDeleted": "Schema is al verwijderd.",
"schemas.dateTimeCalculatedDefaultAndDefaultError": "Berekende standaardwaarde en standaardwaarde kunnen niet samen worden gebruikt.",
"schemas.duplicateFieldName": "Veld '{field}' is twee keer toegevoegd.",
"schemas.fieldCannotBeUIField": "Veld mag geen UI-veld zijn.",

8
backend/i18n/source/frontend_en.json

@ -12,11 +12,11 @@
"apps.appNameWarning": "The app name cannot be changed later.",
"apps.appsButtonCreate": "Apps Overview",
"apps.appsButtonFallbackTitle": "Apps Overview",
"apps.archieve": "Archive App",
"apps.archieveConfirmText": "Do you really want to archive this App?",
"apps.archieveConfirmTitle": "Archive App",
"apps.archieveWarning": "Once you archive an app, there is no going back. Please be certain.",
"apps.archive": "Archive App",
"apps.archiveConfirmText": "Do you really want to archive this app?",
"apps.archiveConfirmTitle": "Archive App",
"apps.archiveFailed": "Failed to archive app. Please reload.",
"apps.archiveWarning": "Once you archive an app, there is no going back. Please be certain.",
"apps.create": "Create App",
"apps.createBlankApp": "New App",
"apps.createBlankAppDescription": "Create a new blank app without content and schemas.",

8
backend/i18n/source/frontend_it.json

@ -12,11 +12,11 @@
"apps.appNameWarning": "Il nome della app non potrà essere cambiato in un secondo momento.",
"apps.appsButtonCreate": "Nuova App",
"apps.appsButtonFallbackTitle": "Lista App",
"apps.archieve": "Archivia l'App",
"apps.archieveConfirmText": "Rimuovi il pattern",
"apps.archieveConfirmTitle": "Sei sicuro di voler archiviare questa app?",
"apps.archieveWarning": "Una volta archiviata una App, non è possibile tornare indietro. Sii certo.",
"apps.archive": "Archivia l'App",
"apps.archiveConfirmText": "Rimuovi il pattern",
"apps.archiveConfirmTitle": "Sei sicuro di voler archiviare questa app?",
"apps.archiveFailed": "Non è stato possibile archiviare l'app. Per favore ricarica.",
"apps.archiveWarning": "Una volta archiviata una App, non è possibile tornare indietro. Sii certo.",
"apps.create": "Crea un'App",
"apps.createBlankApp": "Nuova App.",
"apps.createBlankAppDescription": "Crea una app vuota senza contenuti o schema.",

8
backend/i18n/source/frontend_nl.json

@ -12,11 +12,11 @@
"apps.appNameWarning": "De app-naam kan later niet worden gewijzigd.",
"apps.appsButtonCreate": "Apps-overzicht",
"apps.appsButtonFallbackTitle": "Apps-overzicht",
"apps.archieve": "App archiveren",
"apps.archieveConfirmText": "Patroon verwijderen",
"apps.archieveConfirmTitle": "Wil je deze app echt archiveren?",
"apps.archieveWarning": "Zodra je een app archiveert, is er geen weg meer terug. Wees alsjeblieft zeker.",
"apps.archive": "App archiveren",
"apps.archiveConfirmText": "Patroon verwijderen",
"apps.archiveConfirmTitle": "Wil je deze app echt archiveren?",
"apps.archiveFailed": "Kan app niet archiveren. Laad opnieuw.",
"apps.archiveWarning": "Zodra je een app archiveert, is er geen weg meer terug. Wees alsjeblieft zeker.",
"apps.create": "App maken",
"apps.createBlankApp": "Nieuwe app.",
"apps.createBlankAppDescription": "Maak een nieuwe lege app zonder inhoud en schema's.",

10
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs

@ -54,8 +54,6 @@ namespace Squidex.Domain.Apps.Entities.Assets
public override Task<object?> ExecuteAsync(IAggregateCommand command)
{
VerifyNotDeleted();
switch (command)
{
case CreateAssetFolder createAssetFolder:
@ -123,13 +121,5 @@ namespace Squidex.Domain.Apps.Entities.Assets
RaiseEvent(Envelope.Create(@event));
}
private void VerifyNotDeleted()
{
if (Snapshot.IsDeleted)
{
throw new DomainException(T.Get("assets.assetFolderAlreadyDeleted"));
}
}
}
}

5
backend/src/Squidex.Domain.Users/PwnedPasswordValidator.cs

@ -29,6 +29,11 @@ namespace Squidex.Domain.Users
public async Task<IdentityResult> ValidateAsync(UserManager<IdentityUser> manager, IdentityUser user, string password)
{
if (string.IsNullOrWhiteSpace(password))
{
return IdentityResult.Success;
}
try
{
var isBreached = await client.IsPasswordPwned(password);

2
backend/src/Squidex.Infrastructure/DomainObjectConflictException.cs

@ -26,7 +26,7 @@ namespace Squidex.Infrastructure
private static string FormatMessage(string id)
{
return T.Get("exceptions.domainObjectDeleted", new { id });
return T.Get("exceptions.domainObjectConflict", new { id });
}
}
}

18
backend/src/Squidex.Shared/Texts.it.resx

@ -106,9 +106,6 @@
<data name="dotnet_annotations_StringLengthMinimum" xml:space="preserve">
<value>The field {0} must be a string with a minimum length of {1} and a maximum length of {2}.</value>
</data>
<data name="apps.alreadyArchieved" xml:space="preserve">
<value>La App è stata già archiviata.</value>
</data>
<data name="apps.clients.idAlreadyExists" xml:space="preserve">
<value>Un client con lo stesso id esiste già.</value>
</data>
@ -169,12 +166,6 @@
<data name="apps.roles.usedRoleByContributorsNotRemovable" xml:space="preserve">
<value>Non è possibile rimuovere un ruolo quando questo è assegnato ad un collaboratore.</value>
</data>
<data name="assets.assetAlreadyDeleted" xml:space="preserve">
<value>La risorsa è stata già eliminata</value>
</data>
<data name="assets.assetFolderAlreadyDeleted" xml:space="preserve">
<value>La cartella delle risorse è stata già eliminata</value>
</data>
<data name="assets.folderNotFound" xml:space="preserve">
<value>La cartella delle risorse non esiste.</value>
</data>
@ -463,9 +454,6 @@
<data name="contents.draftToDeleteNotFound" xml:space="preserve">
<value>Non c'è niente da eliminare.</value>
</data>
<data name="contents.invalidArrayOfIds" xml:space="preserve">
<value>Errore nel json, atteso un array di string guid.</value>
</data>
<data name="contents.invalidArrayOfObjects" xml:space="preserve">
<value>Errore nel json, attesp un array di objects.</value>
</data>
@ -832,15 +820,9 @@
<data name="login.githubPrivateEmail" xml:space="preserve">
<value>Il tuo indirizzo email è impostato su privato in Github. Impostalo come pubblico per poter utilizzare il login Github.</value>
</data>
<data name="rules.alreadyDeleted" xml:space="preserve">
<value>La regola è stata già cancellata.</value>
</data>
<data name="rules.ruleAlreadyRunning" xml:space="preserve">
<value>E' in esecuzione un'altra regola.</value>
</data>
<data name="schemas.alreadyDeleted" xml:space="preserve">
<value>Lo schema è stato già cancellato.</value>
</data>
<data name="schemas.dateTimeCalculatedDefaultAndDefaultError" xml:space="preserve">
<value>Il valore predefinito calcolato e il valore predefinito non possono essere utilizzati insieme.</value>
</data>

18
backend/src/Squidex.Shared/Texts.nl.resx

@ -106,9 +106,6 @@
<data name="dotnet_annotations_StringLengthMinimum" xml:space="preserve">
<value>Het veld {0} moet een string zijn met een minimum lengte van {1} en een maximum lengte van {2}.</value>
</data>
<data name="apps.alreadyArchieved" xml:space="preserve">
<value>App is al gearchiveerd.</value>
</data>
<data name="apps.clients.idAlreadyExists" xml:space="preserve">
<value>Er bestaat al een client met dezelfde id.</value>
</data>
@ -169,12 +166,6 @@
<data name="apps.roles.usedRoleByContributorsNotRemovable" xml:space="preserve">
<value>Kan een rol niet verwijderen wanneer een bijdrager is toegewezen.</value>
</data>
<data name="assets.assetAlreadyDeleted" xml:space="preserve">
<value>Asset is al verwijderd</value>
</data>
<data name="assets.assetFolderAlreadyDeleted" xml:space="preserve">
<value>Assetmap is al verwijderd</value>
</data>
<data name="assets.folderNotFound" xml:space="preserve">
<value>Assetmap bestaat niet.</value>
</data>
@ -463,9 +454,6 @@
<data name="contents.draftToDeleteNotFound" xml:space="preserve">
<value>Er is niets te verwijderen.</value>
</data>
<data name="contents.invalidArrayOfIds" xml:space="preserve">
<value>Ongeldig json-type, verwachte array van guid-strings.</value>
</data>
<data name="contents.invalidArrayOfObjects" xml:space="preserve">
<value>Ongeldig json-type, verwachte reeks objecten.</value>
</data>
@ -832,15 +820,9 @@
<data name="login.githubPrivateEmail" xml:space="preserve">
<value>Jouw e-mailadres is ingesteld op privé in Github. Stel het in op openbaar om Github-login te gebruiken.</value>
</data>
<data name="rules.alreadyDeleted" xml:space="preserve">
<value>Regel is al verwijderd.</value>
</data>
<data name="rules.ruleAlreadyRunning" xml:space="preserve">
<value>Er wordt al een andere regel uitgevoerd.</value>
</data>
<data name="schemas.alreadyDeleted" xml:space="preserve">
<value>Schema is al verwijderd.</value>
</data>
<data name="schemas.dateTimeCalculatedDefaultAndDefaultError" xml:space="preserve">
<value>Berekende standaardwaarde en standaardwaarde kunnen niet samen worden gebruikt.</value>
</data>

18
backend/src/Squidex.Shared/Texts.resx

@ -106,9 +106,6 @@
<data name="dotnet_annotations_StringLengthMinimum" xml:space="preserve">
<value>The field {0} must be a string with a minimum length of {1} and a maximum length of {2}.</value>
</data>
<data name="apps.alreadyArchieved" xml:space="preserve">
<value>App has already been archived.</value>
</data>
<data name="apps.clients.idAlreadyExists" xml:space="preserve">
<value>A client with the same id already exists.</value>
</data>
@ -169,12 +166,6 @@
<data name="apps.roles.usedRoleByContributorsNotRemovable" xml:space="preserve">
<value>Cannot remove a role when a contributor is assigned.</value>
</data>
<data name="assets.assetAlreadyDeleted" xml:space="preserve">
<value>Asset has already been deleted</value>
</data>
<data name="assets.assetFolderAlreadyDeleted" xml:space="preserve">
<value>Asset folder has already been deleted</value>
</data>
<data name="assets.folderNotFound" xml:space="preserve">
<value>Asset folder does not exist.</value>
</data>
@ -463,9 +454,6 @@
<data name="contents.draftToDeleteNotFound" xml:space="preserve">
<value>There is nothing to delete.</value>
</data>
<data name="contents.invalidArrayOfIds" xml:space="preserve">
<value>Invalid json type, expected array of guid strings.</value>
</data>
<data name="contents.invalidArrayOfObjects" xml:space="preserve">
<value>Invalid json type, expected array of objects.</value>
</data>
@ -832,15 +820,9 @@
<data name="login.githubPrivateEmail" xml:space="preserve">
<value>Your email address is set to private in Github. Please set it to public to use Github login.</value>
</data>
<data name="rules.alreadyDeleted" xml:space="preserve">
<value>Rule has already been deleted.</value>
</data>
<data name="rules.ruleAlreadyRunning" xml:space="preserve">
<value>Another rule is already running.</value>
</data>
<data name="schemas.alreadyDeleted" xml:space="preserve">
<value>Schema has already been deleted.</value>
</data>
<data name="schemas.dateTimeCalculatedDefaultAndDefaultError" xml:space="preserve">
<value>Calculated default value and default value cannot be used together.</value>
</data>

20
backend/src/Squidex/Areas/Frontend/Middlewares/IndexExtensions.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.IO;
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -33,20 +34,23 @@ namespace Squidex.Areas.Frontend.Middlewares
return context.Request.Path.Value.EndsWith(".html", StringComparison.OrdinalIgnoreCase);
}
public static bool IsHtml(this HttpContext context)
public static bool IsNotModified(this HttpResponse response)
{
return context.Response.ContentType?.ToLower().Contains("text/html") == true;
return response.StatusCode == (int)HttpStatusCode.NotModified;
}
public static string AdjustHtml(this string html, HttpContext httpContext)
public static string AdjustBase(this string html, HttpContext httpContext)
{
var result = html;
if (httpContext.Request.PathBase.HasValue)
{
result = result.Replace("<base href=\"/\">", $"<base href=\"{httpContext.Request.PathBase}/\">");
html = html.Replace("<base href=\"/\">", $"<base href=\"{httpContext.Request.PathBase}/\">");
}
return html;
}
public static string AddOptions(this string html, HttpContext httpContext)
{
var uiOptions = httpContext.RequestServices.GetService<IOptions<MyUIOptions>>()?.Value;
if (uiOptions != null)
@ -72,10 +76,10 @@ namespace Squidex.Areas.Frontend.Middlewares
var texts = GetText(CultureInfo.CurrentUICulture.Name);
result = result.Replace("<body>", $"<body>\n<script>\nvar options = {jsonOptions};\nvar texts = {texts};</script>");
html = html.Replace("<body>", $"<body>\n<script>\nvar options = {jsonOptions};\nvar texts = {texts};</script>");
}
return result;
return html;
}
private static string GetText(string culture)

11
backend/src/Squidex/Areas/Frontend/Middlewares/IndexMiddleware.cs

@ -23,7 +23,7 @@ namespace Squidex.Areas.Frontend.Middlewares
public async Task InvokeAsync(HttpContext context)
{
if (context.IsHtmlPath() && context.Response.StatusCode != 304)
if (context.IsHtmlPath() && !context.Response.IsNotModified())
{
var responseBuffer = new MemoryStream();
var responseBody = context.Response.Body;
@ -32,13 +32,18 @@ namespace Squidex.Areas.Frontend.Middlewares
await next(context);
if (context.Response.StatusCode != 304)
if (!context.Response.IsNotModified())
{
context.Response.Body = responseBody;
var html = Encoding.UTF8.GetString(responseBuffer.ToArray());
html = html.AdjustHtml(context);
html = html.AdjustBase(context);
if (context.IsIndex())
{
html = html.AddOptions(context);
}
context.Response.ContentLength = Encoding.UTF8.GetByteCount(html);
context.Response.Body = responseBody;

28
backend/src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
@ -26,7 +27,7 @@ namespace Squidex.Areas.Frontend.Middlewares
public async Task InvokeAsync(HttpContext context)
{
if (context.IsIndex() && context.Response.StatusCode != 304)
if (context.IsIndex() && !context.Response.IsNotModified())
{
var handler = new HttpClientHandler
{
@ -43,35 +44,12 @@ namespace Squidex.Areas.Frontend.Middlewares
{
var html = await result.Content.ReadAsStringAsync();
html = html.AdjustHtml(context);
html = html.AdjustBase(context);
await context.Response.WriteAsync(html);
}
}
}
else if (context.IsHtmlPath() && context.Response.StatusCode != 304)
{
var responseBuffer = new MemoryStream();
var responseBody = context.Response.Body;
context.Response.Body = responseBuffer;
await next(context);
if (context.Response.StatusCode != 304)
{
context.Response.Body = responseBody;
var html = Encoding.UTF8.GetString(responseBuffer.ToArray());
html = html.AdjustHtml(context);
context.Response.ContentLength = Encoding.UTF8.GetByteCount(html);
context.Response.Body = responseBody;
await context.Response.WriteAsync(html);
}
}
else
{
await next(context);

6
backend/src/Squidex/Areas/Frontend/Startup.cs

@ -52,14 +52,12 @@ namespace Squidex.Areas.Frontend
return next();
});
app.UseMiddleware<IndexMiddleware>();
if (environment.IsDevelopment())
{
app.UseMiddleware<WebpackMiddleware>();
}
else
{
app.UseMiddleware<IndexMiddleware>();
}
app.UseStaticFiles(new StaticFileOptions
{

4
frontend/app/features/apps/pages/apps-page.component.html

@ -1,8 +1,8 @@
<sqx-title message="i18n:apps.listPageTitle"></sqx-title>
<div class="panel-container page">
<div class="panel-container page" *ngIf="authState.userChanges | async; let user">
<div class="apps-section">
<h1 class="apps-title">{{ 'apps.welcomeTitle' | sqxTranslate: { user: authState.user?.displayName } }}</h1>
<h1 class="apps-title">{{ 'apps.welcomeTitle' | sqxTranslate: { user: user.displayName } }}</h1>
<div class="subtext">
{{ 'apps.welcomeSubtitle' | sqxTranslate }}

2
frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.html

@ -37,7 +37,7 @@
<input type="url" class="form-control" id="contentsSidebarUrl" formControlName="contentsSidebarUrl">
<sqx-form-hint>{{ 'schemas.contentsSidebarUrl' | sqxTranslate }}</sqx-form-hint>
<sqx-form-hint>{{ 'schemas.contentsSidebarUrlHint' | sqxTranslate }}</sqx-form-hint>
</div>
<div class="form-group">

10
frontend/app/features/settings/pages/more/more-page.component.html

@ -90,18 +90,18 @@
<div class="card-body">
<div class="row">
<div class="col">
<h5>{{ 'apps.archieve' | sqxTranslate }}</h5>
<h5>{{ 'apps.archive' | sqxTranslate }}</h5>
<sqx-form-hint>
{{ 'apps.archieveWarning' | sqxTranslate }}
{{ 'apps.archiveWarning' | sqxTranslate }}
</sqx-form-hint>
</div>
<div class="col-auto">
<button type="button" class="btn btn-danger" [disabled]="!isDeletable"
(sqxConfirmClick)="archiveApp()"
confirmTitle="i18n:apps.archieveConfirmTitle"
confirmText="i18n:apps.archieveConfirmText">
{{ 'apps.archieve' | sqxTranslate }}
confirmTitle="i18n:apps.archiveConfirmTitle"
confirmText="i18n:apps.archiveConfirmText">
{{ 'apps.archive' | sqxTranslate }}
</button>
</div>
</div>

2
frontend/app/features/settings/pages/roles/role.component.html

@ -81,7 +81,7 @@
<form class="form-group row no-gutters" [formGroup]="addPermissionForm.form" (ngSubmit)="addPermission()" *ngIf="isEditable">
<div class="col">
<sqx-autocomplete formControlName="permission" [source]="allPermissions" #addInput
placeholder="{{ 'i18n:roles.permissionsPlaceholder' | sqxTranslate }}">
placeholder="{{ 'roles.permissionsPlaceholder' | sqxTranslate }}">
</sqx-autocomplete>
</div>
<div class="col-auto pl-1">

33
frontend/app/framework/angular/routers/router-2-state.spec.ts

@ -23,17 +23,17 @@ describe('Router2State', () => {
synchronizer.writeValue(value, params);
expect(params['key']).toEqual('my-string');
expect(params).toEqual({ key: 'my-string' });
});
it('should not write value to route when not a string', () => {
it('Should write undefined when not a string', () => {
const params: Params = {};
const value = 123;
synchronizer.writeValue(value, params);
expect(params).toEqual({});
expect(params).toEqual({ key: undefined });
});
it('should get string from route', () => {
@ -60,27 +60,27 @@ describe('Router2State', () => {
synchronizer.writeValue(value, params);
expect(params['key']).toEqual('flag1,flag2');
expect(params).toEqual({ key: 'flag1,flag2' });
});
it('should write empty object to route', () => {
it('Should write undefined when empty', () => {
const params: Params = {};
const value = {};
synchronizer.writeValue(value, params);
expect(params['key']).toEqual('');
expect(params).toEqual({ key: undefined });
});
it('should not write value to route when not an object', () => {
it('Should write undefined when not an object', () => {
const params: Params = {};
const value = 123;
synchronizer.writeValue(value, params);
expect(params).toEqual({});
expect(params).toEqual({ key: undefined });
});
it('should get object from route', () => {
@ -117,45 +117,44 @@ describe('Router2State', () => {
synchronizer.writeValue(value, params);
expect(params['page']).toEqual('10');
expect(params['take']).toEqual('20');
expect(params).toEqual({ take: '20', page: '10' });
localStore.verify(x => x.setInt('contents.pageSize', 20), Times.once());
});
it('should not write page if zero', () => {
it('Should write undefined when page number is zero', () => {
const params: Params = {};
const value = new Pager(0, 0, 20, true);
synchronizer.writeValue(value, params);
expect(params['page']).toBeUndefined();
expect(params['take']).toEqual('20');
expect(params).toEqual({ take: '20', page: undefined });
localStore.verify(x => x.setInt('contents.pageSize', 20), Times.once());
});
it('should not write value to route when not pager', () => {
it('should write undefined when value not a pager', () => {
const params: Params = {};
const value = 123;
synchronizer.writeValue(value, params);
expect(params).toEqual({});
expect(params).toEqual({ take: undefined, page: undefined });
localStore.verify(x => x.setInt('contents.pageSize', 20), Times.never());
});
it('should not write value to route when null', () => {
it('should write undefined when value is null', () => {
const params: Params = {};
const value = null;
synchronizer.writeValue(value, params);
expect(params).toEqual({});
expect(params).toEqual({ take: undefined, page: undefined });
localStore.verify(x => x.setInt('contents.pageSize', 20), Times.never());
});

11
frontend/app/framework/angular/routers/router-2-state.ts

@ -54,6 +54,9 @@ export class PagerSynchronizer implements RouteSynchronizer {
}
public writeValue(state: any, params: Params) {
params['page'] = undefined;
params['take'] = undefined;
if (Types.is(state, Pager)) {
if (state.page > 0) {
params['page'] = state.page.toString();
@ -81,6 +84,8 @@ export class StringSynchronizer implements RouteSynchronizer {
}
public writeValue(state: any, params: Params) {
params[this.name] = undefined;
if (Types.isString(state)) {
params[this.name] = state;
}
@ -110,10 +115,14 @@ export class StringKeysSynchronizer implements RouteSynchronizer {
}
public writeValue(state: any, params: Params) {
params[this.name] = undefined;
if (Types.isObject(state)) {
const value = Object.keys(state).join(',');
params[this.name] = value;
if (value.length > 0) {
params[this.name] = value;
}
}
}
}

1
frontend/app/shared/components/assets/asset-folder.component.scss

@ -14,6 +14,7 @@ img {
& {
border-bottom-width: 1px;
border-width: 1px;
cursor: default;
width: $asset-width;
}

107
frontend/app/shared/state/query.spec.ts

@ -1,3 +1,4 @@
import { Params } from '@angular/router';
/*
* Squidex Headless CMS
*
@ -6,7 +7,7 @@
*/
import { Query } from '@app/shared/internal';
import { equalsQuery } from './query';
import { equalsQuery, QueryFullTextSynchronizer, QuerySynchronizer } from './query';
describe('equalsQuery', () => {
it('should return true when comparing with empty query', () => {
@ -53,4 +54,108 @@ describe('equalsQuery', () => {
expect(equalsQuery(lhs, rhs)).toBeTruthy();
});
});
describe('QueryFullTextSynchronizer', () => {
const synchronizer = new QueryFullTextSynchronizer();
it('should write full text to route', () => {
const params: Params = {};
const value = { fullText: 'my-query' };
synchronizer.writeValue(value, params);
expect(params).toEqual({ query: 'my-query' });
});
it('Should write undefined when not a query', () => {
const params: Params = {};
const value = 123;
synchronizer.writeValue(value, params);
expect(params).toEqual({ query: undefined });
});
it('Should write undefined query has no full text', () => {
const params: Params = {};
const value = { fullText: '' };
synchronizer.writeValue(value, params);
expect(params).toEqual({ query: undefined });
});
it('should get query from route', () => {
const params: Params = {
query: 'my-query'
};
const value = synchronizer.getValue(params);
expect(value).toEqual({ fullText: 'my-query' });
});
it('should get query as undefined from route', () => {
const params: Params = {};
const value = synchronizer.getValue(params);
expect(value).toBeUndefined();
});
});
describe('QuerySynchronizer', () => {
const synchronizer = new QuerySynchronizer();
it('should write query to route', () => {
const params: Params = {};
const value = { filter: 'my-filter' };
synchronizer.writeValue(value, params);
expect(params).toEqual({ query: '{"filter":"my-filter","sort":[]}' });
});
it('Should write undefined when not a query', () => {
const params: Params = {};
const value = 123;
synchronizer.writeValue(value, params);
expect(params).toEqual({ query: undefined });
});
it('should get query from route', () => {
const params: Params = {
query: '{"filter":"my-filter"}'
};
const value = synchronizer.getValue(params) as any;
expect(value).toEqual({ filter: 'my-filter' });
});
it('should get query full text from route', () => {
const params: Params = {
query: 'my-query'
};
const value = synchronizer.getValue(params);
expect(value).toEqual({ fullText: 'my-query' });
});
it('should get query as undefined from route', () => {
const params: Params = {};
const value = synchronizer.getValue(params);
expect(value).toBeUndefined();
});
});

4
frontend/app/shared/state/query.ts

@ -128,6 +128,8 @@ export class QueryFullTextSynchronizer implements RouteSynchronizer {
}
public writeValue(state: any, params: Params) {
params['query'] = undefined;
if (Types.isObject(state) && Types.isString(state.fullText) && state.fullText.length > 0) {
params['query'] = state.fullText;
}
@ -144,6 +146,8 @@ export class QuerySynchronizer implements RouteSynchronizer {
}
public writeValue(state: any, params: Params) {
params['query'] = undefined;
if (Types.isObject(state)) {
params['query'] = serializeQuery(state);
}

Loading…
Cancel
Save