Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to add support for script type and data-* attributes. Also added Script and Stylesheet classes to simplify Resource declarations. #4927

Merged
merged 1 commit into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Oqtane.Client/Modules/ModuleBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,17 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
var inline = 0;
foreach (Resource resource in resources)
{
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload)
{
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() });
scripts.Add(new { href = url, type = resource.Type ?? "", bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", location = resource.Location.ToString().ToLower(), dataAttributes = resource.DataAttributes });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Type ?? "", resource.Content, resource.Location.ToString().ToLower());
}
}
}
Expand Down
9 changes: 3 additions & 6 deletions Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,8 @@
public override List<Resource> Resources => new List<Resource>()
{
// obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css",
Integrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==",
CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = Constants.BootstrapScriptUrl, Integrity = Constants.BootstrapScriptIntegrity, CrossOrigin = "anonymous", Location = ResourceLocation.Body },
new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css", "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==", "anonymous"),
new Stylesheet(ThemePath() + "Theme.css"),
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
};

}
8 changes: 3 additions & 5 deletions Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ public class ThemeInfo : ITheme
Resources = new List<Resource>()
{
// obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css",
Integrity = "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==",
CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = Constants.BootstrapScriptUrl, Integrity = Constants.BootstrapScriptIntegrity, CrossOrigin = "anonymous", Location = ResourceLocation.Body }
new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css", "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==", "anonymous"),
new Stylesheet("~/Theme.css"),
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
}
};
}
Expand Down
6 changes: 3 additions & 3 deletions Oqtane.Client/Themes/ThemeBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,17 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
var inline = 0;
foreach (Resource resource in resources)
{
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload)
{
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() });
scripts.Add(new { href = url, type = resource.Type ?? "", bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", location = resource.Location.ToString().ToLower(), dataAttributes = resource.DataAttributes });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Type ?? "", resource.Content, resource.Location.ToString().ToLower());
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion Oqtane.Client/UI/Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,17 @@ public Task IncludeScript(string id, string src, string integrity, string crosso
}

public Task IncludeScript(string id, string src, string integrity, string crossorigin, string type, string content, string location)
{
return IncludeScript(id, src, integrity, crossorigin, type, content, location, null);
}

public Task IncludeScript(string id, string src, string integrity, string crossorigin, string type, string content, string location, Dictionary<string, string> dataAttributes)
{
try
{
_jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeScript",
id, src, integrity, crossorigin, type, content, location);
id, src, integrity, crossorigin, type, content, location, dataAttributes);
return Task.CompletedTask;
}
catch
Expand Down
6 changes: 3 additions & 3 deletions Oqtane.Client/UI/ThemeBuilder.razor
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@
if (!string.IsNullOrEmpty(src))
{
src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src;
scripts.Add(new { href = src, bundle = "", integrity = integrity, crossorigin = crossorigin, es6module = (type == "module"), location = location.ToString().ToLower(), dataAttributes = dataAttributes });
scripts.Add(new { href = src, type = type, bundle = "", integrity = integrity, crossorigin = crossorigin, location = location.ToString().ToLower(), dataAttributes = dataAttributes });
}
else
{
Expand All @@ -186,8 +186,8 @@
count += 1;
id = $"page{PageState.Page.PageId}-script{count}";
}
index = script.IndexOf(">") + 1;
await interop.IncludeScript(id, "", "", "", "", script.Substring(index, script.IndexOf("</script>") - index), location.ToString().ToLower());
var pos = script.IndexOf(">") + 1;
await interop.IncludeScript(id, "", "", "", type, script.Substring(pos, script.IndexOf("</script>") - pos), location.ToString().ToLower(), dataAttributes);
}
index = content.IndexOf("<script", index + 1);
}
Expand Down
15 changes: 13 additions & 2 deletions Oqtane.Server/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -554,11 +554,22 @@
if (!resource.Reload)
{
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;

var dataAttributes = "";
if (resource.DataAttributes != null && resource.DataAttributes.Count > 0)
{
foreach (var attribute in resource.DataAttributes)
{
dataAttributes += " " + attribute.Key + "=\"" + attribute.Value + "\"";
}
}

return "<script src=\"" + url + "\"" +
((resource.ES6Module) ? " type=\"module\"" : "") +
((!string.IsNullOrEmpty(resource.Type)) ? " type=\"" + resource.Type + "\"" : "") +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
"></script>";
((!string.IsNullOrEmpty(dataAttributes)) ? dataAttributes : "") +
"></script>";
}
else
{
Expand Down
72 changes: 40 additions & 32 deletions Oqtane.Server/wwwroot/js/interop.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,22 @@ Oqtane.Interop = {
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) {
includeScript: function (id, src, integrity, crossorigin, type, content, location, dataAttributes) {
var script;
if (src !== "") {
script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]");
}
else {
script = document.getElementById(id);
if (id !== "") {
script = document.getElementById(id);
} else {
const scripts = document.querySelectorAll("script:not([src])");
for (let i = 0; i < scripts.length; i++) {
if (scripts[i].textContent.includes(content)) {
script = scripts[i];
}
}
}
}
if (script !== null) {
script.remove();
Expand All @@ -152,37 +161,36 @@ Oqtane.Interop = {
else {
script.innerHTML = content;
}
script.async = false;
this.addScript(script, location)
.then(() => {
if (src !== "") {
console.log(src + ' loaded');
}
else {
console.log(id + ' loaded');
}
})
.catch(() => {
if (src !== "") {
console.error(src + ' failed');
}
else {
console.error(id + ' failed');
}
});
if (dataAttributes !== null) {
for (var key in dataAttributes) {
script.setAttribute(key, dataAttributes[key]);
}
}

try {
this.addScript(script, location);
} catch (error) {
if (src !== "") {
console.error("Failed to load external script: ${src}", error);
} else {
console.error("Failed to load inline script: ${content}", error);
}
}
}
},
addScript: function (script, location) {
if (location === 'head') {
document.head.appendChild(script);
}
if (location === 'body') {
document.body.appendChild(script);
}
return new Promise((resolve, reject) => {
script.async = false;
script.defer = false;

script.onload = () => resolve();
script.onerror = (error) => reject(error);

return new Promise((res, rej) => {
script.onload = res();
script.onerror = rej();
if (location === 'head') {
document.head.appendChild(script);
} else {
document.body.appendChild(script);
}
});
},
includeScripts: async function (scripts) {
Expand Down Expand Up @@ -222,10 +230,10 @@ Oqtane.Interop = {
if (scripts[s].crossorigin !== '') {
element.crossOrigin = scripts[s].crossorigin;
}
if (scripts[s].es6module === true) {
element.type = "module";
if (scripts[s].type !== '') {
element.type = scripts[s].type;
}
if (typeof scripts[s].dataAttributes !== "undefined" && scripts[s].dataAttributes !== null) {
if (scripts[s].dataAttributes !== null) {
for (var key in scripts[s].dataAttributes) {
element.setAttribute(key, scripts[s].dataAttributes[key]);
}
Expand Down
40 changes: 34 additions & 6 deletions Oqtane.Shared/Models/Resource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Oqtane.Shared;

namespace Oqtane.Models
Expand Down Expand Up @@ -27,6 +29,11 @@ public string Url
}
}

/// <summary>
/// For Scripts this allows type to be specified - not applicable to Stylesheets
/// </summary>
public string Type { get; set; }

/// <summary>
/// Integrity checks to increase the security of resources accessed. Especially common in CDN resources.
/// </summary>
Expand All @@ -52,11 +59,6 @@ public string Url
/// </summary>
public ResourceLocation Location { get; set; }

/// <summary>
/// For Scripts this allows type="module" registrations - not applicable to Stylesheets
/// </summary>
public bool ES6Module { get; set; }

/// <summary>
/// Allows specification of inline script - not applicable to Stylesheets
/// </summary>
Expand All @@ -72,6 +74,11 @@ public string Url
/// </summary>
public bool Reload { get; set; }

/// <summary>
/// Cusotm data-* attributes for scripts - not applicable to Stylesheets
/// </summary>
public Dictionary<string, string> DataAttributes { get; set; }

/// <summary>
/// The namespace of the component that declared the resource - only used in SiteRouter
/// </summary>
Expand All @@ -82,20 +89,41 @@ public Resource Clone(ResourceLevel level, string name)
var resource = new Resource();
resource.ResourceType = ResourceType;
resource.Url = Url;
resource.Type = Type;
resource.Integrity = Integrity;
resource.CrossOrigin = CrossOrigin;
resource.Bundle = Bundle;
resource.Location = Location;
resource.ES6Module = ES6Module;
resource.Content = Content;
resource.RenderMode = RenderMode;
resource.Reload = Reload;
resource.DataAttributes = new Dictionary<string, string>();
if (DataAttributes != null && DataAttributes.Count > 0)
{
foreach (var kvp in DataAttributes)
{
resource.DataAttributes.Add(kvp.Key, kvp.Value);
}
}
resource.Level = level;
resource.Namespace = name;
return resource;
}

[Obsolete("ResourceDeclaration is deprecated", false)]
public ResourceDeclaration Declaration { get; set; }

[Obsolete("ES6Module is deprecated. Use Type property instead for scripts.", false)]
public bool ES6Module
{
get => (Type == "module");
set
{
if (value)
{
Type = "module";
};
}
}
}
}
53 changes: 53 additions & 0 deletions Oqtane.Shared/Models/Script.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using Oqtane.Shared;

namespace Oqtane.Models
{
/// <summary>
/// Script inherits from Resource and offers constructors with parameters specific to Scripts
/// </summary>
public class Script : Resource
{
private void SetDefaults()
{
this.ResourceType = ResourceType.Script;
this.Location = ResourceLocation.Body;
}

public Script(string Src)
{
SetDefaults();
this.Url = Src;
}

public Script(string Content, string Type)
{
SetDefaults();
this.Content = Content;
this.Type = Type;
}

public Script(string Src, string Integrity, string CrossOrigin)
{
SetDefaults();
this.Url = Src;
this.Integrity = Integrity;
this.CrossOrigin = CrossOrigin;
}

public Script(string Src, string Integrity, string CrossOrigin, string Type, string Content, ResourceLocation Location, string Bundle, bool Reload, Dictionary<string, string> DataAttributes, string RenderMode)
{
SetDefaults();
this.Url = Src;
this.Integrity = Integrity;
this.CrossOrigin = CrossOrigin;
this.Type = Type;
this.Content = Content;
this.Location = Location;
this.Bundle = Bundle;
this.Reload = Reload;
this.DataAttributes = DataAttributes;
this.RenderMode = RenderMode;
}
}
}
Loading