Merge pull request #5 from oqtane/master

Sync master
This commit is contained in:
jimspillane
2020-05-09 14:04:36 -04:00
committed by GitHub
72 changed files with 783 additions and 11452 deletions

View File

@ -42,7 +42,7 @@ else
<hr /> <hr />
[Module] Module Created Successfully. Use Edit Mode To Add A [Module]. You Can Access The Files At The Following Locations:<br /><br /> [Module] Module Created Successfully. Use Edit Mode To Add A [Module]. You Can Access The Files At The Following Locations:<br /><br />
[RootPath]Client\<br /> [RootPath]Client\<br />
- [Owner].[Module]s.Module.Client.csproj - client project<br /> - [Owner].[Module]s.Client.csproj - client project<br />
- _Imports.razor - global imports for module components<br /> - _Imports.razor - global imports for module components<br />
- Edit.razor - component for adding or editing content<br /> - Edit.razor - component for adding or editing content<br />
- Index.razor - main component for your module **the content you are reading is in this file**<br /> - Index.razor - main component for your module **the content you are reading is in this file**<br />
@ -51,12 +51,12 @@ else
- Services\I[Module]Service.cs - interface for defining service API methods<br /> - Services\I[Module]Service.cs - interface for defining service API methods<br />
- Services\[Module]Service.cs - implements service API interface methods<br /><br /> - Services\[Module]Service.cs - implements service API interface methods<br /><br />
[RootPath]Package\<br /> [RootPath]Package\<br />
- [Owner].[Module]s.Module.nuspec - nuget manifest for packaging module<br /> - [Owner].[Module]s.nuspec - nuget manifest for packaging module<br />
- [Owner].[Module]s.Module.Package.csproj - packaging project<br /> - [Owner].[Module]s.Package.csproj - packaging project<br />
- debug.cmd - copies assemblies to Oqtane bin folder when in Debug mode<br /> - debug.cmd - copies assemblies to Oqtane bin folder when in Debug mode<br />
- release.cmd - creates nuget package and deploys to Oqtane wwwroot/modules folder when in Release mode<br /><br /> - release.cmd - creates nuget package and deploys to Oqtane wwwroot/modules folder when in Release mode<br /><br />
[RootPath]Server\<br /> [RootPath]Server\<br />
- [Owner].[Module]s.Module.Server.csproj - server project<br /> - [Owner].[Module]s.Server.csproj - server project<br />
- Controllers\[Module]Controller.cs - API methods implemented using a REST pattern<br /> - Controllers\[Module]Controller.cs - API methods implemented using a REST pattern<br />
- Manager\[Module]Manager.cs - implements optional module interfaces for features such as import/export of content<br /> - Manager\[Module]Manager.cs - implements optional module interfaces for features such as import/export of content<br />
- Repository\I[Module]Repository.cs - interface for defining repository methods<br /> - Repository\I[Module]Repository.cs - interface for defining repository methods<br />
@ -65,7 +65,7 @@ else
- Scripts\[Owner].[Module].1.0.0.sql - database schema definition script<br /><br /> - Scripts\[Owner].[Module].1.0.0.sql - database schema definition script<br /><br />
- Scripts\[Owner].[Module].Uninstall.sql - database uninstall script<br /><br /> - Scripts\[Owner].[Module].Uninstall.sql - database uninstall script<br /><br />
[RootPath]Shared\<br /> [RootPath]Shared\<br />
- [Owner].[Module]s.Module.Shared.csproj - shared project<br /> - [Owner].[Module]s.csproj - shared project<br />
- Models\[Module].cs - model definition<br /><br /> - Models\[Module].cs - model definition<br /><br />
<!-- The content above is for informational purposes only and can be safely removed --> <!-- The content above is for informational purposes only and can be safely removed -->

View File

@ -10,7 +10,7 @@ namespace [Owner].[Module]s.Modules
Name = "[Module]", Name = "[Module]",
Description = "[Module]", Description = "[Module]",
Version = "1.0.0", Version = "1.0.0",
Dependencies = "[Owner].[Module]s.Module.Shared", Dependencies = "[Owner].[Module]s.Shared.Oqtane",
ServerManagerType = "[ServerManagerType]", ServerManagerType = "[ServerManagerType]",
ReleaseVersions = "1.0.0" ReleaseVersions = "1.0.0"
}; };

View File

@ -7,8 +7,9 @@
<Authors>[Owner]</Authors> <Authors>[Owner]</Authors>
<Company>[Owner]</Company> <Company>[Owner]</Company>
<Description>[Description]</Description> <Description>[Description]</Description>
<Product>[Owner].[Module]s.Module</Product> <Product>[Owner].[Module]s</Product>
<Copyright>[Owner]</Copyright> <Copyright>[Owner]</Copyright>
<AssemblyName>[Owner].[Module]s.Client.Oqtane</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -18,7 +19,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Shared\[Owner].[Module]s.Module.Shared.csproj" /> <ProjectReference Include="..\Shared\[Owner].[Module]s.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -6,9 +6,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Client\[Owner].[Module]s.Module.Client.csproj" /> <ProjectReference Include="..\Client\[Owner].[Module]s.Client.csproj" />
<ProjectReference Include="..\Server\[Owner].[Module]s.Module.Server.csproj" /> <ProjectReference Include="..\Server\[Owner].[Module]s.Server.csproj" />
<ProjectReference Include="..\Shared\[Owner].[Module]s.Module.Shared.csproj" /> <ProjectReference Include="..\Shared\[Owner].[Module]s.Shared.csproj" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>[Owner].[Module]s.Module</id> <id>[Owner].[Module]s</id>
<version>1.0.0</version> <version>1.0.0</version>
<authors>[Owner]</authors> <authors>[Owner]</authors>
<owners>[Owner]</owners> <owners>[Owner]</owners>
@ -20,12 +20,12 @@
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>
<file src="..\Client\bin\Release\netstandard2.1\[Owner].[Module]s.Module.Client.dll" target="lib" /> <file src="..\Client\bin\Release\netstandard2.1\[Owner].[Module]s.Client.Oqtane.dll" target="lib" />
<file src="..\Client\bin\Release\netstandard2.1\[Owner].[Module]s.Module.Client.pdb" target="lib" /> <file src="..\Client\bin\Release\netstandard2.1\[Owner].[Module]s.Client.Oqtane.pdb" target="lib" />
<file src="..\Server\bin\Release\netcoreapp3.1\[Owner].[Module]s.Module.Server.dll" target="lib" /> <file src="..\Server\bin\Release\netcoreapp3.1\[Owner].[Module]s.Server.Oqtane.dll" target="lib" />
<file src="..\Server\bin\Release\netcoreapp3.1\[Owner].[Module]s.Module.Server.pdb" target="lib" /> <file src="..\Server\bin\Release\netcoreapp3.1\[Owner].[Module]s.Server.Oqtane.pdb" target="lib" />
<file src="..\Shared\bin\Release\netstandard2.1\[Owner].[Module]s.Module.Shared.dll" target="lib" /> <file src="..\Shared\bin\Release\netstandard2.1\[Owner].[Module]s.Shared.Oqtane.dll" target="lib" />
<file src="..\Shared\bin\Release\netstandard2.1\[Owner].[Module]s.Module.Shared.pdb" target="lib" /> <file src="..\Shared\bin\Release\netstandard2.1\[Owner].[Module]s.Shared.Oqtane.pdb" target="lib" />
<file src="..\wwwroot\**\*.*" target="wwwroot" /> <file src="..\wwwroot\**\*.*" target="wwwroot" />
</files> </files>
</package> </package>

View File

@ -1,6 +1,6 @@
XCOPY "..\Client\bin\Debug\netstandard2.1\[Owner].[Module]s.Module.Client.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y XCOPY "..\Client\bin\Debug\netstandard2.1\[Owner].[Module]s.Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y
XCOPY "..\Client\bin\Debug\netstandard2.1\[Owner].[Module]s.Module.Client.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y XCOPY "..\Client\bin\Debug\netstandard2.1\[Owner].[Module]s.Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y
XCOPY "..\Server\bin\Debug\netcoreapp3.1\[Owner].[Module]s.Module.Server.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y XCOPY "..\Server\bin\Debug\netcoreapp3.1\[Owner].[Module]s.Server.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y
XCOPY "..\Server\bin\Debug\netcoreapp3.1\[Owner].[Module]s.Module.Server.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y XCOPY "..\Server\bin\Debug\netcoreapp3.1\[Owner].[Module]s.Server.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y
XCOPY "..\Shared\bin\Debug\netstandard2.1\[Owner].[Module]s.Module.Shared.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y XCOPY "..\Shared\bin\Debug\netstandard2.1\[Owner].[Module]s.Shared.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y
XCOPY "..\Shared\bin\Debug\netstandard2.1\[Owner].[Module]s.Module.Shared.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y XCOPY "..\Shared\bin\Debug\netstandard2.1\[Owner].[Module]s.Shared.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y

View File

@ -1,2 +1,2 @@
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack [Owner].[Module]s.Module.nuspec "..\..\[RootFolder]\oqtane.package\nuget.exe" pack [Owner].[Module]s.nuspec
XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\wwwroot\Modules\" /Y XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\wwwroot\Modules\" /Y

View File

@ -5,11 +5,12 @@
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc> <AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<Version>1.0.0</Version> <Version>1.0.0</Version>
<Product>[Owner].[Module]s.Module</Product> <Product>[Owner].[Module]s</Product>
<Authors>[Owner]</Authors> <Authors>[Owner]</Authors>
<Company>[Owner]</Company> <Company>[Owner]</Company>
<Description>[Description]</Description> <Description>[Description]</Description>
<Copyright>[Owner]</Copyright> <Copyright>[Owner]</Copyright>
<AssemblyName>[Owner].[Module]s.Server.Oqtane</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -26,7 +27,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Shared\[Owner].[Module]s.Module.Shared.csproj" /> <ProjectReference Include="..\Shared\[Owner].[Module]s.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -4,11 +4,12 @@
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<Version>1.0.0</Version> <Version>1.0.0</Version>
<Product>[Owner].[Module].Module</Product> <Product>[Owner].[Module]s</Product>
<Authors>[Owner]</Authors> <Authors>[Owner]</Authors>
<Company>[Owner]</Company> <Company>[Owner]</Company>
<Description>[Description]</Description> <Description>[Description]</Description>
<Copyright>[Owner]</Copyright> <Copyright>[Owner]</Copyright>
<AssemblyName>[Owner].[Module]s.Shared.Oqtane</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -3,13 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 16
VisualStudioVersion = 16.0.28621.142 VisualStudioVersion = 16.0.28621.142
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Module]s.Module.Client", "Client\[Owner].[Module]s.Module.Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Module]s.Client", "Client\[Owner].[Module]s.Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Module]s.Module.Server", "Server\[Owner].[Module]s.Module.Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Module]s.Server", "Server\[Owner].[Module]s.Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Module]s.Module.Shared", "Shared\[Owner].[Module]s.Module.Shared.csproj", "{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Module]s.Shared", "Shared\[Owner].[Module]s.Shared.csproj", "{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "[Owner].[Module]s.Module.Package", "Package\[Owner].[Module]s.Module.Package.csproj", "{C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "[Owner].[Module]s.Package", "Package\[Owner].[Module]s.Package.csproj", "{C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -32,6 +32,17 @@
</select> </select>
</td> </td>
</tr> </tr>
<tr>
<td>
<Label For="allpages" HelpText="Indicate if this module should be displayed on all pages">Display On All Pages? </Label>
</td>
<td>
<select id="allpages" class="form-control" @bind="@_allPages">
<option value="True">Yes</option>
<option value="False">No</option>
</select>
</td>
</tr>
<tr> <tr>
<td> <td>
<Label For="page" HelpText="The page that the module is on">Page: </Label> <Label For="page" HelpText="The page that the module is on">Page: </Label>
@ -77,6 +88,7 @@
private Dictionary<string, string> _containers; private Dictionary<string, string> _containers;
private string _title; private string _title;
private string _containerType; private string _containerType;
private string _allPages = "false";
private string _permissionNames = ""; private string _permissionNames = "";
private string _permissions; private string _permissions;
private string _pageId; private string _pageId;
@ -95,6 +107,7 @@
_title = ModuleState.Title; _title = ModuleState.Title;
_containers = ThemeService.GetContainerTypes(await ThemeService.GetThemesAsync()); _containers = ThemeService.GetContainerTypes(await ThemeService.GetThemesAsync());
_containerType = ModuleState.ContainerType; _containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.Permissions; _permissions = ModuleState.Permissions;
_permissionNames = ModuleState.ModuleDefinition.PermissionNames; _permissionNames = ModuleState.ModuleDefinition.PermissionNames;
_pageId = ModuleState.PageId.ToString(); _pageId = ModuleState.PageId.ToString();
@ -120,18 +133,18 @@
private async Task SaveModule() private async Task SaveModule()
{ {
var module = ModuleState;
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
pagemodule.PageId = int.Parse(_pageId); pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title; pagemodule.Title = _title;
pagemodule.ContainerType = _containerType; pagemodule.ContainerType = _containerType;
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
if (_settingsModuleType != null) if (_settingsModuleType != null)
{ {
var moduleType = Type.GetType(ModuleState.ModuleType); var moduleType = Type.GetType(ModuleState.ModuleType);

View File

@ -11,29 +11,27 @@
<TabPanel Name="Download"> <TabPanel Name="Download">
@if (_upgradeavailable) @if (_upgradeavailable)
{ {
<ModuleMessage Type="MessageType.Info" Message="Download a new version of the framework. Once you are ready click Install to complete the installation."></ModuleMessage> <ModuleMessage Type="MessageType.Info" Message="Select The Upgrade Button To Install a New Framework Version"></ModuleMessage>
@("Framework") @_package.Version <button type="button" class="btn btn-success" @onclick=@(async () => await Download(Constants.PackageId, Constants.Version))>Download</button> @("Framework") @_package.Version <button type="button" class="btn btn-success" @onclick=@(async () => await Download(Constants.PackageId, Constants.Version))>Upgrade</button>
} }
else else
{ {
<ModuleMessage Type="MessageType.Info" Message="Framework Is Already Up To Date"></ModuleMessage> <ModuleMessage Type="MessageType.Info" Message="Framework Is Already Up To Date"></ModuleMessage>
} }
</TabPanel> </TabPanel>
@if (_upgradeavailable)
{
<TabPanel Name="Upload"> <TabPanel Name="Upload">
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
<td> <td>
<Label HelpText="Upload a new framework package. Once it is uploaded click Install to complete the installation.">Framework: </Label> <Label HelpText="Upload a framework package and select Install to complete the installation">Framework: </Label>
</td> </td>
<td> <td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Framework" /> <FileManager Filter="nupkg" ShowFiles="false" Folder="Framework" />
</td> </td>
</tr> </tr>
</table> </table>
<button type="button" class="btn btn-success" @onclick="Upgrade">Install</button>
</TabPanel> </TabPanel>
}
</TabStrip> </TabStrip>
} }

View File

@ -1,6 +1,6 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@if (_visible) @if (_visible)
{ {
<div class="app-admin-modal"> <div class="app-admin-modal">

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IUserService UserService @inject IUserService UserService
@if (_authorized) @if (_authorized)

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@if (_text != string.Empty) @if (_text != string.Empty)
{ {

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IFolderService FolderService @inject IFolderService FolderService
@inject IFileService FileService @inject IFileService FileService
@inject IJSRuntime JsRuntime @inject IJSRuntime JsRuntime

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@if (!string.IsNullOrEmpty(HelpText)) @if (!string.IsNullOrEmpty(HelpText))
{ {

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@if (!string.IsNullOrEmpty(_message)) @if (!string.IsNullOrEmpty(_message))
{ {

View File

@ -1,7 +1,9 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@typeparam TableItem @typeparam TableItem
<p> <p>
@if(Format == "Table") @if(Format == "Table")
{ {

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IRoleService RoleService @inject IRoleService RoleService
@inject IUserService UserService @inject IUserService UserService

View File

@ -1,15 +1,21 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IJSRuntime JsRuntime @inject IJSRuntime JsRuntime
<div class="row" style="margin-bottom: 50px;">
<div class="col">
<TabStrip>
<TabPanel Name="Rich" Heading="Rich Text Editor">
@if (_filemanagervisible) @if (_filemanagervisible)
{ {
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" /> <FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
@((MarkupString)_message) @((MarkupString)_message)
<br /> <br />
} }
<div class="row justify-content-center"> <div class="row justify-content-center" style="margin-bottom: 20px;">
<button type="button" class="btn btn-success" @onclick="InsertImage">Insert Image</button> <button type="button" class="btn btn-secondary" @onclick="RefreshRichText">Synchronize Content</button>&nbsp;&nbsp;
<button type="button" class="btn btn-primary" @onclick="InsertImage">Insert Image</button>
@if (_filemanagervisible) @if (_filemanagervisible)
{ {
@((MarkupString)"&nbsp;&nbsp;") @((MarkupString)"&nbsp;&nbsp;")
@ -19,22 +25,72 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div @ref="@_toolBar"> <div @ref="@_toolBar">
@if (ToolbarContent != null)
{
@ToolbarContent @ToolbarContent
}
else
{
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
}
</div> </div>
<div @ref="@_editorElement"> <div @ref="@_editorElement">
</div> </div>
</div> </div>
</div> </div>
</TabPanel>
<TabPanel Name="Raw" Heading="Raw HTML Editor">
<div class="row justify-content-center" style="margin-bottom: 20px;">
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">Synchronize Content</button>
</div>
@if (ReadOnly)
{
<textarea class="form-control" placeholder="@Placeholder" @bind="@_content" rows="10" readonly></textarea>
}
else
{
<textarea class="form-control" placeholder="@Placeholder" @bind="@_content" rows="10"></textarea>
}
</TabPanel>
</TabStrip>
</div>
</div>
@code { @code {
private ElementReference _editorElement; private ElementReference _editorElement;
private ElementReference _toolBar; private ElementReference _toolBar;
private bool _filemanagervisible = false; private bool _filemanagervisible = false;
private FileManager _fileManager; private FileManager _fileManager;
private string _content = string.Empty;
private string _original = string.Empty;
private string _message = string.Empty; private string _message = string.Empty;
[Parameter] [Parameter]
public RenderFragment ToolbarContent { get; set; } public string Content { get; set; }
[Parameter] [Parameter]
public bool ReadOnly { get; set; } = false; public bool ReadOnly { get; set; } = false;
@ -42,12 +98,21 @@
[Parameter] [Parameter]
public string Placeholder { get; set; } = "Enter Your Content..."; public string Placeholder { get; set; } = "Enter Your Content...";
// parameters only applicable to rich text editor
[Parameter]
public RenderFragment ToolbarContent { get; set; }
[Parameter] [Parameter]
public string Theme { get; set; } = "snow"; public string Theme { get; set; } = "snow";
[Parameter] [Parameter]
public string DebugLevel { get; set; } = "info"; public string DebugLevel { get; set; } = "info";
protected override void OnInitialized()
{
_content = Content; // raw HTML
}
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) if (firstRender)
@ -60,42 +125,57 @@
Placeholder, Placeholder,
Theme, Theme,
DebugLevel); DebugLevel);
await RichTextEditorInterop.LoadEditorContent(
JsRuntime,
_editorElement, Content);
// preserve a copy of the rich text content ( Quill sanitizes content so we need to retrieve it from the editor )
_original = await RichTextEditorInterop.GetHtml(
JsRuntime,
_editorElement);
} }
} }
public async Task<string> GetText() public void CloseFileManager()
{ {
return await RichTextEditorInterop.GetText( _filemanagervisible = false;
_message = string.Empty;
StateHasChanged();
}
public async Task RefreshRichText()
{
await RichTextEditorInterop.LoadEditorContent(
JsRuntime,
_editorElement, _content);
}
public async Task RefreshRawHtml()
{
_content = await RichTextEditorInterop.GetHtml(
JsRuntime, JsRuntime,
_editorElement); _editorElement);
StateHasChanged();
} }
public async Task<string> GetHtml() public async Task<string> GetHtml()
{ {
return await RichTextEditorInterop.GetHtml( // get rich text content
string content = await RichTextEditorInterop.GetHtml(
JsRuntime, JsRuntime,
_editorElement); _editorElement);
}
public async Task<string> GetContent() if (_original != content)
{ {
return await RichTextEditorInterop.GetContent( // rich text content has changed - return it
JsRuntime, return content;
_editorElement);
} }
else
public async Task LoadContent(string content)
{ {
await RichTextEditorInterop.LoadEditorContent( // return raw html content
JsRuntime, return _content;
_editorElement, content);
} }
public async Task EnableEditor(bool mode)
{
await RichTextEditorInterop.EnableEditor(
JsRuntime,
_editorElement, mode);
} }
public async Task InsertImage() public async Task InsertImage()
@ -121,16 +201,28 @@
_filemanagervisible = true; _filemanagervisible = true;
_message = string.Empty; _message = string.Empty;
} }
StateHasChanged(); StateHasChanged();
} }
public void CloseFileManager() // other rich text editor methods which can be used by developers
public async Task<string> GetText()
{ {
_filemanagervisible = false; return await RichTextEditorInterop.GetText(
_message = string.Empty; JsRuntime,
_editorElement);
StateHasChanged();
} }
public async Task<string> GetContent()
{
return await RichTextEditorInterop.GetContent(
JsRuntime,
_editorElement);
}
public async Task EnableEditor(bool mode)
{
await RichTextEditorInterop.EnableEditor(
JsRuntime,
_editorElement, mode);
}
} }

View File

@ -2,7 +2,7 @@
using Microsoft.JSInterop; using Microsoft.JSInterop;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.UI namespace Oqtane.Modules.Controls
{ {
public static class RichTextEditorInterop public static class RichTextEditorInterop
{ {

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
<div class="d-flex"> <div class="d-flex">
<div> <div>

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@if (Name == Parent.ActiveTab) @if (Name == Parent.ActiveTab)
{ {

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
<CascadingValue Value="this"> <CascadingValue Value="this">
<div class="container-fluid"> <div class="container-fluid">

View File

@ -7,111 +7,48 @@
@inject HttpClient http @inject HttpClient http
@inject SiteState sitestate @inject SiteState sitestate
<div class="row" style="margin-bottom: 50px;"> @if (_content != null)
<div class="col @_visibleText">
<textarea class="form-control" @bind="@content" rows="10"></textarea>
</div>
<div class="col @_visibleRich">
<RichTextEditor @ref="@RichTextEditorHtml">
<ToolbarContent>
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
</ToolbarContent>
</RichTextEditor>
</div>
</div>
<div class="row">
<div class="col">
@if (!RichTextEditorMode)
{ {
<button type="button" class="btn btn-secondary" @onclick="RichTextEditor">Rich Text Editor</button> <RichTextEditor Content="@_content" @ref="@RichTextEditorHtml"></RichTextEditor>
}
else
{
<button type="button" class="btn btn-secondary" @onclick="RawHtmlEditor">Raw HTML Editor</button>
}
<button type="button" class="btn btn-success" @onclick="SaveContent">Save</button> <button type="button" class="btn btn-success" @onclick="SaveContent">Save</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
</div> @if (!string.IsNullOrEmpty(_content))
</div> {
<br /><br />
<div class="row"> <AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
<div class="col"> }
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo> }
</div>
</div>
@code { @code {
private string _visibleText = "d-none";
private string _visibleRich;
private bool _richTextEditorMode;
private RichTextEditor RichTextEditorHtml; private RichTextEditor RichTextEditorHtml;
private string content; private string _content = null;
private string createdby; private string _createdby;
private DateTime createdon; private DateTime _createdon;
private string modifiedby; private string _modifiedby;
private DateTime modifiedon; private DateTime _modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Edit Html/Text"; public override string Title => "Edit Html/Text";
public bool RichTextEditorMode protected override async Task OnInitializedAsync()
{
get => _richTextEditorMode;
set
{
_richTextEditorMode = value;
if (_richTextEditorMode)
{
_visibleText = "d-none";
_visibleRich = string.Empty;
}
else
{
_visibleText = string.Empty;
_visibleRich = "d-none";
}
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
try try
{ {
if (firstRender) var htmltextservice = new HtmlTextService(http, sitestate);
var htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{ {
if (content == null) _content = htmltext.Content;
{ _content = _content.Replace(Constants.ContentUrl, "/" + PageState.Alias.AliasId.ToString() + Constants.ContentUrl);
RichTextEditorMode = true; _createdby = htmltext.CreatedBy;
await LoadText(); _createdon = htmltext.CreatedOn;
_modifiedby = htmltext.ModifiedBy;
_modifiedon = htmltext.ModifiedOn;
} }
else
{
_content = string.Empty;
} }
} }
catch (Exception ex) catch (Exception ex)
@ -121,48 +58,10 @@
} }
} }
private async Task LoadText()
{
var htmltextservice = new HtmlTextService(http, sitestate);
var htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{
content = htmltext.Content;
createdby = htmltext.CreatedBy;
createdon = htmltext.CreatedOn;
modifiedby = htmltext.ModifiedBy;
modifiedon = htmltext.ModifiedOn;
if (RichTextEditorMode)
{
await RichTextEditorHtml.LoadContent(content);
StateHasChanged();
}
}
}
private async Task RichTextEditor()
{
RichTextEditorMode = true;
await RichTextEditorHtml.LoadContent(content);
StateHasChanged();
}
private async Task RawHtmlEditor()
{
content = await this.RichTextEditorHtml.GetHtml();
RichTextEditorMode = false;
StateHasChanged();
}
private async Task SaveContent() private async Task SaveContent()
{ {
if (RichTextEditorMode) string content = await RichTextEditorHtml.GetHtml();
{ content = content.Replace("/" + PageState.Alias.AliasId.ToString() + Constants.ContentUrl, Constants.ContentUrl);
content = await RichTextEditorHtml.GetHtml();
}
content = content.Replace(((PageState.Alias.Path == string.Empty) ? "/~" : PageState.Alias.Path) + Constants.ContentUrl, Constants.ContentUrl);
try try
{ {
@ -190,5 +89,4 @@
AddModuleMessage("Error Saving Content", MessageType.Error); AddModuleMessage("Error Saving Content", MessageType.Error);
} }
} }
} }

View File

@ -30,7 +30,7 @@
if (htmltext != null) if (htmltext != null)
{ {
content = htmltext.Content; content = htmltext.Content;
content = content.Replace(Constants.ContentUrl, ((PageState.Alias.Path == "") ? "/~" : PageState.Alias.Path) + Constants.ContentUrl); content = content.Replace(Constants.ContentUrl, "/" + PageState.Alias.AliasId.ToString() + Constants.ContentUrl);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -6,7 +6,7 @@
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<RazorLangVersion>3.0</RazorLangVersion> <RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>0.9.0</Version> <Version>0.9.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>

View File

@ -82,7 +82,6 @@ namespace Oqtane.Services
var result = await response.Content.ReadFromJsonAsync<TResult>(); var result = await response.Content.ReadFromJsonAsync<TResult>();
return result; return result;
} }
return default; return default;
} }
@ -121,6 +120,8 @@ namespace Oqtane.Services
if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.NotFound) if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.NotFound)
{ {
//TODO: Log errors here //TODO: Log errors here
Console.WriteLine($"Request: {response.RequestMessage.RequestUri}");
Console.WriteLine($"Response status: {response.StatusCode} {response.ReasonPhrase}"); Console.WriteLine($"Response status: {response.StatusCode} {response.ReasonPhrase}");
} }

View File

@ -2,7 +2,9 @@
@inherits ContainerBase @inherits ContainerBase
<div class="container"> <div class="container">
<div class="row px-4"> <div class="row px-4">
<div class="d-flex flex-nowrap">
<ModuleActions /><h2><ModuleTitle /></h2> <ModuleActions /><h2><ModuleTitle /></h2>
</div>
<hr class="app-rule" /> <hr class="app-rule" />
</div> </div>
<div class="row px-4"> <div class="row px-4">

View File

@ -7,15 +7,7 @@
<div class="sidebar"> <div class="sidebar">
<nav class="navbar"> <nav class="navbar">
<Logo /> <Logo /><Menu Orientation="Vertical" />
<button class="navbar-toggler" aria-expanded="false" aria-controls="navbarSupportedContent"
aria-label="Toggle navigation" type="button" data-toggle="collapse"
data-target="#navbarSupportedContent">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<Menu Orientation="Vertical" />
</div>
</nav> </nav>
</div> </div>

View File

@ -1,27 +1,34 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ThemeControlBase @inherits ThemeControlBase
@attribute [OqtaneIgnore]
@if (BreadCrumbPages.Any()) @if (BreadCrumbPages.Any())
{ {
<span class="app-breadcrumbs">
<ol class="breadcrumb"> <ol class="breadcrumb">
@foreach (var p in BreadCrumbPages) @foreach (var p in BreadCrumbPages)
{ {
<li class="breadcrumb-item @ActiveClass(p)"> if (p.PageId == PageState.Page.PageId)
{
<li class="breadcrumb-item active">
<a href="@NavigateUrl(p.Path)">@p.Name</a> <a href="@NavigateUrl(p.Path)">@p.Name</a>
</li> </li>
} }
else
{
<li class="breadcrumb-item">
<a href="@NavigateUrl(p.Path)">@p.Name</a>
</li>
}
}
</ol> </ol>
</span>
} }
@code { @code {
protected IEnumerable<Page> BreadCrumbPages => GetBreadCrumbPages().Reverse().ToList(); protected IEnumerable<Page> BreadCrumbPages => GetBreadCrumbPages().Reverse().ToList();
protected string ActiveClass(Page page)
{
return (page.PageId == PageState.Page.PageId) ? " active" : string.Empty;
}
private IEnumerable<Page> GetBreadCrumbPages() private IEnumerable<Page> GetBreadCrumbPages()
{ {
var page = PageState.Page; var page = PageState.Page;

View File

@ -1,6 +1,6 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@using Oqtane.Enums
@inherits ThemeControlBase @inherits ThemeControlBase
@attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserService UserService @inject IUserService UserService
@inject IModuleDefinitionService ModuleDefinitionService @inject IModuleDefinitionService ModuleDefinitionService
@ -16,7 +16,7 @@
<div class="@CardClass"> <div class="@CardClass">
<div class="@HeaderClass"> <div class="@HeaderClass">
Control Panel <span class="font-weight-bold">Control Panel</span>
<button type="button" class="close" @onclick="HideControlPanel" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" @onclick="HideControlPanel" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
@ -245,17 +245,17 @@
{ {
if (string.IsNullOrEmpty(ButtonClass)) if (string.IsNullOrEmpty(ButtonClass))
{ {
ButtonClass = "btn-outline-primary"; ButtonClass = "btn-outline-secondary";
} }
if (string.IsNullOrEmpty(CardClass)) if (string.IsNullOrEmpty(CardClass))
{ {
CardClass = "card bg-secondary mb-3"; CardClass = "card border-secondary mb-3";
} }
if (string.IsNullOrEmpty(HeaderClass)) if (string.IsNullOrEmpty(HeaderClass))
{ {
HeaderClass = "card-header text-white"; HeaderClass = "card-header";
} }
if (string.IsNullOrEmpty(BodyClass)) if (string.IsNullOrEmpty(BodyClass))
@ -361,6 +361,7 @@
module.SiteId = PageState.Site.SiteId; module.SiteId = PageState.Site.SiteId;
module.PageId = PageState.Page.PageId; module.PageId = PageState.Page.PageId;
module.ModuleDefinitionName = _moduleDefinitionName; module.ModuleDefinitionName = _moduleDefinitionName;
module.AllPages = false;
module.Permissions = PageState.Page.Permissions; module.Permissions = PageState.Page.Permissions;
module = await ModuleService.AddModuleAsync(module); module = await ModuleService.AddModuleAsync(module);
_moduleId = module.ModuleId.ToString(); _moduleId = module.ModuleId.ToString();

View File

@ -1,6 +1,8 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits LoginBase @inherits LoginBase
@attribute [OqtaneIgnore]
<span class="app-login">
<AuthorizeView> <AuthorizeView>
<Authorizing> <Authorizing>
<text>...</text> <text>...</text>
@ -12,3 +14,4 @@
<button type="button" class="btn btn-primary" @onclick="LoginUser">Login</button> <button type="button" class="btn btn-primary" @onclick="LoginUser">Login</button>
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
</span>

View File

@ -1,21 +1,13 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ThemeControlBase @inherits ThemeControlBase
@inject NavigationManager NavigationManager @attribute [OqtaneIgnore]
@if (PageState.Site.LogoFileId != null) @if (PageState.Site.LogoFileId != null)
{ {
<a href="@Href"> <span class="app-logo">
<a href="@PageState.Alias.Path">
<img class="img-fluid" src="@ContentUrl(PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name" /> <img class="img-fluid" src="@ContentUrl(PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name" />
</a> </a>
</span>
} }
@code {
string Href
{
get
{
var uri = new Uri(NavigationManager.Uri);
return $"{uri.Scheme}://{uri.Authority}";
}
}
}

View File

@ -1,12 +1,14 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits MenuBase @inherits MenuBase
@attribute [OqtaneIgnore]
@if (MenuPages.Any()) @if (MenuPages.Any())
{ {
<div class="app-menu"> <span class="app-menu-toggler">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle Navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
</span>
<div class="app-menu">
<div class="collapse navbar-collapse" id="Menu"> <div class="collapse navbar-collapse" id="Menu">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
@foreach (var p in MenuPages) @foreach (var p in MenuPages)

View File

@ -1,8 +1,15 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits MenuBase @inherits MenuBase
@attribute [OqtaneIgnore]
@if (MenuPages.Any()) @if (MenuPages.Any())
{ {
<span class="app-menu-toggler">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle Navigation">
<span class="navbar-toggler-icon"></span>
</button>
</span>
<div class="app-menu"> <div class="app-menu">
<div class="collapse navbar-collapse" id="Menu">
<ul class="nav flex-column"> <ul class="nav flex-column">
@foreach (var p in MenuPages) @foreach (var p in MenuPages)
{ {
@ -18,10 +25,10 @@
<span class="oi oi-@p.Icon" aria-hidden="true"></span> <span class="oi oi-@p.Icon" aria-hidden="true"></span>
} }
@p.Name @p.Name
</a> </a>
</li> </li>
} }
</ul> </ul>
</div> </div>
</div>
} }

View File

@ -1,8 +1,10 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ModuleActionsBase @inherits ModuleActionsBase
@attribute [OqtaneIgnore]
@if (PageState.EditMode && !PageState.Page.EditMode && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions)) @if (PageState.EditMode && !PageState.Page.EditMode && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions))
{ {
<div class="app-moduleactions">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"></a> <a class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"></a>
<div class="dropdown-menu" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 37px, 0px);"> <div class="dropdown-menu" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 37px, 0px);">
@foreach (var action in Actions) @foreach (var action in Actions)
@ -17,4 +19,5 @@
} }
} }
</div> </div>
</div>
} }

View File

@ -1,7 +1,10 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ContainerBase @inherits ContainerBase
@attribute [OqtaneIgnore]
<span class="app-moduletitle">
@((MarkupString)title) @((MarkupString)title)
</span>
@code { @code {
private string title = ""; private string title = "";

View File

@ -1,7 +1,9 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ThemeControlBase @inherits ThemeControlBase
@attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<span class="app-profile">
<AuthorizeView> <AuthorizeView>
<Authorizing> <Authorizing>
<text>...</text> <text>...</text>
@ -16,7 +18,7 @@
} }
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
</span>
@code { @code {

View File

@ -2,7 +2,9 @@
@inherits ContainerBase @inherits ContainerBase
<div class="container"> <div class="container">
<div class="row px-4"> <div class="row px-4">
<div class="d-flex flex-nowrap">
<ModuleActions /><h2><ModuleTitle /></h2> <ModuleActions /><h2><ModuleTitle /></h2>
</div>
<hr class="app-rule" /> <hr class="app-rule" />
</div> </div>
<div class="row px-4"> <div class="row px-4">

View File

@ -3,9 +3,12 @@
<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">
<Logo /><Menu Orientation="Horizontal" /><div class="ml-md-auto"><UserProfile /> <Login /> <ControlPanel ButtonClass="btn-outline-secondary" CardClass="bg-light" /></div> <Logo /><Menu Orientation="Horizontal" />
<div class="controls ml-md-auto">
<div class="controls-group"><UserProfile /> <Login /> <ControlPanel /></div>
</div>
</nav> </nav>
<div class="container"> <div class="content container">
<PaneLayout /> <PaneLayout />
<div class="row px-4"> <div class="row px-4">
<Pane Name="Admin" /> <Pane Name="Admin" />
@ -18,6 +21,9 @@
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
// go to https://www.bootstrapcdn.com/bootswatch/ and take your favorite theme
//<link href="https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/cyborg/bootstrap.min.css" rel="stylesheet" integrity="sha384-l7xaoY0cJM4h9xh1RfazbgJVUZvdtyLWPueWNtLAphf/UbBgOVzqbOTogxPwYLHM" crossorigin="anonymous">
await LoadBootstrapTheme("https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/cyborg/bootstrap.min.css","sha384-l7xaoY0cJM4h9xh1RfazbgJVUZvdtyLWPueWNtLAphf/UbBgOVzqbOTogxPwYLHM");
await IncludeCSS("Theme.css"); await IncludeCSS("Theme.css");
} }
} }

View File

@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI; using Oqtane.UI;
@ -30,6 +30,13 @@ namespace Oqtane.Themes
await interop.IncludeCSS("Theme", Url); await interop.IncludeCSS("Theme", Url);
} }
public async Task LoadBootstrapTheme(string url, string integrity = null)
{
var interop = new Interop(JSRuntime);
string crossorigin = string.IsNullOrEmpty(integrity) ? string.Empty : "anonymous";
await interop.IncludeLink("bootstrap", "stylesheet", url, "text/css", integrity, crossorigin);
}
public string NavigateUrl() public string NavigateUrl()
{ {
return NavigateUrl(PageState.Page.Path); return NavigateUrl(PageState.Page.Path);

View File

@ -1,4 +1,6 @@
@namespace Oqtane.UI @using System.Diagnostics.CodeAnalysis
@using System.Runtime.InteropServices
@namespace Oqtane.UI
@inject AuthenticationStateProvider AuthenticationStateProvider @inject AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState @inject SiteState SiteState
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@ -11,9 +13,6 @@
@inject IModuleService ModuleService @inject IModuleService ModuleService
@inject IModuleDefinitionService ModuleDefinitionService @inject IModuleDefinitionService ModuleDefinitionService
@inject ILogService LogService @inject ILogService LogService
@using System.Diagnostics.CodeAnalysis
@using Oqtane.Enums
@using System.Runtime.InteropServices
@implements IHandleAfterRender @implements IHandleAfterRender
@DynamicComponent @DynamicComponent

View File

@ -18,3 +18,4 @@
@using Oqtane.Themes @using Oqtane.Themes
@using Oqtane.Themes.Controls @using Oqtane.Themes.Controls
@using Oqtane.UI @using Oqtane.UI
@using Oqtane.Enums

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Framework</id> <id>Oqtane.Framework</id>
<version>0.9.0</version> <version>0.9.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>

View File

@ -16,14 +16,16 @@ namespace Oqtane.Controllers
{ {
private readonly IModuleRepository _modules; private readonly IModuleRepository _modules;
private readonly IPageModuleRepository _pageModules; private readonly IPageModuleRepository _pageModules;
private readonly IPageRepository _pages;
private readonly IModuleDefinitionRepository _moduleDefinitions; private readonly IModuleDefinitionRepository _moduleDefinitions;
private readonly IUserPermissions _userPermissions; private readonly IUserPermissions _userPermissions;
private readonly ILogManager _logger; private readonly ILogManager _logger;
public ModuleController(IModuleRepository modules, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, IUserPermissions userPermissions, ILogManager logger) public ModuleController(IModuleRepository modules, IPageModuleRepository pageModules, IPageRepository pages, IModuleDefinitionRepository moduleDefinitions, IUserPermissions userPermissions, ILogManager logger)
{ {
_modules = modules; _modules = modules;
_pageModules = pageModules; _pageModules = pageModules;
_pages = pages;
_moduleDefinitions = moduleDefinitions; _moduleDefinitions = moduleDefinitions;
_userPermissions = userPermissions; _userPermissions = userPermissions;
_logger = logger; _logger = logger;
@ -42,6 +44,7 @@ namespace Oqtane.Controllers
Module module = new Module(); Module module = new Module();
module.SiteId = pagemodule.Module.SiteId; module.SiteId = pagemodule.Module.SiteId;
module.ModuleDefinitionName = pagemodule.Module.ModuleDefinitionName; module.ModuleDefinitionName = pagemodule.Module.ModuleDefinitionName;
module.AllPages = pagemodule.Module.AllPages;
module.Permissions = pagemodule.Module.Permissions; module.Permissions = pagemodule.Module.Permissions;
module.CreatedBy = pagemodule.Module.CreatedBy; module.CreatedBy = pagemodule.Module.CreatedBy;
module.CreatedOn = pagemodule.Module.CreatedOn; module.CreatedOn = pagemodule.Module.CreatedOn;
@ -111,7 +114,20 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit)) if (ModelState.IsValid && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit))
{ {
module = _modules.UpdateModule(module); module = _modules.UpdateModule(module);
if (module.AllPages)
{
var pageModule = _pageModules.GetPageModules(module.SiteId).FirstOrDefault(item => item.ModuleId == module.ModuleId);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Updated {Module}", module); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Updated {Module}", module);
var pages = _pages.GetPages(module.SiteId).ToList();
foreach (Page page in pages)
{
if (page.PageId != pageModule.PageId && !page.EditMode)
{
_pageModules.AddPageModule(new PageModule { PageId = page.PageId, ModuleId = pageModule.ModuleId, Title = pageModule.Title, Pane = pageModule.Pane, Order = pageModule.Order, ContainerType = pageModule.ContainerType });
}
}
}
} }
else else
{ {

View File

@ -190,9 +190,9 @@ namespace Oqtane.Controllers
} }
else else
{ {
rootPath = Utilities.PathCombine(rootFolder.Parent.FullName , moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Module","\\"); rootPath = Utilities.PathCombine(rootFolder.Parent.FullName , moduleDefinition.Owner + "." + moduleDefinition.Name + "s","\\");
moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Modules, " + moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Module.Client"; moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Modules, " + moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Client.Oqtane";
moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Module.Server"; moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Server.Oqtane";
} }
ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, moduleDefinition); ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, moduleDefinition);

View File

@ -124,6 +124,16 @@ namespace Oqtane.Controllers
page = _pages.AddPage(page); page = _pages.AddPage(page);
_syncManager.AddSyncEvent(_tenants.GetTenant().TenantId, EntityNames.Site, page.SiteId); _syncManager.AddSyncEvent(_tenants.GetTenant().TenantId, EntityNames.Site, page.SiteId);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Page Added {Page}", page); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Page Added {Page}", page);
if (!page.EditMode)
{
var modules = _modules.GetModules(page.SiteId).Where(item => item.AllPages).ToList();
foreach (Module module in modules)
{
var pageModule = _pageModules.GetPageModules(page.SiteId).FirstOrDefault(item => item.ModuleId == module.ModuleId);
_pageModules.AddPageModule(new PageModule { PageId = page.PageId, ModuleId = pageModule.ModuleId, Title = pageModule.Title, Pane = pageModule.Pane, Order = pageModule.Order, ContainerType = pageModule.ContainerType });
}
}
} }
else else
{ {
@ -174,6 +184,7 @@ namespace Oqtane.Controllers
module.SiteId = page.SiteId; module.SiteId = page.SiteId;
module.PageId = page.PageId; module.PageId = page.PageId;
module.ModuleDefinitionName = pm.Module.ModuleDefinitionName; module.ModuleDefinitionName = pm.Module.ModuleDefinitionName;
module.AllPages = false;
module.Permissions = new List<Permission> { module.Permissions = new List<Permission> {
new Permission(PermissionNames.View, userid, true), new Permission(PermissionNames.View, userid, true),
new Permission(PermissionNames.Edit, userid, true) new Permission(PermissionNames.Edit, userid, true)

View File

@ -1,5 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using Oqtane.Shared;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace System.Reflection namespace System.Reflection
@ -31,5 +33,20 @@ namespace System.Reflection
return assembly.GetTypes() return assembly.GetTypes()
.Where(t => t.GetInterfaces().Contains(interfaceType)); .Where(t => t.GetInterfaces().Contains(interfaceType));
} }
public static bool IsOqtaneAssembly(this Assembly assembly)
{
return assembly.FullName != null && (assembly.FullName.Contains("oqtane", StringComparison.OrdinalIgnoreCase));
}
public static bool IsOqtaneAssembly(this FileInfo fileInfo)
{
return (fileInfo.Name.Contains("oqtane", StringComparison.OrdinalIgnoreCase));
}
public static IEnumerable<Assembly> GetOqtaneAssemblies(this AppDomain appDomain)
{
return appDomain.GetAssemblies().Where(a => a.IsOqtaneAssembly());
}
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
@ -16,10 +17,11 @@ namespace Microsoft.Extensions.DependencyInjection
} }
// load MVC application parts from module assemblies // load MVC application parts from module assemblies
foreach (var assembly in OqtaneServiceCollectionExtensions.GetOqtaneModuleAssemblies()) var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{ {
// check if assembly contains MVC Controllers // check if assembly contains MVC Controllers
if (assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Controller))).ToArray().Length > 0) if (assembly.GetTypes().Any(t => t.IsSubclassOf(typeof(Controller))))
{ {
var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly); var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
foreach (var part in partFactory.GetApplicationParts(assembly)) foreach (var part in partFactory.GetApplicationParts(assembly))

View File

@ -5,67 +5,35 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Oqtane.Extensions;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Shared;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
{ {
public static class OqtaneServiceCollectionExtensions public static class OqtaneServiceCollectionExtensions
{ {
private static readonly IList<Assembly> OqtaneModuleAssemblies = new List<Assembly>(); public static IServiceCollection AddOqtaneParts(this IServiceCollection services)
private static Assembly[] Assemblies => AppDomain.CurrentDomain.GetAssemblies();
internal static IEnumerable<Assembly> GetOqtaneModuleAssemblies() => OqtaneModuleAssemblies;
public static IServiceCollection AddOqtaneModules(this IServiceCollection services)
{ {
if (services is null) LoadAssemblies();
{ services.AddOqtaneServices();
throw new ArgumentNullException(nameof(services));
}
LoadAssemblies("Module");
return services; return services;
} }
public static IServiceCollection AddOqtaneThemes(this IServiceCollection services) private static IServiceCollection AddOqtaneServices(this IServiceCollection services)
{ {
if (services is null) if (services is null)
{ {
throw new ArgumentNullException(nameof(services)); throw new ArgumentNullException(nameof(services));
} }
LoadAssemblies("Theme"); var hostedServiceType = typeof(IHostedService);
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
return services;
}
public static IServiceCollection AddOqtaneSiteTemplates(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
LoadAssemblies("SiteTemplate");
return services;
}
public static IServiceCollection AddOqtaneServices(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
// dynamically register module services, contexts, and repository classes
var assemblies = Assemblies.Where(item => item.FullName != null && (item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Module."))).ToArray();
foreach (var assembly in assemblies) foreach (var assembly in assemblies)
{ {
// dynamically register module services, contexts, and repository classes
var implementationTypes = assembly.GetInterfaces<IService>(); var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes) foreach (var implementationType in implementationTypes)
{ {
@ -75,22 +43,8 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped(serviceType ?? implementationType, implementationType); services.AddScoped(serviceType ?? implementationType, implementationType);
} }
} }
}
return services;
}
public static IServiceCollection AddOqtaneHostedServices(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
// dynamically register hosted services // dynamically register hosted services
var hostedServiceType = typeof(IHostedService);
foreach (var assembly in Assemblies)
{
var serviceTypes = assembly.GetTypes(hostedServiceType); var serviceTypes = assembly.GetTypes(hostedServiceType);
foreach (var serviceType in serviceTypes) foreach (var serviceType in serviceTypes)
{ {
@ -104,22 +58,37 @@ namespace Microsoft.Extensions.DependencyInjection
return services; return services;
} }
private static void LoadAssemblies(string pattern) private static void LoadAssemblies()
{ {
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (assemblyPath == null) return; if (assemblyPath == null) return;
var assembliesFolder = new DirectoryInfo(assemblyPath); var assembliesFolder = new DirectoryInfo(assemblyPath);
// iterate through Oqtane assemblies in /bin ( filter is narrow to optimize loading process ) // iterate through Oqtane assemblies in /bin ( filter is narrow to optimize loading process )
foreach (var dll in assembliesFolder.EnumerateFiles($"*.{pattern}.*.dll")) foreach (var dll in assembliesFolder.EnumerateFiles($"*.dll", SearchOption.TopDirectoryOnly).Where(f => f.IsOqtaneAssembly()))
{ {
// check if assembly is already loaded AssemblyName assemblyName;
var assembly = Assemblies.FirstOrDefault(a =>!a.IsDynamic && a.Location == dll.FullName); try
if (assembly == null)
{ {
assemblyName = AssemblyName.GetAssemblyName(dll.FullName);
}
catch
{
Console.WriteLine($"Not Assembly : {dll.Name}");
continue;
}
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
if (!assemblies.Any(a => AssemblyName.ReferenceMatchesDefinition(assemblyName, a.GetName())))
{
try
{
var pdb = Path.ChangeExtension(dll.FullName, ".pdb");
Assembly assembly = null;
// load assembly ( and symbols ) from stream to prevent locking files ( as long as dependencies are in /bin they will load as well ) // load assembly ( and symbols ) from stream to prevent locking files ( as long as dependencies are in /bin they will load as well )
string pdb = dll.FullName.Replace(".dll", ".pdb");
if (File.Exists(pdb)) if (File.Exists(pdb))
{ {
assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)), new MemoryStream(File.ReadAllBytes(pdb))); assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)), new MemoryStream(File.ReadAllBytes(pdb)));
@ -128,10 +97,11 @@ namespace Microsoft.Extensions.DependencyInjection
{ {
assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName))); assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)));
} }
if (pattern == "Module") Console.WriteLine($"Loaded : {assemblyName}");
}
catch (Exception e)
{ {
// build a list of module assemblies Console.WriteLine($"Failed : {assemblyName}\n{e}");
OqtaneModuleAssemblies.Add(assembly);
} }
} }
} }

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Linq;
namespace Oqtane.Extensions
{
public static class StringExtensions
{
public static bool StartWithAnyOf(this string s, IEnumerable<string> list)
{
if (s == null)
{
return false;
}
return list.Any(f => s.StartsWith(f));
}
}
}

View File

@ -180,17 +180,17 @@ namespace Oqtane.Infrastructure
private void FinishUpgrade() private void FinishUpgrade()
{ {
string folder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
// check if upgrade application exists // check if upgrade application exists
string folder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (folder == null || !File.Exists(Path.Combine(folder, "Oqtane.Upgrade.exe"))) return; if (folder == null || !File.Exists(Path.Combine(folder, "Oqtane.Upgrade.exe"))) return;
// run upgrade application // run upgrade application
var process = new Process var process = new Process
{ {
StartInfo = StartInfo =
{ {
FileName = Path.Combine(folder, "Oqtane.Upgrade.exe"), FileName = Path.Combine(folder, "Oqtane.Upgrade.exe"),
Arguments = "", Arguments = "\"" + _environment.ContentRootPath + "\" \"" + _environment.WebRootPath + "\"",
ErrorDialog = false, ErrorDialog = false,
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true, CreateNoWindow = true,

View File

@ -4,7 +4,7 @@
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>0.9.0</Version> <Version>0.9.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -17,10 +17,15 @@
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Remove="Scripts\Tenant.0.9.1.sql" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<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" />
<EmbeddedResource Include="Scripts\Master.0.9.0.sql" /> <EmbeddedResource Include="Scripts\Master.0.9.0.sql" />
<EmbeddedResource Include="Scripts\Tenant.0.9.1.sql" />
<EmbeddedResource Include="Scripts\Tenant.0.9.0.sql" /> <EmbeddedResource Include="Scripts\Tenant.0.9.0.sql" />
</ItemGroup> </ItemGroup>

View File

@ -75,6 +75,7 @@ namespace Oqtane.Repository
// get module assemblies // get module assemblies
_moduleDefinitions = LoadModuleDefinitionsFromAssemblies(); _moduleDefinitions = LoadModuleDefinitionsFromAssemblies();
} }
List<ModuleDefinition> moduleDefinitions = _moduleDefinitions; List<ModuleDefinition> moduleDefinitions = _moduleDefinitions;
List<Permission> permissions = new List<Permission>(); List<Permission> permissions = new List<Permission>();
@ -109,18 +110,22 @@ namespace Oqtane.Repository
{ {
moduledefinition.Name = moduledef.Name; moduledefinition.Name = moduledef.Name;
} }
if (!string.IsNullOrEmpty(moduledef.Description)) if (!string.IsNullOrEmpty(moduledef.Description))
{ {
moduledefinition.Description = moduledef.Description; moduledefinition.Description = moduledef.Description;
} }
if (!string.IsNullOrEmpty(moduledef.Categories)) if (!string.IsNullOrEmpty(moduledef.Categories))
{ {
moduledefinition.Categories = moduledef.Categories; moduledefinition.Categories = moduledef.Categories;
} }
if (!string.IsNullOrEmpty(moduledef.Version)) if (!string.IsNullOrEmpty(moduledef.Version))
{ {
moduledefinition.Version = moduledef.Version; moduledefinition.Version = moduledef.Version;
} }
if (siteId != -1) if (siteId != -1)
{ {
if (permissions.Count == 0) if (permissions.Count == 0)
@ -139,9 +144,11 @@ namespace Oqtane.Repository
} }
} }
} }
// remove module definition from list as it is already synced // remove module definition from list as it is already synced
moduledefs.Remove(moduledef); moduledefs.Remove(moduledef);
} }
moduledefinition.ModuleDefinitionId = moduledef.ModuleDefinitionId; moduledefinition.ModuleDefinitionId = moduledef.ModuleDefinitionId;
moduledefinition.SiteId = siteId; moduledefinition.SiteId = siteId;
moduledefinition.CreatedBy = moduledef.CreatedBy; moduledefinition.CreatedBy = moduledef.CreatedBy;
@ -157,6 +164,7 @@ namespace Oqtane.Repository
{ {
_permissions.DeletePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId); _permissions.DeletePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId);
} }
_db.ModuleDefinition.Remove(moduledefinition); // delete _db.ModuleDefinition.Remove(moduledefinition); // delete
_db.SaveChanges(); _db.SaveChanges();
} }
@ -168,12 +176,12 @@ namespace Oqtane.Repository
{ {
List<ModuleDefinition> moduleDefinitions = new List<ModuleDefinition>(); List<ModuleDefinition> moduleDefinitions = new List<ModuleDefinition>();
// iterate through Oqtane module assemblies // iterate through Oqtane module assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies() var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
.Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Module.")).ToArray();
foreach (Assembly assembly in assemblies) foreach (Assembly assembly in assemblies)
{ {
moduleDefinitions = LoadModuleDefinitionsFromAssembly(moduleDefinitions, assembly); moduleDefinitions = LoadModuleDefinitionsFromAssembly(moduleDefinitions, assembly);
} }
return moduleDefinitions; return moduleDefinitions;
} }
@ -183,13 +191,15 @@ namespace Oqtane.Repository
Type[] modulecontroltypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModuleControl))).ToArray(); Type[] modulecontroltypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModuleControl))).ToArray();
foreach (Type modulecontroltype in modulecontroltypes) foreach (Type modulecontroltype in modulecontroltypes)
{ {
if (modulecontroltype.Name != "ModuleBase" && !modulecontroltype.Namespace.EndsWith(".Controls")) // Check if type should be ignored
{ if (modulecontroltype.Name == "ModuleBase"
string[] typename = modulecontroltype.AssemblyQualifiedName?.Split(',').Select(item => item.Trim()).ToArray(); || modulecontroltype.IsGenericType
string[] segments = typename[0].Split('.'); || modulecontroltype.IsAbstract
Array.Resize(ref segments, segments.Length - 1); || Attribute.IsDefined(modulecontroltype, typeof(OqtaneIgnoreAttribute))
string moduleType = string.Join(".", segments); ) continue;
string qualifiedModuleType = moduleType + ", " + typename[1];
string moduleNamespace = modulecontroltype.Namespace;
string qualifiedModuleType = moduleNamespace + ", " + modulecontroltype.Assembly.GetName().Name;
int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType); int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
if (index == -1) if (index == -1)
@ -198,7 +208,7 @@ namespace Oqtane.Repository
Type moduletype = assembly Type moduletype = assembly
.GetTypes() .GetTypes()
.Where(item => item.Namespace != null) .Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(moduleType)) .Where(item => item.Namespace.StartsWith(moduleNamespace))
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule))); .FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule)));
if (moduletype != null) if (moduletype != null)
{ {
@ -211,21 +221,23 @@ namespace Oqtane.Repository
// set default property values // set default property values
moduledefinition = new ModuleDefinition moduledefinition = new ModuleDefinition
{ {
Name = moduleType.Substring(moduleType.LastIndexOf(".") + 1), Name = moduleNamespace.Substring(moduleNamespace.LastIndexOf(".") + 1),
Description = "Manage " + moduleType.Substring(moduleType.LastIndexOf(".") + 1), Description = "Manage " + moduleNamespace.Substring(moduleNamespace.LastIndexOf(".") + 1),
Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "") Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "")
}; };
} }
// set internal properties // set internal properties
moduledefinition.ModuleDefinitionName = qualifiedModuleType; moduledefinition.ModuleDefinitionName = qualifiedModuleType;
moduledefinition.Version = ""; // will be populated from database moduledefinition.Version = ""; // will be populated from database
moduledefinition.ControlTypeTemplate = moduleType + "." + Constants.ActionToken + ", " + typename[1]; moduledefinition.ControlTypeTemplate = moduleNamespace + "." + Constants.ActionToken + ", " + modulecontroltype.Assembly.GetName().Name;
moduledefinition.AssemblyName = assembly.GetName().Name; moduledefinition.AssemblyName = assembly.GetName().Name;
if (string.IsNullOrEmpty(moduledefinition.Categories)) if (string.IsNullOrEmpty(moduledefinition.Categories))
{ {
moduledefinition.Categories = "Common"; moduledefinition.Categories = "Common";
} }
if (moduledefinition.Categories == "Admin") if (moduledefinition.Categories == "Admin")
{ {
moduledefinition.Permissions = new List<Permission> moduledefinition.Permissions = new List<Permission>
@ -241,9 +253,12 @@ namespace Oqtane.Repository
new Permission(PermissionNames.Utilize, Constants.RegisteredRole, true) new Permission(PermissionNames.Utilize, Constants.RegisteredRole, true)
}.EncodePermissions(); }.EncodePermissions();
} }
Console.WriteLine($"Registering module: {moduledefinition.ModuleDefinitionName}");
moduledefinitions.Add(moduledefinition); moduledefinitions.Add(moduledefinition);
index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType); index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
} }
moduledefinition = moduledefinitions[index]; moduledefinition = moduledefinitions[index];
// actions // actions
var modulecontrolobject = Activator.CreateInstance(modulecontroltype); var modulecontrolobject = Activator.CreateInstance(modulecontroltype);
@ -252,15 +267,14 @@ namespace Oqtane.Repository
{ {
foreach (string action in actions.Split(',')) foreach (string action in actions.Split(','))
{ {
moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + typename[1] + ";"); moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + modulecontroltype.Assembly.GetName().Name + ";");
} }
} }
moduledefinitions[index] = moduledefinition; moduledefinitions[index] = moduledefinition;
} }
}
return moduledefinitions; return moduledefinitions;
} }
} }
} }

View File

@ -775,6 +775,7 @@ namespace Oqtane.Repository
{ {
SiteId = site.SiteId, SiteId = site.SiteId,
ModuleDefinitionName = pagetemplatemodule.ModuleDefinitionName, ModuleDefinitionName = pagetemplatemodule.ModuleDefinitionName,
AllPages = false,
Permissions = pagetemplatemodule.ModulePermissions, Permissions = pagetemplatemodule.ModulePermissions,
}; };
module = _moduleRepository.AddModule(module); module = _moduleRepository.AddModule(module);

View File

@ -22,8 +22,8 @@ namespace Oqtane.Repository
List<SiteTemplate> siteTemplates = new List<SiteTemplate>(); List<SiteTemplate> siteTemplates = new List<SiteTemplate>();
// iterate through Oqtane site template assemblies // iterate through Oqtane site template assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies() var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
.Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".SiteTemplate.")).ToArray();
foreach (Assembly assembly in assemblies) foreach (Assembly assembly in assemblies)
{ {
siteTemplates = LoadSiteTemplatesFromAssembly(siteTemplates, assembly); siteTemplates = LoadSiteTemplatesFromAssembly(siteTemplates, assembly);

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Themes; using Oqtane.Themes;
namespace Oqtane.Repository namespace Oqtane.Repository
@ -31,8 +32,7 @@ namespace Oqtane.Repository
List<Theme> themes = new List<Theme>(); List<Theme> themes = new List<Theme>();
// iterate through Oqtane theme assemblies // iterate through Oqtane theme assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies() var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
.Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Theme.")).ToArray();
foreach (Assembly assembly in assemblies) foreach (Assembly assembly in assemblies)
{ {
themes = LoadThemesFromAssembly(themes, assembly); themes = LoadThemesFromAssembly(themes, assembly);
@ -47,20 +47,22 @@ namespace Oqtane.Repository
Type[] themeControlTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IThemeControl))).ToArray(); Type[] themeControlTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IThemeControl))).ToArray();
foreach (Type themeControlType in themeControlTypes) foreach (Type themeControlType in themeControlTypes)
{ {
if (themeControlType.Name != "ThemeBase") // Check if type should be ignored
{ if (themeControlType.Name == "ThemeBase"
string[] typename = themeControlType.AssemblyQualifiedName.Split(',').Select(item => item.Trim()).ToList().ToArray(); || themeControlType.IsGenericType
string[] segments = typename[0].Split('.'); || Attribute.IsDefined(themeControlType, typeof(OqtaneIgnoreAttribute))
Array.Resize(ref segments, segments.Length - 1); ) continue;
string @namespace = string.Join(".", segments);
int index = themes.FindIndex(item => item.ThemeName == @namespace); string themeNamespace = themeControlType.Namespace;
string qualifiedModuleType = themeNamespace + ", " + themeControlType.Assembly.GetName().Name;
int index = themes.FindIndex(item => item.ThemeName == themeNamespace);
if (index == -1) if (index == -1)
{ {
// determine if this theme implements ITheme // determine if this theme implements ITheme
Type themetype = assembly.GetTypes() Type themetype = assembly.GetTypes()
.Where(item => item.Namespace != null) .Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace)) .Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault(); .Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault();
if (themetype != null) if (themetype != null)
{ {
@ -76,25 +78,25 @@ namespace Oqtane.Repository
}; };
} }
// set internal properties // set internal properties
theme.ThemeName = @namespace; theme.ThemeName = themeNamespace;
theme.ThemeControls = ""; theme.ThemeControls = "";
theme.PaneLayouts = ""; theme.PaneLayouts = "";
theme.ContainerControls = ""; theme.ContainerControls = "";
theme.AssemblyName = assembly.FullName.Split(",")[0]; theme.AssemblyName = assembly.FullName.Split(",")[0];
themes.Add(theme); themes.Add(theme);
index = themes.FindIndex(item => item.ThemeName == @namespace); index = themes.FindIndex(item => item.ThemeName == themeNamespace);
} }
theme = themes[index]; theme = themes[index];
theme.ThemeControls += (themeControlType.FullName + ", " + typename[1] + ";"); theme.ThemeControls += (themeControlType.FullName + ", " + themeControlType.Assembly.GetName().Name + ";");
// layouts // layouts
Type[] layouttypes = assembly.GetTypes() Type[] layouttypes = assembly.GetTypes()
.Where(item => item.Namespace != null) .Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace)) .Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(ILayoutControl))).ToArray(); .Where(item => item.GetInterfaces().Contains(typeof(ILayoutControl))).ToArray();
foreach (Type layouttype in layouttypes) foreach (Type layouttype in layouttypes)
{ {
string panelayout = layouttype.FullName + ", " + typename[1] + ";"; string panelayout = layouttype.FullName + ", " + themeControlType.Assembly.GetName().Name + ";";
if (!theme.PaneLayouts.Contains(panelayout)) if (!theme.PaneLayouts.Contains(panelayout))
{ {
theme.PaneLayouts += panelayout; theme.PaneLayouts += panelayout;
@ -104,11 +106,11 @@ namespace Oqtane.Repository
// containers // containers
Type[] containertypes = assembly.GetTypes() Type[] containertypes = assembly.GetTypes()
.Where(item => item.Namespace != null) .Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace)) .Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray(); .Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray();
foreach (Type containertype in containertypes) foreach (Type containertype in containertypes)
{ {
string container = containertype.FullName + ", " + typename[1] + ";"; string container = containertype.FullName + ", " + themeControlType.Assembly.GetName().Name + ";";
if (!theme.ContainerControls.Contains(container)) if (!theme.ContainerControls.Contains(container))
{ {
theme.ContainerControls += container; theme.ContainerControls += container;
@ -117,7 +119,6 @@ namespace Oqtane.Repository
themes[index] = theme; themes[index] = theme;
} }
}
return themes; return themes;
} }
} }

View File

@ -0,0 +1,14 @@
/*
migration script
*/
ALTER TABLE [dbo].[Module] ADD
[AllPages] [bit] NULL
GO
UPDATE [dbo].[Module]
SET [AllPages] = 0
GO

View File

@ -41,7 +41,7 @@ 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.AddMvc().AddNewtonsoftJson();
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 )
@ -188,16 +188,13 @@ namespace Oqtane
services.AddTransient<IUpgradeManager, UpgradeManager>(); services.AddTransient<IUpgradeManager, UpgradeManager>();
// load the external assemblies into the app domain // load the external assemblies into the app domain
services.AddOqtaneModules(); services.AddOqtaneParts();
services.AddOqtaneThemes();
services.AddOqtaneSiteTemplates();
services.AddMvc() services.AddMvc()
.AddOqtaneApplicationParts() // register any Controllers from custom modules .AddOqtaneApplicationParts() // register any Controllers from custom modules
.AddNewtonsoftJson(); .AddNewtonsoftJson();
services.AddOqtaneServices();
services.AddOqtaneHostedServices();
services.AddSwaggerGen(c => services.AddSwaggerGen(c =>
{ {

View File

@ -37,6 +37,10 @@
top: -2px; top: -2px;
} }
.app-menu {
width: 100%
}
.breadcrumb { .breadcrumb {
margin-bottom: 0; margin-bottom: 0;
} }
@ -83,6 +87,7 @@
.navbar-toggler { .navbar-toggler {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
margin-left: auto;
} }
@media (max-width: 767.98px) { @media (max-width: 767.98px) {

File diff suppressed because it is too large Load Diff

View File

@ -28,16 +28,6 @@ app {
width: 100%; /* 100% width */ width: 100%; /* 100% width */
} }
/* Pad the navigation links */
.app-controlpanel .nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.app-controlpanel .card-body .control-label {
color: white;
}
/* Admin Modal */ /* Admin Modal */
.app-admin-modal .modal { .app-admin-modal .modal {
position: fixed; /* Stay in place */ position: fixed; /* Stay in place */

View File

@ -70,7 +70,7 @@ window.interop = {
link.integrity = integrity; link.integrity = integrity;
} }
if (crossorigin !== "") { if (crossorigin !== "") {
link.crossorigin = crossorigin; link.crossOrigin = crossorigin;
} }
document.head.appendChild(link); document.head.appendChild(link);
} }
@ -87,7 +87,7 @@ window.interop = {
if (integrity !== "" && link.integrity !== integrity) { if (integrity !== "" && link.integrity !== integrity) {
link.setAttribute('integrity', integrity); link.setAttribute('integrity', integrity);
} }
if (crossorigin !== "" && link.crossorigin !== crossorigin) { if (crossorigin !== "" && link.crossOrigin !== crossorigin) {
link.setAttribute('crossorigin', crossorigin); link.setAttribute('crossorigin', crossorigin);
} }
} }

View File

@ -9,6 +9,7 @@ namespace Oqtane.Models
public int ModuleId { get; set; } public int ModuleId { get; set; }
public int SiteId { get; set; } public int SiteId { get; set; }
public string ModuleDefinitionName { get; set; } public string ModuleDefinitionName { get; set; }
public bool AllPages { get; set; }
public string CreatedBy { get; set; } public string CreatedBy { get; set; }
public DateTime CreatedOn { get; set; } public DateTime CreatedOn { get; set; }

View File

@ -4,7 +4,7 @@
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>0.9.0</Version> <Version>0.9.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>

View File

@ -3,8 +3,8 @@
public class Constants public class Constants
{ {
public const string PackageId = "Oqtane.Framework"; public const string PackageId = "Oqtane.Framework";
public const string Version = "0.9.0"; public const string Version = "0.9.1";
public const string ReleaseVersions = "0.9.0"; public const string ReleaseVersions = "0.9.0,0.9.1";
public const string PageComponent = "Oqtane.UI.ThemeBuilder, Oqtane.Client"; public const string PageComponent = "Oqtane.UI.ThemeBuilder, Oqtane.Client";
public const string ContainerComponent = "Oqtane.UI.ContainerBuilder, Oqtane.Client"; public const string ContainerComponent = "Oqtane.UI.ContainerBuilder, Oqtane.Client";

View File

@ -0,0 +1,9 @@
using System;
namespace Oqtane.Shared
{
[AttributeUsage(AttributeTargets.Class)]
public class OqtaneIgnoreAttribute : Attribute
{
}
}

View File

@ -4,7 +4,7 @@
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>0.9.0</Version> <Version>0.9.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>

View File

@ -10,14 +10,12 @@ namespace Oqtane.Upgrade
{ {
static void Main(string[] args) static void Main(string[] args)
{ {
string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); // requires 2 arguments - the contentrootpath and the webrootpath of the site
if (args.Length == 2)
// assumes that the application executable must be deployed to the /bin of the Oqtane.Server project
if (binfolder.Contains(Path.Combine("Oqtane.Server", "bin")))
{ {
// ie. binfolder = Oqtane.Server\bin\Debug\netcoreapp3.0\ string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
string rootfolder = Directory.GetParent(binfolder).Parent.Parent.FullName; string rootfolder = args[0];
string deployfolder = Path.Combine(rootfolder, Path.Combine("wwwroot","Framework")); string deployfolder = Path.Combine(args[1], "Framework");
if (Directory.Exists(deployfolder)) if (Directory.Exists(deployfolder))
{ {

View File

@ -22,7 +22,7 @@ Please note that this project is governed by the **[.NET Foundation Contributor
# Roadmap # Roadmap
This project is a work in progress and the schedule for implementing enhancements is dependent upon the availability of community members who are willing/able to assist. This project is a work in progress and the schedule for implementing enhancements is dependent upon the availability of community members who are willing/able to assist.
Note: We are planning to release V1 at the same time that Blazor WebAssembly ships on May 21, 2020 Note: We are planning to release V1 at the same time that Blazor WebAssembly ships on May 19, 2020
V1 (MVP) V1 (MVP)
- [x] Multi-Tenant ( Shared Database & Isolated Database ) - [x] Multi-Tenant ( Shared Database & Isolated Database )