From 57d443be8dc20236a2c55dbd6c8163247330ab6c Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 14 May 2025 12:18:37 -0400 Subject: [PATCH] support for module header and footer content --- .../Modules/Admin/Modules/Settings.razor | 75 +++++++++++++------ .../Modules/Admin/Modules/Settings.resx | 15 ++++ Oqtane.Client/UI/ModuleInstance.razor | 9 +++ Oqtane.Server/Controllers/ModuleController.cs | 2 + Oqtane.Server/Controllers/PageController.cs | 4 + .../Tenant/06010302_AddModuleHeaderFooter.cs | 29 +++++++ Oqtane.Server/Repository/SiteRepository.cs | 2 + Oqtane.Server/Services/SiteService.cs | 2 + Oqtane.Shared/Models/Module.cs | 14 ++++ Oqtane.Shared/Models/PageModule.cs | 13 ++++ Oqtane.Shared/Models/SiteTemplate.cs | 4 + 11 files changed, 147 insertions(+), 22 deletions(-) create mode 100644 Oqtane.Server/Migrations/Tenant/06010302_AddModuleHeaderFooter.cs diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index a949271f..b7cf4f4c 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -97,6 +97,23 @@ +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
} @@ -144,6 +161,8 @@ private string _pane; private string _containerType; private string _allPages = "false"; + private string _header = ""; + private string _footer = ""; private string _permissionNames = ""; private List _permissions = null; private string _pageId; @@ -167,37 +186,47 @@ protected override async Task OnInitializedAsync() { SetModuleTitle(Localizer["ModuleSettings.Title"]); - - _title = ModuleState.Title; _moduleSettingsTitle = Localizer["ModuleSettings.Heading"]; - _pane = ModuleState.Pane; + _containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType); - _containerType = ModuleState.ContainerType; - _allPages = ModuleState.AllPages.ToString(); - _permissions = ModuleState.PermissionList; - _pageId = ModuleState.PageId.ToString(); - createdby = ModuleState.CreatedBy; - createdon = ModuleState.CreatedOn; - modifiedby = ModuleState.ModifiedBy; - modifiedon = ModuleState.ModifiedOn; - _effectivedate = Utilities.UtcAsLocalDate(ModuleState.EffectiveDate); - _expirydate = Utilities.UtcAsLocalDate(ModuleState.ExpiryDate); _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); - if (ModuleState.ModuleDefinition != null) - { - _module = ModuleState.ModuleDefinition.Name; - _permissionNames = ModuleState.ModuleDefinition?.PermissionNames; + var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); - if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType)) + _pageId = pagemodule.PageId.ToString(); + _title = pagemodule.Title; + _pane = pagemodule.Pane; + _containerType = pagemodule.ContainerType; + if (string.IsNullOrEmpty(_containerType)) + { + _containerType = (!string.IsNullOrEmpty(PageState.Page.DefaultContainerType)) ? PageState.Page.DefaultContainerType : PageState.Site.DefaultContainerType; + } + _header = pagemodule.Header; + _footer = pagemodule.Footer; + _effectivedate = Utilities.UtcAsLocalDate(pagemodule.EffectiveDate); + _expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate); + + _allPages = pagemodule.Module.AllPages.ToString(); + createdby = pagemodule.Module.CreatedBy; + createdon = pagemodule.Module.CreatedOn; + modifiedby = pagemodule.Module.ModifiedBy; + modifiedon = pagemodule.Module.ModifiedOn; + _permissions = pagemodule.Module.PermissionList; + + if (pagemodule.Module.ModuleDefinition != null) + { + _module = pagemodule.Module.ModuleDefinition.Name; + _permissionNames = pagemodule.Module.ModuleDefinition?.PermissionNames; + + if (!string.IsNullOrEmpty(pagemodule.Module.ModuleDefinition.SettingsType)) { // module settings type explicitly declared in IModule interface - _moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType); + _moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.SettingsType); } else { // legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module ) - _moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true); + _moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true); } if (_moduleSettingsType != null) { @@ -218,7 +247,7 @@ } else { - AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error); + AddModuleMessage(string.Format(Localizer["Error.Module.Load"], pagemodule.Module.ModuleDefinitionName), MessageType.Error); } var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType))); @@ -270,10 +299,12 @@ { pagemodule.ContainerType = string.Empty; } + pagemodule.Header = _header; + pagemodule.Footer = _footer; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - var module = ModuleState; + var module = await ModuleService.GetModuleAsync(ModuleState.ModuleId); module.AllPages = bool.Parse(_allPages); module.PageModuleId = ModuleState.PageModuleId; module.PermissionList = _permissionGrid.GetPermissionList(); diff --git a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx index a91aa36e..4f8f895c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx @@ -189,4 +189,19 @@ Module Settings + + Header: + + + Optionally provide content to be injected above the module instance + + + Footer: + + + Optionally provide content to be injected below the module instance + + + Content + \ No newline at end of file diff --git a/Oqtane.Client/UI/ModuleInstance.razor b/Oqtane.Client/UI/ModuleInstance.razor index 1bd5296f..e8353a82 100644 --- a/Oqtane.Client/UI/ModuleInstance.razor +++ b/Oqtane.Client/UI/ModuleInstance.razor @@ -1,6 +1,7 @@ @namespace Oqtane.UI @inject SiteState SiteState +@((MarkupString)ModuleState.Header) @if (_comment != null) { @((MarkupString)_comment) @@ -13,6 +14,7 @@ } } +@((MarkupString)ModuleState.Footer) @code { [CascadingParameter] @@ -23,6 +25,8 @@ private bool _prerender; private string _comment; + private string _header; + private string _footer; protected override void OnParametersSet() { @@ -39,11 +43,16 @@ } _comment += " -->"; + _header = ModuleState.Header; + _footer = ModuleState.Footer; + if (PageState.RenderMode == RenderModes.Static && ModuleState.RenderMode == RenderModes.Interactive) { // trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries // please note that this performance optimization results in the PageState.Pages property not being available for use in Interactive components PageState.Site.Pages = new List(); + ModuleState.Header = string.Empty; + ModuleState.Footer = string.Empty; } } diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index a7c09fcb..02f6f8c6 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -76,6 +76,8 @@ namespace Oqtane.Controllers module.ContainerType = pagemodule.ContainerType; module.EffectiveDate = pagemodule.EffectiveDate; module.ExpiryDate = pagemodule.ExpiryDate; + module.Header = pagemodule.Header; + module.Footer = pagemodule.Footer; module.ModuleDefinition = _moduleDefinitions.FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName)); diff --git a/Oqtane.Server/Controllers/PageController.cs b/Oqtane.Server/Controllers/PageController.cs index ee2ff20c..6086bd0f 100644 --- a/Oqtane.Server/Controllers/PageController.cs +++ b/Oqtane.Server/Controllers/PageController.cs @@ -246,6 +246,10 @@ namespace Oqtane.Controllers pagemodule.Pane = pm.Pane; pagemodule.Order = pm.Order; pagemodule.ContainerType = pm.ContainerType; + pagemodule.EffectiveDate = pm.EffectiveDate; + pagemodule.ExpiryDate = pm.ExpiryDate; + pagemodule.Header = pm.Header; + pagemodule.Footer = pm.Footer; _pageModules.AddPageModule(pagemodule); } diff --git a/Oqtane.Server/Migrations/Tenant/06010302_AddModuleHeaderFooter.cs b/Oqtane.Server/Migrations/Tenant/06010302_AddModuleHeaderFooter.cs new file mode 100644 index 00000000..39e01287 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/06010302_AddModuleHeaderFooter.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.06.01.03.02")] + public class AddModuleHeaderFooter : MultiDatabaseMigration + { + public AddModuleHeaderFooter(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var pageModuleEntityBuilder = new PageModuleEntityBuilder(migrationBuilder, ActiveDatabase); + pageModuleEntityBuilder.AddMaxStringColumn("Header", true); + pageModuleEntityBuilder.AddMaxStringColumn("Footer", true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 10de3576..48620ead 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -442,6 +442,8 @@ namespace Oqtane.Repository pageModule.Pane = (string.IsNullOrEmpty(pageTemplateModule.Pane)) ? PaneNames.Default : pageTemplateModule.Pane; pageModule.Order = (pageTemplateModule.Order == 0) ? 1 : pageTemplateModule.Order; pageModule.ContainerType = pageTemplateModule.ContainerType; + pageModule.Header = pageTemplateModule.Header; + pageModule.Footer = pageTemplateModule.Footer; pageModule.IsDeleted = pageTemplateModule.IsDeleted; pageModule.Module.PermissionList = new List(); foreach (var permission in pageTemplateModule.PermissionList) diff --git a/Oqtane.Server/Services/SiteService.cs b/Oqtane.Server/Services/SiteService.cs index cc0991d8..f316b766 100644 --- a/Oqtane.Server/Services/SiteService.cs +++ b/Oqtane.Server/Services/SiteService.cs @@ -285,6 +285,8 @@ namespace Oqtane.Services ContainerType = pagemodule.ContainerType, EffectiveDate = pagemodule.EffectiveDate, ExpiryDate = pagemodule.ExpiryDate, + Header = pagemodule.Header, + Footer = pagemodule.Footer, ModuleDefinition = _moduleDefinitions.FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == pagemodule.Module.ModuleDefinitionName)), diff --git a/Oqtane.Shared/Models/Module.cs b/Oqtane.Shared/Models/Module.cs index 55f31357..7775fcf5 100644 --- a/Oqtane.Shared/Models/Module.cs +++ b/Oqtane.Shared/Models/Module.cs @@ -113,6 +113,18 @@ namespace Oqtane.Models [NotMapped] public DateTime? ExpiryDate { get; set; } + /// + /// Header content to include at the top of a module instance in the UI + /// + [NotMapped] + public string Header { get; set; } + + /// + /// Footer content to include below a module instance in the UI + /// + [NotMapped] + public string Footer { get; set; } + #endregion #region SiteRouter properties @@ -218,6 +230,8 @@ namespace Oqtane.Models ContainerType = ContainerType, EffectiveDate = EffectiveDate, ExpiryDate = ExpiryDate, + Header = Header, + Footer = Footer, CreatedBy = CreatedBy, CreatedOn = CreatedOn, ModifiedBy = ModifiedBy, diff --git a/Oqtane.Shared/Models/PageModule.cs b/Oqtane.Shared/Models/PageModule.cs index 0e7c812f..20985e93 100644 --- a/Oqtane.Shared/Models/PageModule.cs +++ b/Oqtane.Shared/Models/PageModule.cs @@ -41,14 +41,27 @@ namespace Oqtane.Models /// Reference to a Razor Container which wraps this module instance. /// public string ContainerType { get; set; } + /// /// Start of when this assignment is valid. See also /// public DateTime? EffectiveDate { get; set; } + /// /// End of when this assignment is valid. See also /// public DateTime? ExpiryDate { get; set; } + + /// + /// Header content to include above the module instance in the UI + /// + public string Header { get; set; } + + /// + /// Footer content to include below the module instance in the UI + /// + public string Footer { get; set; } + #region IDeletable Properties public string DeletedBy { get; set; } diff --git a/Oqtane.Shared/Models/SiteTemplate.cs b/Oqtane.Shared/Models/SiteTemplate.cs index 348e1834..42cbffee 100644 --- a/Oqtane.Shared/Models/SiteTemplate.cs +++ b/Oqtane.Shared/Models/SiteTemplate.cs @@ -95,6 +95,8 @@ namespace Oqtane.Models Pane = PaneNames.Default; Order = 1; ContainerType = ""; + Header = ""; + Footer = ""; IsDeleted = false; PermissionList = new List() { @@ -110,6 +112,8 @@ namespace Oqtane.Models public string Pane { get; set; } public int Order { get; set; } public string ContainerType { get; set; } + public string Header { get; set; } + public string Footer { get; set; } public bool IsDeleted { get; set; } public List PermissionList { get; set; } public List Settings { get; set; }