Compare commits

...

143 Commits

Author SHA1 Message Date
e95ae8dbcf Merge pull request #4295 from oqtane/master
5.1.2 release
2024-05-28 15:12:02 -04:00
5fbd64da71 Merge pull request #4294 from oqtane/dev
5.1.2 release
2024-05-28 15:11:34 -04:00
b282a2a621 Merge pull request #4291 from sbwalker/dev
introduce Clone method in Permission model
2024-05-28 07:56:01 -04:00
9a7a534051 introduce Clone method in Permission model 2024-05-28 07:55:45 -04:00
52fd030b6e Merge pull request #4286 from sbwalker/dev
fix issues when importing SiteTemplates
2024-05-24 22:51:57 -04:00
dfe530a764 fix issues when importing SiteTemplates 2024-05-24 22:51:34 -04:00
b079956075 Merge pull request #4283 from sbwalker/dev
add ability to specify session duration for visitor tracking
2024-05-23 09:44:59 -04:00
e30037c4d1 add ability to specify session duration for visitor tracking 2024-05-23 09:44:42 -04:00
eda7be627c Merge pull request #4281 from sbwalker/dev
fix #4279 - remove Theme Settings tab from Add Page UI
2024-05-21 11:06:55 -04:00
af0a649656 fix #4279 - remove Theme Settings tab from Add Page UI 2024-05-21 11:06:42 -04:00
8b6a3c4236 Merge pull request #4278 from sbwalker/dev
changed terminology from Library to Headless
2024-05-20 22:12:14 -04:00
0988a92d8a changed terminology from Library to Headless 2024-05-20 22:12:01 -04:00
9cf67764b7 Merge pull request #4277 from sbwalker/dev
update theme and module templates to .NET SDK 8.0.5
2024-05-20 16:59:56 -04:00
6c4e1d1c41 update theme and module templates to .NET SDK 8.0.5 2024-05-20 16:59:44 -04:00
b1cd1ea8b3 Merge pull request #4276 from sbwalker/dev
upgrade to .NET 8.0.5
2024-05-20 16:54:24 -04:00
5169ed494c upgrade to .NET 8.0.5 2024-05-20 16:54:11 -04:00
824211c31b Merge pull request #4275 from sbwalker/dev
prepare for 5.1.2 release
2024-05-20 16:42:48 -04:00
be3dd83bc7 prepare for 5.1.2 release 2024-05-20 16:42:35 -04:00
c2911c1e48 Merge pull request #4274 from sbwalker/dev
script formatting
2024-05-20 16:36:30 -04:00
9a66c5c07d script formatting 2024-05-20 16:36:17 -04:00
34d393b986 Merge pull request #4273 from sbwalker/dev
optimize scripts
2024-05-20 16:29:28 -04:00
d4da02318d optimize scripts 2024-05-20 16:29:12 -04:00
47162af6d5 Merge pull request #4272 from sbwalker/dev
improve validation in package extraction
2024-05-20 09:34:23 -04:00
8cd6a72dd3 improve validation in package extraction 2024-05-20 09:33:46 -04:00
ba0a183b6f Merge pull request #4271 from sbwalker/dev
fix #4249 - allow EmailConfirmed property to be updated
2024-05-20 08:54:33 -04:00
73781c7edb fix #4249 - allow EmailConfirmed property to be updated 2024-05-20 08:54:19 -04:00
6d99852c81 Merge pull request #4269 from sbwalker/dev
refactor #4268 to support static render mode
2024-05-19 09:05:56 -04:00
9325c726fd refactor #4268 to support static render mode 2024-05-19 09:05:35 -04:00
947bb8530e Merge pull request #4268 from zyhfish/task/fix-issue-3885
Fix #3885: only re-render the component when message changed.
2024-05-19 08:48:36 -04:00
Ben
2b32f316ee Fix #3885: only re-render the component when message changed. 2024-05-18 21:55:37 +08:00
4b1f23a189 Merge pull request #4266 from sbwalker/dev
improve scroll position navigation behavior
2024-05-17 15:42:26 -04:00
71d220e7a4 improve scroll position navigation behavior 2024-05-17 15:42:13 -04:00
747d0d0d17 Merge pull request #4265 from sbwalker/dev
fix #4246 - module message form exception when clicking close button
2024-05-17 14:12:02 -04:00
5c72e6d335 fix #4246 - module message form exception when clicking close button 2024-05-17 14:11:48 -04:00
e1ac2b0e10 Merge pull request #4264 from sbwalker/dev
set browser scroll position on navigation in Static Rendering
2024-05-17 13:01:20 -04:00
0ba94f3bc9 set browser scroll position on navigation in Static Rendering 2024-05-17 13:01:03 -04:00
ba0bfafcd5 Merge pull request #4263 from sbwalker/dev
fix redirect logic when adding a new page
2024-05-17 08:51:42 -04:00
81adb80b7e fix redirect logic when adding a new page 2024-05-17 08:51:28 -04:00
cb238ef170 Merge pull request #4262 from sbwalker/dev
fix #4224 - reload page after adding module in Static Rendering
2024-05-17 08:38:33 -04:00
b9b921de82 fix #4224 - reload page after adding module in Static Rendering 2024-05-17 08:38:22 -04:00
d10e31c278 Merge pull request #4261 from sbwalker/dev
fix #4232 - Html/Text module not initializing content
2024-05-16 15:39:30 -04:00
fd641d77c7 fix #4232 - Html/Text module not initializing content 2024-05-16 15:39:17 -04:00
2aa9710dd1 Merge pull request #4260 from leigh-pointer/Bug4259
Fix for #4259 Localizer Null in Module Settings
2024-05-16 15:14:28 -04:00
4afb2ef2b8 Fix for #4259 Localizer Null
removed the override string Title as it will be set Localized in the OnInitialized using SetModuleTitle base method.
2024-05-16 10:22:37 +02:00
a54e6e7c4b Merge pull request #4253 from zyhfish/task/fix-issue-4252
Fix #4252: do not reset the user photo setting when edit the user.
2024-05-13 08:51:33 -04:00
7af26a356f Merge pull request #4255 from zyhfish/task/fix-issue-4254
Fix #4254: remove the redundant space.
2024-05-13 08:49:22 -04:00
2f66165f8c Merge pull request #4256 from sbwalker/dev
add defensive logic to route parsing
2024-05-13 08:45:15 -04:00
e86ce8fc38 add defensive logic to route parsing 2024-05-13 08:45:03 -04:00
Ben
9b48c65129 Fix #4254: remove the redundant space. 2024-05-13 16:35:23 +08:00
Ben
434cd133df Fix #4252: do not reset the user photo setting when edit the user. 2024-05-13 16:08:43 +08:00
aa91e4cdee Merge pull request #4250 from sbwalker/dev
revert prerender changes and change default
2024-05-10 16:28:32 -04:00
d57c1e7ff0 revert prerender changes and change default 2024-05-10 16:28:19 -04:00
13e97703e5 Merge pull request #4244 from sbwalker/dev
modify prerendering UI options
2024-05-09 15:09:07 -04:00
c597b293b8 modify prerendering UI options 2024-05-09 15:08:52 -04:00
6620d64ce7 Merge pull request #4243 from sbwalker/dev
add support for Auto Prerendering
2024-05-09 14:43:07 -04:00
2ae120c878 add support for Auto Prerendering 2024-05-09 14:42:54 -04:00
7a25035fb1 Merge pull request #4239 from sbwalker/dev
require AntiForgery on Static Rendered components
2024-05-08 14:42:59 -04:00
bf4052b550 require AntiForgery on Static Rendered components 2024-05-08 14:42:39 -04:00
5ca5ad2cee Merge pull request #4226 from ohba-ikuo/oqtane-#4223
Fix #4223 In the Ubuntu environment, an error occurs when trying to upload a file.
2024-05-07 13:49:25 -04:00
2848f1e13c Merge pull request #4237 from sbwalker/dev
fix #4235 - add space above Logout button in Control Panel
2024-05-07 13:49:14 -04:00
f7895823cb fix #4235 - add space above Logout button in Control Panel 2024-05-07 13:48:58 -04:00
b841c5c5e5 Merge pull request #4234 from sbwalker/dev
add shadow-none to page links in pager
2024-05-06 15:56:20 -04:00
a7952a4633 add shadow-none to page links in pager 2024-05-06 15:56:05 -04:00
d047d26dbf Reverted and fixed the source code. 2024-05-05 12:12:40 +09:00
ddedc1640f Merge pull request #4227 from sbwalker/dev
fix #4221 - exception in Module Management when a module has been uninstalled (credit @marceloatoledo)
2024-05-03 13:37:29 -04:00
021d7e5efc fix #4221 - exception in Module Management when a module has been uninstalled (credit @marceloatoledo) 2024-05-03 13:37:10 -04:00
332e528012 Fix #4223 2024-05-03 21:18:28 +09:00
a0155da06b Merge pull request #4219 from mdmontesinos/dev
[ENH] Support for IconOnly in ActionDialog open button
2024-05-02 07:45:06 -04:00
d58d22adbe Merge pull request #4218 from leigh-pointer/CheckNullString
Null or empty check for FormatContent
2024-05-02 07:43:39 -04:00
653352bff0 Support for IconOnly in ActionDialog open button 2024-05-02 10:36:29 +02:00
4f5b33d8df Null or empty check for FormatContent
Added null or empty check for the content and alias parameters at the beginning of the method.
2024-05-02 09:03:26 +02:00
7e7d83ac36 Merge pull request #4217 from sbwalker/dev
fix support for Site-level Scripts in Resources
2024-05-01 15:18:51 -04:00
0de5c043bb fix support for Site-level Scripts in Resources 2024-05-01 15:18:36 -04:00
ec2769ea3c Merge pull request #4215 from sbwalker/dev
fix RESX file (add missing element)
2024-05-01 11:52:22 -04:00
3f742f5f8e fix RESX file (add missing element) 2024-05-01 11:52:07 -04:00
50849101d4 Merge pull request #4208 from iJungleboy/patch-1
Update README.md, move history to docs
2024-05-01 11:49:23 -04:00
1ccf4a74c9 Merge pull request #4214 from leigh-pointer/TranslateModuleSettingsTitle
ModuleSettings Title Localized
2024-05-01 11:49:08 -04:00
7cd4967963 Merge pull request #4205 from leigh-pointer/MissingParams
Fix for missing parameters and Resx values Issue #4202 #4203 #4210 and part #4209
2024-05-01 11:47:11 -04:00
21e2700da5 ModuleSettings Title Localized 2024-05-01 08:33:51 +02:00
395a68ad80 Merge branch 'dev' into MissingParams 2024-05-01 08:20:29 +02:00
b7f0132675 Merge pull request #4213 from sbwalker/dev
fix #4206 - validate folder name for duplicates
2024-04-30 16:41:37 -04:00
4ac827b9e8 fix #4206 - validate folder name for duplicates 2024-04-30 16:41:24 -04:00
d96963862e Merge pull request #4212 from leigh-pointer/ModuleContainerSettingsTrans
ModuleSettings ContainerSettings #4211
2024-04-30 16:19:28 -04:00
53217b061d ModuleSettings ContainerSettings #4211
ModuleSettings ContainerSettings #4211
2024-04-30 20:07:34 +02:00
4770daa7c6 Fix for Issue #4210 and part #4209
This I will work on on a  different Issue
2024-04-30 19:46:10 +02:00
2b709ad094 Update README.md, move history to docs
moved release history and old announcements to the docs
2024-04-30 13:14:58 +02:00
378b81b13b Fix for missing parameters and Resx values
Issue #4202 Issue #4203
2024-04-30 08:17:53 +02:00
56ee72214f Merge pull request #4204 from sbwalker/dev
refactor #4198 - copy existing module
2024-04-29 15:01:12 -04:00
2e7c3167f5 refactor #4198 - copy existing module 2024-04-29 14:58:30 -04:00
a2fb728d3b Merge pull request #4198 from zyhfish/task/fix-issue-4030
Fix #4030: add copy module option for add existing module function.
2024-04-29 13:38:34 -04:00
Ben
be1c936e90 Fix #4030: move copy option to the add module dropdown. 2024-04-29 22:04:01 +08:00
af8037ab03 Merge pull request #4201 from sbwalker/dev
allow hidden pages to be included in SiteMap
2024-04-29 08:58:38 -04:00
3b8dc98226 allow hidden pages to be included in SiteMap 2024-04-29 08:58:20 -04:00
Ben
b411b4e61b display error message for different action. 2024-04-27 19:32:07 +08:00
Ben
436eb30490 Fix #4030: add copy module option for add existing module function. 2024-04-27 12:32:31 +08:00
09b8087787 Merge pull request #4194 from ijaz-saeed/dev
Format Exception in int.Parse(route.ModuleId)
2024-04-26 13:26:02 -04:00
4ac4c69820 Merge pull request #4195 from zyhfish/task/fix-language-switch-redirect-issue
avoid redirect to home page when switching language.
2024-04-26 13:25:48 -04:00
3ebc5c0865 Merge pull request #4197 from sbwalker/dev
add support for Library modules and optimize usage of reflection during startup
2024-04-26 13:23:17 -04:00
7b94f8f105 add support for Library modules and optimize usage of reflection during startup 2024-04-26 13:22:56 -04:00
Ben
ec994b3e97 avoid redirect to home page when switching language. 2024-04-25 23:18:17 +08:00
86ae0182fd Format Exception in int.Parse(route.ModuleId)
int.Parse("-1") throws  FormatException for cultures other than
 English (en-US)
2024-04-25 19:30:24 +05:00
bfa891f0ca Merge pull request #4193 from leigh-pointer/LangButtStyle
LanguageSwitcher to use the ButtonClass parameter
2024-04-25 08:22:19 -04:00
ef843cac63 LanguageSwitcher to use the ButtonClass parameter
This change allows the buttons to be uniform.
2024-04-25 12:33:52 +02:00
78d68d0a4f Merge pull request #4190 from sbwalker/dev
fix #4186 - enable language switcher in static render mode
2024-04-24 15:55:16 -04:00
4bceba777d fix #4186 - enable language switcher in static render mode 2024-04-24 15:55:00 -04:00
2e537b1e5e Merge pull request #4189 from sbwalker/dev
fix HTML comment to indicate actual RenderMode for component (including Interactivity)
2024-04-24 13:23:54 -04:00
df8463b625 fix HTML comment to indicate actual RenderMode for component (including Interactivity) 2024-04-24 13:22:28 -04:00
f40371e0cf Update README.md 2024-04-24 10:04:10 -04:00
a565e7aed6 Merge pull request #4188 from sbwalker/dev
minor refactoring of #4179
2024-04-24 09:49:08 -04:00
c948361090 minor refactoring of #4179 2024-04-24 09:48:51 -04:00
4cf2b74a01 Merge pull request #4179 from LearnOqtane/dev
[ENH] - Add Prerender IModuleControl property (similar to RenderMode)…
2024-04-24 09:32:11 -04:00
de9c8362ac [ENH] - #4178 simplified version with null-coalescing operator ?? 2024-04-24 10:13:46 +10:00
b5bb5d35e7 [ENH] - #4178 correcting logic error 2024-04-24 09:49:07 +10:00
d910cfa919 [ENH] - #4178 modifications after review 2024-04-24 09:46:07 +10:00
5857e3d5c6 Merge branch 'oqtane:dev' into dev 2024-04-24 06:36:47 +10:00
25daa343c6 Merge pull request #4184 from leigh-pointer/ParamsLang
Parameters Missing fix for #4180 #4182
2024-04-23 14:10:43 -04:00
85224c8f0c Merge pull request #4185 from sbwalker/dev
fix #4160 - content entered being overidden by original content
2024-04-23 14:05:02 -04:00
5f8583e3eb fix #4160 - content entered being overidden by original content 2024-04-23 14:04:52 -04:00
062821d267 Parameters Missing fix for #4180 #4182 2024-04-23 19:45:13 +02:00
1fdaaf82d2 Merge pull request #4169 from leigh-pointer/Bug4168
Fix for #4168 #4170 missing Translations
2024-04-23 13:19:24 -04:00
e4c1b17810 Merge pull request #4183 from sbwalker/dev
fix path issue for root page
2024-04-23 13:15:55 -04:00
70057542c1 fix path issue for root page 2024-04-23 13:15:44 -04:00
f2255ee707 Merge pull request #4181 from sbwalker/dev
replace form with link in AdminContainer
2024-04-23 12:54:56 -04:00
791cc70b09 replace form with link in AdminContainer 2024-04-23 12:54:44 -04:00
24dcb9973b Merge pull request #4164 from ohba-ikuo/add-ohba-ikuo
Datetime formatting issue
2024-04-23 08:45:58 -04:00
adfd0d5c18 Fix MicroService And Controller 2024-04-23 21:27:13 +09:00
cfb128acb8 [ENH] - Add Prerender IModuleControl property (similar to RenderMode) #4178 2024-04-23 15:22:02 +10:00
1e8e246ffb Merge pull request #4177 from sbwalker/dev
fix #4165 - missing slash in subfolder sites
2024-04-22 17:09:31 -04:00
6162244730 fix #4165 - missing slash in subfolder sites 2024-04-22 17:09:18 -04:00
86cbdf2442 Merge pull request #4161 from zyhfish/task/fix-issue-4158
Fix #4158: insert image into correct position.
2024-04-22 16:28:47 -04:00
5334626efb Merge pull request #4167 from leigh-pointer/ExtraTDinTheme
Rogue TD in table
2024-04-22 16:16:30 -04:00
708d473b47 Missing Parameter
#4176
2024-04-22 20:19:33 +02:00
ead954ddaa Missing Parameters
#4174 #4175
2024-04-22 19:18:02 +02:00
294f511b9a Missing parameters
#4172 #4173
2024-04-22 18:27:01 +02:00
b5ebcc3e07 Update Index.razor 2024-04-22 17:46:13 +02:00
7b8e7ac5c2 Fix for #4168
Resx entry for Module Settings Permissions tab
2024-04-22 17:14:09 +02:00
904d39beac Rouge TD in table
Deleted a rouge TD in the themes index table
2024-04-22 14:08:09 +02:00
8958b61fdd Datetime formatting issue 2024-04-21 20:44:41 +09:00
Ben
09293f7d9a Fix #4158: insert image into correct position. 2024-04-20 16:56:32 +08:00
d520c3d674 Merge pull request #4157 from sbwalker/dev
fix #4150 - remove Add Existing Module option when managing personalized pages
2024-04-18 18:43:43 -04:00
430e616328 fix #4150 - remove Add Existing Module option when managing personalized pages 2024-04-18 18:43:14 -04:00
976ad5fcee Update README.md 2024-04-16 13:47:24 -04:00
4d58ee2162 Update README.md 2024-04-16 13:46:55 -04:00
83 changed files with 911 additions and 703 deletions

View File

@ -170,6 +170,7 @@
try
{
Folder folder;
if (_folderId != -1)
{
folder = await FolderService.GetFolderAsync(_folderId);
@ -179,8 +180,6 @@
folder = new Folder();
}
folder.SiteId = PageState.Site.SiteId;
if (_parentId == -1)
{
folder.ParentId = null;
@ -190,6 +189,14 @@
folder.ParentId = _parentId;
}
// check for duplicate folder names
if (_folders.Any(item => item.ParentId == folder.ParentId && item.Name == _name && item.FolderId != _folderId))
{
AddModuleMessage(Localizer["Message.Folder.Duplicate"], MessageType.Warning);
return;
}
folder.SiteId = PageState.Site.SiteId;
folder.Name = _name;
folder.Type = _type;
folder.ImageSizes = _imagesizes;

View File

@ -26,8 +26,8 @@ else
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td>
<td><ActionLink Action="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td>
<td><ActionLink Action="Log" Text="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
<td>@context.Name</td>
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>

View File

@ -63,7 +63,7 @@ else
<th>@Localizer["Function"]</th>
</Header>
<Row>
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Text="Details" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
<td class="@GetClass(context.Function)">@context.LogDate</td>
<td class="@GetClass(context.Function)">@context.Level</td>
<td class="@GetClass(context.Function)">@context.Feature</td>

View File

@ -32,7 +32,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
<div class="col-sm-9">
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" required />
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" />
</div>
</div>
<div class="row mb-1 align-items-center">
@ -306,10 +306,9 @@
_languages = _languages.OrderBy(item => item.Name).ToList();
}
// Group modules by PageId
// Get distinct PageIds where modules are present
// get distinct pages where module exists
var distinctPageIds = PageState.Modules
.Where(md => md.ModuleDefinition.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false)
.Where(md => md.ModuleDefinition?.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false)
.Select(md => md.PageId)
.Distinct();

View File

@ -50,7 +50,7 @@ else
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
<td>
@if (context.AssemblyName != Constants.ClientId)
{

View File

@ -94,7 +94,7 @@
</div>
}
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<TabPanel Name="Permissions" Heading="Permissions" ResourceKey="Permissions">
@if (_permissions != null)
{
<div class="container">
@ -128,7 +128,6 @@
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings";
private ElementReference form;
private bool validated = false;
@ -144,7 +143,7 @@
private PermissionGrid _permissionGrid;
private Type _moduleSettingsType;
private object _moduleSettings;
private string _moduleSettingsTitle = "Module Settings";
private string _moduleSettingsTitle;
private RenderFragment ModuleSettingsComponent { get; set; }
private Type _containerSettingsType;
private object _containerSettings;
@ -158,8 +157,10 @@
protected override void OnInitialized()
{
SetModuleTitle(Localizer["ModuleSettings.Title"]);
_module = ModuleState.ModuleDefinition.Name;
_title = ModuleState.Title;
_moduleSettingsTitle = Localizer["ModuleSettings.Heading"];
_pane = ModuleState.Pane;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType;
@ -173,6 +174,7 @@
_effectivedate = Utilities.UtcAsLocalDate(ModuleState.EffectiveDate);
_expirydate = Utilities.UtcAsLocalDate(ModuleState.ExpiryDate);
if (ModuleState.ModuleDefinition != null)
{
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;

View File

@ -198,12 +198,6 @@
</div>
</div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings">
@_themeSettingsComponent
</TabPanel>
}
</TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
@ -238,9 +232,6 @@
private string _bodycontent;
private string _permissions = null;
private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment _themeSettingsComponent { get; set; }
private bool _refresh = false;
protected Page _parent = null;
protected Dictionary<string, string> _icons;
@ -281,7 +272,6 @@
}
_effectivedate = Utilities.UtcAsLocalDate(PageState.Page.EffectiveDate);
_expirydate = Utilities.UtcAsLocalDate(PageState.Page.ExpiryDate);
ThemeSettings();
_initialized = true;
}
else
@ -324,7 +314,6 @@
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = _containers.First().TypeName;
ThemeSettings();
StateHasChanged();
// if theme chosen is different than default site theme, display warning message to user
@ -334,28 +323,6 @@
}
}
private void ThemeSettings()
{
_themeSettingsType = null;
_themeSettingsComponent = null;
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
if (_themeSettingsType != null)
{
_themeSettingsComponent = builder =>
{
builder.OpenComponent(0, _themeSettingsType);
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
builder.AddComponentReferenceCapture(2, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
builder.CloseComponent();
};
}
_refresh = true;
}
}
private async Task SavePage()
{
validated = true;
@ -482,11 +449,11 @@
await logger.LogInformation("Page Added {Page}", page);
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(PageState.ReturnUrl, true);
NavigationManager.NavigateTo(page.Path, true); // redirect to page added and reload
}
else
{
NavigationManager.NavigateTo(page.Path); // redirect to new page created
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
}
}
else

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Admin.Pages
@using Oqtane.Interfaces
@using System.Globalization
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IPageService PageService
@ -362,7 +363,7 @@
_parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
}
_children = new List<Page>();
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid, CultureInfo.InvariantCulture))))
{
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
@ -643,11 +644,11 @@
await logger.LogInformation("Page Saved {Page}", _page);
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(PageState.ReturnUrl, true);
NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
}
else
{
NavigationManager.NavigateTo(NavigateUrl(), true); // redirect to page being edited
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
}
}
else

View File

@ -17,7 +17,7 @@
<th>@SharedLocalizer["Name"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
<td>@(new string('-', context.Level * 2))@(context.Name)</td>

View File

@ -22,7 +22,7 @@ else
<th>@Localizer["Order"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditProfile" /></td>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditProfile" /></td>
<td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
<td>@context.Name</td>
<td>@context.Title</td>

View File

@ -20,9 +20,9 @@ else
<th>@SharedLocalizer["Name"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td>
<td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td>
<td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Users" /></td>
<td><ActionLink Action="Users" Text="Users" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Users" /></td>
<td>@context.Name</td>
</Row>
</Pager>

View File

@ -319,7 +319,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label>
<div class="col-sm-9">
<select id="rendermode" class="form-select" @bind="@_rendermode" required>
<select id="rendermode" class="form-select" value="@_rendermode" @onchange="(e => RenderModeChanged(e))" required>
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
@ -337,7 +337,7 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="prerender" HelpText="Specifies if interactive components should prerender their output" ResourceKey="Prerender">Prerender? </Label>
<Label Class="col-sm-3" For="prerender" HelpText="Specifies if interactive components should prerender their output on the server" ResourceKey="Prerender">Prerender: </Label>
<div class="col-sm-9">
<select id="prerender" class="form-select" @bind="@_prerender" required>
<option value="True">@SharedLocalizer["Yes"]</option>
@ -572,6 +572,23 @@
}
}
private void RenderModeChanged(ChangeEventArgs e)
{
_rendermode = (string)e.Value;
switch (_rendermode)
{
case RenderModes.Interactive:
_prerender = "True";
break;
case RenderModes.Static:
_prerender = "False";
break;
case RenderModes.Headless:
_prerender = "False";
break;
}
}
private async Task SaveSite()
{
validated = true;

View File

@ -29,7 +29,7 @@ else
<th>&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditTheme" /></td>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditTheme" /></td>
<td>
@if (context.AssemblyName != Constants.ClientId)
{
@ -63,7 +63,6 @@ else
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
}
</td>
<td></td>
</Row>
</Pager>
}

View File

@ -37,7 +37,7 @@ else
<th>@Localizer["Requested"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
<td><ActionDialog Header="Delete Url Mapping" Message="@string.Format(Localizer["Confirm.DeleteUrlMapping"], context.Url)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUrlMapping(context))" ResourceKey="DeleteUrlMapping" /></td>
<td>
<a href="@Utilities.TenantUrl(PageState.Alias, context.Url)">@context.Url</a>

View File

@ -226,11 +226,6 @@
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
if (user.PhotoFileId == -1)
{
user.PhotoFileId = null;
}
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));

View File

@ -32,13 +32,13 @@ else
</Header>
<Row>
<td>
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
<ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
</td>
<td>
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" />
</td>
<td>
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
<ActionLink Action="Roles" Text="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
</td>
<td>@context.User.Username</td>
<td>@context.User.DisplayName</td>

View File

@ -43,7 +43,7 @@ else
<th>@Localizer["Created"]</th>
</Header>
<Row>
<td><ActionLink Action="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
<td><ActionLink Action="Detail" Text="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
<td>@context.IPAddress</td>
<td>
@if (context.UserId != null)
@ -69,6 +69,12 @@ else
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="duration" HelpText="The duration of a browsing session considered to be a distinct visit (in minutes)" ResourceKey="Duration">Session Duration: </Label>
<div class="col-sm-9">
<input id="duration" class="form-control" type="number" min="0" step="1" @bind="@_duration" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="filter" HelpText="Comma delimited list of terms which may exist in IP addresses, user agents, or languages identifying visitors which should not be tracked" ResourceKey="Filter">Filter: </Label>
<div class="col-sm-9">
@ -76,7 +82,7 @@ else
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention (Days): </Label>
<Label Class="col-sm-3" For="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention: </Label>
<div class="col-sm-9">
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
</div>
@ -103,6 +109,7 @@ else
private int _page = 1;
private List<Visitor> _visitors;
private string _tracking;
private int _duration = 5;
private string _filter = "";
private int _retention = 30;
private string _correlation = "true";
@ -128,6 +135,7 @@ else
_tracking = PageState.Site.VisitorTracking.ToString();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_duration = int.Parse(SettingService.GetSetting(settings, "VisitorDuration", "5"));
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
_retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30"));
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
@ -179,6 +187,7 @@ else
await SiteService.UpdateSiteAsync(site);
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "VisitorDuration", _duration.ToString(), true);
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention.ToString(), true);
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);

View File

@ -35,11 +35,11 @@
{
if (Disabled)
{
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
}
else
{
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button>
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
}
}
}
@ -83,13 +83,13 @@ else
{
if (Disabled)
{
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
}
else
{
<form method="post" @formname="@($"ActionDialogActionForm{Id}")" @onsubmit="DisplayModal" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button>
<button type="submit" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
</form>
}
}
@ -101,6 +101,8 @@ else
private bool _editmode = false;
private bool _authorized = false;
private string _iconSpan = string.Empty;
private string _openIconSpan = string.Empty;
private string _openText = string.Empty;
[Parameter]
public string Header { get; set; } // required
@ -138,6 +140,9 @@ else
[Parameter]
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
[Parameter]
public bool IconOnly { get; set; } // optional - specifies only icon in opening link
[Parameter]
public string Id { get; set; } // optional - specifies a unique id for the compoment - required when there are multiple component instances on a page in static rendering
@ -157,6 +162,8 @@ else
{
Text = Action;
}
_openText = Text;
if (string.IsNullOrEmpty(Class))
{
Class = "btn btn-success";
@ -169,11 +176,17 @@ else
if (!string.IsNullOrEmpty(IconName))
{
if (IconOnly)
{
_openText = string.Empty;
}
if (!IconName.Contains(" "))
{
IconName = "oi oi-" + IconName;
}
_iconSpan = $"<span class=\"{IconName}\"></span>&nbsp;";
_openIconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : "&nbsp")}";
_iconSpan = $"<span class=\"{IconName}\"></span>&nbsp";
}
Text = Localize(nameof(Text), Text);

View File

@ -6,23 +6,24 @@
{
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
@((MarkupString)Message)
@if (PageState != null)
{
@if (Type == MessageType.Error && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
}
<form method="post" @onsubmit="DismissModal" @formname="@_formname" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn-close" aria-label="Close"></button>
</form>
@if (ModuleState.RenderMode == RenderModes.Static)
{
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
}
else
{
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
}
</div>
}
@code {
private string _message = string.Empty;
private string _classname = string.Empty;
private string _formname = "ModuleMessageForm";
[Parameter]
public string Message { get; set; }
@ -30,32 +31,13 @@
[Parameter]
public MessageType Type { get; set; }
public void RefreshMessage(string message, MessageType type)
{
Message = message;
Type = type;
UpdateClassName();
StateHasChanged();
}
protected override void OnInitialized()
{
if (ModuleState != null)
{
_formname += ModuleState.PageModuleId.ToString();
}
}
[Parameter]
public RenderModeBoundary Parent { get; set; }
protected override void OnParametersSet()
{
UpdateClassName();
}
private void UpdateClassName()
{
if (!string.IsNullOrEmpty(Message))
_message = Message;
if (!string.IsNullOrEmpty(_message))
{
_classname = GetMessageType(Type);
}
@ -82,9 +64,15 @@
return classname;
}
private void DismissModal()
private void CloseMessage(MouseEventArgs e)
{
Message = "";
if(Parent != null)
{
Parent.DismissMessage();
}
else
{
NavigationManager.NavigateTo(NavigationManager.Uri);
}
}
}

View File

@ -23,16 +23,16 @@
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
@ -40,30 +40,30 @@
if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
@ -86,16 +86,16 @@
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
@ -103,30 +103,30 @@
if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
@ -202,16 +202,16 @@
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
@ -219,30 +219,30 @@
if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
@ -250,16 +250,16 @@
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
@ -267,30 +267,30 @@
if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}

View File

@ -122,6 +122,7 @@
private string _message = string.Empty;
private bool _contentchanged = false;
private int _editorIndex;
[Parameter]
public string Content { get; set; }
@ -173,7 +174,11 @@
_rawhtml = Content;
_originalrawhtml = _rawhtml; // preserve for comparison later
_originalrichhtml = "";
if (Content != _originalrawhtml)
{
_contentchanged = true; // identifies when Content parameter has changed
}
if (!AllowRichText)
{
@ -286,7 +291,7 @@
var file = _fileManager.GetFile();
if (file != null)
{
await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name));
await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name), _editorIndex);
_richhtml = await interop.GetHtml(_editorElement);
_richfilemanager = false;
}
@ -297,6 +302,7 @@
}
else
{
_editorIndex = await interop.GetCurrentCursor(_editorElement);
_richfilemanager = true;
}
StateHasChanged();

View File

@ -105,13 +105,25 @@ namespace Oqtane.Modules.Controls
}
}
public Task InsertImage(ElementReference quillElement, string imageUrl, string altText)
public ValueTask<int> GetCurrentCursor(ElementReference quillElement)
{
try
{
return _jsRuntime.InvokeAsync<int>("Oqtane.RichTextEditor.getCurrentCursor", quillElement);
}
catch
{
return new ValueTask<int>(Task.FromResult(0));
}
}
public Task InsertImage(ElementReference quillElement, string imageUrl, string altText, int editorIndex)
{
try
{
_jsRuntime.InvokeAsync<object>(
"Oqtane.RichTextEditor.insertQuillImage",
quillElement, imageUrl, altText);
quillElement, imageUrl, altText, editorIndex);
return Task.CompletedTask;
}
catch

View File

@ -37,6 +37,10 @@
content = htmltext.Content;
content = Utilities.FormatContent(content, PageState.Alias, "render");
}
else
{
content = "";
}
}
}
catch (Exception ex)

View File

@ -52,6 +52,8 @@ namespace Oqtane.Modules
public virtual string RenderMode { get { return RenderModes.Interactive; } } // interactive by default
public virtual bool? Prerender { get { return null; } } // allows the Site Prerender property to be overridden
// url parameters
public virtual string UrlParametersTemplate { get; set; }
@ -276,7 +278,6 @@ namespace Oqtane.Modules
public void AddModuleMessage(string message, MessageType type, string position)
{
ClearModuleMessage();
RenderModeBoundary.AddModuleMessage(message, type, position);
}

View File

@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Configurations>Debug;Release</Configurations>
<Version>5.1.1</Version>
<Version>5.1.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -12,7 +12,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -22,9 +22,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.5" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
</ItemGroup>

View File

@ -195,4 +195,7 @@
<data name="Folder Management" xml:space="preserve">
<value>Folder Management</value>
</data>
<data name="Message.Folder.Duplicate" xml:space="preserve">
<value>Folder Name Specified Already Exists In Parent</value>
</data>
</root>

View File

@ -156,7 +156,7 @@
<data name="Module.Text" xml:space="preserve">
<value>Module:</value>
</data>
<data name="Module Settings" xml:space="preserve">
<data name="ModuleSettings.Heading" xml:space="preserve">
<value>Module Settings</value>
</data>
<data name="Pane.HelpText" xml:space="preserve">
@ -177,4 +177,16 @@
<data name="ExpiryDate.Text" xml:space="preserve">
<value>Expiry Date: </value>
</data>
<data name="Permissions.Text" xml:space="preserve">
<value>Permissions</value>
</data>
<data name="Permissions.Heading" xml:space="preserve">
<value>Permissions</value>
</data>
<data name="ContainerSettings.Heading" xml:space="preserve">
<value>Container Settings</value>
</data>
<data name="ModuleSettings.Title" xml:space="preserve">
<value>Module Settings</value>
</data>
</root>

View File

@ -147,4 +147,7 @@
<data name="Title" xml:space="preserve">
<value>Title</value>
</data>
<data name="Detail.Text" xml:space="preserve">
<value>Detail</value>
</data>
</root>

View File

@ -277,10 +277,10 @@
<value>UI Component Settings</value>
</data>
<data name="Prerender.HelpText" xml:space="preserve">
<value>Specifies if interactive components should prerender their output</value>
<value>Specifies if interactive components should prerender their output on the server</value>
</data>
<data name="Prerender.Text" xml:space="preserve">
<value>Prerender? </value>
<value>Prerender: </value>
</data>
<data name="RenderMode.HelpText" xml:space="preserve">
<value>The default render mode for the site</value>

View File

@ -159,4 +159,7 @@
<data name="Url" xml:space="preserve">
<value>Url</value>
</data>
<data name="Edit.Text" xml:space="preserve">
<value>Edit</value>
</data>
</root>

View File

@ -184,7 +184,7 @@
<value>Number of days of visitor activity to retain</value>
</data>
<data name="Retention.Text" xml:space="preserve">
<value>Retention (Days):</value>
<value>Retention:</value>
</data>
<data name="Correlation.HelpText" xml:space="preserve">
<value>Indicate if new visitors to this site should be correlated based on their IP Address</value>
@ -192,4 +192,10 @@
<data name="Correlation.Text" xml:space="preserve">
<value>Correlate Visitors?</value>
</data>
<data name="Duration.HelpText" xml:space="preserve">
<value>The duration of a browsing session considered to be a distinct visit (in minutes)</value>
</data>
<data name="Duration.Text" xml:space="preserve">
<value>Session Duration:</value>
</data>
</root>

View File

@ -453,4 +453,10 @@
<data name="RenderModeStatic" xml:space="preserve">
<value>Static</value>
</data>
<data name="Disabled" xml:space="preserve">
<value>Disabled</value>
</data>
<data name="Enabled" xml:space="preserve">
<value>Enabled</value>
</data>
</root>

View File

@ -198,4 +198,7 @@
<data name="LocationTop" xml:space="preserve">
<value>Top</value>
</data>
<data name="Module.CopyExisting" xml:space="preserve">
<value>Copy Existing Module</value>
</data>
</root>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="DeleteModule" xml:space="preserve">
<value>Delete Module</value>
</data>
<data name="ExportContent" xml:space="preserve">
<value>Export Content</value>
</data>
<data name="ImportContent" xml:space="preserve">
<value>Import Content</value>
</data>
<data name="ManageSettings" xml:space="preserve">
<value>Manage Settings</value>
</data>
<data name="MoveDown" xml:space="preserve">
<value>Move Down</value>
</data>
<data name="MoveToBottom" xml:space="preserve">
<value>Move To Bottom</value>
</data>
<data name="MoveToTop" xml:space="preserve">
<value>Move To Top</value>
</data>
<data name="MoveUp" xml:space="preserve">
<value>MoveUp</value>
</data>
<data name="PublishModule" xml:space="preserve">
<value>Publish Module</value>
</data>
<data name="UnpublishModule" xml:space="preserve">
<value>Unpublish Module</value>
</data>
</root>

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using Oqtane.Documentation;
using Oqtane.Shared;
using System;
using System.Globalization;
namespace Oqtane.Services
{
@ -18,7 +19,7 @@ namespace Oqtane.Services
public async Task<List<Visitor>> GetVisitorsAsync(int siteId, DateTime fromDate)
{
List<Visitor> visitors = await GetJsonAsync<List<Visitor>>($"{Apiurl}?siteid={siteId}&fromdate={fromDate.ToString("dd-MMM-yyyy")}");
List<Visitor> visitors = await GetJsonAsync<List<Visitor>>($"{Apiurl}?siteid={siteId}&fromdate={fromDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}");
return visitors.OrderByDescending(item => item.VisitedOn).ToList();
}

View File

@ -1,6 +1,5 @@
@namespace Oqtane.Themes
@inherits ContainerBase
@inject NavigationManager NavigationManager
<div class="app-admin-modal">
<div class="modal" tabindex="-1" role="dialog">
@ -8,10 +7,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><ModuleTitle /></h5>
<form method="post" class="app-form-inline" @formname="AdminContainerForm" @onsubmit="@CloseModal" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn-close" aria-label="Close"></button>
</form>
<a href="@_url" class="btn-close" aria-label="Close"></a>
</div>
<div class="modal-body">
<ModuleInstance />
@ -22,9 +18,11 @@
</div>
@code {
private void CloseModal()
private string _url;
protected override void OnParametersSet()
{
NavigationManager.NavigateTo((!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl());
_url = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl();
}
}

View File

@ -10,6 +10,6 @@
}
else
{
<ModuleActionsInteractive PageState="@PageState" ModuleState="@ModuleState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" />
<ModuleActionsInteractive PageState="@PageState" ModuleState="@ModuleState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, false)" />
}
}

View File

@ -9,6 +9,8 @@ using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI;
using System.Net;
using static System.Runtime.InteropServices.JavaScript.JSType;
using Microsoft.Extensions.Localization;
// ReSharper disable UnassignedGetOnlyAutoProperty
// ReSharper disable MemberCanBePrivate.Global
@ -20,6 +22,7 @@ namespace Oqtane.Themes.Controls
[Inject] public NavigationManager NavigationManager { get; set; }
[Inject] public IPageModuleService PageModuleService { get; set; }
[Inject] public IModuleService ModuleService { get; set; }
[Inject] public IStringLocalizer<ModuleActionsBase> Localizer { get; set; }
[Parameter] public PageState PageState { get; set; }
[Parameter] public Module ModuleState { get; set; }
@ -37,30 +40,30 @@ namespace Oqtane.Themes.Controls
if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{
actionList.Add(new ActionViewModel { Icon = Icons.Cog, Name = "Manage Settings", Action = async (u, m) => await Settings(u, m) });
actionList.Add(new ActionViewModel { Icon = Icons.Cog, Name = Localizer["ManageSettings"], Action = async (u, m) => await Settings(u, m) });
if (UserSecurity.ContainsRole(ModuleState.PermissionList, PermissionNames.View, RoleNames.Everyone))
{
actionList.Add(new ActionViewModel { Icon = Icons.CircleX, Name = "Unpublish Module", Action = async (s, m) => await Unpublish(s, m) });
actionList.Add(new ActionViewModel { Icon = Icons.CircleX, Name = Localizer["UnpublishModule"], Action = async (s, m) => await Unpublish(s, m) });
}
else
{
actionList.Add(new ActionViewModel { Icon = Icons.CircleCheck, Name = "Publish Module", Action = async (s, m) => await Publish(s, m) });
actionList.Add(new ActionViewModel { Icon = Icons.CircleCheck, Name = Localizer["PublishModule"], Action = async (s, m) => await Publish(s, m) });
}
actionList.Add(new ActionViewModel { Icon = Icons.Trash, Name = "Delete Module", Action = async (u, m) => await DeleteModule(u, m) });
actionList.Add(new ActionViewModel { Icon = Icons.Trash, Name = Localizer["DeleteModule"], Action = async (u, m) => await DeleteModule(u, m) });
if (ModuleState.ModuleDefinition != null && ModuleState.ModuleDefinition.IsPortable)
{
actionList.Add(new ActionViewModel { Name = "" });
actionList.Add(new ActionViewModel { Icon = Icons.CloudUpload, Name = "Import Content", Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Import") });
actionList.Add(new ActionViewModel { Icon = Icons.CloudDownload, Name = "Export Content", Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Export") });
actionList.Add(new ActionViewModel { Icon = Icons.CloudUpload, Name = Localizer["ImportContent"], Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Import") });
actionList.Add(new ActionViewModel { Icon = Icons.CloudDownload, Name = Localizer["ExportContent"], Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Export") });
}
actionList.Add(new ActionViewModel { Name = "" });
if (ModuleState.PaneModuleIndex > 0)
{
actionList.Add(new ActionViewModel { Icon = Icons.DataTransferUpload, Name = "Move To Top", Action = async (s, m) => await MoveTop(s, m) });
actionList.Add(new ActionViewModel { Icon = Icons.DataTransferUpload, Name = Localizer["MoveToTop"], Action = async (s, m) => await MoveTop(s, m) });
}
if (ModuleState.PaneModuleIndex > 0)

View File

@ -6,7 +6,7 @@
@if (ShowLanguageSwitcher)
{
<LanguageSwitcher DropdownAlignment="@LanguageDropdownAlignment" />
<LanguageSwitcher ButtonClass="@ButtonClass" DropdownAlignment="@LanguageDropdownAlignment" />
}
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
@ -36,7 +36,7 @@
}
else
{
<ControlPanelInteractive PageState="@PageState" SiteState="@SiteState" ButtonClass="@ButtonClass" ContainerClass="@ContainerClass" HeaderClass="@HeaderClass" BodyClass="@BodyClass" ShowLanguageSwitcher="@ShowLanguageSwitcher" LanguageDropdownAlignment="@LanguageDropdownAlignment" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" />
<ControlPanelInteractive PageState="@PageState" SiteState="@SiteState" ButtonClass="@ButtonClass" ContainerClass="@ContainerClass" HeaderClass="@HeaderClass" BodyClass="@BodyClass" ShowLanguageSwitcher="@ShowLanguageSwitcher" LanguageDropdownAlignment="@LanguageDropdownAlignment" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, false)" />
}
}

View File

@ -15,6 +15,7 @@
@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>
@ -93,9 +94,13 @@
<div class="row">
<div class="col text-center">
<label for="Module" class="control-label">@Localizer["Module.Manage"]</label>
<select class="form-select" @bind="@_moduleType">
<select class="form-select" @onchange="(e => ModuleTypeChanged(e))">
<option value="new">@Localizer["Module.AddNew"]</option>
<option value="existing">@Localizer["Module.AddExisting"]</option>
@if (PageState.Page.UserId == null)
{
<option value="add">@Localizer["Module.AddExisting"]</option>
<option value="copy">@Localizer["Module.CopyExisting"]</option>
}
</select>
@if (_moduleType == "new")
{
@ -138,7 +143,7 @@
}
else
{
<select class="form-select mt-1" @onchange="(e => PageChanged(e))">
<select class="form-select mt-1" value="@_pageId" @onchange="(e => PageChanged(e))">
<option value="-">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page p in _pages)
{
@ -211,7 +216,7 @@
<div class="row d-flex">
<div class="col">
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-secondary col-12" @onclick=@(async () => await LogoutUser())>@Localizer["Logout"]</button>
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-secondary col-12 mt-2" @onclick=@(async () => await LogoutUser())>@Localizer["Logout"]</button>
</div>
</div>
</div>
@ -291,7 +296,7 @@
_containerType = PageState.Site.DefaultContainerType;
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries)).Distinct().Where(item => item != "Headless").ToList();
}
}
@ -334,6 +339,13 @@
StateHasChanged();
}
private void ModuleTypeChanged(ChangeEventArgs e)
{
_moduleType = (string)e.Value;
_pageId = "-";
_moduleId = "-";
}
private void PageChanged(ChangeEventArgs e)
{
_pageId = (string)e.Value;
@ -341,7 +353,8 @@
{
_modules = PageState.Modules
.Where(module => module.PageId == int.Parse(_pageId) &&
UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList) &&
(_moduleType == "add" || module.ModuleDefinition.IsPortable))
.ToList();
}
_moduleId = "-";
@ -354,6 +367,7 @@
{
if ((_moduleType == "new" && _moduleDefinitionName != "-") || (_moduleType != "new" && _moduleId != "-"))
{
var newModuleId = _moduleId != "-" ? int.Parse(_moduleId) : 0;
if (_moduleType == "new")
{
Module module = new Module();
@ -361,33 +375,37 @@
module.PageId = PageState.Page.PageId;
module.ModuleDefinitionName = _moduleDefinitionName;
module.AllPages = false;
var permissions = new List<Permission>();
if (_visibility == "view")
{
// set module view permissions to page view permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.View);
}
else
{
// set module view permissions to page edit permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.Edit);
}
// set module edit permissions to page edit permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.Edit, PermissionNames.Edit);
module.PermissionList = permissions;
module.PermissionList = GenerateDefaultPermissions(module.SiteId);
module = await ModuleService.AddModuleAsync(module);
_moduleId = module.ModuleId.ToString();
newModuleId = module.ModuleId;
}
else if (_moduleType == "copy")
{
var module = await ModuleService.GetModuleAsync(int.Parse(_moduleId));
module.ModuleId = 0;
module.SiteId = PageState.Site.SiteId;
module.PageId = PageState.Page.PageId;
module.AllPages = false;
module.PermissionList = GenerateDefaultPermissions(module.SiteId);
module = await ModuleService.AddModuleAsync(module);
var moduleContent = await ModuleService.ExportModuleAsync(int.Parse(_moduleId), PageState.Page.PageId);
if (!string.IsNullOrEmpty(moduleContent))
{
await ModuleService.ImportModuleAsync(module.ModuleId, PageState.Page.PageId, moduleContent);
}
newModuleId = module.ModuleId;
}
var pageModule = new PageModule
{
PageId = PageState.Page.PageId,
ModuleId = int.Parse(_moduleId),
ModuleId = newModuleId,
Title = _title
};
if (pageModule.Title == "")
if (string.IsNullOrEmpty(pageModule.Title))
{
if (_moduleType == "new")
{
@ -412,10 +430,17 @@
await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane);
await UpdateSettingsAsync();
if (PageState.RenderMode == RenderModes.Interactive)
{
_message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>";
_title = "";
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, ""));
}
else // reload page in static rendering
{
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, ""), true);
}
}
else
{
_message = $"<div class=\"alert alert-warning mt-2 text-center\" role=\"alert\">{Localizer["Message.Require.ModuleSelect"]}</div>";
@ -427,6 +452,25 @@
}
}
private List<Permission> GenerateDefaultPermissions(int siteId)
{
var permissions = new List<Permission>();
if (_visibility == "view")
{
// set module view permissions to page view permissions
permissions = SetPermissions(permissions, siteId, PermissionNames.View, PermissionNames.View);
}
else
{
// set module view permissions to page edit permissions
permissions = SetPermissions(permissions, siteId, PermissionNames.View, PermissionNames.Edit);
}
// set module edit permissions to page edit permissions
permissions = SetPermissions(permissions, siteId, PermissionNames.Edit, PermissionNames.Edit);
return permissions;
}
private List<Permission> SetPermissions(List<Permission> permissions, int siteId, string modulePermission, string pagePermission)
{
foreach (var permission in PageState.Page.PermissionList.Where(item => item.PermissionName == pagePermission))

View File

@ -1,21 +1,29 @@
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@using Microsoft.AspNetCore.Http
@using Oqtane.Models
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@inject ILanguageService LanguageService
@inject NavigationManager NavigationManager
@if (_supportedCultures?.Count() > 1)
{
<div class="btn-group pe-1" role="group">
<button id="btnCultures" type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button id="btnCultures" type="button" class="btn @ButtonClass dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="oi oi-globe"></span>
</button>
<div class="dropdown-menu @MenuAlignment" aria-labelledby="btnCultures">
@foreach (var culture in _supportedCultures)
{
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="#" @onclick="@(async e => await SetCultureAsync(culture.Name))">@culture.DisplayName</a>
@if (PageState.RenderMode == RenderModes.Interactive)
{
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="#" @onclick="@(async e => await SetCultureAsync(culture.Name))" @onclick:preventDefault="true">@culture.DisplayName</a>
}
else
{
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="@NavigateUrl(PageState.Page.Path, "culture=" + culture.Name)">@culture.DisplayName</a>
}
}
</div>
</div>
@ -23,9 +31,15 @@
@code{
private IEnumerable<Culture> _supportedCultures;
private string MenuAlignment = string.Empty;
[Parameter]
public string DropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right
private string MenuAlignment = string.Empty;
[Parameter]
public string ButtonClass { get; set; } = "btn-outline-secondary";
[CascadingParameter]
HttpContext HttpContext { get; set; }
protected override void OnParametersSet()
{
@ -33,16 +47,26 @@
var languages = PageState.Languages;
_supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
if (PageState.QueryString.ContainsKey("culture"))
{
var culture = PageState.QueryString["culture"];
if (_supportedCultures.Any(item => item.Name == culture))
{
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
HttpContext.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, new CookieOptions { Path = "/", Expires = DateTimeOffset.UtcNow.AddYears(365) });
}
NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""), forceLoad: true);
}
}
private async Task SetCultureAsync(string culture)
{
if (culture != CultureInfo.CurrentUICulture.Name)
{
var interop = new Interop(JSRuntime);
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
var interop = new Interop(JSRuntime);
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
}
}

View File

@ -1,13 +1,17 @@
@namespace Oqtane.UI
@inject SiteState SiteState
@if (_comment != null)
{
@((MarkupString)_comment)
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static)
{
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
}
else
{
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" />
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, _prerender)" />
}
}
@code {
@ -20,6 +24,24 @@ else
[CascadingParameter]
private Module ModuleState { get; set; }
private bool _prerender;
private string _comment;
protected override void OnParametersSet()
{
_prerender = ModuleState.Prerender ?? PageState.Site.Prerender;
_comment = "<!-- rendermode: ";
if (PageState.RenderMode == RenderModes.Static && ModuleState.RenderMode == RenderModes.Static)
{
_comment += RenderModes.Static;
}
else
{
_comment += $"{RenderModes.Interactive}:{PageState.Runtime} - prerender: {_prerender}";
}
_comment += " -->";
}
[Obsolete("AddModuleMessage is deprecated. Use AddModuleMessage in ModuleBase instead.", false)]
public void AddModuleMessage(string message, MessageType type)

View File

@ -10,14 +10,19 @@
{
@if (ModuleType != null)
{
@((MarkupString)$"<!-- rendermode: {ModuleState.RenderMode} -->")
<ModuleMessage @ref="moduleMessageTop" Message="@_messageContent" Type="@_messageType" />
@if (!string.IsNullOrEmpty(_messageContent) && _messagePosition == "top")
{
<ModuleMessage Message="@_messageContent" Type="@_messageType" Parent="@this" />
}
@DynamicComponent
@if (_progressIndicator)
{
<div class="app-progress-indicator"></div>
}
<ModuleMessage @ref="moduleMessageBottom" Message="@_messageContent" Type="@_messageType" />
@if (!string.IsNullOrEmpty(_messageContent) && _messagePosition == "bottom")
{
<ModuleMessage Message="@_messageContent" Type="@_messageType" Parent="@this" />
}
}
}
else
@ -42,8 +47,6 @@
private string _messagePosition;
private bool _progressIndicator = false;
private string _error;
private ModuleMessage moduleMessageTop;
private ModuleMessage moduleMessageBottom;
[Parameter]
public SiteState SiteState { get; set; }
@ -103,13 +106,18 @@
}
public void AddModuleMessage(string message, MessageType type, string position)
{
if(message != _messageContent
|| type != _messageType
|| position != _messagePosition)
{
_messageContent = message;
_messageType = type;
_messagePosition = position;
_progressIndicator = false;
Refresh();
StateHasChanged();
}
}
public void ShowProgressIndicator()
@ -124,25 +132,10 @@
StateHasChanged();
}
private void DismissMessage()
public void DismissMessage()
{
_messageContent = "";
}
private void Refresh()
{
var updateTop = string.IsNullOrEmpty(_messageContent) || _messagePosition == "top";
var updateBottom = string.IsNullOrEmpty(_messageContent) || _messagePosition == "bottom";
if (updateTop && moduleMessageTop != null)
{
moduleMessageTop.RefreshMessage(_messageContent, _messageType);
}
if (updateBottom && moduleMessageBottom != null)
{
moduleMessageBottom.RefreshMessage(_messageContent, _messageType);
}
StateHasChanged();
}
protected override async Task OnErrorAsync(Exception exception)

View File

@ -1,6 +1,7 @@
@using System.Diagnostics.CodeAnalysis
@using System.Net
@using Microsoft.AspNetCore.Http
@using System.Globalization
@namespace Oqtane.UI
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState
@ -103,7 +104,7 @@
_error = "";
Route route = new Route(_absoluteUri, SiteState.Alias.Path);
int moduleid = int.Parse(route.ModuleId);
int moduleid = int.Parse(route.ModuleId, CultureInfo.InvariantCulture);
var action = route.Action;
var querystring = Utilities.ParseQueryString(route.Query);
@ -263,7 +264,7 @@
}
else
{
editmode = (page.PageId == ((user.Settings.ContainsKey("CP-editmode")) ? int.Parse(user.Settings["CP-editmode"]) : -1));
editmode = (page.PageId == ((user.Settings.ContainsKey("CP-editmode")) ? int.Parse(user.Settings["CP-editmode"], CultureInfo.InvariantCulture) : -1));
if (!editmode)
{
var userSettings = new Dictionary<string, string> { { "CP-editmode", "-1" } };
@ -476,6 +477,7 @@
// retrieve module component resources
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
module.RenderMode = moduleobject.RenderMode;
module.Prerender = moduleobject.Prerender;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
@ -549,7 +551,7 @@
{
foreach (var resource in resources)
{
if (resource.Level != ResourceLevel.Site)
if (resource.ResourceType == ResourceType.Stylesheet || resource.Level != ResourceLevel.Site)
{
if (resource.Url.StartsWith("~"))
{

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.1.1</Version>
<Version>5.1.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.1.1</Version>
<Version>5.1.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -34,8 +34,8 @@
<ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
</ItemGroup>
<ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.1.1</Version>
<Version>5.1.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -33,7 +33,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.5" />
</ItemGroup>
<ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.1.1</Version>
<Version>5.1.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -33,7 +33,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
</ItemGroup>
<ItemGroup>

View File

@ -6,7 +6,7 @@
<!-- <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks> -->
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType>
<Version>5.1.1</Version>
<Version>5.1.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -14,7 +14,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane.Maui</RootNamespace>
@ -31,7 +31,7 @@
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
<!-- Versions -->
<ApplicationDisplayVersion>5.1.1</ApplicationDisplayVersion>
<ApplicationDisplayVersion>5.1.2</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
@ -65,15 +65,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.5" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.20" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.20" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.20" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.40" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.40" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.40" />
</ItemGroup>
<ItemGroup>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Client</id>
<version>5.1.1</version>
<version>5.1.2</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Framework</id>
<version>5.1.1</version>
<version>5.1.2</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -11,8 +11,8 @@
<copyright>.NET Foundation</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v5.1.1/Oqtane.Framework.5.1.1.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</releaseNotes>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v5.1.2/Oqtane.Framework.5.1.2.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane framework</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Server</id>
<version>5.1.1</version>
<version>5.1.2</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Shared</id>
<version>5.1.1</version>
<version>5.1.2</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Updater</id>
<version>5.1.1</version>
<version>5.1.2</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.1.Install.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.2.Install.zip" -Force

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.1.Upgrade.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.2.Upgrade.zip" -Force

View File

@ -16,6 +16,7 @@
@using Oqtane.Shared
@using Oqtane.Themes
@using Oqtane.Extensions
@using System.Globalization
@inject NavigationManager NavigationManager
@inject IAntiforgery Antiforgery
@inject IConfigManager ConfigManager
@ -39,7 +40,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<base href="/" />
<link rel="stylesheet" href="css/app.css" />
@if (!string.IsNullOrEmpty(_PWAScript))
@if (_scripts.Contains("PWA Manifest"))
{
<link id="app-manifest" rel="manifest" />
}
@ -68,20 +69,13 @@
<Routes PageState="@_pageState" RenderMode="@_renderMode" Runtime="@_runtime" AntiForgeryToken="@_antiForgeryToken" AuthorizationToken="@_authorizationToken" Platform="@_platform" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(_runtime, _prerender)" />
}
@if (!string.IsNullOrEmpty(_reconnectScript))
{
@((MarkupString)_reconnectScript)
}
@if (!string.IsNullOrEmpty(_PWAScript))
{
@((MarkupString)_PWAScript)
}
@((MarkupString)_bodyResources)
<script src="_framework/blazor.web.js"></script>
<script src="js/app.js"></script>
<script src="js/loadjs.min.js"></script>
<script src="js/interop.js"></script>
<script src="_framework/blazor.web.js"></script>
@((MarkupString)_scripts)
@((MarkupString)_bodyResources)
}
else
{
@ -105,8 +99,7 @@
private string _headResources = "";
private string _bodyResources = "";
private string _styleSheets = "";
private string _PWAScript = "";
private string _reconnectScript = "";
private string _scripts = "";
private string _message = "";
private PageState _pageState;
@ -175,23 +168,25 @@
CreateJwtToken(alias);
}
// include stylesheets to prevent FOUC
var resources = GetPageResources(alias, site, page, int.Parse(route.ModuleId), route.Action);
// includes resources
var resources = GetPageResources(alias, site, page, int.Parse(route.ModuleId, CultureInfo.InvariantCulture), route.Action);
ManageStyleSheets(resources);
// scripts
if (_renderMode == RenderModes.Static)
{
ManageScripts(resources, alias);
}
// generate scripts
if (_renderMode == RenderModes.Interactive && _runtime == Runtimes.Server)
{
_reconnectScript = CreateReconnectScript();
_scripts += CreateReconnectScript();
}
if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null)
{
_PWAScript = CreatePWAScript(alias, site, route);
_scripts += CreatePWAScript(alias, site, route);
}
@if (_renderMode == RenderModes.Static)
{
_scripts += CreateScrollPositionScript();
}
_headResources += ParseScripts(site.HeadContent);
_bodyResources += ParseScripts(site.BodyContent);
@ -329,14 +324,26 @@
int? userid = Context.User.UserId();
userid = (userid == -1) ? null : userid;
// check if cookie already exists
// get cookie value
var visitorCookieName = Constants.VisitorCookiePrefix + SiteId.ToString();
var visitorCookieValue = Context.Request.Cookies[visitorCookieName];
DateTime expiry = DateTime.MinValue;
if (visitorCookieValue != null && visitorCookieValue.Contains("|"))
{
var values = visitorCookieValue.Split('|');
int.TryParse(values[0], out _visitorId);
DateTime.TryParse(values[1], out expiry);
}
else // legacy cookie format
{
int.TryParse(visitorCookieValue, out _visitorId);
}
bool setcookie = false;
Visitor visitor = null;
bool addcookie = false;
var VisitorCookie = Constants.VisitorCookiePrefix + SiteId.ToString();
if (!int.TryParse(Context.Request.Cookies[VisitorCookie], out _visitorId))
if (_visitorId <= 0)
{
// if enabled use IP Address correlation
_visitorId = -1;
var correlate = bool.Parse(settings.GetValue("VisitorCorrelation", "true"));
if (correlate)
{
@ -344,12 +351,12 @@
if (visitor != null)
{
_visitorId = visitor.VisitorId;
addcookie = true;
setcookie = true;
}
}
}
if (_visitorId == -1)
if (_visitorId <= 0)
{
// create new visitor
visitor = new Visitor();
@ -365,13 +372,16 @@
visitor.VisitedOn = DateTime.UtcNow;
visitor = VisitorRepository.AddVisitor(visitor);
_visitorId = visitor.VisitorId;
addcookie = true;
setcookie = true;
}
else
{
// check expiry
if (DateTime.UtcNow > expiry)
{
if (visitor == null)
{
// get visitor if it was not previously loaded
// get visitor if not previously loaded
visitor = VisitorRepository.GetVisitor(_visitorId);
}
if (visitor != null)
@ -392,23 +402,27 @@
visitor.Visits += 1;
visitor.VisitedOn = DateTime.UtcNow;
VisitorRepository.UpdateVisitor(visitor);
setcookie = true;
}
else
{
// remove cookie if VisitorId does not exist
Context.Response.Cookies.Delete(VisitorCookie);
// remove cookie if visitor does not exist
Context.Response.Cookies.Delete(visitorCookieName);
}
}
}
// append cookie
if (addcookie)
// set cookie
if (setcookie)
{
expiry = DateTime.UtcNow.AddMinutes(int.Parse(settings.GetValue("VisitorDuration", "5")));
Context.Response.Cookies.Append(
VisitorCookie,
_visitorId.ToString(),
visitorCookieName,
$"{_visitorId}|{expiry}",
new CookieOptions()
{
Expires = DateTimeOffset.UtcNow.AddYears(1),
Expires = DateTimeOffset.UtcNow.AddYears(10),
IsEssential = true
}
);
@ -432,7 +446,7 @@
private string CreatePWAScript(Alias alias, Site site, Route route)
{
return
return Environment.NewLine +
"<script>" + Environment.NewLine +
" // PWA Manifest" + Environment.NewLine +
" setTimeout(() => {" + Environment.NewLine +
@ -468,14 +482,14 @@
" console.log('ServiceWorker Registration Failed ', err);" + Environment.NewLine +
" });" + Environment.NewLine +
" };" + Environment.NewLine +
"</script>";
"</script>" + Environment.NewLine;
}
private string CreateReconnectScript()
{
return
return Environment.NewLine +
"<script>" + Environment.NewLine +
" // Blazor Server Reconnect" + Environment.NewLine +
" // Interactive Blazor Server Reconnect" + Environment.NewLine +
" new MutationObserver((mutations, observer) => {" + Environment.NewLine +
" if (document.querySelector('#components-reconnect-modal h5 a')) {" + Environment.NewLine +
" async function attemptReload() {" + Environment.NewLine +
@ -487,7 +501,26 @@
" setInterval(attemptReload, 5000);" + Environment.NewLine +
" }" + Environment.NewLine +
" }).observe(document.body, { childList: true, subtree: true });" + Environment.NewLine +
"</script>";
"</script>" + Environment.NewLine;
}
private string CreateScrollPositionScript()
{
return Environment.NewLine +
"<script>" + Environment.NewLine +
" // Blazor Static Rendering Scroll Position" + Environment.NewLine +
" window.interceptNavigation = () => {" + Environment.NewLine +
" let currentUrl = window.location.href;" + Environment.NewLine +
" Blazor.addEventListener('enhancedload', () => {" + Environment.NewLine +
" let newUrl = window.location.href;" + Environment.NewLine +
" if (currentUrl != newUrl) {" + Environment.NewLine +
" window.scrollTo({ top: 0, left: 0, behavior: 'instant' });" + Environment.NewLine +
" }" + Environment.NewLine +
" currentUrl = newUrl;" + Environment.NewLine +
" });" + Environment.NewLine +
" };" + Environment.NewLine +
" document.onload += window.interceptNavigation();" + Environment.NewLine +
"</script>" + Environment.NewLine;
}
private string ParseScripts(string content)
@ -566,13 +599,13 @@
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType));
if (theme != null)
{
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode);
}
else
{
// fallback to default Oqtane theme
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == Constants.DefaultTheme));
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode);
}
var type = Type.GetType(themeType);
if (type != null)
@ -580,7 +613,7 @@
var obj = Activator.CreateInstance(type) as IThemeControl;
if (obj != null)
{
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace);
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, site.RenderMode);
}
}
@ -589,7 +622,7 @@
var typename = "";
if (module.ModuleDefinition != null)
{
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode);
// handle default action
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
@ -635,7 +668,7 @@
var obj = Activator.CreateInstance(moduletype) as IModuleControl;
if (obj != null)
{
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
{
// settings components are embedded within a framework settings module
@ -643,7 +676,7 @@
if (moduletype != null)
{
obj = Activator.CreateInstance(moduletype) as IModuleControl;
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
}
}
}
@ -656,18 +689,20 @@
{
if (module.ModuleDefinition?.Resources != null)
{
resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode);
}
}
return resources;
}
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name)
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name, string rendermode)
{
if (resources != null)
{
foreach (var resource in resources)
{
if (rendermode == RenderModes.Static || resource.ResourceType == ResourceType.Stylesheet || resource.Level == ResourceLevel.Site)
{
if (resource.Url.StartsWith("~"))
{
@ -685,6 +720,7 @@
}
}
}
}
return pageresources;
}
@ -692,6 +728,7 @@
{
if (resources != null)
{
// include stylesheets to prevent FOUC
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
int count = 0;
foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Stylesheet))

View File

@ -760,7 +760,7 @@ namespace Oqtane.Controllers
{
if (!Directory.Exists(folderpath))
{
string path = "";
string path = folderpath.StartsWith(Path.DirectorySeparatorChar) ? Path.DirectorySeparatorChar.ToString() : string.Empty;
var separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
string[] folders = folderpath.Split(separators, StringSplitOptions.RemoveEmptyEntries);
foreach (string folder in folders)

View File

@ -8,6 +8,7 @@ using Oqtane.Infrastructure;
using Oqtane.Repository;
using System.Net;
using System;
using System.Globalization;
namespace Oqtane.Controllers
{
@ -33,7 +34,7 @@ namespace Oqtane.Controllers
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{
return _visitors.GetVisitors(SiteId, DateTime.Parse(fromdate));
return _visitors.GetVisitors(SiteId, DateTime.ParseExact(fromdate, "yyyy-MM-dd", CultureInfo.InvariantCulture));
}
else
{

View File

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Routing;
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Antiforgery;
namespace OqtaneSSR.Extensions
{
@ -23,6 +24,7 @@ namespace OqtaneSSR.Extensions
{
routeEndpointBuilder.Metadata.Add(new RootComponentMetadata(typeof(App)));
routeEndpointBuilder.Metadata.Add(new ComponentTypeMetadata(typeof(App)));
routeEndpointBuilder.Metadata.Add(new RequireAntiforgeryTokenAttribute());
});
}
}

View File

@ -539,6 +539,8 @@ namespace Oqtane.Infrastructure
var identityUserManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>();
var tenant = tenants.GetTenants().FirstOrDefault(item => item.Name == install.TenantName);
var rendermode = (!string.IsNullOrEmpty(install.RenderMode)) ? install.RenderMode : _configManager.GetSection("RenderMode").Value;
var runtime = (!string.IsNullOrEmpty(install.Runtime)) ? install.Runtime : _configManager.GetSection("Runtime").Value;
site = new Site
{
@ -556,9 +558,9 @@ namespace Oqtane.Infrastructure
DefaultContainerType = (!string.IsNullOrEmpty(install.DefaultContainer)) ? install.DefaultContainer : Constants.DefaultContainer,
AdminContainerType = (!string.IsNullOrEmpty(install.DefaultAdminContainer)) ? install.DefaultAdminContainer : Constants.DefaultAdminContainer,
SiteTemplateType = install.SiteTemplate,
RenderMode = (!string.IsNullOrEmpty(install.RenderMode)) ? install.RenderMode : _configManager.GetSection("RenderMode").Value,
Runtime = (!string.IsNullOrEmpty(install.Runtime)) ? install.Runtime : _configManager.GetSection("Runtime").Value,
Prerender = true,
RenderMode = rendermode,
Runtime = runtime,
Prerender = (rendermode == RenderModes.Interactive),
Hybrid = false
};
site = sites.AddSite(site);

View File

@ -197,6 +197,12 @@ namespace Oqtane.Infrastructure
string[] segments = entry.FullName.Split('/'); // ZipArchiveEntries always use unix path separator
string filename = Path.Combine(folder, string.Join(Path.DirectorySeparatorChar, segments, ignoreLeadingSegments, segments.Length - ignoreLeadingSegments));
// validate path to prevent path traversal
if (!Path.GetFullPath(filename).StartsWith(folder + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
{
return "";
}
try
{
if (!Directory.Exists(Path.GetDirectoryName(filename)))
@ -227,6 +233,7 @@ namespace Oqtane.Infrastructure
// an error occurred extracting the file
filename = "";
}
return filename;
}

View File

@ -201,29 +201,27 @@ namespace Oqtane.Managers
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null)
{
var valid = true;
if (!string.IsNullOrEmpty(user.Password))
{
var validator = new PasswordValidator<IdentityUser>();
var result = await validator.ValidateAsync(_identityUserManager, null, user.Password);
valid = result.Succeeded;
if (valid)
if (result.Succeeded)
{
identityuser.PasswordHash = _identityUserManager.PasswordHasher.HashPassword(identityuser, user.Password);
await _identityUserManager.UpdateAsync(identityuser);
}
else
{
_logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Update, "Unable To Update User {Username}. Password Does Not Meet Complexity Requirements.", user.Username);
return null;
}
if (valid)
{
if (!string.IsNullOrEmpty(user.Password))
{
await _identityUserManager.UpdateAsync(identityuser); // requires password to be provided
}
if (user.Email != identityuser.Email)
{
await _identityUserManager.SetEmailAsync(identityuser, user.Email);
// if email address changed and user is not administrator, email verification is required for new email address
// if email address changed and it is not confirmed, verification is required for new email address
if (!user.EmailConfirmed)
{
var alias = _tenantManager.GetAlias();
@ -233,12 +231,13 @@ namespace Oqtane.Managers
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
_notifications.AddNotification(notification);
}
else
}
if (user.EmailConfirmed)
{
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
}
}
user = _users.UpdateUser(user);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Update);
@ -248,10 +247,9 @@ namespace Oqtane.Managers
}
else
{
_logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Update, "Unable To Update User {Username}. Password Does Not Meet Complexity Requirements.", user.Username);
_logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Update, "Unable To Update User {Username}. User Does Not Exist.", user.Username);
user = null;
}
}
return user;
}

View File

@ -7,6 +7,7 @@ using Oqtane.Repository;
using Oqtane.Shared;
using Oqtane.Migrations.Framework;
using Oqtane.Documentation;
using System.Linq;
// ReSharper disable ConvertToUsingDeclaration
@ -29,10 +30,11 @@ namespace Oqtane.Modules.HtmlText.Manager
public string ExportModule(Module module)
{
string content = "";
var htmlText = _htmlText.GetHtmlText(module.ModuleId);
if (htmlText != null)
var htmltexts = _htmlText.GetHtmlTexts(module.ModuleId);
if (htmltexts != null && htmltexts.Any())
{
content = WebUtility.HtmlEncode(htmlText.Content);
var htmltext = htmltexts.OrderByDescending(item => item.CreatedOn).First();
content = WebUtility.HtmlEncode(htmltext.Content);
}
return content;
}

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Version>5.1.1</Version>
<Version>5.1.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -33,19 +33,19 @@
<EmbeddedResource Include="Scripts\MigrateTenant.sql" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.5" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.4">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.5" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.4" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.5" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.5" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.8" />
</ItemGroup>
<ItemGroup>

View File

@ -46,13 +46,16 @@ namespace Oqtane.Pages
{
var sitemap = new List<Sitemap>();
// internal pages which should not be indexed
string[] internalPaths = { "login", "register", "reset", "404" };
// build site map
var rooturl = _alias.Protocol + (string.IsNullOrEmpty(_alias.Path) ? _alias.Name : _alias.Name.Substring(0, _alias.Name.IndexOf("/")));
var moduleDefinitions = _moduleDefinitions.GetModuleDefinitions(_alias.SiteId).ToList();
var pageModules = _pageModules.GetPageModules(_alias.SiteId);
foreach (var page in _pages.GetPages(_alias.SiteId))
{
if (_userPermissions.IsAuthorized(null, PermissionNames.View, page.PermissionList) && page.IsNavigation)
if (_userPermissions.IsAuthorized(null, PermissionNames.View, page.PermissionList) && !internalPaths.Contains(page.Path))
{
var pageurl = rooturl;
if (string.IsNullOrEmpty(page.Url))

View File

@ -287,7 +287,9 @@ namespace Oqtane.Repository
{
ModuleDefinition moduledefinition;
Type[] moduletypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule))).ToArray();
Type[] modulecontroltypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModuleControl))).ToArray();
foreach (Type modulecontroltype in modulecontroltypes)
{
// Check if type should be ignored
@ -299,12 +301,9 @@ namespace Oqtane.Repository
int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
if (index == -1)
{
// determine if this module implements IModule
Type moduletype = assembly
.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace == modulecontroltype.Namespace || item.Namespace.StartsWith(modulecontroltype.Namespace + "."))
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule)));
// determine if this component is part of a module which implements IModule
Type moduletype = moduletypes.FirstOrDefault(item => item.Namespace == modulecontroltype.Namespace);
if (moduletype != null)
{
// get property values from IModule
@ -399,6 +398,22 @@ namespace Oqtane.Repository
moduledefinitions[index] = moduledefinition;
}
// process modules without UI components
foreach (var moduletype in moduletypes.Where(m1 => !modulecontroltypes.Any(m2 => m1.Namespace == m2.Namespace)))
{
// get property values from IModule
var moduleobject = Activator.CreateInstance(moduletype) as IModule;
moduledefinition = moduleobject.ModuleDefinition;
moduledefinition.ModuleDefinitionName = moduletype.Namespace + ", " + moduletype.Assembly.GetName().Name;
moduledefinition.AssemblyName = assembly.GetName().Name;
moduledefinition.Categories = "Headless";
moduledefinition.PermissionList = new List<Permission>
{
new Permission(PermissionNames.Utilize, RoleNames.Host, true)
};
moduledefinitions.Add(moduledefinition);
}
return moduledefinitions;
}

View File

@ -165,7 +165,8 @@ namespace Oqtane.Repository
if (!serverstate.IsInitialized)
{
var site = GetSite(alias.SiteId);
if (site != null)
{
// initialize theme Assemblies
site.Themes = _themeRepository.GetThemes().ToList();
@ -180,7 +181,7 @@ namespace Oqtane.Repository
site.Version = version;
UpdateSite(site);
}
}
serverstate.IsInitialized = true;
}
}
@ -411,7 +412,7 @@ namespace Oqtane.Repository
}
else
{
parent = pages.FirstOrDefault(item => item.Path.ToLower() == pageTemplate.Parent.ToLower());
parent = pages.FirstOrDefault(item => item.Path.ToLower() == ((pageTemplate.Parent == "/") ? "" : pageTemplate.Parent.ToLower()));
}
page.ParentId = (parent != null) ? parent.PageId : null;
page.Path = page.Path.ToLower();
@ -487,7 +488,11 @@ namespace Oqtane.Repository
pageModule.Order = (pageTemplateModule.Order == 0) ? 1 : pageTemplateModule.Order;
pageModule.ContainerType = pageTemplateModule.ContainerType;
pageModule.IsDeleted = pageTemplateModule.IsDeleted;
pageModule.Module.PermissionList = pageTemplateModule.PermissionList;
pageModule.Module.PermissionList = new List<Permission>();
foreach (var permission in pageTemplateModule.PermissionList)
{
pageModule.Module.PermissionList.Add(permission.Clone(permission));
}
pageModule.Module.AllPages = false;
pageModule.Module.IsDeleted = false;
try
@ -539,9 +544,12 @@ namespace Oqtane.Repository
try
{
var module = _moduleRepository.GetModule(pageModule.ModuleId);
if (module != null)
{
var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
((IPortable)moduleobject).ImportModule(module, pageTemplateModule.Content, moduleDefinition.Version);
}
}
catch (Exception ex)
{
if (alias != null)

View File

@ -13,6 +13,7 @@ using Oqtane.Shared;
using Oqtane.Themes;
using System.Reflection.Metadata;
using Oqtane.Migrations.Master;
using Oqtane.Modules;
namespace Oqtane.Repository
{
@ -224,9 +225,11 @@ namespace Oqtane.Repository
private List<Theme> LoadThemesFromAssembly(List<Theme> themes, Assembly assembly)
{
Theme theme;
List<Type> themeTypes = new List<Type>();
Type[] themeTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme))).ToArray();
Type[] themeControlTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IThemeControl))).ToArray();
Type[] containerControlTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray();
foreach (Type themeControlType in themeControlTypes)
{
// Check if type should be ignored
@ -240,16 +243,9 @@ namespace Oqtane.Repository
int index = themes.FindIndex(item => item.ThemeName == qualifiedThemeType);
if (index == -1)
{
// Find all types in the assembly with the same namespace root
themeTypes = assembly.GetTypes()
.Where(item => !item.IsOqtaneIgnore())
.Where(item => item.Namespace != null)
.Where(item => item.Namespace == themeControlType.Namespace || item.Namespace.StartsWith(themeControlType.Namespace + "."))
.ToList();
// determine if this component is part of a theme which implements ITheme
Type themetype = themeTypes.FirstOrDefault(item => item.Namespace == themeControlType.Namespace);
// determine if this theme implements ITheme
Type themetype = themeTypes
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(ITheme)));
if (themetype != null)
{
var themeobject = Activator.CreateInstance(themetype) as ITheme;
@ -285,6 +281,7 @@ namespace Oqtane.Repository
}
theme = themes[index];
// add theme control
var themecontrolobject = Activator.CreateInstance(themeControlType) as IThemeControl;
theme.Themes.Add(
new ThemeControl
@ -296,14 +293,12 @@ namespace Oqtane.Repository
}
);
// containers
Type[] containertypes = themeTypes
.Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray();
foreach (Type containertype in containertypes)
if (!theme.Containers.Any())
{
// add container controls
foreach (Type containertype in containerControlTypes.Where(item => item.Namespace == themeControlType.Namespace))
{
var containerobject = Activator.CreateInstance(containertype) as IThemeControl;
if (theme.Containers.FirstOrDefault(item => item.TypeName == containertype.FullName + ", " + themeControlType.Assembly.GetName().Name) == null)
{
theme.Containers.Add(
new ThemeControl
{

View File

@ -216,6 +216,7 @@ namespace Oqtane
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
if (_useSwagger)
{

View File

@ -13,9 +13,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.5" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.5" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />

View File

@ -19,10 +19,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.5" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.5" />
</ItemGroup>
<ItemGroup>

View File

@ -12,9 +12,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.5" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.5" />
</ItemGroup>
<ItemGroup>

View File

@ -35,13 +35,15 @@ Oqtane.RichTextEditor = {
enableQuillEditor: function (editorElement, mode) {
editorElement.__quill.enable(mode);
},
insertQuillImage: function (quillElement, imageURL, altText) {
var Delta = Quill.import('delta');
editorIndex = 0;
getCurrentCursor: function (quillElement) {
var editorIndex = 0;
if (quillElement.__quill.getSelection() !== null) {
editorIndex = quillElement.__quill.getSelection().index;
}
return editorIndex;
},
insertQuillImage: function (quillElement, imageURL, altText, editorIndex) {
var Delta = Quill.import('delta');
return quillElement.__quill.updateContents(
new Delta()

View File

@ -35,5 +35,10 @@ namespace Oqtane.Modules
/// Specifies the required render mode for the module control ie. Static,Interactive
/// </summary>
string RenderMode { get; }
/// <summary>
/// Specifies the prerender mode for the moudle control ie: true or false
/// </summary>
bool? Prerender { get; }
}
}

View File

@ -117,6 +117,8 @@ namespace Oqtane.Models
public bool UseAdminContainer { get; set; }
[NotMapped]
public string RenderMode{ get; set; }
[NotMapped]
public bool? Prerender { get; set; }
#endregion

View File

@ -101,6 +101,20 @@ namespace Oqtane.Models
IsAuthorized = isAuthorized;
}
public Permission Clone(Permission permission)
{
return new Permission
{
SiteId = permission.SiteId,
EntityName = permission.EntityName,
EntityId = permission.EntityId,
PermissionName = permission.PermissionName,
RoleName = permission.RoleName,
UserId = permission.UserId,
IsAuthorized = permission.IsAuthorized
};
}
[Obsolete("The Role property is deprecated", false)]
[NotMapped]
[JsonIgnore] // exclude from API payload

View File

@ -42,14 +42,20 @@ namespace Oqtane.Models
}
int pos = PagePath.IndexOf("/" + Constants.UrlParametersDelimiter + "/");
if (pos != -1)
{
if (pos + 3 < PagePath.Length)
{
UrlParameters = PagePath.Substring(pos + 3);
}
PagePath = PagePath.Substring(0, pos);
}
pos = PagePath.IndexOf("/" + Constants.ModuleDelimiter + "/");
if (pos != -1)
{
if (pos + 3 < PagePath.Length)
{
ModuleId = PagePath.Substring(pos + 3);
}
PagePath = PagePath.Substring(0, pos);
}
if (ModuleId.Length != 0)

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Version>5.1.1</Version>
<Version>5.1.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -19,8 +19,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.3" />

View File

@ -4,8 +4,8 @@ namespace Oqtane.Shared
{
public class Constants
{
public static readonly string Version = "5.1.1";
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1";
public static readonly string Version = "5.1.2";
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2";
public const string PackageId = "Oqtane.Framework";
public const string ClientId = "Oqtane.Client";
public const string UpdaterPackageId = "Oqtane.Updater";

View File

@ -44,9 +44,10 @@ namespace Oqtane.Shared
string querystring = "";
string fragment = "";
if (!string.IsNullOrEmpty(path) && !path.StartsWith("/")) path = "/" + path;
if (!string.IsNullOrEmpty(parameters))
{
// parse parameters
(string urlparameters, querystring, fragment) = ParseParameters(parameters);
if (!string.IsNullOrEmpty(urlparameters))
{
@ -138,6 +139,9 @@ namespace Oqtane.Shared
public static string FormatContent(string content, Alias alias, string operation)
{
if (string.IsNullOrEmpty(content) || alias == null)
return content;
var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : "";
switch (operation)
{

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Version>5.1.1</Version>
<Version>5.1.2</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>

205
README.md
View File

@ -1,6 +1,6 @@
# Latest Release
[5.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0) was released on Mar 27, 2024 and is a major release providing Static Server Rendering support for Blazor in .NET 8. This release includes 263 pull requests by 6 different contributors, pushing the total number of project commits all-time to over 5100. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
[5.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1) was released on Apr 16, 2024 and is primarily a stabilization release, including a variety of improvements to the Static Server-Side Rendering support for Blazor in .NET 8. This release includes 40 pull requests by 6 different contributors, pushing the total number of project commits all-time to over 5200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Foqtane%2Foqtane.framework%2Fmaster%2Fazuredeploy.json)
@ -8,7 +8,7 @@
![Oqtane](https://github.com/oqtane/framework/blob/master/oqtane.png?raw=true "Oqtane")
Oqtane is an open source CMS and Application Framework that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a fully dynamic digital experience which can be hosted on Blazor Server, Blazor WebAssembly, or Blazor Hybrid (via .NET MAUI).
Oqtane is an open source CMS and Application Framework that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a fully dynamic digital experience which can be hosted on Static Blazor, Blazor Server, Blazor WebAssembly, or Blazor Hybrid (via .NET MAUI).
Oqtane is being developed based on some fundamental principles which are outlined in the [Oqtane Philosophy](https://www.oqtane.org/blog/!/20/oqtane-philosophy).
@ -18,15 +18,15 @@ Please note that this project is owned by the .NET Foundation and is governed by
**Using Version 5:**
- Install **[.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)**.
- Install **[.NET 8.0.4 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)**.
- Install the latest edition (v17.8 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**.
- Install the latest edition (v17.9 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**.
- Clone the Oqtane dev branch source code to your local system.
- Open the **Oqtane.sln** solution file.
- **Important:** Build the solution.
- **Important:** Rebuild the entire solution before running it.
- Make sure you specify Oqtane.Server as the Startup Project
@ -63,8 +63,8 @@ Backlog (TBD)
- [ ] Folder Providers
- [ ] Generative AI Integration
5.1.1 (Apr 2024)
- [ ] Stabilization improvements
[5.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1) (Apr 16, 2024)
- [x] Stabilization improvements
[5.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0) (Mar 27, 2024)
- [x] Migration to the new unified Blazor approach in .NET 8 (ie. blazor.web.js)
@ -79,200 +79,11 @@ Backlog (TBD)
[5.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0) (Nov 16, 2023)
- [x] Migration to .NET 8
[4.0.6](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.6) ( Oct 16, 2023 )
- [x] Stabilization improvements
[4.0.5](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.5) ( Sep 26, 2023 )
- [x] Stabilization improvements
[4.0.4](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4) ( Sep 25, 2023 )
- [x] Stabilization improvements
- [x] User Import
[4.0.3](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3) ( Aug 29, 2023 )
- [x] Stabilization improvements
[4.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2) ( Aug 9, 2023 )
- [x] Stabilization improvements
[4.0.1](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.1) ( Jul 18, 2023 )
- [x] Stabilization improvements
[4.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0) ( Jun 26, 2023 )
- [x] Migration to .NET 7
- [x] Improved JavaScript, CSS, and Meta support
- [x] Optimized Client Assembly Loading
- [x] Routable Modules (ie. declarative configuration)
- [x] Site Template improvements
- [x] IEventSubscriber interface
[3.4.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3) ( May 3, 2023 )
- [x] Stabilization improvements
[3.4.2](https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.2) ( Mar 29, 2023 )
- [x] Stabilization improvements
[3.4.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1) ( Mar 13, 2023 )
- [x] Stabilization improvements
[3.4.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.0) ( Mar 12, 2023 )
- [x] Permissions performance optimization
- [x] Connection string management improvements
- [x] XML site map generator
- [x] OIDC integration with User Profiles
[3.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1) ( Jan 14, 2023 )
- [x] Stabilization improvements
[3.3.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.0) ( Jan 12, 2023 )
- [x] Dynamic Authorization Policies
- [x] Entity-Level Permissions
- [x] Extended Module Permissions
[3.2.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1) ( Oct 17, 2022 )
- [x] Stabilization improvements
- [x] Server Event System
[3.2.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0) ( Sep 13, 2022 )
- [x] .NET MAUI / Blazor Hybrid support
- [x] Upgrade to Bootstrap 5.2
[3.1.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3) ( Jun 27, 2022 )
- [x] Stabilization improvements
[3.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.2) ( May 14, 2022 )
- [x] Stabilization improvements
[3.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1) ( May 3, 2022 )
- [x] Stabilization improvements
[3.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.0) ( Apr 5, 2022 )
- [x] User account lockout support
- [x] Two factor authentication support
- [x] Per-site configuration of password complexity, lockout criteria
- [x] External login support via OAuth2 / OpenID Connect
- [x] Support for Single Sign On (SSO) via OpenID Connect
- [x] External client support via Jwt tokens
- [x] Downstream API support via Jwt tokens
- [x] CSS resource hierarchy support
- [x] Site structure/content migration
- [x] Event log notifications
- [x] 404 page handling
- [x] Property change component notifications
- [x] Support for ES6 JavaScript modules
[3.0.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3) ( Feb 15, 2022 )
- [x] Url fragment and anchor navigation support
- [x] Meta tag support in page head
- [x] Html/Text content versioning support
[3.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.2) ( Jan 16, 2022 )
- [x] Default alias specification, auto alias registration, redirect logic
- [x] Improvements to visitor tracking and url mapping
- [x] Scheduler enhancements for stop/start, weekly and one-time jobs
- [x] Purge job for daily housekeeping of event log and visitors
- [x] Granular security filtering for Settings
[3.0.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1) ( Dec 12, 2021 )
- [x] Url mapping for broken links, content migration
- [x] Visitor tracking for usage insights, personalization
- [x] User experience improvements in Page and Module management
[3.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0) ( Nov 11, 2021 )
- [x] Migration to .NET 6
- [x] Blazor hosting model flexibility per site
- [x] Blazor WebAssembly prerendering support
[2.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v2.3.1) ( Sep 27, 2021 )
- [x] Complete UI migration to Bootstrap 5 and HTML5 form validation
- [x] Improve module/theme installation and add support for commercial extensions
- [x] Replace System.Drawing with ImageSharp
- [x] Image resizing service
[2.2.0](https://github.com/oqtane/oqtane.framework/releases/tag/v2.2.0) ( Jul 6, 2021 )
- [x] Bootstrap 5 Upgrade
- [x] Package Service integration
- [x] Default and Shared Resource File inclusion
- [x] Startup Error logging
- [x] API Controller Validation and Logging
[2.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v2.1.0) ( Jun 4, 2021 )
- [x] Cross Platform Database Support ( ie. LocalDB, SQL Server, SQLite, MySQL, PostgreSQL ) - see [#964](https://github.com/oqtane/oqtane.framework/discussions/964)
- [x] Utilize EF Core Migrations - see [#964](https://github.com/oqtane/oqtane.framework/discussions/964)
- [x] Public Content Folder support
- [x] Multi-tenant Infrastructure improvements
- [x] User Authorization optimization
- [x] Consolidation of Package Management
- [x] Blazor Server Pre-rendering
- [x] Translation Package installation support
[2.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v2.0.2) ( Apr 19, 2021 )
- [x] Assorted fixes and user experience improvements
[2.0.1](https://github.com/oqtane/oqtane.framework/releases/tag/v2.0.1) ( Feb 27, 2021 )
- [x] Complete Static Localization of Admin UI
[2.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v2.0.0) ( Nov 11, 2020 )
- [x] Migration to .NET 5
- [x] Static Localization ( ie. labels, help text, etc.. )
- [x] Improved JavaScript Reference Support
- [x] Performance Optimizations
- [x] Developer Productivity Enhancements
[1.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v1.0.0) ( May 19, 2020 )
- [x] Migration to .NET Core 3.2
- [x] Multi-Tenant ( Shared Database & Isolated Database )
- [x] Modular Architecture
- [x] Headless API with Swagger Support
- [x] Dynamic Page Compositing Model / Site & Page Management
- [x] Authentication / User Management / Profile Management
- [x] Authorization / Roles Management / Granular Permissions
- [x] Dynamic Routing
- [x] Extensibility via Custom Modules
- [x] Extensibility via Custom Themes
- [x] Event Logging / Audit Trail
- [x] Folder / File Management
- [x] Recycle Bin
- [x] Scheduled Jobs ( Background Processing )
- [x] Notifications / Email Delivery
- [x] Seamless Upgrade Experience
- [x] Progressive Web Application Support
- [x] JavaScript Lazy Loading
- [x] Dynamic CSS/Lazy Loading
[POC](https://www.oqtane.org/blog/!/7/announcing-oqtane-a-modular-application-framework-for-blazor) ( May 9, 2019 )
- [x] Initial public release on GitHub
- [x] .NET Core 3.0
➡️ Full list and older versions can be found in the [docs roadmap](https://docs.oqtane.org/guides/roadmap/index.html)
# Background
Oqtane was created by [Shaun Walker](https://www.linkedin.com/in/shaunbrucewalker/) and is inspired by the DotNetNuke web application framework. Initially created as a proof of concept, Oqtane is a native Blazor application written from the ground up using modern .NET Core technology and a Single Page Application (SPA) architecture. It is a modular application framework offering a fully dynamic page compositing model, multi-site support, designer friendly themes, and extensibility via third party modules.
# Release Announcements
[Oqtane 5.0](https://www.oqtane.org/blog/!/75/announcing-oqtane-5-0-for-net-8)
[Oqtane 4.0](https://www.oqtane.org/blog/!/63/announcing-oqtane-4-0-for-net-7)
[Oqtane 3.4](https://www.oqtane.org/blog/!/56/oqtane-3-4-0-released)
[Oqtane 3.3](https://www.oqtane.org/blog/!/54/oqtane-3-3-0-released)
[Oqtane 3.2](https://www.oqtane.org/blog/!/50/oqtane-3-2-for-net-maui-blazor-hybrid)
[Oqtane 3.1](https://www.oqtane.org/blog/!/41/oqtane-3-1-released)
[Oqtane 3.0](https://www.oqtane.org/Resources/Blog/PostId/551/announcing-oqtane-30-for-net-6)
[Oqtane 2.2](https://www.oqtane.org/Resources/Blog/PostId/549/oqtane-22-upgrades-to-bootstrap-5)
[Oqtane 2.1](https://www.oqtane.org/Resources/Blog/PostId/548/oqtane-21-now-supports-multiple-databases)
[Oqtane 2.0](https://www.oqtane.org/Resources/Blog/PostId/544/announcing-oqtane-20-for-net-5)
[Oqtane 1.0](https://www.oqtane.org/Resources/Blog/PostId/540/announcing-oqtane-10-a-modular-application-framework-for-blazor)
[Oqtane POC](https://www.oqtane.org/Resources/Blog/PostId/520/announcing-oqtane-a-modular-application-framework-for-blazor)
# Reference Implementations
[Built On Blazor!](https://builtonblazor.net) - a showcase of sites built on Blazor