From 2958e73e82de1e8bdeab35f2643e511590734bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 27 Nov 2023 01:42:50 +0100 Subject: [PATCH 01/32] Add model, viewmodel and controller. --- .../Controllers/ContentController.cs | 36 +++++++++++++++++++ .../ViewModels/EditContentItemViewModel.cs | 14 ++++++++ Lombiq.JsonEditor/Views/Content/Edit.cshtml | 12 +++++++ 3 files changed, 62 insertions(+) create mode 100644 Lombiq.JsonEditor/Controllers/ContentController.cs create mode 100644 Lombiq.JsonEditor/ViewModels/EditContentItemViewModel.cs create mode 100644 Lombiq.JsonEditor/Views/Content/Edit.cshtml diff --git a/Lombiq.JsonEditor/Controllers/ContentController.cs b/Lombiq.JsonEditor/Controllers/ContentController.cs new file mode 100644 index 0000000..c2ca3a1 --- /dev/null +++ b/Lombiq.JsonEditor/Controllers/ContentController.cs @@ -0,0 +1,36 @@ +using Lombiq.JsonEditor.ViewModels; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using OrchardCore.Admin; +using OrchardCore.ContentManagement; +using OrchardCore.Contents; +using System.Threading.Tasks; + +namespace Lombiq.JsonEditor.Controllers; + +public class ContentController : Controller +{ + private readonly IContentManager _contentManager; + private readonly IAuthorizationService _authorizationService; + + public ContentController(IContentManager contentManager, IAuthorizationService authorizationService) + { + _contentManager = contentManager; + _authorizationService = authorizationService; + } + + [Admin] + [Route("/Contents/ContentItems/{contentItemId}/Edit/Json")] + public async Task Edit(string contentItemId) + { + if (string.IsNullOrWhiteSpace(contentItemId) || + await _contentManager.GetAsync(contentItemId, VersionOptions.Latest) is not { } contentItem || + !await _authorizationService.AuthorizeAsync(User, CommonPermissions.EditContent, contentItem)) + { + return NotFound(); + } + + return View(new EditContentItemViewModel(contentItem, JsonConvert.SerializeObject(contentItem))); + } +} diff --git a/Lombiq.JsonEditor/ViewModels/EditContentItemViewModel.cs b/Lombiq.JsonEditor/ViewModels/EditContentItemViewModel.cs new file mode 100644 index 0000000..433f611 --- /dev/null +++ b/Lombiq.JsonEditor/ViewModels/EditContentItemViewModel.cs @@ -0,0 +1,14 @@ +using OrchardCore.ContentManagement; + +namespace Lombiq.JsonEditor.ViewModels; + +public record EditContentItemViewModel( + string ContentItemId, + string DisplayText, + string Json) +{ + public EditContentItemViewModel(ContentItem contentItem, string json) + : this(contentItem.ContentItemId, contentItem.DisplayText, json) + { + } +} diff --git a/Lombiq.JsonEditor/Views/Content/Edit.cshtml b/Lombiq.JsonEditor/Views/Content/Edit.cshtml new file mode 100644 index 0000000..ce79bea --- /dev/null +++ b/Lombiq.JsonEditor/Views/Content/Edit.cshtml @@ -0,0 +1,12 @@ +@model Lombiq.JsonEditor.ViewModels.EditContentItemViewModel + +@{ +} + +
+ + +
From cb6f93afbc427c65bcf8f91743f0e2dd9a30f4c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 27 Nov 2023 01:57:02 +0100 Subject: [PATCH 02/32] Post action. --- .../Controllers/ContentController.cs | 40 +++++++++++++++++-- Lombiq.JsonEditor/Views/Content/Edit.cshtml | 21 +++++----- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/Lombiq.JsonEditor/Controllers/ContentController.cs b/Lombiq.JsonEditor/Controllers/ContentController.cs index c2ca3a1..30f332c 100644 --- a/Lombiq.JsonEditor/Controllers/ContentController.cs +++ b/Lombiq.JsonEditor/Controllers/ContentController.cs @@ -6,18 +6,24 @@ using OrchardCore.ContentManagement; using OrchardCore.Contents; using System.Threading.Tasks; +using YesSql; namespace Lombiq.JsonEditor.Controllers; public class ContentController : Controller { - private readonly IContentManager _contentManager; private readonly IAuthorizationService _authorizationService; + private readonly IContentManager _contentManager; + private readonly ISession _session; - public ContentController(IContentManager contentManager, IAuthorizationService authorizationService) + public ContentController( + IAuthorizationService authorizationService, + IContentManager contentManager, + ISession session) { - _contentManager = contentManager; _authorizationService = authorizationService; + _contentManager = contentManager; + _session = session; } [Admin] @@ -26,11 +32,37 @@ public async Task Edit(string contentItemId) { if (string.IsNullOrWhiteSpace(contentItemId) || await _contentManager.GetAsync(contentItemId, VersionOptions.Latest) is not { } contentItem || - !await _authorizationService.AuthorizeAsync(User, CommonPermissions.EditContent, contentItem)) + !await CanEditAsync(contentItem)) { return NotFound(); } return View(new EditContentItemViewModel(contentItem, JsonConvert.SerializeObject(contentItem))); } + + [Admin] + [ValidateAntiForgeryToken] + [HttpPost, ActionName(nameof(Edit))] + public async Task EditPost(string contentItemId, string json) + { + if (string.IsNullOrWhiteSpace(contentItemId) || + string.IsNullOrWhiteSpace(json) || + JsonConvert.DeserializeObject(json) is not { } contentItem) + { + return NotFound(); + } + + if (string.IsNullOrWhiteSpace(contentItem.ContentItemId)) contentItem.ContentItemId = contentItemId; + if (!await CanEditAsync(contentItem)) + { + return NotFound(); + } + + await _contentManager.PublishAsync(contentItem); + _session.Save(contentItem); + return RedirectToAction(nameof(Edit)); + } + + private Task CanEditAsync(ContentItem contentItem) => + _authorizationService.AuthorizeAsync(User, CommonPermissions.EditContent, contentItem); } diff --git a/Lombiq.JsonEditor/Views/Content/Edit.cshtml b/Lombiq.JsonEditor/Views/Content/Edit.cshtml index ce79bea..22d9462 100644 --- a/Lombiq.JsonEditor/Views/Content/Edit.cshtml +++ b/Lombiq.JsonEditor/Views/Content/Edit.cshtml @@ -1,12 +1,15 @@ @model Lombiq.JsonEditor.ViewModels.EditContentItemViewModel -@{ -} +
+ -
- - -
+
+ + +
+ + +
From 3d4bdfef1de1755addec72a7630968789e5bee53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 2 Dec 2023 20:53:27 +0100 Subject: [PATCH 03/32] Rename files and fix route mapping. --- ...ContentController.cs => AdminController.cs} | 18 +++++++++++------- Lombiq.JsonEditor/Startup.cs | 18 ++++++++++++++++++ .../Views/{Content => Admin}/Edit.cshtml | 3 ++- 3 files changed, 31 insertions(+), 8 deletions(-) rename Lombiq.JsonEditor/Controllers/{ContentController.cs => AdminController.cs} (82%) rename Lombiq.JsonEditor/Views/{Content => Admin}/Edit.cshtml (79%) diff --git a/Lombiq.JsonEditor/Controllers/ContentController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs similarity index 82% rename from Lombiq.JsonEditor/Controllers/ContentController.cs rename to Lombiq.JsonEditor/Controllers/AdminController.cs index 30f332c..c2b50ed 100644 --- a/Lombiq.JsonEditor/Controllers/ContentController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; -using OrchardCore.Admin; using OrchardCore.ContentManagement; using OrchardCore.Contents; using System.Threading.Tasks; @@ -10,13 +9,13 @@ namespace Lombiq.JsonEditor.Controllers; -public class ContentController : Controller +public class AdminController : Controller { private readonly IAuthorizationService _authorizationService; private readonly IContentManager _contentManager; private readonly ISession _session; - public ContentController( + public AdminController( IAuthorizationService authorizationService, IContentManager contentManager, ISession session) @@ -26,8 +25,6 @@ public ContentController( _session = session; } - [Admin] - [Route("/Contents/ContentItems/{contentItemId}/Edit/Json")] public async Task Edit(string contentItemId) { if (string.IsNullOrWhiteSpace(contentItemId) || @@ -40,7 +37,6 @@ await _contentManager.GetAsync(contentItemId, VersionOptions.Latest) is not { } return View(new EditContentItemViewModel(contentItem, JsonConvert.SerializeObject(contentItem))); } - [Admin] [ValidateAntiForgeryToken] [HttpPost, ActionName(nameof(Edit))] public async Task EditPost(string contentItemId, string json) @@ -58,9 +54,17 @@ public async Task EditPost(string contentItemId, string json) return NotFound(); } + if (await _contentManager.GetAsync(contentItem.ContentItemId, VersionOptions.Latest) is { } existing) + { + existing.Latest = false; + existing.Published = false; + _session.Save(existing); + contentItem.ContentItemVersionId = null; + } + await _contentManager.PublishAsync(contentItem); _session.Save(contentItem); - return RedirectToAction(nameof(Edit)); + return RedirectToAction(nameof(Edit), new { contentItemId }); } private Task CanEditAsync(ContentItem contentItem) => diff --git a/Lombiq.JsonEditor/Startup.cs b/Lombiq.JsonEditor/Startup.cs index ee9fe32..a192dac 100644 --- a/Lombiq.JsonEditor/Startup.cs +++ b/Lombiq.JsonEditor/Startup.cs @@ -1,19 +1,30 @@ +using Lombiq.JsonEditor.Constants; +using Lombiq.JsonEditor.Controllers; using Lombiq.JsonEditor.Drivers; using Lombiq.JsonEditor.Fields; using Lombiq.JsonEditor.Settings; using Lombiq.JsonEditor.TagHelpers; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using OrchardCore.Admin; using OrchardCore.ContentManagement; using OrchardCore.ContentManagement.Display.ContentDisplay; using OrchardCore.ContentTypes.Editors; using OrchardCore.Modules; +using OrchardCore.Mvc.Core.Utilities; using OrchardCore.ResourceManagement; +using System; namespace Lombiq.JsonEditor; public class Startup : StartupBase { + private readonly AdminOptions _adminOptions; + + public Startup(IOptions adminOptions) => _adminOptions = adminOptions.Value; + public override void ConfigureServices(IServiceCollection services) { services.AddTransient, ResourceManagementOptionsConfiguration>(); @@ -22,4 +33,11 @@ public override void ConfigureServices(IServiceCollection services) services.AddContentField().UseDisplayDriver(); services.AddScoped(); } + + public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) => + routes.MapAreaControllerRoute( + name: "EditContentItem", + areaName: FeatureIds.Area, + pattern: _adminOptions.AdminUrlPrefix + "/Contents/ContentItems/{contentItemId}/Edit/Json", + defaults: new { controller = typeof(AdminController).ControllerName(), action = nameof(AdminController.Edit) }); } diff --git a/Lombiq.JsonEditor/Views/Content/Edit.cshtml b/Lombiq.JsonEditor/Views/Admin/Edit.cshtml similarity index 79% rename from Lombiq.JsonEditor/Views/Content/Edit.cshtml rename to Lombiq.JsonEditor/Views/Admin/Edit.cshtml index 22d9462..88b802f 100644 --- a/Lombiq.JsonEditor/Views/Content/Edit.cshtml +++ b/Lombiq.JsonEditor/Views/Admin/Edit.cshtml @@ -1,6 +1,7 @@ @model Lombiq.JsonEditor.ViewModels.EditContentItemViewModel
+ @Html.AntiForgeryToken()
@@ -11,5 +12,5 @@ name="json">
- +
From 8eb3c6c7e758c4a7c31d3c26f44b1c6bda27581f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 3 Dec 2023 00:49:02 +0100 Subject: [PATCH 04/32] Add actions menu. --- Lombiq.JsonEditor/Controllers/AdminController.cs | 2 +- .../EditJsonActionsMenuContentDisplayDriver.cs | 14 ++++++++++++++ Lombiq.JsonEditor/Startup.cs | 2 ++ .../Views/Content_EditJsonActions.cshtml | 12 ++++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs create mode 100644 Lombiq.JsonEditor/Views/Content_EditJsonActions.cshtml diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index c2b50ed..95b6c43 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -25,7 +25,7 @@ public AdminController( _session = session; } - public async Task Edit(string contentItemId) + public async Task Edit(string contentItemId, string returnUrl) { if (string.IsNullOrWhiteSpace(contentItemId) || await _contentManager.GetAsync(contentItemId, VersionOptions.Latest) is not { } contentItem || diff --git a/Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs b/Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs new file mode 100644 index 0000000..bbbee21 --- /dev/null +++ b/Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs @@ -0,0 +1,14 @@ +using OrchardCore.ContentManagement; +using OrchardCore.ContentManagement.Display.ContentDisplay; +using OrchardCore.ContentManagement.Display.ViewModels; +using OrchardCore.DisplayManagement.ModelBinding; +using OrchardCore.DisplayManagement.Views; + +namespace Lombiq.JsonEditor.Drivers; + +public class EditJsonActionsMenuContentDisplayDriver : ContentDisplayDriver +{ + public override IDisplayResult Display(ContentItem model, IUpdateModel updater) => + Initialize("Content_EditJsonActions", a => a.ContentItem = model.ContentItem) + .Location("ActionsMenu:after"); +} diff --git a/Lombiq.JsonEditor/Startup.cs b/Lombiq.JsonEditor/Startup.cs index a192dac..6f58b88 100644 --- a/Lombiq.JsonEditor/Startup.cs +++ b/Lombiq.JsonEditor/Startup.cs @@ -32,6 +32,8 @@ public override void ConfigureServices(IServiceCollection services) services.AddContentField().UseDisplayDriver(); services.AddScoped(); + + services.AddScoped(); } public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) => diff --git a/Lombiq.JsonEditor/Views/Content_EditJsonActions.cshtml b/Lombiq.JsonEditor/Views/Content_EditJsonActions.cshtml new file mode 100644 index 0000000..b777248 --- /dev/null +++ b/Lombiq.JsonEditor/Views/Content_EditJsonActions.cshtml @@ -0,0 +1,12 @@ +@using OrchardCore.ContentManagement +@using OrchardCore +@using Lombiq.JsonEditor.Controllers + +@{ + var contentItem = (ContentItem)Model.ContentItem; + var url = Orchard.Action(controller => controller.Edit( + contentItem.ContentItemId, + FullRequestPath)); +} + +@T["Edit as JSON"] \ No newline at end of file From 2321bded093e786e98001827eaace8133a9534d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 3 Dec 2023 03:19:10 +0100 Subject: [PATCH 05/32] Add title part that works the same way as regular Edit. --- .../Controllers/AdminController.cs | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index 95b6c43..055b220 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -1,9 +1,14 @@ -using Lombiq.JsonEditor.ViewModels; +using Lombiq.HelpfulLibraries.OrchardCore.Contents; +using Lombiq.JsonEditor.ViewModels; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; using Newtonsoft.Json; using OrchardCore.ContentManagement; using OrchardCore.Contents; +using OrchardCore.DisplayManagement; +using OrchardCore.DisplayManagement.Layout; +using OrchardCore.Title.ViewModels; using System.Threading.Tasks; using YesSql; @@ -13,19 +18,28 @@ public class AdminController : Controller { private readonly IAuthorizationService _authorizationService; private readonly IContentManager _contentManager; + private readonly ILayoutAccessor _layoutAccessor; private readonly ISession _session; + private readonly IShapeFactory _shapeFactory; + private readonly IStringLocalizer T; public AdminController( IAuthorizationService authorizationService, IContentManager contentManager, - ISession session) + ILayoutAccessor layoutAccessor, + ISession session, + IShapeFactory shapeFactory, + IStringLocalizer stringLocalizer) { _authorizationService = authorizationService; _contentManager = contentManager; + _layoutAccessor = layoutAccessor; _session = session; + _shapeFactory = shapeFactory; + T = stringLocalizer; } - public async Task Edit(string contentItemId, string returnUrl) + public async Task Edit(string contentItemId) { if (string.IsNullOrWhiteSpace(contentItemId) || await _contentManager.GetAsync(contentItemId, VersionOptions.Latest) is not { } contentItem || @@ -34,6 +48,13 @@ await _contentManager.GetAsync(contentItemId, VersionOptions.Latest) is not { } return NotFound(); } + var titleShape = await _shapeFactory.CreateAsync("TitlePart", model => + { + model.Title = T["Edit {0} as JSON", contentItem.ContentType]; + model.ContentItem = contentItem; + }); + await _layoutAccessor.AddShapeToZoneAsync("Title", titleShape); + return View(new EditContentItemViewModel(contentItem, JsonConvert.SerializeObject(contentItem))); } From 4587141d689638dde4fc76f5d1972354a2bd4514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 3 Dec 2023 03:19:28 +0100 Subject: [PATCH 06/32] Handle returnUrl as expected. --- Lombiq.JsonEditor/Controllers/AdminController.cs | 16 ++++++++++++++-- Lombiq.JsonEditor/Views/Admin/Edit.cshtml | 14 +++++++++++--- .../Views/Content_EditJsonActions.cshtml | 11 ++++++----- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index 055b220..ec314a9 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -60,7 +60,11 @@ await _contentManager.GetAsync(contentItemId, VersionOptions.Latest) is not { } [ValidateAntiForgeryToken] [HttpPost, ActionName(nameof(Edit))] - public async Task EditPost(string contentItemId, string json) + public async Task EditPost( + string contentItemId, + string json, + string returnUrl, + [Bind(Prefix = "submit.Publish")] string submitPublish) { if (string.IsNullOrWhiteSpace(contentItemId) || string.IsNullOrWhiteSpace(json) || @@ -85,7 +89,15 @@ public async Task EditPost(string contentItemId, string json) await _contentManager.PublishAsync(contentItem); _session.Save(contentItem); - return RedirectToAction(nameof(Edit), new { contentItemId }); + + if (!string.IsNullOrEmpty(returnUrl) && + submitPublish != "submit.PublishAndContinue" && + Url.IsLocalUrl(returnUrl)) + { + return Redirect(returnUrl); + } + + return RedirectToAction(nameof(Edit), new { contentItemId, returnUrl }); } private Task CanEditAsync(ContentItem contentItem) => diff --git a/Lombiq.JsonEditor/Views/Admin/Edit.cshtml b/Lombiq.JsonEditor/Views/Admin/Edit.cshtml index 88b802f..e15e174 100644 --- a/Lombiq.JsonEditor/Views/Admin/Edit.cshtml +++ b/Lombiq.JsonEditor/Views/Admin/Edit.cshtml @@ -1,16 +1,24 @@ @model Lombiq.JsonEditor.ViewModels.EditContentItemViewModel +@{ + var returnUrl = Context.Request.Query["returnUrl"]; +}
@Html.AntiForgeryToken() + -
- +
- + + + @if (!string.IsNullOrWhiteSpace(returnUrl) && Url.IsLocalUrl(returnUrl)) + { + @T["Cancel"] + } diff --git a/Lombiq.JsonEditor/Views/Content_EditJsonActions.cshtml b/Lombiq.JsonEditor/Views/Content_EditJsonActions.cshtml index b777248..4a457dd 100644 --- a/Lombiq.JsonEditor/Views/Content_EditJsonActions.cshtml +++ b/Lombiq.JsonEditor/Views/Content_EditJsonActions.cshtml @@ -1,12 +1,13 @@ @using OrchardCore.ContentManagement -@using OrchardCore @using Lombiq.JsonEditor.Controllers @{ var contentItem = (ContentItem)Model.ContentItem; - var url = Orchard.Action(controller => controller.Edit( - contentItem.ContentItemId, - FullRequestPath)); } -@T["Edit as JSON"] \ No newline at end of file +@T["Edit as JSON"] \ No newline at end of file From 92631509520f73915fb90155564286a0fda04428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 3 Dec 2023 04:08:27 +0100 Subject: [PATCH 07/32] Add UI test to verify content editing. --- .../Extensions/TestCaseUITestContextExtensions.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs index 9a3bad5..3c3098b 100644 --- a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs +++ b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs @@ -15,6 +15,7 @@ public static class TestCaseUITestContextExtensions private const string WorldValue = "world"; private const string TestField = "testField"; private const string TestValue = "testValue"; + private const string TestAuthor = "Custom Test Author"; private static readonly By ObjectByXPath = By.XPath($"//div[@class='jsoneditor-readonly' and contains(text(),'object')]"); private static readonly By ObjectCountByXPath = By.XPath($"//div[@class='jsoneditor-value jsoneditor-object' and contains(text(),'{{2}}')]"); @@ -67,6 +68,15 @@ await context.ClickReliablyOnAsync( await context.SwitchToModeAsync("Preview"); context.TestCodeStyleMode(); + + // Test that content JSON editing works. + await context.GoToContentItemListAsync(); + await context.SelectFromBootstrapDropdownReliablyAsync(By.CssSelector(".dropdown-toggle.actions"), "Edit as JSON"); + context + .Get(By.XPath("//div[contains(@class, 'jsoneditor-field') and contains(., 'Author')]/../..//div[contains(@class, 'jsoneditor-value')]")) + .FillInWith(TestAuthor); + await context.ClickPublishAsync(); + context.Exists(By.CssSelector(".ta-badge[data-bs-original-title='Author']")); } private static void CheckValueInTreeMode(this UITestContext context, string arrayValue, bool exists = true) From efc865d86560f0f6825f6c3a13e794e680f4e1c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 3 Dec 2023 13:37:34 +0100 Subject: [PATCH 08/32] Don't use the first item. --- .../Extensions/TestCaseUITestContextExtensions.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs index 3c3098b..4fc50c9 100644 --- a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs +++ b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs @@ -71,7 +71,9 @@ await context.ClickReliablyOnAsync( // Test that content JSON editing works. await context.GoToContentItemListAsync(); - await context.SelectFromBootstrapDropdownReliablyAsync(By.CssSelector(".dropdown-toggle.actions"), "Edit as JSON"); + await context.SelectFromBootstrapDropdownReliablyAsync( + By.CssSelector(".list-group-item:nth-child(3) .dropdown-toggle.actions"), + "Edit as JSON"); context .Get(By.XPath("//div[contains(@class, 'jsoneditor-field') and contains(., 'Author')]/../..//div[contains(@class, 'jsoneditor-value')]")) .FillInWith(TestAuthor); From be3047c195fbb180ad9dd3be4016d0e6774c0d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 5 Dec 2023 04:11:20 +0100 Subject: [PATCH 09/32] Add documentation in readme. --- Docs/actions-menu.png | Bin 0 -> 27294 bytes Docs/content-editor.png | Bin 0 -> 67869 bytes Lombiq.JsonEditor/Lombiq.JsonEditor.csproj | 1 + Readme.md | 14 ++++++++++++++ 4 files changed, 15 insertions(+) create mode 100644 Docs/actions-menu.png create mode 100644 Docs/content-editor.png diff --git a/Docs/actions-menu.png b/Docs/actions-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..6d456debfcdd9e8419d28dfe4bbd4a4ec759930e GIT binary patch literal 27294 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

X2WZ7h%T;X8L=M>~5=5DnpV1*76jFFICo_qrw9 zEIOXeV7NttR5N1>I-Ky1n-vd?JRL?Znp%plAb{zpyN|CPMerw; z?R(7Md+ca~wE(^!r?OX0^)yp$S{r}pV>E$Dk|N~8%5&h_mj(>ZT`ZyxcQ3t^Qe3%4 zynCa9hlJW2UuHDnzwAn7-s|peGRr*lP+ExDI~W=YI*p61i7W$v7d!~5(?2-VuRz#( zXAEA7cIh*Gwf06en2<1Si|{>tMiEY;46Z#em5ycr+i$W%{CX!st9hPtf@G+Y=cl0F z$bpZhWEgx#(qSoOw~@rBH`Hsk{^oQL!%2^Cy{$HE5*mi%@<(ucu&;y9YE~>GN29r> z5N~>9_vapQYpPKBd3kI9<{Uvhq<|g5n2p1W6=>3RfF%me7j=Xa)?zoqVR*8|sH3GN zWo2d67QJO1y$EX%l?ryp~fKhbMA&AW4Q7q75fa9>5 zx|>mrHg>Z1lHF-JPa8|v2^s7YMs~wVuSIYGl>aCf;N7sM?(59ZbCvYB6tlbjguDs$A=7f!-xdp zIAi}2^8M$-mmmUwk~~eE|HhNd{(|uTvHY-*{&D|bzh=49SIxqlaQAOmatDve3*r91 zSwTknXFQs0WklqqjJf`ABrsPZ*2Uwe_WX9C-F-xhg7QzvB z&Aiit`=9<1wrE443kAr4eT8%XRk$P-9wLbQOlN`luenlb5kcI2;1TjaBgCK~YzX=g zl7jp{K^!_Fi0dS6{dZSD?;wIWDmJ=;e+?Ex`Xh*YDbM-uJRnKLn*=R9g!!NDj31oL zD1Fb5XKLIRJTATlQx{cK;Jkc={(yvJ;^uN+quj7A+XIbS6`Slq^Ym+grRab0qa+w_ z_F}jGs5{MFr@2r-!#d&IHi3^NCyQb{)sWIFyT-N8bz=ra?9E>dNJ3FkgevtLS>PU6 zwVyki++#8MppBZIC>z*+mL4OBqF>Upsv$1dCEP(|g}*7TaN?5pQW8x6E~4P?_KQQQ zI9IsqZH}R(7Qur~mW&s9!8~@ieU{OrgnR`QXU%>qxVzlUc%ou3{{cU9;u`378cH(O z^NrJX=^`?T+y82d=K>mUrBe;`+=0dI-Vt!rQC6Uui}ybLW=pCJQ>QGFF%TVu&_L!) z*^mAh7Nqz(^l$=eRkRB0LKHPE^zg{abyd_RIW}{}l__&!Pk+AFPVYSmAuKSQSj$XukD|M^R7sW5jdAK!_q~}S#hyaSM1HGW_B=>9OZ-`x#n*_uV|Vxp%$e@Px_BvonunQL&6%ulKp!l2 zQlDg@0T7NPOXPb}9L9qjAXe+M85iE8pK_n#rJDyd)i?3Ok04vOnNVllJ=+7IhzPPn z0tc~nyKlZH~vPX(|i^{V2r+ zMFn}2Iw+T_ptEO7_63UvkcZh==IXG?;t zr>teMh#RwcIR<9q)hiFa<-NU2#%B}b)HNsWKpX%TbfThk-T7n_@|ye;oLWGciqqujjwvZHRmwo#r3D3 zd@YubA#c%earjFcU19fE>a`TAn@tUEARccxRh*Yl%Dq~CBCO74!pqd zEiAXKuQ1unBfO`*)|0R7+gpnV6&7c`3mVxmlmzE~HA#WDRHa;q=lPzl#1(Ci`W~J7 z38auF!z?b=-RHgJm3vk{j+n;gitcCT-3HkCyxf-Et=|~RuM~93q4zn-=G71tPFsra z*6mO>B@WIYG(lT$a=gB0SZ_|x{n?^&dH!YJ2$9FLXcE(1HFs|L5AI6m$I0ROckAA7#w%z+I$9KlN z+b%d6d%(EzYCQm8_VPyY+hVb$g|f!&HL8ir?Dko}U7JzoUJ=jHq#iyO7ka~zR@db| zO1I?W7N=Xv`d9wncyehe`OkdPoLdHmBUr4id|a*$RF3U7DVzPX7dF&#%}t{72?wHi zaN9R_sReQc$D1|pE3-^QR$<2S6{m%Y)=LitV)q^sc< z_NcEhK{!aP!*a9`+k5tQRB#IHedGifUDF!a!Z1f6BQj*)obY6(^-CLw5qecFGI`BO^)a|gzlF1}?V52lZ35fsGV-v!z!J)IbEAh#J0 zO%`5H#i~9JPQQ9y_ZpgNl2F{@ZHsS1l_)UauUgqMofNc1LP9-}5MDSMJQ-bab?Bt1 zW50{plVCsR!y*Q(P_>~|J;bAhsDFxq@i{di4VMlarl^E%(JnM1?E}md=r>eEMDs$# zZ>Zm^?3EOv_7aapxrW=o-@3b9ry<=X3=0y5o$KXVk5X@9u=sqL6b4yTrob_IN7(IDr(rUy*i2Q$~0QolU`M@oO7G+Xdqup z>B}|dXn-qd<2FheysKp^YBzQB$`6PV+muc6n62G(y;vu+w(_FeNqR@dl*gSytc8Zu zSG|Q=E03G7UH9$FkuEPU>f%0Ll|hhqlgDYzcMCg^RG2OKoHZ(F1d4OC#~K3An0=zq z3S^EjNdWpdGfarp5~hPS{XgnI1Qjd`zOYa|YQAkw2iHb?g&pnOHow~(jy)=kCfXoy zj@4NQ^}XvY!nI`v9Wh7xZTi-ZaT|!UNP=p0s|`!OK<8)&KR&?L2KO$|jVFavF;4*J zbElKCU@?GnX7apz<3){2#3p#3&aotCsXt#6Gh)6o-Rkx2h_vC0vC%NH- zfc^22jmP#A2+!oa*}Q?L)Z_hNzo`BYNjw`b=Q`tZ|U6!PSG5+>AAv+q)ujK`1c#{k6Y# zEO455&SEuvUo2PKnNA(Z| zNd~I!16{{-{~mos1Sst&joIJ67wzCl}6nlW?(l>zff_)a`4^od9zk0c4Jd zAM$N|GRwFY8BV)SgXLkWf~{=H;)*JbMyk(TPi|fNQaK$$1%P_sW1YxbKkzJ4Ym`t% zP?jHNKOIDVAc%QrW!5E`kiaz3Q&Ux7EexYc;POOeJDUMJAL0q6~cW$RmI4> zb+vNm=IneO5S`{O`qXhr5zw^5I?^jCeFCxMw8G?v$jS!8Adh6&^Sx^N!Z}7b2Z{(Q*?zrGgdv zF+Ue)-1Ce}c9+n+!IPR^xm2rN`|DzPCj<&;Zx657AC#S0tV{wuhR*4ev_Y^d9ENKs z4~;@O11~X#Ako*Z#2a+nM-*DzR>)(0uK2@~ICq4`D27E3zilxZh6RJ`a<0|D=XhhK zgx|IfUBk9ArTS@`3w&p-i4X{>mR4oynMAYO!Z6?WEY?~A)eo0r>7ko7w2O=>*SCWa z>PFjmTej2^2GT%r?nvUQkE(sm90>&>vm{K9LBx+z(5P^)_VLk6PNG%>?6Ntyj5~=5;6<-s-6KJ zCD7~x0`B!4dP@R$Oq zP!c5c&O1+UN13agJyn(yGphcWcUN7O_@O`z_Q^ zBR5NzxtI_Jbkk)3_^lDs*Er%sQK%vZ#jGkfBX;BV8XCSg;C=P|C=h`KhRTHsEq|Tr zQA-x&ZQk1YY_6$f(x)um(#rlinckxxuHzZR;Y=c63Hh$O4RK={1a}14XLjGc6u_Ic}%bZ1>uD98ql(9f9 zu$pt-PsJaA!Z{(xTL*n+QgkfelYBwO+m@nerRe2sHOjgdn`b~ESei!SGTlf~L-*9q9c z08wblU=67)!{LJ{JorvuFfnxQ=?5Y|Pqak9bRN^_&I)*S2=lon_*h%Gs}gEZ@>ytC zN{r1xhG2K&BfDro2gh4Kf*1I?uW_?5I3fKZ)kb-vqE?+0wUKjE3Y_ni2&Am@mxS_U z5dGD5wwW`=J1%R?WV_|Sb}u}(VQ$rH>`SJdHT0b2f&p>NO&h4$Yw1Mif%l*WUr9`< zNHFLi;~BuDl~+V#G6jm;MQp;5{yx{9_0;>c;%))Vb!^g$w}a2S^dkVG%e;BBAx}J2 z<%?2da2F-xw+OUTcE{}YhY1Y9yK%~@ul?1{4$^Oq(vkWJ@YN%_RKS$G9}NNXLyk-j zZddGMry4^xF^Ds#x2dJ53tt}6655==Ri^Ozdl=vuC%vxC{T1q+4AWOZQVM*AmxhW$ z7@fyKpWns_(vXcge(jMlSU9;M^3y8GsM9KuOV!h5$d%u@gT`j~JQ?UW5aiVnv<~E8 ze%KOmRD+#ZgKa85nBxbZrdGTaZ~$4Rd1yw)+U*`-(nHFG8Aj`0D2&e?X62hO_f_-p zzwvK%+?Y2UXZI4Xp@*eX+XCe+eWB+g;wryyYeW+-I0W8mm6&twB zfS385?~;Hk@hT`dehPQti@?}aFPGqctA_`RhM@U-?Rc>T4vO#7r?DOij(h}t$3mkg zk}~Fm2z0kvY7oJN-f*cxQ^k&~ztVZw70-Qx= zwaYz80un1t!zm(|s?WnB5wBR{o#&Ax0<(03_GNF*zBP`H;KI}7RKd)&Uva69(8Xk< z$3HpAkD{Rs=LHN#=i`phqa52`DWN6%#ArT-MASfkma0?t21?&IC=0o;K; z+x3sEi8=B&wV;PId+ffq%JP#YA|le!)!pzxlkj2lba&@}`t+$5#8lzQM1h)^t#EGZ z^eS=xdMt2vZZR37x351ch8IO-F6mH6^bjS=!J2fJNp$Ocyd&0Ki{h!^w+!Cnbf2Bd z${N0?;t4R3`oagf)W(`Le2eRQZDx2Ttb=9+^711*+mA}f@AaqY?ba2UuM3YV45)w! z1sh4(JtiFu=d+h4f&GC*qK6G%l~nI=m86_!8lM=$%QA)M`X^Yto@-M`XFv`7lB!?M z2T3cxagT~?3hi%hFMQRR4O9+r*W(@mba#9Nug0!O>!j^diW57VCiz-LTUYxsimEKu z^>_9iW)+C7k-Be~2>C&nM#5)iP9H&qM=SGRu~LKrvP%1NAA;)8-390&J2S#6OJ~f( zAXg0Rsi=IFf|nl>Yx=7nvpAU8Cc%dCKrVxec)E=x&JMc{mf}s0Vv{Yr(1gc;${^=x;y? zWgStDT-T?q49Q+tYdn9@sv+KojYk>_Ta?7#VtVW1?h_IuR5U&=@3tr0DHdH4?nVHb z)Q?FZueSy2fL-5-xq#QIHJJyi{bZiuUq3hHfu*iXnw3zJrXObjDXUWl%TSGR&XeS_ zyIr%o6R_5-Kg%2`R?EIieV6rF{j-^cd@5*|GRkJX!EAu9CrUpaH~(V_av%OUo+je2AbopX5$ zF2@ziq^E5Pk|bxeu&L}SChgA})L3{~+DoR@52BK0vy$;Y;3GgKE`AUID3s<5clST5 z3VLzc1V9!IUmK8}d5ldydUMHN=k~M@@ZJKaLMv7^pK{@RlqLG@>85&JL75k=zv*I1 z-p3b3f`weqet;otG=yLg8lo(A1E^%}606mYfg%KbuGV@#V#XgBf?^Ew z515pcM6kx!pPsPcfYkA`4JZ*;DAfFAnRqdXS{9lQ55i@@+9>FtsCzVqcmCjJbOj_h zMU>I3VGQg8wCorU5xCR;(~2nfp}^A*>!yV;Bg-NLe{F;MhiKDK1Vjl6WT(x)bm!G7 zb;cMAYA)~XpgQ>;SmUz~R&}GcrOwADAFH{uR$NiRF}7!xb>8f;T3>N86UEQ%&yMMWs+Bh(!)vxzvn;hmLOf`;kmh z7!%8V^1l*L{$h~cQX+;?1;bbugks;ZN&zcpBk_E`a~U=wea~27rxePtI#FH!G+uvS z1amZOu)!xHA<{-vd+-XSE$+NnX=~NdGgG*}ILTjb^=4ogtFhGoa1cu|U5-^N z@?El*E#@#|4J-WX??NF-ae|`a;v6S_(g%lLht6Ovi0!54%GgEZR875y)1(^A-RE+* zo|@lo+1O{^nce~)hQP-LEF<@kp~98*$P8$|!EmDj0=@=elNXsZ*G-mK4KAi5$!BPz zAgXK>5=+~%A>w9A!PI^a6Ft8{6{qkRQ9vWb>)p?!ewCxq++w^sUr(MaGtdrkz2S5( zvumJ3;(xi)u5S3|~w3-G6N+!)7PnpAbXz-ZtB(@f=w#C2{lTjo8QhzUeP5g&=C;@JG znsYueFt-?m^uJQ&U=*RNZuV&5qX5v0cEtL4>+t?QBg)VbZDsc&K0~n)Y8@DS^`O|8gh91)4s zH8~ev@_bW%)b5xkvmz3!yCT}vo|>T2qB7&|8?0X3k{+bIK(Gv4Bdd?A@b_Ubd9pXn zoj$`ai|d`J1R6zMcs;QE7C^Fd@~P|3jsqj4?(v5TGeK_R$CV|)iClqfc|XLb{raBl zuU5Pf3y<)J!~p%}namk~E^k)wuRg2jvgx>P?&>B2srp;r=w`YJ4^NVdhZiu6vw0bI zhaOq$-S~OZ)=I}EnJZyoJXx8aPAjIj+4LP8E{1cTN6uv3S8n*N6`J2#`GD``xM6SD zaAsMu)J$S;jGn+}uXufNRB$Mtu{B`Rad^yko$Fmvj2%?v=<|5@V%z*Pi?A|UENo(P z84}+CfL(OjvWZ!5$nxe`jkBTGg40zCo|X1hzwpf|uhkg;5Z{(v+R$~oZIb5NfyeGN zpOSp(rQN0bWr`Mp+IEM__oo;EUl3^^|L|zXS3x%b;Gsp0rVp+ zu{O}u5V;uY9k@s8#g%$WMLVNf{L? zkfllD4c8iQ<+-L4reLUW0++Gm8iWCiHc5-=^RW1!!bd$%iiEs(){llJdsk^8qMmz? zh&Ps-+)Fz~Lk(+R$gQuhBc6J^`EX~Bt}d6$C=+4EVUL2H=x5;@YLv*CoQH!ORLFni zD3l%a^Un+Rdr>Y8etQ^R3Wz6d6l24$o!YSO*Rys#j@ROao!f)&s^zWpozC0xDQIkS zJrOb$%T#C>&VV}BW!TPKSk%mUZydV2)q~_~Qy5kSm%nTWlPMur#R3^d!bvy_e&ul{ z-a9O>AWF}2=DvMicoac zI^NIwq9@I;alk7t_|eN|eI@_jnUi8CXV98U2nxMU_I62NIx7hzauue7^RuSeag>w*YJY8(~>zRgtw zv%Q^JTdWpNGNMIX3wD|c-3N(@yUzLA?e{1@3^QCG9Yyljx+ESb;+{_$1_IDvBz7Pi zaY`fXc1Dzf#XDpn_yMzhhl`D_wxiinZTLV@(O+pGY((iKSsC@T*5TIt7!NMdMB`XZ z(d58J`9k8rx^j*{c^N#z31JkG*A4<(g@sOK#fhbv3f@J9(=1S7A&EZMm)1r28psGw z_yh7#mr?%SM+0}hmtY549M<=g$(YP^+=(#IKBVwe{A{!;{NC>@(CjWppX5c-qx%c> z8!e@{o6sDYqg%&YM>H|9Yt*JMS6Y*+uQ#^H*$j2*@T}E9oro+@1Tlx9^op(Be7PNo z$kw~hh)U_=C+*Jch%LT7kSD;==lbGGQ&b!UQv;k#G=^4B6dMcG(x7LYoEVUjJK7R1 zknGg~@3(ly-6&M;el90$hLsDuZfy@6ZT5l1HistEqaYRI@^NAJyI#<7Q>fzAZBIL8 zyORqL5VDTxSsKb$V#*+iwnm_VJ#cJe3HQ)LnS)eX|6U7R^nmLx7mB3>6G0v3zi1GJXc z=AD_&ZSSq?voHJKIv*uhwaU0&!D~ggf=C3%UJn+I3YmLE=H2KBtuo|AFYKWQE!>QY3SN99+VXYD>V^O#x;KT+?=)lhP=%M?k}*C=XFXXO>9 zch>92jhIM8a0(<{9HKw~Y}CTM2#kVh3eL(hE)+AJPynmtsK?kd%U7OjL^7QiU1b@H z^y#pFbg*5pWWI8FlZrC8o+?Ku!A~Ei`rfJW=0y$~2gGNq-^f2(>iMN7PCuepzN&Ir zgSFlvTZz$>AsNUqv_b6zkDo)11$qSJ!@uRrWrK9)nQpmT;f6+J1!nrMRJA$4($r5R zn&6OjTCJ+uQZ((4R{Ola2^La^`Bw64fAwzAXBY03 z)bd6-FgA0+VYqD%lCzAye?|Dm$I0_d+aWJ}c8Y5T4}a$_=W~N}BWKrC7r3m_aJ5un z`lqJaWFqymJQs)Zd)duR%^FNa_l=KFEcxgA$*yHnkrl7Z0)SmAgvsdZ!R)MJ11 zBeXyBBlI6OS`6HLW|QUT6@yPEo!1!bL|4y*SZMQQlWtt?kby7!_xuZevcE?ycrrLoM^E#aeWh?2$l8|J|AOH)=haXM$pJ5b8!>0YHi*7*z<9 z?mp<`{PkA{ypWV1N>X!i|GdPOXC4A?$pL#WnP~_rMU`6~`}u!x&doWyUA_1LH-9ES zHVbC(gD1XEXwaVaRSVI9J94g7BLMXrQ-@jo59~Mwn`q+s1-+MtHr!%8_6;WCfEUxd zI*so12b&+`ga)CLySRXgqdS{=`DvI_b~)eBf|q9=I=Gj zLh?7p%@vuf*kN-f;P^&JREBj{QL}NGSjr;~S$pB_Ivt8c_sR2g5=EY#YvM-PajW)c zVjO*J+t%Bhibn!OAe?JKDTbyE)7l|cT1ZdRcQMD84t52*DJ#mam={%^`FZSQRdJDo zAG*bL&FRPZ8%8vj?;;mN;QhZ;5yj~{6V*>R2- z9@;AZJpQeQxB~s>!Y1$Yy*smw!SQ1Tso&2c4I)OH;ktVb?rV>mtDrXI)CpqCyLgGC~F=ZSa6z~P(dB+Yzc5V09rjnM0h zV=-k>x%|khXr)h}$ThfqSZ!a*lD%D{VqAZIWsOuiA~&{84pjjtAm z&cnLfLxXr6W!YGmseFA$yj*yV%)$XT)~+^^C`_k(x^+TUKxt;%ijkl?#?N^}u~O2< z$wvcm>4=pFyVLHu%P|$;Q{Z4z3Po5r<(oOC5XSk$R+IS$op7GYGFADs zSs8r&cl~DIbL_VxeTVmX9p1u(!{fv}!ejUCTNF0bg2xISzT=`0ZW`6nZ|$5zDySc# zf|3g4zgNx)$L&rX>F zi)4exU2|ZDy>=XthE~X&>6=4MyNEw=m za%2sLinqKD9Pqw_JDPnzI0jf*B@JF%^?0a|hJnzDC*siAe?Ie}80~AZq~_lzkTkNy ze>nDLI_6nin?;PGQk@SrYd4r?BSQpzDR;Hpb4P@=+iCl5E|gW)QbcwQk|zYp@(CLU zP9_8Zjt`-rk+S?;!31;F1GubcYk9PLXS^vsbj#P$Sh`42i>Kt=o2W(n7>B{$+>;<-l7Wy zkz}wpRn116?ou(TcYK;=U8-AN)Y4+Cl#xgl(V&?ic)v*uoQ^wpS@Uj_A*mt*wke-- z%Z`fAULR}|lAx?YKGq1ke+nq!xGfG!H;-{6ucUfYW~&URa7-hueEL!tl15{u3`r5Z zOD5dsu#qZ=CQl2orgF6t8@CsAN<$u~p3$d;klvPMckbYYBm#hFQcd3Oq8sYVD@hIu zN>~NGS61RS`NvdguD00j9;6PjCwYg{>_+;_!;$CR6zu|l50MjOs7fZz)PmEK1M)re z>ZK#S1dreu0J+YxYwTuWC#=j4jRm|X0!IWFwj@^pjQ!d?I_e8t-Z4{E;5w-;jZt(a z2-x!2v{;88auS?31oA+|z9w>f7_kPUU+G2o>lT3q13jg(S*9fhxi>sc#FJp-v(FB4 zliE4a`yPH8&8L$r2}5r93zluZU8#{3HCh;yqh+C1*$0_s1)zdy>e+%G&g1#I!q#8YJvRd?;iVQ z9{Z;AZI*gCvRSPGRYh8ez_U0n$HTGa`#|!!+(#2>0pvQ`&)p4V)00g?j)2vMcQLRp zrN|(hk^B6sN(cg+>u&z$yfeDlR>Q$zL>Q;gRJ~v(%cM4ps0h@ zXEwi3&A}ri5KbG1a}o@#xkbG)f?Y%P#qcCP-%;B-pTz*oZ3V;66`KZf+Z2oAyDe+z zyN}l{f8ZB^D9E;K5qkKG21x=CU*A@$aP;%0XVQ)?J)j#!h~G?GZzl08N}em<9)i;Cwu=~`%;j2?PkYA&c4C2+*{WgG z`5aD=Je8wtzRS~fzKIMu9u|jWAV`>McmuEO*lKrUUYcaYtM?befC`(Rj7pr)NL1>@ zo;29cD7-4-(-i9C*8VlWG2{2ep}C{a!5eQ?X-4-3ZPPv@$jw#@{=8+cLnQ`eW}@c& zD;O`y4`iyMk>O5O-V~Sb`0)1j+ZLcz=7F6SxOKob-ZRhPECF(is|=31jgD5$S&5=X z0Ktjl?wVgfa9s8^#!*K%HKAw_A*b*ij}8BJ@z(s+Av>-B@Qs%57j!rI_0$^U^s-QqDs_;w{b7sm;lIw9YzN+U4P2(nuqiUm(%&k5mOg? zkT3Ix{%STL0H1tdR61Z=VRp>2r~hFby7NZ|T8GZ+3|$#oco~m%LO;hZ)VW8NARrLlmGDzz`2y+W{Vw0oNEl5qaksgwq(AT8vcd35NpG1!)Hso|1|UJb(KWD*`R8 zMj8Lg&ibSKXGR~zu-GWtcqp{^p{#L5tHy$2l4Y(Exa+IOd{Ax6zE9w_BoqJw(B8*;BPsF|&$^2A<*NOWD_|6{9tO9yB6MV`pFK&9{+^1Z(f!e;d$;Eq zJm{xLuwfTVy5E>GhWbaNgmsufSLDyeUWX|zs^7yhy+md~*8UR0b*EUW zT=7lD5r*Qem<|pOiVZ$MoK>$UU^&?pMSk$#iR?S^ua%E*KS2ul5)>p9R3!zzhgjv+8lBPIlS*QzaXyk-$nj}xK(6a z_u>VAS;Q%cobWfD47j2}bUf$Q9-?#n)#z9nbnpjJ2M{9oHL}9Ht-{csiY>$t|Bv&d zD6Vc zMQ-ueg^Rk_Yky=zW<qT#T?JA zri%Sh=0wklQ5x*`_R9o6?cQSIr~2n~zf1g&`)@d+|6rYumv+#)+SAnVoq@CzP@HBp2KmZ zSxpD=&bz>B8X!>G7_%mBCB9wBAS-nx8tdHIkO?j0ZS?zqmAo}7q{;mjMLxPU1DtxH z4}gLYXIi>C7JqnD(xW9$Sy?za3lV=AfjLnj#|E9Nc%jXbBu^MQ;}1v1?kWC^Ix~6J zT_rHB!!h=8h1%IzqMqBz1f0)fPhB2UM;obws7!F?6n=TsfCJA^xfBrial-y!wfpzc zE}bqvZSR(EwJ(9ct3$HHeQ8EX7u{EOufQJr7+yeyd%ekaCW!(pId{|etL~FWXq(Bs zRLeA9H2jn%q9@z)$Z>mvA6z!bJ2%1fJh!&LI_cYsVRIZNNSiHPW6hgD;b{br{b)5< zWQF*%4{g4elB`v>6Abfp4&=u>bG({j4{$V|4c6_3hy?e-iux-Ejq2^q&xIcr;yg6g z20pAO{!jqltiC=~Se#~GhL=UEM~h2HW@S2vI?H(LWU7oDmaq;@h`bsXIHG_J+M?18 zH`b_8T1G|!Aeo}c_pr&JPsAC%-QKL7eAIV|6Z~ zLje#UfAQAS&%sft_eJ&|B6uiw@er5Owlw;9b&Yvr8$Zm%SCSEzf5dm`W(mjJ`vS}D z^LcJj_nqMhUj3R9S(=@+k3ABmC#3AgHSgLcrmKx+INZiD-WEHrJ<0c5Ge(C~z@|RW z!eb>r_aFYk)Vv~>S5T3Qo%`J(<;=yxoC~y`HD)?TBR-5<{t5F`iy~v4My?ysfC#oA7NMJNnv?P?!yTd|FfvLh# zAJ&#~=F1Hu^rnJW?^sK9$FQocjqVvQSKW=gSuYyWpj)M)h2YnTPmLaN8Sdu@Cr@j_DApf5pEVEs zR382RuKf@5J-)Z(ayVEcRW@lZw8VR$^8&^{o3Akz7|os+1=PqB)o`+7XKvFwY4Q=S zy1iN31CH)*M--_J-tTIbCkzWH_`OtB?# z%+&wexvXU^z{>8y!d&~~XpoH9aZ@{ZM5H~$G|jYfiPe5OSy3#y<`6&Yl^^%;!x)No zU%<(!l@D74pjvUc(3&q=>4<&CY}dwn$w$q0bJ0TN#rIRbwKHFQ`~5{*x-z^z>%RZ5 zg8I!~7CCkRD^p{q?bw`tC8sZi=OHALl~E2E-vv~OXU=` zl5QiS_`cgi-whXT|Cn4CeP^3K>~QxjN8VQxG=}i+eZ0xQoBbl_993c+u6nIRmf3(* z7YCDGQsuskxTLDAgW+S`{LZ?>Nu5${W4E@S_8(+O!1h6KyA!JY5^Vw9^v2pOk_nl zh~Ce?|6n&}FJ@?;>}h1w^en!d(xKGA&oTZ>mwHW&Ro^R5BYz7~N8j_^9j;iICcGt| zJ2GQ2BnduXO@{XWw07RnaCUFM*OCxKbkT<(1R+|6LG<1wdKbM$9W?|YQKPrfdpBB; zNJNWZ^j@B5qehJ0&n?CCJiqh4XT9gF^Vh63W35^DcJF;(`?EjScajNh*3=|Qq&Gmc zRm&IoOD7tMcfJ(tGcin@BFolS3OU-Pn@nv&^@K%kRnd+NKy?K-TrxtyE|P9IERWBcm$nqtzJ{{JShBL-_V&r)U1J@w3rm zG12>f<7c$r_!)bz8NkoelgX=U43-@60DjIUAh&xM^7(-_Q-;{{6CY2f0LaPNNb3px zZH|DzSj$(ue^vyOcVmHPm7n~9(jR_9=`2;CWU#|0WEN9D=qHc8CbooON4)+M-FL;= zqz38D#*PSe#d#Jr>jfVLEs3wm9S|9u%YpEj3U7!1)5(7bb~|A#mx_Ld>IqKnNi%!J zg^TiW-;=c59bw*TrjZC#5?fFQNf(`P4!9J(bD|)+6!H_kx;#9Xh#A@-c1wIydn2|j zIF!S3CgF8Xb)=yG4u)Ju!yH4RY~zMULFcvPXESzZ*TSZ+pOs4PvuFUM+OM{U10dCt zKYSf3?QNGkQ|A-`Qmta_=eLmo=OPb_=>=orUz3}949rg4K+ZU9l1NA@w8vC8I4wHa zkv(7Xo+K<8a&c}P5OHHE)x5)4QdV)#FaBy&1wr_rV28y_Sv>QSjF(2dEsL1zT_j^IT2F~a<`3wyxE#i zznL3j>GwHP(r%N%_m0wtCjVv!@6E>V)` z3dYSuV-*H&OrJtr5nb_kT|mjF>7Pei8#}nSi51WPov#0d3svXR1c_}7Pt&*2=UI1S zEfc*ATf}ZMq3n90 zmhdcSztv4g7bewxOUIb+`LsU^UK7wP?gq^BPA9-TC&c<~o30Fu=Pv>{D%5k4AQt+Rgm_Q|0P2q z^`KA!2Y7~q$s)-*5+d|GHkQTc?jXOpHE}i7nRbo$!vQ>Dg2@d%;)OOGYIH{mMakeN zgZwqLP_{77x34^T?s{i%E)j%;8Nw85@9lJVo9Dg~j(Z{HIlQQ&*gGzj;EK&{Z?>~-z{9+HlkC^bR3Dy zg(F23{NLaErHHpXEKmJQ5jX!^5s&?cBF>O;sfcS|D&i&jhefA0b&@b&6%Im*LmJ#J zmkcSx`7bogu_kRhHcQr2rwo!Qb)Yai-3Nx#6knG=J)jQf#d0o#OpzQ!2rJ#2U$`-W z=}5*YKMe*M%y#7E@w)d|O~tr@kRe_0Zkc;(*HP5i zoY3rq*}LEUw#U1x61^#W8q#cEEtz3AN^4F1joj1Hk{0X|*J@j3ORaCqLC4-y4oAnX zTW6jX3d((`Ub-hF1+_4E^t>tgXayi))@ahCJT0-f8KslOG^~xC0BKS5ZNhA&+eef)N!t&ajtgUfdf64?Y)2+=$ zp-_+KONX79ahLwW)H+?9*xa6`E;*6AmW-9r_n~JPUtM9sm=T8!Yn9qpq?RVGWJ>2F zL+o$)+90aBjjR4uoc) z!_45EdUO~348-aJGZ2&T52m_4)FQv~35e=Ts}eTJwcBloKvJ*_NoXoJbd7V73LZNk z4{E$s%EZ64-fwk$u9yZcdy)B@9OvDoxc_2@6{Ea}N3rbMPqjI)Gv&H8=M*x4>We8r zjT7(*0a`rYO6oZljl^?6o6zZh$J~df1#EU}d^Pps|OKiROUP65V z)D3^n#9ZrGv`d)xLwe$=jTI_u|2JHK6tvPFchW8*_t*w$6E}~?@#3$e(3zL75+pK} zjW1O|d%uPP2dVoAeNL5Jft=8OaoC7}*sX-q1xAgP`h`v)#>8op%ltR%>w;n^W)}Es z(mleUDT3e*7?TC~ReoLH?bfE{i<7jc88Cag{G7wazFAt|1Ha6(S8*!S{7j-CGBfLd zL#1>A8$m+o-D0~M1}_ISqIkXBrZIPu$(S;_2f224!xMY>n{Zb_roNJ}oYPF8mqZ;n z@c|*!#HIAENZTqC0IZ4``!)EL`GKCTaFJ7DP|LUqEP2>Q+}lgNVXt{99wIaGRMd4c zWB~Eryw?}TvnMC$TAGY&e@6)LG0t8lXaLQ`I^?ob*z&YXSCXkX0OBa}I`*ipwPcnM z2@G+s`ouNfb@;IpV=3cXrll|`U4v>~ldqtnoC&%UW106pQ(N|uVUe0-Q(Qqwi%ZqB zTXu9@ot4O~&#$N1xnBlG6SL;p4Ns`s&fFZnwG$VOAi@+F0tdOqWF=f^Wd%Mo{Doq@ z|BYfh{69*w1u>?SAPHk*G}wt%xc6GsM>huee8s&*^J?%@AwtK}ixd968V|BAmD3^@ zY22$(A|wJXX70N=E4dewTJa{h*W$ag0QW;*a9Kkn5;Elyo1-`dECtaS_L2ila=CQY zR3E%SkAD)tTWBOwSA#OB08!bN8U4VrJU2oEx8RsY6S;_f<(h_as+lJFME>$9;{G2qZq?zw}$D+xNW`gKsI2 z?_EvCL2=+q1_!w*IXr(Vy!t<;u+V;gN)HSU7PBE^hZQRL7nS~MV@CT2#<5&}sFMHM zx-I+{m9`t;qW(jrT`#HhN;ucW?k|gZw^F9$YJfnO7IRm=8vZ3NzqFY7|2rxTfM+B= zw<6XbL`=VqHCn18ArWHL+4b!j#vSG#-H(Zvzg!cUb{|&Tn_~>&Xfq&NEO)xSt66}CW zD)uqzv9mMxrq*O=%Enw0J;JZ%hsf1kxPi?TU_F;b?0aFRw9yfQzVsk~`GhZDiE2y( zm{7tjVO=ig-_d)nQ0-f^SY$%k6vKR4Y)N9%OAEpkd%X{V5af3v)5YH-rkaG61gW(B;(y@&HxL=kELg*NvyIy(kw=d7txE^YTh61k)(N@>52FwLmN|VhZV5Pw8IN{a5 z01Whnq`4?02w(zPOTFr*f6f|I9|37o#3WAhR}3y_`5V8i^{OmeG7^#?gP z6}oeeya&Y_w=Lhcu4iP;P0PBTz;lWgS{w^4l3C0Fm$5=|FX+@~?nS36q zA3AyI$de0;fVMH#Y=qxnM>Tz09dPYqyxY}6KVDZI_vKyiBz|()ZhecWObd-@A6x@s z!UeYtoc%p}GU>5v-qmpe(O@qFAgM8y29a7r?fX_$x4Li7dnms(qF%u`+n2M5o4nVw z{PqTK&N(p+gY+;6k$_m(6PS_yDLeP=I~Gm8_g?+|*exdTAcSqQn%7=rGh|CMG1Xl7 zoCO>Np-ImmYM6>M!g1&nk^h$CPm>Y0kpRSwPdVL*gsH)^*iu{7oI+fjm=tu{p5df04R;v0*vdY^DVro&Brmj(Mzuj6;p@!QJ4a zj*NF_EtAtIYop^{rvIIO!@U9e{lo5W`u$utvt{Z?cypHchcZq}JjY)L3RLm^=)Ec0 z*e{&ds4)K2L!+cI+o4k_OH-aeZ430^>IeogA;Aq0y)l)-Eh$N{ zg)Tw3t>sO+01vk&4A;1U2h?@pdbrve>#w^!+w6N$_K>Os%Vf`dHbfXUw~-u3oxK^E zvj3?BDpxh3Ozv(fYgyrAHJSvmf0@DCYOdc>`N*77dFr-^5Af|khRtB6*h?SAzI`KV zAn3?(7D5T}bm}`-o@jRS?u!&I$8;R(WQYbY>|Glu@SsMlwbac^OANEuk5oNg3!YR`kw#rO0A0oD$B%)E?>5(O8 z>!e9zl&$^6MH}5>epBn<=!+Y#d+kF+P$_M#Ac2`v1p1>hJspCH=d)maDJGftuO*|TN=NoPsBECon_QUIjVR4G!JTj2g&CQ;_;dVw3YtqQU-172Fc z<$epkiO#AkXL+$kAXf#*Kyx?O!2`)NtlMk&DC(Dcsxe61K03K{Ats51p8-CKe*%0? zbspneO)fI;o^ZSktDTi_;VjGQ<4$sEe0BRNw)w(j)ya)U`&nMf(LHU!tkXjCn2Om< zJqN#J{?K?qyaI1#nEYllbHgrcAZ&q7>|5?!> zG^88LBrOZX)QD4^KR*u3IC}V`{J52SdV7IWd6g=-zqDCI3q4aDNJ)O3X+^yV4z$h; z!(c>%@{{XFNh1`>{f;s(!Tov|-TrV?5t29}U72?;%T!RSLRY1J)=-7OL7^_^?JXi{ zm=5oj22Xb9-Q$GyT1mDk+y5Z&*J^7&-wdE3`acTY1^+KXH{OX(0L0SR`DP~W6DX~V z9JjD*18G;y4Xysw91VYn2fA?w2_s<9@aK6fnrGihOsW3u?9TK($iIV$H{bZkkxX5< z=b>%wK?7v{Sz)Mq(k2%u!@0(v(~Sa3c7XoohZw9ZBfL_azK-FFwd)FfivdC}ierqQ zK!*Hyz2%G5jb;>`?#l+fK{Yt}mvhmsA-)T7Wb47#8fm}cyPMb(zX_0nR z-V#LEH{@A^9mfM%ZhOY5(%V4z)h#@RKaTeHrK8=`NAZ3M6NpiCC5^yFDG2wjz*)O3 zCmh*MD4A<)~^RgtL7wfA`@?b%1JkA`+r z?D97PjC8j)FOq7JG{hg+BzLu+(34CH4 z#dwe~X{u67IAmwH#iTBFC~(t4BQm4uhll0HLw*sr%jlcbsYkwk-aBCmPh&&BZ%(UT0=08iG3T4W4Wo%m7*4RWE?W^37DgiX#UL% z_kfm1G=`L}M?m?80Tqr&Q&n-qQHYkEF@AfRuy|eK4aAnT*B{ROwut)NwwMj!Rovk( zs=_i`?iCB1vrN-o|vcdWXBT22Y7BlhYc{Kw}JD524GgiWYrU5=qyf zB28Y|JjXV%9``WG7%8{?4L>5ZMk!h+Rm;wL-f-*Clo5losPj2ILZp{Fnsril0<60TUD1D-(?6S$ zN-zG={Ahmc`_Q%HMs(!hM)#3dB%Jq&#KtHslxz;3MAz*YRG3)}U`J(78N8D&ZJ@iOe;CClBot7KM6X zFPs1~X*r5gsMe2;kmbjL4ms!nhc;t2;E?q(mPqNV=ZufHQMq_lK4dTCJV5w+aycWrTnsh2eH2zaGBL<$XbE~H3I{K^Ovo7 zW)hhV4M~0{O;nd*a+ZKTFS^gK;-GhL7zQ)IQW@X{v!j#2F0FlvB97q7?f1U z{rtgu5s5c9cbZKVygENfiqvS?gedkwX=@Fm3hWCdVYNAB#NcttCnK>EfwHu7wO7o% z>ATN=WpA&%z7w+-gA_Vp=ZhL3R?gNwzW#pjyXIQcc&HDhsZyBPw^pCLO}B64y8k6F zcd`AOmplA}muvomm$w4E+%n)YRVBMEop>-xW?@jBQplpl$=sj|3PMp+naGYkR*y$k zup|y0h>qskdRmcVQDdcSi~%^dfteQ7?1EI*ZT94TqNxdVR3~p49QTa;2SYbBMM|ib z9`z}vAat3hxvaV?mna$&Qe-pqRWR)Z@LqQgGYvg%ew9xIrFH8|KE?;{k7?;rb`D!3 ziqq>r(;q$J+~Kq8o6Mrz?SXHs|Dw8XaCdx9T7r8|qCqU?z)^0>7!fR&8+Ug|?vzX@ z9CA{-8nnfr?P);6*fq7S3i7$wDroV?b8qo+gnFsS1=4 zXa}`P!Imt+xCQq2a_?#*S&CAV8cw{s1@OG`Nwhui&3ZK0*=e~u;HU?3B2%!nXo%fB#-&SOgqOK^-# zw6o(YL*Gd}e>U@pK>mQ)gQ!;!o)E=Moj9-c(K2~iy;_^RS8Xpt;p(4@gEs{5ibvt` zJF%Aaoh~~T%_{e?!G+pScr*lLIi9`57cRWrX4A+`t9ZNI0g3iz=>CWwVY+vp6C!8L zG@P+S0{5-M0$)G9ZTBP&2SO(=Zx;LLF*mRJiXkO2zp^EB4AO6lj20$}F8 zw5OS>lZ8RItVY|^iNZ-g9+9Ar4d2Nh4czpxikJChri2Mf+}wVQpXp?bQH)}@O%lEv zoM->nHH>7Sq?G+}-Bjd)}syJLtHyfy}U7d$CYzCtq`r>qwQHOPPlK z@*u#0!&s^8{(J|c!+L__LI4Cg^_r~1haSPLW69hvdfJe>KRha)Em4Yd^7(J?x*5!M z3n20@dER*AmlkQHoB{h}UnLi)4?yG%-?!2TExvOaqvg5rD_kI;lt}gotmSyfWVnX6 zMB_2?nslbcfdi#?`^YxfKIh}1S>qp+)Q_begFN0USuHo>&B7_5*6zpSKI$RzTu2A= zF0^Z{7Ij{%OjLFAD69L)%3Bmf{-GIYSk+4Oq|kg{KFxZdwdVZv09NA%3#yT^@A6aA3DcT( z)+Y_3O0s%Js}6@}7rtGC?dbczyJ8J&r%%-`u=lU8;6kq%;P~QV_y*DgjfRSihmX2{F14EW{UWn z4=6h?yI5kJMP2lL4a4Sr^XOteRO}Y;o(aZ#r|r&T9+n>{=8rJYK!u8c#)1C}GGVg{ literal 0 HcmV?d00001 diff --git a/Docs/content-editor.png b/Docs/content-editor.png new file mode 100644 index 0000000000000000000000000000000000000000..77cd5f1d07761fb33517ceb9732bfecde4d53bdc GIT binary patch literal 67869 zcmZr%cU)6h(^fq3t#a`^^Z8S!PF++}Rnk9o>J0YO zDUu)L=YU^mUHhF*o#Hs9rgYQL6SAB}hBW**5;*_-;_I_#FJ#i4)@!C@Shk2)xDsmd zkWuN3d+1$iI^|?NQXZuT%`{~llk}uz%6cFc{!H-4bC2m(QB%$LZ06IOY(4r{oA{!l z#wQBaQZ;=0w|i5QGXi!kOR&#Rk#d|mO|E!~ggWr=KQsAB?Wot^J%OgrkphW*6@1p$DUU%bwk!8i9%tPF4Ph8pU|18#*VulC18E^=( z*gQQQ%T#VEVsk%P8z3ZhZ%CD_*V-#ez;OZp&mr^V7=m$6CCwAVs* z%8IXGo=j{dTieMs-_$ih9K3!owP885^ZK=h-G2FOcWNDY8ngEKw!^@)U{wdbKYtnO z-@1r)Cy7z^^;&Tm+D^NUn|PTa?OJ4Mpp*8cG-VGpXR(pI)V(=rbXx(bLUbB&(w2!$ z_rIOzO2W0|1y#5HX*2&E0>u~#C5Ck}l&|uH^t7Js8yX6Q#+A3YA6o7@kG_JZgQrk3 zEKCYl2UnAry5|3TdJNA8se+pp0u&0^3|b$2Jm zu3>L9$t1>s?y@qGxMox6ZZB!#;=SjPA^$zKMF?YuH_?-H$Mt@9NN}mSPW79Nnakv< zK3{U|=8A=Juj`GvwT9j=l?v_n&h_pa6E_Lhr=mW(L$xu~#&#UYonw;}dvUc1>|q~G4Q zWxmE_lhI23Isup38>L%rd$5dg>b&$#qQ9UfV32jl%k0v<{M|vJ-)$$z5VZ4T{Q-1} zNLBcR-8Wx8WB27~rrJYjw^r)~kvoe^xq1)L@dZwEl}_r_aG1tRvbDx6j_HAjuooE~ zY6?}J>#?c+!{!xlncY`Dlqalz1~*-;kUT(RN)4v2TFjifQP6u6m)}FNxEE{iIm&le z9lu$AWUUZ~#yc=q>G_R%n6OCPMf8!Vr9lu;&$8)l&B;W$R1>+=h8RsH? z*BUDBh-vB*_>BCQdomOWnz!^F2|cu1r=vjaJeMzob~Bt!B3KgnFTyKrQsrQLGXc@Pl|2+MFU; zG&(w}`Dv$+NvcUn+|;2Yb=~5}9RED)cf!zPd=)y-LE9*$WJqkj=%^>X*l}+mXxVmy zI3=@fjIt&r&ZuE7OQl(qhRa;aBaA*vxmA(r_nTpyBH`iUpz(jmD_w$kb&)0iw28Ut zH43;tr2>vLuU_U*%9Lo;D=s(hsE$;v(>4;Wgt2>UFxhp*=9Es`#j0tc@a57`er3#`Vnsqo$%-VtNS znYSXp8IEoYsgj#dsw+hMJV7r&+MJa*|Co)AK9a#%;cYdK?+5w-1HROO*2ThjFr+h{ zn13lNkxCGY(X7{K+~^HRBSbkhqNGcQ4xxK{i#0j8O3b;UwE%^lUZMB1b%N+`D?ZW) z*?sTD=b9r+3E2%Pm>9urV?HPM_uhmeNl}DoJ!ZF%D4kRZW9^R=s}VQ{DVp?KBPuD$ zc2$G5p11tw$P|sJk@Hhj`97liExq;&&@&g9FT$IiD~)Tl@VKxU3Ku9B?V-0^r;uu0 z&2}ypv1;^FgIq@D1HCGJo}bmMJnH+VaF45|?u-?6Z7S#zk7P= zd%P#Nt?+Hcc;U1j_3u71km_{JnJ?#A9TLCqP*lNRXvi@k%n9-v*^Kun?Pa#H8cW%E z8l#6ldO}y6b+l`0PQRI+^y!~KTlPzfE*8vGGD?H}=VspG!aoYkxPNxfD{{ctFG;_j zO^V-a9&9gy`$wgZy&ScH7!bC#1B{~jlZqWvn_L2rLVWvePPlQEUdTp?^urBml?9g4 z@>j|4=Z)Up^bAFg%@5?KD((yj(54x>Z{BLV&6UUTn?XdAofd4fq0OOlR-ovAm|RvE z_Bx7)u}Jb;tVrDxG^^CjJLEaSz-zlauXCHvQERjzCJACy#Jv?+&QDJkFt!7qpjYbe z>1ykv9N^(YDHm*!VV+A>62@FI8AVOzB08Z59U$k$er>*^`+F+50fBSUx4%eMMh1pI zLmhn&8F`HKYmG~0TiSl9{`=;uk~o6>yUsQaX{WjWF>T@|V&yKd2sSwqOyD$ZtK@m63 zwCYHh$}JtM-~0dv_;d|=tE77L*2M#x1Bg^qzsAF-ce9H`NjehE8QA6+kM||+_V3hh*;Fy(z zKEe!hX6SN&l)NTw*{3!sH$m;R8^J6xtJ?Ulv^u-Sf?^prrYQ~hDq$%I-nha3kk!uu zn)_1Lb4MGE$=jRYAx}KiXj*(&uQB%z6VZS@>ActEHL&ffB!f}B{AR1fh(+2+saf{n z^6e3&rU?Sfex#bpF0QY76`6pWT8k})EuAI}_00~_{~dYcdy|y&?e|plL;Y+CfFbDS zrM@{ze=OPQoGq=LmVe=3JH<&)xM9mK-_B>Ps(#PDU-VXjmd4x9;KLCeOQ|(tBDp(J z({%SunvgDwr1_L9+mYE}(cVJH2yU7kWk^GBnJ(q#P-4VV$YAdn?WN;sKlHdtAz#Ht z$1qFY`^MOvZiT1WcUIj>P4~uxaoK9YXdP6kr}KWoSZcPrU4u2Z&1O^D+IVNR?3lOU&r;$5re~D2(3JiVveqInO zk`&V9YcwG;4YGwCyBEVNUd(?!nvE6_Ri8ew#^ zWHQ#4`ThetgC0@9wx&Ec|HSXHIK6Vb%YBeqp{=tO6gW~lMzL`-IF5|Im5lL_ZadTY z1yMd0rjaG=yBWzW>)=~WRB$X$ag8?BnhT&sr6qbv?vUb%M+_l26y(mgdWorfXm8-z-_@-Q@}+u&bVFTIf`?)R36w(&FaHIffIMkdk)s^eoqZ0a0BQVzml@e5 zPVri#Zmk>H;fl_p4l-Ah@V>l-#9Ui9b3$-;*Fs0txQ@fbP*k3Jt5=`&YKKr@4 zhOtjKX2zMyD*UF3AWA%Qd zZERVje?#B77C6fPFd}q=J;mGLd&YcK$xo9y_Oa3- zO=xF>u+g-sNI_K9sqq>?)nZ}5UKk7*Gqdn^zb4fP0s%^ZXmV4lAs6% z3q9kOKI}1Z%I}!hG`75&o|lLb+P3B;t9C%&_k-RCuz$0bhrT^RovuOONo4LndA|S! zH!rqMP3pAYFxCkj!07i*zV{NEpv9{$ba`=Om)+PQQ{rw`j2%8C!mtA>={kUsqE8zG z{aaCmp^8{AMC->Jf!~0^ajwR3<+PIQmFu7Wn-Ont1j6P|M-~1Sw2vc8P(CU3dVU)} z)qfa<&frtvbTZk(TmMO~-_a_TOV>b@VGef!7r3`+&{D@7>Hg6ntdCR)_qrzV&u zbdtG;`R8^SUJT-QVLcb-*ne1)=tOkdg&cN;?eJyvxG9+ng@$$K1V|{4ukJBJCMzC#X0L!jka}Ij-1OBSzqSKlfgDL3!)kVISt&3i z>0H>2;M$`xhNq~`8h9#W%$Z~MiIaRwdi%kYelQuN)U^63(>tE9lSEHu9r@SGEB=>P zA&a=&c#QAiE}RJtsZ}sj`6xcjPgcn}I|LOK8QTCqGiDC}ppuvJj<5Cb?qwo*L^&*5} ziD}h&%%3?YM&QuBLmDeDIFVkL@gl{#;>nVwzm%lU-IRMDbn3!6XRIr8|DY$azWJ>O zmA)p^_ZG9I($@Wehl%@_r7D_|dr}}@_bH`dDL=SRq@7Q?I_jV+~3Tk+mxB3I1( zL8`5=FFU3E%F>4(wB90oM()Hu#50{1Es@g;C7i|KH|8K=h0(mj_x^E}_yR~LtPdnJrh#GG=6dk&HSKd;IcWYCYB zmU+)|j*1(c-k0;x@PwH zdIgzaH{eN(&wAiVoi7A9GH8ip#misloBwJM|3-;QtkyjYm`%A|nEl4x-gHquuC=)~ zFm;o$e!3?5f17th;!1d2nJ`ljB5jaBY{ihPZnbRn;hl>C8V+)LL6%Pnm0aWcj2mK zR{Q*`m-^C%o&-nDKA&Xje^K+m5H>;DxwhYSoay52S9CvNqpE9QTRF8rU zcndU?8Z1(J(MQ&7B%M48Yjp;ERltf~JxQf^mt)(! z*}BNbn!$xvi%4?k(0xh*q58aa!|~EBJMS{gi^CH>?n<1Zu6G?5b`Bk^KRD6E$P|DZ zVHELcnd+3NTIB7GC*MxK$ReH5wY@!l6uoH+!iS4a@jyd8fn@)a9M;UC%u7OWg26fF z2VrG2KThXH`{H5Dk!+5Q@5Ynr247*2p8;-`TAI~D$F8UHOwa>b#kO#*)RO`fzEspj zH)z|{31XQIqoeB+_J3qW4Mk)f#Y9ApiTHodA*MBsx$rR2*KlEDljUv3(?I9${-W+iW;wR-H@$gC4u=0q=Z2c#$aF$ghbBvVID}xDY#T zQ@RWcR>8)6I-srkmf&xj^TNe(uY{;5OWCi}fT+yRp3hEGAuIgB= zm&!;R+?U0-8R*Ca$nShOgAP12$%kSr_*S}Ow|5}^vqn(8DgPuCYObg~f~819xK1#Pue=w_ML){4FzA#l0 zv%Z9Ax+UIqX@(b`iY(=sI52OzAh1vPvo*}~4GhUj8ljf#vJ z(N}g1&R&_$pBCj@5_^0*S4=g6g9YD7W9`yg>;Y<3xxAwT_) z&jR)Xh7_ugzDut&p(AEbN@^zVOqyI*WPYxlw6F|bwBO7Yt`p) zZxq;LB0N*Lq)==0ed#}5C5dF+`NO>_Hn?ACXDp#Fqo%*Xqhb4AUI$x4DL>eeZJ0O1 zo&v6r0xk_0hR)uX6#TxC^xCAo1fd~}%W5BY=L4s2U-_a~Kw(%8yI^Jz%PsCV6T#&Y zt--ySV8U0>6_=M^;M9}@$=GsP=Bb@7t!I7fQa`>IIiZ*Pu5+x2R&=_rq2N}jOnXG{ z6YVC2MisEKmLM}Ezum!yooSHO-DQRq{8bSVnC zG)aM5oQJsiAK3Q~qAAB5rVnaW)-I$~LPz-Izm;o*$xOosXa!AAj8WC1H%R`MdA7_*!Hizp&I10y=q7XpQP)d+CO;urn{tU97#Sm9ZYWX)EetF?bxhKERp1Pt>z?W7Hn@TOV`^R);mL*Ryn!OI6u%u&nDYOJnUQLYk zM`zR*WkpGg@RL?6)lWS$wQ;(*2#2Y`?P_O)rJis8>RqR0BSitA{P-JSXP)K!` zkq}tOr)AzxM&Sa8BFarc@5V;}tt9L}+yptx)Ct|v@jklmHZykd*KUTd1I9m<_^9w(<5!NccJz&*5;GQoijQ$ytMQ&Y6 zu#jGJ{c4kn+>dX!N66nPQOc~mZ>;lHJd!5Me)9cCY`zuOAsg;~@q2w90ecn6*=N;S zk`qd-hk02p42LBc%OWmQNX)JZyN~%r2Z~9>432 zoUJV3C$>jK0+g~kd|>zm8C&sYq3Dx&x^0aCE7PS3z(+wb9yaxFjS%4*JdC$A{AJlV6Ni-p=X$)r5)L zLeGVKIwTz?uhu&?d=t$%LvAP)R2hy`y|>L3RT|e)1lM{8n=4A6*#wxFv_!{MvKk#> z4_a|a5M1g_9yI*Vl(h{)okMFF((8>`lS9{mPHzLY8$woL5%b}CmdajOt`Pg&3%`1~ zS{-d^{FdS!J+{(L&jX+Y^g1V!{72v+e7{MeCvonYnfSQ&tmIOy!q?1E5Q0&k5f|6mUdk7%clJ(ZqPalb@F!@A$i&~Zwj8pP%))% zdLyo_Im0C|5FbJ4kDFvRkLn*vB$y8Lt=#T*ldchbJLmV#QjhP+rrxBEus!t_+xj&s z@fV?wZY^LUYO9z`+N@vieSeO5`;rh0?gD$QF4B*6Qm;QJ4ym{PFDnYV3oPdD*L>2T z4X$V#P|aD4VDd6=-A6j$#4Zw&MP%eA67_1xNCx_vP@=W^EJT zZPNqL`o_LY3h1w+tY{5jMFAhbBv#UL(t#^j8j6XgO(7|PS$A9o-H$NRb83b(p!I77 z^Mqz|8zuerH3>@8ZPW(H8MTm)LA*2xaMWktDxxR&;iRu9_z<3o&*BTTD;x`UP5Dhgl79rg)4XP+B#cMPdY!XGG^@71ukkkd-3v4NsoRCF2sj(T^2bku5hK$=wl zc4n&5xW+eidJt8t0!mq8a8Tni)(Tt(Y+cGrC10Jr?$AQzIiqK>JlAoFX0B~tE}?eo zBqa$nF^BzJR7zoy^^I#X3*ry!oNDU!PREbQ`|U5E@}YEGN!r$nhSjqc&%5*ANBeCR z>~_Bn`kQXJ5(uPKr^7qxIH+GKN`)d*PSq+?gjcD^E~`yAE`9U3pBTR#&OA>GqEe6Z zU@0;QAS~7Q&^EM=`#4(n{Bb7jQ{!sg(xdRvo(VQ?)vz78?a91!pYJ)+e90kQr>=F( zx<9Xnu!LS?_d~}tAs%gdYbA{p_0Ocgc{U|ix~8%%P1n~$LqX%5Wb(N+D^oK8xuh=l zPd3>C%HN$!@v5@;~?Yg648X*LTH|lmr!)O(|>`rKI@oh{nl}nrHUfou? z0B(6(!k}54ad){>0cujeHq0aNrDblNFTE#pvinbb(MkhkpO;FsZ*j-~DwAC_n0Sel zZy&jKDL^N!=FC?r`W6`Sb+gTb^4o1$hYSXPV})q;pycH7J*bi_YOdhoY4Yp@#R4T+ zScnqq#EYBy&QUURk5%-?v-L+WvVc6oDPMUtCT@LC-CDbM+e&W;YoenrBnQJ30d~gh2XTG<2mLUzlCQ}`NMVMNRUcY2)W|~ zB~;Wq)0{J*T38#^2EIua21DEfUW(rT%SQ9B0Y~?xApc-cAmeeUeQk!y*!>8%>4x3E^ z%v`^^V^Ts$hL@@Td%$w;9>muwt?V%fUQDvMKdmF*>xJZyt$vL1$_Fy7I{9OWw6ECS zM06i3JVTh$NqaVay%cpzWSCEs>iIGm>0Wd27OPukiWj>)v8Mu;#l()eH+anzb_H90 z7;-QLf_ggJNgoBA{vYkk&k8UnZp({IB+5LxS3y8scHxhc#J08SDUfih^21JQ7|=-> zbvr`(1an8Nny%Q};8lwd9<=zuTrp!c2qslm13uvK+xr{#96PD}J0t}AZm}~uARQ7K zkqlDh(LhBJAjCboF>w9_6$z#O5xc-`W2p<$c@S6$$j1%LsPl@e$>JY-UcX_gz*K!Ob%#7Z;}@SB^6h`-@ar$h6u%HvH}&Gf#%G!Dw<>` zmqM<53PklX^cFRL05HQYf>ZQF(`f!5z;D*YTor+e96%9=8g%v_BR_sgmkxNUcWsb@ zR2_&y!bIk4B)?=jO-_DVDRuu8iP5c7AVlm-o8%L-769_*`G@|=K^hCJhOafo_~f|J z!oXaX&(R@@NkCU>_oKIe@}5xF$Kw#~ADYBLYJA+YO8eWFlz>Fi!$EqAncsj_Kj+Fl zb5f!g;OKmqFr-|mac^|VPHk>#&+hB{x1$d5KD*u?R4t&$0gz&wMn`)33963cK8>nj zn^WTorTBF&EMbU;3u~L|OESZGDu@PfF&g9p;jdeQc^xhP>UN(jFNUyX;&%XErG@J!X}qRN3$5~|XB&iy(@Trlt$!<*-kd6dlvq&uM6O6PAJ(MbUT@OT9c{P;{gFlu-$4(mM#W?n{=OTQ(!Z zh%;dbQl~l2%JmGUd^H;77iT|*)8%O|v~OPJe;8*EWd9paLvbf$2g=#bAh=pMOkrz* z7WdwfOa=^D(3tVqtaeEC&p}yutj4eCzx#fHLCB5UTqrI1m{5gwD>(HhiW+=TzPt6^ zg%L7pndZN-u3cBxbU>LNF3Rx4WG`uZ#WycpR7dK%Oi1QoZ<)G-sR%;goYb_+dQSk11uj4Bkj6&w~(#yFL7gT-Y+F=E9=8YT)!&A2l{O&jA0^TmSJ> zNtvFc;P9PdVcVi^GIRJ>azx$ogP{j;b6!<>vnJs0rVX>1;-F=KjKC^|@Q65uAt&Bm zu5<*trkfcsAPg(bzD(nk&c*5jjD8ltF&#_K?@5-!0F2_T-dISZ1G=9 z)dHqNYRkYM6P#6`9@g}{;>1!Pz8iXY?g5O}F8%j(fCE!$(h99oHdjgSrC5k}HER== zU%SL7EeJ=~JARFLt0dcVcbU#hj?M2!YELsFK&c_w-(oYx{<6|I+q%_6lwohO1XSK1 zTacF)kO3s4g=-%fuk5Y@Eat55ocP0u$Vr2e0LRIOBxX^9M4st%dxR??dJLM~!|E|n za!hiKX#tFm0pFz>u)!V6k}-gw1lSLG-87Gh+r1g62LhoJCTIyN`5%E#WxVCVyA`(m zR{fs-HL_JdkEEgfN4hRTJNcda7?cnC^(`4j3MN<}q6bR?l0Bsxk20!~ z908U3D<^3m4MSrY*55x|4}Y&h?<{(ORlCZ3 zOzB=Gjv&iwq!sEd9Kk{>b_H;0aCe&fS9yU7ch;B*GIoD4fhAGfUj6;$fcFDLTt=@% zPNb|@bhjXw4V{aoEt4EWV}))-!b}d=I7MIlEUw(*sD$}6CNF>Giqa)pt*N;!+OyQ1 z?oBu=4%Aj~pO60LtZ40x06H2WAW9`CsysePTiVTdt1YG_zRKUnB2Uw2ca7gVFE62L^J5YIJN52; zsa}FnD;)LE7Fvfhj6fx~?dp|3_p-EUwFQq>Do@zU>uU=M66yUOib(yks+Xja zXSI6X^khl$Y|v+LsimY1Rk>fiMB&=m#x$Ve7YLv0RhXrotZt#SAtt_zZ@#NlnDI5} z}CJyHBds$Oa=_=P0D_At=0*BbLd_HR#wnCC`=lrnaQj?K8{$~ zzETbrK09%uMr0@j#Q#rDr*bXjNDKAaifo}tmrp<*DFBspKdJgtfb{?Cm{A4li|HJi z$oAJi+a^_qM0XC3kRyC^ySoGiZB#nJbOJj?jR>6iv#wB42*SvqZIKZ+K0R0s`ZBBB; z1Awi_VefLlO+2!>?zWLo4_IMkiK@FoGulxl4oshCo1lzf^>mTb>Hvz>fkDUyU+->UqJ+SAX2*;rWVIdvh& z;+YEHx$sN8Xh&ds>o6f-ZgS8APYti02|l6Ad}`q7%d6ajfr^~KHoVN%82fpsPLT}V z0Z@B2y=@aU3|L3h>4+;Q9#a1}&NA$QJ^j}fWd@RN#3YhXhUaophXsx`x9yG(UK(aN zFY}T^_mFhpDt64#wEy%$A7ghnyV%b}<3oa?`~ZnQd3>_sNcaxib*_*v-M{ZU60>Sy zST9kc(HnorsY`#%pRkl1sz;CZoJk(F76^F7_SgQz^8l866&`W(NISDw*_`{Y!cC4wK%daU zx{8NMz1w8WSpcb@*W`!*H{EHk@LsdD>_W}%No`))U`f9>cObK9-`{JGhOC$~0tDQ*h}`wJd5fdw0ow zO0X-~@=oy*v92@b8A{`k?`~m)Au(gVaJHzp#LmP*W=50$e~0)9aQ`mP+2~J$yCelP z>r5HGxpu|}+xG}|!)_0Ufc6f|?)cH?hq#w;zBykE;MQ@4{)}G36c=8(0Q9s%kXRT{ zv)}hL$B@bpD$KA!cD#&jyjoV5yl(R_b_eNg~WC|I*8S- zoJw_k)PFwQ6pyZ)En`bYTT08YoTf6LPwr`rdyN!EpmI%{+V2E?y!=0-UgV)xZVNcv zwZ*qmDdc|)XOy8eb?p_eILh{T^}0BgBHp|We$}Fo4Jh=#*41&DZJ6|qKnK3Qn*vvA zv9&#n>D*rNI5N{p+B5FkZtRy%qrvCmrC$ zGHVXR*hzl-_`1PI&~qGyy!2?hV`ZHEF6o519-G*fRA!TDhq}$4{B(=3Ho_>JLD&R! zkQ<*}v=$!&O)C^4>KqjsPwablhA<_`eJ{F71j{VX7M12DB8EGm%y@W@zIauVc7**1 zj-_za=#W}$rE^Ns-Z1MN+xNz^nNw%F(W%N_*;o}e5dCo|GUN@q25!3FOoAUWu`KY$ z;|c)ZsasOk&P@47Xmo#uf+cZp32~kY(X-FBQgNjK7H()MvvnsVhI~ujXVo~wWUSZJ zHfzk^r$xE_Xn%6QPzNE3=Iu?+O_P}pRve+tw*kS)oichLTSEa;;fET$IGJ~fK40Ph zkRuHAy~RA+{2(jmKXS&uJAAcZUdG8dPR+uyYzk89j1V%YU?4pTd-LU4=nG-9R^r;B zH>#&w>0?0g;I|Ew`Vc*%H2O$0en8MLOkzGsck@K8)dsj!1gS6wUl#?U{<|XuzZ*z;X z9Z~a&k3J@@zaf*&54)H;r!Bq8KlCKTArAK(XkV#5_BS6%lD8I@ZvyRMlgmhub1z@m z@p7I_G1yP!O);DnomBw33l@^KrH-_?Wx1xILQ-nPZM2(Ttar7h4Qj{fq8a^G;?1@T zVeocgJ<;S3Gg-k0u z3m6QwYb^9ZM>;l6JJUKPp7=LYO^yl_@Vb|(DyWTQP8^V0rv^_|jt6Xv%-el=w?+$c z7TmSj-yJ8Y)xi7j=BGH4xCjNyR&I1(qA)4>e^0VG;71}1P`EF)yupv^*O5= zrgf>Y^1FkxrHJ@AG5q8{0%q9Bw!XwhC4b*3;bVqP{l%&&8CTVmWhO*dr&sSrPGs9r z^_|Vv9}tlDmZp-RsV3zJX@9u4o!(sA&ajqUyi)h9$l!>RuX~cW{czY8RAy-6pwIqW z-in@%Lv`~fLGsN2eY=IYX3Y!dp8pkW=>ip_VfNoQ;`pT+RyO+;Jl=h$p`S0LB++~1 zI|0QQ^`_V!`ZUkeP-KYOMjAXA)RHu^N3b5)B%;GT|D@27X7xMpqskAB%PWj&Jx9N+ zt!bJX=ZLsZoA*7I)^i0fiB8+r)P*4SXer!VZiEFjFvu5$adDl&V;a=fZ(04wL(2;QwO;0-?XD%LStR6pGh<%w1% z*PGO6>mNZh)%k3Q;J5Y#kblV3H6=?MNM*3TH;Rz9ncu_BlnD?j_xMv<&B@MR14)og$*%9r2sR7T&)t2**SE0kw+&8O5%XcDXDpJ+1~IQCp` zb3l-9U$+3dI6*5Qu8w>uS5zU_v}ucNf5Cp5oR*U8cuNTA(=)-@M0NGH93x4lD6a1V ziLf2!qTx1gVdU;-u0LvY3l^Qf@!svwX~Q=;3-+2eokZS|Ow*dLq83 zrxn!PzSD_1Y%<0-W<2(M-mPu`;52n|(okfizPg+KyLd-<#$mu7y0-Kptx2kZBtChqX$M*L?PMb_Nw{akyB_XW{~wGgr~Rc37m8XLh`zU)RC3 zMt{$T)!HNceu~5BG;9z8T69Q`|!B6wi}2Ms|J6iH(*| zV%z!CdB?ry1}%~a|MUU`iP4hNtP7(K2E05x9UMwrG;}auo+Uj@k#Hd4dh^7l`70LG zsgk`(zl#n-PTBW9((xQvT<~7ZZQ6}w_P{i%U#J|5txG85T9kh7w;ZE#L+dNn;b?g{ zS805#>PbmjSHt%u7Ot>K$vO#zEDr0+rkB z?d($h>h~UPN6l9ytJ;F+CzfB=zl;TwGeW!*1gy#?@y)8V6i)f~@cagbzI6p}A&lJ~v-)*?$$POMBzwShSw*G&~Y=G(8Q%rD2s2S z>6S|7YiFXa^wnu04A5ECaB_0yg7YNcX^(NgfH{xH7pgSoS#S5(CM!4Sid;J9|8~*} zavtOqBPFTA?K`GrlD;a)S9E0m0QMMpy6A9VB+1}&KtZrDS`BBFIj_fC3>)LiN+GM= z^@DeO{_CPb<}w8`6ba`LcNj&7hAp+$8k;$Y^WAE~6`~Z3gngJbfr5aQr2>O;3;xY` z^_^_}wuym(#(^(Ye^gEsC&ZPex8-DwX)z-*xTe9~E?qutjRe?|+&8O__f#sO8FNjp ziwI~DL*LTiyJ|Qo*=S%Odr)PlC+A?b7NtLNr;$%)!7&WnqL%lu`|GqbZ(MrSc;xj#oKFZ zfsyFzFFB)61sR4RciDGn786w`Bv@X(U~xN)8O<10+QLQTZkYmu>JZE9_Y;*++`{eB%6mk=1c$&YGW} zmm7s7S*(|^XXBK=+awjlU_a$n+olB%?pP#M$)IZ>XKvXE`@6kaz@~RMIjO<%u}-ed z=PzT2d>>x(ASVT>X2BS*IE+Oo130e>^`hlvd>`YNfiD+&g9(3Shd?>^ zIRVD?ebKVjj?LH*8yx`{E$F))VQ{+Xvej$Wf3nde`87S)Dx| zAZErXfXI@!kzLyNMN>f2)=Z=LOOW9)*kjv;%%p4UQKy5r4@B@#)-_U& zXayj{4|rAiPsXe03k>M%j=m}vWC-(Iynb9L`uDYu za$e{X`Co9m62juY| zMXr(mqdy>Kp9iW|CY`rLE|Sy50y3)BRRVSLe<%)J2O!zDGjpE15cDT$7$-K0up{5z znNRZTjZ;Az;Li?Q(&vF`#UECl5ap)?nIu3?PLIsjq#$_-B-@P7uQ>d(_s58y3y_`J zBu(lz$2mZ2=fn#CnfzGeA6^24i~~T(yZ{3PPKy%fPtkyFJPiMVy>f zkrO*k3IuU}K-K)3bSE@@vUtcDZ43ggY_p?TJ;s5?#_yz60Gl&^HSuuueJiCwZ?d?? z$}Rt$?;*I|`3#f&<%+1$2jPMbA_k*w7oRHtX8vbJ-Qx;&^XGwUq*uZf#lv@kspJ<_ z51aN^&7zfuR>C#efkEExU+Z`Y3u(V|^VerCz-L|Xz|)GEvgGkuFh1pBoi?bLj3au2 z?lGdX2_s9ro^AZb{U?K`=nU+`$5|~WYJM}490q5>V&+V3WlRQ;ctEr=q%YDXWizH0xBCex4aLo5FRUS_BXJRulh1J787tLK{axpJoad0kruGH3uJd)d zJMa~)?hii+Dn)fby2}=SzB(IJ3F5DUMO~5-5l93hz$uO1_)nh-6CZ)PL)g4KLF%059%R7z*V}|C1l9 zKPd<uI{g zt<9PSTLj567T0E6L2eZnFZ0DbiZQLfcv%?qc!Y&br43)cOHnI0IoI$=(;wt445|@5 zJ7XKVG3JUn5M!)qUDbD4y8g$nopT1}3NzE^B@Jq!=FfqReX$oTrujHT;}Dh|X_EpL z(ih;18-r&%Njgo+vMAT+K;Rp@Fr+L$HPg18$w2dpM&3t0PV7KUdOeZj_D&bl?Of2< zpBG#uzg+pr+T|Y;W>ADeMTp{Kk{r5qAYK@>wccsN&jdqU8C2SW!99??)cTy^F+E3C z>us6h-qg6BIGE>xS=#fTO8HfJHar)kABLpNXic!IN}=!IX( zFa-pCvO6e4`stNofwJrZ+HJG)nPB0~yxcLe_@umC4fR@iF|F?Ese(m4W#hK5OMzmW z*QANIwR&4!KfL|tkpD;Adqy?6by1^=DA>S&h^Q1p6A@6VbkNXy6QznuhakO!h(hRs z^e(*!QF@O`5Tr^C9SJo+KzfI}qj+AvJH9dQ&wIx3#~Dc;_Or`cbFMYl+H1KugHC%c zFB9VAp0%QF#}}#pULcYXi*BWUH~uj7F|j-c)^?gE(vB`$Y3|?ABbb=~$q{bfRBeaS z@#*L@D>U=|w=qG^8wQLj#HH%31RLp{Csmppk1J%YyFQvvF19G49p%e)g+O;?mS?r#D#+Rc zzpB;bvvXVN)%p5DOIOX)rdY7yEHXy_O%xa_q2two@B%xMCaur1q!5(%cNcAC1B7kS zV@BJX<(OX+0~_4g>h9?IEJGLWtlK3KxdBU+Cc7Rfi{e!uC1!$(J)6ABXS%2zZhQm6 zwNz@lc5KWz;0a9oo&o-OpFJXyshY#)vMrgtJItIMLjJJCtr;C<3#)NkgqR$wK2?28 zq{W3TGTd14@$CgdarJp5rYWWj)`m(#ePMf^?b6~Lu_(NkrCV%03!=XV=pP!zDJc%a zYI)scLqg(!$)q%zcUTi;ArzY3s`(Rg0oN1O)yy~$R}r1h7_V-d0NkhP$2lNPNgAa0 zGdHra(b_aWFm8UPjAy&D?_Sv*I*vjZh(dHfV(RZbWt?QsOMTU>*$olu2D;h@}ZTL$^EL(%> zcH3P^KhTTG*7gh_$2x27E9+{+gDqZoZJy!qw^z>RP<%Uk#pLa^+*zh@oT?&2G#MwVIPH8dZUx zM{;1{5ALkg8Kg^x#>!Z8$)>i7Voip2PQE%AO?)rd@sMn|V}Q#>yyWNeD*-XBM8a+c zd5)Q5Pl=#dfngEpw&c|)`5?s1YK+?NV7C7wm9cl$<8Xh|Rlx3N$i;kJbm@!TOf~l( zRj`O^x(ej`GVAjnNf$%X8%-D|%MkOj#kH?T$ix`fu{tZ6imL8vFukEV6%i1ijhv6Rs z9~EFxMo(jWP}A$0ca0QzN&e?QPn^^SCgg#D-<57oVdrK+!4VJ7C$XYfNhs&iOMb(! zY1Vur*f6}~yg%@6Zsfarr_bAee}Ve01f71LT^AR!;2!dX?oLyt#%i1A%vlb~HYN#% z!Z(cAvtP2${%?!<1On{BS`$2N{;O+k+FBr=vAs%Rx0=D0UrJggaQ)N?`)fi{V#^7^fv`5`+H}b>aYK>AWaB`J3X{;Q=HX2)_)`i zKw;>?57v|%e(S-l_R6_Ugmw=t#E9EqcXUL(n~_(cGm`Qh@C1qj8Be;K7^TpRjOj%G z+5A@$rzw6@E@KS6^+V{z4ICGTpU}E5d`>QwL9_q)=OW0 z53ENsoMGU)nC*(@RFn`%;167c7*!#*8X2_4=g130L^xK}i%>eIej(5biIXFr zh8gH(?rkoO8PYoTsO5R?ZhWp&cPt9sLp7Qw8-IH!mfffEH?s0Z&~6V$p`b z6Re5?2uXr;u27i}i%Yhm>v24$d7S!Hw*(;MlsIWLWi{UHs3U{wGO-oJd*ugf)ZTL$ zRB151!%s>1gL|M118YOcLhAE^RMQvX&rHP%p6)SV9FvuDof;VHMZ*$*Zi$@n;-WF%s~77M^y059iX z0!%>Fp2Wj2kMVDB3;l}&f?J#sF6$$#tyhTxF6EqokW(ypK(R%nBHDh{y_g4CKe$-83n%1RUQFrkyl<*4 z;fjfVsZp01x~Q@V5#ZB5Z`YA5s)h2}7untlsECVSbpDwDXZ%tB3(9qi>L0L`6$Ohm zCFunU)I}OOe6F6Yilv4u_i*u?dLtnOGNRj=SuMBadvwyUPJvYm34N^2GWymKy;V&I z$n$}0CN;})06BI+3<*=$Q{FIaxo0b-SMChnBM)rz{LXZlYNv5#tdmjr0>HVs%EE5Q z%A`R$N74UUI!H`WlDdVtTm@F>s(d6U_q_~FMj=|P*P7XpIo^8{^n%Vk%rSatW&O{B znw;Ze%z~^G=A3_SNb57X+z!V+#~Ttju3cCFNYwdp9GHZlz^>|Pl|h{Vhk4-sIJ|8`pTj! zLvG_1t82y{=-d+#kT(AkN8{uFM{bqUOt`XD{9Ggie0xj2N& zg6_M(zo^0|j}dUokq90m(#TLiyAu&Dw?k=or1{{(kAJu`uhy;z^678l9Umun*d)g< zI$EOGX_OxfqY36E2H{8EhJI+s6b?7M@$Fx|IyVY63{_8|{ z+aC~DLCYVC4DZ~CdKs`r)Hi1x*6-q*zFN!G@rWpu2cA)5`BZ;Y_n-X`piYxwGCBRa?nMdZ>{6kNlqFnv5DiiH2zb5$yyEB=t~9 z=HDOzeq|%DdKcTP*NAV2fY&!?Z}#|iAOH--0@cEVYC0Ln`Uiom|M}}t$3DCr41QpA zzS)7{oe9_j@!I~Mj|n&~gC7{&=~_E=lJpiVG2#En)XM{6eER>t@nm2>m6>;E8EdBj zht-}kBK8~zUH=CJKqLv_@}a2|Q9ejHY}KuTpmRuEMFbn{K9fdVk%W>p{IAe7-8$^E2K5N)1we;a7ljimJZ9vsP(& zZ*=ck{DyfRC*CP{uG9uubWOk)gp~3Uz+{cgcP!jg)g%I*Y#6ABZA68NNj38j#Xoz2 zkQ&+UXxeuP^lw7AR3aHB{3U20vc3rFb1^{Tf7Yqr?(H?x+`-!YjUlbEI?v(}mBjmM zgl;RJ$7|NQ6z2M$hiL&*Eca2V`h!VZeD3l@W4`F-FwacB&nc2TwRGtz505m^<$bJVr$6D{EVJ;1(UDaB^DnCscjo;Z``8?m^!crqBIpVOV^no#lj}bFA^<@kN3grm+4Ty6- zecr^ox6)z)>LKoqiSfl|${Cs_kmUz~39>Fh>$)$d9-;3k;nty5jSC)%PKw;4@12C% z)_jBCwNO?+z6$Z)PLHAVcA;?pqDhd`yaQCn{yI2CQj7%DaK(f#z~$=6C{lcl>*@`U zkt)X)`JL7{9<3No{T^Uh^%&=N+!Lw?{5(u$LfV=tL&*{#?l_gD6#mAn-GQgR7wzco z%!&?{fekecH^8Dg_84w+_R~C#l24*be}f6<`4F#XE9#=hhwQX!1*D_ zf!<(NLxmSr0f_Ka&Icz>Yj+9d{Y{iAyOJ^V{sZeOT$TkI0cpU!Gq@cuaSc}U)4%le zS*Oy>EK9Ww1sd_SpAWTGr`v$*PwTIBckV?Rif7Izf|3rn!0Pv2T)1Uq&5C@|Y_;}be8?zS>Ta==*ci$K77Y`b z0*$6P-orZ$P&*Vx7;I+4Pmq&yew;wl00g*oJQ{7*wxv=(3v7@i7rxaaJ@-#MfMU5- zIMO!8oot6X`zaOm`M7y8Oip((`}8dr0>?l zTf81KrVM!gFm)5v6)?x&;5S8vCB-jpq)H5S`q#>BSF?^K;SG`D$tfP^y6V2R38Hx?bd5m90&4&9NVbA7@8 z&074%r%i?Gbv9HS;V?}MJr5I2TsUF0ywbvDN`pi3fuduv7!vB~N3SwA5eX6b4K1OF zU3?3f#@J;Iq~;4Qh%+TCN~3Ilqtx<8*J}6PNLssrcX|TeQjRwr4YzKvHvImyr3ABA zLp;L4r@v8b+1W#`R`PfVJGXg4`uohI!#NqGQ(OGIoMO+s>R=lTf3*-f_kyFX?kWAz zc_!~&y0XSw>y63W{MC)a?DgL)f$Um)<8YTp7zBBN155ttdC%qdQ^%PkXCbV94!ikT zvTi!4K~C?-b|;7}Y`AXK7bZOGK9Zb*sLu3L5arke3C~Xx#h?o6DnMruk__4(V8fwh zRX~j%0lR5zQ${;=LBsGny*7Gevg-o^wXu zZ9)euKN>(6qjivs-#46hoV$j-F0t^9MLseDb-IQuZ$f2TL`|goY7bH#O`A)v@flXv z2(q-P$c(i4p9I{JqgJf-q3_%t^0diCpE&|g{M0Ul^&EdookyvB|7*DvEg$A>>+D9K zu1!Ix_<;+b^P=AR-fD-il!K0YikK%Mdkq4_D*XC-bGVip4~I8e&&EQNJFdPM%Xo3V ziJAOPvd4Ry%wRzjadZ%iyez{-Fhe+hB|&oCLe+^oQq_{-lYn?nEz#_j z*teziKsgDADTo~lDlm<}s=NE*cNNk=qqnWstDg}iv`?Zs(t9v?nE*t!TpnEJ#cq$N za>452LhAd`bVNnMa6G;mUmgm@O5*1$EyiOW_6?`CsdFy4qw&iDFZ?_=D|G3Npja=O zlRIAbQ|nK*mmyMpfwI4xhiw?z7oSp-J*ucc4VMH$-hK7G5yceXCCmw@;CIZe3Q~uk z1}`D<8mXoI7BrY|xl;~e4{sWA3k@1UB%AETOdDXO{(PHcK4c`J?RE3;>(%B++j2fx6(G!ahP^z|J z$OXVD7^|AnNT}ideNZNcNnumo@G=-dfo3QUi{(>Z1H3O#Jq`jVFIX4`CC1GyM8(sU zd$vg|@d&6$2yBDiv95vDm}Hy%Pf`aQ#ybbq$kpFcgEw|p`mLmW+a8JsFAp)oQ*PTJ zbI07~HrjYjEc7_+o(vaz+-AM&Q(}sI_zsl7i8CK+x7`J47bp;DQl8+5a6Q7ife$6tqrwwwyN8+ZEb zo$E@Ggu>AzXrXcc5{P$XXORR{tlebm4-?zTu{R}2iiekhQnt4Djcpp;^>vLy`E|h< zc)hiU0a%g4``KBI`|MEJ497Qv>=rbdhTo}}p^N*alh6OHmq2%A#_UxMKU5rH73S4Qc&k=QI#l0 zAGx#WXi?QFg^UU98p{4QG(kPmqcj|t8J@HqvLlFHK&2PTXP z!${hIk(A!~x|rA@jh|}HT#!u*kjAb~qTs^2gnS)UunDReNRAVI_l3I?c8)XyXp%fF z+#a_|X6O4``-4RJ7kNU0vOaeahvR?$DHr$F1XpHL_tUcrpzsraTIf(hIm4e<(z*OP ziMaymFoiNj^X|{{*D9-p4I12lgbDdEKrqjL;wflVX3r?e@gTOE-%6l0>@>K81EIgD z*pNAL)A!<1H$POWJ|EDMV z-xp+orv!4gC8Sv{-4rl%zQWYr6Qz6vt*n^`$L>-yY`-1b5EC|tyOr1ynJ_h6iK}wG6oQV)%(jaZx z?s*L2pE#KbE^7cO*HkkwptKKJjK>Ca8I;p$)ZD*tktm8F`7NO`{-3t~1d%P^P_{X0 zAWr*#$+c1ue(>qEvCt)1(1%K-Ex*41_<#sm)hp&ZU_k1&kB3UkyNlR6KRhIv>42wv z2K5I2F~3W^u)1>$C(FaK~QI)GBEC7MG) zXp&CU?f!lkooH3g#QnqM)!r8R+K*WV_xK!8j49W13%%AX_?*$hUxaK2$*p)(zch|{ zd>qv8Qf{ko^0TNm8uCbBciqqOjL$>X9X8X*;H^uU>BB@{#lN@g`G?Wb-#4-o4kk++ z4+l0``&cf56SoZ}Yt>ZtJg~{K1m>cOaA`{Ldj>SpshUu_=Q+0n5sl{JV?t3(*l%vb z`4dplTR&n9ugx)H%^#ose!{1U7%arOKMTj?d)9EgpGRb2A(QjCHo+$=Q_{W%jOj4U#=Fj zoez`5SqTyyf4Wqeug?OuD3`=ORFN&s(PSz=^O7UYo&LNmbbaQ4XPR8G=XEE~q<^%%3OU>uvgvIT82bp4q8U|A_-hvYLTN&#oi`lK%M~7i)*vqk z^hLv$xj{q@vS}&agZ4O=DT$V1GUbSk3VEThZGji&mLvvqNPJizimf~3ZO zdO&Z&10#V6tG*)FDx53$6OjE+5fZnd*f5b5S*Q2PeEW@T0#M?-5DDb!BWc*kwAjDU z!(%6!t^7Z+jrkdka8c~&+Bec1nBz=a!VGU$fo}Q7jCs#S-!m8OLUQJp@S|<~lfuFl z*3uf8OZFY20@6GghCR16YA9Gzt1V3;5-W?iR7# zGoc*Lt#Y3H^OXm+o4a(&&z;0rhWu!)+p;}JvT4)+uzY4|=$2B%;q7*(j8tLQ-KtK; ziL5J`lX2`v{rOsQgP?p5ZwdcXiK4R`y`KJy10>WNb>`)@D?2>qGNyF1{EA*C;zAY$ znAHX0j&?6R%MP@J<660DK|)FU00t?JA=h50j9plFHhJ{M!=%s$LkCj4G20`e+lz^$ zwIaL`+f6q>1h)P{LdcqRp?~|zPfM1~N^K!jC1;iRzVM}Wqo|>!Gbx}I(%Fm$XhJ&w zST^V}t~HLH7&O~FB2c1=AIo|iIAp!d(G<1TqcyIN;#)N>^~V&~uh>$W^Jt*3lg_b)f5;POkgsudhkmgoDz;Ev9vk%@Jqkt|h57L)WRO#M$arBS6rU2Wvjfc4w4C|mQtazsg%G2c_GCI$K5DMgk*vgs5v_vX$+|7!W+l5Y-^*5Ls?i4dN>%@3DZ3zx9)FH-jwn z+>S}gh7*xM_$KyOUzwmZs(x?}igglwUp}gAB(8Vw?bxJEd&j(F#CBy8Bz5L0tL4p< zF}1k|sl&joPnA6jrvBr1k##+5cX4Q_n#pA?7#@fy>|sMni<7(K_5L%kjQt%*r}#<} zV=)B2JN_yZF;7|%qk2UiT_0)#k{M0O@&|wD(htM4Cb!FyqB+P(C%?X>q2+JTpBpN& zI^auBUzglns;xM^z7#DDM+OK=yWiOGB}tH>@HP&dY>O9Xzy2WviiN*6%X(CehipI$labkD<5Z>0)UZeP;!i<;Pa4g z++BBc{XrCt1)&D|4!J4W{?G>XjTm*44vez*W&~`%GNV{Qr&v3(o2$8Sw~w58IITer zhW0g}1!x>!St*5<(rnWX9UvDkNvtv!inXNFMzSu4pd-VMdO-;za_nkk&Ct4(<_xFXCa+DvDQa;|8k zvMm=~b30g39q;dK%*!W3HEW~mUtE|i4Xl#U3zL#5WVRj){_Rg?Y@dSK{vt&uf^cb} zvyYqUk7+bHyhBcpK*V4VpWuoqz;2HE6xe!_$e7S~_S(OP{5vtZi|gg+QN${-zv&aAt_M0-z(n@=>yb9?m4z3})x5MuG#SwB3bJ1^3N~YQYoY>2TwUZ&Kl>y?9 zH3mqGSnC=t72FB2@hz}IkYsi8_opSuW**n`@}Za4!As6>v_;zQmj{4kfj$dJ9v6n~ zhdQ`hJEqtXRfm4_7vvjJK-bsEh_vV8w)d2-YoYOLFLytc<rVp)!?W}duJI6A%%xkGP9kNU)JLeQ_A|cA2``gw zWzP7Ue~u)#V0tdO)Ui3VL~lYz_&vx6dI+r7e{I9Z5l~5wvsoD+smoWh#G!X0q zHiH{}XME%dS);)~Zu(=;{ysqa_j__cP67>{+xShI`Phf&2tVk5%}q<15eFhk)4fJk z$1r&V_yH@-?O0ibYuPP|F8r@kJ;0XJR$@+{r_Q}D6Q-qofG1l2VVaUZE-;eUF1mYx zps10x{&9awXrqbIZf?NZz_sm?qjH!=@6J45V2{|_o4-JRoSB1T`2W=_bs$A>_PJuq z)#pNoJj&;0MzPk?trFP`M6;1Szy4bT~iA(f3u z63$(}#@8$if(O+`Cho8laS(f2asn zPtB<_2|AgZSX9B132L0}y^M*_=3GEiKWG(Q|6ne`UeV0lj75sDm%Fo+S}1To zKlrYimUpPX3Hbm*>i?!*^C}=xA5Ql|IotNd-Iq=o%V@FA!55R_SdIE9WD(jzvA39r zL(DFO^PhZu9!Ybz06i_;{q%$+YdFH3A@%~t$qU_(v$Qv3gI_;=6FS|Q#`LWrob@Dy z&dG%F@8@}aa1z(P@A(*wCjF{*Hd)!xuE^gU(D!!I!U*bX&E%Ipc1RiYM%Aj+YoltN z2e@;Y9cF?&si2%G0?H}VKXMjMqTHq~&Q+DM*Ln_2i~ev}tr|M>;vnUel-^vQd)>qf z{s4qRtGG z1{WXn)G|Tg^nEP*U7cRVVMe6B(1q!7I}@56Nm!4OGtEV+hG5X>#^aJ0RO~fnc;0!r zUx}qNE_iA5c|i}}zuj5xfMY1F@$IrLWN|c!%WcKFISUlvbHhh22reiA@TEB-!l&OA zACb@8w;&(;lS~FUYR8{Oa5X%sJThiMfp_f1`_6Z&iezUeCDHkBrDV_(#6x`grGtX% zQ}F&zk{pJ0!$_~8GHZ)(Z_dW?na=kWvOU)E@H>%kvtXIUZSd=;m_@PDG#^R8US;Linxjp1BUBaw3NL z6m;=H0Pmko_os=EG^24~#T8?BLuWmho&fVL9Xe1Nf~4(M7_^ovuNJrgAkOXnXSA5m zTedO&XA--TJ7KM^_YhmZyw>g;b!@A$SR^-DXluvJ;c&~V9C{V11%@awH9@ri8F_RY zznpk=b+tX?*mVK8W^HY)MMEH!a-vkm`Z_w0HE|_20G#BeroZtDM-T<-F#B!&x7ozg z;`BNw^TtW*D!~omMk&pyBG7|i^Ub_lF#^P8zhn+CvL>(0mp2F&(i)`|&|g>>I&^FB zS`GGl`^-){l@2ycb~JZFTlsQ9Wjc*WSg`n<_-=GodQX!I{oA) ze#6C#7?Vl8IXEcOxn($~`{}8V_QwH!{f_?6&)GquO6WDrqdig3Vz|PVikuFt+~P>J zsCmqairDsxQrA@zhFQya-uGjE4Thth!3-y;)|NdA&E|KV-1KFJPrdzw za8VsMD*15`@7O`Z5IgK~5WL68+!nI@seW*6Cd(tp zVaWY!J%B*SAq#QErq`6lM`o}HxE@of2s?EMLU>A4>%)s?4iFwXh|a3MLqpBRw;3Gd z0v{~-5p=~8&|hZ<2TfQ;`W&P)MZhMRe!PsHY!*=w`ii8N%`mS7%ITUJ%4gZvR4j84uw!A%u-0vd`KqU2XDnOK}(NdwJx%_}Fn?F`RDV$3S< zNX2+AL;FHPLJZJ`3RQPP$lWhp!W`T=AdSUZNH;nTaH>t0Ko{LebstxK%3L-lPilG1 z`fmcTgqYi#3PUQQqDaH&rVgvbMbxR<+f-_1g`@VB7;)jGPVsklcAXTX*kL&JMlX21<|)a9@mdVV^mkaXIOO z@*gyC3zTZq>^)Z`zxnPuux$HMXycUzkf3voRg=ZI| z9@_$aaZ=H+9-T-mlFxpC5zf^hFeI)ABuz%bRd^`YHXL68)R)A%gD-+gJ<2}2r1lFRKTHfp|(``DZC(&XkTd%iUgI)7b?8c87H}Og@ z@ydOFY2~MwBI}upx{v6bcB*K5O`o{69emXI3!pLxh4sWT{6Nbr=mUsFsfvvO6way$NDl3@$K_H!S0%k1XlCvJze+!hWTy z^8H08Pv3y`VX-;VP@n*<(0L9}LNHTV05l;VFR0ab*7x3}J|>x2d5At0{+jzi#^9 zNF$>Y$r0Q7O3uv*5UU&*7&GnTs$=&My+Z(VMoJw3Dx}B3qx>I+)f{`6F`(fw#!NU8 z;&Xt;a(!XwgdKaAJn%00p|R({v?&2AR`@gedGr{1h*h1b3t^&4gLGWbZIj*iY=2_BEP`1<;^va8%0zmI)i z^2Gjba>M2;;v=Bu6AWgA#wX({*geT7pkhs=kgyc4C)u$=j(HZ=)>3H7*We!!<58cH zFFq%rizfn@!29zP7J6Xqg&CU=l#aE(5SQ26t`lP^laYA<{?iet&qrQ^*4a(Tm%>M? zz8uJU>Xupk_=voC@nT+aF`+(&y_mc0HQaOdQR!A;g9YBzvX|LZ4?C6@g*fiy3*`@Q zUH&`RcdTHglBIpiBvL0q_&IY<3tj4Ad2g)DIwAh9(E}w@4?fuN!Yf%HK^El?nq^xkvVhw#WhmuhJMoXc}~mXq^FD_!tYFDV&-V zO}b*ka}K_AdhC;tFQFzM6P1;fb<-W)wj1$%cxL3U-^EQ0)wtT5(bzG98e%0OR0iLW zuxa}ZAaJV)bduW{sdm@WXxM{EF1uVtS9u}tKc22E&slxUN&NV;2a0R+# zH+DxjC%C4=e(*>PEel|zihMB8^h;O}xq`wiU~I18oRng&*|l?@38+9g%E&Zc3cn!q zQ7Ndbl1aAkWb&q7SDeiByAUoBxxc^gkmm$>y$pAAY|EEt+TP$AEd-8U5lPC?tW#L(VEH7b z2D)UlIRVovf1inylMen{mZs_ema$Xj-NwUI39yXOSuG9#ZAKUnJ9MT=Jw)p)nnMKA zf2bd1DJA&bfjH~O(`jZ+>KB`Yl#2_0+&2PQ?RB8y+fGBaly5dG2<<;nEvb$N=b(tA z-3VG>yuU(kSm=Zw(z4B~Vp~PH_#DV~YyLTCf9$eJPA*X5N&tB}FSM{h%iC9xjhu2h z72zI<2$~#>VcUcb6kXO&TZFa(pZAdN!kYk>pIvsjEu*YR#w8=<f0^G7wX_YI#zPJqa73UMj7ETF;cMM zEAy$`Z5=OE++MRP(-IkLYb_}a9(1CYc9N^+?=?*|(c61+l z{;>k84zuwdd<&veJHPmK{noo| zA=p$*gcKf9&bX!?{lss8-#x7AV5f?+0E5$)(uS0xKTj>?vX7gOR3hqjLh)JlSNO6%eveHVz95au8dSkDf#NFjlJP3w8D2P1A8 z?q&hdn@Xl$>ifOk`!3R@>KBLur3g?E51^n-hJk_MbTgxMRjTo%c}rjmbxErjC;8@o zK1Krce-cS|{U9ZxSaP^KcBo_R^(zK2qZ+m>LfZ4I?L%~NTz%=~i*RQ%|G6$z z74T`Wv@T4q5YVSw%Y*5_o0);HTCEZbLq)KpP%O>8>{u-1RxyIwhPSyBs{;ThMe>y0 zZ@Zt_)c;I+1~YKp%%)S6kQA4N-5xHwy{Mg@1(h6Jw-`|JnQhJ6)Qrnj=uLO6SsrXi zmpYGmEv=q!-%m_Y5ffipZOeO(-`ulnHC}@9PA>@??#MVE`jY1=Q0p&bVjwO_+koNr z&N7Y_rkp2YB+Q7Ix@We*|GdsgQZuls()N-;6_;7L;nEGInoAy^JMxu+Dm+H-d&Y0> z9$>~)wUITF=0?%(b~rl+bgglAJ9{1})62b6k#zD)Bi_({1 z?3(b>!H;)?>qa&aOZn%yR?_>SnDURP5$er~Oq|edPO4aiQJy)@1_=f#u=G^?+($c$ z09hgd21)LG6%XZ<3Vew;k6%FK^fl)T%cQ-P5iF z1s_h7H-b9=pZ(hxfl2_LY;1M9kzNMez#wNl?K2zc4Lc*lKs|Zdn&%JlJYM{(iTx`K z2)$n5_qX-d4gPCMuU-Wq=*8y5bHVk|D3?rS{pGw(8<@z+7ZFGC4tWCn`O46Bn)LEp z2|AbusKym5WYfrU5hw9{bUCYp$B-EGK{AaA)k=vIlCRzF^UkS9t$?PS$eS5_e-DmN zJa~7@*KzV-$!@BeKm?3OjZ4-ao`@;QU?cOn4?#^Y)b|E<+*p*`!sQ>xiSDl)N~42# zYD)e=2>{VWcp@(Y7=Q+`Rg$wev9`sTv71T|)GGF#h1|^iTnhP~KPIkNoLhcmxA*gC zhrP`}y4ObaDKFM1@dmBSS-nRiDFOSSWU>i3VJ4P$BoLKhv&IWYi0DRsn`aRSYve^o zU+Eni6aQFdqNS|Kc@>PWEzr+87DO~YEG`BVI^r@Y+3`$AQVoiE~c_`?+Wk?oV2 zoLjZ(>0^f*rV)p@C9*!-?XRB}SN`jPM?|9Id#_CJ^-YF~~& zAZV-kjJ^>ghoHTne`@|Ee2yK{A&7u@y2k7Qho4ta(8jI;tFEjZIP?Vf;Em3lzj7z% zOX5_QgZ`&GQr<2DL&)2dXED1C_+=$p2nOmoSWfa*mzs_ht1_I^!>AC|wNj9Ia300p zvX)MNv_po$u0!6{Dviz1i8&SXpTY@}Yl4s&T!EK-jg|%wjtl^$0CjIS z`@z37pio~A6S2ZoC5c<%s-Ku%fqHFeJ6)`azcaA1xSW1iJXgIO`x~_X0JcJHp#lLA zj5n9%SkBu!`H6AJgjo$@3&EpqY;JtDwj0?s&omemxw;%Jid7-o zdro)2;@Zz>5M`91`q$3Al4GETdX0H*ZqIMcO)(i|-6Wm^1-1q#D7?dYb_hO7EOf6zR{ngIY>d(m3_MRuw`UPi(b5WRW#J)9`9h5SE^;Voq|Lg>K>g8zgX8j-1$1mNYBbjrJVakM@D{9%ypxL*uLa12@h`-ERvz4CQdl#L2g{C6NU z33W!ag^{5CnHYGVxGR^g(w(G+hV-E8br}I-sE6sEB1+ zB=8PzD4Ki`OZ}hgZN?(mG-^_O#hzci`A&w26M9gr(3sbuNSit8F)!XTZs6nn{ zV)-*$IqqOGMpRCokVthpN-|vPB_tX8?Bna|#Q5x|6-%v$_<>P-bMw}tLcQF+0^J#J zx@ctzD-eQrba2Q?PELl&W-BM<>FDbAzCL}PV(PrY4NP{9JQlVWUreseE@ofBI-ZT( zVO?KqIlK<(c+qp~C_L~E1S~~#lOxagbWz^_-KT4DIB%2l{935;ur^37&;Y%tr(R|+ zUy^P?RPM@cth0fLGzZQ@e}C2I@b@*34H@ZNca%`yuInO)drya#^Oq%3h3@WP5wPrk zwn8s3ho-F?sKb3^?=*=QF2=1VwsS5XRSBKYvC!6=$x6wy9#PTMjLlNP?BPQ!mg@E` zq$eCoJyUD?8Vnyb)-KhC(++HRiRXouj-6M!?u>GZoXgK5K+3B5M*X>(4uSkI&$SF# zQim^s)#BxA^pRtx&`g@o&@G2+ad2=@SzB3HsLx}N&KBkQPph-+^LpeXW&S_CG8a_J zWBsPQ=T5e~Qewgx@_+AiNtV!MW0(Yq}O!n&k1?`TI>p~rTzg`b7S8#tWtn}Ill>KhS z>!Of5hMpXsgBh{DyPXqnJo`^Yy(cO`Y;JDua#)y|sRmu)PzN0TbAbbu6~^H+)fuL% zY>=@E+w?g+UW@(F$KSffoJui>t!TPk#W#D3Nw4I@;WZHwYCHScNbsi9Dwpma-*L?SPO(%N6Bv+25qfOwd{{u+t?uc zdEiT00`wxXS#Qr%KPoZYp(st!UqJ^{WVr5BrT6-WZl6c$>!uv0mvf2Cp1Zv4c#1Wj ze(Ry}irSL8MY!7chcUO-=O%2j2UfFd*Ox4>J>K<7QQ!31!FNoVddkJIUi?UuaF8AK zlxsM@$MbvrbLdi`H&pg-*n}B0tu?*-1@thOixSA0A1u^N4-Zjf16SYb-i+j+@fi8Ae=%be0#42tyMhi|AfhmTcPRqc_S&}p6k{+_Jgzx0Rt zntx2~ejYa{mp2@4PY{9vo3iE{C4?XHwJSIuM!CFysF5oyez#&lUtxQSq=mZ{1R?*N zK?Z_-;;Z@^T%`SEqxC!JoFJFzFuc2$Q+a1#ab!CU6{)}I{%k*6bjm4G^cM7v>$Pl^ z)#%V6a^kK?(+LEHE!&|+vG};?C4_~)z8_nMtu=4=ne{PF%ljTYvF9AM->^*>?l#BN zY|}d4#NJVg**4--at=Wxblry5n0K>qr4=H_5nBL0);w!nX?3;YQYxR1*aKxls}1^N zm^3;kaOs`%emc(`$Pzv;IHEeRLUs>T!_E8bAizgsaUg6_$l-4;$tQw<{jNTSpbbT~ z@?{|yk|3Pyr@}|DH39*I;)H}glHV&nJ$9utmE=U$8Vd&(>DwSg3!TfZC8I2E%ftMhM?anBq(Zx^P{x>1m39JQt*i20MhGFY4q)`_QMnJ;qK{WxIi0=1B@uDM)DQ&XXjiI z2yM%EdZ+*7W&w2bjm}E2mSx92kw#myP<&oGO2Wb$0!m;tpyq8ZySzo=Is~+9Irwew zedB(xHFB`@a4gABooTN(D#Y@Iz$0DFYwnoo%c&_IW#Swp=11+YLJIxXlihTne zLi(lUz}>sUA3<=A)2`O$aDKFCry_H^^wRR3EfZdEg=lhz;t$Jy~6Qo^fG|HXu}ILtyO8r(kMz51&hq=VEv zc;e4xanw5sdr?09mH4Sx5D0q=?<+JO0tFFXr8*!R z-CUxgRKDq*Ey!1E;;mRBq43rSlx%P)#jH*Ln751H5#Q(E>2Zk8#8^EhgeU=!S^-47 z!^NXjqdIeTf|X7fmgpF@QCmS;#>TBv2YOBvUv~@eh+na~v0*-CXUOF#&@Elc$POW& zLjiFVjjd9>zywaU89Ql&NId(i>c+^Mcm99VL$qrZwUdQY?~Z5$!5f3?G%hV;Lufes zz91+FO(t9fv&Lu9ctDQBZ(;+o$!Y$FPR#+v%A}DOBJ$ZIV9l8nF%Cjm*qIuC3L z7o(&wgDMFjN8tD>j`J84odi9^lUGlsxn82ikuehFT=3moI_2u5(h`MX%~ zy{k0;`?E*4&y1&bF_TULRaG)3{Db~Ka0Y;7DaeCiBKRF8Q`-Tva1=W^OQ`rb!kh%m zYzhV6;sclibAj(K82>xyNy1;6`1-;8(yi-YL{^OQ-v9j$j0j-8ZdFE*S$YSeA<5^i z#~u4n9x!IEfmdC$q>}{g(M;zD>*FH=31Cbh?>hLiw~tafOj(L?*!>%8H1)^wMkjnM z2m&vuXgfgUZXTh$3$g!WcILtV4G zzrRm~`_`<~j6xz++7bmdtcS{!lw$ARGDYgB2&VCr?R)g=*n4wperiFx20tixXu8_O}(mz_IB_%mo zSuEn}1yWjR-R2qh3lP5}XHZ#1wC!3%datET-88fJk;uS8 zs*fbuRU)a^Hu`YjtMKi^V%nHPLa-n1_B#vMRlLaLw}*R7|Fj~3cWl=iqqsKPgcfaT z4U=54aXtg>PuxT2-0~D~PO;ky7pf9*7-emh4N=?({I4iS#q9mUivD17#(ue&L%xm& z2HWq{zaH;dVYOjwu|A{iF1JwmVfA!d`lq_s)%x}sow_IA6Qq{e7+QSOZ-Ib_35zZ zBu5eV#K1CI=5ec z|6zP1;}Bz0=hfuPAdJ+n^QuUldXnL?H@hrm8fPCSawz*YF~NAyp4%$a!fRvrWt&YO zP2T-fPRYaX{k z*TGHi)r7IFXIv0~6l2`wd{+XukN~we{hURVgg#XuTJ`aX`^s4#YNAl$2{41#^+{8p zBISg_HNOI865Jn;#BQA&bK{1od`hDnyZDD{Ln`AcRiQo7G}3l!DxJ zbrZC8e|m0<;Eb$o6}s!AYb?+9t+1=Z#b7@nUL$lIQ-#pNa6c(f?$2D?197~i|IH{* zwlMwwA?_{1qF%%9(JdPV69yEdVQ7WH0+dElM^XfVp;S7DZbVduP8n%bkYR8XW9 z7>7ndx};->^ZW?6@Bh5#!?~_=U1xvTUj%;rJnLTfTI()wH^=eQMc$VkesULazCac= z*RwHO@`wpm+8mZW#93X0Ce-Mi`)IXVP7i0+@_vaSTM^FK9y2wfAbld+?=#7BPEey@7#>Rt0$VYT<uy}mw|t_m%O8~JRbQubb@PA z1tlw{RHTO_I62^!RzEaHT58|6MHrWS60(H3@JnJ|Ac@v!Wq7lRqxLV&nBr#5V@Bs_ zE{EzQu?P;L;WoBWUyUa*+xRv8S!;&yt7l$oa(`KGy$+fpel*w{^n$ghMGDhmsmS@` z=FoM4T_NbQauF3t8ek0wrI>h_DbqduO6S>dEw9+gC(5iU#La>tplNbM)r9L5je1*J zPE)FdQ7v#HV@?H3_=AVEn7z`#ddD6G+_@XGt5Xw?I_mOM6L?@@*^)EwOUp3!;bO+T zU`90sJ+W*R$*g-D1oTB>*6rR@qrvb4ZBhekf0J^AKYN1)j8^`4v`O9Cv&DF9SDwi3)9@@)1qwss6oA_m@?UgmCSLWx@)in z4tSCfJn2!GYjl0Z3@j|lFkUN(&g*lPEh^e7$v(L zXzA0PIiemjojH&P#4ehA^&#j4jQNYll&==?~M4?Y(uX*J}{NIeJkqfyZF z!n#;Z(vjMOT^TozDZ9a@1QYlO^gX;)McT>h%%uwbH0A*<^MlnMUji14B5bP_`cX9k zPM`Vjxtv?L9uNXZHbgDC;`m49T_+W_^vH&7a6<3OHbpo_H!=L`QoX}c(4!h6g!7^w zG*Sb{Yrk;cdFLyYZdcsfMiUZXy(KYtA~E#kMT*VkXCY)pV# zzb$>Uf*w@>_a&)TdgjeoM)_`^)te2bfF_oeJU5Tz>Sh!twA?^r1aEwf(3eV~+*@wI zSvs*!f$61rVh05cti6 zsoB~8{c}?vOC1Lv=)Zn}7_6O1XId7_)zPE0Vz#Xk|A{t1)b5MX!`JTrdyf(B-4G^} z<v!J{ear0bVoJh5(fd*kHUtZ5?luB^2?4kcXnpn2*qelY3rMnio8@u>C z0D)DmCw9%@tEtTTc`|MDWw$$Nw>agQ~(!U-f`c(B9e0*ezer?X84(r|}Tw0t0wve-Fi+#gBjD9kEph-DoK=DF? zf|)DR1X00H_ww~RZ``xs%qqbMU%Qsk)}{cTCCVuir;YB6de5YfuBrKa^f=jP!badFH=< z>w8kErk?$0&U4xasJwYM$K@U_Zar_}oS1W>3Da8XAPy1^zc-nza1?FvbUvonwrrW6 z%}q>Nwh>mb)Li4M;Vx=+-auO|dliN@n;5*+^zn8w{I1JVagV>o5^W_q(_%E*=O8nF zGETwB>+-mQYR#q3W7p$nMa@6nngUX=b$ri~icR-JtGDl#keg-mkF{%4g=^Wz*6JRG zKaH3cV`TzW9}CH|a|?y>Pj>%(d2IG!*skU*1Li{W3uii)Zf5k=sR820ps9K9d?Q8b zc*&~`LE)C)Vo1j#o%n~TIEAsVkRrUhy21nMFxc*T>h8gvASAWB?FD$knC zIsxz=;kCIWA0|uTpbntPV~xcY%zMr#<9FqE!<`j3;K67rfys{m`EKP)bU);pC3iP` z)u7INOt!c#uc6p8QNSz#^;vt!u?l!?J6P3I$8Z(^E3}u& zVkncS^A~0`P|9uS)MEt<<|u4EJUl)np*?UFOs)E5U~D!qO~75!1JE&3z?H1>sjGm< z*3z=tvqIL2)Pj{RSxkO~GjLp1`)EE6Zq}8TTo$tzT+uhJN_aD>;SbW8e01bAv`cl% zo))v`>BHpjvbH+czrf9t$yLs}*VW(c^&-;RYL!abdl-uT@~(rOmU)UV_T;^0v-Jif z{06!*npP%Uhm|(;&G`lQtzRCI(W2{K8#&+odVX{093*gp>`J;LFF>gO&g_sRaVy;K zdy83Vp8iuEM0F}^F=_E#RJGSAG_kNSGzICd9f-gVQX^{Fhd*sn(c~5{(md{)s}fxh ztxronkO1zrY`=}k-S_Z=51Y%O1Wai!1F>HoCZkfezV3`=VRMutXt4Gakz1DII~Z0g zdn9=kyyvH;7Fqr=<8?WQZWXdJuNYvyCE!$s*i{iBIBIAL%$|;bE;sx_h0bhl zvn9+^@t?9R48p|UobFglS-s8R0wj2_av>t>G%W0*GEUnuTqjZ$gBj{J^NGgQQFNoywMlGDSCa{ZxQ}7L`&|;X6NY-nl?9S7afp zJLaX?x>Mhft?4azBF1YZOnisIc2bmMW?u27Y`J{B9_JmFK;yZeB@`b|=$0^Z z_ZUTTd7Tmc&5r>7VHNE_*T_Y`&uyF!tOZ7uL9IMG?R;9emw|g&jku;HwD?;QvGiVO zsb<>{jCn&KH|yYIA00q#vvcBU%8k5n!+x13Rl6=ptKcJXiQ>%pS7eCBHud(mNF* zy_4O6G!y6!U#V6KRGOAI_u7>p2<_*4z&Y2A<<#7esBsT)zhNRfvHisI0nK znj|50E8kmo_U9QLDB7I?-)M3*O-HTg09!MRa_Shvo0fmO&i^*=j=KiZFNo0;D%l zyy+Hp{8+WWc5Tu{zSoS)n|VCSdGjDWsRew-HiZD7>{H2V%1O@q| zf_lmV3fiHwxNxW+1f|56%b@;f@RR9eRTTnBIp z6*XG6K1ehjwlEyZ#DDNaeu3d8N!W-KCd!O|_vGS6-(d^aKR7FCDJHMm+t#JG<>Mj+TIJFOZTnKdg z5lhyk&1AZf%v3dE_-XqMmN8e~V@Ytn(4}^1UXY9QxBG_QtrZGKKYRfB*3~mgN=k$6B3Uf7Fc;vDyKG2cxrRf@ z^|xIlS@^OPwhp)6MNG;8*8DCqfJv=u#q1pg)j-+2f6$Llcv}K2T3>b+Q0Li{yaP6q zTK7d?mnl}mi|mgExS}>TYv@8^3JR=hm$QFjo(Gyae03#jJ>DwF>*F8>nM|AjRHi_s z8QNd%vsLtC7FGx*p?On4 zm8YkLUhv1yM-GO%-exXr$5SqVCoe0OQET4E!ZB?0+5=!)da807WeCykJO%Ur@(kd= zsqit)sE*l=noAMiDg0*KXUR^ZHq&PEA67dNAC+X@?LfZ{<>V3Dv7;jZ?T$^*;n zO6fImtJ)%QwYp#09Rcs-o*;&ST;eU88d~pd|NO*62F7*T9*pfm4nR_gH}k7?e)&Z0 zfU<4FS8)cztgw!6vYA?|Zb61cIkt3Emt%-V-)tNxa7^EnI+B6hGtR6sCU1I7e|*-D z_GeVu@@m#U4!rgtSTx81C8=bhT_S15*D~==*4H?!(n^j}b2W?1gFzEq?kQn|jQK9qXME|3V#BUL@3o z`~=O<_Bl0qgcQWeg95LP%kpKg1#ihxEV8+HCfZ&s{Lmd2lD7kfP#55SRRM3yp2H(FVC_zhAsan2Ngh*KX6E zos>mAc$>%&pvr+J)T8Dl%#D~Y+wF<(pd8gCxXadJ?_ zZPZ)qWqfyuhq|y2Kp#5jhD{MY$ukb1>c-JrCvcAMdE6p+gaMsg-QRDO{wqwU*9M!+ zwP#!X+S%19TMsDuUcl0e4;xV-67*IvmAA{7OEbi4)f_I}c^viK_B$y4dLvUbuQR&zxbd9wCu{(BzEo65 z^fUY}Fxnh=Vky-PSZh~tf7g<3it1@3>83y>Uxp3U9pLcdO z>uJheLRy$n`ecQ%c-^KY2xOJHe#jnH`*(#jOI2`&|=Z;zAUPiq2EfOi&zhEU!E?Or9pT``sPav zvfOAMMsT`4NY~JciEhnbEar*#oVj7v_9|t~wjsozA2dJ4%uu_tmxacCx+=%&1`$85 znBD@C*Jvmsd~PAlh4Ra}yi7-9CC}SssdmEaHlihJnqW~h-EUeBe3=l zPg9ToA))^_GcMhK_=}YZ03aq|dr%$mLeTE+wRpZizYC$PXel!3Ebq1D zs}@}uY8Jizk{J?L$L~0Gzi&qn2EB-QFc&#}s%P9l^`SgVrgH4J43lJZ1yU1wCUv0G zMe#4Q_8;dIP-MuYgu@4&2S8J9ZYNOsrO=A3)8uP`PiKM^x6b)EgoxPv$PFwq6;8|? z6G}W6pG*qgQ+Mz@-JO5^r5NF%@ckIDeph@q#YW#n_Xjy@Ed-YhD)b=6L=WL1oOj8F z@B3?#TZ8mb! zO9>foJ_qM*u&M%yHvnDw0_;kXe_h0D^0Uf7y9l9eveib;y=r-OR-hCoL*eDGvzusp zRTp#x&XW{WKOEX2Jk&}iJ?DqHTKyN)WKGfdDT_LmyZw1SW8B?EGkA!b?2NAHnnUZL zDf~o%KPpuZ{;MI_@;8Njl!unjl5UdXOReUr_N<=d_3I9x)qr99J+W%z)R*Xz9VBpF zD^bMmt5M`hn99wdeb3~6VPMRustCh=2C`ZeG6+uRFb4aG9v5kj%v4K3!6<4`fUckFU5$cBg2T8cbKi`NOneeF+MoE8TC4u7qL;BUZ24|9UBf zz*8^d3!2^V-@Tl_<@tN%;YgUdD*sa43;in$LM&S>8qhj(|ge)|MznzKfP1Dkwu8s%np!Z;*OS0CS zfe+eOV0>`{ix5-M^Qog3T+O&V)knaou5AxX<5!mWy%}0EDzrnDany0flkKnd8)*n8 zOn8EE4{}y1w1c|`!8$A4N8L4^vBP;xz9LxD@Nu~D3R&`Fm(v2Q;u7@Eqdph5Rt>2bFAuDZ;F*&6j1v)vZqChd!8=87Ru=`(~>9)rrWtOAsJt2~1Jx5sZ)GtyjTzV`Rk< zmq~mi!56-4qNnB+gFmv!3ZEzIc|o*HR+@)8Aw{_@AW&Q^-tAs{8l%^TdHlWIS-o>` zn?+2eLHo>v=s|PQjPwNCu-g0?!uz4C`9N79YL#3@b2LJVh&NtMDVJTH5tDAN;tZ*- z(jB+lX<-z&X%kpflb)vQO=#|(#S|%Dj4;!-7g)5$a&B*ZS1Q#IBh8oV7c*>|i}L0% z^ag}j7foSanFQGi&deae-PV@=IIRgp*oGw10Pe1IFBBuSDG|8vdkam&w7mAMR@;h# zoTV$D1DyM}`<;WIAIL6QFFloFlSS>M8@%o&JGR5*)L7u4O@hE(A`W!6yAm6Xhe{di zg?3(|lTsy1wuK{%gXz0j5FJTefR-ypt4*@Pgc*2-;a4O~S|ZLNE1c*>;dyUH?0v3- zWa*y%WE(I(g2xAlD^69+8R%0Kjo5xxB{An@g!#l9iw8pD-7ZL|vJo@Hz`y{8oayG( zKKL&n=R=`DR?m;?-$E-5T1(V~4!MdRDxRkp)4tKEXc`dEvuMQp)uiM-4H}4UmKW`p zemMc^4d7X(6`{4NX^F{eYhUl5_+1qxo#linJz;GnvCZAZ zrF!Uzz*~;?9t_uc{JxL$Hv4da=k~h>AL{ZM)ETiTLO|jrO3wDpK&pil&Own5Mvmcv zT^)n;WRW++`GR*JN!Wc{*par5l$cJN96LS>AFz+Cl#m54%*=++n_os=-~Ef9tq4q` zUscTMKiN2tw$DFw3DAnPW|;%@1pKJcSF!xPs6nmecKv z*=MNS1I2YK`U0cAC|?X|#qps832lpX@wF0rL(^I=)hISDwk z!b{@*%Imub10xD66W%flJ~CHU5EweENMLv9sfea5A(POX{B_~t9(wDpK-_TIqiAHy z)jaCc#HqGH($YOJ8oi)Yz!LLEQ$1tdV$?)DA-8;S|5&v~<- zRr6S?=J;8iJ=w$Ns0>9q*H+_IWvy}Vycjf~NKOfKi8ph(&Wf4dOP+GyYGv~; z?wOpi)#~4VG$gMM>ae}5y%b4$f`52c*jquIPNO^>Or?ULW!g$dyl$dL_5@!C<>rG=qPZldJ^lLv)&OLt@|XLpBti znGV>{D{OzUp|m8=XaF|m0NA(y!o~_zFoEWrw+Hr0&>3fiQp9guwFelL_jt5+{arX4 z!{I^$04Ec`i_>;rC$Eso2ZFJ`a{pFGS^Si94!EsLuE`lG;1P?4TKjLpl3i#`V$kRLnzEVlxkGL^2^&UGABSUiB zi{Mzfig$smIE50;b>zR({f9LAW_|2YaNd9U?hgO?KYLyiq&)7!bo?EYcnuu!TGtm) zbfg0MHvOagzJ+X$`SgVzDpDxLk&MI*@+l9!d*~l#))dV<*e;zs8q03|*C0Y3AwW2F zk0?yDftgCiH0ZlyddK7>N$qYSD=hbzv<7x*O>K_KCaSAIzo-M#6$A(vVX=Yf0QexqISjG zX)M6hL?_SE=s?x`;6;gTCoVBhS#;-G@#a$Y#Vpx+J%z`Pdkc>RX6+ z07FhLj$2bK!pxKN@_(xKl=t8OBoxd_{$Rvcmmj7w^j3Aidee)Gcqo%i3>cK}0y`IY zK(N`<3_=;9p=Si_^XB_d@o{lon=8}XL@3;6V{z2`ibwnU$7d(;5Ti!~8e%4@7uZ!N z7Z;B;r+R$?Oz15r;JIE_^g{$N5Cb=bg-=hE)3}Jn4T|NK-yG)L1ww1hZ!L$c@Mdzr zx{Ek|beq}H7IfhB>FjMkqk(zeSsd-4D@h*ie+H6usFiL#kZ`?3v?^)Mky$fPsCSti zN=usY=s-RwRZxWev^+f|R{)Gqo*zo)kxY>$U})3r z{Y@K;bHy38AvaHl4-2;(#wbXV^)5) zE;sd~k)J^@j=)B!?i`b5*e?(QaPqlqe{n|K>zuyDM&9J*)gs__(tgfAsEsM3JROwL z?Dt%c=zO}|R&t9{!h8*9dV_`M$geQ7n!@zbnA@eRTczC04D5L<|6yTz`=I-MF8Bv+J`dDtg28n zu$O#2!+u7Bk9tM*knUW(M< z$5l)AZn49P=5^*OXJA;zg5^H_IIvf$@36N^s>9NR(|YqHX9P`+aJ)g(8yp@W1gK-% z^w}8*V({>RH7{RAp^i;e44lF?s7#UA&X(CVpZLYx&I!6|vv02h>+s9tV~yjhU=}s7 zC)G}8PTqnK->UIdn+FMglTp=mKu}6ttt?67A8Y)1OGSwV1nc=X9&M~GFVa^Q!`|&} zjT+_*YyAK+fUUyH23$78!IT(TvBn%u{=JVvBMPjPN)^#yc)bQCq}U~PITF}#rC>+2 z|9eLy17|26e8uajlrq?N{QmXDj6YI827Qt6=5C2&CKD-T$+NT@L0rk+?Nr#J~mY-qUM!< z*yls+0xcf0Df#f7YEkdhlGA(&1VWFJ=@~w0No^)UlcL=oKx4g-ZJBj+h;M5mXZZvU zd<)#ATV@e7`=1L$rljPMp5PsBm)L!xQ@m+nd*_V!GLyx|IfJ_+VRW=2@?kdfnxzA7 zr9Qi#YpdVh>~0n*TRusGXJs|L0!4HFazW(i%U02|-;DhO2KJ`ODZ*@P7euGZ2NRj6 zK?+aKTcvGPzBMwR6-#{iWV(JunCH6eN-@rpSBT;`;FMmGF8k#I!bfZ39Ie7kT-Bk+5qOnQ=toDm1;e z3vgMN--IzbYoGncmxqK3Iwh0&Q_oWY1#`9ZZ`Mjx3g(kF&e8>Zt4p7im2W0*2ZV`+ zjSNUmE-K&0TK0@$-EqRrQ&b<;rv(tISLxF|Zz{#PTD8`nhqYU9@HRZ#Q-|tJ zfXv%Jw<2YR^aKE}bp(jH)3_p@P^CsK{d$JV8Ydq;-~=XB=cOuQ&-q>hCuAy@Z2#9O z1@5J$F3(`f^l~tZN!7%vu-PvgL)(Ua$y-?$u^G1-n5MT8ZZ$vwI8(H8(3j9|L#TOFm!cLMr@Qm4 z^k#(RI8YLuZga#>e){E+nutWLrWf1GCW9Q?Z2Y7sh$vxzh>{`mN`?K98*jpa`70K= z3o*lgz6jrI5PJzkT@Q~vbzFI${RM@99lP;s_PuXul~z*jZd<8d0jDxM$=2^TefO43 z$6TNAJ=xh7oy)pjrqmHEv8Xs-;}wu$7C}(6VPd1WfE+LF4E*gg{Z`k)-4ONQ{v)1i zNA7;qqp-eMxJTLK?^8BXtOBGUGcZHQc5p^8y|#8JQ~Lnh>G#S7(PWmC2&H>+9FTy~ zu~HDjS?B2$JMC*dw(~odg_yCq_{<=qw`?k-l<(?L>AaIMLY(lzLXBYF`uQc?Cg~{x zw$#A9t7I762dS^LtX+9sA4>tI!w%%_E=$*MTsRleS~09klttUc%uW`~MQ@nBN} zI_@Nj;E_X}ol!i=9Ed0Nf`7EHIkMYPo@3A;XpP|E3yHvtV_{`=hbbbI3QgAsCGjee zTl&C0UkYVAOcc|G&KNn~N?y9<4XtF{?br1}Bux_9@c3$O)T)QewkPsaha+*|2fTkiS&Y{%tSmMLY`=9# zX__P3h8O3K+@~xPlLgn6lD9AFb>!8GVF5#vbz2if37qA3=_B$X%xX@^(T=*W8N`aN zgQ4&Kkb5}_ne?f339@)9bgaPAc>7v|hWAjl$6EL*tgT0QU^GB^uf)XH1_btex!@_9q8CxvJF`Dq3X z1qFZFb3$vh@gZjW=?rU1!thBd2RbaUlQkgtDX48)`CGGm zjZ~yg9H>Kn1%sUlb^mr(r9-ER_+|3S&%qMvjrxloWP_4BO>?Cb*v#j_<5pJYXMlGF zN^gO}hS>-5Cgtsa7h$G_?j(uzP%nZpt?4!~vJW9I)96r z1H^;={(jUuQr#ds506TLt_rJi0vPvsx<6s+(RoB`t2VdSMp1{qC$fYJ{Lx6upOV&4fwh}H)$F@!!%?| zzA4WYJ5>9%i{C*p5yPfLW9R$O*)FyL19d0@In)qfW(ihh_7ha9$z%_XNocN zjw|4&8zr@73TeGrFZZZ*!X}*jSYtMC#$x#UP*+Pe#yrflKPAvs!i|EG#-b8B!x)T;IWm{h8g(xYE-bW_?Sr7s;x}_$(R=r)fLI)b zWZ|2P0s-Hxjnk)sVPLw`=V_V<=YFv z3Vx3<>8+%&%C52pa4Z=;R0Dld?ouT27pbFeeXDmpm=8D>5`?Mjng?0$Hvn42qRA~R zzXp=sVyIV0ex8gUI@<)aQ_${w_Jx#QkgFP5h_g&e#vdP%)uQM9K5*b{DX}2eRxZ;2T-d%Mz!XN9 zeBWtJ9!v^^R_lttQxB*z=oJ3Xe!Kw3b^5;mt?C7E`!kQxuKTKWP zdj{ZD_?!I!iw~3$%rbotCUlX(<9`G!v4A_`{rO3ZRLb(9p*(fpQ2vwjFeZq*`ye{^ zUc}1EN@na;(=45?d-zb&vo%mw8VG}*SN8F#y}J_U&!Jyr76J0p9hwvlMtk?%Hu8Zo@%cc%k+6G(j-Gd2?!SBoZ+4jPcmkNn1SZ~pvm!qWlD_s;WeWD5 z#%_xl@?A^G*+2US1wl9|78(a%W4$it8|8u%*Qw z@;5xWvhP3|@W`z6caM*);=Sl^sJl=7;EQ8}6DgQ-H-la-VusT;{5v3mmU^=V?yk-% zI#E&=dh*r{Ci8pz1hWXLhgWWsv?~6(-~Cc6E~C~?yT;S)SH~g`7+`rRA;WVjKwf?y zW=eekCD2iZOK~TC;nYxNISndHNYNs1pHTew#>H);nShxlsf!Vew_lrh6Xs5Zh`g}a z*WEqAawas9MnJU4kVKDesy86rYsjYyiWHv@ z2(vH3lP$mJEUcRL!CZ91M?kp93a4V5^;_V4z>cUZBw&yXubFACt1wa>C|_@bK83)> zKb_*_zaST+6-MM zhJNxX5OdwS15Dm}g(pN-#3mj&S0+oRe>44U>IXkqj?+MX2iE@n@gr@XR6X5->KKHI_@l$B=nwV zvx9<%=_%mvK~rI=C9&il_A!X?b-v;1q^mHwzhECB{}myXDZ4e;x|W@6?adm!1jvZ2 zawXGIG@}_c=@gWmScQ=4y!BVPhoa^Q!AP!W8)UaBN#;Hlck4hyl)<^=;uc~~94Nxa z-BryPNt>YMYWhpZ-&0pPNN=+Z@4EkZTrq5$w-EEX*;t{bRk57!UhRS{P7SIio>D{p z^i$xAqLWG`|Ldq9sd=7Z;=kGTob;X>kMlp(0_uGATF(iid571-&yq_W4M~rG@xyr$ z4OE=QNWj>be-#cgPWMXHayHcyu-!o>N%cPF-CVMj3R_QcRmxwKrT4OJS36p^x)tzP zfvuZ1$9$hlr6`gz4-DqoR4&;#EgT)sx@&rk%K}6_?`7lvDSU6=2Al7o_lV0@^$&>9 zcqNm@DYY6Z05DE0MJ6xZ!!Y_cHD23+Ez{%|^IgV+po%}^K)PbpI+gZ%lbsc|u*zVo z31}k~z7fFMwbHu(Ejy0}3hQqhM;oCo|x4*k{5FToayn!#PWTPH>F&kSBB^RZ{L z{g7hK1uBv=cDDd?by%OPx`PH3x`$z>(r2AzYE;XaNLH*Q;r(DDMO-*vOb>A*e^&h8 z`Lo!ypmyf&yAu7#)n@$~&u4*wpD9X1Q#W7>sr8D<(NgrlMH{8g^`EKwu@v3$vtU58 z*Uvh-m-%%Akul~9z^nw3II}&K?Zocjgywl+$DDh$KC5n$LFCoZrh(Ob;#ikd$TWxK z9`&01xKuSd;Z9f8=5SyLp2Kplh_9~OcKy0Vy{F09MLp6}j>e5rZKk4bCEJp`4HkfW z2si8fE=?YXC(SRUIvu{q;ChSMfH^OW$g;_?UVwQtE#9k+(@Rn;1PT+XoHygyeYlmF z+@Pl{fM%aK!Hj(B4LpP>>LLo!*`hZ=CSmV>6cdmoqDH#~mwnvw5y#%D&O(|K_ z8EMZwi|bYHzrhz2ILq?m7o5`O`iqWl6JzZy3r|H9A9J=&u!m_xg-P0A7^mHV8V5c+ z_QA>|DFviA2O``S%Mu1(T%KH%ejjZ|pza!BP5~L&$T_e^$%&|V8 z53;j;kSiQh0fRc5e-18n3NX$OVYGuf_M@arUPOH@pEbYJGS8)Dfu*_NQGc)nV6kxq ztN6gWAH%Y!*Rwir8qWMB*Sy~cK3dx4C|I4GP)PY*=mI$*U9 zq8QT;#uGeo*m=#FaG>Xi^s((J_>{SEsPXS`c6QFvE;P9@%Kpc30fi${r*N$_1<1a|bL=Rtp?) zbKu*zS3oPT{vpbrW?*2TjU+jX>JSdG{s4ZEcBy*fefHr^vxzJ!##e#q7uU_LoyMr= zGa%m<$NoLCt)Ci@=+fVuwP^$(jo`oR3(AGhWu26Me48DZDiNJm-a4~T1(KzXsr)}c zIgm-@aPt!y*7@AiDoT6C)6)Tl9oX1$SLdz&O6d zSPE6l<@>`hWt|#_w9kRDyl|usLZTzT8|F074Uqt@-RrI`*8t`XYIK8Ea1QUW(LC zSfQE0YSHB*(g_&LHlyqlSLhy{IDiCF?;-SJ3R*sEVDnjMx{{6~3n+j(y}0k0lX(#~ z1V`;c74;+x#}o}_WzjoH%E z(>vEpSb2p8eMWB)1}VTk*!c9T?*O`Ib>iBsGad}zQ=$$F5;z-1ldFS>{rnmiG zd$W}ISSlCX9Y5Udi#I{KFU#M^PF*Q60ysxqAne9!Nr#Ux+6QaLZNs6iSp}mx0f>rj zs=NSAyzNVGTF83S;$9xcjt7HeSI>VoT?n2?CLxP1@*nB9$-8Jlk=G5E=>M{=2#sv8 z+ZZ!vb2{V@^i~bhLom~|R8(|;kLk>hd8;sr!_~?!AG8E66_B2|c&LYP=w1c^5z0`l zU5D@|0oA7~qXT$eGZZO^DjKHoZ)z-GmyW@>gP~SFy_LZCj~bqZm#ZsV zT3SvpFO~^V17Qp1P}nj|$&-VjCA-WIK+?7UK^ce^t_3tRl2b3_r;V&m4vsaBg4UM0 zch}_f<|RRKTBQI0>xsXdZ(LJSHdMJVY(nfvh+}p()a!0sT-E=bQTKcO;$e)Llr?zN z#iL5rlo^&JZo4Fb35Qw$plVBwn-h8OCXq7>(BnPs1iL4eT8dgSB%80~BJZ}hlKnGA z?6B;%$~qLBz6a`stDNAz#{mdRrZ%o!Zz;r9g};3|~Kl5AuQImiJtQ+UGd z^p}tAEYwA7LBGLM@^C=yO12N*%>HcVU`Qn-gb++{JVvqsg&li%n*FCWdHjFR5B?7a z>`k9HIqb9kzY~i13J-Z+RVoP-^KokCS#j-4PDhM~1NxZpPGUSjrBX%!uRWu?Ko7!& z|G*A8@IjsXseTQRCcyS$Du4J75MKZ|wFK?Gf)iZp)h><0{9~xQ_YnMED4G9T=@cul zS{8)xY#*)>fLBli<_i^>5HWDf3(#8)9P{he$p<+dnIejRNk)#4Uk7pUC?e)FIVcE8N-71nNhbH#@4n7Z|T#@qVjLFJvW#I1DQX-Y%TPAYSMvjZs@ zry06{(yqEFF)`8Ndycm9#^Ds6$5P(l<=J3j&;dci&?EOh3u_-*rnv#lu#3Y4se-U{ zD8^VL(V*NuZE9}LnPy$%MfjoE=6?(W*jw-v4`d_RnxcA5pp?gnOl7=5MRMI{e|=Gl z9)^#Y1|y1Oow#m^slyLu=uHY2XJ+zV$_m49(&^Y@2#)}c-qpDH@j-g3(|;Q2q1Uu=Fo!=mL!t{;(o`QsbLl2rTw z<)sf&Vutslm7WA<@UY6hCk^GJS~Ckh=69^semEHOK>jG>^I1Xm%7Ut6&#=G3A|{8F zvK)vt)m^h5L}#a!Qim~&Eg^UGF73FOEk5_6(<5E&_EI=vdu=WE}EtdIX8A!nt`mb!$g-CAtUdi9EJWQKm=hpuh)sFzD4Czi7QVdBvX zGAXx7t;mPdKyu8YIvXDpj(4AZQBtCpgq#az9*UzmGsgAzP`iv-S)vCQ8ma4|J! z@H6vRe(7uOkdrW}e~y5t@03R*Jx|J>E`Y2>lc(t8Kke^lKec<}+W6Kl5N4d&t+Omz5xJ@ut#=XNO15A^E_%Gw3m&dG4a7t5=5+%HM7y zR_w}oPlpq_oy{W8u7ygCl2g`S6vfb1UP1iqs6D}as>Gg*_C&MVld?%RtMaBvt*k@e1xfnrOodj zHq#V2RM+IFl4C1A+608nK2L#zn+V-Wj2TAmPPY} zK}Y74!pDD-qJ?+kzhvwkBuk5RXu8?)d^_z&xlmU9>NN^>1=i?)q^s2rtXPcCRa+*49=z-K-r!@BhEthT2Y-uF>OHqAT6*jL!#A5N^ZNI^?!MaW63VT9Wz|z6lk1KS>5bboeWjwe zrX%<}5P2`GOL7DJsR9xN*}@}E^V5I+N=oF>3kb~agB4s?7T&Vz#atg(=#pj)c6Ygv z%i-%g84o=C-q?MMd!B1a)k-xi8w0Ph43Y<5h`B5?`36#hd!WTg&m_P1DMA0UQ%-r{ z=UNgQ{#$&a3Hc(&y)+~97Hz~mM{WLHxeM=!G8^wTdZe}V$NE#Ru##GxQ-ovsM4UID zL5sxRO7U2#pTxF*CrnPOX9he@?mM{e7l%JA5-4hPPM1wQd4ea!nHpEI(jBG5D5Z2s zi=dkfq;_3LRw)%28FOO}a#rOWBQ*Rk; zRlVGO#5){WMEX<4wkFtC-sjNWCWv}9k>Ycw>5fh(5`To@$N2l=qAfiv7;qAuD*1ZH z)x!{8UzY8g*uvSQD94}guRVK*et*VSReI~i(2l&nc=RkHeIxOci*i3^p1!MAF~$JKha5> zP}&@34vjz7LTQOOcyZ67r;$7_ZZw#hSD}H^_3B1gY@DWUB;*-UEVZq4q4J6M*=6}) z7#rndzU*{wAwx8GdGUzE!loST4k`t|R9;LjHOgzhsAHK+tbILX!y5l|hEQmo>uIMB zvs14~Jknn~YT>r=)CXz$Xmav6XU$Vv2YJ7`mA*>fnp@@XcJ-&IhGhvU{_7d`mc6G% ze67aXN5=`}b(@iOVyUR`1b^;IG%Lvh$5+Cf-~OX zdze6z59zn9|L|;Z5dOBotNEyF*I$@I<55SZPZGb{kz?Jb-ofPjw0jffCX*BU?oU&h zP&Yx3QSD1WM1=&>Z2vejVgRjwg=NbP!JCULPsA~G>r*;q*P6Y#q=+&tBo0!^y-?B{ zLBU-K`r{wVN{+1mF2+!_I@WeQZ^p+JhGvK(Y%OuK2Ug-_LQ6%baKmp)c1OVMPX-Io zBLoi9N(aNNf7N9jvDy2;$8|;CAy`{&@qM&WRyndxed6hlP#IFM<>yF{6pJ5JimJ~O zUJK%jDGuUGyV8+|>+haII)3AZVVWfL)#$5bIm07$5z2LYX2fU2OPFw|zh;+R(8WfX z)`uqOj5tQm zZoy)CgA_M8kvCsV%H2mR<&f}r=UPf-PgdFvF7ne2~vL!{zbuW5GJMjW4x~j)5z+v01J1Oq}~wzn^QC-+3iT4N-kU zDO8bo7FztH*ZC)XA17JGg>mcn`%0c0L7s}$+D7+`4$U#`kObQ`HkOIh+z$I3IQk>P z)>uztV@xBYm{Vr1V_JYX>Yb8fpCeqW%YgpH2$}hH)fW9nhB^!pW%8oRd{p*xn^xN? z%<;WRXkSm&zF~tJV#qz^C&BXAjWdIf?b$fUXX>3m7`B$Se)%H|^dk*u+57FH?2BlH685sz%W(EV`FyGL$8ww%qm!Yb0#}ANX}2nIt&qA z>0#GX-|gBMye69?P>rG$T1jwj#2S)w>O4^`4C``DHNzN3@R63*898XItY794e%D%g z+_I-s>p_9V&w)}CwY87m$m6ed{74W5P~|H4c}EK=y5NrXNR+ikb8RJ7Q)!xx0t^|K z`}` ze9hz~>9?1c(L(&j-1s@am5FQtHPUR{K!LyTY16rR+Y3TF?({l%bewnJTXp0GTIFh+ z8B+PT{&k_kIcu}a)lRMQc_CRQd2^3GBR3uIl{pILF@gIm3wg|s5yJK3g_Esq){peK zTQCwVr*)UrV{;%zGl|N%NWXp5YrFKLFo^!Lki3(vn7a3U9 zORS6{ea0*dF3iSs#H4sCSnUQ}S?VK*(J%{a8Fqcqm3@(|79)f%bl!ISfe_0M4oQ0Zcz@0Oj&* zeq68=8I{bJ;@pEwnyJzD0v3+uno#f*I&O(HmGzUu8M*9--^t#k^(f6@>0T}Q-G|m#VSB;?3(3-OXUeA4&_}?MJ=h`8Ul2DoQhax~ zotk$Wuix}^nrSq#?$QaZLx|HAq8{cm!)yuN2eWElwl)gl#s(CPCYQeLA{2yZ>a_I-FOix~$YtP(pfgV1>4ZME-l9FD%{DP}` ztaiTX;1585$m4?(pJJW{T&EbMQ8iaq{7Z}HHwq{k1A*d>GBq2u=GdkY`|b@4s8uq=F)Y&|_JPad|i^XvWK z()+?BD4()2n?@nzrxgbub~Fqy6fW*d?NfGD!^Q~a*7jwZt*GRBcKs+Es^h`k^VrQM zvRN+*g2-@!XX``UcwZgHQ24yUasYGCcwwP3Pe?7C*x5ffJ64&hiBPQ|%My{(%Rv(` zx%uMWTJ?2>2`eo!s^S&CG%AJac3IVsC4Pa#M&b?~HM_R@7BVT9qMT*bCQ(lfNwYtz zkyI2}(VdCRYF%o0b94DclTeq<$f)wNUV1z#eD2F@Uxx^HD6V1X`V-xMumyazjfJGI z-^Ixo$Nq&ECvjqBXW4bA0O^ucoa^vFSuZ>ORzzUA|J)mWJuZTLb`h6qsb`LJZ* zwacn*EDsbg21|p~4s=F&X6xba!4|f=yr|M|=gcA&k?vo<7v3FX@AO_*T3vK4Iqn%; z)2w>KrScB9(`Kxu?*%F6n2T*BDw5`x@65W^^V>Dug>a=cwQfDHLaKWo6BGH^NET(wmz*ixp8Bal5d@$AfAR5F&6w6Z)K z>h7fS+7A2N#KT#8;2qWUnD{>tz8}Dr%JD}WJJC6t(?&Y#OJgYh64i@8O|#WEW;^Cw zu)7heE&H4Ja7GP<;YIyaaKF6_3FxAzCm=Tb*^_4Hmb-yZRPMWOvDh>QMDi*KIMp5f zkrhMsQ(UcuX2qwvY&uHa55$*jKpn1Ws|L#G1==kyv<``QL1ya6<5uxbD&}p|V_bt9 zl=lS~gNN#qt?ml;kFxq%)VWe8i;p3asTG?Zgen9vX5)N2T;$;^R2#z9Q;=v=@0T84 zciBM)8sE_EdxKxSrsP1i@xdW}RYkk^<(v!etaA3?sNq^Q@mvG+5H>}%FMb%s zN>ZMq&P3Wx?Cf2ODq{k70)*?StPK8u%-p1kY5Hts$|E3CW@W$_J=nkO$7$O~yn?td z>oE+6Uke}*1xEySt!nfWCNF&^ST*BE{hTgGRMejQ zavxb0@{8~K#xE0Sz-rya7|wsg1^O(%-Vx07K#lGJjd0b5ydoQSiT;iNNHVvS6*PdY zgY*suYPAnqF*UBLV!|qi`9%MTrTSqAH!I1rt_pNHN~&3dhr3mrbi)Gn#)9gHAG9K5 zpWWp4N5h1L{}V329>Kz?)wf(I3o8X`*43BpyPPv~B-or4LBX3r5J2eLzMnP+t-=KRPfCjdnQXIHH@fHb9`g8UO{OdrE) zo%%TyLpDTT2t#rAo=M$=Hv?3Wkd$-X3C6GeCvcQtC6x%(y37mt&D;WSFHnlkEthBE ztET1rT#^V^YchEmS)KrVvp;SvBTd6j=D)EM{g`^_*}vTTy(*7<@~ zfW6WG))svBFa#=<3px=mtDTCI0eZ8s{Egl~ubq6oMYVpb_kD|95|ymHU6gl~B^dF7 zFGZAr=epmI7N!BTrTzVZxUknS4?@bX%pdAYj-IZon6Fx$0yN@z$T;bFmPtZ-rJtJE z`s~vO8k6h24um%!n!B9yaw#xDMk`xNFAIG(&Vn>HsyjNd{MGd;d*`%E#jY}KGrci?OK_t)FEee0?1i%r}(Ep{KoTc!-= znIQuYOO*%vVVL+3(Vde9gbnZLIG2VmPJ+V&;nS*q-#_lj6!-ew%&WJqnY&r+X)^z@ z)^i9|;#b#iIuFdpQhoemVH*oo`+71YT9Y`^<>vcJuICctwU@_xrk5A-hQ*exEsO8E zUAw+hec^aZIm4>uh>ADFN{f8oaY;`S&Ds?jWM8yEa ziV6}sY)`FSw4;WHRllkWYAt^gy8xh;JsbF&-(S7(=y_7NrT zFJe})w+-~{%YU=VUoiOmbaJ|cZ;%>V0r`f^qr?rJ73Qe_UE`KQL|+In^38<0FGrSpXl>+L&14QumDyP&oh&^5z6RWtzYvp*&u>yx>Nc}u1Sy`(?f8aH z_E>l&N6tVOqdbvPi`0w7-t1;KKJ*=YGkWx-9Rc|{3HemDU*tw_c^2jA?8rNJHEba7fE4Q~mPT{2XUOtV7_(7Ob3r8@A%`m^7pW6tQm==)4W8rvoFv>^~9a zf))oyZ{c$evKDfiTNT>J+A|S%*cq!3(MtVaJvz$^Qd_@zbE8VtxTZAt29XTOwn?tq z121I8Oa1qQ%%?he?-N15kRBXL! z<~F591Has%MeElMrA0n>2tVW~8q?(6Z+gcB1#6$AcT`aDiP@9CjuT^jy}`IPdx>y%se4spe@xenwwz8f_wviaYCr}`iGwBFyDS)EK! z|JTyP^RefVPXSKkyS*2b+v2MzcrS#Ton%u!`K#Q4VgjzgBVU|KedJ(gdGfT7PW>^! zgdBNVZqV0Nz^5mUX(dnnPE+rNJTD3jM!ZrI?Tyd$>C`g;(d?xk_tHg6BWUfO^5^)}x{ue^z`ZWGke8N? zv}SA&fUD8@Y`-mT$$%#BksGol2K3O?GlsmGl4m|yB@7~-&P@z|LxbakF%yHT}*4aF(0i@9^eI5YMw&v+OJ+ z68Gyab29oShvN2U(EsgN5j8yLu`2gWjvK1!@8B9;q^K| zRrg9Y)1Xq30RK3~Pu$sVv6wyuO_#i6f988|vmFtq0=8H(jj1*Rk+EJ`e2@abLTxm( zeW7%bdw@tWp5~)a4A|NO>5udDIAZyWVUJ}$+p$^mfJrZi!lBqJqT|erqx>|qVZL^7 zTV}d7*j3N*t{5IPBaMpf*|ydWaFqfKBl*oxhUEfa%-Ha|5*p?CuvmdAil&4Ure=%N1?Wlu@_7c*wt z2$$}FWc2t>`aYH?v~#d2!St|gnc$mdAS_x96@A5EUni0;0x9{2k`3c(M{VGax9NSJ( zFN?kqxg=A!UK0$iS4(2C(^zs*9LZoGcK?fJnB^4kqJZFF!Tt2fwSi|wABrr3nHmGVpPs+IZ+joQ7C&1|8+C$e2g-ThscLwZTKCOD6* zH8+r-4zC%9P5Gpml}@KTuO#~4>pmS3D6TH|$00{OKq3!AZ3N`8z%#`$UGlur(A@h4 z6Moh7zLW*NVjk*DAH}78t?u^kV@hErc3u;xk11jp-9s1BHb!Z|u1A23ruTXhe}pkF03HGH z41gQ#;`6o@$SaVU?_&x4^lY4G29jIKY9T^`5wV2f*%2_hw;!ZMOi=_n0*2&A{Sp;6lO z;TmHsXREx(U-^n zFp@XW$lKG*8v96{)^_v}8a6cS-|3FynN6%NJdvERf z!Myc@;osM*%PCe}#mX{S$NuTY(K9qW5X?*K&e!XM8DhIy(&>JG1o*7Ysl&dkXm5a# zt3T>zMNWs9NHa}v!ih;uf!RX`d zZz53hKma8h!$~*ODS*)dS40Jw8P9;g{{{XL1$X@^e literal 0 HcmV?d00001 diff --git a/Lombiq.JsonEditor/Lombiq.JsonEditor.csproj b/Lombiq.JsonEditor/Lombiq.JsonEditor.csproj index 39adec2..7e95f5b 100644 --- a/Lombiq.JsonEditor/Lombiq.JsonEditor.csproj +++ b/Lombiq.JsonEditor/Lombiq.JsonEditor.csproj @@ -22,6 +22,7 @@ + diff --git a/Readme.md b/Readme.md index 8d2bcd7..1b93162 100644 --- a/Readme.md +++ b/Readme.md @@ -10,6 +10,8 @@ Do you want to quickly try out this project and see it in action? Check it out i ## Documentation +### JSON editor + You can use the JSON editor either as a content field by adding a _Json Field_ to your content type, or by invoking the "JsonEditor" shape with the below tag helper: ```html @@ -37,6 +39,18 @@ The properties are: All attributes are optional. If neither content nor json is set, an empty object is taken as the content. +### JSON content editor + +The module also provides an editor for content items. This can be used to directly edit a content item as JSON data. This tool can be useful to inspect how the content item is serialized in the YesSql database without directly accessing the database or exporting the content item via deployment. It can also be used to edit properties that currently don't have an editor. + +When the module is enabled, a new _Edit as JSON_ entry is added to the actions dropdown in the admin dashboard's content item listing: +![actions menu](Docs/actions-menu.png) + +Clicking on it encodes the content item as JSON and displays it in the JSON editor: +![JSON content editor](Docs/content-editor.png) + +This still requires edit permission to the content item, so the security is the same as the regular content item editor. Clicking _Publish_ deserializes the received JSON into a ContentItem and saves it into the database. + ## Dependencies This module has the following dependencies: From 3a9687b5ccef83904637a9c9b45b868af9122668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 5 Dec 2023 04:13:05 +0100 Subject: [PATCH 10/32] Update Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Zoltán Lehóczky --- .../Drivers/EditJsonActionsMenuContentDisplayDriver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs b/Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs index bbbee21..89f1339 100644 --- a/Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs +++ b/Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs @@ -9,6 +9,6 @@ namespace Lombiq.JsonEditor.Drivers; public class EditJsonActionsMenuContentDisplayDriver : ContentDisplayDriver { public override IDisplayResult Display(ContentItem model, IUpdateModel updater) => - Initialize("Content_EditJsonActions", a => a.ContentItem = model.ContentItem) + Initialize("Content_EditJsonActions", viewModel => viewModel.ContentItem = model.ContentItem) .Location("ActionsMenu:after"); } From 40665db00e7d63db92137e80a89878f41eda09b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 5 Dec 2023 04:26:14 +0100 Subject: [PATCH 11/32] Fix readme. --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 1b93162..24ed137 100644 --- a/Readme.md +++ b/Readme.md @@ -49,7 +49,7 @@ When the module is enabled, a new _Edit as JSON_ entry is added to the actions d Clicking on it encodes the content item as JSON and displays it in the JSON editor: ![JSON content editor](Docs/content-editor.png) -This still requires edit permission to the content item, so the security is the same as the regular content item editor. Clicking _Publish_ deserializes the received JSON into a ContentItem and saves it into the database. +This still requires edit permission to the content item, so the security is the same as the regular content item editor. Clicking _Publish_ deserializes the received JSON into a ContentItem and publishes it. ## Dependencies From 0cf11d435b134884e9eea177853d5054b8e7122f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 5 Dec 2023 04:34:45 +0100 Subject: [PATCH 12/32] Add warning note. --- Lombiq.JsonEditor/Views/Admin/Edit.cshtml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Lombiq.JsonEditor/Views/Admin/Edit.cshtml b/Lombiq.JsonEditor/Views/Admin/Edit.cshtml index e15e174..d268bc6 100644 --- a/Lombiq.JsonEditor/Views/Admin/Edit.cshtml +++ b/Lombiq.JsonEditor/Views/Admin/Edit.cshtml @@ -1,8 +1,15 @@ -@model Lombiq.JsonEditor.ViewModels.EditContentItemViewModel +@using Microsoft.AspNetCore.Html +@model Lombiq.JsonEditor.ViewModels.EditContentItemViewModel @{ var returnUrl = Context.Request.Query["returnUrl"]; + + var warning = new HtmlString(" ").Join( + T["Be careful while editing a content item as any typo can lead to a loss of functionality."], + T["The submitted JSON will be deserialized and published so a properties may be altered or regenerated at that step."]); } +

@warning

+
@Html.AntiForgeryToken() From df79fd34707c42f3aa05921fcbf08b9591008c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 5 Dec 2023 05:32:33 +0100 Subject: [PATCH 13/32] Make the title show up. --- Lombiq.JsonEditor/Controllers/AdminController.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index ec314a9..02772b4 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -2,12 +2,14 @@ using Lombiq.JsonEditor.ViewModels; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Extensions.Localization; using Newtonsoft.Json; using OrchardCore.ContentManagement; using OrchardCore.Contents; using OrchardCore.DisplayManagement; using OrchardCore.DisplayManagement.Layout; +using OrchardCore.DisplayManagement.Title; using OrchardCore.Title.ViewModels; using System.Threading.Tasks; using YesSql; @@ -19,6 +21,7 @@ public class AdminController : Controller private readonly IAuthorizationService _authorizationService; private readonly IContentManager _contentManager; private readonly ILayoutAccessor _layoutAccessor; + private readonly IPageTitleBuilder _pageTitleBuilder; private readonly ISession _session; private readonly IShapeFactory _shapeFactory; private readonly IStringLocalizer T; @@ -27,6 +30,7 @@ public AdminController( IAuthorizationService authorizationService, IContentManager contentManager, ILayoutAccessor layoutAccessor, + IPageTitleBuilder pageTitleBuilder, ISession session, IShapeFactory shapeFactory, IStringLocalizer stringLocalizer) @@ -34,6 +38,7 @@ public AdminController( _authorizationService = authorizationService; _contentManager = contentManager; _layoutAccessor = layoutAccessor; + _pageTitleBuilder = pageTitleBuilder; _session = session; _shapeFactory = shapeFactory; T = stringLocalizer; @@ -48,9 +53,11 @@ await _contentManager.GetAsync(contentItemId, VersionOptions.Latest) is not { } return NotFound(); } + var title = T["Edit {0} as JSON", contentItem.ContentType].Value; + _pageTitleBuilder.AddSegment(new StringHtmlContent(title)); var titleShape = await _shapeFactory.CreateAsync("TitlePart", model => { - model.Title = T["Edit {0} as JSON", contentItem.ContentType]; + model.Title = title; model.ContentItem = contentItem; }); await _layoutAccessor.AddShapeToZoneAsync("Title", titleShape); From 3735468fa0b3a7ed0b9120076af408195211fe91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 5 Dec 2023 05:33:09 +0100 Subject: [PATCH 14/32] Menu item only if authorized. --- ...EditJsonActionsMenuContentDisplayDriver.cs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs b/Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs index 89f1339..e744842 100644 --- a/Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs +++ b/Lombiq.JsonEditor/Drivers/EditJsonActionsMenuContentDisplayDriver.cs @@ -1,14 +1,30 @@ -using OrchardCore.ContentManagement; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using OrchardCore.ContentManagement; using OrchardCore.ContentManagement.Display.ContentDisplay; using OrchardCore.ContentManagement.Display.ViewModels; -using OrchardCore.DisplayManagement.ModelBinding; +using OrchardCore.Contents; +using OrchardCore.DisplayManagement.Handlers; using OrchardCore.DisplayManagement.Views; +using System.Threading.Tasks; namespace Lombiq.JsonEditor.Drivers; public class EditJsonActionsMenuContentDisplayDriver : ContentDisplayDriver { - public override IDisplayResult Display(ContentItem model, IUpdateModel updater) => - Initialize("Content_EditJsonActions", viewModel => viewModel.ContentItem = model.ContentItem) - .Location("ActionsMenu:after"); + private readonly IAuthorizationService _authorizationService; + private readonly IHttpContextAccessor _hca; + + public EditJsonActionsMenuContentDisplayDriver(IAuthorizationService authorizationService, IHttpContextAccessor hca) + { + _authorizationService = authorizationService; + _hca = hca; + } + + public override async Task DisplayAsync(ContentItem model, BuildDisplayContext context) => + await _authorizationService.AuthorizeAsync(_hca.HttpContext?.User, CommonPermissions.EditContent, model) + ? Initialize("Content_EditJsonActions", viewModel => + viewModel.ContentItem = model.ContentItem) + .Location("ActionsMenu:after") + : null; } From daf2434f95dd54457f6ad13f07caa148fb556242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 5 Dec 2023 05:37:16 +0100 Subject: [PATCH 15/32] Split into separate feature. --- .../Extensions/TestCaseUITestContextExtensions.cs | 1 + .../Extensions/UITestContextExtensions.cs | 3 +++ Lombiq.JsonEditor/Constants/FeatureIds.cs | 1 + Lombiq.JsonEditor/Manifest.cs | 11 +++++++++++ .../Recipes/JsonEditor.Sample.recipe.json | 3 ++- Lombiq.JsonEditor/Startup.cs | 14 +++++++++----- 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs index 4fc50c9..effb21a 100644 --- a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs +++ b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs @@ -26,6 +26,7 @@ public static class TestCaseUITestContextExtensions public static async Task TestJsonEditorBehaviorAsync(this UITestContext context) { await context.EnableJsonEditorFeatureAsync(); + await context.EnableJsonContentEditorFeatureAsync(); await context.ExecuteJsonEditorSampleRecipeDirectlyAsync(); diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs index 37560eb..4c5752e 100644 --- a/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs +++ b/Lombiq.JsonEditor.Test.UI/Extensions/UITestContextExtensions.cs @@ -11,4 +11,7 @@ public static Task ExecuteJsonEditorSampleRecipeDirectlyAsync(this UITestContext public static Task EnableJsonEditorFeatureAsync(this UITestContext context) => context.EnableFeatureDirectlyAsync("Lombiq.JsonEditor"); + + public static Task EnableJsonContentEditorFeatureAsync(this UITestContext context) => + context.EnableFeatureDirectlyAsync("Lombiq.JsonEditor.ContentEditor"); } diff --git a/Lombiq.JsonEditor/Constants/FeatureIds.cs b/Lombiq.JsonEditor/Constants/FeatureIds.cs index 84da693..19181ad 100644 --- a/Lombiq.JsonEditor/Constants/FeatureIds.cs +++ b/Lombiq.JsonEditor/Constants/FeatureIds.cs @@ -5,4 +5,5 @@ public static class FeatureIds public const string Area = "Lombiq.JsonEditor"; public const string Default = Area; + public const string ContentEditor = $"{Area}.{nameof(ContentEditor)}"; } diff --git a/Lombiq.JsonEditor/Manifest.cs b/Lombiq.JsonEditor/Manifest.cs index 6f6e3b6..a2704cf 100644 --- a/Lombiq.JsonEditor/Manifest.cs +++ b/Lombiq.JsonEditor/Manifest.cs @@ -20,3 +20,14 @@ "OrchardCore.ResourceManagement", } )] + +[assembly: Feature( + Id = ContentEditor, + Name = "Lombiq JSON Content Editor", + Category = "Content", + Description = "Adds an actions menu item to the content item list for editing them as JSON.", + Dependencies = new[] + { + Default, + } +)] diff --git a/Lombiq.JsonEditor/Recipes/JsonEditor.Sample.recipe.json b/Lombiq.JsonEditor/Recipes/JsonEditor.Sample.recipe.json index 4a4a4bf..171c3ab 100644 --- a/Lombiq.JsonEditor/Recipes/JsonEditor.Sample.recipe.json +++ b/Lombiq.JsonEditor/Recipes/JsonEditor.Sample.recipe.json @@ -19,7 +19,8 @@ "name": "feature", "disable": [], "enable": [ - "Lombiq.JsonEditor" + "Lombiq.JsonEditor", + "Lombiq.JsonEditor.ContentEditor" ] }, { diff --git a/Lombiq.JsonEditor/Startup.cs b/Lombiq.JsonEditor/Startup.cs index 6f58b88..94e3c65 100644 --- a/Lombiq.JsonEditor/Startup.cs +++ b/Lombiq.JsonEditor/Startup.cs @@ -21,10 +21,6 @@ namespace Lombiq.JsonEditor; public class Startup : StartupBase { - private readonly AdminOptions _adminOptions; - - public Startup(IOptions adminOptions) => _adminOptions = adminOptions.Value; - public override void ConfigureServices(IServiceCollection services) { services.AddTransient, ResourceManagementOptionsConfiguration>(); @@ -32,9 +28,17 @@ public override void ConfigureServices(IServiceCollection services) services.AddContentField().UseDisplayDriver(); services.AddScoped(); + } +} +public class ContentEditorStartup : StartupBase +{ + private readonly AdminOptions _adminOptions; + + public ContentEditorStartup(IOptions adminOptions) => _adminOptions = adminOptions.Value; + + public override void ConfigureServices(IServiceCollection services) => services.AddScoped(); - } public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) => routes.MapAreaControllerRoute( From e476a5931318b1eeb40de61b28e726abbcdd1b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 5 Dec 2023 05:48:49 +0100 Subject: [PATCH 16/32] Only if feature is enabled. --- Lombiq.JsonEditor/Startup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Lombiq.JsonEditor/Startup.cs b/Lombiq.JsonEditor/Startup.cs index 94e3c65..fe4977f 100644 --- a/Lombiq.JsonEditor/Startup.cs +++ b/Lombiq.JsonEditor/Startup.cs @@ -31,6 +31,7 @@ public override void ConfigureServices(IServiceCollection services) } } +[Feature(FeatureIds.ContentEditor)] public class ContentEditorStartup : StartupBase { private readonly AdminOptions _adminOptions; From 0f34c5e6d14b6f8dcc4c7cd541304272a87760e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 5 Dec 2023 06:20:24 +0100 Subject: [PATCH 17/32] Ensure content is published. --- Lombiq.JsonEditor/Controllers/AdminController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index 02772b4..aa0feab 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -94,8 +94,8 @@ public async Task EditPost( contentItem.ContentItemVersionId = null; } + contentItem.Published = false; await _contentManager.PublishAsync(contentItem); - _session.Save(contentItem); if (!string.IsNullOrEmpty(returnUrl) && submitPublish != "submit.PublishAndContinue" && From 9879110c688506ca2e05634b4508d2f7d41312d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 5 Dec 2023 06:34:01 +0100 Subject: [PATCH 18/32] Generate new version ID. --- .../Controllers/AdminController.cs | 19 ++++++++++--------- Lombiq.JsonEditor/Startup.cs | 6 +++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index aa0feab..9f8eef7 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -1,4 +1,5 @@ using Lombiq.HelpfulLibraries.OrchardCore.Contents; +using Lombiq.HelpfulLibraries.OrchardCore.DependencyInjection; using Lombiq.JsonEditor.ViewModels; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -19,6 +20,7 @@ namespace Lombiq.JsonEditor.Controllers; public class AdminController : Controller { private readonly IAuthorizationService _authorizationService; + private readonly IContentItemIdGenerator _contentItemIdGenerator; private readonly IContentManager _contentManager; private readonly ILayoutAccessor _layoutAccessor; private readonly IPageTitleBuilder _pageTitleBuilder; @@ -27,21 +29,20 @@ public class AdminController : Controller private readonly IStringLocalizer T; public AdminController( - IAuthorizationService authorizationService, - IContentManager contentManager, + IContentItemIdGenerator contentItemIdGenerator, ILayoutAccessor layoutAccessor, IPageTitleBuilder pageTitleBuilder, - ISession session, IShapeFactory shapeFactory, - IStringLocalizer stringLocalizer) + IOrchardServices services) { - _authorizationService = authorizationService; - _contentManager = contentManager; + _authorizationService = services.AuthorizationService.Value; + _contentItemIdGenerator = contentItemIdGenerator; + _contentManager = services.ContentManager.Value; _layoutAccessor = layoutAccessor; _pageTitleBuilder = pageTitleBuilder; - _session = session; + _session = services.Session.Value; _shapeFactory = shapeFactory; - T = stringLocalizer; + T = services.StringLocalizer.Value; } public async Task Edit(string contentItemId) @@ -91,7 +92,7 @@ public async Task EditPost( existing.Latest = false; existing.Published = false; _session.Save(existing); - contentItem.ContentItemVersionId = null; + contentItem.ContentItemVersionId = _contentItemIdGenerator.GenerateUniqueId(existing); } contentItem.Published = false; diff --git a/Lombiq.JsonEditor/Startup.cs b/Lombiq.JsonEditor/Startup.cs index fe4977f..a4bf83c 100644 --- a/Lombiq.JsonEditor/Startup.cs +++ b/Lombiq.JsonEditor/Startup.cs @@ -1,3 +1,4 @@ +using Lombiq.HelpfulLibraries.OrchardCore.DependencyInjection; using Lombiq.JsonEditor.Constants; using Lombiq.JsonEditor.Controllers; using Lombiq.JsonEditor.Drivers; @@ -38,8 +39,11 @@ public class ContentEditorStartup : StartupBase public ContentEditorStartup(IOptions adminOptions) => _adminOptions = adminOptions.Value; - public override void ConfigureServices(IServiceCollection services) => + public override void ConfigureServices(IServiceCollection services) + { services.AddScoped(); + services.AddOrchardServices(); + } public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) => routes.MapAreaControllerRoute( From d8c4f83dcbfb41dd6d0a46c0f9cd408ea3b7cdc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 5 Dec 2023 14:54:29 +0100 Subject: [PATCH 19/32] Better title text. --- Lombiq.JsonEditor/Controllers/AdminController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index 9f8eef7..b102198 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -54,7 +54,10 @@ await _contentManager.GetAsync(contentItemId, VersionOptions.Latest) is not { } return NotFound(); } - var title = T["Edit {0} as JSON", contentItem.ContentType].Value; + var name = string.IsNullOrWhiteSpace(contentItem.DisplayText) + ? contentItem.ContentType + : $"\"{contentItem.DisplayText}\""; + var title = T["Edit {0} as JSON", name].Value; _pageTitleBuilder.AddSegment(new StringHtmlContent(title)); var titleShape = await _shapeFactory.CreateAsync("TitlePart", model => { From d9439309eb43e0454380b5424da9dc1a81b3e53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 5 Dec 2023 15:28:04 +0100 Subject: [PATCH 20/32] Remove unnecessary line. --- Lombiq.JsonEditor/Controllers/AdminController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index b102198..9df04fa 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -93,7 +93,6 @@ public async Task EditPost( if (await _contentManager.GetAsync(contentItem.ContentItemId, VersionOptions.Latest) is { } existing) { existing.Latest = false; - existing.Published = false; _session.Save(existing); contentItem.ContentItemVersionId = _contentItemIdGenerator.GenerateUniqueId(existing); } From c00d5898fc6c9052a7fec983f0b7202815ffc427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 8 Dec 2023 10:24:09 +0100 Subject: [PATCH 21/32] call LoadAsync --- Lombiq.JsonEditor/Controllers/AdminController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index 9df04fa..8fcb5ae 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -90,6 +90,8 @@ public async Task EditPost( return NotFound(); } + await _contentManager.LoadAsync(contentItem); + if (await _contentManager.GetAsync(contentItem.ContentItemId, VersionOptions.Latest) is { } existing) { existing.Latest = false; From 9e9287f71980e053d01de471542ab44faffad4a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 8 Dec 2023 13:01:16 +0100 Subject: [PATCH 22/32] Clean up call order. --- Lombiq.JsonEditor/Controllers/AdminController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index 8fcb5ae..dfa6758 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -85,13 +85,13 @@ public async Task EditPost( } if (string.IsNullOrWhiteSpace(contentItem.ContentItemId)) contentItem.ContentItemId = contentItemId; + contentItem = await _contentManager.LoadAsync(contentItem); + if (!await CanEditAsync(contentItem)) { return NotFound(); } - await _contentManager.LoadAsync(contentItem); - if (await _contentManager.GetAsync(contentItem.ContentItemId, VersionOptions.Latest) is { } existing) { existing.Latest = false; From 979afa5f1d4ea9cd5886930a38853ffbaa9bfdf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 8 Dec 2023 14:55:52 +0100 Subject: [PATCH 23/32] Use ApiController.Post to update the content item. --- .../Controllers/AdminController.cs | 61 +++++++++++++------ Lombiq.JsonEditor/Lombiq.JsonEditor.csproj | 1 + 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index dfa6758..716bce1 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -3,46 +3,55 @@ using Lombiq.JsonEditor.ViewModels; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Localization; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Extensions.Localization; using Newtonsoft.Json; using OrchardCore.ContentManagement; +using OrchardCore.ContentManagement.Metadata; using OrchardCore.Contents; +using OrchardCore.Contents.Controllers; using OrchardCore.DisplayManagement; using OrchardCore.DisplayManagement.Layout; +using OrchardCore.DisplayManagement.Notify; using OrchardCore.DisplayManagement.Title; using OrchardCore.Title.ViewModels; using System.Threading.Tasks; -using YesSql; namespace Lombiq.JsonEditor.Controllers; public class AdminController : Controller { private readonly IAuthorizationService _authorizationService; - private readonly IContentItemIdGenerator _contentItemIdGenerator; private readonly IContentManager _contentManager; + private readonly IContentDefinitionManager _contentDefinitionManager; private readonly ILayoutAccessor _layoutAccessor; + private readonly INotifier _notifier; private readonly IPageTitleBuilder _pageTitleBuilder; - private readonly ISession _session; private readonly IShapeFactory _shapeFactory; + private readonly IStringLocalizer _apiStringLocalizer; private readonly IStringLocalizer T; + private readonly IHtmlLocalizer H; public AdminController( - IContentItemIdGenerator contentItemIdGenerator, + IContentDefinitionManager contentDefinitionManager, ILayoutAccessor layoutAccessor, + INotifier notifier, IPageTitleBuilder pageTitleBuilder, IShapeFactory shapeFactory, - IOrchardServices services) + IOrchardServices services, + IStringLocalizer apiStringLocalizer) { _authorizationService = services.AuthorizationService.Value; - _contentItemIdGenerator = contentItemIdGenerator; _contentManager = services.ContentManager.Value; + _contentDefinitionManager = contentDefinitionManager; _layoutAccessor = layoutAccessor; + _notifier = notifier; _pageTitleBuilder = pageTitleBuilder; - _session = services.Session.Value; _shapeFactory = shapeFactory; + _apiStringLocalizer = apiStringLocalizer; T = services.StringLocalizer.Value; + H = services.HtmlLocalizer.Value; } public async Task Edit(string contentItemId) @@ -54,10 +63,7 @@ await _contentManager.GetAsync(contentItemId, VersionOptions.Latest) is not { } return NotFound(); } - var name = string.IsNullOrWhiteSpace(contentItem.DisplayText) - ? contentItem.ContentType - : $"\"{contentItem.DisplayText}\""; - var title = T["Edit {0} as JSON", name].Value; + var title = T["Edit {0} as JSON", GetName(contentItem)].Value; _pageTitleBuilder.AddSegment(new StringHtmlContent(title)); var titleShape = await _shapeFactory.CreateAsync("TitlePart", model => { @@ -92,15 +98,29 @@ public async Task EditPost( return NotFound(); } - if (await _contentManager.GetAsync(contentItem.ContentItemId, VersionOptions.Latest) is { } existing) + using var contentApiController = new ApiController( + _contentManager, + _contentDefinitionManager, + _authorizationService, + _apiStringLocalizer) { - existing.Latest = false; - _session.Save(existing); - contentItem.ContentItemVersionId = _contentItemIdGenerator.GenerateUniqueId(existing); - } + ControllerContext = { HttpContext = HttpContext }, + }; - contentItem.Published = false; - await _contentManager.PublishAsync(contentItem); + var result = await contentApiController.Post(contentItem); + switch (result) + { + case BadRequestObjectResult { Value: ValidationProblemDetails details } + when !string.IsNullOrWhiteSpace(details.Detail): + await _notifier.ErrorAsync(new LocalizedHtmlString(details.Detail, details.Detail)); + return await Edit(contentItem.ContentItemId); + case OkObjectResult: + await _notifier.SuccessAsync(H["Content item {0} has been successfully saved.", GetName(contentItem)]); + break; + default: + await _notifier.ErrorAsync(H["The submission has failed, please try again."]); + return await Edit(contentItem.ContentItemId); + } if (!string.IsNullOrEmpty(returnUrl) && submitPublish != "submit.PublishAndContinue" && @@ -114,4 +134,9 @@ public async Task EditPost( private Task CanEditAsync(ContentItem contentItem) => _authorizationService.AuthorizeAsync(User, CommonPermissions.EditContent, contentItem); + + private static string GetName(ContentItem contentItem) => + string.IsNullOrWhiteSpace(contentItem.DisplayText) + ? contentItem.ContentType + : $"\"{contentItem.DisplayText}\""; } diff --git a/Lombiq.JsonEditor/Lombiq.JsonEditor.csproj b/Lombiq.JsonEditor/Lombiq.JsonEditor.csproj index 7e95f5b..d737fa5 100644 --- a/Lombiq.JsonEditor/Lombiq.JsonEditor.csproj +++ b/Lombiq.JsonEditor/Lombiq.JsonEditor.csproj @@ -37,6 +37,7 @@ + From a8424da5f3299e03d751ff15d9480e0b3d0b2ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 8 Dec 2023 15:15:58 +0100 Subject: [PATCH 24/32] Add draft feature. --- Lombiq.JsonEditor/Controllers/AdminController.cs | 15 +++++++-------- .../ViewModels/EditContentItemViewModel.cs | 13 ++++--------- Lombiq.JsonEditor/Views/Admin/Edit.cshtml | 9 ++++++++- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index 716bce1..19c107d 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -72,7 +72,8 @@ await _contentManager.GetAsync(contentItemId, VersionOptions.Latest) is not { } }); await _layoutAccessor.AddShapeToZoneAsync("Title", titleShape); - return View(new EditContentItemViewModel(contentItem, JsonConvert.SerializeObject(contentItem))); + var definition = _contentDefinitionManager.GetTypeDefinition(contentItem.ContentType); + return View(new EditContentItemViewModel(contentItem, definition, JsonConvert.SerializeObject(contentItem))); } [ValidateAntiForgeryToken] @@ -81,7 +82,8 @@ public async Task EditPost( string contentItemId, string json, string returnUrl, - [Bind(Prefix = "submit.Publish")] string submitPublish) + [Bind(Prefix = "submit.Publish")] string submitPublish, + [Bind(Prefix = "submit.Save")] string submitSave) { if (string.IsNullOrWhiteSpace(contentItemId) || string.IsNullOrWhiteSpace(json) || @@ -102,13 +104,10 @@ public async Task EditPost( _contentManager, _contentDefinitionManager, _authorizationService, - _apiStringLocalizer) - { - ControllerContext = { HttpContext = HttpContext }, - }; + _apiStringLocalizer); + contentApiController.ControllerContext.HttpContext = HttpContext; - var result = await contentApiController.Post(contentItem); - switch (result) + switch (await contentApiController.Post(contentItem, submitSave != null)) { case BadRequestObjectResult { Value: ValidationProblemDetails details } when !string.IsNullOrWhiteSpace(details.Detail): diff --git a/Lombiq.JsonEditor/ViewModels/EditContentItemViewModel.cs b/Lombiq.JsonEditor/ViewModels/EditContentItemViewModel.cs index 433f611..e369ff1 100644 --- a/Lombiq.JsonEditor/ViewModels/EditContentItemViewModel.cs +++ b/Lombiq.JsonEditor/ViewModels/EditContentItemViewModel.cs @@ -1,14 +1,9 @@ using OrchardCore.ContentManagement; +using OrchardCore.ContentManagement.Metadata.Models; namespace Lombiq.JsonEditor.ViewModels; public record EditContentItemViewModel( - string ContentItemId, - string DisplayText, - string Json) -{ - public EditContentItemViewModel(ContentItem contentItem, string json) - : this(contentItem.ContentItemId, contentItem.DisplayText, json) - { - } -} + ContentItem ContentItem, + ContentTypeDefinition ContentTypeDefinition, + string Json); diff --git a/Lombiq.JsonEditor/Views/Admin/Edit.cshtml b/Lombiq.JsonEditor/Views/Admin/Edit.cshtml index d268bc6..855954c 100644 --- a/Lombiq.JsonEditor/Views/Admin/Edit.cshtml +++ b/Lombiq.JsonEditor/Views/Admin/Edit.cshtml @@ -1,5 +1,7 @@ @using Microsoft.AspNetCore.Html +@using OrchardCore.ContentManagement.Metadata.Models @model Lombiq.JsonEditor.ViewModels.EditContentItemViewModel + @{ var returnUrl = Context.Request.Query["returnUrl"]; @@ -12,7 +14,7 @@ @Html.AntiForgeryToken() - +
@@ -24,6 +26,11 @@ + @if (Model.ContentTypeDefinition.IsDraftable()) + { + + } + @if (!string.IsNullOrWhiteSpace(returnUrl) && Url.IsLocalUrl(returnUrl)) { @T["Cancel"] From 25c2106f9c843476b13242ebd5b38268002446d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 8 Dec 2023 15:28:04 +0100 Subject: [PATCH 25/32] Don't fail if the user doesn't have AccessContentApi permission. --- .../Controllers/AdminController.cs | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index 19c107d..ca38862 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -1,4 +1,5 @@ -using Lombiq.HelpfulLibraries.OrchardCore.Contents; +using AngleSharp.Common; +using Lombiq.HelpfulLibraries.OrchardCore.Contents; using Lombiq.HelpfulLibraries.OrchardCore.DependencyInjection; using Lombiq.JsonEditor.ViewModels; using Microsoft.AspNetCore.Authorization; @@ -16,6 +17,8 @@ using OrchardCore.DisplayManagement.Notify; using OrchardCore.DisplayManagement.Title; using OrchardCore.Title.ViewModels; +using System; +using System.Security.Claims; using System.Threading.Tasks; namespace Lombiq.JsonEditor.Controllers; @@ -100,14 +103,7 @@ public async Task EditPost( return NotFound(); } - using var contentApiController = new ApiController( - _contentManager, - _contentDefinitionManager, - _authorizationService, - _apiStringLocalizer); - contentApiController.ControllerContext.HttpContext = HttpContext; - - switch (await contentApiController.Post(contentItem, submitSave != null)) + switch (await UpdateContentAsync(contentItem, submitSave != null)) { case BadRequestObjectResult { Value: ValidationProblemDetails details } when !string.IsNullOrWhiteSpace(details.Detail): @@ -122,7 +118,7 @@ public async Task EditPost( } if (!string.IsNullOrEmpty(returnUrl) && - submitPublish != "submit.PublishAndContinue" && + (IsContinue(submitSave) || IsContinue(submitPublish)) && Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); @@ -134,6 +130,30 @@ public async Task EditPost( private Task CanEditAsync(ContentItem contentItem) => _authorizationService.AuthorizeAsync(User, CommonPermissions.EditContent, contentItem); + private async Task UpdateContentAsync(ContentItem contentItem, bool isDraft) + { + var currentUser = User; + HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity(User.Claims.Concat(Permissions.AccessContentApi))); + + try + { + using var contentApiController = new ApiController( + _contentManager, + _contentDefinitionManager, + _authorizationService, + _apiStringLocalizer); + contentApiController.ControllerContext.HttpContext = HttpContext; + return await contentApiController.Post(contentItem, isDraft); + } + finally + { + HttpContext.User = currentUser; + } + } + + private static bool IsContinue(string submitString) => + submitString?.EndsWithOrdinalIgnoreCase("AndContinue") == true; + private static string GetName(ContentItem contentItem) => string.IsNullOrWhiteSpace(contentItem.DisplayText) ? contentItem.ContentType From 7e95d84a28f86fee96e9e358631cca316e179e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 8 Dec 2023 17:05:56 +0100 Subject: [PATCH 26/32] Bug fix. --- Lombiq.JsonEditor/Controllers/AdminController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index ca38862..ebaf057 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -118,7 +118,7 @@ public async Task EditPost( } if (!string.IsNullOrEmpty(returnUrl) && - (IsContinue(submitSave) || IsContinue(submitPublish)) && + !(IsContinue(submitSave) || IsContinue(submitPublish)) && Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); From bbfceff29c7f9118efa466fc5118f415ad7291c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 12 Dec 2023 21:57:05 +0100 Subject: [PATCH 27/32] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Zoltán Lehóczky --- Lombiq.JsonEditor/Views/Admin/Edit.cshtml | 2 +- Readme.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lombiq.JsonEditor/Views/Admin/Edit.cshtml b/Lombiq.JsonEditor/Views/Admin/Edit.cshtml index 855954c..616af89 100644 --- a/Lombiq.JsonEditor/Views/Admin/Edit.cshtml +++ b/Lombiq.JsonEditor/Views/Admin/Edit.cshtml @@ -7,7 +7,7 @@ var warning = new HtmlString(" ").Join( T["Be careful while editing a content item as any typo can lead to a loss of functionality."], - T["The submitted JSON will be deserialized and published so a properties may be altered or regenerated at that step."]); + T["The submitted JSON will be deserialized and published so properties may be altered or regenerated at that step."]); }

@warning

diff --git a/Readme.md b/Readme.md index 24ed137..5cff5b2 100644 --- a/Readme.md +++ b/Readme.md @@ -10,7 +10,7 @@ Do you want to quickly try out this project and see it in action? Check it out i ## Documentation -### JSON editor +### JSON Editor You can use the JSON editor either as a content field by adding a _Json Field_ to your content type, or by invoking the "JsonEditor" shape with the below tag helper: @@ -39,7 +39,7 @@ The properties are: All attributes are optional. If neither content nor json is set, an empty object is taken as the content. -### JSON content editor +### JSON Content Editor The module also provides an editor for content items. This can be used to directly edit a content item as JSON data. This tool can be useful to inspect how the content item is serialized in the YesSql database without directly accessing the database or exporting the content item via deployment. It can also be used to edit properties that currently don't have an editor. From aaf657486c5814581a7db2e87c656e296bc3c3f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 12 Dec 2023 21:59:21 +0100 Subject: [PATCH 28/32] Not needed. --- .../Extensions/TestCaseUITestContextExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs index effb21a..3655c33 100644 --- a/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs +++ b/Lombiq.JsonEditor.Test.UI/Extensions/TestCaseUITestContextExtensions.cs @@ -25,7 +25,6 @@ public static class TestCaseUITestContextExtensions public static async Task TestJsonEditorBehaviorAsync(this UITestContext context) { - await context.EnableJsonEditorFeatureAsync(); await context.EnableJsonContentEditorFeatureAsync(); await context.ExecuteJsonEditorSampleRecipeDirectlyAsync(); From a4c119e8a781de571172f07e8e045f27be025b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 12 Dec 2023 22:20:12 +0100 Subject: [PATCH 29/32] Inject the API controller. --- Lombiq.JsonEditor/Controllers/AdminController.cs | 12 ++++-------- Lombiq.JsonEditor/Startup.cs | 4 +++- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index ebaf057..c8afac6 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -32,7 +32,7 @@ public class AdminController : Controller private readonly INotifier _notifier; private readonly IPageTitleBuilder _pageTitleBuilder; private readonly IShapeFactory _shapeFactory; - private readonly IStringLocalizer _apiStringLocalizer; + private readonly Lazy _contentApiControllerLazy; private readonly IStringLocalizer T; private readonly IHtmlLocalizer H; @@ -43,7 +43,7 @@ public AdminController( IPageTitleBuilder pageTitleBuilder, IShapeFactory shapeFactory, IOrchardServices services, - IStringLocalizer apiStringLocalizer) + Lazy contentApiControllerLazy) { _authorizationService = services.AuthorizationService.Value; _contentManager = services.ContentManager.Value; @@ -52,7 +52,7 @@ public AdminController( _notifier = notifier; _pageTitleBuilder = pageTitleBuilder; _shapeFactory = shapeFactory; - _apiStringLocalizer = apiStringLocalizer; + _contentApiControllerLazy = contentApiControllerLazy; T = services.StringLocalizer.Value; H = services.HtmlLocalizer.Value; } @@ -137,11 +137,7 @@ private async Task UpdateContentAsync(ContentItem contentItem, bo try { - using var contentApiController = new ApiController( - _contentManager, - _contentDefinitionManager, - _authorizationService, - _apiStringLocalizer); + var contentApiController = _contentApiControllerLazy.Value; contentApiController.ControllerContext.HttpContext = HttpContext; return await contentApiController.Post(contentItem, isDraft); } diff --git a/Lombiq.JsonEditor/Startup.cs b/Lombiq.JsonEditor/Startup.cs index a4bf83c..db38eb8 100644 --- a/Lombiq.JsonEditor/Startup.cs +++ b/Lombiq.JsonEditor/Startup.cs @@ -1,6 +1,5 @@ using Lombiq.HelpfulLibraries.OrchardCore.DependencyInjection; using Lombiq.JsonEditor.Constants; -using Lombiq.JsonEditor.Controllers; using Lombiq.JsonEditor.Drivers; using Lombiq.JsonEditor.Fields; using Lombiq.JsonEditor.Settings; @@ -12,11 +11,13 @@ using OrchardCore.Admin; using OrchardCore.ContentManagement; using OrchardCore.ContentManagement.Display.ContentDisplay; +using OrchardCore.Contents.Controllers; using OrchardCore.ContentTypes.Editors; using OrchardCore.Modules; using OrchardCore.Mvc.Core.Utilities; using OrchardCore.ResourceManagement; using System; +using AdminController=Lombiq.JsonEditor.Controllers.AdminController; namespace Lombiq.JsonEditor; @@ -43,6 +44,7 @@ public override void ConfigureServices(IServiceCollection services) { services.AddScoped(); services.AddOrchardServices(); + services.AddScoped(); } public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) => From 427b7f9e0fce08d1f4ef63073754be8f6c040f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 12 Dec 2023 22:20:32 +0100 Subject: [PATCH 30/32] Add comments about AdminController.UpdateContentAsync. --- Lombiq.JsonEditor/Controllers/AdminController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index c8afac6..0a8c789 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -132,17 +132,23 @@ private Task CanEditAsync(ContentItem contentItem) => private async Task UpdateContentAsync(ContentItem contentItem, bool isDraft) { + // The Content API Controller requires the AccessContentApi permission. As this isn't an external API request it + // doesn't make sense to require this permission. So we create a temporary claims principal and explicitly grant + // the permission var currentUser = User; HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity(User.Claims.Concat(Permissions.AccessContentApi))); try { + // Here the API controller is called directly. The behavior is the same as if we sent a POST request using a + // HTTP client (except the permission bypass above), but it's faster and more resource efficient. var contentApiController = _contentApiControllerLazy.Value; contentApiController.ControllerContext.HttpContext = HttpContext; return await contentApiController.Post(contentItem, isDraft); } finally { + // Ensure that the original claims principal is restored, just in case. HttpContext.User = currentUser; } } From 7fe7193a36d4c6c33adbfd99edca407be9f88c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 12 Dec 2023 22:32:31 +0100 Subject: [PATCH 31/32] Code styling. --- Lombiq.JsonEditor/Startup.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lombiq.JsonEditor/Startup.cs b/Lombiq.JsonEditor/Startup.cs index db38eb8..98ea666 100644 --- a/Lombiq.JsonEditor/Startup.cs +++ b/Lombiq.JsonEditor/Startup.cs @@ -17,7 +17,8 @@ using OrchardCore.Mvc.Core.Utilities; using OrchardCore.ResourceManagement; using System; -using AdminController=Lombiq.JsonEditor.Controllers.AdminController; + +using AdminController = Lombiq.JsonEditor.Controllers.AdminController; namespace Lombiq.JsonEditor; From 8061daba85530334c5f57bffc08f5391bd4059be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 20 Dec 2023 02:23:48 +0100 Subject: [PATCH 32/32] Typos --- Lombiq.JsonEditor/Controllers/AdminController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lombiq.JsonEditor/Controllers/AdminController.cs b/Lombiq.JsonEditor/Controllers/AdminController.cs index 0a8c789..ed8b29b 100644 --- a/Lombiq.JsonEditor/Controllers/AdminController.cs +++ b/Lombiq.JsonEditor/Controllers/AdminController.cs @@ -134,14 +134,14 @@ private async Task UpdateContentAsync(ContentItem contentItem, bo { // The Content API Controller requires the AccessContentApi permission. As this isn't an external API request it // doesn't make sense to require this permission. So we create a temporary claims principal and explicitly grant - // the permission + // the permission. var currentUser = User; HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity(User.Claims.Concat(Permissions.AccessContentApi))); try { - // Here the API controller is called directly. The behavior is the same as if we sent a POST request using a - // HTTP client (except the permission bypass above), but it's faster and more resource efficient. + // Here the API controller is called directly. The behavior is the same as if we sent a POST request using an + // HTTP client (except the permission bypass above), but it's faster and more resource-efficient. var contentApiController = _contentApiControllerLazy.Value; contentApiController.ControllerContext.HttpContext = HttpContext; return await contentApiController.Post(contentItem, isDraft);