Browse Source

Standalone (#1042)

* Started with migration.

* Fix.

* Fix import order.

* Fix storybook.

* Fix copy

* Fix hash and regex.

* Improve hash provider again.
pull/1043/head
Sebastian Stehle 2 years ago
committed by GitHub
parent
commit
4ecb4e4942
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Dockerfile
  2. 2
      backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj
  3. 12
      backend/extensions/Squidex.Extensions/Text/ElasticSearch/ElasticSearchTextIndex.cs
  4. 2
      backend/i18n/translator/Squidex.Translator/Squidex.Translator.csproj
  5. 2
      backend/src/Migrations/Migrations.csproj
  6. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
  7. 11
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs
  8. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  9. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
  10. 11
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplatesClient.cs
  11. 2
      backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  12. 2
      backend/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj
  13. 2
      backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
  14. 2
      backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj
  15. 2
      backend/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj
  16. 2
      backend/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj
  17. 2
      backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
  18. 7
      backend/src/Squidex.Infrastructure/Language.cs
  19. 2
      backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  20. 12
      backend/src/Squidex.Infrastructure/StringExtensions.cs
  21. 9
      backend/src/Squidex.Shared/DefaultClients.cs
  22. 35
      backend/src/Squidex.Shared/Identity/SquidexClaimTypes.cs
  23. 325
      backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs
  24. 35
      backend/src/Squidex.Shared/PermissionExtensions.cs
  25. 419
      backend/src/Squidex.Shared/PermissionIds.cs
  26. 3
      backend/src/Squidex.Shared/Squidex.Shared.csproj
  27. 15
      backend/src/Squidex.Shared/Texts.cs
  28. 63
      backend/src/Squidex.Shared/Users/ClientUser.cs
  29. 17
      backend/src/Squidex.Shared/Users/IUser.cs
  30. 35
      backend/src/Squidex.Shared/Users/IUserResolver.cs
  31. 7
      backend/src/Squidex.Web/IgnoreHashFileProvider.cs
  32. 2
      backend/src/Squidex.Web/Squidex.Web.csproj
  33. 2
      backend/src/Squidex/Squidex.csproj
  34. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj
  35. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj
  36. 2
      backend/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj
  37. 2
      backend/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj
  38. 2
      backend/tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj
  39. 2
      backend/tools/GenerateLanguages/GenerateLanguages.csproj
  40. 13
      frontend/.storybook/main.js
  41. 15
      frontend/angular.json
  42. 383
      frontend/package-lock.json
  43. 2
      frontend/package.json
  44. 20
      frontend/src/app/app.component.ts
  45. 129
      frontend/src/app/app.module.ts
  46. 43
      frontend/src/app/app.routes.ts
  47. 22
      frontend/src/app/assets/squid.svg
  48. 15
      frontend/src/app/features/administration/administration-area.component.ts
  49. 16
      frontend/src/app/features/administration/declarations.ts
  50. 44
      frontend/src/app/features/administration/guards/user-must-exist.guard.spec.ts
  51. 47
      frontend/src/app/features/administration/guards/user-must-exist.guard.ts
  52. 9
      frontend/src/app/features/administration/pages/event-consumers/event-consumer.component.ts
  53. 27
      frontend/src/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
  54. 25
      frontend/src/app/features/administration/pages/restore/restore-page.component.ts
  55. 20
      frontend/src/app/features/administration/pages/users/user-page.component.ts
  56. 15
      frontend/src/app/features/administration/pages/users/user.component.ts
  57. 31
      frontend/src/app/features/administration/pages/users/users-page.component.ts
  58. 52
      frontend/src/app/features/administration/routes.ts
  59. 2
      frontend/src/app/features/administration/services/event-consumers.service.spec.ts
  60. 2
      frontend/src/app/features/administration/services/users.service.spec.ts
  61. 6
      frontend/src/app/features/administration/state/event-consumers.state.spec.ts
  62. 2
      frontend/src/app/features/administration/state/event-consumers.state.ts
  63. 2
      frontend/src/app/features/administration/state/users.forms.ts
  64. 4
      frontend/src/app/features/administration/state/users.state.spec.ts
  65. 2
      frontend/src/app/features/administration/state/users.state.ts
  66. 14
      frontend/src/app/features/api/api-area.component.ts
  67. 9
      frontend/src/app/features/api/declarations.ts
  68. 9
      frontend/src/app/features/api/index.ts
  69. 37
      frontend/src/app/features/api/module.ts
  70. 19
      frontend/src/app/features/api/pages/graphql/graphql-page.component.ts
  71. 23
      frontend/src/app/features/api/routes.ts
  72. 12
      frontend/src/app/features/apps/declarations.ts
  73. 9
      frontend/src/app/features/apps/index.ts
  74. 34
      frontend/src/app/features/apps/module.ts
  75. 17
      frontend/src/app/features/apps/pages/app.component.ts
  76. 24
      frontend/src/app/features/apps/pages/apps-page.component.ts
  77. 11
      frontend/src/app/features/apps/pages/news-dialog.component.ts
  78. 16
      frontend/src/app/features/apps/pages/onboarding-dialog.component.ts
  79. 13
      frontend/src/app/features/apps/pages/team.component.ts
  80. 16
      frontend/src/app/features/apps/routes.ts
  81. 11
      frontend/src/app/features/assets/declarations.ts
  82. 9
      frontend/src/app/features/assets/index.ts
  83. 39
      frontend/src/app/features/assets/module.ts
  84. 15
      frontend/src/app/features/assets/pages/asset-tag-dialog.component.ts
  85. 13
      frontend/src/app/features/assets/pages/asset-tags.component.ts
  86. 12
      frontend/src/app/features/assets/pages/assets-filters-page.component.ts
  87. 31
      frontend/src/app/features/assets/pages/assets-page.component.ts
  88. 23
      frontend/src/app/features/assets/routes.ts
  89. 43
      frontend/src/app/features/content/declarations.ts
  90. 9
      frontend/src/app/features/content/index.ts
  91. 135
      frontend/src/app/features/content/module.ts
  92. 23
      frontend/src/app/features/content/pages/calendar/calendar-page.component.ts
  93. 8
      frontend/src/app/features/content/pages/comments/comments-page.component.ts
  94. 13
      frontend/src/app/features/content/pages/content/content-event.component.ts
  95. 25
      frontend/src/app/features/content/pages/content/content-history-page.component.ts
  96. 44
      frontend/src/app/features/content/pages/content/content-page.component.ts
  97. 20
      frontend/src/app/features/content/pages/content/editor/content-editor.component.ts
  98. 13
      frontend/src/app/features/content/pages/content/inspecting/content-inspection.component.ts
  99. 18
      frontend/src/app/features/content/pages/content/references/content-references.component.ts
  100. 12
      frontend/src/app/features/content/pages/contents/contents-filters-page.component.ts

2
Dockerfile

@ -86,7 +86,7 @@ WORKDIR /app
# Copy from build stages
COPY --from=backend /build/ .
COPY --from=frontend /build/ wwwroot/build/
COPY --from=frontend /build/browser wwwroot/build/
EXPOSE 80
EXPOSE 443

2
backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

12
backend/extensions/Squidex.Extensions/Text/ElasticSearch/ElasticSearchTextIndex.cs

@ -15,10 +15,10 @@ using Squidex.Infrastructure.Json;
namespace Squidex.Extensions.Text.ElasticSearch;
public sealed class ElasticSearchTextIndex : ITextIndex, IInitializable
public sealed partial class ElasticSearchTextIndex : ITextIndex, IInitializable
{
private static readonly Regex LanguageRegex = new Regex(@"[^\w]+([a-z\-_]{2,}):", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
private static readonly Regex LanguageRegexStart = new Regex(@"$^([a-z\-_]{2,}):", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
private static readonly Regex RegexLanguageNormal = BuildLanguageRegexNormal();
private static readonly Regex RegexLanguageStart = BuildLanguageRegexStart();
private readonly IJsonSerializer jsonSerializer;
private readonly IElasticSearchClient elasticClient;
private readonly QueryParser queryParser = new QueryParser(ElasticSearchIndexDefinition.GetFieldPath);
@ -227,4 +227,10 @@ public sealed class ElasticSearchTextIndex : ITextIndex, IInitializable
{
return scope == SearchScope.Published ? "servePublished" : "serveAll";
}
[GeneratedRegex("[^\\w]+([a-z\\-_]{2,}):", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
private static partial Regex BuildLanguageRegexNormal();
[GeneratedRegex("$^([a-z\\-_]{2,}):", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
private static partial Regex BuildLanguageRegexStart();
}

2
backend/i18n/translator/Squidex.Translator/Squidex.Translator.csproj

@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>NU1608</NoWarn>
</PropertyGroup>

2
backend/src/Migrations/Migrations.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

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

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Domain.Apps.Core</RootNamespace>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<NeutralLanguage>en</NeutralLanguage>
<Nullable>enable</Nullable>

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

@ -23,7 +23,7 @@ using Squidex.Text;
namespace Squidex.Domain.Apps.Core.Scripting;
public sealed class ScriptingCompleter
public sealed partial class ScriptingCompleter
{
private readonly IEnumerable<IScriptDescriptor> descriptors;
private static readonly FilterSchema DynamicData = new FilterSchema(FilterSchemaType.Object)
@ -112,9 +112,9 @@ public sealed class ScriptingCompleter
return new Process(descriptors).UsageTrigger();
}
private sealed class Process
private sealed partial class Process
{
private static readonly Regex PropertyRegex = new Regex(@"^(?!\d)[\w$]+$", RegexOptions.Compiled);
private static readonly Regex RegexProperty = BuildPropertyRegex();
private readonly Stack<string> prefixes = new Stack<string>();
private readonly Dictionary<string, ScriptingValue> result = new Dictionary<string, ScriptingValue>();
private readonly IEnumerable<IScriptDescriptor> descriptors;
@ -523,7 +523,7 @@ public sealed class ScriptingCompleter
{
prefixes.Push(name);
}
else if (PropertyRegex.IsMatch(name))
else if (RegexProperty.IsMatch(name))
{
prefixes.Push($".{name}");
}
@ -532,5 +532,8 @@ public sealed class ScriptingCompleter
prefixes.Push($"['{name}']");
}
}
[GeneratedRegex("^(?!\\d)[\\w$]+$", RegexOptions.Compiled)]
private static partial Regex BuildPropertyRegex();
}
}

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

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Domain.Apps.Core</RootNamespace>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<NeutralLanguage>en</NeutralLanguage>
<Nullable>enable</Nullable>

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

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

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

@ -11,9 +11,9 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Templates;
public sealed class TemplatesClient
public sealed partial class TemplatesClient
{
private static readonly Regex Regex = new Regex("\\* \\[(?<Title>.*)\\]\\((?<Name>.*)\\/README\\.md\\): (?<Description>.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
private static readonly Regex RegexTemplate = BuildTemplateRegex();
private readonly IHttpClientFactory httpClientFactory;
private readonly TemplatesOptions options;
@ -34,7 +34,7 @@ public sealed class TemplatesClient
var text = await httpClient.GetStringAsync(url, ct);
foreach (Match match in Regex.Matches(text).OfType<Match>())
foreach (var match in RegexTemplate.Matches(text).OfType<Match>())
{
var currentName = match.Groups["Name"].Value;
@ -61,7 +61,7 @@ public sealed class TemplatesClient
var text = await httpClient.GetStringAsync(url, ct);
foreach (Match match in Regex.Matches(text).OfType<Match>())
foreach (Match match in RegexTemplate.Matches(text).OfType<Match>())
{
var title = match.Groups["Title"].Value;
@ -97,4 +97,7 @@ public sealed class TemplatesClient
return null;
}
[GeneratedRegex("\\* \\[(?<Title>.*)\\]\\((?<Name>.*)\\/README\\.md\\): (?<Description>.*)", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
private static partial Regex BuildTemplateRegex();
}

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

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<NeutralLanguage>en</NeutralLanguage>
<Nullable>enable</Nullable>

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

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

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

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

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

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

2
backend/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Infrastructure</RootNamespace>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

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

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Infrastructure</RootNamespace>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

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

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Infrastructure</RootNamespace>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

7
backend/src/Squidex.Infrastructure/Language.cs

@ -14,7 +14,7 @@ namespace Squidex.Infrastructure;
[TypeConverter(typeof(LanguageTypeConverter))]
public partial record Language
{
private static readonly Regex CultureRegex = new Regex("^(?<Code>[a-z]{2})(\\-[a-z]{2})?$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
private static readonly Regex RegexCulture = BuildCultureRegex();
public static Language GetLanguage(string iso2Code)
{
@ -85,7 +85,7 @@ public partial record Language
if (input.Length != 2)
{
var match = CultureRegex.Match(input);
var match = RegexCulture.Match(input);
if (!match.Success)
{
@ -107,4 +107,7 @@ public partial record Language
{
return EnglishName;
}
[GeneratedRegex("^(?<Code>[a-z]{2})(\\-[a-z]{2})?$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture, "en-US")]
private static partial Regex BuildCultureRegex();
}

2
backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<NeutralLanguage>en</NeutralLanguage>
<Nullable>enable</Nullable>

12
backend/src/Squidex.Infrastructure/StringExtensions.cs

@ -13,10 +13,10 @@ using System.Text.RegularExpressions;
namespace Squidex.Infrastructure;
public static class StringExtensions
public static partial class StringExtensions
{
private static readonly Regex RegexEmail = new Regex(@"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-||_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+([a-z]+|\d|-|\.{0,1}|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
private static readonly Regex RegexProperty = new Regex("^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
private static readonly Regex RegexEmail = BuildEmailRegex();
private static readonly Regex RegexProperty = BuildPropertyRegex();
private static readonly JsonSerializerOptions JsonEscapeOptions = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
@ -113,4 +113,10 @@ public static class StringExtensions
return sb;
}
[GeneratedRegex("^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-||_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+([a-z]+|\\d|-|\\.{0,1}|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])?([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled, "en-US")]
private static partial Regex BuildEmailRegex();
[GeneratedRegex("^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*$", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
private static partial Regex BuildPropertyRegex();
}

9
backend/src/Squidex.Shared/DefaultClients.cs

@ -5,10 +5,9 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Shared
namespace Squidex.Shared;
public static class DefaultClients
{
public static class DefaultClients
{
public const string Frontend = "squidex-frontend";
}
public const string Frontend = "squidex-frontend";
}

35
backend/src/Squidex.Shared/Identity/SquidexClaimTypes.cs

@ -5,36 +5,35 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Shared.Identity
namespace Squidex.Shared.Identity;
public static class SquidexClaimTypes
{
public static class SquidexClaimTypes
{
public const string ClientSecret = "urn:squidex:clientSecret";
public const string ClientSecret = "urn:squidex:clientSecret";
public const string Consent = "urn:squidex:consent";
public const string Consent = "urn:squidex:consent";
public const string ConsentForEmails = "urn:squidex:consent:emails";
public const string ConsentForEmails = "urn:squidex:consent:emails";
public const string Custom = "urn:squidex:custom";
public const string Custom = "urn:squidex:custom";
public const string DisplayName = "urn:squidex:name";
public const string DisplayName = "urn:squidex:name";
public const string Hidden = "urn:squidex:hidden";
public const string Hidden = "urn:squidex:hidden";
public const string Invited = "urn:squidex:invited";
public const string Invited = "urn:squidex:invited";
public const string NotifoKey = "urn:squidex:notifo";
public const string NotifoKey = "urn:squidex:notifo";
public const string Permissions = "urn:squidex:permissions";
public const string Permissions = "urn:squidex:permissions";
public const string PictureUrl = "urn:squidex:picture";
public const string PictureUrl = "urn:squidex:picture";
public const string PictureUrlStore = "store";
public const string PictureUrlStore = "store";
public const string Answer = "urn:squidex:answer";
public const string Answer = "urn:squidex:answer";
public const string TotalApps = "urn:squidex:internal:totalApps";
public const string TotalApps = "urn:squidex:internal:totalApps";
public const string UIProperty = "urn:squidex:ui";
}
public const string UIProperty = "urn:squidex:ui";
}

325
backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs

@ -15,236 +15,241 @@ using System.Text.RegularExpressions;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Security;
namespace Squidex.Shared.Identity
namespace Squidex.Shared.Identity;
public static partial class SquidexClaimsExtensions
{
public static class SquidexClaimsExtensions
{
private const string ClientPrefix = "client_";
private const string ClientPrefix = "client_";
private static readonly Regex KeyValueAppClaim = new Regex("(?<App>[\\S]+),(?<Key>[^=]+)=(?<Value>.+)", RegexOptions.Compiled);
private static readonly Regex KeyValueClaim = new Regex("(?<Key>[^=]+)=(?<Value>.+)", RegexOptions.Compiled);
private static readonly Regex RegexKeyValueAppClaim = BuildKeyValueAppClaimRegex();
private static readonly Regex RegexKeyValueClaim = BuildKeyValueClaimRegex();
public static PermissionSet Permissions(this IEnumerable<Claim> user)
{
var permissions = user.GetClaims(SquidexClaimTypes.Permissions).Select(x => x.Value);
public static PermissionSet Permissions(this IEnumerable<Claim> user)
{
var permissions = user.GetClaims(SquidexClaimTypes.Permissions).Select(x => x.Value);
return new PermissionSet(permissions);
}
return new PermissionSet(permissions);
}
public static bool IsHidden(this IEnumerable<Claim> user)
{
return user.HasClaimValue(SquidexClaimTypes.Hidden, "true");
}
public static bool IsHidden(this IEnumerable<Claim> user)
{
return user.HasClaimValue(SquidexClaimTypes.Hidden, "true");
}
public static bool HasConsent(this IEnumerable<Claim> user)
{
return user.HasClaimValue(SquidexClaimTypes.Consent, "true");
}
public static bool HasConsent(this IEnumerable<Claim> user)
{
return user.HasClaimValue(SquidexClaimTypes.Consent, "true");
}
public static bool HasConsentForEmails(this IEnumerable<Claim> user)
{
return user.HasClaimValue(SquidexClaimTypes.ConsentForEmails, "true");
}
public static bool HasConsentForEmails(this IEnumerable<Claim> user)
{
return user.HasClaimValue(SquidexClaimTypes.ConsentForEmails, "true");
}
public static bool HasDisplayName(this IEnumerable<Claim> user)
{
return user.HasClaim(SquidexClaimTypes.DisplayName);
}
public static bool HasDisplayName(this IEnumerable<Claim> user)
{
return user.HasClaim(SquidexClaimTypes.DisplayName);
}
public static bool HasPictureUrl(this IEnumerable<Claim> user)
{
return user.HasClaim(SquidexClaimTypes.PictureUrl);
}
public static bool HasPictureUrl(this IEnumerable<Claim> user)
{
return user.HasClaim(SquidexClaimTypes.PictureUrl);
}
public static bool IsPictureUrlStored(this IEnumerable<Claim> user)
{
return user.HasClaimValue(SquidexClaimTypes.PictureUrl, SquidexClaimTypes.PictureUrlStore);
}
public static bool IsPictureUrlStored(this IEnumerable<Claim> user)
{
return user.HasClaimValue(SquidexClaimTypes.PictureUrl, SquidexClaimTypes.PictureUrlStore);
}
public static string? ClientSecret(this IEnumerable<Claim> user)
{
return user.GetClaimValue(SquidexClaimTypes.ClientSecret);
}
public static string? ClientSecret(this IEnumerable<Claim> user)
{
return user.GetClaimValue(SquidexClaimTypes.ClientSecret);
}
public static string? PictureUrl(this IEnumerable<Claim> user)
{
return user.GetClaimValue(SquidexClaimTypes.PictureUrl);
}
public static string? PictureUrl(this IEnumerable<Claim> user)
{
return user.GetClaimValue(SquidexClaimTypes.PictureUrl);
}
public static string? DisplayName(this IEnumerable<Claim> user)
{
return user.GetClaimValue(SquidexClaimTypes.DisplayName);
}
public static string? DisplayName(this IEnumerable<Claim> user)
{
return user.GetClaimValue(SquidexClaimTypes.DisplayName);
}
public static string? Answer(this IEnumerable<Claim> user, string name)
{
var prefix = $"{name}=";
public static string? Answer(this IEnumerable<Claim> user, string name)
{
var prefix = $"{name}=";
foreach (var claim in user)
foreach (var claim in user)
{
if (claim.Type == SquidexClaimTypes.Answer && claim.Value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
if (claim.Type == SquidexClaimTypes.Answer && claim.Value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
return claim.Value[prefix.Length..];
}
return claim.Value[prefix.Length..];
}
return null;
}
public static int GetTotalApps(this IEnumerable<Claim> user)
{
var value = user.GetClaimValue(SquidexClaimTypes.TotalApps);
return null;
}
int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result);
public static int GetTotalApps(this IEnumerable<Claim> user)
{
var value = user.GetClaimValue(SquidexClaimTypes.TotalApps);
return result;
}
int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result);
public static bool HasClaim(this IEnumerable<Claim> user, string type)
{
return user.GetClaims(type).Any();
}
return result;
}
public static bool HasClaimValue(this IEnumerable<Claim> user, string type, string value)
{
return user.GetClaims(type).Any(x => string.Equals(x.Value, value, StringComparison.OrdinalIgnoreCase));
}
public static bool HasClaim(this IEnumerable<Claim> user, string type)
{
return user.GetClaims(type).Any();
}
public static IEnumerable<Claim> GetSquidexClaims(this IEnumerable<Claim> user)
public static bool HasClaimValue(this IEnumerable<Claim> user, string type, string value)
{
return user.GetClaims(type).Any(x => string.Equals(x.Value, value, StringComparison.OrdinalIgnoreCase));
}
public static IEnumerable<Claim> GetSquidexClaims(this IEnumerable<Claim> user)
{
const string prefix = "urn:squidex:";
foreach (var claim in user)
{
const string prefix = "urn:squidex:";
var type = GetType(claim);
foreach (var claim in user)
if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
var type = GetType(claim);
if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
yield return claim;
}
yield return claim;
}
}
}
public static IEnumerable<(string Name, string Value)> GetCustomProperties(this IEnumerable<Claim> user)
public static IEnumerable<(string Name, string Value)> GetCustomProperties(this IEnumerable<Claim> user)
{
var prefix = $"{SquidexClaimTypes.Custom}:";
foreach (var claim in user)
{
var prefix = $"{SquidexClaimTypes.Custom}:";
var type = GetType(claim);
foreach (var claim in user)
if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
var type = GetType(claim);
var name = type[prefix.Length..].ToString();
if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
var name = type[prefix.Length..].ToString();
yield return (name.Trim(), claim.Value.Trim());
}
else if (type.Equals(SquidexClaimTypes.Custom, StringComparison.OrdinalIgnoreCase))
{
var match = RegexKeyValueClaim.Match(claim.Value);
yield return (name.Trim(), claim.Value.Trim());
}
else if (type.Equals(SquidexClaimTypes.Custom, StringComparison.OrdinalIgnoreCase))
if (match.Success)
{
var match = KeyValueClaim.Match(claim.Value);
if (match.Success)
{
yield return (match.Groups["Key"].Value.Trim(), match.Groups["Value"].Value.Trim());
}
yield return (match.Groups["Key"].Value.Trim(), match.Groups["Value"].Value.Trim());
}
}
}
}
public static IEnumerable<(string Name, JsonValue Value)> GetUIProperties(this IEnumerable<Claim> user, string app)
public static IEnumerable<(string Name, JsonValue Value)> GetUIProperties(this IEnumerable<Claim> user, string app)
{
var prefix = $"{SquidexClaimTypes.UIProperty}:{app}:";
static JsonValue Parse(string value)
{
var prefix = $"{SquidexClaimTypes.UIProperty}:{app}:";
value = value.Trim();
static JsonValue Parse(string value)
try
{
value = value.Trim();
try
{
var root = JsonDocument.Parse(value).RootElement;
var root = JsonDocument.Parse(value).RootElement;
return JsonValue.Create(root);
}
catch
{
return JsonValue.Create(value);
}
return JsonValue.Create(root);
}
foreach (var claim in user)
catch
{
var type = GetType(claim);
if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
var name = type[prefix.Length..].ToString();
yield return (name.Trim(), Parse(claim.Value));
}
else if (type.Equals(SquidexClaimTypes.UIProperty, StringComparison.OrdinalIgnoreCase))
{
if (!claim.Value.StartsWith(app, StringComparison.OrdinalIgnoreCase))
{
continue;
}
var match = KeyValueAppClaim.Match(claim.Value);
if (match.Success)
{
yield return (match.Groups["Key"].Value.Trim(), Parse(match.Groups["Value"].Value));
}
}
return JsonValue.Create(value);
}
}
public static string? PictureNormalizedUrl(this IEnumerable<Claim> user)
foreach (var claim in user)
{
var url = user.FirstOrDefault(x => x.Type == SquidexClaimTypes.PictureUrl)?.Value;
var type = GetType(claim);
if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
var name = type[prefix.Length..].ToString();
if (Uri.IsWellFormedUriString(url, UriKind.Absolute) && url.Contains("gravatar", StringComparison.Ordinal))
yield return (name.Trim(), Parse(claim.Value));
}
else if (type.Equals(SquidexClaimTypes.UIProperty, StringComparison.OrdinalIgnoreCase))
{
if (url.Contains('?', StringComparison.Ordinal))
if (!claim.Value.StartsWith(app, StringComparison.OrdinalIgnoreCase))
{
url += "&d=404";
continue;
}
else
var match = RegexKeyValueAppClaim.Match(claim.Value);
if (match.Success)
{
url += "?d=404";
yield return (match.Groups["Key"].Value.Trim(), Parse(match.Groups["Value"].Value));
}
}
return url;
}
}
private static string? GetClaimValue(this IEnumerable<Claim> user, string type)
{
return user.GetClaims(type).FirstOrDefault()?.Value;
}
public static string? PictureNormalizedUrl(this IEnumerable<Claim> user)
{
var url = user.FirstOrDefault(x => x.Type == SquidexClaimTypes.PictureUrl)?.Value;
private static IEnumerable<Claim> GetClaims(this IEnumerable<Claim> user, string request)
if (Uri.IsWellFormedUriString(url, UriKind.Absolute) && url.Contains("gravatar", StringComparison.Ordinal))
{
foreach (var claim in user)
if (url.Contains('?', StringComparison.Ordinal))
{
var type = GetType(claim);
if (type.Equals(request, StringComparison.OrdinalIgnoreCase))
{
yield return claim;
}
url += "&d=404";
}
else
{
url += "?d=404";
}
}
private static ReadOnlySpan<char> GetType(Claim claim)
return url;
}
private static string? GetClaimValue(this IEnumerable<Claim> user, string type)
{
return user.GetClaims(type).FirstOrDefault()?.Value;
}
private static IEnumerable<Claim> GetClaims(this IEnumerable<Claim> user, string request)
{
foreach (var claim in user)
{
var type = claim.Type.AsSpan();
var type = GetType(claim);
if (type.StartsWith(ClientPrefix, StringComparison.OrdinalIgnoreCase))
if (type.Equals(request, StringComparison.OrdinalIgnoreCase))
{
type = type[ClientPrefix.Length..];
yield return claim;
}
}
}
private static ReadOnlySpan<char> GetType(Claim claim)
{
var type = claim.Type.AsSpan();
return type;
if (type.StartsWith(ClientPrefix, StringComparison.OrdinalIgnoreCase))
{
type = type[ClientPrefix.Length..];
}
return type;
}
[GeneratedRegex("(?<App>[\\S]+),(?<Key>[^=]+)=(?<Value>.+)", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
private static partial Regex BuildKeyValueAppClaimRegex();
[GeneratedRegex("(?<Key>[^=]+)=(?<Value>.+)", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
private static partial Regex BuildKeyValueClaimRegex();
}

35
backend/src/Squidex.Shared/PermissionExtensions.cs

@ -8,29 +8,28 @@
using System.Linq;
using Squidex.Infrastructure.Security;
namespace Squidex.Shared
namespace Squidex.Shared;
public static class PermissionExtensions
{
public static class PermissionExtensions
public static bool Allows(this PermissionSet permissions, string id, string app = Permission.Any, string schema = Permission.Any, string team = Permission.Any)
{
public static bool Allows(this PermissionSet permissions, string id, string app = Permission.Any, string schema = Permission.Any, string team = Permission.Any)
{
var permission = PermissionIds.ForApp(id, app, schema, team);
var permission = PermissionIds.ForApp(id, app, schema, team);
return permissions.Allows(permission);
}
return permissions.Allows(permission);
}
public static string[] ToAppNames(this PermissionSet permissions)
{
var matching = permissions.Where(x => x.StartsWith("squidex.apps."));
public static string[] ToAppNames(this PermissionSet permissions)
{
var matching = permissions.Where(x => x.StartsWith("squidex.apps."));
var result =
matching
.Select(x => x.Id.Split('.')).Where(x => x.Length > 2)
.Select(x => x[2])
.Distinct()
.ToArray();
var result =
matching
.Select(x => x.Id.Split('.')).Where(x => x.Length > 2)
.Select(x => x[2])
.Distinct()
.ToArray();
return result;
}
return result;
}
}

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

@ -9,219 +9,218 @@ using System;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Security;
namespace Squidex.Shared
namespace Squidex.Shared;
public static class PermissionIds
{
public static class PermissionIds
public const string All = "squidex.*";
public const string Admin = "squidex.admin.*";
// Admin App Creation
public const string AdminAppCreate = "squidex.admin.apps.create";
// Admin Team Creation
public const string AdminTeamCreate = "squidex.admin.teams.create";
// Backup Admin
public const string AdminRestore = "squidex.admin.restore";
// Event Admin
public const string AdminEvents = "squidex.admin.events";
public const string AdminEventsRead = "squidex.admin.events.read";
public const string AdminEventsManage = "squidex.admin.events.manage";
// User Admin
public const string AdminUsers = "squidex.admin.users";
public const string AdminUsersRead = "squidex.admin.users.read";
public const string AdminUsersCreate = "squidex.admin.users.create";
public const string AdminUsersUpdate = "squidex.admin.users.update";
public const string AdminUsersUnlock = "squidex.admin.users.unlock";
public const string AdminUsersLock = "squidex.admin.users.lock";
// Team
public const string Team = "squidex.teams.{team}";
// Team Transfer
public const string Transfer = "squidex.transfer";
// Team General
public const string TeamAdmin = "squidex.teams.{team}.*";
public const string TeamUpdate = "squidex.teams.{team}.update";
// Team Contributors
public const string TeamContributors = "squidex.teams.{team}.contributors";
public const string TeamContributorsRead = "squidex.teams.{team}.contributors.read";
public const string TeamContributorsAssign = "squidex.teams.{team}.contributors.assign";
public const string TeamContributorsRevoke = "squidex.teams.{team}.contributors.revoke";
// Team Plans
public const string TeamPlans = "squidex.teams.{team}.plans";
public const string TeamPlansRead = "squidex.teams.{team}.plans.read";
public const string TeamPlansChange = "squidex.teams.{team}.plans.change";
// Team Usage
public const string TeamUsage = "squidex.teams.{team}.usage";
// Team History
public const string TeamHistory = "squidex.teams.{team}.history";
// App
public const string App = "squidex.apps.{app}";
// App
public const string AppNoScripting = "squidex.apps.{app}.no-scripting";
// App General
public const string AppAdmin = "squidex.apps.{app}.*";
public const string AppDelete = "squidex.apps.{app}.delete";
public const string AppTransfer = "squidex.apps.{app}.transfer";
public const string AppUpdate = "squidex.apps.{app}.update";
public const string AppUpdateSettings = "squidex.apps.{app}.settings";
// App Image
public const string AppImageUpload = "squidex.apps.{app}.image";
public const string AppImageDelete = "squidex.apps.{app}.image";
// App History
public const string AppHistory = "squidex.apps.{app}.history";
// App Ping
public const string AppPing = "squidex.apps.{app}.ping";
// App Search
public const string AppSearch = "squidex.apps.{app}.search";
// App Translate
public const string AppTranslate = "squidex.apps.{app}.translate";
// App Usage
public const string AppUsage = "squidex.apps.{app}.usage";
// App Comments
public const string AppComments = "squidex.apps.{app}.comments";
public const string AppCommentsRead = "squidex.apps.{app}.comments.read";
public const string AppCommentsCreate = "squidex.apps.{app}.comments.create";
public const string AppCommentsUpdate = "squidex.apps.{app}.comments.update";
public const string AppCommentsDelete = "squidex.apps.{app}.comments.delete";
// App Clients
public const string AppClients = "squidex.apps.{app}.clients";
public const string AppClientsRead = "squidex.apps.{app}.clients.read";
public const string AppClientsCreate = "squidex.apps.{app}.clients.create";
public const string AppClientsUpdate = "squidex.apps.{app}.clients.update";
public const string AppClientsDelete = "squidex.apps.{app}.clients.delete";
// App Contributors
public const string AppContributors = "squidex.apps.{app}.contributors";
public const string AppContributorsRead = "squidex.apps.{app}.contributors.read";
public const string AppContributorsAssign = "squidex.apps.{app}.contributors.assign";
public const string AppContributorsRevoke = "squidex.apps.{app}.contributors.revoke";
// App Languages
public const string AppLanguages = "squidex.apps.{app}.languages";
public const string AppLanguagesRead = "squidex.apps.{app}.languages.read";
public const string AppLanguagesCreate = "squidex.apps.{app}.languages.create";
public const string AppLanguagesUpdate = "squidex.apps.{app}.languages.update";
public const string AppLanguagesDelete = "squidex.apps.{app}.languages.delete";
// App Roles
public const string AppRoles = "squidex.apps.{app}.roles";
public const string AppRolesRead = "squidex.apps.{app}.roles.read";
public const string AppRolesCreate = "squidex.apps.{app}.roles.create";
public const string AppRolesUpdate = "squidex.apps.{app}.roles.update";
public const string AppRolesDelete = "squidex.apps.{app}.roles.delete";
// App Workflows
public const string AppWorkflows = "squidex.apps.{app}.workflows";
public const string AppWorkflowsRead = "squidex.apps.{app}.workflows.read";
public const string AppWorkflowsCreate = "squidex.apps.{app}.workflows.create";
public const string AppWorkflowsUpdate = "squidex.apps.{app}.workflows.update";
public const string AppWorkflowsDelete = "squidex.apps.{app}.workflows.delete";
// App Backups
public const string AppBackups = "squidex.apps.{app}.backups";
public const string AppBackupsRead = "squidex.apps.{app}.backups.read";
public const string AppBackupsCreate = "squidex.apps.{app}.backups.create";
public const string AppBackupsDelete = "squidex.apps.{app}.backups.delete";
public const string AppBackupsDownload = "squidex.apps.{app}.backups.download";
// App Plans
public const string AppPlans = "squidex.apps.{app}.plans";
public const string AppPlansRead = "squidex.apps.{app}.plans.read";
public const string AppPlansChange = "squidex.apps.{app}.plans.change";
// App Assets
public const string AppAssets = "squidex.apps.{app}.assets";
public const string AppAssetsRead = "squidex.apps.{app}.assets.read";
public const string AppAssetsCreate = "squidex.apps.{app}.assets.create";
public const string AppAssetsUpload = "squidex.apps.{app}.assets.upload";
public const string AppAssetsUpdate = "squidex.apps.{app}.assets.update";
public const string AppAssetsDelete = "squidex.apps.{app}.assets.delete";
// App Asset Folders
public const string AppAssetFolders = "squidex.apps.{app}.assets.folders";
public const string AppAssetFoldersCreate = "squidex.apps.{app}.assets.folders.create";
public const string AppAssetFoldersUpdate = "squidex.apps.{app}.assets.folders.update";
public const string AppAssetFoldersDelete = "squidex.apps.{app}.assets.folders.delete";
// App Asset Scripts
public const string AppAssetScripts = "squidex.apps.{app}.asset-scripts";
public const string AppAssetSScriptsRead = "squidex.apps.{app}.asset-scripts.read";
public const string AppAssetsScriptsUpdate = "squidex.apps.{app}.asset-scripts.update";
// App Rules
public const string AppRules = "squidex.apps.{app}.rules";
public const string AppRulesRead = "squidex.apps.{app}.rules.read";
public const string AppRulesCreate = "squidex.apps.{app}.rules.create";
public const string AppRulesUpdate = "squidex.apps.{app}.rules.update";
public const string AppRulesDisable = "squidex.apps.{app}.rules.disable";
public const string AppRulesDelete = "squidex.apps.{app}.rules.delete";
// App Rule Events
public const string AppRulesEvents = "squidex.apps.{app}.rules.events";
public const string AppRulesEventsRun = "squidex.apps.{app}.rules.events.run";
public const string AppRulesEventsRead = "squidex.apps.{app}.rules.events.read";
public const string AppRulesEventsUpdate = "squidex.apps.{app}.rules.events.update";
public const string AppRulesEventsDelete = "squidex.apps.{app}.rules.events.delete";
// App Schemas
public const string AppSchemas = "squidex.apps.{app}.schemas";
public const string AppSchemasRead = "squidex.apps.{app}.schemas.read";
public const string AppSchemasCreate = "squidex.apps.{app}.schemas.create";
public const string AppSchemasUpdate = "squidex.apps.{app}.schemas.{schema}.update";
public const string AppSchemasScripts = "squidex.apps.{app}.schemas.{schema}.scripts";
public const string AppSchemasPublish = "squidex.apps.{app}.schemas.{schema}.publish";
public const string AppSchemasDelete = "squidex.apps.{app}.schemas.{schema}.delete";
// App Contents
public const string AppContents = "squidex.apps.{app}.contents.{schema}";
public const string AppContentsRead = "squidex.apps.{app}.contents.{schema}.read";
public const string AppContentsReadOwn = "squidex.apps.{app}.contents.{schema}.read.own";
public const string AppContentsCreate = "squidex.apps.{app}.contents.{schema}.create";
public const string AppContentsUpdate = "squidex.apps.{app}.contents.{schema}.update";
public const string AppContentsUpdateOwn = "squidex.apps.{app}.contents.{schema}.update.own";
public const string AppContentsChangeStatusCancel = "squidex.apps.{app}.contents.{schema}.changestatus.cancel";
public const string AppContentsChangeStatusCancelOwn = "squidex.apps.{app}.contents.{schema}.changestatus.cancel.own";
public const string AppContentsChangeStatus = "squidex.apps.{app}.contents.{schema}.changestatus";
public const string AppContentsChangeStatusOwn = "squidex.apps.{app}.contents.{schema}.changestatus.own";
public const string AppContentsUpsert = "squidex.apps.{app}.contents.{schema}.upsert";
public const string AppContentsVersionCreate = "squidex.apps.{app}.contents.{schema}.version.create";
public const string AppContentsVersionCreateOwn = "squidex.apps.{app}.contents.{schema}.version.create.own";
public const string AppContentsVersionDelete = "squidex.apps.{app}.contents.{schema}.version.delete";
public const string AppContentsVersionDeleteOwn = "squidex.apps.{app}.contents.{schema}.version.delete.own";
public const string AppContentsDelete = "squidex.apps.{app}.contents.{schema}.delete";
public const string AppContentsDeleteOwn = "squidex.apps.{app}.contents.{schema}.delete.own";
public static Permission ForApp(string id, string app = Permission.Any, string schema = Permission.Any, string team = Permission.Any)
{
public const string All = "squidex.*";
Guard.NotNull(id);
public const string Admin = "squidex.admin.*";
// Admin App Creation
public const string AdminAppCreate = "squidex.admin.apps.create";
id = id.Replace("{app}", app ?? Permission.Any, StringComparison.Ordinal);
id = id.Replace("{schema}", schema ?? Permission.Any, StringComparison.Ordinal);
id = id.Replace("{team}", team ?? Permission.Any, StringComparison.Ordinal);
// Admin Team Creation
public const string AdminTeamCreate = "squidex.admin.teams.create";
// Backup Admin
public const string AdminRestore = "squidex.admin.restore";
// Event Admin
public const string AdminEvents = "squidex.admin.events";
public const string AdminEventsRead = "squidex.admin.events.read";
public const string AdminEventsManage = "squidex.admin.events.manage";
// User Admin
public const string AdminUsers = "squidex.admin.users";
public const string AdminUsersRead = "squidex.admin.users.read";
public const string AdminUsersCreate = "squidex.admin.users.create";
public const string AdminUsersUpdate = "squidex.admin.users.update";
public const string AdminUsersUnlock = "squidex.admin.users.unlock";
public const string AdminUsersLock = "squidex.admin.users.lock";
// Team
public const string Team = "squidex.teams.{team}";
// Team Transfer
public const string Transfer = "squidex.transfer";
// Team General
public const string TeamAdmin = "squidex.teams.{team}.*";
public const string TeamUpdate = "squidex.teams.{team}.update";
// Team Contributors
public const string TeamContributors = "squidex.teams.{team}.contributors";
public const string TeamContributorsRead = "squidex.teams.{team}.contributors.read";
public const string TeamContributorsAssign = "squidex.teams.{team}.contributors.assign";
public const string TeamContributorsRevoke = "squidex.teams.{team}.contributors.revoke";
// Team Plans
public const string TeamPlans = "squidex.teams.{team}.plans";
public const string TeamPlansRead = "squidex.teams.{team}.plans.read";
public const string TeamPlansChange = "squidex.teams.{team}.plans.change";
// Team Usage
public const string TeamUsage = "squidex.teams.{team}.usage";
// Team History
public const string TeamHistory = "squidex.teams.{team}.history";
// App
public const string App = "squidex.apps.{app}";
// App
public const string AppNoScripting = "squidex.apps.{app}.no-scripting";
// App General
public const string AppAdmin = "squidex.apps.{app}.*";
public const string AppDelete = "squidex.apps.{app}.delete";
public const string AppTransfer = "squidex.apps.{app}.transfer";
public const string AppUpdate = "squidex.apps.{app}.update";
public const string AppUpdateSettings = "squidex.apps.{app}.settings";
// App Image
public const string AppImageUpload = "squidex.apps.{app}.image";
public const string AppImageDelete = "squidex.apps.{app}.image";
// App History
public const string AppHistory = "squidex.apps.{app}.history";
// App Ping
public const string AppPing = "squidex.apps.{app}.ping";
// App Search
public const string AppSearch = "squidex.apps.{app}.search";
// App Translate
public const string AppTranslate = "squidex.apps.{app}.translate";
// App Usage
public const string AppUsage = "squidex.apps.{app}.usage";
// App Comments
public const string AppComments = "squidex.apps.{app}.comments";
public const string AppCommentsRead = "squidex.apps.{app}.comments.read";
public const string AppCommentsCreate = "squidex.apps.{app}.comments.create";
public const string AppCommentsUpdate = "squidex.apps.{app}.comments.update";
public const string AppCommentsDelete = "squidex.apps.{app}.comments.delete";
// App Clients
public const string AppClients = "squidex.apps.{app}.clients";
public const string AppClientsRead = "squidex.apps.{app}.clients.read";
public const string AppClientsCreate = "squidex.apps.{app}.clients.create";
public const string AppClientsUpdate = "squidex.apps.{app}.clients.update";
public const string AppClientsDelete = "squidex.apps.{app}.clients.delete";
// App Contributors
public const string AppContributors = "squidex.apps.{app}.contributors";
public const string AppContributorsRead = "squidex.apps.{app}.contributors.read";
public const string AppContributorsAssign = "squidex.apps.{app}.contributors.assign";
public const string AppContributorsRevoke = "squidex.apps.{app}.contributors.revoke";
// App Languages
public const string AppLanguages = "squidex.apps.{app}.languages";
public const string AppLanguagesRead = "squidex.apps.{app}.languages.read";
public const string AppLanguagesCreate = "squidex.apps.{app}.languages.create";
public const string AppLanguagesUpdate = "squidex.apps.{app}.languages.update";
public const string AppLanguagesDelete = "squidex.apps.{app}.languages.delete";
// App Roles
public const string AppRoles = "squidex.apps.{app}.roles";
public const string AppRolesRead = "squidex.apps.{app}.roles.read";
public const string AppRolesCreate = "squidex.apps.{app}.roles.create";
public const string AppRolesUpdate = "squidex.apps.{app}.roles.update";
public const string AppRolesDelete = "squidex.apps.{app}.roles.delete";
// App Workflows
public const string AppWorkflows = "squidex.apps.{app}.workflows";
public const string AppWorkflowsRead = "squidex.apps.{app}.workflows.read";
public const string AppWorkflowsCreate = "squidex.apps.{app}.workflows.create";
public const string AppWorkflowsUpdate = "squidex.apps.{app}.workflows.update";
public const string AppWorkflowsDelete = "squidex.apps.{app}.workflows.delete";
// App Backups
public const string AppBackups = "squidex.apps.{app}.backups";
public const string AppBackupsRead = "squidex.apps.{app}.backups.read";
public const string AppBackupsCreate = "squidex.apps.{app}.backups.create";
public const string AppBackupsDelete = "squidex.apps.{app}.backups.delete";
public const string AppBackupsDownload = "squidex.apps.{app}.backups.download";
// App Plans
public const string AppPlans = "squidex.apps.{app}.plans";
public const string AppPlansRead = "squidex.apps.{app}.plans.read";
public const string AppPlansChange = "squidex.apps.{app}.plans.change";
// App Assets
public const string AppAssets = "squidex.apps.{app}.assets";
public const string AppAssetsRead = "squidex.apps.{app}.assets.read";
public const string AppAssetsCreate = "squidex.apps.{app}.assets.create";
public const string AppAssetsUpload = "squidex.apps.{app}.assets.upload";
public const string AppAssetsUpdate = "squidex.apps.{app}.assets.update";
public const string AppAssetsDelete = "squidex.apps.{app}.assets.delete";
// App Asset Folders
public const string AppAssetFolders = "squidex.apps.{app}.assets.folders";
public const string AppAssetFoldersCreate = "squidex.apps.{app}.assets.folders.create";
public const string AppAssetFoldersUpdate = "squidex.apps.{app}.assets.folders.update";
public const string AppAssetFoldersDelete = "squidex.apps.{app}.assets.folders.delete";
// App Asset Scripts
public const string AppAssetScripts = "squidex.apps.{app}.asset-scripts";
public const string AppAssetSScriptsRead = "squidex.apps.{app}.asset-scripts.read";
public const string AppAssetsScriptsUpdate = "squidex.apps.{app}.asset-scripts.update";
// App Rules
public const string AppRules = "squidex.apps.{app}.rules";
public const string AppRulesRead = "squidex.apps.{app}.rules.read";
public const string AppRulesCreate = "squidex.apps.{app}.rules.create";
public const string AppRulesUpdate = "squidex.apps.{app}.rules.update";
public const string AppRulesDisable = "squidex.apps.{app}.rules.disable";
public const string AppRulesDelete = "squidex.apps.{app}.rules.delete";
// App Rule Events
public const string AppRulesEvents = "squidex.apps.{app}.rules.events";
public const string AppRulesEventsRun = "squidex.apps.{app}.rules.events.run";
public const string AppRulesEventsRead = "squidex.apps.{app}.rules.events.read";
public const string AppRulesEventsUpdate = "squidex.apps.{app}.rules.events.update";
public const string AppRulesEventsDelete = "squidex.apps.{app}.rules.events.delete";
// App Schemas
public const string AppSchemas = "squidex.apps.{app}.schemas";
public const string AppSchemasRead = "squidex.apps.{app}.schemas.read";
public const string AppSchemasCreate = "squidex.apps.{app}.schemas.create";
public const string AppSchemasUpdate = "squidex.apps.{app}.schemas.{schema}.update";
public const string AppSchemasScripts = "squidex.apps.{app}.schemas.{schema}.scripts";
public const string AppSchemasPublish = "squidex.apps.{app}.schemas.{schema}.publish";
public const string AppSchemasDelete = "squidex.apps.{app}.schemas.{schema}.delete";
// App Contents
public const string AppContents = "squidex.apps.{app}.contents.{schema}";
public const string AppContentsRead = "squidex.apps.{app}.contents.{schema}.read";
public const string AppContentsReadOwn = "squidex.apps.{app}.contents.{schema}.read.own";
public const string AppContentsCreate = "squidex.apps.{app}.contents.{schema}.create";
public const string AppContentsUpdate = "squidex.apps.{app}.contents.{schema}.update";
public const string AppContentsUpdateOwn = "squidex.apps.{app}.contents.{schema}.update.own";
public const string AppContentsChangeStatusCancel = "squidex.apps.{app}.contents.{schema}.changestatus.cancel";
public const string AppContentsChangeStatusCancelOwn = "squidex.apps.{app}.contents.{schema}.changestatus.cancel.own";
public const string AppContentsChangeStatus = "squidex.apps.{app}.contents.{schema}.changestatus";
public const string AppContentsChangeStatusOwn = "squidex.apps.{app}.contents.{schema}.changestatus.own";
public const string AppContentsUpsert = "squidex.apps.{app}.contents.{schema}.upsert";
public const string AppContentsVersionCreate = "squidex.apps.{app}.contents.{schema}.version.create";
public const string AppContentsVersionCreateOwn = "squidex.apps.{app}.contents.{schema}.version.create.own";
public const string AppContentsVersionDelete = "squidex.apps.{app}.contents.{schema}.version.delete";
public const string AppContentsVersionDeleteOwn = "squidex.apps.{app}.contents.{schema}.version.delete.own";
public const string AppContentsDelete = "squidex.apps.{app}.contents.{schema}.delete";
public const string AppContentsDeleteOwn = "squidex.apps.{app}.contents.{schema}.delete.own";
public static Permission ForApp(string id, string app = Permission.Any, string schema = Permission.Any, string team = Permission.Any)
{
Guard.NotNull(id);
id = id.Replace("{app}", app ?? Permission.Any, StringComparison.Ordinal);
id = id.Replace("{schema}", schema ?? Permission.Any, StringComparison.Ordinal);
id = id.Replace("{team}", team ?? Permission.Any, StringComparison.Ordinal);
return new Permission(id);
}
return new Permission(id);
}
}

3
backend/src/Squidex.Shared/Squidex.Shared.csproj

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>9.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

15
backend/src/Squidex.Shared/Texts.cs

@ -7,15 +7,14 @@
using System.Resources;
namespace Squidex.Shared
namespace Squidex.Shared;
public static class Texts
{
public static class Texts
{
private static ResourceManager? resourceManager;
private static ResourceManager? resourceManager;
public static ResourceManager ResourceManager
{
get => resourceManager ??= new ResourceManager("Squidex.Shared.Texts", typeof(Texts).Assembly);
}
public static ResourceManager ResourceManager
{
get => resourceManager ??= new ResourceManager("Squidex.Shared.Texts", typeof(Texts).Assembly);
}
}

63
backend/src/Squidex.Shared/Users/ClientUser.cs

@ -12,46 +12,45 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Security;
using Squidex.Shared.Identity;
namespace Squidex.Shared.Users
namespace Squidex.Shared.Users;
public sealed class ClientUser : IUser
{
public sealed class ClientUser : IUser
{
private readonly RefToken token;
private readonly List<Claim> claims;
private readonly RefToken token;
private readonly List<Claim> claims;
public string Id
{
get => token.Identifier;
}
public string Id
{
get => token.Identifier;
}
public string Email
{
get => token.ToString();
}
public string Email
{
get => token.ToString();
}
public bool IsLocked
{
get => false;
}
public bool IsLocked
{
get => false;
}
public IReadOnlyList<Claim> Claims
{
get => claims;
}
public IReadOnlyList<Claim> Claims
{
get => claims;
}
public object Identity => throw new NotSupportedException();
public object Identity => throw new NotSupportedException();
public ClientUser(RefToken token)
{
Guard.NotNull(token);
public ClientUser(RefToken token)
{
Guard.NotNull(token);
this.token = token;
this.token = token;
claims = new List<Claim>
{
new Claim(OpenIdClaims.ClientId, token.Identifier),
new Claim(SquidexClaimTypes.DisplayName, token.Identifier)
};
}
claims = new List<Claim>
{
new Claim(OpenIdClaims.ClientId, token.Identifier),
new Claim(SquidexClaimTypes.DisplayName, token.Identifier)
};
}
}

17
backend/src/Squidex.Shared/Users/IUser.cs

@ -8,18 +8,17 @@
using System.Collections.Generic;
using System.Security.Claims;
namespace Squidex.Shared.Users
namespace Squidex.Shared.Users;
public interface IUser
{
public interface IUser
{
bool IsLocked { get; }
bool IsLocked { get; }
string Id { get; }
string Id { get; }
string Email { get; }
string Email { get; }
object Identity { get; }
object Identity { get; }
IReadOnlyList<Claim> Claims { get; }
}
IReadOnlyList<Claim> Claims { get; }
}

35
backend/src/Squidex.Shared/Users/IUserResolver.cs

@ -9,29 +9,28 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Squidex.Shared.Users
namespace Squidex.Shared.Users;
public interface IUserResolver
{
public interface IUserResolver
{
Task<(IUser? User, bool Created)> CreateUserIfNotExistsAsync(string email, bool invited = false,
CancellationToken ct = default);
Task<(IUser? User, bool Created)> CreateUserIfNotExistsAsync(string email, bool invited = false,
CancellationToken ct = default);
Task SetClaimAsync(string id, string type, string value, bool silent = false,
CancellationToken ct = default);
Task SetClaimAsync(string id, string type, string value, bool silent = false,
CancellationToken ct = default);
Task<IUser?> FindByIdOrEmailAsync(string idOrEmail,
CancellationToken ct = default);
Task<IUser?> FindByIdOrEmailAsync(string idOrEmail,
CancellationToken ct = default);
Task<IUser?> FindByIdAsync(string idOrEmail,
CancellationToken ct = default);
Task<IUser?> FindByIdAsync(string idOrEmail,
CancellationToken ct = default);
Task<List<IUser>> QueryByEmailAsync(string email,
CancellationToken ct = default);
Task<List<IUser>> QueryByEmailAsync(string email,
CancellationToken ct = default);
Task<List<IUser>> QueryAllAsync(
CancellationToken ct = default);
Task<List<IUser>> QueryAllAsync(
CancellationToken ct = default);
Task<Dictionary<string, IUser>> QueryManyAsync(string[] ids,
CancellationToken ct = default);
}
Task<Dictionary<string, IUser>> QueryManyAsync(string[] ids,
CancellationToken ct = default);
}

7
backend/src/Squidex.Web/IgnoreHashFileProvider.cs

@ -11,7 +11,7 @@ using Microsoft.Extensions.Primitives;
namespace Squidex.Web;
public sealed class IgnoreHashFileProvider : IFileProvider
public sealed partial class IgnoreHashFileProvider : IFileProvider
{
private readonly char[] pathSeparators = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, '\\' };
private readonly Dictionary<string, string> map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@ -21,7 +21,7 @@ public sealed class IgnoreHashFileProvider : IFileProvider
{
this.inner = inner;
var regex = new Regex("^(?<Name>[^.]+)\\.[0-9a-f]{4,}\\.(?<Extension>.+)$");
var regex = BuildFileWithHashRegex();
void MapDirectory(string path)
{
@ -91,4 +91,7 @@ public sealed class IgnoreHashFileProvider : IFileProvider
return $"{path1}/{path2}";
}
[GeneratedRegex("^(?<Name>[^.]+)(\\.|-)[0-9A-Za-z]{4,}\\.(?<Extension>.+)$")]
private static partial Regex BuildFileWithHashRegex();
}

2
backend/src/Squidex.Web/Squidex.Web.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

2
backend/src/Squidex/Squidex.csproj

@ -3,7 +3,7 @@
<TargetFramework>net7.0</TargetFramework>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<NeutralLanguage>en</NeutralLanguage>
<NoWarn>NU1608</NoWarn>

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj

@ -3,7 +3,7 @@
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Domain.Apps.Core</RootNamespace>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj

@ -3,7 +3,7 @@
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Domain.Apps.Entities</RootNamespace>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NeutralLanguage>en</NeutralLanguage>

2
backend/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj

@ -3,7 +3,7 @@
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Domain.Users</RootNamespace>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

2
backend/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj

@ -3,7 +3,7 @@
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Infrastructure</RootNamespace>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NeutralLanguage>en</NeutralLanguage>

2
backend/tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj

@ -3,7 +3,7 @@
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Web</RootNamespace>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

2
backend/tools/GenerateLanguages/GenerateLanguages.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>

13
frontend/.storybook/main.js

@ -5,6 +5,8 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
@ -16,6 +18,17 @@ module.exports = {
name: "@storybook/angular",
options: {}
},
webpackFinal: async config => {
/*
* Copy lazy loaded libraries to output.
*/
config.plugins.push(new CopyPlugin({
patterns: [
{ from: './node_modules/ace-builds/src-min/', to: 'dependencies/ace/' },
]
}));
return config;
},
docs: {
autodocs: true
}

15
frontend/angular.json

@ -18,11 +18,11 @@
"prefix": "sqx",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "build",
"index": "src/index.html",
"main": "src/main.ts",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
@ -30,6 +30,7 @@
"inlineStyleLanguage": "scss",
"allowedCommonJsDependencies": [
"@tweenjs/tween.js",
"client-only",
"copy-to-clipboard",
"cropperjs",
"crypto-js",
@ -37,12 +38,16 @@
"crypto-js/enc-base64.js",
"crypto-js/enc-utf8.js",
"crypto-js/sha256.js",
"mousetrap",
"graphql-ws",
"markdown-it",
"mersenne-twister",
"mousetrap",
"nullthrows",
"pikaday",
"pikaday/pikaday",
"progressbar.js",
"react-dom",
"react",
"set-value",
"slugify"
],
@ -117,12 +122,10 @@
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"extractLicenses": false,
"namedChunks": true,
"optimization": false,
"sourceMap": true,
"vendorChunk": true
"sourceMap": true
}
},
"defaultConfiguration": "production"

383
frontend/package-lock.json

@ -8,6 +8,7 @@
"name": "squidex",
"version": "0.0.0",
"dependencies": {
"@angular-devkit/architect": "^0.1700.0",
"@angular/animations": "17.0.2",
"@angular/cdk": "17.0.0",
"@angular/cdk-experimental": "17.0.0",
@ -30,6 +31,7 @@
"angular-gridster2": "16.0.0",
"angular-mentions": "1.5.0",
"bootstrap": "5.2.3",
"copy-webpack-plugin": "^11.0.0",
"core-js": "3.33.2",
"cropperjs": "2.0.0-alpha.1",
"date-fns": "2.30.0",
@ -148,59 +150,19 @@
}
},
"node_modules/@angular-devkit/architect": {
"version": "0.1602.10",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.10.tgz",
"integrity": "sha512-FwemQXh3edqA/S6zPpsqKei5v7gt0R0WpjJoAJaz+FOpfDwij1fwnKr88XINY8xcefTcQaTDQxJZheJShA/hHw==",
"dev": true,
"peer": true,
"version": "0.1700.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.0.tgz",
"integrity": "sha512-whi7HvOjv1J3He9f+H8xNJWKyjAmWuWNl8gxNW6EZP/XLcrOu+/5QT4bPtXQBRIL/avZuc++5sNQS+kReaNCig==",
"dependencies": {
"@angular-devkit/core": "16.2.10",
"@angular-devkit/core": "17.0.0",
"rxjs": "7.8.1"
},
"engines": {
"node": "^16.14.0 || >=18.10.0",
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
}
},
"node_modules/@angular-devkit/architect/node_modules/@angular-devkit/core": {
"version": "16.2.10",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.10.tgz",
"integrity": "sha512-eo7suLDjyu5bSlEr4TluYkFm4v2PVLSAPgnau8XHHlN5Yg4P/BZ00ve7LA7C9S1gzRSCrxQhK5ki4rnoFTo5zg==",
"dev": true,
"peer": true,
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"picomatch": "2.3.1",
"rxjs": "7.8.1",
"source-map": "0.7.4"
},
"engines": {
"node": "^16.14.0 || >=18.10.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^3.5.2"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@angular-devkit/architect/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"peer": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@angular-devkit/build-angular": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.0.0.tgz",
@ -324,48 +286,6 @@
}
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": {
"version": "0.1700.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.0.tgz",
"integrity": "sha512-whi7HvOjv1J3He9f+H8xNJWKyjAmWuWNl8gxNW6EZP/XLcrOu+/5QT4bPtXQBRIL/avZuc++5sNQS+kReaNCig==",
"dev": true,
"dependencies": {
"@angular-devkit/core": "17.0.0",
"rxjs": "7.8.1"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.0.tgz",
"integrity": "sha512-QUu3LnEi4A8t733v2+I0sLtyBJx3Q7zdTAhaauCbxbFhDid0cbYm8hYsyG/njor1irTPxSJbn6UoetVkwUQZxg==",
"dev": true,
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"picomatch": "3.0.1",
"rxjs": "7.8.1",
"source-map": "0.7.4"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^3.5.2"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@babel/core": {
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
@ -1174,15 +1094,6 @@
"ajv": "^6.9.1"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/terser": {
"version": "5.24.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz",
@ -1725,26 +1636,10 @@
"webpack-dev-server": "^4.0.0"
}
},
"node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": {
"version": "0.1700.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.0.tgz",
"integrity": "sha512-whi7HvOjv1J3He9f+H8xNJWKyjAmWuWNl8gxNW6EZP/XLcrOu+/5QT4bPtXQBRIL/avZuc++5sNQS+kReaNCig==",
"dev": true,
"dependencies": {
"@angular-devkit/core": "17.0.0",
"rxjs": "7.8.1"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
}
},
"node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": {
"node_modules/@angular-devkit/core": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.0.tgz",
"integrity": "sha512-QUu3LnEi4A8t733v2+I0sLtyBJx3Q7zdTAhaauCbxbFhDid0cbYm8hYsyG/njor1irTPxSJbn6UoetVkwUQZxg==",
"dev": true,
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
@ -1767,11 +1662,10 @@
}
}
},
"node_modules/@angular-devkit/build-webpack/node_modules/picomatch": {
"node_modules/@angular-devkit/core/node_modules/picomatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
"integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
"dev": true,
"engines": {
"node": ">=10"
},
@ -1779,47 +1673,9 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@angular-devkit/build-webpack/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@angular-devkit/core": {
"version": "16.1.7",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.1.7.tgz",
"integrity": "sha512-AXc9/F57Nf/A26yGu+w7PhNYriTvwazPTQsVPW/SBcTcpBa/hAsBTbPl8o8ErRJneJIoYqy/EIuabf9iiU8bRA==",
"dev": true,
"peer": true,
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"rxjs": "7.8.1",
"source-map": "0.7.4"
},
"engines": {
"node": "^16.14.0 || >=18.10.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^3.5.2"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@angular-devkit/core/node_modules/source-map": {
"version": "0.7.4",
"dev": true,
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": ">= 8"
}
@ -2443,48 +2299,6 @@
"yarn": ">= 1.13.0"
}
},
"node_modules/@angular/cli/node_modules/@angular-devkit/architect": {
"version": "0.1700.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.0.tgz",
"integrity": "sha512-whi7HvOjv1J3He9f+H8xNJWKyjAmWuWNl8gxNW6EZP/XLcrOu+/5QT4bPtXQBRIL/avZuc++5sNQS+kReaNCig==",
"dev": true,
"dependencies": {
"@angular-devkit/core": "17.0.0",
"rxjs": "7.8.1"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
}
},
"node_modules/@angular/cli/node_modules/@angular-devkit/core": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.0.tgz",
"integrity": "sha512-QUu3LnEi4A8t733v2+I0sLtyBJx3Q7zdTAhaauCbxbFhDid0cbYm8hYsyG/njor1irTPxSJbn6UoetVkwUQZxg==",
"dev": true,
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"picomatch": "3.0.1",
"rxjs": "7.8.1",
"source-map": "0.7.4"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^3.5.2"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@angular/cli/node_modules/@angular-devkit/schematics": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.0.0.tgz",
@ -2538,27 +2352,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@angular/cli/node_modules/picomatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
"integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@angular/cli/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@angular/common": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-17.0.2.tgz",
@ -6672,7 +6465,6 @@
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
@ -8451,33 +8243,6 @@
"yarn": ">= 1.13.0"
}
},
"node_modules/@schematics/angular/node_modules/@angular-devkit/core": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.0.tgz",
"integrity": "sha512-QUu3LnEi4A8t733v2+I0sLtyBJx3Q7zdTAhaauCbxbFhDid0cbYm8hYsyG/njor1irTPxSJbn6UoetVkwUQZxg==",
"dev": true,
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"picomatch": "3.0.1",
"rxjs": "7.8.1",
"source-map": "0.7.4"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^3.5.2"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@schematics/angular/node_modules/@angular-devkit/schematics": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.0.0.tgz",
@ -8514,27 +8279,6 @@
"node": ">=12"
}
},
"node_modules/@schematics/angular/node_modules/picomatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
"integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@schematics/angular/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@sigstore/bundle": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.1.0.tgz",
@ -11066,7 +10810,6 @@
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.1.tgz",
"integrity": "sha512-XpNDc4Z5Tb4x+SW1MriMVeIsMoONHCkWFMkR/aPJbzEsxqHy+4Glu/BqTdPrApfDeMaXbtNh6bseNgl5KaWrSg==",
"dev": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
@ -11074,7 +10817,6 @@
},
"node_modules/@types/eslint-scope": {
"version": "3.7.4",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint": "*",
@ -11179,8 +10921,7 @@
"node_modules/@types/json-schema": {
"version": "7.0.12",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
"integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==",
"dev": true
"integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA=="
},
"node_modules/@types/json5": {
"version": "0.0.29",
@ -11240,7 +10981,6 @@
"version": "20.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz",
"integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==",
"devOptional": true,
"dependencies": {
"undici-types": "~5.26.4"
}
@ -11997,7 +11737,6 @@
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
"integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
"dev": true,
"dependencies": {
"@webassemblyjs/helper-numbers": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6"
@ -12006,26 +11745,22 @@
"node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
"dev": true
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw=="
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
"dev": true
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q=="
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
"integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==",
"dev": true
"integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA=="
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
"dev": true,
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
@ -12035,14 +11770,12 @@
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
"dev": true
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA=="
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
"integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
"dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
@ -12054,7 +11787,6 @@
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
"dev": true,
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
@ -12063,7 +11795,6 @@
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
"dev": true,
"dependencies": {
"@xtuc/long": "4.2.2"
}
@ -12071,14 +11802,12 @@
"node_modules/@webassemblyjs/utf8": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
"dev": true
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA=="
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
"integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
"dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
@ -12094,7 +11823,6 @@
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
"integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
"dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
@ -12107,7 +11835,6 @@
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
"integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
"dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
@ -12119,7 +11846,6 @@
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
"integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
"dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
@ -12133,7 +11859,6 @@
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
"integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
"dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@xtuc/long": "4.2.2"
@ -12156,14 +11881,12 @@
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
"dev": true
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
},
"node_modules/@yarnpkg/esbuild-plugin-pnp": {
"version": "3.0.0-rc.15",
@ -12306,7 +12029,6 @@
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
@ -12318,7 +12040,6 @@
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
"integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
"dev": true,
"peerDependencies": {
"acorn": "^8"
}
@ -12439,7 +12160,6 @@
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@ -12453,7 +12173,6 @@
},
"node_modules/ajv-formats": {
"version": "2.1.1",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "^8.0.0"
@ -12469,7 +12188,6 @@
},
"node_modules/ajv-keywords": {
"version": "5.1.0",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3"
@ -13964,7 +13682,6 @@
},
"node_modules/buffer-from": {
"version": "1.1.2",
"dev": true,
"license": "MIT"
},
"node_modules/builtins": {
@ -14451,7 +14168,6 @@
},
"node_modules/chrome-trace-event": {
"version": "1.0.3",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0"
@ -14686,7 +14402,6 @@
},
"node_modules/commander": {
"version": "2.20.3",
"dev": true,
"license": "MIT"
},
"node_modules/common-path-prefix": {
@ -14926,8 +14641,8 @@
},
"node_modules/copy-webpack-plugin": {
"version": "11.0.0",
"dev": true,
"license": "MIT",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz",
"integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==",
"dependencies": {
"fast-glob": "^3.2.11",
"glob-parent": "^6.0.1",
@ -14949,7 +14664,6 @@
},
"node_modules/copy-webpack-plugin/node_modules/glob-parent": {
"version": "6.0.2",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
@ -14960,7 +14674,6 @@
},
"node_modules/copy-webpack-plugin/node_modules/globby": {
"version": "13.1.2",
"dev": true,
"license": "MIT",
"dependencies": {
"dir-glob": "^3.0.1",
@ -14978,7 +14691,6 @@
},
"node_modules/copy-webpack-plugin/node_modules/slash": {
"version": "4.0.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@ -15782,7 +15494,6 @@
},
"node_modules/dir-glob": {
"version": "3.0.1",
"dev": true,
"license": "MIT",
"dependencies": {
"path-type": "^4.0.0"
@ -16302,7 +16013,6 @@
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
"integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
@ -16486,8 +16196,7 @@
"node_modules/es-module-lexer": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
"integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==",
"dev": true
"integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
},
"node_modules/es-set-tostringtag": {
"version": "2.0.1",
@ -17119,7 +16828,6 @@
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"esrecurse": "^4.3.0",
@ -17424,7 +17132,6 @@
},
"node_modules/esrecurse": {
"version": "4.3.0",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"estraverse": "^5.2.0"
@ -17435,7 +17142,6 @@
},
"node_modules/esrecurse/node_modules/estraverse": {
"version": "5.3.0",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
@ -17443,7 +17149,6 @@
},
"node_modules/estraverse": {
"version": "4.3.0",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
@ -17508,7 +17213,6 @@
},
"node_modules/events": {
"version": "3.3.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.8.x"
@ -17773,14 +17477,12 @@
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"dev": true,
"license": "MIT"
},
"node_modules/fast-glob": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@ -17794,7 +17496,6 @@
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"dev": true,
"license": "MIT"
},
"node_modules/fast-levenshtein": {
@ -18626,7 +18327,6 @@
},
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/global": {
@ -18726,7 +18426,6 @@
},
"node_modules/graceful-fs": {
"version": "4.2.10",
"dev": true,
"license": "ISC"
},
"node_modules/graphemer": {
@ -19321,7 +19020,6 @@
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
"dev": true,
"engines": {
"node": ">= 4"
}
@ -20726,7 +20424,6 @@
},
"node_modules/jest-worker": {
"version": "27.4.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
@ -20739,7 +20436,6 @@
},
"node_modules/jest-worker/node_modules/has-flag": {
"version": "4.0.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -20747,7 +20443,6 @@
},
"node_modules/jest-worker/node_modules/supports-color": {
"version": "8.1.1",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
@ -20952,12 +20647,10 @@
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"dev": true,
"license": "MIT"
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"dev": true,
"license": "MIT"
},
"node_modules/json-stable-stringify-without-jsonify": {
@ -20979,8 +20672,7 @@
"node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
"dev": true
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
},
"node_modules/jsonfile": {
"version": "6.1.0",
@ -21586,7 +21278,6 @@
},
"node_modules/loader-runner": {
"version": "4.2.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.11.5"
@ -22299,7 +21990,6 @@
},
"node_modules/merge-stream": {
"version": "2.0.0",
"dev": true,
"license": "MIT"
},
"node_modules/merge2": {
@ -22361,7 +22051,6 @@
},
"node_modules/mime-db": {
"version": "1.51.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
@ -22369,7 +22058,6 @@
},
"node_modules/mime-types": {
"version": "2.1.34",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-db": "1.51.0"
@ -22815,7 +22503,6 @@
},
"node_modules/neo-async": {
"version": "2.6.2",
"dev": true,
"license": "MIT"
},
"node_modules/next-tick": {
@ -24325,7 +24012,6 @@
},
"node_modules/path-type": {
"version": "4.0.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -25013,7 +24699,6 @@
},
"node_modules/punycode": {
"version": "2.1.1",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -25161,7 +24846,6 @@
},
"node_modules/randombytes": {
"version": "2.1.0",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "^5.1.0"
@ -25752,7 +25436,6 @@
},
"node_modules/require-from-string": {
"version": "2.0.2",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -26100,7 +25783,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
"integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
@ -26247,7 +25929,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
"integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
"dev": true,
"dependencies": {
"randombytes": "^2.1.0"
}
@ -26706,7 +26387,6 @@
},
"node_modules/source-map-support": {
"version": "0.5.21",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
@ -26715,7 +26395,6 @@
},
"node_modules/source-map-support/node_modules/source-map": {
"version": "0.6.1",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@ -27680,7 +27359,6 @@
},
"node_modules/tapable": {
"version": "2.2.1",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -27823,7 +27501,6 @@
"version": "5.19.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz",
"integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@ -27841,7 +27518,6 @@
"version": "5.3.9",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz",
"integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==",
"dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.17",
"jest-worker": "^27.4.5",
@ -27873,7 +27549,6 @@
},
"node_modules/terser-webpack-plugin/node_modules/ajv": {
"version": "6.12.6",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
@ -27888,7 +27563,6 @@
},
"node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
"version": "3.5.2",
"dev": true,
"license": "MIT",
"peerDependencies": {
"ajv": "^6.9.1"
@ -27896,12 +27570,10 @@
},
"node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
"version": "0.4.1",
"dev": true,
"license": "MIT"
},
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
"version": "3.1.1",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.8",
@ -28509,8 +28181,7 @@
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"devOptional": true
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0",
@ -28727,7 +28398,6 @@
},
"node_modules/uri-js": {
"version": "4.4.1",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"punycode": "^2.1.0"
@ -29077,7 +28747,6 @@
},
"node_modules/watchpack": {
"version": "2.4.0",
"dev": true,
"license": "MIT",
"dependencies": {
"glob-to-regexp": "^0.4.1",
@ -29114,7 +28783,6 @@
"version": "5.88.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz",
"integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.0",
@ -29289,7 +28957,6 @@
},
"node_modules/webpack-sources": {
"version": "3.2.3",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.13.0"
@ -29326,7 +28993,6 @@
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@ -29342,7 +29008,6 @@
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"peerDependencies": {
"ajv": "^6.9.1"
}
@ -29350,14 +29015,12 @@
"node_modules/webpack/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"node_modules/webpack/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",

2
frontend/package.json

@ -15,6 +15,7 @@
},
"private": true,
"dependencies": {
"@angular-devkit/architect": "^0.1700.0",
"@angular/animations": "17.0.2",
"@angular/cdk": "17.0.0",
"@angular/cdk-experimental": "17.0.0",
@ -37,6 +38,7 @@
"angular-gridster2": "16.0.0",
"angular-mentions": "1.5.0",
"bootstrap": "5.2.3",
"copy-webpack-plugin": "^11.0.0",
"core-js": "3.33.2",
"cropperjs": "2.0.0-alpha.1",
"date-fns": "2.30.0",

20
frontend/src/app/app.component.ts

@ -5,13 +5,31 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component } from '@angular/core';
import { NgIf } from '@angular/common';
import { Component, Injector } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { AnalyticsService, CopyGlobalDirective, DialogRendererComponent, RootViewComponent, TourGuideComponent, TourTemplateComponent, TranslatePipe } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-app',
styleUrls: ['./app.component.scss'],
templateUrl: './app.component.html',
imports: [
CopyGlobalDirective,
DialogRendererComponent,
NgIf,
RootViewComponent,
RouterOutlet,
TourGuideComponent,
TourTemplateComponent,
TranslatePipe,
],
})
export class AppComponent {
public isLoaded?: boolean | null;
constructor(injector: Injector) {
injector.get(AnalyticsService);
}
}

129
frontend/src/app/app.module.ts

@ -1,129 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
/* eslint-disable global-require */
/* eslint-disable import/no-dynamic-require */
import { APP_BASE_HREF, CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { ApplicationRef, DoBootstrap, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ActivatedRouteSnapshot, BaseRouteReuseStrategy, RouteReuseStrategy, RouterModule } from '@angular/router';
import { NgChartsModule } from 'ng2-charts';
import { environment } from './../environments/environment';
import { AppComponent } from './app.component';
import { routing } from './app.routes';
import { ApiUrlConfig, DateHelper, LocalizerService, SqxFrameworkModule, SqxSharedModule, TitlesConfig, UIOptions } from './shared';
import { SqxShellModule } from './shell';
const options = (window as any)['options'] || {};
DateHelper.setlocale(options.more?.culture);
function basePath() {
const baseElements = document.getElementsByTagName('base');
let baseHref: string = null!;
if (baseElements.length > 0) {
baseHref = baseElements[0].href;
}
if (baseHref.indexOf('http') === 0) {
baseHref = new URL(baseHref).pathname;
}
if (!baseHref) {
baseHref = '';
}
let path = options.embedPath || '/';
while (baseHref.endsWith('/')) {
baseHref = baseHref.substring(0, baseHref.length - 1);
}
return `${baseHref}${path}`;
}
function configApiUrl() {
const baseElements = document.getElementsByTagName('base');
let baseHref: string = null!;
if (baseElements.length > 0) {
baseHref = baseElements[0].href;
}
if (!baseHref) {
baseHref = '/';
}
if (baseHref.indexOf('http') === 0) {
return new ApiUrlConfig(baseHref);
} else {
return new ApiUrlConfig(`${window.location.protocol}//${window.location.host}${baseHref}`);
}
}
function configUIOptions() {
return new UIOptions(options);
}
function configTitles() {
return new TitlesConfig(undefined, 'i18n:common.product');
}
function configLocalizerService() {
return new LocalizerService(environment.textResolver()).logMissingKeys(environment.textLogger);
}
export class AppRouteReuseStrategy extends BaseRouteReuseStrategy {
public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) {
return (future.routeConfig === curr.routeConfig) || (future.data['reuseId'] && future.data['reuseId'] === curr.data['reuseId']);
}
}
@NgModule({
imports: [
BrowserAnimationsModule,
BrowserModule,
CommonModule,
FormsModule,
HttpClientModule,
NgChartsModule.forRoot(),
ReactiveFormsModule,
RouterModule,
SqxFrameworkModule.forRoot(),
SqxSharedModule.forRoot(),
SqxShellModule,
routing,
],
declarations: [
AppComponent,
],
providers: [
{ provide: ApiUrlConfig, useFactory: configApiUrl },
{ provide: LocalizerService, useFactory: configLocalizerService },
{ provide: RouteReuseStrategy, useClass: AppRouteReuseStrategy },
{ provide: TitlesConfig, useFactory: configTitles },
{ provide: UIOptions, useFactory: configUIOptions },
{ provide: APP_BASE_HREF, useValue: basePath() },
],
})
export class AppModule implements DoBootstrap {
public ngDoBootstrap(appRef: ApplicationRef) {
try {
appRef.bootstrap(AppComponent);
} catch (e) {
// eslint-disable-next-line no-console
console.log('Application element not found.');
}
}
}

43
frontend/src/app/app.routes.ts

@ -5,76 +5,75 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ModuleWithProviders } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppMustExistGuard, LoadAppsGuard, LoadSettingsGuard, LoadTeamsGuard, MustBeAuthenticatedGuard, MustBeNotAuthenticatedGuard, TeamMustExistGuard, UnsetAppGuard, UnsetTeamGuard } from './shared';
import { Routes } from '@angular/router';
import { appMustExistGuard, loadAppsGuard, loadSettingsGuard, loadTeamsGuard, mustBeAuthenticatedGuard, mustBeNotAuthenticatedGuard, teamMustExistGuard, unsetAppGuard, unsetTeamGuard } from './shared';
import { AppAreaComponent, ForbiddenPageComponent, HomePageComponent, InternalAreaComponent, LoginPageComponent, LogoutPageComponent, NotFoundPageComponent, TeamsAreaComponent } from './shell';
const routes: Routes = [
export const APP_ROUTES: Routes = [
{
path: '',
component: HomePageComponent,
canActivate: [MustBeNotAuthenticatedGuard],
canActivate: [mustBeNotAuthenticatedGuard],
},
{
path: 'app',
component: InternalAreaComponent,
canActivate: [MustBeAuthenticatedGuard, LoadAppsGuard, LoadTeamsGuard, LoadSettingsGuard],
canActivate: [mustBeAuthenticatedGuard, loadAppsGuard, loadTeamsGuard, loadSettingsGuard],
children: [
{
path: '',
loadChildren: () => import('./features/apps/module').then(m => m.SqxFeatureAppsModule),
canActivate: [UnsetAppGuard, UnsetTeamGuard],
loadChildren: () => import('./features/apps/routes').then(m => m.APPS_ROUTES),
canActivate: [unsetAppGuard, unsetTeamGuard],
},
{
path: 'administration',
loadChildren: () => import('./features/administration/module').then(m => m.SqxFeatureAdministrationModule),
canActivate: [UnsetAppGuard, UnsetTeamGuard],
loadChildren: () => import('./features/administration/routes').then(m => m.ADMINISTRATION_ROUTES),
canActivate: [unsetAppGuard, unsetTeamGuard],
},
{
path: 'teams',
component: TeamsAreaComponent,
canActivate: [UnsetAppGuard, UnsetTeamGuard],
canActivate: [unsetAppGuard, unsetTeamGuard],
children: [
{
path: ':teamName',
canActivate: [TeamMustExistGuard],
loadChildren: () => import('./features/teams/module').then(m => m.SqxFeatureTeamsModule),
canActivate: [teamMustExistGuard],
loadChildren: () => import('./features/teams/routes').then(m => m.TEAM_ROUTES),
},
],
},
{
path: ':appName',
component: AppAreaComponent,
canActivate: [AppMustExistGuard],
canActivate: [appMustExistGuard],
children: [
{
path: '',
loadChildren: () => import('./features/dashboard/module').then(m => m.SqxFeatureDashboardModule),
loadChildren: () => import('./features/dashboard/routes').then(m => m.DASHBOARD_ROUTES),
},
{
path: 'content',
loadChildren: () => import('./features/content/module').then(m => m.SqxFeatureContentModule),
loadChildren: () => import('./features/content/routes').then(m => m.CONTENT_ROUTES),
},
{
path: 'schemas',
loadChildren: () => import('./features/schemas/module').then(m => m.SqxFeatureSchemasModule),
loadChildren: () => import('./features/schemas/routes').then(m => m.SCHEMAS_ROUTES),
},
{
path: 'assets',
loadChildren: () => import('./features/assets/module').then(m => m.SqxFeatureAssetsModule),
loadChildren: () => import('./features/assets/routes').then(m => m.ASSETS_ROUTES),
},
{
path: 'rules',
loadChildren: () => import('./features/rules/module').then(m => m.SqxFeatureRulesModule),
loadChildren: () => import('./features/rules/routes').then(m => m.RULES_ROUTES),
},
{
path: 'settings',
loadChildren: () => import('./features/settings/module').then(m => m.SqxFeatureSettingsModule),
loadChildren: () => import('./features/settings/routes').then(m => m.SETTINGS_ROUTES),
},
{
path: 'api',
loadChildren: () => import('./features/api/module').then(m => m.SqxFeatureApiModule),
loadChildren: () => import('./features/api/routes').then(m => m.API_ROUTES),
},
],
},
@ -97,5 +96,3 @@ const routes: Routes = [
component: NotFoundPageComponent,
},
];
export const routing: ModuleWithProviders<RouterModule> = RouterModule.forRoot(routes, { useHash: false });

22
frontend/src/app/assets/squid.svg

@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Layer_1"
viewBox="0 0 204.02457 346.98514"
version="1.1"
sodipodi:docname="squid.svg"
width="204.02457"
height="346.98514"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
id="Layer_1"
viewBox="0 0 204.02457 346.98514"
version="1.1"
sodipodi:docname="squid.svg"
width="204.02457"
height="346.98514"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
>
<defs
id="defs13" />
<sodipodi:namedview

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

15
frontend/src/app/features/administration/administration-area.component.ts

@ -5,13 +5,26 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgIf } from '@angular/common';
import { Component } from '@angular/core';
import { UIState } from '@app/shared';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { LayoutContainerDirective, TitleComponent, TranslatePipe, UIState } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-administration-area',
styleUrls: ['./administration-area.component.scss'],
templateUrl: './administration-area.component.html',
imports: [
AsyncPipe,
LayoutContainerDirective,
NgIf,
RouterLink,
RouterLinkActive,
RouterOutlet,
TitleComponent,
TranslatePipe,
],
})
export class AdministrationAreaComponent {
constructor(

16
frontend/src/app/features/administration/declarations.ts

@ -1,16 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './administration-area.component';
export * from './guards/user-must-exist.guard';
export * from './internal';
export * from './pages/event-consumers/event-consumer.component';
export * from './pages/event-consumers/event-consumers-page.component';
export * from './pages/restore/restore-page.component';
export * from './pages/users/user-page.component';
export * from './pages/users/user.component';
export * from './pages/users/users-page.component';

44
frontend/src/app/features/administration/guards/user-must-exist.guard.spec.ts

@ -5,24 +5,36 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { firstValueFrom, of } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import { UserDto, UsersState } from '@app/features/administration/internal';
import { UserMustExistGuard } from './user-must-exist.guard';
import { UserDto, UsersState } from '../internal';
import { userMustExistGuard } from './user-must-exist.guard';
describe('UserMustExistGuard', () => {
let usersState: IMock<UsersState>;
let router: IMock<Router>;
let userGuard: UserMustExistGuard;
let usersState: IMock<UsersState>;
beforeEach(() => {
router = Mock.ofType<Router>();
usersState = Mock.ofType<UsersState>();
userGuard = new UserMustExistGuard(usersState.object, router.object);
TestBed.configureTestingModule({
providers: [
{
provide: Router,
useValue: router.object,
},
{
provide: UsersState,
useValue: usersState.object,
},
],
});
});
it('should load user and return true if found', async () => {
bit('should load user and return true if found', async () => {
usersState.setup(x => x.select('123'))
.returns(() => of(<UserDto>{}));
@ -32,14 +44,14 @@ describe('UserMustExistGuard', () => {
},
};
const result = await firstValueFrom(userGuard.canActivate(route));
const result = await firstValueFrom(userMustExistGuard(route));
expect(result).toBeTruthy();
usersState.verify(x => x.select('123'), Times.once());
});
it('should load user and return false if not found', async () => {
bit('should load user and return false if not found', async () => {
usersState.setup(x => x.select('123'))
.returns(() => of(null));
@ -49,14 +61,14 @@ describe('UserMustExistGuard', () => {
},
};
const result = await firstValueFrom(userGuard.canActivate(route));
const result = await firstValueFrom(userMustExistGuard(route));
expect(result).toBeFalsy();
router.verify(x => x.navigate(['/404']), Times.once());
});
it('should unset user if user id is undefined', async () => {
bit('should unset user if user id is undefined', async () => {
usersState.setup(x => x.select(null))
.returns(() => of(null));
@ -66,14 +78,14 @@ describe('UserMustExistGuard', () => {
},
};
const result = await firstValueFrom(userGuard.canActivate(route));
const result = await firstValueFrom(userMustExistGuard(route));
expect(result).toBeTruthy();
usersState.verify(x => x.select(null), Times.once());
});
it('should unset user if user id is <new>', async () => {
bit('should unset user if user id is <new>', async () => {
usersState.setup(x => x.select(null))
.returns(() => of(null));
@ -83,10 +95,16 @@ describe('UserMustExistGuard', () => {
},
};
const result = await firstValueFrom(userGuard.canActivate(route));
const result = await firstValueFrom(userMustExistGuard(route));
expect(result).toBeTruthy();
usersState.verify(x => x.select(null), Times.once());
});
});
function bit(name: string, assertion: (() => PromiseLike<any>) | (() => void)) {
it(name, () => {
return TestBed.runInInjectionContext(() => assertion());
});
}

47
frontend/src/app/features/administration/guards/user-must-exist.guard.ts

@ -5,37 +5,30 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Injectable } from '@angular/core';
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { UsersState } from '@app/features/administration/internal';
import { allParams } from '@app/framework';
import { allParams } from '@app/shared';
import { UsersState } from '../internal';
@Injectable()
export class UserMustExistGuard {
constructor(
private readonly usersState: UsersState,
private readonly router: Router,
) {
}
export const userMustExistGuard = (route: ActivatedRouteSnapshot) => {
const usersState = inject(UsersState);
public canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
const userId = allParams(route)['userId'];
const userId = allParams(route)['userId'];
if (!userId || userId === 'new') {
return this.usersState.select(null).pipe(map(u => u === null));
}
if (!userId || userId === 'new') {
return usersState.select(null).pipe(map(u => u === null));
}
const result =
this.usersState.select(userId).pipe(
tap(dto => {
if (!dto) {
this.router.navigate(['/404']);
}
}),
map(u => !!u));
const router = inject(Router);
const result =
usersState.select(userId).pipe(
tap(dto => {
if (!dto) {
router.navigate(['/404']);
}
}),
map(u => !!u));
return result;
}
}
return result;
};

9
frontend/src/app/features/administration/pages/event-consumers/event-consumer.component.ts

@ -7,14 +7,21 @@
/* eslint-disable @angular-eslint/component-selector */
import { NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { EventConsumerDto, EventConsumersState } from '@app/features/administration/internal';
import { TooltipDirective } from '@app/shared';
import { EventConsumerDto, EventConsumersState } from '../../internal';
@Component({
standalone: true,
selector: '[sqxEventConsumer]',
styleUrls: ['./event-consumer.component.scss'],
templateUrl: './event-consumer.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
NgIf,
TooltipDirective,
],
})
export class EventConsumerComponent {
@Output()

27
frontend/src/app/features/administration/pages/event-consumers/event-consumers-page.component.ts

@ -5,16 +5,39 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgFor } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { timer } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { EventConsumerDto, EventConsumersState } from '@app/features/administration/internal';
import { DialogModel, Subscriptions } from '@app/shared';
import { DialogModel, LayoutComponent, ListViewComponent, ModalDialogComponent, ModalDirective, ShortcutDirective, SidebarMenuDirective, Subscriptions, SyncWidthDirective, TitleComponent, TooltipDirective, TourStepDirective, TranslatePipe } from '@app/shared';
import { EventConsumerDto, EventConsumersState } from '../../internal';
import { EventConsumerComponent } from './event-consumer.component';
@Component({
standalone: true,
selector: 'sqx-event-consumers-page',
styleUrls: ['./event-consumers-page.component.scss'],
templateUrl: './event-consumers-page.component.html',
imports: [
AsyncPipe,
EventConsumerComponent,
LayoutComponent,
ListViewComponent,
ModalDialogComponent,
ModalDirective,
NgFor,
RouterLink,
RouterLinkActive,
RouterOutlet,
ShortcutDirective,
SidebarMenuDirective,
SyncWidthDirective,
TitleComponent,
TooltipDirective,
TourStepDirective,
TranslatePipe,
],
})
export class EventConsumersPageComponent implements OnInit {
private readonly subscriptions = new Subscriptions();

25
frontend/src/app/features/administration/pages/restore/restore-page.component.ts

@ -5,14 +5,37 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { Component } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { timer } from 'rxjs';
import { AuthService, BackupsService, DialogService, RestoreForm, switchSafe } from '@app/shared';
import { AuthService, BackupsService, ControlErrorsComponent, DialogService, ISODatePipe, LayoutComponent, ListViewComponent, RestoreForm, SidebarMenuDirective, switchSafe, TitleComponent, TooltipDirective, TourStepDirective, TranslatePipe } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-restore-page',
styleUrls: ['./restore-page.component.scss'],
templateUrl: './restore-page.component.html',
imports: [
AsyncPipe,
ControlErrorsComponent,
FormsModule,
ISODatePipe,
LayoutComponent,
ListViewComponent,
NgFor,
NgIf,
ReactiveFormsModule,
RouterLink,
RouterLinkActive,
RouterOutlet,
SidebarMenuDirective,
TitleComponent,
TooltipDirective,
TourStepDirective,
TranslatePipe,
],
})
export class RestorePageComponent {
public restoreForm = new RestoreForm();

20
frontend/src/app/features/administration/pages/users/user-page.component.ts

@ -5,15 +5,31 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgIf } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { UpsertUserDto, UserDto, UserForm, UsersState } from '@app/features/administration/internal';
import { Subscriptions } from '@app/shared';
import { ControlErrorsComponent, FormErrorComponent, LayoutComponent, ShortcutDirective, Subscriptions, TitleComponent, TooltipDirective, TranslatePipe } from '@app/shared';
import { UpsertUserDto, UserDto, UserForm, UsersState } from '../../internal';
@Component({
standalone: true,
selector: 'sqx-user-page',
styleUrls: ['./user-page.component.scss'],
templateUrl: './user-page.component.html',
imports: [
AsyncPipe,
ControlErrorsComponent,
FormErrorComponent,
FormsModule,
LayoutComponent,
NgIf,
ReactiveFormsModule,
ShortcutDirective,
TitleComponent,
TooltipDirective,
TranslatePipe,
],
})
export class UserPageComponent implements OnInit {
private readonly subscriptions = new Subscriptions();

15
frontend/src/app/features/administration/pages/users/user.component.ts

@ -7,14 +7,27 @@
/* eslint-disable @angular-eslint/component-selector */
import { NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { UserDto, UsersState } from '@app/features/administration/internal';
import { RouterLink, RouterLinkActive } from '@angular/router';
import { ConfirmClickDirective, StopClickDirective, TooltipDirective, UserDtoPicture } from '@app/shared';
import { UserDto, UsersState } from '../../internal';
@Component({
standalone: true,
selector: '[sqxUser]',
styleUrls: ['./user.component.scss'],
templateUrl: './user.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ConfirmClickDirective,
NgIf,
RouterLink,
RouterLinkActive,
StopClickDirective,
TooltipDirective,
UserDtoPicture,
],
})
export class UserComponent {
@Input('sqxUser')

31
frontend/src/app/features/administration/pages/users/users-page.component.ts

@ -5,18 +5,43 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { UntypedFormControl } from '@angular/forms';
import { UserDto, UsersState } from '@app/features/administration/internal';
import { Router2State, Subscriptions } from '@app/framework';
import { FormsModule, ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { LayoutComponent, ListViewComponent, PagerComponent, Router2State, ShortcutDirective, SidebarMenuDirective, Subscriptions, SyncWidthDirective, TitleComponent, TooltipDirective, TourStepDirective, TranslatePipe } from '@app/shared';
import { UserDto, UsersState } from '../../internal';
import { UserComponent } from './user.component';
@Component({
standalone: true,
selector: 'sqx-users-page',
styleUrls: ['./users-page.component.scss'],
templateUrl: './users-page.component.html',
providers: [
Router2State,
],
imports: [
AsyncPipe,
FormsModule,
LayoutComponent,
ListViewComponent,
NgFor,
NgIf,
PagerComponent,
ReactiveFormsModule,
RouterLink,
RouterLinkActive,
RouterOutlet,
ShortcutDirective,
SidebarMenuDirective,
SyncWidthDirective,
TitleComponent,
TooltipDirective,
TourStepDirective,
TranslatePipe,
UserComponent,
],
})
export class UsersPageComponent implements OnInit {
private readonly subscriptions = new Subscriptions();

52
frontend/src/app/features/administration/module.ts → frontend/src/app/features/administration/routes.ts

@ -5,12 +5,17 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HelpComponent, SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { AdministrationAreaComponent, EventConsumerComponent, EventConsumersPageComponent, EventConsumersService, EventConsumersState, RestorePageComponent, UserComponent, UserMustExistGuard, UserPageComponent, UsersPageComponent, UsersService, UsersState } from './declarations';
import { Routes } from '@angular/router';
import { HelpComponent, UsersService } from '@app/shared';
import { AdministrationAreaComponent } from './administration-area.component';
import { userMustExistGuard } from './guards/user-must-exist.guard';
import { EventConsumersService, EventConsumersState, UsersState } from './internal';
import { EventConsumersPageComponent } from './pages/event-consumers/event-consumers-page.component';
import { RestorePageComponent } from './pages/restore/restore-page.component';
import { UserPageComponent } from './pages/users/user-page.component';
import { UsersPageComponent } from './pages/users/users-page.component';
const routes: Routes = [
export const ADMINISTRATION_ROUTES: Routes = [
{
path: '',
component: AdministrationAreaComponent,
@ -23,6 +28,10 @@ const routes: Routes = [
{
path: 'event-consumers',
component: EventConsumersPageComponent,
providers: [
EventConsumersService,
EventConsumersState,
],
children: [
{
path: 'help',
@ -49,6 +58,10 @@ const routes: Routes = [
{
path: 'users',
component: UsersPageComponent,
providers: [
UsersService,
UsersState,
],
children: [
{
path: 'help',
@ -60,35 +73,10 @@ const routes: Routes = [
{
path: ':userId',
component: UserPageComponent,
canActivate: [UserMustExistGuard],
canActivate: [userMustExistGuard],
},
],
},
],
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
SqxFrameworkModule,
SqxSharedModule,
],
declarations: [
AdministrationAreaComponent,
EventConsumerComponent,
EventConsumersPageComponent,
RestorePageComponent,
UserComponent,
UserPageComponent,
UsersPageComponent,
],
providers: [
EventConsumersService,
EventConsumersState,
UserMustExistGuard,
UsersService,
UsersState,
],
})
export class SqxFeatureAdministrationModule {}
];

2
frontend/src/app/features/administration/services/event-consumers.service.spec.ts

@ -7,7 +7,7 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing';
import { ApiUrlConfig, Resource, ResourceLinks } from '@app/framework';
import { ApiUrlConfig, Resource, ResourceLinks } from '@app/shared';
import { EventConsumerDto, EventConsumersDto, EventConsumersService } from './event-consumers.service';
describe('EventConsumersService', () => {

2
frontend/src/app/features/administration/services/users.service.spec.ts

@ -7,7 +7,7 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing';
import { ApiUrlConfig, Resource, ResourceLinks } from '@app/framework';
import { ApiUrlConfig, Resource, ResourceLinks } from '@app/shared';
import { UserDto, UsersDto, UsersService } from './users.service';
describe('UsersService', () => {

6
frontend/src/app/features/administration/state/event-consumers.state.spec.ts

@ -7,9 +7,9 @@
import { of, onErrorResumeNextWith, throwError } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq';
import { EventConsumersService } from '@app/features/administration/internal';
import { DialogService } from '@app/framework';
import { createEventConsumer } from './../services/event-consumers.service.spec';
import { DialogService } from '@app/shared';
import { EventConsumersService } from '../internal';
import { createEventConsumer } from '../services/event-consumers.service.spec';
import { EventConsumersState } from './event-consumers.state';
describe('EventConsumersState', () => {

2
frontend/src/app/features/administration/state/event-consumers.state.ts

@ -9,7 +9,7 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { finalize, tap } from 'rxjs/operators';
import { debug, DialogService, LoadingState, shareSubscribed, State } from '@app/shared';
import { EventConsumerDto, EventConsumersService } from './../services/event-consumers.service';
import { EventConsumerDto, EventConsumersService } from '../services/event-consumers.service';
interface Snapshot extends LoadingState {
// The list of event consumers.

2
frontend/src/app/features/administration/state/users.forms.ts

@ -7,7 +7,7 @@
import { UntypedFormControl, Validators } from '@angular/forms';
import { ExtendedFormGroup, Form, ValidatorsEx } from '@app/shared';
import { UpsertUserDto, UserDto } from './../services/users.service';
import { UpsertUserDto, UserDto } from '../services/users.service';
export class UserForm extends Form<ExtendedFormGroup, UpsertUserDto, UserDto> {
constructor() {

4
frontend/src/app/features/administration/state/users.state.spec.ts

@ -7,9 +7,9 @@
import { firstValueFrom, of, onErrorResumeNextWith, throwError } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq';
import { UpsertUserDto, UsersService } from '@app/features/administration/internal';
import { DialogService } from '@app/shared';
import { createUser } from './../services/users.service.spec';
import { UpsertUserDto, UsersService } from '../internal';
import { createUser } from '../services/users.service.spec';
import { UsersState } from './users.state';
describe('UsersState', () => {

2
frontend/src/app/features/administration/state/users.state.ts

@ -12,7 +12,7 @@ import '@app/framework/utils/rxjs-extensions';
import { EMPTY, Observable, of } from 'rxjs';
import { catchError, finalize, tap } from 'rxjs/operators';
import { debug, DialogService, getPagingInfo, ListState, shareSubscribed, State } from '@app/shared';
import { UpsertUserDto, UserDto, UsersService } from './../services/users.service';
import { UpsertUserDto, UserDto, UsersService } from '../services/users.service';
interface Snapshot extends ListState<string> {
// The current users.

14
frontend/src/app/features/api/api-area.component.ts

@ -6,12 +6,24 @@
*/
import { Component } from '@angular/core';
import { AppsState } from '@app/shared';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { AppsState, ExternalLinkDirective, LayoutComponent, TitleComponent, TourStepDirective, TranslatePipe } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-api-area',
styleUrls: ['./api-area.component.scss'],
templateUrl: './api-area.component.html',
imports: [
ExternalLinkDirective,
LayoutComponent,
RouterLink,
RouterLinkActive,
RouterOutlet,
TitleComponent,
TourStepDirective,
TranslatePipe,
],
})
export class ApiAreaComponent {
constructor(

9
frontend/src/app/features/api/declarations.ts

@ -1,9 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './api-area.component';
export * from './pages/graphql/graphql-page.component';

9
frontend/src/app/features/api/index.ts

@ -1,9 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './declarations';
export * from './module';

37
frontend/src/app/features/api/module.ts

@ -1,37 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { ApiAreaComponent, GraphQLPageComponent } from './declarations';
const routes: Routes = [
{
path: '',
component: ApiAreaComponent,
children: [
{
path: 'graphql',
component: GraphQLPageComponent,
},
],
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
SqxFrameworkModule,
SqxSharedModule,
],
declarations: [
ApiAreaComponent,
GraphQLPageComponent,
],
})
export class SqxFeatureApiModule {}

19
frontend/src/app/features/api/pages/graphql/graphql-page.component.ts

@ -5,17 +5,34 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { createGraphiQLFetcher } from '@graphiql/toolkit';
import GraphiQL from 'graphiql';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { ApiUrlConfig, AppsState, AuthService, ClientDto, ClientsService, ClientsState, DialogModel, MessageBus, QueryExecuted, Types } from '@app/shared';
import { ApiUrlConfig, AppsState, AuthService, ClientDto, ClientsService, ClientsState, DialogModel, FormHintComponent, LayoutComponent, MessageBus, ModalDialogComponent, ModalDirective, QueryExecuted, TitleComponent, TooltipDirective, TourStepDirective, TranslatePipe, Types } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-graphql-page',
styleUrls: ['./graphql-page.component.scss'],
templateUrl: './graphql-page.component.html',
imports: [
AsyncPipe,
FormHintComponent,
FormsModule,
LayoutComponent,
ModalDialogComponent,
ModalDirective,
NgFor,
NgIf,
TitleComponent,
TooltipDirective,
TourStepDirective,
TranslatePipe,
],
})
export class GraphQLPageComponent implements AfterViewInit, OnInit {
@ViewChild('graphiQLContainer', { static: false })

23
frontend/src/app/features/api/routes.ts

@ -0,0 +1,23 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Routes } from '@angular/router';
import { ApiAreaComponent } from './api-area.component';
import { GraphQLPageComponent } from './pages/graphql/graphql-page.component';
export const API_ROUTES: Routes = [
{
path: '',
component: ApiAreaComponent,
children: [
{
path: 'graphql',
component: GraphQLPageComponent,
},
],
},
];

12
frontend/src/app/features/apps/declarations.ts

@ -1,12 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './pages/app.component';
export * from './pages/apps-page.component';
export * from './pages/news-dialog.component';
export * from './pages/onboarding-dialog.component';
export * from './pages/team.component';

9
frontend/src/app/features/apps/index.ts

@ -1,9 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './declarations';
export * from './module';

34
frontend/src/app/features/apps/module.ts

@ -1,34 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { AppComponent, AppsPageComponent, NewsDialogComponent, OnboardingDialogComponent, TeamComponent } from './declarations';
const routes: Routes = [
{
path: '',
component: AppsPageComponent,
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
SqxFrameworkModule,
SqxSharedModule,
],
declarations: [
AppComponent,
AppsPageComponent,
NewsDialogComponent,
OnboardingDialogComponent,
TeamComponent,
],
})
export class SqxFeatureAppsModule {}

17
frontend/src/app/features/apps/pages/app.component.ts

@ -5,14 +5,29 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { AppDto, ModalModel } from '@app/shared';
import { RouterLink } from '@angular/router';
import { AppDto, AvatarComponent, ConfirmClickDirective, DropdownMenuComponent, ModalDirective, ModalModel, ModalPlacementDirective, StopClickDirective, TourStepDirective, TranslatePipe } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-app',
styleUrls: ['./app.component.scss'],
templateUrl: './app.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AvatarComponent,
ConfirmClickDirective,
DropdownMenuComponent,
ModalDirective,
ModalPlacementDirective,
NgIf,
RouterLink,
StopClickDirective,
TourStepDirective,
TranslatePipe,
],
})
export class AppComponent {
@Input({ required: true })

24
frontend/src/app/features/apps/pages/apps-page.component.ts

@ -5,18 +5,38 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { combineLatest } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { AppDto, AppsState, AuthService, DialogModel, FeatureDto, LocalStoreService, NewsService, TeamDto, TeamsState, TemplateDto, TemplatesState, TourState, UIOptions, UIState } from '@app/shared';
import { Settings } from '@app/shared/state/settings';
import { AppDto, AppFormComponent, AppsState, AuthService, DialogModel, FeatureDto, FormHintComponent, LocalStoreService, ModalDirective, NewsService, Settings, TeamDto, TeamsState, TemplateDto, TemplatesState, TitleComponent, TourState, TourStepDirective, TranslatePipe, UIOptions, UIState } from '@app/shared';
import { AppComponent } from './app.component';
import { NewsDialogComponent } from './news-dialog.component';
import { OnboardingDialogComponent } from './onboarding-dialog.component';
import { TeamComponent } from './team.component';
type GroupedApps = { team?: TeamDto; apps: AppDto[] };
@Component({
standalone: true,
selector: 'sqx-apps-page',
styleUrls: ['./apps-page.component.scss'],
templateUrl: './apps-page.component.html',
imports: [
AppComponent,
AppFormComponent,
AsyncPipe,
FormHintComponent,
ModalDirective,
NewsDialogComponent,
NgFor,
NgIf,
OnboardingDialogComponent,
TeamComponent,
TitleComponent,
TourStepDirective,
TranslatePipe,
],
})
export class AppsPageComponent implements OnInit {
public addAppDialog = new DialogModel();

11
frontend/src/app/features/apps/pages/news-dialog.component.ts

@ -5,13 +5,22 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgFor } from '@angular/common';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FeatureDto } from '@app/shared';
import { FeatureDto, HelpMarkdownPipe, ModalDialogComponent, TooltipDirective, TranslatePipe } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-news-dialog',
styleUrls: ['./news-dialog.component.scss'],
templateUrl: './news-dialog.component.html',
imports: [
HelpMarkdownPipe,
ModalDialogComponent,
NgFor,
TooltipDirective,
TranslatePipe,
],
})
export class NewsDialogComponent {
@Output()

16
frontend/src/app/features/apps/pages/onboarding-dialog.component.ts

@ -5,18 +5,28 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgIf } from '@angular/common';
import { Component, EventEmitter, Output } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { fadeAnimation, slideAnimation } from '@app/framework';
import { TourState, UsersService } from '@app/shared';
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { fadeAnimation, MarkdownPipe, ModalDialogComponent, SafeHtmlPipe, slideAnimation, TourState, TranslatePipe, UsersService } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-onboarding-dialog',
styleUrls: ['./onboarding-dialog.component.scss'],
templateUrl: './onboarding-dialog.component.html',
animations: [
fadeAnimation, slideAnimation,
],
imports: [
FormsModule,
MarkdownPipe,
ModalDialogComponent,
NgIf,
ReactiveFormsModule,
SafeHtmlPipe,
TranslatePipe,
],
})
export class OnboardingDialogComponent {

13
frontend/src/app/features/apps/pages/team.component.ts

@ -6,13 +6,24 @@
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ModalModel, TeamDto } from '@app/shared';
import { RouterLink } from '@angular/router';
import { ConfirmClickDirective, DropdownMenuComponent, ModalDirective, ModalModel, ModalPlacementDirective, StopClickDirective, TeamDto, TranslatePipe } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-team',
styleUrls: ['./team.component.scss'],
templateUrl: './team.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ConfirmClickDirective,
DropdownMenuComponent,
ModalDirective,
ModalPlacementDirective,
RouterLink,
StopClickDirective,
TranslatePipe,
],
})
export class TeamComponent {
@Input({ required: true })

16
frontend/src/app/features/apps/routes.ts

@ -0,0 +1,16 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Routes } from '@angular/router';
import { AppsPageComponent } from './pages/apps-page.component';
export const APPS_ROUTES: Routes = [
{
path: '',
component: AppsPageComponent,
},
];

11
frontend/src/app/features/assets/declarations.ts

@ -1,11 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './pages/asset-tag-dialog.component';
export * from './pages/asset-tags.component';
export * from './pages/assets-filters-page.component';
export * from './pages/assets-page.component';

9
frontend/src/app/features/assets/index.ts

@ -1,9 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './declarations';
export * from './module';

39
frontend/src/app/features/assets/module.ts

@ -1,39 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { AssetsFiltersPageComponent, AssetsPageComponent, AssetTagDialogComponent, AssetTagsComponent } from './declarations';
const routes: Routes = [
{
path: '',
component: AssetsPageComponent,
children: [
{
path: 'filters',
component: AssetsFiltersPageComponent,
},
],
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
SqxFrameworkModule,
SqxSharedModule,
],
declarations: [
AssetsFiltersPageComponent,
AssetsPageComponent,
AssetTagDialogComponent,
AssetTagsComponent,
],
})
export class SqxFeatureAssetsModule {}

15
frontend/src/app/features/assets/pages/asset-tag-dialog.component.ts

@ -5,13 +5,28 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe } from '@angular/common';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ControlErrorsComponent, FocusOnInitDirective, FormErrorComponent, ModalDialogComponent, TooltipDirective, TranslatePipe } from '@app/shared';
import { AssetsState, RenameAssetTagForm } from '@app/shared/internal';
@Component({
standalone: true,
selector: 'sqx-asset-tag-dialog',
styleUrls: ['./asset-tag-dialog.component.scss'],
templateUrl: './asset-tag-dialog.component.html',
imports: [
AsyncPipe,
ControlErrorsComponent,
FocusOnInitDirective,
FormErrorComponent,
FormsModule,
ModalDialogComponent,
ReactiveFormsModule,
TooltipDirective,
TranslatePipe,
],
})
export class AssetTagDialogComponent implements OnInit {
@Output()

13
frontend/src/app/features/assets/pages/asset-tags.component.ts

@ -7,14 +7,25 @@
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
import { NgFor, NgIf } from '@angular/common';
import { booleanAttribute, ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { DialogModel, TagItem, TagsSelected } from '@app/shared';
import { DialogModel, ModalDirective, StopClickDirective, TagItem, TagsSelected, TranslatePipe } from '@app/shared';
import { AssetTagDialogComponent } from './asset-tag-dialog.component';
@Component({
standalone: true,
selector: 'sqx-asset-tags',
styleUrls: ['./asset-tags.component.scss'],
templateUrl: './asset-tags.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AssetTagDialogComponent,
ModalDirective,
NgFor,
NgIf,
StopClickDirective,
TranslatePipe,
],
})
export class AssetTagsComponent {
@Output()

12
frontend/src/app/features/assets/pages/assets-filters-page.component.ts

@ -5,13 +5,23 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe } from '@angular/common';
import { Component } from '@angular/core';
import { AssetsState, Queries, Query, UIState } from '@app/shared';
import { AssetsState, LayoutComponent, Queries, Query, SavedQueriesComponent, TranslatePipe, UIState } from '@app/shared';
import { AssetTagsComponent } from './asset-tags.component';
@Component({
standalone: true,
selector: 'sqx-assets-filters-page',
styleUrls: ['./assets-filters-page.component.scss'],
templateUrl: './assets-filters-page.component.html',
imports: [
AssetTagsComponent,
AsyncPipe,
LayoutComponent,
SavedQueriesComponent,
TranslatePipe,
],
})
export class AssetsFiltersPageComponent {
public assetsQueries: Queries;

31
frontend/src/app/features/assets/pages/assets-page.component.ts

@ -5,17 +5,44 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgIf } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { AssetDto, AssetsState, DialogModel, LocalStoreService, MathHelper, Queries, Query, QueryFullTextSynchronizer, Router2State, UIState } from '@app/shared';
import { Settings } from '@app/shared/state/settings';
import { FormsModule } from '@angular/forms';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { AssetDialogComponent, AssetDto, AssetFolderDialogComponent, AssetPathComponent, AssetsListComponent, AssetsState, DialogModel, LayoutComponent, ListViewComponent, LocalStoreService, MathHelper, ModalDirective, PagerComponent, Queries, Query, QueryFullTextSynchronizer, Router2State, SearchFormComponent, Settings, ShortcutDirective, SidebarMenuDirective, TagEditorComponent, TitleComponent, TooltipDirective, TourStepDirective, TranslatePipe, UIState } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-assets-page',
styleUrls: ['./assets-page.component.scss'],
templateUrl: './assets-page.component.html',
providers: [
Router2State,
],
imports: [
AssetDialogComponent,
AssetFolderDialogComponent,
AssetPathComponent,
AssetsListComponent,
AsyncPipe,
FormsModule,
LayoutComponent,
ListViewComponent,
ModalDirective,
NgIf,
PagerComponent,
RouterLink,
RouterLinkActive,
RouterOutlet,
SearchFormComponent,
ShortcutDirective,
SidebarMenuDirective,
TagEditorComponent,
TitleComponent,
TooltipDirective,
TourStepDirective,
TranslatePipe,
],
})
export class AssetsPageComponent implements OnInit {
public editAsset?: AssetDto;

23
frontend/src/app/features/assets/routes.ts

@ -0,0 +1,23 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Routes } from '@angular/router';
import { AssetsFiltersPageComponent } from './pages/assets-filters-page.component';
import { AssetsPageComponent } from './pages/assets-page.component';
export const ASSETS_ROUTES: Routes = [
{
path: '',
component: AssetsPageComponent,
children: [
{
path: 'filters',
component: AssetsFiltersPageComponent,
},
],
},
];

43
frontend/src/app/features/content/declarations.ts

@ -1,43 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './pages/calendar/calendar-page.component';
export * from './pages/comments/comments-page.component';
export * from './pages/content/content-event.component';
export * from './pages/content/content-history-page.component';
export * from './pages/content/content-page.component';
export * from './pages/content/editor/content-editor.component';
export * from './pages/content/editor/content-field.component';
export * from './pages/content/editor/content-section.component';
export * from './pages/content/editor/field-copy-button.component';
export * from './pages/content/editor/field-languages.component';
export * from './pages/content/inspecting/content-inspection.component';
export * from './pages/content/references/content-references.component';
export * from './pages/contents/contents-filters-page.component';
export * from './pages/contents/contents-page.component';
export * from './pages/contents/custom-view-editor.component';
export * from './pages/schemas/schemas-page.component';
export * from './pages/sidebar/sidebar-page.component';
export * from './pages/references/references-page.component';
export * from './shared/content-extension.component';
export * from './shared/due-time-selector.component';
export * from './shared/forms/array-editor.component';
export * from './shared/forms/array-item.component';
export * from './shared/forms/assets-editor.component';
export * from './shared/forms/component-section.component';
export * from './shared/forms/component.component';
export * from './shared/forms/field-editor.component';
export * from './shared/forms/iframe-editor.component';
export * from './shared/forms/stock-photo-editor.component';
export * from './shared/list/content.component';
export * from './shared/preview-button.component';
export * from './shared/references/content-creator.component';
export * from './shared/references/reference-dropdown.component';
export * from './shared/references/reference-item.component';
export * from './shared/references/references-checkboxes.component';
export * from './shared/references/references-editor.component';
export * from './shared/references/references-tags.component';

9
frontend/src/app/features/content/index.ts

@ -1,9 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './declarations';
export * from './module';

135
frontend/src/app/features/content/module.ts

@ -1,135 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { VirtualScrollerModule } from '@iharbeck/ngx-virtual-scroller';
import { CanDeactivateGuard, ContentMustExistGuard, LoadLanguagesGuard, LoadSchemasGuard, SchemaMustExistPublishedGuard, SchemaMustNotBeSingletonGuard, SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { ArrayEditorComponent, ArrayItemComponent, AssetsEditorComponent, CalendarPageComponent, CommentsPageComponent, ComponentComponent, ComponentSectionComponent, ContentComponent, ContentCreatorComponent, ContentEditorComponent, ContentEventComponent, ContentExtensionComponent, ContentFieldComponent, ContentHistoryPageComponent, ContentInspectionComponent, ContentPageComponent, ContentReferencesComponent, ContentSectionComponent, ContentsFiltersPageComponent, ContentsPageComponent, CustomViewEditorComponent, DueTimeSelectorComponent, FieldCopyButtonComponent, FieldEditorComponent, FieldLanguagesComponent, IFrameEditorComponent, PreviewButtonComponent, ReferenceDropdownComponent, ReferenceItemComponent, ReferencesCheckboxesComponent, ReferencesEditorComponent, ReferencesPageComponent, ReferencesTagsComponent, SchemasPageComponent, SidebarPageComponent, StockPhotoEditorComponent } from './declarations';
const routes: Routes = [
{
path: '',
component: SchemasPageComponent,
canActivate: [LoadLanguagesGuard, LoadSchemasGuard],
children: [
{
path: '__calendar',
component: CalendarPageComponent,
},
{
path: '__references/:referenceId',
component: ReferencesPageComponent,
},
{
path: ':schemaName',
canActivate: [SchemaMustExistPublishedGuard],
children: [
{
path: '',
component: ContentsPageComponent,
canActivate: [SchemaMustNotBeSingletonGuard, ContentMustExistGuard],
canDeactivate: [CanDeactivateGuard],
children: [
{
path: 'filters',
component: ContentsFiltersPageComponent,
},
{
path: 'sidebar',
component: SidebarPageComponent,
},
],
},
{
path: 'new',
component: ContentPageComponent,
canActivate: [SchemaMustNotBeSingletonGuard, ContentMustExistGuard],
canDeactivate: [CanDeactivateGuard],
data: {
reuseId: 'contentPage',
},
},
{
path: ':contentId',
component: ContentPageComponent,
canActivate: [ContentMustExistGuard],
canDeactivate: [CanDeactivateGuard],
data: {
reuseId: 'contentPage',
},
children: [
{
path: 'history',
component: ContentHistoryPageComponent,
data: {
channel: 'contents.{contentId}',
},
},
{
path: 'comments',
component: CommentsPageComponent,
},
{
path: 'sidebar',
component: SidebarPageComponent,
},
],
},
],
}],
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
SqxFrameworkModule,
SqxSharedModule,
VirtualScrollerModule,
],
declarations: [
ArrayEditorComponent,
ArrayItemComponent,
AssetsEditorComponent,
CalendarPageComponent,
CommentsPageComponent,
ComponentComponent,
ComponentSectionComponent,
ContentComponent,
ContentCreatorComponent,
ContentEditorComponent,
ContentEventComponent,
ContentExtensionComponent,
ContentFieldComponent,
ContentHistoryPageComponent,
ContentInspectionComponent,
ContentPageComponent,
ContentReferencesComponent,
ContentSectionComponent,
ContentsFiltersPageComponent,
ContentsPageComponent,
CustomViewEditorComponent,
DueTimeSelectorComponent,
FieldCopyButtonComponent,
FieldEditorComponent,
FieldLanguagesComponent,
IFrameEditorComponent,
PreviewButtonComponent,
ReferenceDropdownComponent,
ReferenceItemComponent,
ReferencesCheckboxesComponent,
ReferencesEditorComponent,
ReferencesEditorComponent,
ReferencesPageComponent,
ReferencesTagsComponent,
SchemasPageComponent,
SidebarPageComponent,
StockPhotoEditorComponent,
],
})
export class SqxFeatureContentModule {}

23
frontend/src/app/features/content/pages/calendar/calendar-page.component.ts

@ -5,17 +5,38 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgIf } from '@angular/common';
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { AppsState, ContentDto, ContentsService, DateTime, DialogModel, getContentValue, LanguageDto, LanguagesState, LocalizerService, ResourceLoaderService } from '@app/shared';
import { FormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router';
import { AppsState, ConfirmClickDirective, ContentDto, ContentsService, ContentStatusComponent, CopyDirective, DateTime, DialogModel, FullDateTimePipe, getContentValue, LanguageDto, LanguagesState, LayoutComponent, LocalizerService, ModalDialogComponent, ModalDirective, ResourceLoaderService, TitleComponent, TooltipDirective, TranslatePipe, UserNameRefPipe, UserPictureRefPipe } from '@app/shared';
declare const tui: any;
type ViewMode = 'day' | 'week' | 'month';
@Component({
standalone: true,
selector: 'sqx-calendar-page',
styleUrls: ['./calendar-page.component.scss'],
templateUrl: './calendar-page.component.html',
imports: [
ConfirmClickDirective,
ContentStatusComponent,
CopyDirective,
FormsModule,
FullDateTimePipe,
LayoutComponent,
ModalDialogComponent,
ModalDirective,
NgIf,
RouterLink,
TitleComponent,
TooltipDirective,
TranslatePipe,
UserNameRefPipe,
UserPictureRefPipe,
],
})
export class CalendarPageComponent implements AfterViewInit, OnDestroy, OnInit {
private calendar: any;

8
frontend/src/app/features/content/pages/comments/comments-page.component.ts

@ -5,14 +5,22 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe } from '@angular/common';
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { map } from 'rxjs/operators';
import { CommentsComponent, LayoutComponent } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-comments-page',
styleUrls: ['./comments-page.component.scss'],
templateUrl: './comments-page.component.html',
imports: [
AsyncPipe,
CommentsComponent,
LayoutComponent,
],
})
export class CommentsPageComponent {
public commentsId = this.route.parent!.params.pipe(map(x => x['contentId']));

13
frontend/src/app/features/content/pages/content/content-event.component.ts

@ -5,14 +5,25 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ContentDto, HistoryEventDto, TypedSimpleChanges } from '@app/shared';
import { ContentDto, FromNowPipe, HistoryEventDto, HistoryMessagePipe, TooltipDirective, TranslatePipe, TypedSimpleChanges, UserNameRefPipe, UserPictureRefPipe } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-content-event',
styleUrls: ['./content-event.component.scss'],
templateUrl: './content-event.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
FromNowPipe,
HistoryMessagePipe,
NgIf,
TooltipDirective,
TranslatePipe,
UserNameRefPipe,
UserPictureRefPipe,
],
})
export class ContentEventComponent {
@Output()

25
frontend/src/app/features/content/pages/content/content-history-page.component.ts

@ -5,17 +5,38 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { Component, OnInit, ViewChild } from '@angular/core';
import { Observable, timer } from 'rxjs';
import { map } from 'rxjs/operators';
import { AppsState, ContentDto, ContentsState, defined, HistoryEventDto, HistoryService, ModalModel, SchemasState, Subscriptions, switchSafe } from '@app/shared';
import { DueTimeSelectorComponent } from './../../shared/due-time-selector.component';
import { AppsState, ConfirmClickDirective, ContentDto, ContentsState, ContentStatusComponent, CopyDirective, defined, DropdownMenuComponent, FormHintComponent, FromNowPipe, HistoryEventDto, HistoryService, LayoutComponent, ModalDirective, ModalModel, ModalPlacementDirective, SchemasState, Subscriptions, switchSafe, TourStepDirective, TranslatePipe } from '@app/shared';
import { DueTimeSelectorComponent } from '../../shared/due-time-selector.component';
import { ContentEventComponent } from './content-event.component';
import { ContentPageComponent } from './content-page.component';
@Component({
standalone: true,
selector: 'sqx-history',
styleUrls: ['./content-history-page.component.scss'],
templateUrl: './content-history-page.component.html',
imports: [
AsyncPipe,
ConfirmClickDirective,
ContentEventComponent,
ContentStatusComponent,
CopyDirective,
DropdownMenuComponent,
DueTimeSelectorComponent,
FormHintComponent,
FromNowPipe,
LayoutComponent,
ModalDirective,
ModalPlacementDirective,
NgFor,
NgIf,
TourStepDirective,
TranslatePipe,
],
})
export class ContentHistoryPageComponent implements OnInit {
private readonly subscriptions = new Subscriptions();

44
frontend/src/app/features/content/pages/content/content-page.component.ts

@ -5,13 +5,21 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgIf, NgSwitch, NgSwitchCase } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { Observable, of } from 'rxjs';
import { filter, map, tap } from 'rxjs/operators';
import { ApiUrlConfig, AppLanguageDto, AppsState, AuthService, AutoSaveKey, AutoSaveService, CanComponentDeactivate, CollaborationService, ContentDto, ContentsState, defined, DialogService, EditContentForm, LanguagesState, LocalStoreService, ModalModel, ResolveAssets, ResolveContents, SchemaDto, SchemasState, Settings, Subscriptions, TempService, ToolbarService, Types, Version } from '@app/shared';
import { ApiUrlConfig, AppLanguageDto, AppsState, AuthService, AutoSaveKey, AutoSaveService, CanComponentDeactivate, CollaborationService, ConfirmClickDirective, ContentDto, ContentsState, defined, DialogService, DropdownMenuComponent, EditContentForm, LanguageSelectorComponent, LanguagesState, LayoutComponent, LocalStoreService, ModalDirective, ModalModel, ModalPlacementDirective, NotifoComponent, ResolveAssets, ResolveContents, SchemaDto, SchemasState, Settings, ShortcutDirective, SidebarMenuDirective, Subscriptions, TempService, TitleComponent, ToolbarComponent, ToolbarService, TooltipDirective, TourHintDirective, TourStepDirective, TranslatePipe, Types, Version, WatchingUsersComponent } from '@app/shared';
import { ContentExtensionComponent } from '../../shared/content-extension.component';
import { PreviewButtonComponent } from '../../shared/preview-button.component';
import { ContentEditorComponent } from './editor/content-editor.component';
import { ContentInspectionComponent } from './inspecting/content-inspection.component';
import { ContentReferencesComponent } from './references/content-references.component';
@Component({
standalone: true,
selector: 'sqx-content-page',
styleUrls: ['./content-page.component.scss'],
templateUrl: './content-page.component.html',
@ -21,6 +29,38 @@ import { ApiUrlConfig, AppLanguageDto, AppsState, AuthService, AutoSaveKey, Auto
ResolveContents,
ToolbarService,
],
imports: [
AsyncPipe,
ConfirmClickDirective,
ContentEditorComponent,
ContentExtensionComponent,
ContentInspectionComponent,
ContentReferencesComponent,
DropdownMenuComponent,
FormsModule,
LanguageSelectorComponent,
LayoutComponent,
ModalDirective,
ModalPlacementDirective,
NgIf,
NgSwitch,
NgSwitchCase,
NotifoComponent,
PreviewButtonComponent,
ReactiveFormsModule,
RouterLink,
RouterLinkActive,
RouterOutlet,
ShortcutDirective,
SidebarMenuDirective,
TitleComponent,
ToolbarComponent,
TooltipDirective,
TourHintDirective,
TourStepDirective,
TranslatePipe,
WatchingUsersComponent,
],
})
export class ContentPageComponent implements CanComponentDeactivate, OnInit {
private readonly subscriptions = new Subscriptions();

20
frontend/src/app/features/content/pages/content/editor/content-editor.component.ts

@ -5,13 +5,31 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { booleanAttribute, Component, EventEmitter, Input, Output } from '@angular/core';
import { AppLanguageDto, EditContentForm, FieldForm, FieldSection, RootFieldDto, SchemaDto, Version } from '@app/shared';
import { FormsModule } from '@angular/forms';
import { AppLanguageDto, CursorsComponent, CursorsDirective, EditContentForm, FieldForm, FieldSection, FormErrorComponent, ListViewComponent, MarkdownInlinePipe, RootFieldDto, SafeHtmlPipe, SchemaDto, TranslatePipe, Version } from '@app/shared';
import { ContentSectionComponent } from '../../../shared/forms/content-section.component';
@Component({
standalone: true,
selector: 'sqx-content-editor',
styleUrls: ['./content-editor.component.scss'],
templateUrl: './content-editor.component.html',
imports: [
AsyncPipe,
ContentSectionComponent,
CursorsComponent,
CursorsDirective,
FormErrorComponent,
FormsModule,
ListViewComponent,
MarkdownInlinePipe,
NgFor,
NgIf,
SafeHtmlPipe,
TranslatePipe,
],
})
export class ContentEditorComponent {
@Output()

13
frontend/src/app/features/content/pages/content/inspecting/content-inspection.component.ts

@ -5,17 +5,28 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgIf } from '@angular/common';
import { ChangeDetectorRef, Component, Input, OnDestroy } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BehaviorSubject, combineLatest, of } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators';
import { AppLanguageDto, ContentDto, ContentsService, ContentsState, ErrorDto, ToolbarService, TypedSimpleChanges } from '@app/shared';
import { AppLanguageDto, CodeEditorComponent, ContentDto, ContentsService, ContentsState, ErrorDto, FormErrorComponent, ToolbarService, TranslatePipe, TypedSimpleChanges } from '@app/shared';
type Mode = 'Content' | 'Data' | 'FlatData';
@Component({
standalone: true,
selector: 'sqx-content-inspection',
styleUrls: ['./content-inspection.component.scss'],
templateUrl: './content-inspection.component.html',
imports: [
AsyncPipe,
CodeEditorComponent,
FormErrorComponent,
FormsModule,
NgIf,
TranslatePipe,
],
})
export class ContentInspectionComponent implements OnDestroy {
private languageChanges$ = new BehaviorSubject<AppLanguageDto | null>(null);

18
frontend/src/app/features/content/pages/content/references/content-references.component.ts

@ -5,16 +5,30 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { AppLanguageDto, ComponentContentsState, ContentDto, QuerySynchronizer, Router2State, ToolbarService, TypedSimpleChanges } from '@app/shared';
import { AppLanguageDto, ComponentContentsState, ContentDto, ContentsColumnsPipe, ListViewComponent, PagerComponent, QuerySynchronizer, Router2State, ToolbarService, TranslatePipe, TypedSimpleChanges } from '@app/shared';
import { ReferenceItemComponent } from '../../../shared/references/reference-item.component';
@Component({
standalone: true,
selector: 'sqx-content-references',
styleUrls: ['./content-references.component.scss'],
templateUrl: './content-references.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
Router2State, ComponentContentsState,
ComponentContentsState,
Router2State,
],
imports: [
AsyncPipe,
ContentsColumnsPipe,
ListViewComponent,
NgFor,
NgIf,
PagerComponent,
ReferenceItemComponent,
TranslatePipe,
],
})
export class ContentReferencesComponent implements OnInit, OnDestroy {

12
frontend/src/app/features/content/pages/contents/contents-filters-page.component.ts

@ -5,14 +5,24 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AsyncPipe, NgIf } from '@angular/common';
import { Component } from '@angular/core';
import { map } from 'rxjs/operators';
import { ContentsState, defined, Queries, Query, SchemasState, UIState } from '@app/shared';
import { ContentsState, defined, LayoutComponent, Queries, Query, QueryListComponent, SavedQueriesComponent, SchemasState, TranslatePipe, UIState } from '@app/shared';
@Component({
standalone: true,
selector: 'sqx-contents-filters-page',
styleUrls: ['./contents-filters-page.component.scss'],
templateUrl: './contents-filters-page.component.html',
imports: [
AsyncPipe,
LayoutComponent,
NgIf,
QueryListComponent,
SavedQueriesComponent,
TranslatePipe,
],
})
export class ContentsFiltersPageComponent {
public schemaQueries =

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

Loading…
Cancel
Save