Merge pull request #18 from oqtane/master

Sync Master
This commit is contained in:
Jim Spillane
2020-06-17 12:14:54 -04:00
committed by GitHub
47 changed files with 399 additions and 281 deletions

View File

@ -1,7 +1,6 @@
@namespace Oqtane.Modules.Admin.Login @namespace Oqtane.Modules.Admin.Login
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IJSRuntime JsRuntime
@inject IUserService UserService @inject IUserService UserService
@inject IServiceProvider ServiceProvider @inject IServiceProvider ServiceProvider
@ -96,7 +95,7 @@
{ {
await logger.LogInformation("Login Successful For Username {Username}", _username); await logger.LogInformation("Login Successful For Username {Username}", _username);
// complete the login on the server so that the cookies are set correctly on SignalR // complete the login on the server so that the cookies are set correctly on SignalR
var interop = new Interop(JsRuntime); var interop = new Interop(JSRuntime);
string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken"); string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken");
var fields = new { __RequestVerificationToken = antiforgerytoken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl }; var fields = new { __RequestVerificationToken = antiforgerytoken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
await interop.SubmitForm($"/{PageState.Alias.AliasId}/pages/login/", fields); await interop.SubmitForm($"/{PageState.Alias.AliasId}/pages/login/", fields);

View File

@ -4,7 +4,6 @@
@inject IFileService FileService @inject IFileService FileService
@inject IModuleDefinitionService ModuleDefinitionService @inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService @inject IPackageService PackageService
@inject IJSRuntime JsRuntime
@if (_packages != null) @if (_packages != null)
{ {
@ -79,7 +78,7 @@
try try
{ {
ShowProgressIndicator(); ShowProgressIndicator();
var interop = new Interop(JsRuntime); var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(), 3); await interop.RedirectBrowser(NavigateUrl(), 3);
await ModuleDefinitionService.InstallModuleDefinitionsAsync(); await ModuleDefinitionService.InstallModuleDefinitionsAsync();
} }

View File

@ -3,7 +3,6 @@
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IModuleDefinitionService ModuleDefinitionService @inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService @inject IPackageService PackageService
@inject IJSRuntime JsRuntime
@if (_moduleDefinitions == null) @if (_moduleDefinitions == null)
{ {
@ -86,7 +85,7 @@ else
await PackageService.DownloadPackageAsync(moduledefinitionname, version, "Modules"); await PackageService.DownloadPackageAsync(moduledefinitionname, version, "Modules");
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", moduledefinitionname, version); await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", moduledefinitionname, version);
ShowProgressIndicator(); ShowProgressIndicator();
var interop = new Interop(JsRuntime); var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(), 3); await interop.RedirectBrowser(NavigateUrl(), 3);
await ModuleDefinitionService.InstallModuleDefinitionsAsync(); await ModuleDefinitionService.InstallModuleDefinitionsAsync();
} }
@ -102,7 +101,7 @@ else
try try
{ {
ShowProgressIndicator(); ShowProgressIndicator();
var interop = new Interop(JsRuntime); var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(), 3); await interop.RedirectBrowser(NavigateUrl(), 3);
await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId); await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId);
} }

View File

@ -4,7 +4,6 @@
@inject IFileService FileService @inject IFileService FileService
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject IPackageService PackageService @inject IPackageService PackageService
@inject IJSRuntime JsRuntime
@if (_packages != null) @if (_packages != null)
{ {
@ -79,7 +78,7 @@
try try
{ {
ShowProgressIndicator(); ShowProgressIndicator();
var interop = new Interop(JsRuntime); var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(), 3); await interop.RedirectBrowser(NavigateUrl(), 3);
await ThemeService.InstallThemesAsync(); await ThemeService.InstallThemesAsync();
} }

View File

@ -4,7 +4,6 @@
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject IPackageService PackageService @inject IPackageService PackageService
@inject IJSRuntime JsRuntime
@if (_themes == null) @if (_themes == null)
{ {
@ -86,7 +85,7 @@ else
await PackageService.DownloadPackageAsync(themename, version, "Themes"); await PackageService.DownloadPackageAsync(themename, version, "Themes");
await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", themename, version); await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", themename, version);
ShowProgressIndicator(); ShowProgressIndicator();
var interop = new Interop(JsRuntime); var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(), 3); await interop.RedirectBrowser(NavigateUrl(), 3);
await ThemeService.InstallThemesAsync(); await ThemeService.InstallThemesAsync();
} }
@ -102,7 +101,7 @@ else
try try
{ {
ShowProgressIndicator(); ShowProgressIndicator();
var interop = new Interop(JsRuntime); var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(), 3); await interop.RedirectBrowser(NavigateUrl(), 3);
await ThemeService.DeleteThemeAsync(Theme.ThemeName); await ThemeService.DeleteThemeAsync(Theme.ThemeName);
} }

View File

@ -4,7 +4,6 @@
@inject IFileService FileService @inject IFileService FileService
@inject IPackageService PackageService @inject IPackageService PackageService
@inject IInstallationService InstallationService @inject IInstallationService InstallationService
@inject IJSRuntime JsRuntime
@if (_package != null) @if (_package != null)
{ {
@ -71,7 +70,7 @@
try try
{ {
ShowProgressIndicator(); ShowProgressIndicator();
var interop = new Interop(JsRuntime); var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(), 3); await interop.RedirectBrowser(NavigateUrl(), 3);
await InstallationService.Upgrade(); await InstallationService.Upgrade();
} }
@ -88,7 +87,7 @@
{ {
await PackageService.DownloadPackageAsync(packageid, version, "Framework"); await PackageService.DownloadPackageAsync(packageid, version, "Framework");
ShowProgressIndicator(); ShowProgressIndicator();
var interop = new Interop(JsRuntime); var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(), 3); await interop.RedirectBrowser(NavigateUrl(), 3);
await InstallationService.Upgrade(); await InstallationService.Upgrade();
} }

View File

@ -73,7 +73,7 @@ else
} }
</TabPanel> </TabPanel>
<TabPanel Name="Profile"> <TabPanel Name="Profile">
@if (profiles != null) @if (profiles != null && settings != null)
{ {
<table class="table table-borderless"> <table class="table table-borderless">
@foreach (Profile profile in profiles) @foreach (Profile profile in profiles)
@ -126,7 +126,16 @@ else
</Row> </Row>
<Detail> <Detail>
<td colspan="2"></td> <td colspan="2"></td>
<td colspan="3">@(context.Body.Length > 100 ? context.Body.Substring(0, 100) : context.Body)</td> <td colspan="3">
@{
string input = "___";
if (context.Body.Contains(input)){
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
} }
@(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body)
</td>
</Detail> </Detail>
</Pager> </Pager>
} }
@ -149,7 +158,16 @@ else
</Row> </Row>
<Detail> <Detail>
<td colspan="2"></td> <td colspan="2"></td>
<td colspan="3">@(context.Body.Length > 100 ? context.Body.Substring(0, 100) : context.Body)</td> <td colspan="3">
@{
string input = "___";
if (context.Body.Contains(input)){
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
} }
@(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body)
</td>
</Detail> </Detail>
</Pager> </Pager>
} }

View File

@ -11,17 +11,35 @@
<td> <td>
<label class="control-label">@title: </label> <label class="control-label">@title: </label>
</td> </td>
@if (title == "From")
{
<td>
<input class="form-control" @bind="@username" readonly />
</td>
}
@if (title == "To")
{
<td> <td>
<input class="form-control" @bind="@username" /> <input class="form-control" @bind="@username" />
</td> </td>
}
</tr> </tr>
<tr> <tr>
<td> <td>
<label class="control-label">Subject: </label> <label class="control-label">Subject: </label>
</td> </td>
@if (title == "From")
{
<td>
<input class="form-control" @bind="@subject" readonly />
</td>
}
@if (title == "To")
{
<td> <td>
<input class="form-control" @bind="@subject" /> <input class="form-control" @bind="@subject" />
</td> </td>
}
</tr> </tr>
@if (title == "From") @if (title == "From")
{ {
@ -30,10 +48,23 @@
<label class="control-label">Date: </label> <label class="control-label">Date: </label>
</td> </td>
<td> <td>
<input class="form-control" @bind="@createdon" /> <input class="form-control" @bind="@createdon" readonly />
</td> </td>
</tr> </tr>
} }
@if (title == "From")
{
<tr>
<td>
<label class="control-label">Message: </label>
</td>
<td>
<textarea class="form-control" @bind="@body" rows="5" readonly />
</td>
</tr>
}
@if (title == "To")
{
<tr> <tr>
<td> <td>
<label class="control-label">Message: </label> <label class="control-label">Message: </label>
@ -42,23 +73,30 @@
<textarea class="form-control" @bind="@body" rows="5" /> <textarea class="form-control" @bind="@body" rows="5" />
</td> </td>
</tr> </tr>
}
</table> </table>
@if (reply != string.Empty) @if (reply != string.Empty)
{ {
<button type="button" class="btn btn-primary" @onclick="Send">Send</button> <button type="button" class="btn btn-primary" @onclick="Send">Send</button> }
}
else else
{ {
if (title == "From") if (title == "From")
{ {
<button type="button" class="btn btn-primary" @onclick="Reply">Reply</button> <button type="button" class="btn btn-primary" @onclick="Reply">Reply</button>}
}
} }
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
<br /> <br />
<br /> <br />
<p>@reply</p> @if (title == "To")
} {
<div class="control-group">
<label class="control-label">Original Message </label>
<textarea class="form-control" @bind="@reply" rows="5" readonly />
</div>
}
}
@code { @code {
private int notificationid; private int notificationid;
@ -124,8 +162,12 @@
private void Reply() private void Reply()
{ {
title = "To"; title = "To";
if (!subject.Contains("RE:"))
{
subject = "RE: " + subject; subject = "RE: " + subject;
}
reply = body; reply = body;
body = "\n\n____________________________________________\nSent: " + createdon + "\nSubject: " + subject + "\n\n" + body;
StateHasChanged(); StateHasChanged();
} }
@ -165,5 +207,5 @@
AddModuleMessage("Error Adding Notification", MessageType.Error); AddModuleMessage("Error Adding Notification", MessageType.Error);
} }
} }
} }

View File

@ -90,7 +90,8 @@
if (!string.IsNullOrEmpty(IconName)) if (!string.IsNullOrEmpty(IconName))
{ {
_iconSpan = $"<span class=\"oi oi-{IconName}\"></span>&nbsp;"; _iconSpan = $"<span class=\"oi oi-{IconName}\"></span>{(IconOnly?"":"&nbsp")}";
} }
_url = EditUrl(Action, _parameters); _url = EditUrl(Action, _parameters);

View File

@ -4,7 +4,6 @@
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
@inject IFolderService FolderService @inject IFolderService FolderService
@inject IFileService FileService @inject IFileService FileService
@inject IJSRuntime JsRuntime
@if (_folders != null) @if (_folders != null)
{ {
@ -258,7 +257,7 @@
private async Task UploadFile() private async Task UploadFile()
{ {
var interop = new Interop(JsRuntime); var interop = new Interop(JSRuntime);
var upload = await interop.GetFiles(_fileinputid); var upload = await interop.GetFiles(_fileinputid);
if (upload.Length > 0) if (upload.Length > 0)
{ {

View File

@ -1,7 +1,6 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
@inject IJSRuntime JsRuntime
<div class="row" style="margin-bottom: 50px;"> <div class="row" style="margin-bottom: 50px;">
<div class="col"> <div class="col">
@ -108,6 +107,13 @@
[Parameter] [Parameter]
public string DebugLevel { get; set; } = "info"; public string DebugLevel { get; set; } = "info";
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill1.3.6.min.js" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js" }
};
protected override void OnInitialized() protected override void OnInitialized()
{ {
_content = Content; // raw HTML _content = Content; // raw HTML
@ -117,7 +123,9 @@
{ {
if (firstRender) if (firstRender)
{ {
var interop = new RichTextEditorInterop(JsRuntime); await base.OnAfterRenderAsync(firstRender);
var interop = new RichTextEditorInterop(JSRuntime);
await interop.CreateEditor( await interop.CreateEditor(
_editorElement, _editorElement,
@ -143,13 +151,13 @@
public async Task RefreshRichText() public async Task RefreshRichText()
{ {
var interop = new RichTextEditorInterop(JsRuntime); var interop = new RichTextEditorInterop(JSRuntime);
await interop.LoadEditorContent(_editorElement, _content); await interop.LoadEditorContent(_editorElement, _content);
} }
public async Task RefreshRawHtml() public async Task RefreshRawHtml()
{ {
var interop = new RichTextEditorInterop(JsRuntime); var interop = new RichTextEditorInterop(JSRuntime);
_content = await interop.GetHtml(_editorElement); _content = await interop.GetHtml(_editorElement);
StateHasChanged(); StateHasChanged();
} }
@ -157,7 +165,7 @@
public async Task<string> GetHtml() public async Task<string> GetHtml()
{ {
// get rich text content // get rich text content
var interop = new RichTextEditorInterop(JsRuntime); var interop = new RichTextEditorInterop(JSRuntime);
string content = await interop.GetHtml(_editorElement); string content = await interop.GetHtml(_editorElement);
if (_original != content) if (_original != content)
@ -179,7 +187,7 @@
var fileid = _fileManager.GetFileId(); var fileid = _fileManager.GetFileId();
if (fileid != -1) if (fileid != -1)
{ {
var interop = new RichTextEditorInterop(JsRuntime); var interop = new RichTextEditorInterop(JSRuntime);
await interop.InsertImage(_editorElement, ContentUrl(fileid)); await interop.InsertImage(_editorElement, ContentUrl(fileid));
_filemanagervisible = false; _filemanagervisible = false;
_message = string.Empty; _message = string.Empty;
@ -200,19 +208,19 @@
// other rich text editor methods which can be used by developers // other rich text editor methods which can be used by developers
public async Task<string> GetText() public async Task<string> GetText()
{ {
var interop = new RichTextEditorInterop(JsRuntime); var interop = new RichTextEditorInterop(JSRuntime);
return await interop.GetText(_editorElement); return await interop.GetText(_editorElement);
} }
public async Task<string> GetContent() public async Task<string> GetContent()
{ {
var interop = new RichTextEditorInterop(JsRuntime); var interop = new RichTextEditorInterop(JSRuntime);
return await interop.GetContent(_editorElement); return await interop.GetContent(_editorElement);
} }
public async Task EnableEditor(bool mode) public async Task EnableEditor(bool mode)
{ {
var interop = new RichTextEditorInterop(JsRuntime); var interop = new RichTextEditorInterop(JSRuntime);
await interop.EnableEditor(_editorElement, mode); await interop.EnableEditor(_editorElement, mode);
} }
} }

View File

@ -13,7 +13,7 @@ namespace Oqtane.Modules.Controls
_jsRuntime = jsRuntime; _jsRuntime = jsRuntime;
} }
public Task CreateEditor( public async Task CreateEditor(
ElementReference quillElement, ElementReference quillElement,
ElementReference toolbar, ElementReference toolbar,
bool readOnly, bool readOnly,
@ -23,15 +23,14 @@ namespace Oqtane.Modules.Controls
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( await _jsRuntime.InvokeAsync<object>(
"Oqtane.RichTextEditor.createQuill", "Oqtane.RichTextEditor.createQuill",
quillElement, toolbar, readOnly, quillElement, toolbar, readOnly, placeholder, theme, debugLevel);
placeholder, theme, debugLevel); return;
return Task.CompletedTask;
} }
catch catch
{ {
return Task.CompletedTask; // handle exception
} }
} }

View File

@ -4,12 +4,12 @@
<div class="d-flex"> <div class="d-flex">
<div> <div>
<a data-toggle="collapse" class="app-link-unstyled" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name"> <a data-toggle="collapse" class="app-link-unstyled" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
<h5>@_heading</h5> <h5>@_heading</h5>
</a> </a>
</div> </div>
<div class="ml-auto"> <div class="ml-auto">
<a data-toggle="collapse" class="app-link-unstyled float-right" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name"> <a data-toggle="collapse" class="app-link-unstyled float-right" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
<i class="oi oi-chevron-bottom"></i>&nbsp; <i class="oi oi-chevron-bottom"></i>&nbsp;
</a> </a>
</div> </div>

View File

@ -11,13 +11,13 @@
<li class="nav-item"> <li class="nav-item">
@if (tabPanel.Name == ActiveTab) @if (tabPanel.Name == ActiveTab)
{ {
<a class="nav-link active" data-toggle="tab" href="#@tabPanel.Name" role="tab"> <a class="nav-link active" data-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true">
@DisplayHeading(tabPanel.Name, tabPanel.Heading) @DisplayHeading(tabPanel.Name, tabPanel.Heading)
</a> </a>
} }
else else
{ {
<a class="nav-link" data-toggle="tab" href="#@tabPanel.Name" role="tab"> <a class="nav-link" data-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true">
@DisplayHeading(tabPanel.Name, tabPanel.Heading) @DisplayHeading(tabPanel.Name, tabPanel.Heading)
</a> </a>
} }

View File

@ -27,12 +27,8 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
// the following resources should be declared in the RichTextEditor component however the framework currently only supports resource management for modules and themes
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.6.bubble.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.6.bubble.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.6.snow.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.6.snow.css" }
new Resource { ResourceType = ResourceType.Script, Url = "js/quill1.3.6.min.js" },
new Resource { ResourceType = ResourceType.Script, Url = "js/quill-blot-formatter.min.js" },
new Resource { ResourceType = ResourceType.Script, Url = "js/quill-interop.js" }
}; };
private RichTextEditor RichTextEditorHtml; private RichTextEditor RichTextEditorHtml;

View File

@ -7,6 +7,8 @@ using System;
using Oqtane.Enums; using Oqtane.Enums;
using Oqtane.UI; using Oqtane.UI;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.JSInterop;
using System.Linq;
namespace Oqtane.Modules namespace Oqtane.Modules
{ {
@ -19,6 +21,9 @@ namespace Oqtane.Modules
[Inject] [Inject]
protected ILogService LoggingService { get; set; } protected ILogService LoggingService { get; set; }
[Inject]
protected IJSRuntime JSRuntime { get; set; }
[CascadingParameter] [CascadingParameter]
protected PageState PageState { get; set; } protected PageState PageState { get; set; }
@ -28,7 +33,6 @@ namespace Oqtane.Modules
[CascadingParameter] [CascadingParameter]
protected ModuleInstance ModuleInstance { get; set; } protected ModuleInstance ModuleInstance { get; set; }
// optional interface properties // optional interface properties
public virtual SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.View; } set { } } // default security public virtual SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.View; } set { } } // default security
@ -40,6 +44,24 @@ namespace Oqtane.Modules
public virtual List<Resource> Resources { get; set; } public virtual List<Resource> Resources { get; set; }
// base lifecycle method for handling JSInterop script registration
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (Resources != null && Resources.Exists(item => item.ResourceType == ResourceType.Script))
{
var scripts = new List<object>();
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script))
{
scripts.Add(new { href = resource.Url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "" });
}
var interop = new Interop(JSRuntime);
await interop.IncludeScripts(scripts.ToArray());
}
}
}
// path method // path method

View File

@ -153,7 +153,7 @@ namespace Oqtane.Services
public string GetSetting(Dictionary<string, string> settings, string settingName, string defaultValue) public string GetSetting(Dictionary<string, string> settings, string settingName, string defaultValue)
{ {
string value = defaultValue; string value = defaultValue;
if (settings.ContainsKey(settingName)) if (settings != null && settings.ContainsKey(settingName))
{ {
value = settings[settingName]; value = settings[settingName];
} }
@ -162,6 +162,10 @@ namespace Oqtane.Services
public Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue) public Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue)
{ {
if (settings == null)
{
settings = new Dictionary<string, string>();
}
if (settings.ContainsKey(settingName)) if (settings.ContainsKey(settingName))
{ {
settings[settingName] = settingValue; settings[settingName] = settingValue;

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Themes.BlazorTheme @namespace Oqtane.Themes.BlazorTheme
@inherits ThemeBase @inherits ThemeBase
@implements IThemeControl
<div class="breadcrumbs"> <div class="breadcrumbs">
<Breadcrumbs /> <Breadcrumbs />
@ -30,6 +31,11 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" } 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" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://code.jquery.com/jquery-3.3.1.slim.min.js", Integrity = "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js", Integrity = "sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js", Integrity = "sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM", CrossOrigin = "anonymous" }
}; };
} }

View File

@ -1,64 +1,11 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Oqtane.Shared;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.UI;
namespace Oqtane.Themes namespace Oqtane.Themes
{ {
public abstract class ContainerBase : ComponentBase, IContainerControl public abstract class ContainerBase : ThemeBase, IContainerControl
{ {
[Inject]
protected IJSRuntime JSRuntime { get; set; }
[CascadingParameter]
protected PageState PageState { get; set; }
[CascadingParameter] [CascadingParameter]
protected Module ModuleState { get; set; } protected Module ModuleState { get; set; }
public virtual string Name { get; set; }
public virtual string Thumbnail { get; set; }
public string ThemePath()
{
return "Themes/" + GetType().Namespace + "/";
}
public string NavigateUrl()
{
return NavigateUrl(PageState.Page.Path);
}
public string NavigateUrl(string path)
{
return NavigateUrl(path, "");
}
public string NavigateUrl(string path, string parameters)
{
return Utilities.NavigateUrl(PageState.Alias.Path, path, parameters);
}
public string EditUrl(string action, string parameters)
{
return EditUrl(ModuleState.ModuleId, action, parameters);
}
public string EditUrl(int moduleid, string action)
{
return EditUrl(moduleid, action, "");
}
public string EditUrl(int moduleid, string action, string parameters)
{
return EditUrl(PageState.Page.Path, moduleid, action, parameters);
}
public string EditUrl(string path, int moduleid, string action, string parameters)
{
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
}
} }
} }

View File

@ -1,21 +1,7 @@
using Microsoft.AspNetCore.Components; namespace Oqtane.Themes
using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane.Themes
{ {
public abstract class LayoutBase : ComponentBase, ILayoutControl public abstract class LayoutBase : ThemeBase, ILayoutControl
{ {
[CascadingParameter]
protected PageState PageState { get; set; }
public virtual string Name { get; set; }
public virtual string Thumbnail { get; set; }
public virtual string Panes { get; set; }
public string LayoutPath()
{
return "Themes/" + GetType().Namespace + "/";
}
} }
} }

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Themes.OqtaneTheme @namespace Oqtane.Themes.OqtaneTheme
@inherits ThemeBase @inherits ThemeBase
@implements IThemeControl
<main role="main"> <main role="main">
<nav class="navbar navbar-expand-md navbar-dark bg-primary fixed-top"> <nav class="navbar navbar-expand-md navbar-dark bg-primary fixed-top">
@ -23,10 +24,10 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "BootswatchCyborg.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/cyborg/bootstrap.min.css", Integrity = "sha384-l7xaoY0cJM4h9xh1RfazbgJVUZvdtyLWPueWNtLAphf/UbBgOVzqbOTogxPwYLHM", CrossOrigin = "anonymous" },
// 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 = ThemePath() + "Theme.css" },
//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.Script, Bundle = "Bootstrap", Url = "https://code.jquery.com/jquery-3.3.1.slim.min.js", Integrity = "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" } new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js", Integrity = "sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js", Integrity = "sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM", CrossOrigin = "anonymous" }
}; };
} }

View File

@ -4,11 +4,12 @@ using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI; using Oqtane.UI;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Themes namespace Oqtane.Themes
{ {
public abstract class ThemeBase : ComponentBase, IThemeControl public abstract class ThemeBase : ComponentBase
{ {
[Inject] [Inject]
protected IJSRuntime JSRuntime { get; set; } protected IJSRuntime JSRuntime { get; set; }
@ -22,6 +23,25 @@ namespace Oqtane.Themes
public virtual string Panes { get; set; } public virtual string Panes { get; set; }
public virtual List<Resource> Resources { get; set; } public virtual List<Resource> Resources { get; set; }
// base lifecycle method for handling JSInterop script registration
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (Resources != null && Resources.Exists(item => item.ResourceType == ResourceType.Script))
{
var scripts = new List<object>();
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script))
{
scripts.Add(new { href = resource.Url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "" });
}
var interop = new Interop(JSRuntime);
await interop.IncludeScripts(scripts.ToArray());
}
}
}
// path method // path method
public string ThemePath() public string ThemePath()
@ -60,5 +80,10 @@ namespace Oqtane.Themes
{ {
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters); return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
} }
public string ContentUrl(int fileid)
{
return Utilities.ContentUrl(PageState.Alias, fileid);
}
} }
} }

View File

@ -1,47 +1,7 @@
using Microsoft.AspNetCore.Components; namespace Oqtane.Themes
using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane.Themes
{ {
public abstract class ThemeControlBase : ComponentBase public abstract class ThemeControlBase : ThemeBase
{ {
[CascadingParameter]
protected PageState PageState { get; set; }
public string NavigateUrl()
{
return NavigateUrl(PageState.Page.Path);
}
public string NavigateUrl(string path)
{
return NavigateUrl(path, "");
}
public string NavigateUrl(string path, string parameters)
{
return Utilities.NavigateUrl(PageState.Alias.Path, path, parameters);
}
public string EditUrl(int moduleid, string action)
{
return EditUrl(moduleid, action, "");
}
public string EditUrl(int moduleid, string action, string parameters)
{
return EditUrl(PageState.Page.Path, moduleid, action, parameters);
}
public string EditUrl(string path, int moduleid, string action, string parameters)
{
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
}
public string ContentUrl(int fileid)
{
return Utilities.ContentUrl(PageState.Alias, fileid);
}
} }
} }

View File

@ -3,6 +3,7 @@
@inject IInstallationService InstallationService @inject IInstallationService InstallationService
@inject ISiteService SiteService @inject ISiteService SiteService
@inject IUserService UserService @inject IUserService UserService
@inject IJSRuntime JSRuntime
<div class="container"> <div class="container">
<div class="row"> <div class="row">
@ -138,6 +139,15 @@
private string _integratedSecurityDisplay = "display: none;"; private string _integratedSecurityDisplay = "display: none;";
private string _loadingDisplay = "display: none;"; private string _loadingDisplay = "display: none;";
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var interop = new Interop(JSRuntime);
await interop.IncludeLink("app-stylesheet", "stylesheet", "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css", "text/css", "sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T", "anonymous", "");
}
}
private void SetIntegratedSecurity(ChangeEventArgs e) private void SetIntegratedSecurity(ChangeEventArgs e)
{ {
_integratedSecurityDisplay = Convert.ToBoolean((string)e.Value) _integratedSecurityDisplay = Convert.ToBoolean((string)e.Value)

View File

@ -17,7 +17,7 @@ namespace Oqtane.UI
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.setCookie", "Oqtane.Interop.setCookie",
name, value, days); name, value, days);
return Task.CompletedTask; return Task.CompletedTask;
@ -46,7 +46,7 @@ namespace Oqtane.UI
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.updateTitle", "Oqtane.Interop.updateTitle",
title); title);
return Task.CompletedTask; return Task.CompletedTask;
@ -61,7 +61,7 @@ namespace Oqtane.UI
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeMeta", "Oqtane.Interop.includeMeta",
id, attribute, name, content, key); id, attribute, name, content, key);
return Task.CompletedTask; return Task.CompletedTask;
@ -76,7 +76,7 @@ namespace Oqtane.UI
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeLink", "Oqtane.Interop.includeLink",
id, rel, href, type, integrity, crossorigin, key); id, rel, href, type, integrity, crossorigin, key);
return Task.CompletedTask; return Task.CompletedTask;
@ -91,7 +91,7 @@ namespace Oqtane.UI
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeLinks", "Oqtane.Interop.includeLinks",
(object) links); (object) links);
return Task.CompletedTask; return Task.CompletedTask;
@ -106,7 +106,7 @@ namespace Oqtane.UI
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeScript", "Oqtane.Interop.includeScript",
id, src, integrity, crossorigin, content, location, key); id, src, integrity, crossorigin, content, location, key);
return Task.CompletedTask; return Task.CompletedTask;
@ -117,18 +117,17 @@ namespace Oqtane.UI
} }
} }
public Task IncludeScripts(object[] scripts) public async Task IncludeScripts(object[] scripts)
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( await _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeScripts", "Oqtane.Interop.includeScripts",
(object)scripts); (object)scripts);
return Task.CompletedTask;
} }
catch catch
{ {
return Task.CompletedTask; // ignore exception
} }
} }
@ -136,7 +135,7 @@ namespace Oqtane.UI
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.removeElementsById", "Oqtane.Interop.removeElementsById",
prefix, first, last); prefix, first, last);
return Task.CompletedTask; return Task.CompletedTask;
@ -165,7 +164,7 @@ namespace Oqtane.UI
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.submitForm", "Oqtane.Interop.submitForm",
path, fields); path, fields);
return Task.CompletedTask; return Task.CompletedTask;
@ -194,7 +193,7 @@ namespace Oqtane.UI
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.uploadFiles", "Oqtane.Interop.uploadFiles",
posturl, folder, id); posturl, folder, id);
return Task.CompletedTask; return Task.CompletedTask;
@ -209,7 +208,7 @@ namespace Oqtane.UI
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.refreshBrowser", "Oqtane.Interop.refreshBrowser",
force, wait); force, wait);
return Task.CompletedTask; return Task.CompletedTask;
@ -224,7 +223,7 @@ namespace Oqtane.UI
{ {
try try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.redirectBrowser", "Oqtane.Interop.redirectBrowser",
url, wait); url, wait);
return Task.CompletedTask; return Task.CompletedTask;
@ -234,5 +233,6 @@ namespace Oqtane.UI
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
} }
} }

View File

@ -277,7 +277,6 @@
{ {
if (user == null) if (user == null)
{ {
await LogService.Log(null, null, null, GetType().AssemblyQualifiedName, Utilities.GetTypeNameLastSegment(GetType().AssemblyQualifiedName, 1), LogFunction.Security, LogLevel.Error, null, "Page Does Not Exist Or User Is Not Authorized To View Page {Path}", path);
// redirect to login page // redirect to login page
NavigationManager.NavigateTo(Utilities.NavigateUrl(alias.Path, "login", "returnurl=" + path)); NavigationManager.NavigateTo(Utilities.NavigateUrl(alias.Path, "login", "returnurl=" + path));
} }
@ -361,22 +360,28 @@
string panes = ""; string panes = "";
Type themetype = Type.GetType(page.ThemeType); Type themetype = Type.GetType(page.ThemeType);
if (themetype != null)
{
var themeobject = Activator.CreateInstance(themetype) as IThemeControl; var themeobject = Activator.CreateInstance(themetype) as IThemeControl;
if (themeobject != null) if (themeobject != null)
{ {
panes = themeobject.Panes; panes = themeobject.Panes;
page.Resources = ManagePageResources(page.Resources, themeobject.Resources); page.Resources = ManagePageResources(page.Resources, themeobject.Resources);
} }
}
if (!string.IsNullOrEmpty(page.LayoutType)) if (!string.IsNullOrEmpty(page.LayoutType))
{ {
Type layouttype = Type.GetType(page.LayoutType); Type layouttype = Type.GetType(page.LayoutType);
if (layouttype != null)
{
var layoutobject = Activator.CreateInstance(layouttype) as ILayoutControl; var layoutobject = Activator.CreateInstance(layouttype) as ILayoutControl;
if (layoutobject != null) if (layoutobject != null)
{ {
panes = layoutobject.Panes; panes = layoutobject.Panes;
} }
} }
}
page.Panes = panes.Replace(";", ",").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); page.Panes = panes.Replace(";", ",").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
} }

View File

@ -30,27 +30,15 @@
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name); await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name);
} }
// include page resources // manage stylesheets for this page
string batch = DateTime.Now.ToString("yyyyMMddHHmmssfff"); string batch = DateTime.Now.ToString("yyyyMMddHHmmssfff");
var links = new List<object>(); var links = new List<object>();
var scripts = new List<object>(); foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
foreach (Resource resource in PageState.Page.Resources)
{ {
switch (resource.ResourceType)
{
case ResourceType.Stylesheet:
links.Add(new { id = "app-stylesheet-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = resource.Url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", key = "" }); links.Add(new { id = "app-stylesheet-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = resource.Url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", key = "" });
break;
case ResourceType.Script:
scripts.Add(new { id = "app-script-" + batch + "-" + (scripts.Count + 1).ToString("00"), src = resource.Url, integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", content = "", location = "body", key = "" });
break;
}
} }
await interop.IncludeLinks(links.ToArray()); await interop.IncludeLinks(links.ToArray());
await interop.IncludeScripts(scripts.ToArray());
// remove any page resource references which are no longer required for this page
await interop.RemoveElementsById("app-stylesheet", "", "app-stylesheet-" + batch + "-00"); await interop.RemoveElementsById("app-stylesheet", "", "app-stylesheet-" + batch + "-00");
await interop.RemoveElementsById("app-script", "", "app-script-" + batch + "-00");
// add favicon // add favicon
if (PageState.Site.FaviconFileId != null) if (PageState.Site.FaviconFileId != null)

View File

@ -75,7 +75,7 @@ namespace Oqtane.Controllers
private User Filter(User user) private User Filter(User user)
{ {
if (user != null && !User.IsInRole(Constants.AdminRole) && User.Identity.Name != user.Username) if (user != null && !User.IsInRole(Constants.AdminRole) && User.Identity.Name?.ToLower() != user.Username.ToLower())
{ {
user.DisplayName = ""; user.DisplayName = "";
user.Email = ""; user.Email = "";

View File

@ -194,6 +194,8 @@ namespace Oqtane.Infrastructure
if (install.TenantName == Constants.MasterTenant) if (install.TenantName == Constants.MasterTenant)
{ {
MigrateScriptNamingConvention("Master", install.ConnectionString);
var upgradeConfig = DeployChanges var upgradeConfig = DeployChanges
.To .To
.SqlDatabase(NormalizeConnectionString(install.ConnectionString)) .SqlDatabase(NormalizeConnectionString(install.ConnectionString))
@ -285,6 +287,8 @@ namespace Oqtane.Infrastructure
{ {
foreach (var tenant in db.Tenant.ToList()) foreach (var tenant in db.Tenant.ToList())
{ {
MigrateScriptNamingConvention("Tenant", tenant.DBConnectionString);
var upgradeConfig = DeployChanges.To.SqlDatabase(NormalizeConnectionString(tenant.DBConnectionString)) var upgradeConfig = DeployChanges.To.SqlDatabase(NormalizeConnectionString(tenant.DBConnectionString))
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Tenant.") && s.EndsWith(".sql", StringComparison.OrdinalIgnoreCase)); .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Tenant.") && s.EndsWith(".sql", StringComparison.OrdinalIgnoreCase));
@ -569,5 +573,17 @@ namespace Oqtane.Infrastructure
return value; return value;
} }
private void MigrateScriptNamingConvention(string scriptType, string connectionString)
{
// migrate to new naming convention for scripts
var migrateConfig = DeployChanges.To.SqlDatabase(NormalizeConnectionString(connectionString))
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s == scriptType + ".00.00.00.00.sql");
var migrate = migrateConfig.Build();
if (migrate.IsUpgradeRequired())
{
migrate.PerformUpgrade();
}
}
} }
} }

View File

@ -23,16 +23,14 @@
<EmbeddedResource Remove="wwwroot\Modules\Templates\**" /> <EmbeddedResource Remove="wwwroot\Modules\Templates\**" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="Scripts\Master.1.0.1.sql" /> <EmbeddedResource Include="Scripts\Master.00.00.00.00.sql" />
<None Remove="Scripts\Tenant.1.0.1.sql" /> <EmbeddedResource Include="Scripts\Master.00.09.00.00.sql" />
</ItemGroup> <EmbeddedResource Include="Scripts\Master.01.00.01.00.sql" />
<ItemGroup> <EmbeddedResource Include="Scripts\Tenant.00.00.00.00.sql" />
<EmbeddedResource Include="Scripts\Master.1.0.1.sql" /> <EmbeddedResource Include="Scripts\Tenant.00.09.00.00.sql" />
<EmbeddedResource Include="Scripts\Master.0.9.0.sql" /> <EmbeddedResource Include="Scripts\Tenant.00.09.01.00.sql" />
<EmbeddedResource Include="Scripts\Tenant.1.0.1.sql" /> <EmbeddedResource Include="Scripts\Tenant.00.09.02.00.sql" />
<EmbeddedResource Include="Scripts\Tenant.0.9.0.sql" /> <EmbeddedResource Include="Scripts\Tenant.01.00.01.00.sql" />
<EmbeddedResource Include="Scripts\Tenant.0.9.1.sql" />
<EmbeddedResource Include="Scripts\Tenant.0.9.2.sql" />
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.1.0.0.sql" /> <EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.1.0.0.sql" />
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.Uninstall.sql" /> <EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.Uninstall.sql" />
</ItemGroup> </ItemGroup>

View File

@ -14,8 +14,8 @@
<link id="app-favicon" rel="shortcut icon" type="image/x-icon" href="favicon.ico" /> <link id="app-favicon" rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<!-- stub the PWA manifest but defer the assignment of href --> <!-- stub the PWA manifest but defer the assignment of href -->
<link id="app-manifest" rel="manifest" /> <link id="app-manifest" rel="manifest" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="css/app.css" /> <link rel="stylesheet" href="css/app.css" />
<script src="js/loadjs.min.js"></script>
</head> </head>
<body> <body>
@(Html.AntiForgeryToken()) @(Html.AntiForgeryToken())
@ -23,10 +23,18 @@
<component type="typeof(Oqtane.App)" render-mode="Server" /> <component type="typeof(Oqtane.App)" render-mode="Server" />
</app> </app>
<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="~/" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="js/interop.js"></script> <script src="js/interop.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
@if (Configuration.GetSection("Runtime").Value == "WebAssembly") @if (Configuration.GetSection("Runtime").Value == "WebAssembly")
{ {

View File

@ -24,9 +24,9 @@ namespace Oqtane.Repository
return _db.Role.Where(item => item.SiteId == siteId || item.SiteId == null); return _db.Role.Where(item => item.SiteId == siteId || item.SiteId == null);
} }
public Role AddRole(Role role) public Role AddRole(Role role)
{ {
role.Description = role.Description.Substring(0, (role.Description.Length > 256) ? 256 : role.Description.Length);
_db.Role.Add(role); _db.Role.Add(role);
_db.SaveChanges(); _db.SaveChanges();
return role; return role;
@ -34,6 +34,7 @@ namespace Oqtane.Repository
public Role UpdateRole(Role role) public Role UpdateRole(Role role)
{ {
role.Description = role.Description.Substring(0, (role.Description.Length > 256) ? 256 : role.Description.Length);
_db.Entry(role).State = EntityState.Modified; _db.Entry(role).State = EntityState.Modified;
_db.SaveChanges(); _db.SaveChanges();
return role; return role;

View File

@ -0,0 +1,10 @@
/*
migrate to new naming convention for scripts
*/
UPDATE [dbo].[SchemaVersions] SET ScriptName = 'Oqtane.Scripts.Master.00.09.00.00.sql' WHERE ScriptName = 'Oqtane.Scripts.Master.0.9.0.sql'
GO
UPDATE [dbo].[SchemaVersions] SET ScriptName = 'Oqtane.Scripts.Master.01.00.01.00.sql' WHERE ScriptName = 'Oqtane.Scripts.Master.1.0.1.sql'
GO

View File

@ -0,0 +1,14 @@
/*
migrate to new naming convention for scripts
*/
UPDATE [dbo].[SchemaVersions] SET ScriptName = 'Oqtane.Scripts.Tenant.00.09.00.00.sql' WHERE ScriptName = 'Oqtane.Scripts.Tenant.0.9.0.sql'
GO
UPDATE [dbo].[SchemaVersions] SET ScriptName = 'Oqtane.Scripts.Tenant.00.09.01.00.sql' WHERE ScriptName = 'Oqtane.Scripts.Tenant.0.9.1.sql'
GO
UPDATE [dbo].[SchemaVersions] SET ScriptName = 'Oqtane.Scripts.Tenant.00.09.02.00.sql' WHERE ScriptName = 'Oqtane.Scripts.Tenant.0.9.2.sql'
GO
UPDATE [dbo].[SchemaVersions] SET ScriptName = 'Oqtane.Scripts.Tenant.01.00.01.00.sql' WHERE ScriptName = 'Oqtane.Scripts.Tenant.1.0.1.sql'
GO

View File

@ -29,6 +29,7 @@ namespace Oqtane
public IConfigurationRoot Configuration { get; } public IConfigurationRoot Configuration { get; }
private string _webRoot; private string _webRoot;
private Runtime _runtime; private Runtime _runtime;
private bool _useSwagger;
public Startup(IWebHostEnvironment env) public Startup(IWebHostEnvironment env)
{ {
@ -39,6 +40,9 @@ namespace Oqtane
_runtime = (Configuration.GetSection("Runtime").Value == "WebAssembly") ? Runtime.WebAssembly : Runtime.Server; _runtime = (Configuration.GetSection("Runtime").Value == "WebAssembly") ? Runtime.WebAssembly : Runtime.Server;
//add possibility to switch off swagger on production.
_useSwagger = Configuration.GetSection("UseSwagger").Value != "false";
_webRoot = env.WebRootPath; _webRoot = env.WebRootPath;
AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data")); AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data"));
} }
@ -47,7 +51,6 @@ namespace Oqtane
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddServerSideBlazor(); services.AddServerSideBlazor();
// setup HttpClient for server side in a client side compatible fashion ( with auth cookie ) // setup HttpClient for server side in a client side compatible fashion ( with auth cookie )
@ -59,7 +62,7 @@ namespace Oqtane
var navigationManager = s.GetRequiredService<NavigationManager>(); var navigationManager = s.GetRequiredService<NavigationManager>();
var httpContextAccessor = s.GetRequiredService<IHttpContextAccessor>(); var httpContextAccessor = s.GetRequiredService<IHttpContextAccessor>();
var authToken = httpContextAccessor.HttpContext.Request.Cookies[".AspNetCore.Identity.Application"]; var authToken = httpContextAccessor.HttpContext.Request.Cookies[".AspNetCore.Identity.Application"];
var client = new HttpClient(new HttpClientHandler { UseCookies = false }); var client = new HttpClient(new HttpClientHandler {UseCookies = false});
if (authToken != null) if (authToken != null)
{ {
client.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Identity.Application=" + authToken); client.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Identity.Application=" + authToken);
@ -201,12 +204,10 @@ namespace Oqtane
.AddOqtaneApplicationParts() // register any Controllers from custom modules .AddOqtaneApplicationParts() // register any Controllers from custom modules
.ConfigureOqtaneMvc(); // any additional configuration from IStart classes. .ConfigureOqtaneMvc(); // any additional configuration from IStart classes.
services.AddSwaggerGen(c => if (_useSwagger)
{ {
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Oqtane", Version = "v1" }); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Oqtane", Version = "v1"}); });
}); }
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -230,11 +231,11 @@ namespace Oqtane
app.UseRouting(); app.UseRouting();
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseSwagger(); if (_useSwagger)
app.UseSwaggerUI(c =>
{ {
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Oqtane V1"); app.UseSwagger();
}); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "Oqtane V1"); });
}
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
@ -242,7 +243,6 @@ namespace Oqtane
endpoints.MapControllers(); endpoints.MapControllers();
endpoints.MapFallbackToPage("/_Host"); endpoints.MapFallbackToPage("/_Host");
}); });
} }
} }
} }

View File

@ -164,3 +164,22 @@ app {
width: 10em; width: 10em;
} }
} }
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

View File

@ -145,7 +145,7 @@ Oqtane.Interop = {
script.innerHTML = content; script.innerHTML = content;
} }
script.async = false; script.async = false;
this.loadScript(script, location) this.addScript(script, location)
.then(() => { .then(() => {
console.log(src + ' loaded'); console.log(src + ' loaded');
}) })
@ -185,7 +185,7 @@ Oqtane.Interop = {
} }
} }
}, },
loadScript: function (script, location) { addScript: function (script, location) {
if (location === 'head') { if (location === 'head') {
document.head.appendChild(script); document.head.appendChild(script);
} }
@ -198,9 +198,48 @@ Oqtane.Interop = {
script.onerror = rej(); script.onerror = rej();
}); });
}, },
includeScripts: function (scripts) { includeScripts: async function (scripts) {
for (let i = 0; i < scripts.length; i++) { const bundles = [];
this.includeScript(scripts[i].id, scripts[i].src, scripts[i].integrity, scripts[i].crossorigin, scripts[i].content, scripts[i].location, scripts[i].key); for (let s = 0; s < scripts.length; s++) {
if (scripts[s].bundle === '') {
scripts[s].bundle = scripts[s].href;
}
if (!bundles.includes(scripts[s].bundle)) {
bundles.push(scripts[s].bundle);
}
}
const urls = [];
for (let b = 0; b < bundles.length; b++) {
for (let s = 0; s < scripts.length; s++) {
if (scripts[s].bundle === bundles[b]) {
urls.push(scripts[s].href);
}
}
const promise = new Promise((resolve, reject) => {
if (loadjs.isDefined(bundles[b])) {
resolve(true);
}
else {
loadjs(urls, bundles[b], {
async: true,
returnPromise: true,
before: function (path, element) {
for (let s = 0; s < scripts.length; s++) {
if (path === scripts[s].href && scripts[s].integrity !== '') {
element.integrity = scripts[s].integrity;
}
if (path === scripts[s].href && scripts[s].crossorigin !== '') {
element.crossOrigin = scripts[s].crossorigin;
}
}
}
})
.then(function () { resolve(true) })
.catch(function (pathsNotFound) { reject(false) });
}
});
await promise;
urls = [];
} }
}, },
getAbsoluteUrl: function (url) { getAbsoluteUrl: function (url) {

View File

@ -0,0 +1 @@
loadjs=function(){var h=function(){},c={},u={},f={};function o(e,n){if(e){var r=f[e];if(u[e]=n,r)for(;r.length;)r[0](e,n),r.splice(0,1)}}function l(e,n){e.call&&(e={success:e}),n.length?(e.error||h)(n):(e.success||h)(e)}function d(r,t,s,i){var c,o,e=document,n=s.async,u=(s.numRetries||0)+1,f=s.before||h,l=r.replace(/[\?|#].*$/,""),a=r.replace(/^(css|img)!/,"");i=i||0,/(^css!|\.css$)/.test(l)?((o=e.createElement("link")).rel="stylesheet",o.href=a,(c="hideFocus"in o)&&o.relList&&(c=0,o.rel="preload",o.as="style")):/(^img!|\.(png|gif|jpg|svg|webp)$)/.test(l)?(o=e.createElement("img")).src=a:((o=e.createElement("script")).src=r,o.async=void 0===n||n),!(o.onload=o.onerror=o.onbeforeload=function(e){var n=e.type[0];if(c)try{o.sheet.cssText.length||(n="e")}catch(e){18!=e.code&&(n="e")}if("e"==n){if((i+=1)<u)return d(r,t,s,i)}else if("preload"==o.rel&&"style"==o.as)return o.rel="stylesheet";t(r,n,e.defaultPrevented)})!==f(r,o)&&e.head.appendChild(o)}function r(e,n,r){var t,s;if(n&&n.trim&&(t=n),s=(t?r:n)||{},t){if(t in c)throw"LoadJS";c[t]=!0}function i(n,r){!function(e,t,n){var r,s,i=(e=e.push?e:[e]).length,c=i,o=[];for(r=function(e,n,r){if("e"==n&&o.push(e),"b"==n){if(!r)return;o.push(e)}--i||t(o)},s=0;s<c;s++)d(e[s],r,n)}(e,function(e){l(s,e),n&&l({success:n,error:r},e),o(t,e)},s)}if(s.returnPromise)return new Promise(i);i()}return r.ready=function(e,n){return function(e,r){e=e.push?e:[e];var n,t,s,i=[],c=e.length,o=c;for(n=function(e,n){n.length&&i.push(e),--o||r(i)};c--;)t=e[c],(s=u[t])?n(t,s):(f[t]=f[t]||[]).push(n)}(e,function(e){l(n,e)}),r},r.done=function(e){o(e,[])},r.reset=function(){c={},u={},f={}},r.isDefined=function(e){return e in c},r}();

View File

@ -1,7 +1,7 @@
var Oqtane = Oqtane || {}; var Oqtane = Oqtane || {};
Oqtane.RichTextEditor = { Oqtane.RichTextEditor = {
createQuill: function ( createQuill: async function (
quillElement, toolBar, readOnly, quillElement, toolBar, readOnly,
placeholder, theme, debugLevel) { placeholder, theme, debugLevel) {

View File

@ -8,5 +8,6 @@ namespace Oqtane.Models
public string Url { get; set; } public string Url { get; set; }
public string Integrity { get; set; } public string Integrity { get; set; }
public string CrossOrigin { get; set; } public string CrossOrigin { get; set; }
public string Bundle { get; set; }
} }
} }

View File

@ -9,7 +9,7 @@ Please note that this project is owned by the .NET Foundation and is governed by
**To get started with Oqtane:** **To get started with Oqtane:**
1.&nbsp;Install **[.NET Core 3.2 SDK (v3.1.300)](https://dotnet.microsoft.com/download/dotnet-core/thank-you/sdk-3.1.300-windows-x64-installer)**. 1.&nbsp;Install **[.NET Core 3.1 SDK (v3.1.300)](https://dotnet.microsoft.com/download/dotnet-core/thank-you/sdk-3.1.300-windows-x64-installer)**.
2.&nbsp;Install the Preview edition of [Visual Studio 2019](https://visualstudio.microsoft.com/vs/preview/) (version 16.6 or higher) with the **ASP.NET and web development** workload enabled. Oqtane works with all editions of Visual Studio from Community to Enterprise. If you do not have a SQL Server installation available already and you wish to use LocalDB for development, you must also install the **.NET desktop development workload**. 2.&nbsp;Install the Preview edition of [Visual Studio 2019](https://visualstudio.microsoft.com/vs/preview/) (version 16.6 or higher) with the **ASP.NET and web development** workload enabled. Oqtane works with all editions of Visual Studio from Community to Enterprise. If you do not have a SQL Server installation available already and you wish to use LocalDB for development, you must also install the **.NET desktop development workload**.