changes to support page level scripts, ability to detect prerendering
This commit is contained in:
parent
e41d9008b3
commit
ded326c822
|
@ -1,6 +1,8 @@
|
||||||
|
@using Microsoft.AspNetCore.Http
|
||||||
@inject IInstallationService InstallationService
|
@inject IInstallationService InstallationService
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
@inject SiteState SiteState
|
@inject SiteState SiteState
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
|
|
||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
|
@ -30,35 +32,47 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string AntiForgeryToken { get; set; }
|
public string AntiForgeryToken { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Runtime { get; set; }
|
public string Runtime { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string RenderMode { get; set; }
|
public string RenderMode { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int VisitorId { get; set; }
|
public int VisitorId { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string RemoteIPAddress { get; set; }
|
public string RemoteIPAddress { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string AuthorizationToken { get; set; }
|
public string AuthorizationToken { get; set; }
|
||||||
|
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private string _display = "display: none;";
|
private string _display = "display: none;";
|
||||||
private Installation _installation = new Installation { Success = false, Message = "" };
|
private Installation _installation = new Installation { Success = false, Message = "" };
|
||||||
|
|
||||||
private PageState PageState { get; set; }
|
private PageState PageState { get; set; }
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
private IHttpContextAccessor accessor;
|
||||||
{
|
|
||||||
SiteState.RemoteIPAddress = RemoteIPAddress;
|
protected override async Task OnParametersSetAsync()
|
||||||
SiteState.AntiForgeryToken = AntiForgeryToken;
|
{
|
||||||
SiteState.AuthorizationToken = AuthorizationToken;
|
SiteState.RemoteIPAddress = RemoteIPAddress;
|
||||||
|
SiteState.AntiForgeryToken = AntiForgeryToken;
|
||||||
|
SiteState.AuthorizationToken = AuthorizationToken;
|
||||||
|
|
||||||
|
accessor = (IHttpContextAccessor)ServiceProvider.GetService(typeof(IHttpContextAccessor));
|
||||||
|
if (accessor != null)
|
||||||
|
{
|
||||||
|
SiteState.IsPrerendering = !accessor.HttpContext.Response.HasStarted;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SiteState.IsPrerendering = true;
|
||||||
|
}
|
||||||
|
|
||||||
_installation = await InstallationService.IsInstalled();
|
_installation = await InstallationService.IsInstalled();
|
||||||
if (_installation.Alias != null)
|
if (_installation.Alias != null)
|
||||||
|
|
|
@ -278,6 +278,7 @@ namespace Oqtane.Modules
|
||||||
SiteState.Properties.PageTitle = title;
|
SiteState.Properties.PageTitle = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// note - only supports links and meta tags - not scripts
|
||||||
public void AddHeadContent(string content)
|
public void AddHeadContent(string content)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(SiteState.Properties.HeadContent))
|
if (string.IsNullOrEmpty(SiteState.Properties.HeadContent))
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
@DynamicComponent
|
@DynamicComponent
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[CascadingParameter] PageState PageState { get; set; }
|
[CascadingParameter] PageState PageState { get; set; }
|
||||||
|
|
||||||
RenderFragment DynamicComponent { get; set; }
|
RenderFragment DynamicComponent { get; set; }
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
|
@ -38,21 +38,21 @@
|
||||||
favicon = Utilities.FileUrl(PageState.Alias, PageState.Site.FaviconFileId.Value);
|
favicon = Utilities.FileUrl(PageState.Alias, PageState.Site.FaviconFileId.Value);
|
||||||
}
|
}
|
||||||
headcontent += $"<link id=\"app-favicon\" rel=\"shortcut icon\" type=\"image/x-icon\" href=\"{favicon}\" />\n";
|
headcontent += $"<link id=\"app-favicon\" rel=\"shortcut icon\" type=\"image/x-icon\" href=\"{favicon}\" />\n";
|
||||||
// head content
|
|
||||||
if (!string.IsNullOrEmpty(PageState.Site.HeadContent))
|
|
||||||
{
|
|
||||||
headcontent += ProcessHeadContent(PageState.Site.HeadContent, "site") + "\n";
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrEmpty(PageState.Page.HeadContent))
|
|
||||||
{
|
|
||||||
headcontent += ProcessHeadContent(PageState.Page.HeadContent, $"page{PageState.Page.PageId}") + "\n";
|
|
||||||
}
|
|
||||||
// stylesheets
|
// stylesheets
|
||||||
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
|
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;
|
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
|
||||||
headcontent += CreateLink(url, resource.Integrity, resource.CrossOrigin) + "\n";
|
headcontent += CreateLink(url, resource.Integrity, resource.CrossOrigin) + "\n";
|
||||||
}
|
}
|
||||||
|
// head content
|
||||||
|
if (!string.IsNullOrEmpty(PageState.Site.HeadContent))
|
||||||
|
{
|
||||||
|
headcontent += PageState.Site.HeadContent + "\n";
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(PageState.Page.HeadContent))
|
||||||
|
{
|
||||||
|
headcontent += PageState.Page.HeadContent + "\n";
|
||||||
|
}
|
||||||
SiteState.Properties.HeadContent = headcontent;
|
SiteState.Properties.HeadContent = headcontent;
|
||||||
|
|
||||||
DynamicComponent = builder =>
|
DynamicComponent = builder =>
|
||||||
|
@ -65,87 +65,75 @@
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
|
if (!firstRender)
|
||||||
{
|
{
|
||||||
// inject scripts into page
|
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
|
||||||
var interop = new Interop(JSRuntime);
|
|
||||||
var count = 0;
|
|
||||||
var index = PageState.Page.HeadContent.IndexOf("<script");
|
|
||||||
while (index >= 0)
|
|
||||||
{
|
{
|
||||||
var script = PageState.Page.HeadContent.Substring(index, PageState.Page.HeadContent.IndexOf("</script>", index) + 9 - index);
|
// inject scripts into page dynamically
|
||||||
var attributes = script.Substring(0, script.IndexOf(">")).Replace("\"", "").Split(" ");
|
var interop = new Interop(JSRuntime);
|
||||||
string id = "";
|
var scripts = new List<object>();
|
||||||
string src = "";
|
var count = 0;
|
||||||
string integrity = "";
|
var index = PageState.Page.HeadContent.IndexOf("<script");
|
||||||
string crossorigin = "";
|
while (index >= 0)
|
||||||
string type = "";
|
|
||||||
foreach (var attribute in attributes)
|
|
||||||
{
|
{
|
||||||
if (attribute.Contains("="))
|
var script = PageState.Page.HeadContent.Substring(index, PageState.Page.HeadContent.IndexOf("</script>", index) + 9 - index);
|
||||||
|
// get script attributes
|
||||||
|
var attributes = script.Substring(0, script.IndexOf(">")).Replace("\"", "").Split(" ");
|
||||||
|
string id = "";
|
||||||
|
string src = "";
|
||||||
|
string integrity = "";
|
||||||
|
string crossorigin = "";
|
||||||
|
string type = "";
|
||||||
|
foreach (var attribute in attributes)
|
||||||
{
|
{
|
||||||
var value = attribute.Split("=");
|
if (attribute.Contains("="))
|
||||||
switch (value[0])
|
|
||||||
{
|
{
|
||||||
case "id":
|
var value = attribute.Split("=");
|
||||||
id = value[1];
|
switch (value[0])
|
||||||
break;
|
{
|
||||||
case "src":
|
case "id":
|
||||||
src = value[1];
|
id = value[1];
|
||||||
break;
|
break;
|
||||||
case "integrity":
|
case "src":
|
||||||
integrity = value[1];
|
src = value[1];
|
||||||
break;
|
break;
|
||||||
case "crossorigin":
|
case "integrity":
|
||||||
crossorigin = value[1];
|
integrity = value[1];
|
||||||
break;
|
break;
|
||||||
case "type":
|
case "crossorigin":
|
||||||
type = value[1];
|
crossorigin = value[1];
|
||||||
break;
|
break;
|
||||||
|
case "type":
|
||||||
|
type = value[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// inject script
|
||||||
if (!string.IsNullOrEmpty(src))
|
if (!string.IsNullOrEmpty(src))
|
||||||
{
|
|
||||||
src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src;
|
|
||||||
await interop.IncludeScript(id, src, integrity, crossorigin, type, "", "head");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (id == "")
|
|
||||||
{
|
{
|
||||||
count += 1;
|
src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src;
|
||||||
id = $"page{PageState.Page.PageId}-script{count}";
|
scripts.Add(new { href = src, bundle = "", integrity = integrity, crossorigin = crossorigin, es6module = (type == "module"), location = "head" });
|
||||||
}
|
}
|
||||||
index = script.IndexOf(">") + 1;
|
else
|
||||||
await interop.IncludeScript(id, "", "", "", "", script.Substring(index, script.IndexOf("</script>") - index), "head");
|
{
|
||||||
|
// inline script must have an id attribute
|
||||||
|
if (id == "")
|
||||||
|
{
|
||||||
|
count += 1;
|
||||||
|
id = $"page{PageState.Page.PageId}-script{count}";
|
||||||
|
}
|
||||||
|
index = script.IndexOf(">") + 1;
|
||||||
|
await interop.IncludeScript(id, "", "", "", "", script.Substring(index, script.IndexOf("</script>") - index), "head");
|
||||||
|
}
|
||||||
|
index = PageState.Page.HeadContent.IndexOf("<script", index + 1);
|
||||||
}
|
}
|
||||||
index = PageState.Page.HeadContent.IndexOf("<script", index + 1);
|
if (scripts.Any())
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string ProcessHeadContent(string headcontent, string id)
|
|
||||||
{
|
|
||||||
// iterate scripts
|
|
||||||
if (!string.IsNullOrEmpty(headcontent))
|
|
||||||
{
|
|
||||||
var count = 0;
|
|
||||||
var index = headcontent.IndexOf("<script");
|
|
||||||
while (index >= 0)
|
|
||||||
{
|
|
||||||
var script = headcontent.Substring(index, headcontent.IndexOf("</script>", index) + 9 - index);
|
|
||||||
if (!script.Contains("src=") && !script.Contains("id="))
|
|
||||||
{
|
{
|
||||||
count += 1;
|
await interop.IncludeScripts(scripts.ToArray());
|
||||||
id += $"-script{count}";
|
|
||||||
headcontent = headcontent.Replace(script, script.Replace("<script", $"<script id=\"{id}\""));
|
|
||||||
index += id.Length;
|
|
||||||
}
|
}
|
||||||
index = headcontent.IndexOf("<script", index + 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return headcontent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string CreateLink(string url, string integrity, string crossorigin)
|
private string CreateLink(string url, string integrity, string crossorigin)
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
{
|
{
|
||||||
<link id="app-manifest" rel="manifest" />
|
<link id="app-manifest" rel="manifest" />
|
||||||
}
|
}
|
||||||
@Html.Raw(Model.HeadResources)
|
|
||||||
<component type="typeof(Oqtane.Head)" render-mode="@((RenderMode)Enum.Parse(typeof(RenderMode), Model.RenderMode, true))" />
|
<component type="typeof(Oqtane.Head)" render-mode="@((RenderMode)Enum.Parse(typeof(RenderMode), Model.RenderMode, true))" />
|
||||||
|
@Html.Raw(Model.HeadResources)
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@if (string.IsNullOrEmpty(Model.Message))
|
@if (string.IsNullOrEmpty(Model.Message))
|
||||||
|
|
|
@ -19,7 +19,6 @@ using Microsoft.Extensions.Primitives;
|
||||||
using Oqtane.Enums;
|
using Oqtane.Enums;
|
||||||
using Oqtane.Security;
|
using Oqtane.Security;
|
||||||
using Oqtane.Extensions;
|
using Oqtane.Extensions;
|
||||||
using Oqtane.Themes;
|
|
||||||
|
|
||||||
namespace Oqtane.Pages
|
namespace Oqtane.Pages
|
||||||
{
|
{
|
||||||
|
@ -130,10 +129,8 @@ namespace Oqtane.Pages
|
||||||
{
|
{
|
||||||
PWAScript = CreatePWAScript(alias, site, route);
|
PWAScript = CreatePWAScript(alias, site, route);
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(site.HeadContent))
|
// site level scripts
|
||||||
{
|
HeadResources += ParseScripts(site.HeadContent);
|
||||||
ProcessHeadContent(site.HeadContent, "site");
|
|
||||||
}
|
|
||||||
|
|
||||||
// get jwt token for downstream APIs
|
// get jwt token for downstream APIs
|
||||||
if (User.Identity.IsAuthenticated)
|
if (User.Identity.IsAuthenticated)
|
||||||
|
@ -174,8 +171,6 @@ namespace Oqtane.Pages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessHeadContent(page.HeadContent, $"page{page.PageId}");
|
|
||||||
|
|
||||||
// include global resources
|
// include global resources
|
||||||
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
|
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
|
||||||
foreach (Assembly assembly in assemblies)
|
foreach (Assembly assembly in assemblies)
|
||||||
|
@ -427,27 +422,20 @@ namespace Oqtane.Pages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessHeadContent(string headcontent, string id)
|
private string ParseScripts(string headcontent)
|
||||||
{
|
{
|
||||||
// add scripts to page
|
// iterate scripts
|
||||||
|
var scripts = "";
|
||||||
if (!string.IsNullOrEmpty(headcontent))
|
if (!string.IsNullOrEmpty(headcontent))
|
||||||
{
|
{
|
||||||
var count = 0;
|
|
||||||
var index = headcontent.IndexOf("<script");
|
var index = headcontent.IndexOf("<script");
|
||||||
while (index >= 0)
|
while (index >= 0)
|
||||||
{
|
{
|
||||||
var script = headcontent.Substring(index, headcontent.IndexOf("</script>", index) + 9 - index);
|
scripts += headcontent.Substring(index, headcontent.IndexOf("</script>", index) + 9 - index);
|
||||||
if (!script.Contains("src=") && !script.Contains("id="))
|
|
||||||
{
|
|
||||||
count += 1;
|
|
||||||
id += $"-script{count}";
|
|
||||||
script = script.Replace("<script", $"<script id=\"{id}\"");
|
|
||||||
index += id.Length;
|
|
||||||
}
|
|
||||||
HeadResources += script + Environment.NewLine;
|
|
||||||
index = headcontent.IndexOf("<script", index + 1);
|
index = headcontent.IndexOf("<script", index + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return scripts;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessResource(Resource resource, int count, Alias alias)
|
private void ProcessResource(Resource resource, int count, Alias alias)
|
||||||
|
|
|
@ -116,6 +116,10 @@ Oqtane.Interop = {
|
||||||
else {
|
else {
|
||||||
script = document.getElementById(id);
|
script = document.getElementById(id);
|
||||||
}
|
}
|
||||||
|
if (script !== null) {
|
||||||
|
script.remove();
|
||||||
|
script = null;
|
||||||
|
}
|
||||||
if (script === null) {
|
if (script === null) {
|
||||||
script = document.createElement("script");
|
script = document.createElement("script");
|
||||||
if (id !== "") {
|
if (id !== "") {
|
||||||
|
@ -139,50 +143,22 @@ Oqtane.Interop = {
|
||||||
script.async = false;
|
script.async = false;
|
||||||
this.addScript(script, location)
|
this.addScript(script, location)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log(src + ' loaded');
|
if (src !== "") {
|
||||||
|
console.log(src + ' loaded');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(id + ' loaded');
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.error(src + ' failed');
|
if (src !== "") {
|
||||||
|
console.error(src + ' failed');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(id + ' failed');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
if (script.id !== id) {
|
|
||||||
script.setAttribute('id', id);
|
|
||||||
}
|
|
||||||
if (type !== "") {
|
|
||||||
if (script.type !== type) {
|
|
||||||
script.setAttribute('type', type);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
script.removeAttribute('type');
|
|
||||||
}
|
|
||||||
if (src !== "") {
|
|
||||||
if (script.src !== this.getAbsoluteUrl(src)) {
|
|
||||||
script.removeAttribute('integrity');
|
|
||||||
script.removeAttribute('crossorigin');
|
|
||||||
script.src = src;
|
|
||||||
}
|
|
||||||
if (integrity !== "") {
|
|
||||||
if (script.integrity !== integrity) {
|
|
||||||
script.setAttribute('integrity', integrity);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
script.removeAttribute('integrity');
|
|
||||||
}
|
|
||||||
if (crossorigin !== "") {
|
|
||||||
if (script.crossOrigin !== crossorigin) {
|
|
||||||
script.setAttribute('crossorigin', crossorigin);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
script.removeAttribute('crossorigin');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (script.innerHTML !== content) {
|
|
||||||
script.innerHTML = content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
addScript: function (script, location) {
|
addScript: function (script, location) {
|
||||||
if (location === 'head') {
|
if (location === 'head') {
|
||||||
|
@ -234,6 +210,10 @@ Oqtane.Interop = {
|
||||||
if (path === scripts[s].href && scripts[s].es6module === true) {
|
if (path === scripts[s].href && scripts[s].es6module === true) {
|
||||||
element.type = "module";
|
element.type = "module";
|
||||||
}
|
}
|
||||||
|
if (path === scripts[s].href && scripts[s].location === 'body') {
|
||||||
|
document.body.appendChild(element);
|
||||||
|
return false; // return false to bypass default DOM insertion mechanism
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace Oqtane.Shared
|
||||||
public string AntiForgeryToken { get; set; } // passed from server for use in service calls on client
|
public string AntiForgeryToken { get; set; } // passed from server for use in service calls on client
|
||||||
public string AuthorizationToken { get; set; } // passed from server for use in service calls on client
|
public string AuthorizationToken { get; set; } // passed from server for use in service calls on client
|
||||||
public string RemoteIPAddress { get; set; } // passed from server as cannot be reliably retrieved on client
|
public string RemoteIPAddress { get; set; } // passed from server as cannot be reliably retrieved on client
|
||||||
|
public bool IsPrerendering{ get; set; }
|
||||||
|
|
||||||
private dynamic _properties;
|
private dynamic _properties;
|
||||||
public dynamic Properties => _properties ?? (_properties = new PropertyDictionary());
|
public dynamic Properties => _properties ?? (_properties = new PropertyDictionary());
|
||||||
|
|
Loading…
Reference in New Issue
Block a user