Compare commits

...

81 Commits

Author SHA1 Message Date
8d7845a44d Merge pull request #4944 from oqtane/master
6.0.1 Release
2024-12-20 16:52:31 -05:00
3a15e6e5e9 Merge pull request #4943 from oqtane/dev
6.0.1 Release
2024-12-20 16:52:10 -05:00
3b8a51e855 Merge pull request #4942 from sbwalker/dev
fix reload script to use static array rather than a live HtmlCollection
2024-12-20 15:39:50 -05:00
f2cb34cc35 fix reload script to use static array rather than a live HtmlCollection 2024-12-20 15:39:35 -05:00
723ce62a34 Merge pull request #4940 from sbwalker/dev
write upgrade errors to log rather than console
2024-12-20 14:38:34 -05:00
2c9a2ea021 write upgrade errors to log rather than console 2024-12-20 14:38:21 -05:00
2be008d6d1 Merge pull request #4939 from sbwalker/dev
fix compilation issues in PR #4782
2024-12-20 12:36:24 -05:00
7fb51bdd0a fix compilation issues in PR #4782 2024-12-20 12:36:10 -05:00
abdbe3694f Merge pull request #4782 from thabaum/edit-add-page-scrolltotoppage-error
Fixes #4781: Adds Edit + Add Page ScrollToTopPage() On Settings Tab Form Error Messages
2024-12-20 12:26:55 -05:00
bd87e5012f Merge pull request #4938 from sbwalker/dev
fix documentation
2024-12-20 11:55:50 -05:00
55e18f2364 fix documentation 2024-12-20 11:55:35 -05:00
655e84072d Merge pull request #4937 from sbwalker/dev
use CompressionEnabled switch to disable static asset compression during publish - eliminates need to cleanup files manually in release.cmd
2024-12-20 11:46:52 -05:00
ab5409d5b6 use CompressionEnabled switch to disable static asset compression during publish - eliminates need to cleanup files manually in release.cmd 2024-12-20 11:46:30 -05:00
5a5da6486c Merge pull request #4933 from sbwalker/dev
remove *.br, *.gz files from wwwroot content folders in published release (as MapStaticAssets is not enabled)
2024-12-19 19:56:36 -05:00
7e99252429 remove *.br, *.gz files from wwwroot content folders in published release (as MapStaticAssets is not enabled) 2024-12-19 19:56:13 -05:00
10e0dcef8b Merge pull request #4931 from sbwalker/dev
ensure Pages collection is always returned in the same order by moving GetPagesHierarchy method to the repository
2024-12-19 14:45:34 -05:00
80c8433aad ensure Pages collection is always returned in the same order by moving GetPagesHierarchy method to the repository. 2024-12-19 14:45:12 -05:00
5b0ae372f8 Merge pull request #4930 from sbwalker/dev
add support for data-reload=false attribute in Interactive rendering
2024-12-19 13:04:59 -05:00
b5a1b529ab add support for data-reload=false attribute in Interactive rendering 2024-12-19 13:04:43 -05:00
af821dcd9a Merge pull request #4914 from leigh-pointer/userSettings
User Profile Update: Utilizing User Settings Object
2024-12-18 15:27:34 -05:00
10d3c81520 Merge pull request #4918 from leigh-pointer/ProjectNameTemplates
Implement Dynamic ProjectName Parameter Across Build Process
2024-12-18 15:27:24 -05:00
e3811b453a Merge pull request #4927 from sbwalker/dev
Improvements to add support for script type and data-* attributes. Also added Script and Stylesheet classes to simplify Resource declarations.
2024-12-18 15:16:16 -05:00
ca0fb05baa Improvements to add support for script type and data-* attributes. Also added Script and Stylesheet classes to simplify Resource declarations. 2024-12-18 15:15:54 -05:00
2b4b01bf6e Merge pull request #4926 from sbwalker/dev
always render page-script elements in body
2024-12-18 11:56:18 -05:00
3a1244bddc always render page-script elements in body 2024-12-18 11:56:03 -05:00
b8fd922b19 Merge pull request #4925 from sbwalker/dev
improve reload script to replicate all attributes
2024-12-18 10:32:12 -05:00
03f856025e improve reload script to replicate all attributes 2024-12-18 10:31:59 -05:00
45f04d24c3 Merge pull request #4924 from sbwalker/dev
refactor Static Blazor script processing
2024-12-18 10:09:20 -05:00
2435d610c7 refactor Static Blazor script processing 2024-12-18 10:09:02 -05:00
06572bcd14 Merge pull request #4922 from sbwalker/dev
page-script optimization
2024-12-17 14:57:17 -05:00
3fab79afc0 page-script optimization 2024-12-17 14:57:01 -05:00
2b7dd3fed5 Merge pull request #4921 from sbwalker/dev
render page-script elements at end of head content
2024-12-17 12:31:40 -05:00
d65efed032 render page-script elements at end of head content 2024-12-17 12:31:26 -05:00
c6896ea936 Merge pull request #4920 from sbwalker/dev
fix page-script to support type attribute on inline scripts
2024-12-17 09:13:42 -05:00
b7a41bddec fix page-script to support type attribute on inline scripts 2024-12-17 09:13:26 -05:00
6fd80c3737 Merge pull request #4919 from sbwalker/dev
page-script improvements
2024-12-17 08:34:55 -05:00
0aa690b3b1 page-script improvements 2024-12-17 08:34:39 -05:00
6d5bcfc6ed Implement Dynamic ProjectName Parameter Across Build Process
This pull request introduces a dynamic ProjectName parameter across our build process, enhancing flexibility and reducing hardcoding in our module and theme development workflow. The changes affect the project file, NuSpec file, command scripts.
2024-12-17 10:35:14 +01:00
b60de69fa5 Merge pull request #4917 from sbwalker/dev
improvements to page-script
2024-12-16 12:44:23 -05:00
d991b57d08 improvements to page-script 2024-12-16 12:44:07 -05:00
1133d7fcba User Profile Update: Utilizing User Settings Object
This pull request updates the User Profile implementation to utilize the existing User Settings object on the user object. While the current implementation is functional, this change offers several benefits:
Improved code consistency
Better guidance for developers accessing User Settings
Alignment with the framework's best practices
2024-12-14 11:09:54 +01:00
6fbf0383bb Merge pull request #4911 from leigh-pointer/FixGlow
This is a cosmetic fix to the Oqtane Glow image
2024-12-13 16:46:49 -05:00
0296230219 Merge pull request #4913 from sbwalker/dev
page-script enhancements
2024-12-13 16:46:23 -05:00
dedfbba27a page-script enhancements 2024-12-13 16:46:08 -05:00
dc5441da07 This is a cosmetic fix to the Oqtane Glow image
The glow on the original is cropped at the left by a few pixcels.  This update fixes that and sizes the image to a 600 x 150
2024-12-13 11:45:13 +01:00
585648b7f3 Merge pull request #4907 from leigh-pointer/ProfileMaxLength
FIX for #4906 Profile Field does not allow max length
2024-12-10 09:12:52 -05:00
cd0ee1c26d FIX for #4906 Profile Field does not allow max length
This update applies the maxlength attribute fix to all input and textarea elements. The maxlength attribute will only be added if p.MaxLength is greater than 0, allowing unlimited characters when it's 0 or less.
2024-12-10 09:25:01 +01:00
d7a7be5af4 Merge pull request #4904 from sbwalker/dev
add sync events for user login/logout
2024-12-09 10:55:55 -05:00
13e4267c11 add sync events for user login/logout 2024-12-09 10:55:40 -05:00
15bc47e3e8 Merge pull request #4898 from leigh-pointer/TemplateParam
Implement Dynamic TargetFramework in Debug Scripts #4897
2024-12-09 07:36:00 -05:00
1a4380dcd7 Variable update 2024-12-07 15:02:27 +01:00
5ace34b5cd Add the $TargetFramework macro to the Release Builds
Add the $TargetFramework macro to the Release Builds
2024-12-06 13:37:37 +01:00
f010c0f1fa Implement Dynamic TargetFramework in Debug Scripts #4897
This PR updates our debug scripts (both .cmd and .sh) to dynamically use the current TargetFramework passed from the build process. This change improves flexibility and future-proofs our build process for different .NET versions.
2024-12-05 12:43:38 +01:00
2c721ad5dd Merge pull request #4895 from tvatavuk/enh-4883-control-panel
Add ShowEditMode parameter to ControlPanel to allow hiding the Edit Mode toggle button
2024-12-04 08:25:17 -05:00
8a7c2ce2c2 Merge pull request #4894 from W6HBR/dev
Fix #4885 - Pass userid as int to GetUser for JWT authentication
2024-12-04 08:24:27 -05:00
b2a7b813de Remove redundant IServiceProvider injection 2024-12-04 09:39:02 +01:00
e85cf04b99 Fix #4883: Add ShowEditMode parameter to ControlPanel to allow hiding the Edit Mode toggle button 2024-12-04 09:37:21 +01:00
ab6fa48172 Fix #4885 - Pass userid as int to GetUser for JWT authentication 2024-12-03 10:34:44 -08:00
c81905882f Merge pull request #4881 from sbwalker/dev
improve security of UserRole API
2024-11-27 14:59:41 -05:00
f0d31c1114 improve security of UserRole API 2024-11-27 14:59:25 -05:00
497b255216 Merge pull request #4880 from sbwalker/dev
User Settings should only be accessible to individual users or administrators
2024-11-27 13:16:04 -05:00
d96286d771 User Settings should only be accessible to individual users or administrators 2024-11-27 13:15:43 -05:00
2441647d75 Merge pull request #4879 from sbwalker/dev
update EFCore.NamingConventions to .NET 9 package version
2024-11-27 13:06:53 -05:00
77b780d631 update EFCore.NamingConventions to .NET 9 package version 2024-11-27 13:06:37 -05:00
cdd03bf3d4 Merge pull request #4878 from sbwalker/dev
User Settings should only be accessible to individual users or administrators
2024-11-27 13:04:27 -05:00
e786c35f7d User Settings should only be accessible to individual users or administrators 2024-11-27 13:04:06 -05:00
e83399acb1 Merge pull request #4876 from sbwalker/dev
prevent notifications from being accessed by other users
2024-11-26 14:30:55 -05:00
ffea9e3210 prevent notifications from being accessed by other users 2024-11-26 14:30:41 -05:00
f71a3a1ce3 Merge pull request #4875 from oqtane/revert-4828-TabChange
Revert "Fix for Tabpanel is not updating the UI. #4778"
2024-11-26 13:36:18 -05:00
a5ccc23604 Revert "Fix for Tabpanel is not updating the UI. #4778" 2024-11-26 13:36:05 -05:00
1ed4c8a094 Merge pull request #4874 from oqtane/revert-4871-HTMLTabError
Revert "Rework for Tabstrip regression issue"
2024-11-26 13:35:37 -05:00
784f3771b3 Remove Blank Page Specific Error Message 2024-10-24 13:22:08 -07:00
80316824f7 Remove Blank Page Specific Error Message 2024-10-24 13:21:40 -07:00
cbaebb7b7c Remove Blank Page Specific Error Message 2024-10-24 13:21:15 -07:00
97d3764b6e Remove Blank Page Specific Error Message 2024-10-24 13:20:52 -07:00
4c937be884 Clarify Page Name Required Message 2024-10-24 12:38:51 -07:00
c25cce4398 Clarify Page Name Required Message 2024-10-24 12:38:16 -07:00
58c422285a Adds Message.Required.PageName 2024-10-24 12:28:17 -07:00
f2f22f35e8 Adds Message.Required.PageName 2024-10-24 12:26:36 -07:00
15867a7807 Adds await ScrollToPageTop(); to error messages + Page Name Error Message 2024-10-24 12:23:29 -07:00
e2c404d8bb Adds await ScrollToPageTop(); to error messages + Page Name Error Message
- Adds await ScrollToPageTop(); to error messages
- Adds Blank Page Name Error Message
2024-10-24 12:18:30 -07:00
55 changed files with 700 additions and 398 deletions

View File

@ -10,7 +10,7 @@
@if (_initialized)
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh" ActiveTab="@_activetab">
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
<div class="container">
<div class="row mb-1 align-items-center">
@ -246,7 +246,6 @@
private string _iconresources = "";
private DateTime? _effectivedate = null;
private DateTime? _expirydate = null;
private string _activetab = "";
protected override async Task OnInitializedAsync()
{
@ -317,10 +316,11 @@
{
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
await ScrollToPageTop();
}
}
private void ThemeChanged(ChangeEventArgs e)
private async Task ThemeChanged(ChangeEventArgs e)
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
@ -331,12 +331,12 @@
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
{
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
await ScrollToPageTop();
}
}
private async Task SavePage()
{
_activetab = "Settings";
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
@ -347,6 +347,7 @@
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
{
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
await ScrollToPageTop();
return;
}
if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
@ -397,12 +398,14 @@
if (_pages.Any(item => item.Path == page.Path))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
await ScrollToPageTop();
return;
}
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
await ScrollToPageTop();
return;
}
@ -470,6 +473,7 @@
else
{
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
await ScrollToPageTop();
}
}
@ -477,11 +481,13 @@
{
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
await ScrollToPageTop();
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
await ScrollToPageTop();
}
}

View File

@ -16,7 +16,7 @@
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
@if (_page.UserId == null)
{
<TabStrip Refresh="@_refresh" ActiveTab="@_activetab">
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
<div class="container">
<div class="row mb-1 align-items-center">
@ -30,16 +30,16 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<div class="col-sm-9">
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in _pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList) && page.PageId != _pageId)
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in _pages)
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList) && page.PageId != _pageId)
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
}
}
</select>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
@ -230,10 +230,10 @@
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
<Pager Items="_pageModules">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["ModuleTitle"]</th>
<th>@Localizer["ModuleDefinition"]</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["ModuleTitle"]</th>
<th>@Localizer["ModuleDefinition"]</th>
</Header>
<Row>
<td><ActionLink Action="Settings" Text="Edit" Path="@_actualpath" ModuleId="@context.ModuleId" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" ResourceKey="ModuleSettings" /></td>
@ -356,7 +356,6 @@
private string _iconresources = "";
private DateTime? _effectivedate = null;
private DateTime? _expirydate = null;
private string _activetab = "";
protected override async Task OnInitializedAsync()
{
@ -480,10 +479,11 @@
{
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
await ScrollToPageTop();
}
}
private void ThemeChanged(ChangeEventArgs e)
private async Task ThemeChanged(ChangeEventArgs e)
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
@ -495,6 +495,7 @@
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
{
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
await ScrollToPageTop();
}
}
@ -513,7 +514,7 @@
builder.OpenComponent(0, _themeSettingsType);
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
builder.AddComponentReferenceCapture(2, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
builder.CloseComponent();
};
}
@ -523,7 +524,6 @@
private async Task SavePage()
{
_activetab = "Settings";
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
@ -533,6 +533,7 @@
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
{
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
await ScrollToPageTop();
return;
}
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
@ -583,12 +584,14 @@
if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId))
{
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
await ScrollToPageTop();
return;
}
if (_page.ParentId == null && Constants.ReservedRoutes.Contains(_page.Name.ToLower()))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], _page.Name), MessageType.Warning);
await ScrollToPageTop();
return;
}
@ -672,18 +675,21 @@
}
else
{
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
await ScrollToPageTop();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Page {Page} {Error}", _page, ex.Message);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
await ScrollToPageTop();
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
await ScrollToPageTop();
}
}

View File

@ -105,7 +105,7 @@
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<div class="col-sm-9">
<div class="col-sm-9">
@if (!string.IsNullOrEmpty(p.Options))
{
@if (!string.IsNullOrEmpty(p.Autocomplete))
@ -149,22 +149,26 @@
{
@if (p.IsRequired)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete" />
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete" />
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
}
}
else
{
@if (p.IsRequired)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
}
}
}
@ -174,22 +178,26 @@
{
@if (p.IsRequired)
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"></textarea>
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
}
else
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"></textarea>
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
}
}
else
{
@if (p.IsRequired)
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
}
else
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
}
}
}
@ -218,11 +226,11 @@
{
<Pager Items="@notifications">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["From"]</th>
<th>@Localizer["Subject"]</th>
<th>@Localizer["Received"]</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["From"]</th>
<th>@Localizer["Subject"]</th>
<th>@Localizer["Received"]</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
@ -230,13 +238,13 @@
@if (context.IsRead)
{
<td>@context.FromDisplayName</td>
<td>@(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName)</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
}
else
{
<td><b>@context.FromDisplayName</b></td>
<td><b>@(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName)</b></td>
<td><b>@context.Subject</b></td>
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
}
@ -281,11 +289,11 @@
{
<Pager Items="@notifications">
<Header>
<th style="width: 1px;"></th>
<th style="width: 1px;"></th>
<th>@Localizer["To"]</th>
<th>@Localizer["Subject"]</th>
<th>@Localizer["Sent"]</th>
<th style="width: 1px;"></th>
<th style="width: 1px;"></th>
<th>@Localizer["To"]</th>
<th>@Localizer["Subject"]</th>
<th>@Localizer["Sent"]</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
@ -363,7 +371,7 @@
private File photo = null;
private string _ImageFiles = string.Empty;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private Dictionary<string, string> userSettings;
private string category = string.Empty;
private string filter = "to";
@ -411,9 +419,9 @@
photo = null;
}
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
userSettings = PageState.User.Settings;
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_ImageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
_ImageFiles = SettingService.GetSetting(userSettings, "ImageFiles", Constants.ImageFiles);
_ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles;
await LoadNotificationsAsync();
@ -440,7 +448,7 @@
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
@ -483,7 +491,7 @@
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved");
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
@ -554,7 +562,7 @@
var value = GetProfileValue(profile.Name, string.Empty);
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
{
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue);
}
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
@ -586,7 +594,7 @@
private void ProfileChanged(ChangeEventArgs e, string SettingName)
{
var value = (string)e.Value;
settings = SettingService.SetSetting(settings, SettingName, value);
userSettings = SettingService.SetSetting(userSettings, SettingName, value);
}
private async Task Delete(Notification Notification)

View File

@ -128,7 +128,7 @@
createdon = notification.CreatedOn.ToString();
body = notification.Body;
if (title == "From")
if (title == "From" && !notification.IsRead)
{
notification.IsRead = true;
notification = await NotificationService.UpdateNotificationAsync(notification);

View File

@ -81,11 +81,11 @@
{
@if (p.Rows == 1)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
}
else
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
}
}
</div>

View File

@ -89,8 +89,8 @@
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<div class="col-sm-9">
@if (!string.IsNullOrEmpty(p.Options))
<div class="col-sm-9">
@if (!string.IsNullOrEmpty(p.Options))
{
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
@ -110,11 +110,11 @@
{
@if (p.Rows == 1)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
}
else
{
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
}
}
</div>
@ -132,7 +132,7 @@
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
}
@code {
@code {
private bool _initialized = false;
private string _passwordrequirements;
private int userid;
@ -148,7 +148,7 @@
private string lastipaddress;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private Dictionary<string, string> userSettings;
private string category = string.Empty;
private string createdby;
@ -181,7 +181,7 @@
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress;
settings = await SettingService.GetUserSettingsAsync(user.UserId);
userSettings = user.Settings;
createdby = user.CreatedBy;
createdon = user.CreatedOn;
modifiedby = user.ModifiedBy;
@ -202,7 +202,7 @@
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
@ -232,7 +232,7 @@
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId);
await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
@ -266,7 +266,7 @@
var value = GetProfileValue(profile.Name, string.Empty);
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
{
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue);
}
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
@ -293,7 +293,7 @@
private void ProfileChanged(ChangeEventArgs e, string SettingName)
{
var value = (string)e.Value;
settings = SettingService.SetSetting(settings, SettingName, value);
userSettings = SettingService.SetSetting(userSettings, SettingName, value);
}
private void TogglePassword()

View File

@ -30,8 +30,6 @@ else
[Parameter]
public SecurityAccessLevel? Security { get; set; } // optional - can be used to specify SecurityAccessLevel
public bool IsActive { get; set; }
protected override void OnParametersSet()
{
base.OnParametersSet();

View File

@ -8,9 +8,18 @@
@foreach (TabPanel tabPanel in _tabPanels)
{
<li class="nav-item" @key="tabPanel.Name">
<a class="nav-link @(tabPanel.IsActive ? "active" : "")" data-bs-toggle="tab" href="#@(Id + tabPanel.Name)" role="tab" @onclick:preventDefault="true" @onclick="() => SetActiveTab(tabPanel.Name)">
@tabPanel.DisplayHeading()
</a>
@if (tabPanel.Name.ToLower() == ActiveTab.ToLower())
{
<a class="nav-link active" data-bs-toggle="tab" href="#@(Id + tabPanel.Name)" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading()
</a>
}
else
{
<a class="nav-link" data-bs-toggle="tab" href="#@(Id + tabPanel.Name)" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading()
</a>
}
</li>
}
</ul>
@ -50,43 +59,16 @@
}
}
protected override void OnParametersSet()
protected override void OnParametersSet()
{
base.OnParametersSet();
if (PageState.QueryString.ContainsKey("tab"))
{
ActiveTab = PageState.QueryString["tab"];
}
if (_tabPanels == null || Refresh)
{
_tabPanels = new List<TabPanel>();
}
// Ensure the active tab is valid and exists
if (!string.IsNullOrEmpty(ActiveTab) && _tabPanels.Any())
{
var activeTabExists = _tabPanels.Any(tp => tp.Name.Equals(ActiveTab, StringComparison.OrdinalIgnoreCase));
if (!activeTabExists)
{
ActiveTab = _tabPanels[0].Name;
}
}
// Update the active tab in the UI
UpdateActiveTab();
}
private void UpdateActiveTab()
{
if (!string.IsNullOrEmpty(ActiveTab) && _tabPanels != null)
{
foreach (var tabPanel in _tabPanels)
{
tabPanel.IsActive = tabPanel.Name.Equals(ActiveTab, StringComparison.OrdinalIgnoreCase);
}
}
}
internal void AddTabPanel(TabPanel tabPanel)
@ -94,20 +76,12 @@
if (!_tabPanels.Exists(item => item.Name == tabPanel.Name) && IsAuthorized(tabPanel))
{
_tabPanels.Add(tabPanel);
if (string.IsNullOrEmpty(ActiveTab))
{
ActiveTab = tabPanel.Name;
}
UpdateActiveTab();
StateHasChanged();
}
}
private void SetActiveTab(string tabName)
{
ActiveTab = tabName;
UpdateActiveTab();
StateHasChanged();
if (string.IsNullOrEmpty(ActiveTab))
{
ActiveTab = tabPanel.Name;
}
}
private bool IsAuthorized(TabPanel tabPanel)

View File

@ -98,17 +98,17 @@ namespace Oqtane.Modules
var inline = 0;
foreach (Resource resource in resources)
{
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload)
{
if (!string.IsNullOrEmpty(resource.Url))
{
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
scripts.Add(new { href = url, type = resource.Type ?? "", bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", location = resource.Location.ToString().ToLower(), dataAttributes = resource.DataAttributes });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Type ?? "", resource.Content, resource.Location.ToString().ToLower());
}
}
}

View File

@ -267,4 +267,4 @@
<data name="ExpiryDate.Text" xml:space="preserve">
<value>Expiry Date: </value>
</data>
</root>
</root>

View File

@ -297,4 +297,4 @@
<data name="ExpiryDate.Text" xml:space="preserve">
<value>Expiry Date: </value>
</data>
</root>
</root>

View File

@ -435,4 +435,7 @@
<data name="Functionality" xml:space="preserve">
<value>Functionality</value>
</data>
<data name="System" xml:space="preserve">
<value>System</value>
</data>
</root>

View File

@ -37,11 +37,8 @@
public override List<Resource> Resources => new List<Resource>()
{
// obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css",
Integrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==",
CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = Constants.BootstrapScriptUrl, Integrity = Constants.BootstrapScriptIntegrity, CrossOrigin = "anonymous", Location = ResourceLocation.Body },
new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css", "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==", "anonymous"),
new Stylesheet(ThemePath() + "Theme.css"),
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
};
}

View File

@ -9,7 +9,7 @@
<LanguageSwitcher ButtonClass="@ButtonClass" DropdownAlignment="@LanguageDropdownAlignment" />
}
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
@if (ShowEditMode && (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))))
{
<form method="post" class="app-form-inline" @formname="EditModeForm" @onsubmit="@(async () => await ToggleEditMode(PageState.EditMode))" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
@ -59,9 +59,15 @@
[Parameter]
public string LanguageDropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right
/// <summary>
/// Ability to hide the Edit Mode toggle button
/// </summary>
[Parameter]
public bool ShowEditMode { get; set; } = true;
private PageState _pageState;
private bool _canViewAdminDashboard = false;
private bool _showEditMode = false;
private bool _showEditMode = false; // internal state (not the same as ShowEditMode parameter)
protected override void OnParametersSet()
{

View File

@ -15,7 +15,6 @@
@inject ILogService LoggingService
@inject IStringLocalizer<ControlPanelInteractive> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject IServiceProvider ServiceProvider
<button type="button" class="btn @ButtonClass ms-1" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel" @onclick="ClearMessage">
<span class="oi oi-cog"></span>

View File

@ -17,11 +17,9 @@ namespace Oqtane.Themes.OqtaneTheme
Resources = new List<Resource>()
{
// obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css",
Integrity = "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==",
CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = Constants.BootstrapScriptUrl, Integrity = Constants.BootstrapScriptIntegrity, CrossOrigin = "anonymous", Location = ResourceLocation.Body }
new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css", "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==", "anonymous"),
new Stylesheet("~/Theme.css"),
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
}
};
}

View File

@ -62,17 +62,17 @@ namespace Oqtane.Themes
var inline = 0;
foreach (Resource resource in resources)
{
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload)
{
if (!string.IsNullOrEmpty(resource.Url))
{
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
scripts.Add(new { href = url, type = resource.Type ?? "", bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", location = resource.Location.ToString().ToLower(), dataAttributes = resource.DataAttributes });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Type ?? "", resource.Content, resource.Location.ToString().ToLower());
}
}
}

View File

@ -41,7 +41,7 @@
}
break;
case "HeadContent":
var content = RemoveScripts(SiteState.Properties.HeadContent) + "\n";
var content = FormatScripts(SiteState.Properties.HeadContent) + "\n";
if (content != _content)
{
_content = content;
@ -51,15 +51,29 @@
}
}
private string RemoveScripts(string headcontent)
private string FormatScripts(string headcontent)
{
if (!string.IsNullOrEmpty(headcontent) && RenderMode == RenderModes.Interactive)
if (!string.IsNullOrEmpty(headcontent))
{
var index = headcontent.IndexOf("<script");
while (index >= 0)
{
headcontent = headcontent.Remove(index, headcontent.IndexOf("</script>") + 9 - index);
index = headcontent.IndexOf("<script");
var script = headcontent.Substring(index, headcontent.IndexOf("</script>", index) - index + 9);
if (RenderMode == RenderModes.Interactive)
{
// remove scripts when interactive rendering
headcontent = headcontent.Remove(index, script.Length);
index = headcontent.IndexOf("<script");
}
else
{
if (!script.Contains("><") && !script.Contains("data-reload"))
{
// add data-reload attribute to inline script
headcontent = headcontent.Replace(script, script.Replace("<script", "<script data-reload=\"true\""));
}
index = headcontent.IndexOf("<script", index + 1);
}
}
}
return headcontent;

View File

@ -117,12 +117,17 @@ namespace Oqtane.UI
}
public Task IncludeScript(string id, string src, string integrity, string crossorigin, string type, string content, string location)
{
return IncludeScript(id, src, integrity, crossorigin, type, content, location, null);
}
public Task IncludeScript(string id, string src, string integrity, string crossorigin, string type, string content, string location, Dictionary<string, string> dataAttributes)
{
try
{
_jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeScript",
id, src, integrity, crossorigin, type, content, location);
id, src, integrity, crossorigin, type, content, location, dataAttributes);
return Task.CompletedTask;
}
catch

View File

@ -11,6 +11,8 @@
RenderFragment DynamicComponent { get; set; }
private string lastPagePath = "";
protected override void OnParametersSet()
{
// handle page redirection
@ -26,7 +28,7 @@
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, "profile", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
return;
}
// set page title
if (!string.IsNullOrEmpty(PageState.Page.Title))
{
@ -90,8 +92,16 @@
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
if (!firstRender && PageState.Page.Path != lastPagePath)
{
if (!string.IsNullOrEmpty(PageState.Site.HeadContent) && PageState.Site.HeadContent.Contains("<script"))
{
await InjectScripts(PageState.Site.HeadContent, ResourceLocation.Head);
}
if (!string.IsNullOrEmpty(PageState.Site.BodyContent) && PageState.Site.BodyContent.Contains("<script"))
{
await InjectScripts(PageState.Site.BodyContent, ResourceLocation.Body);
}
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
{
await InjectScripts(PageState.Page.HeadContent, ResourceLocation.Head);
@ -100,6 +110,7 @@
{
await InjectScripts(PageState.Page.BodyContent, ResourceLocation.Body);
}
lastPagePath = PageState.Page.Path;
}
// style sheets
@ -176,18 +187,20 @@
if (!string.IsNullOrEmpty(src))
{
src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src;
scripts.Add(new { href = src, bundle = "", integrity = integrity, crossorigin = crossorigin, es6module = (type == "module"), location = location.ToString().ToLower(), dataAttributes = dataAttributes });
scripts.Add(new { href = src, type = type, bundle = "", integrity = integrity, crossorigin = crossorigin, location = location.ToString().ToLower(), dataAttributes = dataAttributes });
}
else
{
// inline script must have an id attribute
if (id == "")
if (dataAttributes == null || !dataAttributes.ContainsKey("data-reload") || dataAttributes["data-reload"] != "false")
{
count += 1;
id = $"page{PageState.Page.PageId}-script{count}";
if (id == "")
{
count += 1;
id = $"page{PageState.Page.PageId}-script{count}";
}
var pos = script.IndexOf(">") + 1;
await interop.IncludeScript(id, "", "", "", type, script.Substring(pos, script.IndexOf("</script>") - pos), location.ToString().ToLower(), dataAttributes);
}
index = script.IndexOf(">") + 1;
await interop.IncludeScript(id, "", "", "", "", script.Substring(index, script.IndexOf("</script>") - index), location.ToString().ToLower());
}
index = content.IndexOf("<script", index + 1);
}

View File

@ -29,12 +29,11 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>1701;1702;EF1001;AD0001;NU1608</NoWarn>
<NoWarn>1701;1702;EF1001;AD0001</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1" />
</ItemGroup>

View File

@ -36,7 +36,6 @@ if "%%~nxi" == "%%j" set /A found=1
)
if not !found! == 1 rmdir /Q/S "%%i"
)
del "..\Oqtane.Server\bin\Release\net9.0\publish\Oqtane.Server.staticwebassets.endpoints.json"
del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
ren "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.release.json" "appsettings.json"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"

View File

@ -76,6 +76,10 @@
@((MarkupString)_scripts)
@((MarkupString)_bodyResources)
@if (_renderMode == RenderModes.Static)
{
<page-script src="./js/reload.js"></page-script>
}
}
else
{
@ -188,9 +192,6 @@
_scripts += CreateScrollPositionScript();
}
_headResources += ParseScripts(site.HeadContent);
_bodyResources += ParseScripts(site.BodyContent);
// set culture if not specified
string cultureCookie = Context.Request.Cookies[Shared.CookieRequestCultureProvider.DefaultCookieName];
if (cultureCookie == null)
@ -510,26 +511,10 @@
"</script>" + Environment.NewLine;
}
private string ParseScripts(string content)
{
var scripts = "";
// in interactive render mode, parse scripts from content and inject into page
if (_renderMode == RenderModes.Interactive && !string.IsNullOrEmpty(content))
{
var index = content.IndexOf("<script");
while (index >= 0)
{
scripts += content.Substring(index, content.IndexOf("</script>", index) + 9 - index);
index = content.IndexOf("<script", index + 1);
}
}
return scripts;
}
private void AddScript(Resource resource, Alias alias)
{
var script = CreateScript(resource, alias);
if (resource.Location == Shared.ResourceLocation.Head)
if (resource.Location == Shared.ResourceLocation.Head && !resource.Reload)
{
if (!_headResources.Contains(script))
{
@ -547,36 +532,29 @@
private string CreateScript(Resource resource, Alias alias)
{
if (!string.IsNullOrEmpty(resource.Url))
if (!resource.Reload)
{
if (!resource.Reload)
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
var dataAttributes = "";
if (resource.DataAttributes != null && resource.DataAttributes.Count > 0)
{
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
return "<script" +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
((resource.ES6Module) ? " type=\"module\"" : "") +
" src=\"" + url + "\"></script>"; // src at end of element due to enhanced navigation patch algorithm
}
else
{
// use custom element which can execute script on every page transition
@if (string.IsNullOrEmpty(resource.Integrity) && string.IsNullOrEmpty(resource.CrossOrigin))
foreach (var attribute in resource.DataAttributes)
{
return "<page-script src=\"" + resource.Url + "\"></page-script>";
}
else
{
// use modulepreload for external resources
return "<link rel=\"modulepreload\" href=\"" + resource.Url + "\" integrity=\"" + resource.Integrity + "\" crossorigin=\"" + resource.CrossOrigin + "\" />\n" +
"<page-script src=\"" + resource.Url + "\"></page-script>";
dataAttributes += " " + attribute.Key + "=\"" + attribute.Value + "\"";
}
}
return "<script src=\"" + url + "\"" +
((!string.IsNullOrEmpty(resource.Type)) ? " type=\"" + resource.Type + "\"" : "") +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
((!string.IsNullOrEmpty(dataAttributes)) ? dataAttributes : "") +
"></script>";
}
else
{
// inline script
return "<script>" + resource.Content + "</script>";
return "<page-script src=\"" + resource.Url + "\"></page-script>";
}
}

View File

@ -183,7 +183,7 @@ namespace Oqtane.Controllers
{
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && notification.NotificationId == id && _notifications.GetNotification(notification.NotificationId, false) != null && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId)))
{
if (!User.IsInRole(RoleNames.Admin))
if (!User.IsInRole(RoleNames.Admin) && notification.FromUserId != null)
{
// content must be HTML encoded for non-admins to prevent HTML injection
notification.Subject = WebUtility.HtmlEncode(notification.Subject);
@ -223,7 +223,7 @@ namespace Oqtane.Controllers
private bool IsAuthorized(int? userid)
{
bool authorized = true;
bool authorized = false;
if (userid != null)
{
authorized = (_userPermissions.GetUser(User).UserId == userid);

View File

@ -9,7 +9,7 @@ using System.Net;
using Oqtane.Enums;
using Oqtane.Infrastructure;
using Oqtane.Repository;
using System.IO;
using System;
namespace Oqtane.Controllers
{

View File

@ -269,11 +269,7 @@ namespace Oqtane.Controllers
authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName);
break;
case EntityNames.User:
authorized = true;
if (permissionName == PermissionNames.Edit)
{
authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
}
authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
break;
case EntityNames.Visitor:
authorized = User.IsInRole(RoleNames.Admin);
@ -319,7 +315,7 @@ namespace Oqtane.Controllers
filter = !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, PermissionNames.Edit);
break;
case EntityNames.User:
filter = !User.IsInRole(RoleNames.Admin) && _userPermissions.GetUser(User).UserId != entityId;
filter = !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) && _userPermissions.GetUser(User).UserId != entityId;
break;
case EntityNames.Visitor:
if (!User.IsInRole(RoleNames.Admin))

View File

@ -28,9 +28,10 @@ namespace Oqtane.Controllers
private readonly IJwtManager _jwtManager;
private readonly IFileRepository _files;
private readonly ISettingRepository _settings;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, IFileRepository files, ISettingRepository settings, ILogManager logger)
public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, IFileRepository files, ISettingRepository settings, ISyncManager syncManager, ILogManager logger)
{
_users = users;
_tenantManager = tenantManager;
@ -40,6 +41,7 @@ namespace Oqtane.Controllers
_jwtManager = jwtManager;
_files = files;
_settings = settings;
_syncManager = syncManager;
_logger = logger;
}
@ -145,20 +147,7 @@ namespace Oqtane.Controllers
filtered.DeletedBy = user.DeletedBy;
filtered.DeletedOn = user.DeletedOn;
filtered.IsDeleted = user.IsDeleted;
}
// if authenticated user is accessing their own user account
if (_userPermissions.GetUser(User).UserId == user.UserId)
{
// include all settings
filtered.Settings = user.Settings;
}
else
{
// include only public settings
filtered.Settings = _settings.GetSettings(EntityNames.User, user.UserId)
.Where(item => !item.IsPrivate)
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
filtered.Settings = user.Settings; // include all settings
}
}
@ -266,6 +255,7 @@ namespace Oqtane.Controllers
if (_userPermissions.GetUser(User).UserId == user.UserId)
{
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, "Logout");
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", (user != null) ? user.Username : "");
}
}
@ -279,6 +269,7 @@ namespace Oqtane.Controllers
{
await _userManager.LogoutUserEverywhere(user);
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, "Logout");
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout Everywhere {Username}", (user != null) ? user.Username : "");
}
}

View File

@ -10,7 +10,6 @@ using System.Linq;
using System.Net;
using Oqtane.Security;
using System;
using Oqtane.Modules.Admin.Roles;
namespace Oqtane.Controllers
{
@ -40,24 +39,34 @@ namespace Oqtane.Controllers
public IEnumerable<UserRole> Get(string siteid, string userid = null, string rolename = null)
{
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && (userid != null || rolename != null))
int UserId = -1;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && (userid != null && int.TryParse(userid, out UserId) || rolename != null))
{
var userroles = _userRoles.GetUserRoles(SiteId).ToList();
if (userid != null)
if (IsAuthorized(UserId, rolename))
{
int UserId = int.TryParse(userid, out UserId) ? UserId : -1;
userroles = userroles.Where(item => item.UserId == UserId).ToList();
var userroles = _userRoles.GetUserRoles(SiteId).ToList();
if (UserId != -1)
{
userroles = userroles.Where(item => item.UserId == UserId).ToList();
}
if (rolename != null)
{
userroles = userroles.Where(item => item.Role.Name == rolename).ToList();
}
var user = _userPermissions.GetUser();
for (int i = 0; i < userroles.Count(); i++)
{
userroles[i] = Filter(userroles[i], user.UserId);
}
return userroles.OrderBy(u => u.User.DisplayName);
}
if (rolename != null)
else
{
userroles = userroles.Where(item => item.Role.Name == rolename).ToList();
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UserRole Get Attempt For Site {SiteId} User {UserId} Role {RoleName}", siteid, userid, rolename);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
var user = _userPermissions.GetUser();
for (int i = 0; i < userroles.Count(); i++)
{
userroles[i] = Filter(userroles[i], user.UserId);
}
return userroles.OrderBy(u => u.User.DisplayName);
}
else
{
@ -73,7 +82,7 @@ namespace Oqtane.Controllers
public UserRole Get(int id)
{
var userrole = _userRoles.GetUserRole(id);
if (userrole != null && SiteValid(userrole.Role.SiteId))
if (userrole != null && SiteValid(userrole.Role.SiteId) && IsAuthorized(userrole.UserId, userrole.Role.Name))
{
return Filter(userrole, _userPermissions.GetUser().UserId);
}
@ -92,33 +101,57 @@ namespace Oqtane.Controllers
}
}
private bool IsAuthorized(int userId, string roleName)
{
bool authorized = true;
if (userId != -1)
{
authorized = _userPermissions.GetUser(User).UserId == userId;
}
if (authorized && !string.IsNullOrEmpty(roleName))
{
authorized = User.IsInRole(roleName);
}
return authorized;
}
private UserRole Filter(UserRole userrole, int userid)
{
// clone object to avoid mutating cache
UserRole filtered = null;
if (userrole != null)
{
userrole.User.Password = "";
userrole.User.IsAuthenticated = false;
userrole.User.TwoFactorCode = "";
userrole.User.TwoFactorExpiry = null;
filtered = new UserRole();
if (!_userPermissions.IsAuthorized(User, userrole.User.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && userid != userrole.User.UserId)
// public properties
filtered.UserRoleId = userrole.UserRoleId;
filtered.UserId = userrole.UserId;
filtered.RoleId = userrole.RoleId;
filtered.User = new User();
filtered.User.SiteId = userrole.User.SiteId;
filtered.User.UserId = userrole.User.UserId;
filtered.User.Username = userrole.User.Username;
filtered.User.DisplayName = userrole.User.DisplayName;
filtered.Role = new Role();
filtered.Role.SiteId = userrole.Role.SiteId;
filtered.Role.RoleId = userrole.Role.RoleId;
filtered.Role.Name = userrole.Role.Name;
// include private properties if administrator
if (_userPermissions.IsAuthorized(User, filtered.User.SiteId, EntityNames.UserRole, -1, PermissionNames.Write, RoleNames.Admin))
{
userrole.User.Email = "";
userrole.User.PhotoFileId = null;
userrole.User.LastLoginOn = DateTime.MinValue;
userrole.User.LastIPAddress = "";
userrole.User.Roles = "";
userrole.User.CreatedBy = "";
userrole.User.CreatedOn = DateTime.MinValue;
userrole.User.ModifiedBy = "";
userrole.User.ModifiedOn = DateTime.MinValue;
userrole.User.DeletedBy = "";
userrole.User.DeletedOn = DateTime.MinValue;
userrole.User.IsDeleted = false;
userrole.User.TwoFactorRequired = false;
filtered.User.Email = userrole.User.Email;
filtered.User.PhotoFileId = userrole.User.PhotoFileId;
filtered.User.LastLoginOn = userrole.User.LastLoginOn;
filtered.User.LastIPAddress = userrole.User.LastIPAddress;
filtered.User.CreatedOn = userrole.User.CreatedOn;
}
}
return userrole;
return filtered;
}
// POST api/<controller>

View File

@ -645,6 +645,9 @@ namespace Oqtane.Extensions
}
}
var _syncManager = httpContext.RequestServices.GetRequiredService<ISyncManager>();
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Login");
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress.ToString(), providerName);
}
}

View File

@ -59,7 +59,7 @@ namespace Oqtane.Infrastructure
if (userid != null && username != null)
{
var _users = context.RequestServices.GetService(typeof(IUserManager)) as IUserManager;
var user = _users.GetUser(userid, alias.SiteId); // cached
var user = _users.GetUser(int.Parse(userid), alias.SiteId); // cached
if (user != null && !user.IsDeleted)
{
var claimsidentity = UserSecurity.CreateClaimsIdentity(alias, user);

View File

@ -1,11 +1,11 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@ -17,12 +17,14 @@ namespace Oqtane.Infrastructure
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IWebHostEnvironment _environment;
private readonly IConfigManager _configManager;
private readonly ILogger<UpgradeManager> _filelogger;
public UpgradeManager(IServiceScopeFactory serviceScopeFactory, IWebHostEnvironment environment, IConfigManager configManager)
public UpgradeManager(IServiceScopeFactory serviceScopeFactory, IWebHostEnvironment environment, IConfigManager configManager, ILogger<UpgradeManager> filelogger)
{
_serviceScopeFactory = serviceScopeFactory;
_environment = environment;
_configManager = configManager;
_filelogger = filelogger;
}
public void Upgrade(Tenant tenant, string version)
@ -91,7 +93,7 @@ namespace Oqtane.Infrastructure
catch (Exception ex)
{
// error deleting directory
Debug.WriteLine($"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}");
_filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}"));
}
}
}
@ -109,7 +111,7 @@ namespace Oqtane.Infrastructure
catch (Exception ex)
{
// error populating guid
Debug.WriteLine($"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}");
_filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}"));
}
}
@ -277,7 +279,7 @@ namespace Oqtane.Infrastructure
}
catch (Exception ex)
{
Debug.WriteLine($"Oqtane Error: Error In 3.2.0 Upgrade Logic - {ex}");
_filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 3.2.0 Upgrade Logic - {ex}"));
}
}
@ -313,7 +315,7 @@ namespace Oqtane.Infrastructure
}
catch (Exception ex)
{
Debug.WriteLine($"Oqtane Error: Error In 3.2.1 Upgrade Logic - {ex}");
_filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 3.2.1 Upgrade Logic - {ex}"));
}
}
@ -357,7 +359,7 @@ namespace Oqtane.Infrastructure
}
catch (Exception ex)
{
Debug.WriteLine($"Oqtane Error: Error In 3.3.0 Upgrade Logic - {ex}");
_filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 3.3.0 Upgrade Logic - {ex}"));
}
}
@ -384,7 +386,7 @@ namespace Oqtane.Infrastructure
catch (Exception ex)
{
// error deleting file
Debug.WriteLine($"Oqtane Error: Error In 5.1.0 Upgrade Logic - {ex}");
_filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 5.1.0 Upgrade Logic - {ex}"));
}
}
}
@ -487,7 +489,7 @@ namespace Oqtane.Infrastructure
catch (Exception ex)
{
// error deleting asesmbly
Debug.WriteLine($"Oqtane Error: 6.0.1 Upgrade Error Removing {assembly} - {ex}");
_filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: 6.0.1 Upgrade Error Removing {assembly} - {ex}"));
}
}
}

View File

@ -374,6 +374,8 @@ namespace Oqtane.Managers
_users.UpdateUser(user);
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful For {Username} From IP Address {IPAddress}", user.Username, LastIPAddress);
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Login");
if (setCookie)
{
await _identitySignInManager.SignInAsync(identityuser, isPersistent);

View File

@ -19,6 +19,7 @@
<DefineConstants>$(DefineConstants);OQTANE;OQTANE3</DefineConstants>
<PreserveCompilationContext>true</PreserveCompilationContext>
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
<CompressionEnabled>false</CompressionEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Remove="wwwroot\Modules\Templates\**" />

View File

@ -35,6 +35,7 @@ namespace Oqtane.Pages
{
await _userManager.LogoutUserEverywhere(user);
}
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Logout");
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, SyncEventActions.Reload);
}

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
@ -31,7 +32,47 @@ namespace Oqtane.Repository
{
page.PermissionList = permissions.Where(item => item.EntityId == page.PageId).ToList();
}
return pages;
return GetPagesHierarchy(pages);
}
private static List<Page> GetPagesHierarchy(List<Page> pages)
{
List<Page> hierarchy = new List<Page>();
Action<List<Page>, Page> getPath = null;
getPath = (pageList, page) =>
{
IEnumerable<Page> children;
int level;
if (page == null)
{
level = -1;
children = pages.Where(item => item.ParentId == null);
}
else
{
level = page.Level;
children = pages.Where(item => item.ParentId == page.PageId);
}
foreach (Page child in children)
{
child.Level = level + 1;
child.HasChildren = pages.Any(item => item.ParentId == child.PageId && !item.IsDeleted && item.IsNavigation);
hierarchy.Add(child);
getPath(pageList, child);
}
};
pages = pages.OrderBy(item => item.Order).ToList();
getPath(pages, null);
// add any non-hierarchical items to the end of the list
foreach (Page page in pages)
{
if (hierarchy.Find(item => item.PageId == page.PageId) == null)
{
hierarchy.Add(page);
}
}
return hierarchy;
}
public Page AddPage(Page page)

View File

@ -127,7 +127,6 @@ namespace Oqtane.Services
.ToDictionary(setting => setting.SettingName, setting => (setting.IsPrivate ? _private : "") + setting.SettingValue);
site.Pages.Add(page);
}
site.Pages = GetPagesHierarchy(site.Pages);
// framework modules
var modules = GetPageModules(site.SiteId);

View File

@ -20,10 +20,10 @@
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="debug.cmd" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="bash $(ProjectDir)debug.sh" />
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Release'" Command="release.cmd" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Release'" Command="bash $(ProjectDir)release.sh" />
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="debug.cmd $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="bash $(ProjectDir)debug.sh $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Release'" Command="release.cmd $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Release'" Command="bash $(ProjectDir)release.sh $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
</Target>
</Project>
</Project>

View File

@ -1,32 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>[Owner].Module.[Module]</id>
<version>1.0.0</version>
<authors>[Owner]</authors>
<owners>[Owner]</owners>
<title>[Module]</title>
<description>[Description]</description>
<copyright>[Owner]</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<icon>icon.png</icon>
<tags>oqtane module</tags>
<releaseNotes></releaseNotes>
<summary></summary>
<dependencies>
<dependency id="Oqtane.Framework" version="[FrameworkVersion]" />
</dependencies>
</metadata>
<files>
<file src="..\Client\bin\Release\net9.0\[Owner].Module.[Module].Client.Oqtane.dll" target="lib\net9.0" />
<file src="..\Client\bin\Release\net9.0\[Owner].Module.[Module].Client.Oqtane.pdb" target="lib\net9.0" />
<file src="..\Server\bin\Release\net9.0\[Owner].Module.[Module].Server.Oqtane.dll" target="lib\net9.0" />
<file src="..\Server\bin\Release\net9.0\[Owner].Module.[Module].Server.Oqtane.pdb" target="lib\net9.0" />
<file src="..\Shared\bin\Release\net9.0\[Owner].Module.[Module].Shared.Oqtane.dll" target="lib\net9.0" />
<file src="..\Shared\bin\Release\net9.0\[Owner].Module.[Module].Shared.Oqtane.pdb" target="lib\net9.0" />
<file src="..\Server\wwwroot\**\*.*" target="wwwroot" />
<file src="icon.png" target="" />
</files>
</package>
<metadata>
<id>$projectname$</id>
<version>1.0.0</version>
<authors>[Owner]</authors>
<owners>[Owner]</owners>
<title>[Module]</title>
<description>[Description]</description>
<copyright>[Owner]</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<icon>icon.png</icon>
<tags>oqtane module</tags>
<releaseNotes></releaseNotes>
<summary></summary>
<dependencies>
<dependency id="Oqtane.Framework" version="[FrameworkVersion]" />
</dependencies>
</metadata>
<files>
<file src="..\Client\bin\Release\$targetframework$\$ProjectName$.Client.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Client\bin\Release\$targetframework$\$ProjectName$.Client.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Server\bin\Release\$targetframework$\$ProjectName$.Server.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Server\bin\Release\$targetframework$\$ProjectName$.Server.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Shared\bin\Release\$targetframework$\$ProjectName$.Shared.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Shared\bin\Release\$targetframework$\$ProjectName$.Shared.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Server\wwwroot\**\*.*" target="wwwroot" />
<file src="icon.png" target="" />
</files>
</package>

View File

@ -1,7 +1,11 @@
XCOPY "..\Client\bin\Debug\net9.0\[Owner].Module.[Module].Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y
XCOPY "..\Client\bin\Debug\net9.0\[Owner].Module.[Module].Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y
XCOPY "..\Server\bin\Debug\net9.0\[Owner].Module.[Module].Server.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y
XCOPY "..\Server\bin\Debug\net9.0\[Owner].Module.[Module].Server.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y
XCOPY "..\Shared\bin\Debug\net9.0\[Owner].Module.[Module].Shared.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y
XCOPY "..\Shared\bin\Debug\net9.0\[Owner].Module.[Module].Shared.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y
XCOPY "..\Server\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I
@echo off
set TargetFramework=%1
set ProjectName=%2
XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Server\bin\Debug\%TargetFramework%\%ProjectName%.Server.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Server\bin\Debug\%TargetFramework%\%ProjectName%.Server.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Shared\bin\Debug\%TargetFramework%\%ProjectName%.Shared.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Shared\bin\Debug\%TargetFramework%\%ProjectName%.Shared.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Server\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I

View File

@ -1,7 +1,12 @@
cp -f "../Client/bin/Debug/net9.0/[Owner].Module.[Module].Client.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/"
cp -f "../Client/bin/Debug/net9.0/[Owner].Module.[Module].Client.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/"
cp -f "../Server/bin/Debug/net9.0/[Owner].Module.[Module].Server.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/"
cp -f "../Server/bin/Debug/net9.0/[Owner].Module.[Module].Server.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/"
cp -f "../Shared/bin/Debug/net9.0/[Owner].Module.[Module].Shared.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/"
cp -f "../Shared/bin/Debug/net9.0/[Owner].Module.[Module].Shared.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/"
cp -rf "../Server/wwwroot/"* "../../oqtane.framework/Oqtane.Server/wwwroot/"
#!/bin/bash
TargetFramework=$1
ProjectName=$2
cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.dll" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.pdb" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Server/bin/Debug/$TargetFramework/$ProjectName$.Server.Oqtane.dll" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Server/bin/Debug/$TargetFramework/$ProjectName$.Server.Oqtane.pdb" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName$.Shared.Oqtane.dll" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName$.Shared.Oqtane.pdb" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -rf "../Server/wwwroot/"* "../../[RootFolder]/Oqtane.Server/wwwroot/"

View File

@ -1,4 +1,7 @@
del "*.nupkg"
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack [Owner].Module.[Module].nuspec
XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\" /Y
@echo off
set TargetFramework=%1
set ProjectName=%2
del "*.nupkg"
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\" /Y

View File

@ -1,2 +1,5 @@
"..\..\oqtane.framework\oqtane.package\nuget.exe" pack [Owner].Module.[Module].nuspec
cp -f "*.nupkg" "..\..\oqtane.framework\Oqtane.Server\Packages\"
TargetFramework=$1
ProjectName=$2
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
cp -f "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\"

View File

@ -18,10 +18,10 @@
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="debug.cmd" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="bash $(ProjectDir)debug.sh" />
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Release'" Command="release.cmd" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Release'" Command="bash $(ProjectDir)release.sh" />
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="debug.cmd $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="bash $(ProjectDir)debug.sh $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Release'" Command="release.cmd $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Release'" Command="bash $(ProjectDir)release.sh $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
</Target>
</Project>

View File

@ -1,28 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>[Owner].Theme.[Theme]</id>
<version>1.0.0</version>
<authors>[Owner]</authors>
<owners>[Owner]</owners>
<title>[Theme]</title>
<description>[Description]</description>
<copyright>[Owner]</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<icon>icon.png</icon>
<tags>oqtane theme</tags>
<releaseNotes></releaseNotes>
<summary></summary>
<dependencies>
<dependency id="Oqtane.Framework" version="[FrameworkVersion]" />
</dependencies>
</metadata>
<files>
<file src="..\Client\bin\Release\net9.0\[Owner].Theme.[Theme].Client.Oqtane.dll" target="lib\net9.0" />
<file src="..\Client\bin\Release\net9.0\[Owner].Theme.[Theme].Client.Oqtane.pdb" target="lib\net9.0" />
<file src="..\Client\wwwroot\**\*.*" target="wwwroot" />
<file src="icon.png" target="" />
</files>
</package>
<metadata>
<id>$projectname$</id>
<version>1.0.0</version>
<authors>[Owner]</authors>
<owners>[Owner]</owners>
<title>[Theme]</title>
<description>[Description]</description>
<copyright>[Owner]</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<icon>icon.png</icon>
<tags>oqtane theme</tags>
<releaseNotes></releaseNotes>
<summary></summary>
<dependencies>
<dependency id="Oqtane.Framework" version="[FrameworkVersion]" />
</dependencies>
</metadata>
<files>
<file src="..\Client\bin\Release\$targetframework$\$projectname$.Client.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Client\bin\Release\$targetframework$\$projectname$.Client.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Client\wwwroot\**\*.*" target="wwwroot" />
<file src="icon.png" target="" />
</files>
</package>

View File

@ -1,3 +1,7 @@
XCOPY "..\Client\bin\Debug\net9.0\[Owner].Theme.[Theme].Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y
XCOPY "..\Client\bin\Debug\net9.0\[Owner].Theme.[Theme].Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y
XCOPY "..\Client\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I
@echo off
set TargetFramework=%1
set ProjectName=%2
XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Client\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I

View File

@ -1,3 +1,8 @@
cp -f "../Client/bin/Debug/net9.0/[Owner].Theme.[Theme].Client.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/"
cp -f "../Client/bin/Debug/net9.0/[Owner].Theme.[Theme].Client.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/"
cp -rf "../Server/wwwroot/"* "../../oqtane.framework/Oqtane.Server/wwwroot/"
#!/bin/bash
TargetFramework=$1
ProjectName=$2
cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.dll" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.pdb" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -rf "../Server/wwwroot/"* "../../[RootFolder]/Oqtane.Server/wwwroot/"

View File

@ -1,3 +1,7 @@
@echo off
set TargetFramework=%1
set ProjectName=%2
del "*.nupkg"
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack [Owner].Theme.[Theme].nuspec
XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\wwwroot\Packages\" /Y
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\wwwroot\Packages\" /Y

View File

@ -1,2 +1,5 @@
"..\..\oqtane.framework\oqtane.package\nuget.exe" pack [Owner].Theme.[Theme].nuspec
cp -f "*.nupkg" "..\..\oqtane.framework\Oqtane.Server\Packages\"
TargetFramework=$1
ProjectName=$2
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
cp -f "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\"

View File

@ -120,13 +120,22 @@ Oqtane.Interop = {
this.includeLink(links[i].id, links[i].rel, links[i].href, links[i].type, links[i].integrity, links[i].crossorigin, links[i].insertbefore);
}
},
includeScript: function (id, src, integrity, crossorigin, type, content, location) {
includeScript: function (id, src, integrity, crossorigin, type, content, location, dataAttributes) {
var script;
if (src !== "") {
script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]");
}
else {
script = document.getElementById(id);
if (id !== "") {
script = document.getElementById(id);
} else {
const scripts = document.querySelectorAll("script:not([src])");
for (let i = 0; i < scripts.length; i++) {
if (scripts[i].textContent.includes(content)) {
script = scripts[i];
}
}
}
}
if (script !== null) {
script.remove();
@ -152,37 +161,36 @@ Oqtane.Interop = {
else {
script.innerHTML = content;
}
script.async = false;
this.addScript(script, location)
.then(() => {
if (src !== "") {
console.log(src + ' loaded');
}
else {
console.log(id + ' loaded');
}
})
.catch(() => {
if (src !== "") {
console.error(src + ' failed');
}
else {
console.error(id + ' failed');
}
});
if (dataAttributes !== null) {
for (var key in dataAttributes) {
script.setAttribute(key, dataAttributes[key]);
}
}
try {
this.addScript(script, location);
} catch (error) {
if (src !== "") {
console.error("Failed to load external script: ${src}", error);
} else {
console.error("Failed to load inline script: ${content}", error);
}
}
}
},
addScript: function (script, location) {
if (location === 'head') {
document.head.appendChild(script);
}
if (location === 'body') {
document.body.appendChild(script);
}
return new Promise((resolve, reject) => {
script.async = false;
script.defer = false;
return new Promise((res, rej) => {
script.onload = res();
script.onerror = rej();
script.onload = () => resolve();
script.onerror = (error) => reject(error);
if (location === 'head') {
document.head.appendChild(script);
} else {
document.body.appendChild(script);
}
});
},
includeScripts: async function (scripts) {
@ -222,10 +230,10 @@ Oqtane.Interop = {
if (scripts[s].crossorigin !== '') {
element.crossOrigin = scripts[s].crossorigin;
}
if (scripts[s].es6module === true) {
element.type = "module";
if (scripts[s].type !== '') {
element.type = scripts[s].type;
}
if (typeof scripts[s].dataAttributes !== "undefined" && scripts[s].dataAttributes !== null) {
if (scripts[s].dataAttributes !== null) {
for (var key in scripts[s].dataAttributes) {
element.setAttribute(key, scripts[s].dataAttributes[key]);
}

View File

@ -0,0 +1,80 @@
const scriptInfoBySrc = new Map();
function getKey(script) {
if (script.hasAttribute("src") && script.src !== "") {
return script.src;
} else {
return script.innerHTML;
}
}
export function onUpdate() {
let timestamp = Date.now();
let enhancedNavigation = scriptInfoBySrc.size !== 0;
// iterate over all script elements in page
const scripts = document.getElementsByTagName("script");
for (const script of Array.from(scripts)) {
let key = getKey(script);
let scriptInfo = scriptInfoBySrc.get(key);
if (!scriptInfo) {
// new script added
scriptInfo = { timestamp: timestamp };
scriptInfoBySrc.set(key, scriptInfo);
if (enhancedNavigation) {
reloadScript(script);
}
} else {
// existing script
scriptInfo.timestamp = timestamp;
if (script.hasAttribute("data-reload") && script.getAttribute("data-reload") === "true") {
reloadScript(script);
}
}
}
// remove scripts that are no longer referenced
for (const [key, scriptInfo] of scriptInfoBySrc) {
if (scriptInfo.timestamp !== timestamp) {
scriptInfoBySrc.delete(key);
}
}
}
function reloadScript(script) {
try {
replaceScript(script);
} catch (error) {
if (script.hasAttribute("src") && script.src !== "") {
console.error("Failed to load external script: ${script.src}", error);
} else {
console.error("Failed to load inline script: ${script.innerHtml}", error);
}
}
}
function replaceScript(script) {
return new Promise((resolve, reject) => {
var newScript = document.createElement("script");
// replicate attributes and content
for (let i = 0; i < script.attributes.length; i++) {
newScript.setAttribute(script.attributes[i].name, script.attributes[i].value);
}
newScript.innerHTML = script.innerHTML;
// dynamically injected scripts cannot be async or deferred
newScript.async = false;
newScript.defer = false;
newScript.onload = () => resolve();
newScript.onerror = (error) => reject(error);
// remove existing script
script.remove();
// replace with new script to force reload in Blazor
document.head.appendChild(newScript);
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -144,25 +144,25 @@ namespace Oqtane.Models
{
FromUserId = from.UserId;
FromDisplayName = from.DisplayName;
FromEmail = from.Email;
FromEmail = from.Email ?? "";
}
else
{
FromUserId = null;
FromDisplayName = fromDisplayName;
FromEmail = fromEmail;
FromEmail = fromEmail ?? "";
}
if (to != null)
{
ToUserId = to.UserId;
ToDisplayName = to.DisplayName;
ToEmail = to.Email;
ToEmail = to.Email ?? "";
}
else
{
ToUserId = null;
ToDisplayName = toDisplayName;
ToEmail = toEmail;
ToEmail = toEmail ?? "";
}
Subject = subject;
Body = body;

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Oqtane.Shared;
namespace Oqtane.Models
@ -27,6 +29,11 @@ namespace Oqtane.Models
}
}
/// <summary>
/// For Scripts this allows type to be specified - not applicable to Stylesheets
/// </summary>
public string Type { get; set; }
/// <summary>
/// Integrity checks to increase the security of resources accessed. Especially common in CDN resources.
/// </summary>
@ -52,11 +59,6 @@ namespace Oqtane.Models
/// </summary>
public ResourceLocation Location { get; set; }
/// <summary>
/// For Scripts this allows type="module" registrations - not applicable to Stylesheets
/// </summary>
public bool ES6Module { get; set; }
/// <summary>
/// Allows specification of inline script - not applicable to Stylesheets
/// </summary>
@ -72,6 +74,11 @@ namespace Oqtane.Models
/// </summary>
public bool Reload { get; set; }
/// <summary>
/// Cusotm data-* attributes for scripts - not applicable to Stylesheets
/// </summary>
public Dictionary<string, string> DataAttributes { get; set; }
/// <summary>
/// The namespace of the component that declared the resource - only used in SiteRouter
/// </summary>
@ -82,14 +89,22 @@ namespace Oqtane.Models
var resource = new Resource();
resource.ResourceType = ResourceType;
resource.Url = Url;
resource.Type = Type;
resource.Integrity = Integrity;
resource.CrossOrigin = CrossOrigin;
resource.Bundle = Bundle;
resource.Location = Location;
resource.ES6Module = ES6Module;
resource.Content = Content;
resource.RenderMode = RenderMode;
resource.Reload = Reload;
resource.DataAttributes = new Dictionary<string, string>();
if (DataAttributes != null && DataAttributes.Count > 0)
{
foreach (var kvp in DataAttributes)
{
resource.DataAttributes.Add(kvp.Key, kvp.Value);
}
}
resource.Level = level;
resource.Namespace = name;
return resource;
@ -97,5 +112,18 @@ namespace Oqtane.Models
[Obsolete("ResourceDeclaration is deprecated", false)]
public ResourceDeclaration Declaration { get; set; }
[Obsolete("ES6Module is deprecated. Use Type property instead for scripts.", false)]
public bool ES6Module
{
get => (Type == "module");
set
{
if (value)
{
Type = "module";
};
}
}
}
}

View File

@ -0,0 +1,53 @@
using System.Collections.Generic;
using Oqtane.Shared;
namespace Oqtane.Models
{
/// <summary>
/// Script inherits from Resource and offers constructors with parameters specific to Scripts
/// </summary>
public class Script : Resource
{
private void SetDefaults()
{
this.ResourceType = ResourceType.Script;
this.Location = ResourceLocation.Body;
}
public Script(string Src)
{
SetDefaults();
this.Url = Src;
}
public Script(string Content, string Type)
{
SetDefaults();
this.Content = Content;
this.Type = Type;
}
public Script(string Src, string Integrity, string CrossOrigin)
{
SetDefaults();
this.Url = Src;
this.Integrity = Integrity;
this.CrossOrigin = CrossOrigin;
}
public Script(string Src, string Integrity, string CrossOrigin, string Type, string Content, ResourceLocation Location, string Bundle, bool Reload, Dictionary<string, string> DataAttributes, string RenderMode)
{
SetDefaults();
this.Url = Src;
this.Integrity = Integrity;
this.CrossOrigin = CrossOrigin;
this.Type = Type;
this.Content = Content;
this.Location = Location;
this.Bundle = Bundle;
this.Reload = Reload;
this.DataAttributes = DataAttributes;
this.RenderMode = RenderMode;
}
}
}

View File

@ -0,0 +1,30 @@
using Oqtane.Shared;
namespace Oqtane.Models
{
/// <summary>
/// Stylesheet inherits from Resource and offers constructors with parameters specific to Stylesheets
/// </summary>
public class Stylesheet : Resource
{
private void SetDefaults()
{
this.ResourceType = ResourceType.Stylesheet;
this.Location = ResourceLocation.Head;
}
public Stylesheet(string Href)
{
SetDefaults();
this.Url = Href;
}
public Stylesheet(string Href, string Integrity, string CrossOrigin)
{
SetDefaults();
this.Url = Href;
this.Integrity = Integrity;
this.CrossOrigin = CrossOrigin;
}
}
}