From c8a679ecce0636c894058b4860976daf650b535a Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 20 Jun 2023 08:52:02 -0400 Subject: [PATCH] integrate old logic for managing stylesheets using JS Interop --- Oqtane.Client/UI/Interop.cs | 30 ++++++++++++ Oqtane.Client/UI/SiteRouter.razor | 2 +- Oqtane.Client/UI/ThemeBuilder.razor | 57 ++++++++++++++++------ Oqtane.Server/Pages/_Host.cshtml | 2 + Oqtane.Server/Pages/_Host.cshtml.cs | 52 +++++++++++++++++++- Oqtane.Server/Repository/SiteRepository.cs | 3 +- Oqtane.Server/wwwroot/js/interop.js | 14 ++++++ 7 files changed, 141 insertions(+), 19 deletions(-) diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs index 827a56bf..9ed3f91e 100644 --- a/Oqtane.Client/UI/Interop.cs +++ b/Oqtane.Client/UI/Interop.cs @@ -90,6 +90,21 @@ namespace Oqtane.UI } } + public Task IncludeLinks(object[] links) + { + try + { + _jsRuntime.InvokeVoidAsync( + "Oqtane.Interop.includeLinks", + (object)links); + return Task.CompletedTask; + } + catch + { + return Task.CompletedTask; + } + } + // external scripts need to specify src, inline scripts need to specify id and content public Task IncludeScript(string id, string src, string integrity, string crossorigin, string content, string location) { @@ -125,6 +140,21 @@ namespace Oqtane.UI } } + public Task RemoveElementsById(string prefix, string first, string last) + { + try + { + _jsRuntime.InvokeVoidAsync( + "Oqtane.Interop.removeElementsById", + prefix, first, last); + return Task.CompletedTask; + } + catch + { + return Task.CompletedTask; + } + } + public ValueTask GetElementByName(string name) { try diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index b724da31..03cda77f 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -522,7 +522,7 @@ } // ensure resource does not exist already - if (pageresources.Find(item => item.Url == resource.Url) == null) + if (!pageresources.Any(item => item.Url.ToLower() == resource.Url.ToLower())) { resource.Level = level; pageresources.Add(resource); diff --git a/Oqtane.Client/UI/ThemeBuilder.razor b/Oqtane.Client/UI/ThemeBuilder.razor index a26f3e26..fefc0025 100644 --- a/Oqtane.Client/UI/ThemeBuilder.razor +++ b/Oqtane.Client/UI/ThemeBuilder.razor @@ -31,6 +31,7 @@ // set page head content var headcontent = ""; + // favicon var favicon = "favicon.ico"; var favicontype = "x-icon"; @@ -40,12 +41,14 @@ favicontype = favicon.Substring(favicon.LastIndexOf(".") + 1); } headcontent += $"\n"; + // stylesheets - foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) - { - var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; - headcontent += "" + "\n"; - } + //foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) + //{ + // var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; + // headcontent += "" + "\n"; + //} + // head content AddHeadContent(headcontent, PageState.Site.HeadContent); if (!string.IsNullOrEmpty(PageState.Site.HeadContent)) @@ -100,27 +103,49 @@ } } - if (PageState.Page.Resources != null && PageState.Page.Resources.Exists(item => item.ResourceType == ResourceType.Script)) + if (PageState.Page.Resources != null) { var interop = new Interop(JSRuntime); - var scripts = new List(); - var inline = 0; - foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site)) + + if (PageState.Page.Resources.Exists(item => item.ResourceType == ResourceType.Stylesheet)) { - if (!string.IsNullOrEmpty(resource.Url)) + string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff"); + var links = new List(); + foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) { + var prefix = "app-stylesheet-" + resource.Level.ToString().ToLower(); var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; - scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() }); + links.Add(new { id = prefix + "-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", insertbefore = prefix }); } - else + if (links.Any()) { - inline += 1; - await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower()); + await interop.IncludeLinks(links.ToArray()); } + await interop.RemoveElementsById("app-stylesheet-page-", "", "app-stylesheet-page-" + batch + "-00"); + await interop.RemoveElementsById("app-stylesheet-module-", "", "app-stylesheet-module-" + batch + "-00"); } - if (scripts.Any()) + + if (PageState.Page.Resources.Exists(item => item.ResourceType == ResourceType.Script)) { - await interop.IncludeScripts(scripts.ToArray()); + var scripts = new List(); + var inline = 0; + foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site)) + { + if (!string.IsNullOrEmpty(resource.Url)) + { + var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; + scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() }); + } + else + { + inline += 1; + await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower()); + } + } + if (scripts.Any()) + { + await interop.IncludeScripts(scripts.ToArray()); + } } } } diff --git a/Oqtane.Server/Pages/_Host.cshtml b/Oqtane.Server/Pages/_Host.cshtml index 6d42d8da..22bc6a90 100644 --- a/Oqtane.Server/Pages/_Host.cshtml +++ b/Oqtane.Server/Pages/_Host.cshtml @@ -15,6 +15,8 @@ { } + + @Html.Raw(Model.HeadResources) diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index ba352e25..29e1e105 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -19,6 +19,8 @@ using Microsoft.Extensions.Primitives; using Oqtane.Enums; using Oqtane.Security; using Oqtane.Extensions; +using Oqtane.Themes; +using System.Collections.Generic; namespace Oqtane.Pages { @@ -163,7 +165,29 @@ namespace Oqtane.Pages } } - // inject scripts + // stylesheets + var resources = new List(); + if (string.IsNullOrEmpty(page.ThemeType)) + { + page.ThemeType = site.DefaultThemeType; + } + var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType)); + if (theme != null) + { + resources.AddRange(theme.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet).ToList()); + } + var type = Type.GetType(page.ThemeType); + if (type != null) + { + var obj = Activator.CreateInstance(type) as IThemeControl; + if (obj.Resources != null) + { + resources.AddRange(obj.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet).ToList()); + } + } + ManageResources(resources, alias, theme.ThemeName); + + // scripts if (Runtime == "Server") { ReconnectScript = CreateReconnectScript(); @@ -468,5 +492,31 @@ namespace Oqtane.Pages CookieRequestCultureProvider.DefaultCookieName, CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture))); } + + private void ManageResources(List resources, Alias alias, string name) + { + if (resources != null) + { + int count = 0; + foreach (var resource in resources) + { + if (resource.Url.StartsWith("~")) + { + resource.Url = resource.Url.Replace("~", "/Themes/" + name + "/").Replace("//", "/"); + } + if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl)) + { + resource.Url = alias.BaseUrl + resource.Url; + } + + if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase)) + { + count++; + string id = "id=\"app-stylesheet-" + ResourceLevel.Page.ToString().ToLower() + "-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + "-" + count.ToString("00") + "\" "; + HeadResources += "" + Environment.NewLine; + } + } + } + } } } diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 138e0a16..72b34405 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -99,7 +99,8 @@ namespace Oqtane.Repository { var site = GetSite(alias.SiteId); - _themeRepository.GetThemes(); + // load themes and module definitions + site.Themes = _themeRepository.GetThemes().ToList(); var moduleDefinitions = _moduleDefinitionRepository.GetModuleDefinitions(alias.SiteId); // site migrations diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index fbbd012a..8b98c530 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -108,6 +108,11 @@ Oqtane.Interop = { } } }, + includeLinks: function (links) { + for (let i = 0; i < links.length; i++) { + this.includeLink(links[i].id, links[i].rel, links[i].href, links[i].type, links[i].integrity, links[i].crossorigin, links[i].insertbefore); + } + }, includeScript: function (id, src, integrity, crossorigin, type, content, location) { var script; if (src !== "") { @@ -234,6 +239,15 @@ Oqtane.Interop = { } return getAbsoluteUrl(url); }, + removeElementsById: function (prefix, first, last) { + var elements = document.querySelectorAll('[id^=' + prefix + ']'); + for (var i = elements.length - 1; i >= 0; i--) { + var element = elements[i]; + if (element.id.startsWith(prefix) && (first === '' || element.id >= first) && (last === '' || element.id <= last)) { + element.parentNode.removeChild(element); + } + } + }, getElementByName: function (name) { var elements = document.getElementsByName(name); if (elements.length) {