diff --git a/Oqtane.Client/Modules/Controls/Section.razor b/Oqtane.Client/Modules/Controls/Section.razor index 4f33b42f..f21a7e17 100644 --- a/Oqtane.Client/Modules/Controls/Section.razor +++ b/Oqtane.Client/Modules/Controls/Section.razor @@ -4,12 +4,12 @@
- +
@_heading
- +  
diff --git a/Oqtane.Client/Modules/Controls/TabStrip.razor b/Oqtane.Client/Modules/Controls/TabStrip.razor index 8d8d3838..ee6a6c82 100644 --- a/Oqtane.Client/Modules/Controls/TabStrip.razor +++ b/Oqtane.Client/Modules/Controls/TabStrip.razor @@ -11,13 +11,13 @@
  • @if (tabPanel.Name == ActiveTab) { - + @DisplayHeading(tabPanel.Name, tabPanel.Heading) } else { - + @DisplayHeading(tabPanel.Name, tabPanel.Heading) } diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index b4d8eb7e..ce4ed823 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -55,7 +55,7 @@ namespace Oqtane.Modules var interop = new Interop(JSRuntime); foreach (var resource in Resources.Where(item => item.ResourceType == ResourceType.Script)) { - await interop.LoadScript(resource.Url); + await interop.LoadScript(resource.Url, resource.Integrity ?? "", resource.CrossOrigin ?? ""); } } } diff --git a/Oqtane.Client/Themes/BlazorTheme/Default.razor b/Oqtane.Client/Themes/BlazorTheme/Default.razor index 14b7df03..0b664c50 100644 --- a/Oqtane.Client/Themes/BlazorTheme/Default.razor +++ b/Oqtane.Client/Themes/BlazorTheme/Default.razor @@ -1,5 +1,6 @@ @namespace Oqtane.Themes.BlazorTheme @inherits ThemeBase +@implements IThemeControl
    @@ -30,6 +31,16 @@ public override List Resources => new List() { + new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css", Integrity = "sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T", CrossOrigin = "anonymous" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" } }; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + var interop = new Interop(JSRuntime); + await interop.LoadBootstrapJS(); + } + } } \ No newline at end of file diff --git a/Oqtane.Client/Themes/OqtaneTheme/Default.razor b/Oqtane.Client/Themes/OqtaneTheme/Default.razor index 7d8fa135..09035b5a 100644 --- a/Oqtane.Client/Themes/OqtaneTheme/Default.razor +++ b/Oqtane.Client/Themes/OqtaneTheme/Default.razor @@ -1,5 +1,6 @@ @namespace Oqtane.Themes.OqtaneTheme @inherits ThemeBase +@implements IThemeControl
    @@ -23,10 +24,16 @@ public override List Resources => new List() { - new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "BootswatchCyborg.css" }, - // remote stylesheets can be linked using the format below, however we want the default theme to display properly in local development scenarios where an Internet connection is not available - //new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/cyborg/bootstrap.min.css", Integrity = "sha384-l7xaoY0cJM4h9xh1RfazbgJVUZvdtyLWPueWNtLAphf/UbBgOVzqbOTogxPwYLHM", CrossOrigin = "anonymous" }, + new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/cyborg/bootstrap.min.css", Integrity = "sha384-l7xaoY0cJM4h9xh1RfazbgJVUZvdtyLWPueWNtLAphf/UbBgOVzqbOTogxPwYLHM", CrossOrigin = "anonymous" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" } }; + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + var interop = new Interop(JSRuntime); + await interop.LoadBootstrapJS(); + } + } } diff --git a/Oqtane.Client/Themes/ThemeBase.cs b/Oqtane.Client/Themes/ThemeBase.cs index d2289a8b..0d3d9454 100644 --- a/Oqtane.Client/Themes/ThemeBase.cs +++ b/Oqtane.Client/Themes/ThemeBase.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace Oqtane.Themes { - public abstract class ThemeBase : ComponentBase, IThemeControl + public abstract class ThemeBase : ComponentBase { [Inject] protected IJSRuntime JSRuntime { get; set; } @@ -34,7 +34,7 @@ namespace Oqtane.Themes var interop = new Interop(JSRuntime); foreach (var resource in Resources.Where(item => item.ResourceType == ResourceType.Script)) { - await interop.LoadScript(resource.Url); + await interop.LoadScript(resource.Url, resource.Integrity ?? "", resource.CrossOrigin ?? ""); } } } diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs index 54f9ab22..f403ab23 100644 --- a/Oqtane.Client/UI/Interop.cs +++ b/Oqtane.Client/UI/Interop.cs @@ -117,11 +117,23 @@ namespace Oqtane.UI } } - public async Task LoadScript(string path) + public async Task LoadScript(string url, string integrity, string crossorigin) { try { - await _jsRuntime.InvokeVoidAsync("Oqtane.Interop.loadScript", path); + await _jsRuntime.InvokeVoidAsync("Oqtane.Interop.loadScript", url, integrity, crossorigin); + } + catch + { + // ignore exception + } + } + + public async Task LoadBootstrapJS() + { + try + { + await _jsRuntime.InvokeVoidAsync("Oqtane.Interop.loadBootstrapJS"); } catch { diff --git a/Oqtane.Server/Pages/_Host.cshtml b/Oqtane.Server/Pages/_Host.cshtml index 35608c1e..c48f226b 100644 --- a/Oqtane.Server/Pages/_Host.cshtml +++ b/Oqtane.Server/Pages/_Host.cshtml @@ -14,7 +14,6 @@ - @@ -36,9 +35,6 @@
    - - - @if (Configuration.GetSection("Runtime").Value == "WebAssembly") { diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index b5c866ec..7588de0f 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -198,12 +198,30 @@ Oqtane.Interop = { script.onerror = rej(); }); }, - loadScript: async function (path) { + loadScript: async function (url, integrity, crossorigin) { const promise = new Promise((resolve, reject) => { - loadjs(path, { returnPromise: true }) - .then(function () { resolve(true) }) - .catch(function (pathsNotFound) { reject(false) }); + + if (loadjs.isDefined(url)) { + resolve(true); + return; + } + + loadjs(url, url, { + async: false, + returnPromise: true, + before: function (path, element) { + if (path === url && integrity !== '') { + element.integrity = integrity; + } + if (path === url && crossorigin !== '') { + element.crossOrigin = crossorigin; + } + } + }) + .then(function () { resolve(true) }) + .catch(function (pathsNotFound) { reject(false) }); }); + const result = await promise; return; }, @@ -325,5 +343,31 @@ Oqtane.Interop = { setInterval(function () { window.location.href = url; }, wait * 1000); + }, + loadBootstrapJS: async function () { + if (!loadjs.isDefined('Bootstrap')) { + const bootstrap = loadjs(['https://code.jquery.com/jquery-3.3.1.slim.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js', 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js'], 'Bootstrap', { + async: false, + returnPromise: true, + before: function (path, element) { + if (path === 'https://code.jquery.com/jquery-3.3.1.slim.min.js') { + element.integrity = 'sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo'; + element.crossOrigin = 'anonymous'; + } + if (path === 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js') { + element.integrity = 'sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1'; + element.crossOrigin = 'anonymous'; + } + if (path === 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js') { + element.integrity = 'sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM'; + element.crossOrigin = 'anonymous'; + } + } + }) + .then(function () { }) + .catch(function (pathsNotFound) { }); + + await bootstrap; + } } }; diff --git a/Oqtane.Server/wwwroot/js/quill-interop.js b/Oqtane.Server/wwwroot/js/quill-interop.js index 22332064..9467584a 100644 --- a/Oqtane.Server/wwwroot/js/quill-interop.js +++ b/Oqtane.Server/wwwroot/js/quill-interop.js @@ -5,27 +5,28 @@ Oqtane.RichTextEditor = { quillElement, toolBar, readOnly, placeholder, theme, debugLevel) { - const loadQuill = loadjs(['js/quill1.3.6.min.js', 'js/quill-blot-formatter.min.js'], 'Quill', - { async: true, returnPromise: true }) - .then(function () { - Quill.register('modules/blotFormatter', QuillBlotFormatter.default); + if (!loadjs.isDefined('Quill')) { + const loadQuill = loadjs(['js/quill1.3.6.min.js', 'js/quill-blot-formatter.min.js'], 'Quill', + { async: true, returnPromise: true }) + .then(function () { + Quill.register('modules/blotFormatter', QuillBlotFormatter.default); + }) + .catch(function (pathsNotFound) { }); + await loadQuill; + } - var options = { - debug: debugLevel, - modules: { - toolbar: toolBar, - blotFormatter: {} - }, - placeholder: placeholder, - readOnly: readOnly, - theme: theme - }; + var options = { + debug: debugLevel, + modules: { + toolbar: toolBar, + blotFormatter: {} + }, + placeholder: placeholder, + readOnly: readOnly, + theme: theme + }; - new Quill(quillElement, options); - }) - .catch(function (pathsNotFound) { }); - - await loadQuill; + new Quill(quillElement, options); }, getQuillContent: function (editorElement) { return JSON.stringify(editorElement.__quill.getContents());