Compare commits

..

225 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
03b6abe5ec Merge pull request #4149 from oqtane/master
5.1.1 release
2024-04-16 13:34:15 -04:00
4479304f3f Merge pull request #4148 from oqtane/dev
5.1.1 release
2024-04-16 13:33:51 -04:00
ed83405254 Merge pull request #4147 from sbwalker/dev
fix SiteMap path issue
2024-04-16 13:04:39 -04:00
b815d945d9 fix SiteMap path issue 2024-04-16 13:04:25 -04:00
2b8e024f48 Merge pull request #4146 from sbwalker/dev
include .NET MAUI CORS policy for static files
2024-04-16 12:36:53 -04:00
2a0399b98d include .NET MAUI CORS policy for static files, add support for [wwwroot] in content 2024-04-16 12:36:31 -04:00
4e333e2d75 Merge pull request #4145 from sbwalker/dev
fix SiteRouter issue when running on .NET MAUI
2024-04-16 08:03:16 -04:00
4f25b7bbbe fix SiteRouter issue when running on .NET MAUI 2024-04-16 08:02:59 -04:00
3678db649b Merge pull request #4144 from sbwalker/dev
fixes for #4134 - Rich Text Editor
2024-04-15 08:54:30 -04:00
9d8b1fd99b fixes for #4134 - Rich Text Editor 2024-04-15 08:54:23 -04:00
7b62c06be3 Merge pull request #4140 from sbwalker/dev
convert Quill's empty content to empty string
2024-04-12 15:11:01 -04:00
bc978a91e3 convert Quill's empty content to empty string 2024-04-12 15:10:47 -04:00
611ba97b60 Merge pull request #4139 from sbwalker/dev
more changes for #4134 - ensure HTML content is preserved
2024-04-12 14:58:27 -04:00
39dff1ea7c more changes for #4134 - ensure HTML content is preserved 2024-04-12 14:58:14 -04:00
bcf7bcba1d Merge pull request #4137 from sbwalker/dev
fix #4134 - RichTextEditor scenarios
2024-04-12 13:25:59 -04:00
8f00730189 fix #4134 - RichTextEditor scenarios 2024-04-12 13:25:44 -04:00
1dde79ace2 Merge pull request #4136 from zyhfish/bug/fix-issue-4121
Fix #4121: avoid nested square bracket issue.
2024-04-12 11:04:25 -04:00
Ben
5954fb91be Fix #4121: avoid nested square bracket issue. 2024-04-12 21:56:49 +08:00
e192383662 Merge pull request #4135 from leigh-pointer/TokenRep
InitializeTokenReplace not setting the correct PackageReference
2024-04-12 07:41:40 -04:00
4a20fad4e5 InitializeTokenReplace not setting the correct PackageReference
For completeness.
2024-04-12 12:21:44 +02:00
a469e0864e Merge pull request #4132 from sbwalker/dev
move RichTextEditor script registration to Body
2024-04-11 15:27:09 -04:00
cfce2bdbd9 move RichTextEditor script registration to Body 2024-04-11 15:26:55 -04:00
529b5c0a00 Merge pull request #4130 from sbwalker/dev
prepare for 5.1.1 release
2024-04-11 14:28:12 -04:00
7cc5787779 prepare for 5.1.1 release 2024-04-11 14:27:59 -04:00
48eeb279e2 Merge pull request #4129 from sbwalker/dev
add Process info to System Info to indicate if process is 32 bit or 64 bit
2024-04-11 13:34:25 -04:00
d67566252a add Process info to System Info to indicate if process is 32 bit or 64 bit 2024-04-11 13:34:06 -04:00
8a2d79e17d Merge pull request #4127 from sbwalker/dev
fix issue where active tab not set correctly when rich text editor disabled
2024-04-11 09:16:57 -04:00
17370dff54 fix issue where active tab not set correctly when rich text editor disabled 2024-04-11 09:16:39 -04:00
f08a8e7634 Merge pull request #4126 from sbwalker/dev
fix https://github.com/oqtane/oqtane.blogs/issues/44 - rich text editor throwing exception in specific scenario
2024-04-11 09:04:00 -04:00
83543bbddc fix https://github.com/oqtane/oqtane.blogs/issues/44 - rich text editor throwing exception in specific scenario 2024-04-11 09:03:41 -04:00
b898c90f41 Merge pull request #4125 from leigh-pointer/ImageSharpUpdate
ImageSharp update from 3.1.3 to 3.1.4
2024-04-11 07:44:51 -04:00
1ec927cf4f ImageSharp update from 3.1.3 to 3.1.4 2024-04-11 11:11:05 +02:00
aef21ba9d5 Merge pull request #4124 from sbwalker/dev
update theme and module templates to .NET SDK 8.0.4
2024-04-10 16:29:35 -04:00
0b31709aee update theme and module templates to .NEt SDK 8.0.4 2024-04-10 16:29:16 -04:00
7c3433256a Merge pull request #4123 from sbwalker/dev
update to .NET SDK 8.0.4
2024-04-10 16:24:40 -04:00
c79c638f35 update to .NET SDK 8.0.4 2024-04-10 16:24:23 -04:00
a148941a39 Merge pull request #4119 from sbwalker/dev
fix #4109 - fix resx key in SqlServerConfig (credit @mmisu)
2024-04-09 08:06:31 -04:00
23abe26b0b fix #4109 - fix resx key in SqlServerConfig (credit @mmisu) 2024-04-09 08:06:12 -04:00
8f21d6dde0 Merge pull request #4112 from mdmontesinos/dev
fix #4103 - Visual Studio build acceleration breaks Oqtane Module build process
2024-04-08 14:32:09 -04:00
b74a6c9e03 Merge pull request #4118 from sbwalker/dev
removing StreamRendering attribute as recent SDK seems to have resolved earlier rendering issues
2024-04-08 14:31:52 -04:00
b63f73ef93 removing StreamRendering attribute as recent SDK seems to have resolved earlier rendering issues 2024-04-08 14:31:12 -04:00
1f0b369a15 Disable Accelerate Builds for Package project in Module template 2024-04-06 08:37:48 +02:00
7a43473513 Disable Accelerate Builds for Package project in Theme template 2024-04-06 08:36:13 +02:00
07f367a2a5 Merge pull request #4111 from sbwalker/dev
fix #4108 - content lost when adding image to RichTextEditor
2024-04-05 16:07:15 -04:00
c52ad68d92 fix #4108 - content lost when adding image to RichTextEditor 2024-04-05 16:07:01 -04:00
85467dbd2a Merge pull request #4106 from sbwalker/dev
update text in component to be consistent with resx
2024-04-04 12:01:01 -04:00
aa767846f0 update text in component to be consistent with resx 2024-04-04 12:00:52 -04:00
6d3ad15d20 Merge pull request #4105 from sbwalker/dev
modify #4099 - fix localization and use Delete rather than Clear in API methods for consistency with rest of framework
2024-04-04 11:58:22 -04:00
7b95db4d13 modify #4099 - fix localization and use Delete rather than Clear in API methods for consistency with rest of framework 2024-04-04 11:58:05 -04:00
160b3ff655 Merge pull request #4099 from zyhfish/task/fix-3625
Fix #3625: add the clear logs function.
2024-04-04 08:26:41 -04:00
Ben
757a39a75e update code by review result. 2024-04-03 22:27:39 +08:00
Ben
4c08a527be Fix #3625: add the clear logs function. 2024-04-03 09:21:13 +08:00
578b7b0512 Update issue templates 2024-04-02 16:43:43 -04:00
45e0259099 Update issue templates 2024-04-02 16:41:52 -04:00
1ac1933ec1 Update issue templates 2024-04-02 16:37:54 -04:00
43353e89bb Merge pull request #4098 from sbwalker/dev
fix ThemeSettings SetSetting() references to not specify IsPrivate property
2024-04-02 10:53:24 -04:00
010e4610f7 fix ThemeSettings SetSetting() references to not specify IsPrivate property 2024-04-02 10:53:07 -04:00
ba38853406 Merge pull request #4097 from sbwalker/dev
fix SiteMap so that it supports page Urls
2024-04-02 08:45:03 -04:00
4944a9e51e fix SiteMap so that it supports page Urls 2024-04-02 08:44:51 -04:00
73ad97859c Merge pull request #4093 from sbwalker/dev
fix #4091 - double slash generated for home page path ("/") and urlparameters
2024-04-01 15:11:38 -04:00
e600da229c fix #4091 - double slash generated for home page path ("/") and urlparameters 2024-04-01 15:11:20 -04:00
273b4f20db Merge pull request #4090 from sbwalker/dev
fix #4088 - redirect to login if not authenticated
2024-04-01 12:01:04 -04:00
9843dccdf0 fix #4088 - redirect to login if not authenticated 2024-04-01 12:00:53 -04:00
60ae1ec1e8 Merge pull request #4089 from W6HBR/dev
Fix incorrect parameter passed from ProfileService to ProfileController
2024-04-01 09:53:48 -04:00
650c6670f2 Fix incorrect parameter passed from ProfileService.cs to ProfileController.cs
ProfileService was passing SiteId instead of ProfileId which was causing updates to profile entries to fail with "Unauthorized Profile Put Attempt".
2024-03-31 10:08:19 -07:00
d6dcba7a60 Merge pull request #4083 from sbwalker/dev
use Constants.RequestVerificationToken rather than magic string
2024-03-29 11:19:33 -04:00
5b3849082f use Constants.RequestVerificationToken rather than magic string 2024-03-29 11:19:21 -04:00
5de988a2e6 Merge pull request #4082 from sbwalker/dev
remove changes to allow path to support urls - urls should be specified as redirects
2024-03-29 10:15:42 -04:00
26220b2f54 remove changes to allow path to support urls - urls should be specified as redirects 2024-03-29 10:15:24 -04:00
8d8436f1f1 Merge pull request #4081 from sbwalker/dev
set active tab correctly in RichTextEditor for scenarios where rich text editor is disabled
2024-03-29 07:50:27 -04:00
93057d9449 set active tab correctly in RichTextEditor for scenarios where rich text editor is disabled 2024-03-29 07:50:03 -04:00
b2419abdc8 Merge pull request #4079 from sbwalker/dev
fix #4073 - OIDC/OAuth2 flow with static rendering
2024-03-28 16:12:15 -04:00
9f654918ae fix #4073 - OIDC/OAuth2 flow with static rendering 2024-03-28 16:12:02 -04:00
cb086fe08d Merge pull request #4077 from zyhfish/task/fix-issue-4074
Fix #4074: use the correct property value.
2024-03-28 14:23:46 -04:00
1482166ba3 Merge pull request #4078 from sbwalker/dev
fix #4075 - auth cookie being rejected under some scenarios - change from Strict to Lax to match latest .NET Identity configuration
2024-03-28 14:23:31 -04:00
6b8dd9bf03 fix #4075 - auth cookie being rejected under some scenarios - change from Strict to Lax to match latest .NET Identity configuration 2024-03-28 14:23:13 -04:00
Ben
a8af9da249 Fix #4074: use the correct property value. 2024-03-28 22:18:30 +08:00
e410f82fdb Update README.md 2024-03-27 15:01:47 -04:00
4f70f228f1 Update README.md 2024-03-27 10:43:55 -04:00
ddc97b99fa Update README.md 2024-03-27 10:37:03 -04:00
5f42918cc9 Update README.md 2024-03-27 10:09:39 -04:00
e6dfe6fe89 Update README.md 2024-03-27 08:57:52 -04:00
110 changed files with 1359 additions and 1094 deletions

26
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@ -0,0 +1,26 @@
---
name: Bug Report
about: Create a bug report to help us improve the product
title: "[BUG] "
labels: ''
assignees: ''
---
### Oqtane Info
Version - #.#.#
Render Mode - Static
Interactivity - Server
Database - SQL Server
### Describe the bug
### Expected Behavior
### Steps To Reproduce
### Anything else?

View File

@ -0,0 +1,20 @@
---
name: Enhancement Request
about: 'Suggest a product enhancement '
title: "[ENH] "
labels: ''
assignees: ''
---
### Oqtane Info
Version - #.#.#
Render Mode - Static
Interactivity - Server
Database - SQL Server
### Describe the enhancement
### Anything else?

View File

@ -170,6 +170,7 @@
try try
{ {
Folder folder; Folder folder;
if (_folderId != -1) if (_folderId != -1)
{ {
folder = await FolderService.GetFolderAsync(_folderId); folder = await FolderService.GetFolderAsync(_folderId);
@ -179,8 +180,6 @@
folder = new Folder(); folder = new Folder();
} }
folder.SiteId = PageState.Site.SiteId;
if (_parentId == -1) if (_parentId == -1)
{ {
folder.ParentId = null; folder.ParentId = null;
@ -189,7 +188,15 @@
{ {
folder.ParentId = _parentId; 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.Name = _name;
folder.Type = _type; folder.Type = _type;
folder.ImageSizes = _imagesizes; folder.ImageSizes = _imagesizes;

View File

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

View File

@ -63,7 +63,7 @@ else
<th>@Localizer["Function"]</th> <th>@Localizer["Function"]</th>
</Header> </Header>
<Row> <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.LogDate</td>
<td class="@GetClass(context.Function)">@context.Level</td> <td class="@GetClass(context.Function)">@context.Level</td>
<td class="@GetClass(context.Function)">@context.Feature</td> <td class="@GetClass(context.Function)">@context.Feature</td>
@ -86,47 +86,48 @@ else
</div> </div>
</div> </div>
<br /> <br />
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
<ActionDialog Header="Clear Events" Message="Are You Sure You Wish To Remove All Log Events?" Action="DeleteLogs" Class="btn btn-danger" OnClick="@(async () => await DeleteLogs())" ResourceKey="DeleteLogs" />
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
} }
@code { @code {
private string _level = "-"; private string _level = "-";
private string _function = "-"; private string _function = "-";
private string _rows = "10"; private string _rows = "10";
private int _page = 1; private int _page = 1;
private List<Log> _logs; private List<Log> _logs;
private int _retention = 30; private int _retention = 30;
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}"; public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
try try
{ {
if (UrlParameters.ContainsKey("level")) if (UrlParameters.ContainsKey("level"))
{ {
_level = UrlParameters["level"]; _level = UrlParameters["level"];
} }
if (UrlParameters.ContainsKey("function")) if (UrlParameters.ContainsKey("function"))
{ {
_function = UrlParameters["function"]; _function = UrlParameters["function"];
} }
if (UrlParameters.ContainsKey("rows")) if (UrlParameters.ContainsKey("rows"))
{ {
_rows = UrlParameters["rows"]; _rows = UrlParameters["rows"];
} }
if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page)) if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page))
{ {
_page = page; _page = page;
} }
await GetLogs(); await GetLogs();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30")); _retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30"));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -213,22 +214,37 @@ else
return classname; return classname;
} }
private async Task SaveSiteSettings() private async Task SaveSiteSettings()
{ {
try try
{ {
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true); settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success); AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message); await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error); AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
} }
} }
private async Task DeleteLogs()
{
try
{
await LogService.DeleteLogsAsync(PageState.Site.SiteId);
await GetLogs();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Logs {Error}", ex.Message);
AddModuleMessage(Localizer["Error.DeleteLogs"], MessageType.Error);
}
}
private void OnPageChange(int page) private void OnPageChange(int page)
{ {

View File

@ -32,7 +32,7 @@
<div class="row mb-1 align-items-center"> <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> <Label Class="col-sm-3" For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
<div class="col-sm-9"> <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> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -306,10 +306,9 @@
_languages = _languages.OrderBy(item => item.Name).ToList(); _languages = _languages.OrderBy(item => item.Name).ToList();
} }
// Group modules by PageId // get distinct pages where module exists
// Get distinct PageIds where modules are present
var distinctPageIds = PageState.Modules 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) .Select(md => md.PageId)
.Distinct(); .Distinct();

View File

@ -50,7 +50,7 @@ else
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
</Header> </Header>
<Row> <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> <td>
@if (context.AssemblyName != Constants.ClientId) @if (context.AssemblyName != Constants.ClientId)
{ {

View File

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

View File

@ -198,12 +198,6 @@
</div> </div>
</div> </div>
</TabPanel> </TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings">
@_themeSettingsComponent
</TabPanel>
}
</TabStrip> </TabStrip>
<br /> <br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
@ -238,9 +232,6 @@
private string _bodycontent; private string _bodycontent;
private string _permissions = null; private string _permissions = null;
private PermissionGrid _permissionGrid; private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment _themeSettingsComponent { get; set; }
private bool _refresh = false; private bool _refresh = false;
protected Page _parent = null; protected Page _parent = null;
protected Dictionary<string, string> _icons; protected Dictionary<string, string> _icons;
@ -281,7 +272,6 @@
} }
_effectivedate = Utilities.UtcAsLocalDate(PageState.Page.EffectiveDate); _effectivedate = Utilities.UtcAsLocalDate(PageState.Page.EffectiveDate);
_expirydate = Utilities.UtcAsLocalDate(PageState.Page.ExpiryDate); _expirydate = Utilities.UtcAsLocalDate(PageState.Page.ExpiryDate);
ThemeSettings();
_initialized = true; _initialized = true;
} }
else else
@ -324,7 +314,6 @@
_themetype = (string)e.Value; _themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype); _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = _containers.First().TypeName; _containertype = _containers.First().TypeName;
ThemeSettings();
StateHasChanged(); StateHasChanged();
// if theme chosen is different than default site theme, display warning message to user // 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() private async Task SavePage()
{ {
validated = true; validated = true;
@ -385,45 +352,34 @@
page.ParentId = Int32.Parse(_parentid); page.ParentId = Int32.Parse(_parentid);
} }
// path can be a link to an external url if (string.IsNullOrEmpty(_path))
if (!_path.Contains("://"))
{ {
if (string.IsNullOrEmpty(_path)) _path = _name;
{ }
_path = _name;
}
(_path, string parameters) = Utilities.ParsePath(_path); if (_path.Contains("/"))
{
if (_path.Contains("/")) if (_path.EndsWith("/") && _path != "/")
{ {
if (_path.EndsWith("/") && _path != "/") _path = _path.Substring(0, _path.Length - 1);
{
_path = _path.Substring(0, _path.Length - 1);
}
_path = _path.Substring(_path.LastIndexOf("/") + 1);
} }
if (_parentid == "-1") _path = _path.Substring(_path.LastIndexOf("/") + 1);
{ }
page.Path = Utilities.GetFriendlyUrl(_path); if (_parentid == "-1")
} {
else page.Path = Utilities.GetFriendlyUrl(_path);
{
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId);
if (parent.Path == string.Empty)
{
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
}
else
{
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
}
}
page.Path += parameters;
} }
else else
{ {
page.Path = _path; Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId);
if (parent.Path == string.Empty)
{
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
}
else
{
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
}
} }
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
@ -493,18 +449,11 @@
await logger.LogInformation("Page Added {Page}", page); await logger.LogInformation("Page Added {Page}", page);
if (!string.IsNullOrEmpty(PageState.ReturnUrl)) if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{ {
NavigationManager.NavigateTo(PageState.ReturnUrl, true); NavigationManager.NavigateTo(page.Path, true); // redirect to page added and reload
} }
else else
{ {
if (!page.Path.Contains("://")) NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
{
NavigationManager.NavigateTo(page.Path); // redirect to new page created
}
else
{
NavigationManager.NavigateTo(NavigateUrl("admin/pages"));
}
} }
} }
else else

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Admin.Pages @namespace Oqtane.Modules.Admin.Pages
@using Oqtane.Interfaces @using Oqtane.Interfaces
@using System.Globalization
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IPageService PageService @inject IPageService PageService
@ -362,7 +363,7 @@
_parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId); _parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
} }
_children = new List<Page>(); _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)) if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{ {
@ -380,7 +381,7 @@
} }
else else
{ {
if (_path.Contains("/") & !_path.Contains("://")) if (_path.Contains("/"))
{ {
_path = _path.Substring(_path.LastIndexOf("/") + 1); _path = _path.Substring(_path.LastIndexOf("/") + 1);
} }
@ -529,45 +530,34 @@
_page.ParentId = Int32.Parse(_parentid); _page.ParentId = Int32.Parse(_parentid);
} }
// path can be a link to an external url if (string.IsNullOrEmpty(_path))
if (!_path.Contains("://"))
{ {
if (string.IsNullOrEmpty(_path)) _path = _name;
{ }
_path = _name;
}
(_path, string parameters) = Utilities.ParsePath(_path); if (_path.Contains("/"))
{
if (_path.Contains("/")) if (_path.EndsWith("/") && _path != "/")
{ {
if (_path.EndsWith("/") && _path != "/") _path = _path.Substring(0, _path.Length - 1);
{
_path = _path.Substring(0, _path.Length - 1);
}
_path = _path.Substring(_path.LastIndexOf("/") + 1);
} }
if (_parentid == "-1") _path = _path.Substring(_path.LastIndexOf("/") + 1);
{ }
_page.Path = Utilities.GetFriendlyUrl(_path); if (_parentid == "-1")
} {
else _page.Path = Utilities.GetFriendlyUrl(_path);
{
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
if (parent.Path == string.Empty)
{
_page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
}
else
{
_page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
}
}
_page.Path += parameters;
} }
else else
{ {
_page.Path = _path; Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
if (parent.Path == string.Empty)
{
_page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
}
else
{
_page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
}
} }
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
@ -654,18 +644,11 @@
await logger.LogInformation("Page Saved {Page}", _page); await logger.LogInformation("Page Saved {Page}", _page);
if (!string.IsNullOrEmpty(PageState.ReturnUrl)) if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{ {
NavigationManager.NavigateTo(PageState.ReturnUrl, true); NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
} }
else else
{ {
if (!_page.Path.Contains("://")) NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
{
NavigationManager.NavigateTo(NavigateUrl(), true); // redirect to page being edited
}
else
{
NavigationManager.NavigateTo(NavigateUrl("admin/pages"));
}
} }
} }
else else

View File

@ -17,7 +17,7 @@
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
</Header> </Header>
<Row> <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><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><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> <td>@(new string('-', context.Level * 2))@(context.Name)</td>

View File

@ -22,7 +22,7 @@ else
<th>@Localizer["Order"]</th> <th>@Localizer["Order"]</th>
</Header> </Header>
<Row> <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><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.Name</td>
<td>@context.Title</td> <td>@context.Title</td>

View File

@ -20,9 +20,9 @@ else
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
</Header> </Header>
<Row> <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><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> <td>@context.Name</td>
</Row> </Row>
</Pager> </Pager>

View File

@ -319,7 +319,7 @@
<div class="row mb-1 align-items-center"> <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> <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"> <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.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option> <option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option> <option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
@ -337,7 +337,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<select id="prerender" class="form-select" @bind="@_prerender" required> <select id="prerender" class="form-select" @bind="@_prerender" required>
<option value="True">@SharedLocalizer["Yes"]</option> <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() private async Task SaveSite()
{ {
validated = true; validated = true;

View File

@ -26,6 +26,12 @@
<input id="osversion" class="form-control" @bind="@_osversion" readonly /> <input id="osversion" class="form-control" @bind="@_osversion" readonly />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="process" HelpText="Indicates if the current process is 32 bit or 64 bit" ResourceKey="Process">Process: </Label>
<div class="col-sm-9">
<input id="process" class="form-control" @bind="@_process" readonly />
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label> <Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -62,12 +68,6 @@
<input id="servertime" class="form-control" @bind="@_servertime" readonly /> <input id="servertime" class="form-control" @bind="@_servertime" readonly />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tickcount" HelpText="Amount Of Time The Service Has Been Available And Operational" ResourceKey="TickCount">Service Uptime: </Label>
<div class="col-sm-9">
<input id="tickcount" class="form-control" @bind="@_tickcount" readonly />
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label> <Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -165,13 +165,13 @@
private string _version = string.Empty; private string _version = string.Empty;
private string _clrversion = string.Empty; private string _clrversion = string.Empty;
private string _osversion = string.Empty; private string _osversion = string.Empty;
private string _machinename = string.Empty; private string _process = string.Empty;
private string _machinename = string.Empty;
private string _ipaddress = string.Empty; private string _ipaddress = string.Empty;
private string _environment = string.Empty; private string _environment = string.Empty;
private string _contentrootpath = string.Empty; private string _contentrootpath = string.Empty;
private string _webrootpath = string.Empty; private string _webrootpath = string.Empty;
private string _servertime = string.Empty; private string _servertime = string.Empty;
private string _tickcount = string.Empty;
private string _workingset = string.Empty; private string _workingset = string.Empty;
private string _installationid = string.Empty; private string _installationid = string.Empty;
@ -192,13 +192,13 @@
{ {
_clrversion = systeminfo["CLRVersion"].ToString(); _clrversion = systeminfo["CLRVersion"].ToString();
_osversion = systeminfo["OSVersion"].ToString(); _osversion = systeminfo["OSVersion"].ToString();
_machinename = systeminfo["MachineName"].ToString(); _process = systeminfo["Process"].ToString();
_machinename = systeminfo["MachineName"].ToString();
_ipaddress = systeminfo["IPAddress"].ToString(); _ipaddress = systeminfo["IPAddress"].ToString();
_environment = systeminfo["Environment"].ToString(); _environment = systeminfo["Environment"].ToString();
_contentrootpath = systeminfo["ContentRootPath"].ToString(); _contentrootpath = systeminfo["ContentRootPath"].ToString();
_webrootpath = systeminfo["WebRootPath"].ToString(); _webrootpath = systeminfo["WebRootPath"].ToString();
_servertime = systeminfo["ServerTime"].ToString() + " UTC"; _servertime = systeminfo["ServerTime"].ToString() + " UTC";
_tickcount = TimeSpan.FromMilliseconds(Convert.ToInt64(systeminfo["TickCount"].ToString())).ToString();
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB"; _workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
} }

View File

@ -29,7 +29,7 @@ else
<th>&nbsp;</th> <th>&nbsp;</th>
</Header> </Header>
<Row> <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> <td>
@if (context.AssemblyName != Constants.ClientId) @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> <button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
} }
</td> </td>
<td></td>
</Row> </Row>
</Pager> </Pager>
} }

View File

@ -37,7 +37,7 @@ else
<th>@Localizer["Requested"]</th> <th>@Localizer["Requested"]</th>
</Header> </Header>
<Row> <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><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> <td>
<a href="@Utilities.TenantUrl(PageState.Alias, context.Url)">@context.Url</a> <a href="@Utilities.TenantUrl(PageState.Alias, context.Url)">@context.Url</a>

View File

@ -226,11 +226,6 @@
user.Password = _password; user.Password = _password;
user.Email = email; user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname; 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)); user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));

View File

@ -32,13 +32,13 @@ else
</Header> </Header>
<Row> <Row>
<td> <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>
<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" /> <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>
<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>
<td>@context.User.Username</td> <td>@context.User.Username</td>
<td>@context.User.DisplayName</td> <td>@context.User.DisplayName</td>

View File

@ -43,7 +43,7 @@ else
<th>@Localizer["Created"]</th> <th>@Localizer["Created"]</th>
</Header> </Header>
<Row> <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>@context.IPAddress</td>
<td> <td>
@if (context.UserId != null) @if (context.UserId != null)
@ -69,14 +69,20 @@ else
</select> </select>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <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> <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"> <div class="col-sm-9">
<textarea id="filter" class="form-control" @bind="@_filter" rows="3"></textarea> <textarea id="filter" class="form-control" @bind="@_filter" rows="3"></textarea>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" /> <input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
</div> </div>
@ -103,7 +109,8 @@ else
private int _page = 1; private int _page = 1;
private List<Visitor> _visitors; private List<Visitor> _visitors;
private string _tracking; private string _tracking;
private string _filter = ""; private int _duration = 5;
private string _filter = "";
private int _retention = 30; private int _retention = 30;
private string _correlation = "true"; private string _correlation = "true";
@ -128,7 +135,8 @@ else
_tracking = PageState.Site.VisitorTracking.ToString(); _tracking = PageState.Site.VisitorTracking.ToString();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter); _duration = int.Parse(SettingService.GetSetting(settings, "VisitorDuration", "5"));
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
_retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30")); _retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30"));
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true"); _correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
} }
@ -179,7 +187,8 @@ else
await SiteService.UpdateSiteAsync(site); await SiteService.UpdateSiteAsync(site);
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true); 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, "VisitorRetention", _retention.ToString(), true);
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true); settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);

View File

@ -35,11 +35,11 @@
{ {
if (Disabled) if (Disabled)
{ {
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button> <button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
} }
else else
{ {
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button> <button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
} }
} }
} }
@ -54,7 +54,7 @@ else
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">@Header</h5> <h5 class="modal-title">@Header</h5>
<form method="post" @formname="@($"ActionDialogCloseForm{Id}")" @onsubmit="DisplayModal" data-enhance> <form method="post" @formname="@($"ActionDialogCloseForm{Id}")" @onsubmit="DisplayModal" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn-close" aria-label="Close"></button> <button type="submit" class="btn-close" aria-label="Close"></button>
</form> </form>
</div> </div>
@ -65,12 +65,12 @@ else
@if (!string.IsNullOrEmpty(Action)) @if (!string.IsNullOrEmpty(Action))
{ {
<form method="post" @formname="@($"ActionDialogConfirmForm{Id}")" @onsubmit="Confirm" data-enhance> <form method="post" @formname="@($"ActionDialogConfirmForm{Id}")" @onsubmit="Confirm" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button> <button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button>
</form> </form>
} }
<form method="post" @formname="@($"ActionDialogCancelForm{Id}")" @onsubmit="DisplayModal" data-enhance> <form method="post" @formname="@($"ActionDialogCancelForm{Id}")" @onsubmit="DisplayModal" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn btn-secondary">@SharedLocalizer["Cancel"]</button> <button type="submit" class="btn btn-secondary">@SharedLocalizer["Cancel"]</button>
</form> </form>
</div> </div>
@ -83,13 +83,13 @@ else
{ {
if (Disabled) if (Disabled)
{ {
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button> <button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
} }
else else
{ {
<form method="post" @formname="@($"ActionDialogActionForm{Id}")" @onsubmit="DisplayModal" data-enhance> <form method="post" @formname="@($"ActionDialogActionForm{Id}")" @onsubmit="DisplayModal" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <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> </form>
} }
} }
@ -101,6 +101,8 @@ else
private bool _editmode = false; private bool _editmode = false;
private bool _authorized = false; private bool _authorized = false;
private string _iconSpan = string.Empty; private string _iconSpan = string.Empty;
private string _openIconSpan = string.Empty;
private string _openText = string.Empty;
[Parameter] [Parameter]
public string Header { get; set; } // required public string Header { get; set; } // required
@ -138,6 +140,9 @@ else
[Parameter] [Parameter]
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon 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] [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 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; Text = Action;
} }
_openText = Text;
if (string.IsNullOrEmpty(Class)) if (string.IsNullOrEmpty(Class))
{ {
Class = "btn btn-success"; Class = "btn btn-success";
@ -169,11 +176,17 @@ else
if (!string.IsNullOrEmpty(IconName)) if (!string.IsNullOrEmpty(IconName))
{ {
if (IconOnly)
{
_openText = string.Empty;
}
if (!IconName.Contains(" ")) if (!IconName.Contains(" "))
{ {
IconName = "oi oi-" + IconName; 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); Text = Localize(nameof(Text), Text);

View File

@ -99,7 +99,7 @@
if (!string.IsNullOrEmpty(Text)) if (!string.IsNullOrEmpty(Text))
{ {
_text = Localize(nameof(Text), _text); _text = Localize(nameof(Text), Text);
} }
else else
{ {

View File

@ -6,23 +6,24 @@
{ {
<div class="@_classname alert-dismissible fade show mb-3" role="alert"> <div class="@_classname alert-dismissible fade show mb-3" role="alert">
@((MarkupString)Message) @((MarkupString)Message)
@if (PageState != null) @if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
@if (Type == MessageType.Error && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) <NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
{ }
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink> @if (ModuleState.RenderMode == RenderModes.Static)
} {
<form method="post" @onsubmit="DismissModal" @formname="@_formname" data-enhance> <a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> }
<button type="submit" class="btn-close" aria-label="Close"></button> else
</form> {
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
} }
</div> </div>
} }
@code { @code {
private string _message = string.Empty;
private string _classname = string.Empty; private string _classname = string.Empty;
private string _formname = "ModuleMessageForm";
[Parameter] [Parameter]
public string Message { get; set; } public string Message { get; set; }
@ -30,32 +31,13 @@
[Parameter] [Parameter]
public MessageType Type { get; set; } public MessageType Type { get; set; }
public void RefreshMessage(string message, MessageType type) [Parameter]
{ public RenderModeBoundary Parent { get; set; }
Message = message;
Type = type;
UpdateClassName();
StateHasChanged();
}
protected override void OnInitialized()
{
if (ModuleState != null)
{
_formname += ModuleState.PageModuleId.ToString();
}
}
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
UpdateClassName(); _message = Message;
} if (!string.IsNullOrEmpty(_message))
private void UpdateClassName()
{
if (!string.IsNullOrEmpty(Message))
{ {
_classname = GetMessageType(Type); _classname = GetMessageType(Type);
} }
@ -82,9 +64,15 @@
return classname; return classname;
} }
private void CloseMessage(MouseEventArgs e)
private void DismissModal()
{ {
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"> <ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> <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> </li>
@if (_pages > _displayPages && _displayPages > 1) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")"> <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>
} }
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> <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> </li>
@for (int i = _startPage; i <= _endPage; i++) @for (int i = _startPage; i <= _endPage; i++)
{ {
@ -40,30 +40,30 @@
if (pager == _page) if (pager == _page)
{ {
<li class="page-item app-pager-pointer active"> <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> </li>
} }
else else
{ {
<li class="page-item app-pager-pointer"> <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>
} }
} }
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")"> <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> </li>
@if (_pages > _displayPages && _displayPages > 1) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")"> <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>
} }
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")"> <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>
<li class="page-item disabled"> <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> </li>
</ul> </ul>
} }
@ -73,7 +73,7 @@
@if (!string.IsNullOrEmpty(SearchProperties)) @if (!string.IsNullOrEmpty(SearchProperties))
{ {
<form method="post" autocomplete="off" @formname="PagerForm" @onsubmit="Search" data-enhance> <form method="post" autocomplete="off" @formname="PagerForm" @onsubmit="Search" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<div class="input-group my-3"> <div class="input-group my-3">
<input type="text" id="pagersearch" name="_search" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" /> <input type="text" id="pagersearch" name="_search" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
<button type="submit" class="btn btn-primary">@SharedLocalizer["Search"]</button> <button type="submit" class="btn btn-primary">@SharedLocalizer["Search"]</button>
@ -86,16 +86,16 @@
{ {
<ul class="pagination justify-content-center my-2"> <ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> <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> </li>
@if (_pages > _displayPages && _displayPages > 1) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")"> <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>
} }
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> <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> </li>
@for (int i = _startPage; i <= _endPage; i++) @for (int i = _startPage; i <= _endPage; i++)
{ {
@ -103,30 +103,30 @@
if (pager == _page) if (pager == _page)
{ {
<li class="page-item app-pager-pointer active"> <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> </li>
} }
else else
{ {
<li class="page-item app-pager-pointer"> <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>
} }
} }
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")"> <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> </li>
@if (_pages > _displayPages && _displayPages > 1) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")"> <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>
} }
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")"> <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>
<li class="page-item disabled"> <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> </li>
</ul> </ul>
} }
@ -202,16 +202,16 @@
{ {
<ul class="pagination justify-content-center my-2"> <ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> <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> </li>
@if (_pages > _displayPages && _displayPages > 1) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")"> <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>
} }
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> <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> </li>
@for (int i = _startPage; i <= _endPage; i++) @for (int i = _startPage; i <= _endPage; i++)
{ {
@ -219,30 +219,30 @@
if (pager == _page) if (pager == _page)
{ {
<li class="page-item app-pager-pointer active"> <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> </li>
} }
else else
{ {
<li class="page-item app-pager-pointer"> <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>
} }
} }
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")"> <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> </li>
@if (_pages > _displayPages && _displayPages > 1) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")"> <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>
} }
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")"> <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>
<li class="page-item disabled"> <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> </li>
</ul> </ul>
} }
@ -250,16 +250,16 @@
{ {
<ul class="pagination justify-content-center my-2"> <ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> <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> </li>
@if (_pages > _displayPages && _displayPages > 1) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")"> <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>
} }
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> <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> </li>
@for (int i = _startPage; i <= _endPage; i++) @for (int i = _startPage; i <= _endPage; i++)
{ {
@ -267,30 +267,30 @@
if (pager == _page) if (pager == _page)
{ {
<li class="page-item app-pager-pointer active"> <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> </li>
} }
else else
{ {
<li class="page-item app-pager-pointer"> <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>
} }
} }
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")"> <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> </li>
@if (_pages > _displayPages && _displayPages > 1) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")"> <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>
} }
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")"> <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>
<li class="page-item disabled"> <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> </li>
</ul> </ul>
} }

View File

@ -63,8 +63,7 @@
</span> </span>
} }
</div> </div>
<div @ref="@_editorElement"> <div @ref="@_editorElement"></div>
</div>
</div> </div>
</div> </div>
</TabPanel> </TabPanel>
@ -91,11 +90,11 @@
</div> </div>
@if (ReadOnly) @if (ReadOnly)
{ {
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea> <textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
} }
else else
{ {
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea> <textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
} }
</TabPanel> </TabPanel>
} }
@ -104,17 +103,26 @@
</div> </div>
@code { @code {
private bool _initialized = false;
private RichTextEditorInterop interop;
private FileManager _fileManager;
private string _activetab = "Rich";
private ElementReference _editorElement; private ElementReference _editorElement;
private ElementReference _toolBar; private ElementReference _toolBar;
private bool _richfilemanager = false; private bool _richfilemanager = false;
private FileManager _fileManager;
private string _richhtml = string.Empty; private string _richhtml = string.Empty;
private string _originalrichhtml = string.Empty; private string _originalrichhtml = string.Empty;
private bool _rawfilemanager = false; private bool _rawfilemanager = false;
private string _rawhtmlid = "RawHtmlEditor_" + Guid.NewGuid().ToString("N");
private string _rawhtml = string.Empty; private string _rawhtml = string.Empty;
private string _originalrawhtml = string.Empty; private string _originalrawhtml = string.Empty;
private string _message = string.Empty; private string _message = string.Empty;
private string _activetab = "Rich"; private bool _contentchanged = false;
private int _editorIndex;
[Parameter] [Parameter]
public string Content { get; set; } public string Content { get; set; }
@ -123,7 +131,7 @@
public bool ReadOnly { get; set; } = false; public bool ReadOnly { get; set; } = false;
[Parameter] [Parameter]
public string Placeholder { get; set; } = "Enter Your Content..."; public string Placeholder { get; set; }
[Parameter] [Parameter]
public bool AllowFileManagement { get; set; } = true; public bool AllowFileManagement { get; set; } = true;
@ -146,11 +154,20 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" }, new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js", Location = ResourceLocation.Body },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js" }, new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js", Location = ResourceLocation.Body },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js" } new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body }
}; };
protected override void OnInitialized()
{
interop = new RichTextEditorInterop(JSRuntime);
if (string.IsNullOrEmpty(Placeholder))
{
Placeholder = Localizer["Placeholder"];
}
}
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
_richhtml = Content; _richhtml = Content;
@ -158,8 +175,12 @@
_originalrawhtml = _rawhtml; // preserve for comparison later _originalrawhtml = _rawhtml; // preserve for comparison later
_originalrichhtml = ""; _originalrichhtml = "";
// Quill wraps content in <p> tags which can be used as a signal to set the active tab if (Content != _originalrawhtml)
if (!string.IsNullOrEmpty(Content) && !Content.StartsWith("<p>") && AllowRawHtml) {
_contentchanged = true; // identifies when Content parameter has changed
}
if (!AllowRichText)
{ {
_activetab = "Raw"; _activetab = "Raw";
} }
@ -171,8 +192,6 @@
if (AllowRichText) if (AllowRichText)
{ {
var interop = new RichTextEditorInterop(JSRuntime);
if (firstRender) if (firstRender)
{ {
await interop.CreateEditor( await interop.CreateEditor(
@ -182,15 +201,38 @@
Placeholder, Placeholder,
Theme, Theme,
DebugLevel); DebugLevel);
}
await interop.LoadEditorContent(_editorElement, _richhtml); await interop.LoadEditorContent(_editorElement, _richhtml);
if (string.IsNullOrEmpty(_originalrichhtml)) // preserve a copy of the content (Quill sanitizes content so we need to retrieve it from the editor as it may have been modified)
{
// preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor)
_originalrichhtml = await interop.GetHtml(_editorElement); _originalrichhtml = await interop.GetHtml(_editorElement);
_initialized = true;
} }
else
{
if (_initialized)
{
if (_contentchanged)
{
// reload editor if Content passed to component has changed
await interop.LoadEditorContent(_editorElement, _richhtml);
_originalrichhtml = await interop.GetHtml(_editorElement);
}
else
{
// preserve changed content on re-render event
var richhtml = await interop.GetHtml(_editorElement);
if (richhtml != _richhtml)
{
_richhtml = richhtml;
await interop.LoadEditorContent(_editorElement, _richhtml);
}
}
}
}
_contentchanged = false;
} }
} }
@ -218,35 +260,38 @@
else else
{ {
var richhtml = ""; var richhtml = "";
if (AllowRichText) if (AllowRichText)
{ {
// return rich text content if it has changed
var interop = new RichTextEditorInterop(JSRuntime);
richhtml = await interop.GetHtml(_editorElement); richhtml = await interop.GetHtml(_editorElement);
} }
// rich text value will only be blank if AllowRichText is disabled or the JS Interop method failed
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml) && !string.IsNullOrEmpty(_originalrichhtml))
{
return richhtml;
}
else
{
// return original raw html content
return _originalrawhtml;
}
}
}
public async Task InsertRichImage() if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml))
{ {
_message = string.Empty; // convert Quill's empty content to empty string
if (_richfilemanager) if (richhtml == "<p><br></p>")
{ {
var file = _fileManager.GetFile(); richhtml = string.Empty;
}
return richhtml;
}
else
{
// return original raw html content
return _originalrawhtml;
}
}
}
public async Task InsertRichImage()
{
_message = string.Empty;
if (_richfilemanager)
{
var file = _fileManager.GetFile();
if (file != null) if (file != null)
{ {
var interop = new RichTextEditorInterop(JSRuntime); await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name), _editorIndex);
await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name));
_richhtml = await interop.GetHtml(_editorElement); _richhtml = await interop.GetHtml(_editorElement);
_richfilemanager = false; _richfilemanager = false;
} }
@ -257,6 +302,7 @@
} }
else else
{ {
_editorIndex = await interop.GetCurrentCursor(_editorElement);
_richfilemanager = true; _richfilemanager = true;
} }
StateHasChanged(); StateHasChanged();
@ -271,7 +317,7 @@
if (file != null) if (file != null)
{ {
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
int pos = await interop.GetCaretPosition("rawhtmleditor"); int pos = await interop.GetCaretPosition(_rawhtmlid);
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\" class=\"img-fluid\">"; var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\" class=\"img-fluid\">";
_rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos); _rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
_rawfilemanager = false; _rawfilemanager = false;

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 try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeAsync<object>(
"Oqtane.RichTextEditor.insertQuillImage", "Oqtane.RichTextEditor.insertQuillImage",
quillElement, imageUrl, altText); quillElement, imageUrl, altText, editorIndex);
return Task.CompletedTask; return Task.CompletedTask;
} }
catch catch

View File

@ -37,6 +37,10 @@
content = htmltext.Content; content = htmltext.Content;
content = Utilities.FormatContent(content, PageState.Alias, "render"); content = Utilities.FormatContent(content, PageState.Alias, "render");
} }
else
{
content = "";
}
} }
} }
catch (Exception ex) 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 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 // url parameters
public virtual string UrlParametersTemplate { get; set; } public virtual string UrlParametersTemplate { get; set; }
@ -276,7 +278,6 @@ namespace Oqtane.Modules
public void AddModuleMessage(string message, MessageType type, string position) public void AddModuleMessage(string message, MessageType type, string position)
{ {
ClearModuleMessage();
RenderModeBoundary.AddModuleMessage(message, type, position); RenderModeBoundary.AddModuleMessage(message, type, position);
} }

View File

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

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -153,7 +153,7 @@
<data name="Integrated" xml:space="preserve"> <data name="Integrated" xml:space="preserve">
<value>Integrated</value> <value>Integrated</value>
</data> </data>
<data name="Encryption,Text" xml:space="preserve"> <data name="Encryption.Text" xml:space="preserve">
<value>Encryption:</value> <value>Encryption:</value>
</data> </data>
<data name="Encryption.HelpText" xml:space="preserve"> <data name="Encryption.HelpText" xml:space="preserve">

View File

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

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -210,4 +210,16 @@
<data name="Success.SaveSiteSettings" xml:space="preserve"> <data name="Success.SaveSiteSettings" xml:space="preserve">
<value>Settings Saved Successfully</value> <value>Settings Saved Successfully</value>
</data> </data>
<data name="DeleteLogs.Header" xml:space="preserve">
<value>Clear Events</value>
</data>
<data name="DeleteLogs.Message" xml:space="preserve">
<value>Are You Sure You Wish To Remove All Log Events?</value>
</data>
<data name="DeleteLogs.Text" xml:space="preserve">
<value>Clear Events</value>
</data>
<data name="Error.DeleteLogs" xml:space="preserve">
<value>Error Deleting Log Events</value>
</data>
</root> </root>

View File

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

View File

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

View File

@ -277,10 +277,10 @@
<value>UI Component Settings</value> <value>UI Component Settings</value>
</data> </data>
<data name="Prerender.HelpText" xml:space="preserve"> <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>
<data name="Prerender.Text" xml:space="preserve"> <data name="Prerender.Text" xml:space="preserve">
<value>Prerender? </value> <value>Prerender: </value>
</data> </data>
<data name="RenderMode.HelpText" xml:space="preserve"> <data name="RenderMode.HelpText" xml:space="preserve">
<value>The default render mode for the site</value> <value>The default render mode for the site</value>

View File

@ -255,12 +255,6 @@
<data name="MachineName.Text" xml:space="preserve"> <data name="MachineName.Text" xml:space="preserve">
<value>Machine Name:</value> <value>Machine Name:</value>
</data> </data>
<data name="TickCount.HelpText" xml:space="preserve">
<value>Amount Of Time The Service Has Been Available And Operational</value>
</data>
<data name="TickCount.Text" xml:space="preserve">
<value>Service Uptime:</value>
</data>
<data name="WebRootPath.HelpText" xml:space="preserve"> <data name="WebRootPath.HelpText" xml:space="preserve">
<value>Server Web Root Path</value> <value>Server Web Root Path</value>
</data> </data>
@ -294,4 +288,10 @@
<data name="Error.ClearLog" xml:space="preserve"> <data name="Error.ClearLog" xml:space="preserve">
<value>Ann Error Occurred Clearing The System Log</value> <value>Ann Error Occurred Clearing The System Log</value>
</data> </data>
<data name="Process.HelpText" xml:space="preserve">
<value>Indicates if the current process is 32 bit or 64 bit</value>
</data>
<data name="Process.Text" xml:space="preserve">
<value>Process: </value>
</data>
</root> </root>

View File

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

View File

@ -184,7 +184,7 @@
<value>Number of days of visitor activity to retain</value> <value>Number of days of visitor activity to retain</value>
</data> </data>
<data name="Retention.Text" xml:space="preserve"> <data name="Retention.Text" xml:space="preserve">
<value>Retention (Days):</value> <value>Retention:</value>
</data> </data>
<data name="Correlation.HelpText" xml:space="preserve"> <data name="Correlation.HelpText" xml:space="preserve">
<value>Indicate if new visitors to this site should be correlated based on their IP Address</value> <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"> <data name="Correlation.Text" xml:space="preserve">
<value>Correlate Visitors?</value> <value>Correlate Visitors?</value>
</data> </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> </root>

View File

@ -126,4 +126,7 @@
<data name="Message.Require.Image" xml:space="preserve"> <data name="Message.Require.Image" xml:space="preserve">
<value>You Must Select An Image To Insert</value> <value>You Must Select An Image To Insert</value>
</data> </data>
<data name="Placeholder" xml:space="preserve">
<value>Enter Your Content...</value>
</data>
</root> </root>

View File

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

View File

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

@ -29,6 +29,13 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task<Log> GetLogAsync(int logId); Task<Log> GetLogAsync(int logId);
/// <summary>
/// Clear the entire logs of the given site.
/// </summary>
/// <param name="siteId"></param>
/// <returns></returns>
Task DeleteLogsAsync(int siteId);
/// <summary> /// <summary>
/// Creates a new log entry /// Creates a new log entry
/// </summary> /// </summary>

View File

@ -35,6 +35,11 @@ namespace Oqtane.Services
return await GetJsonAsync<Log>($"{Apiurl}/{logId}"); return await GetJsonAsync<Log>($"{Apiurl}/{logId}");
} }
public async Task DeleteLogsAsync(int siteId)
{
await DeleteAsync($"{Apiurl}?siteid={siteId}");
}
public async Task Log(int? pageId, int? moduleId, int? userId, string category, string feature, LogFunction function, LogLevel level, Exception exception, string message, params object[] args) public async Task Log(int? pageId, int? moduleId, int? userId, string category, string feature, LogFunction function, LogLevel level, Exception exception, string message, params object[] args)
{ {
await Log(null, pageId, moduleId, userId, category, feature, function, level, exception, message, args); await Log(null, pageId, moduleId, userId, category, feature, function, level, exception, message, args);

View File

@ -33,7 +33,7 @@ namespace Oqtane.Services
public async Task<Profile> UpdateProfileAsync(Profile profile) public async Task<Profile> UpdateProfileAsync(Profile profile)
{ {
return await PutJsonAsync<Profile>($"{Apiurl}/{profile.SiteId}", profile); return await PutJsonAsync<Profile>($"{Apiurl}/{profile.ProfileId}", profile);
} }
public async Task DeleteProfileAsync(int profileId) public async Task DeleteProfileAsync(int profileId)
{ {

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Shared; using Oqtane.Shared;
using System; using System;
using System.Globalization;
namespace Oqtane.Services namespace Oqtane.Services
{ {
@ -18,7 +19,7 @@ namespace Oqtane.Services
public async Task<List<Visitor>> GetVisitorsAsync(int siteId, DateTime fromDate) 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(); return visitors.OrderByDescending(item => item.VisitedOn).ToList();
} }

View File

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

View File

@ -10,6 +10,6 @@
} }
else 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.Shared;
using Oqtane.UI; using Oqtane.UI;
using System.Net; using System.Net;
using static System.Runtime.InteropServices.JavaScript.JSType;
using Microsoft.Extensions.Localization;
// ReSharper disable UnassignedGetOnlyAutoProperty // ReSharper disable UnassignedGetOnlyAutoProperty
// ReSharper disable MemberCanBePrivate.Global // ReSharper disable MemberCanBePrivate.Global
@ -20,6 +22,7 @@ namespace Oqtane.Themes.Controls
[Inject] public NavigationManager NavigationManager { get; set; } [Inject] public NavigationManager NavigationManager { get; set; }
[Inject] public IPageModuleService PageModuleService { get; set; } [Inject] public IPageModuleService PageModuleService { get; set; }
[Inject] public IModuleService ModuleService { get; set; } [Inject] public IModuleService ModuleService { get; set; }
[Inject] public IStringLocalizer<ModuleActionsBase> Localizer { get; set; }
[Parameter] public PageState PageState { get; set; } [Parameter] public PageState PageState { get; set; }
[Parameter] public Module ModuleState { 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)) 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)) 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 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) if (ModuleState.ModuleDefinition != null && ModuleState.ModuleDefinition.IsPortable)
{ {
actionList.Add(new ActionViewModel { Name = "" }); 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.CloudUpload, Name = Localizer["ImportContent"], 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.CloudDownload, Name = Localizer["ExportContent"], Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Export") });
} }
actionList.Add(new ActionViewModel { Name = "" }); actionList.Add(new ActionViewModel { Name = "" });
if (ModuleState.PaneModuleIndex > 0) 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) if (ModuleState.PaneModuleIndex > 0)

View File

@ -6,13 +6,13 @@
@if (ShowLanguageSwitcher) @if (ShowLanguageSwitcher)
{ {
<LanguageSwitcher DropdownAlignment="@LanguageDropdownAlignment" /> <LanguageSwitcher ButtonClass="@ButtonClass" DropdownAlignment="@LanguageDropdownAlignment" />
} }
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))) @if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
{ {
<form method="post" class="app-form-inline" @formname="EditModeForm" @onsubmit="@(async () => await ToggleEditMode(PageState.EditMode))" data-enhance> <form method="post" class="app-form-inline" @formname="EditModeForm" @onsubmit="@(async () => await ToggleEditMode(PageState.EditMode))" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
@if (PageState.EditMode) @if (PageState.EditMode)
{ {
<button type="submit" class="btn @ButtonClass active" aria-pressed="true" autocomplete="off"> <button type="submit" class="btn @ButtonClass active" aria-pressed="true" autocomplete="off">
@ -36,7 +36,7 @@
} }
else 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 ILogService LoggingService
@inject IStringLocalizer<ControlPanelInteractive> Localizer @inject IStringLocalizer<ControlPanelInteractive> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @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"> <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> <span class="oi oi-cog"></span>
@ -93,9 +94,13 @@
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<label for="Module" class="control-label">@Localizer["Module.Manage"]</label> <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="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> </select>
@if (_moduleType == "new") @if (_moduleType == "new")
{ {
@ -138,7 +143,7 @@
} }
else 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> <option value="-">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page p in _pages) @foreach (Page p in _pages)
{ {
@ -211,7 +216,7 @@
<div class="row d-flex"> <div class="row d-flex">
<div class="col"> <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> </div>
</div> </div>
@ -291,7 +296,7 @@
_containerType = PageState.Site.DefaultContainerType; _containerType = PageState.Site.DefaultContainerType;
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); _allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList(); _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(); StateHasChanged();
} }
private void ModuleTypeChanged(ChangeEventArgs e)
{
_moduleType = (string)e.Value;
_pageId = "-";
_moduleId = "-";
}
private void PageChanged(ChangeEventArgs e) private void PageChanged(ChangeEventArgs e)
{ {
_pageId = (string)e.Value; _pageId = (string)e.Value;
@ -341,7 +353,8 @@
{ {
_modules = PageState.Modules _modules = PageState.Modules
.Where(module => module.PageId == int.Parse(_pageId) && .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(); .ToList();
} }
_moduleId = "-"; _moduleId = "-";
@ -354,6 +367,7 @@
{ {
if ((_moduleType == "new" && _moduleDefinitionName != "-") || (_moduleType != "new" && _moduleId != "-")) if ((_moduleType == "new" && _moduleDefinitionName != "-") || (_moduleType != "new" && _moduleId != "-"))
{ {
var newModuleId = _moduleId != "-" ? int.Parse(_moduleId) : 0;
if (_moduleType == "new") if (_moduleType == "new")
{ {
Module module = new Module(); Module module = new Module();
@ -361,33 +375,37 @@
module.PageId = PageState.Page.PageId; module.PageId = PageState.Page.PageId;
module.ModuleDefinitionName = _moduleDefinitionName; module.ModuleDefinitionName = _moduleDefinitionName;
module.AllPages = false; module.AllPages = false;
module.PermissionList = GenerateDefaultPermissions(module.SiteId);
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 = await ModuleService.AddModuleAsync(module); 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 var pageModule = new PageModule
{ {
PageId = PageState.Page.PageId, PageId = PageState.Page.PageId,
ModuleId = int.Parse(_moduleId), ModuleId = newModuleId,
Title = _title Title = _title
}; };
if (pageModule.Title == "") if (string.IsNullOrEmpty(pageModule.Title))
{ {
if (_moduleType == "new") if (_moduleType == "new")
{ {
@ -412,9 +430,16 @@
await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane);
await UpdateSettingsAsync(); await UpdateSettingsAsync();
_message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>"; if (PageState.RenderMode == RenderModes.Interactive)
_title = ""; {
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, "")); _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 else
{ {
@ -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) 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)) 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 System.Globalization
@using Microsoft.AspNetCore.Localization @using Microsoft.AspNetCore.Localization
@using Microsoft.AspNetCore.Http
@using Oqtane.Models @using Oqtane.Models
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@inject ILanguageService LanguageService @inject ILanguageService LanguageService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@if (_supportedCultures?.Count() > 1) @if (_supportedCultures?.Count() > 1)
{ {
<div class="btn-group pe-1" role="group"> <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> <span class="oi oi-globe"></span>
</button> </button>
<div class="dropdown-menu @MenuAlignment" aria-labelledby="btnCultures"> <div class="dropdown-menu @MenuAlignment" aria-labelledby="btnCultures">
@foreach (var culture in _supportedCultures) @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>
</div> </div>
@ -23,9 +31,15 @@
@code{ @code{
private IEnumerable<Culture> _supportedCultures; private IEnumerable<Culture> _supportedCultures;
private string MenuAlignment = string.Empty;
[Parameter] [Parameter]
public string DropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right 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() protected override void OnParametersSet()
{ {
@ -33,16 +47,26 @@
var languages = PageState.Languages; var languages = PageState.Languages;
_supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name }); _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) private async Task SetCultureAsync(string culture)
{ {
if (culture != CultureInfo.CurrentUICulture.Name) if (culture != CultureInfo.CurrentUICulture.Name)
{ {
var interop = new Interop(JSRuntime);
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
var interop = new Interop(JSRuntime);
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360); await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
} }
} }

View File

@ -16,7 +16,7 @@
else else
{ {
<form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm"> <form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm">
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<input type="hidden" name="returnurl" value="@returnurl" /> <input type="hidden" name="returnurl" value="@returnurl" />
<button type="submit" class="btn btn-primary">@Localizer["Logout"]</button> <button type="submit" class="btn btn-primary">@Localizer["Logout"]</button>
</form> </form>

View File

@ -121,15 +121,15 @@
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
if (_login != "-") if (_login != "-")
{ {
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login, true); settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login);
} }
if (_register != "-") if (_register != "-")
{ {
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register, true); settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register);
} }
if (_footer != "-") if (_footer != "-")
{ {
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Footer", _footer, true); settings = SettingService.SetSetting(settings, GetType().Namespace + ":Footer", _footer);
} }
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
} }

View File

@ -3,8 +3,6 @@
@using Oqtane.Shared @using Oqtane.Shared
@inject SiteState SiteState @inject SiteState SiteState
@implements IDisposable @implements IDisposable
@* the following StreamRendering attribute is required - if it is removed the framework will not render the content in static rendering *@
@attribute [StreamRendering]
@if (!string.IsNullOrEmpty(_title)) @if (!string.IsNullOrEmpty(_title))
{ {

View File

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

View File

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

View File

@ -3,8 +3,6 @@
@inject IInstallationService InstallationService @inject IInstallationService InstallationService
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject SiteState SiteState @inject SiteState SiteState
@* the following StreamRendering attribute is required - if it is removed the framework will not render the content in static rendering *@
@attribute [StreamRendering]
@if (_initialized) @if (_initialized)
{ {

View File

@ -1,6 +1,7 @@
@using System.Diagnostics.CodeAnalysis @using System.Diagnostics.CodeAnalysis
@using System.Net @using System.Net
@using Microsoft.AspNetCore.Http @using Microsoft.AspNetCore.Http
@using System.Globalization
@namespace Oqtane.UI @namespace Oqtane.UI
@inject AuthenticationStateProvider AuthenticationStateProvider @inject AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState @inject SiteState SiteState
@ -103,7 +104,7 @@
_error = ""; _error = "";
Route route = new Route(_absoluteUri, SiteState.Alias.Path); 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 action = route.Action;
var querystring = Utilities.ParseQueryString(route.Query); var querystring = Utilities.ParseQueryString(route.Query);
@ -124,12 +125,20 @@
{ {
if (querystring.ContainsKey("reload") && querystring["reload"] == "post") if (querystring.ContainsKey("reload") && querystring["reload"] == "post")
{ {
// post back so that the cookies are set correctly - required on any change to the principal if (PageState.RenderMode == RenderModes.Interactive)
var interop = new Interop(JSRuntime); {
var fields = new { returnurl = "/" + NavigationManager.ToBaseRelativePath(_absoluteUri) }; // post back so that the cookies are set correctly - required on any change to the principal
string url = Utilities.TenantUrl(SiteState.Alias, "/pages/external/"); var interop = new Interop(JSRuntime);
await interop.SubmitForm(url, fields); var fields = new { returnurl = "/" + NavigationManager.ToBaseRelativePath(_absoluteUri) };
return; string url = Utilities.TenantUrl(SiteState.Alias, "/pages/external/");
await interop.SubmitForm(url, fields);
return;
}
else
{
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload=post", "").Replace("&reload=post", ""), true);
return;
}
} }
else else
{ {
@ -163,9 +172,9 @@
visitorId = PageState.VisitorId; visitorId = PageState.VisitorId;
} }
if (PageState.RenderMode == RenderModes.Interactive) if (PageState != null && PageState.RenderMode == RenderModes.Interactive)
{ {
// process any sync events (for synchrozing the client application with the server) // process any sync events (for synchronizing the client application with the server)
var sync = await SyncService.GetSyncEventsAsync(lastsyncdate); var sync = await SyncService.GetSyncEventsAsync(lastsyncdate);
lastsyncdate = sync.SyncDate; lastsyncdate = sync.SyncDate;
if (sync.SyncEvents.Any()) if (sync.SyncEvents.Any())
@ -243,105 +252,97 @@
} }
} }
if (page != null) // check if user is authorized to view page
if (page != null && UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList)))
{ {
// check if user is authorized to view page // edit mode
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList))) if (user != null)
{ {
// edit mode if (querystring.ContainsKey("editmode") && querystring["edit"] == "true")
if (user != null)
{ {
if (querystring.ContainsKey("editmode") && querystring["edit"] == "true") editmode = true;
}
else
{
editmode = (page.PageId == ((user.Settings.ContainsKey("CP-editmode")) ? int.Parse(user.Settings["CP-editmode"], CultureInfo.InvariantCulture) : -1));
if (!editmode)
{ {
editmode = true; var userSettings = new Dictionary<string, string> { { "CP-editmode", "-1" } };
} await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId);
else
{
editmode = (page.PageId == ((user.Settings.ContainsKey("CP-editmode")) ? int.Parse(user.Settings["CP-editmode"]) : -1));
if (!editmode)
{
var userSettings = new Dictionary<string, string> { { "CP-editmode", "-1" } };
await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId);
}
} }
} }
}
// load additional metadata for current page
page = ProcessPage(page, site, user, SiteState.Alias);
// load additional metadata for modules // load additional metadata for current page
(page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias); page = ProcessPage(page, site, user, SiteState.Alias);
// populate page state (which acts as a client-side cache for subsequent requests) // load additional metadata for modules
_pagestate = new PageState (page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias);
// populate page state (which acts as a client-side cache for subsequent requests)
_pagestate = new PageState
{
Alias = SiteState.Alias,
Site = site,
Page = page,
User = user,
Uri = new Uri(_absoluteUri, UriKind.Absolute),
Route = route,
QueryString = querystring,
UrlParameters = route.UrlParameters,
ModuleId = moduleid,
Action = action,
EditMode = editmode,
LastSyncDate = lastsyncdate,
RenderMode = RenderMode,
Runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime),
VisitorId = visitorId,
RemoteIPAddress = SiteState.RemoteIPAddress,
ReturnUrl = returnurl,
IsInternalNavigation = _isInternalNavigation,
RenderId = Guid.NewGuid(),
Refresh = false
};
OnStateChange?.Invoke(_pagestate);
if (PageState.RenderMode == RenderModes.Interactive)
{
await ScrollToFragment(_pagestate.Uri);
}
}
else
{
if (page == null)
{
// check for url mapping
var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath);
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
{ {
Alias = SiteState.Alias, var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl + route.Query;
Site = site, NavigationManager.NavigateTo(url, false);
Page = page, return;
User = user,
Uri = new Uri(_absoluteUri, UriKind.Absolute),
Route = route,
QueryString = querystring,
UrlParameters = route.UrlParameters,
ModuleId = moduleid,
Action = action,
EditMode = editmode,
LastSyncDate = lastsyncdate,
RenderMode = RenderMode,
Runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime),
VisitorId = visitorId,
RemoteIPAddress = SiteState.RemoteIPAddress,
ReturnUrl = returnurl,
IsInternalNavigation = _isInternalNavigation,
RenderId = Guid.NewGuid(),
Refresh = false
};
OnStateChange?.Invoke(_pagestate);
if (PageState.RenderMode == RenderModes.Interactive)
{
await ScrollToFragment(_pagestate.Uri);
} }
} }
else else
{
// Need to redirect 404 as page doesnot exist in a Permission or Timeframe
if (route.PagePath != "404")
{
// redirect to 404 page
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", ""));
}
}
}
else // page not found
{
// look for url mapping
var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath);
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
{
var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl + route.Query;
NavigationManager.NavigateTo(url, false);
}
else // not mapped
{ {
if (user == null) if (user == null)
{ {
// redirect to login page if user not logged in as they may need to be authenticated // redirect to login page if user not logged in as they may need to be authenticated
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery))); NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery)));
return;
} }
else }
{
if (route.PagePath != "404") // page not found or user does not have sufficient access
{ if (route.PagePath != "404")
// redirect to 404 page {
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", "")); // redirect to 404 page
} NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", ""));
else }
{ else
// redirect to home page as a fallback {
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", "")); // redirect to home page as a fallback
} NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));
}
} }
} }
} }
@ -476,6 +477,7 @@
// retrieve module component resources // retrieve module component resources
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
module.RenderMode = moduleobject.RenderMode; module.RenderMode = moduleobject.RenderMode;
module.Prerender = moduleobject.Prerender;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace); page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
if (action.ToLower() == "settings" && module.ModuleDefinition != null) if (action.ToLower() == "settings" && module.ModuleDefinition != null)
@ -549,7 +551,7 @@
{ {
foreach (var resource in resources) foreach (var resource in resources)
{ {
if (resource.Level != ResourceLevel.Site) if (resource.ResourceType == ResourceType.Stylesheet || resource.Level != ResourceLevel.Site)
{ {
if (resource.Url.StartsWith("~")) if (resource.Url.StartsWith("~"))
{ {

View File

@ -1,21 +0,0 @@
@attribute [StreamRendering(false)]
@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)" />
}
@code {
[Parameter]
public SiteState SiteState { get; set; }
[Parameter]
public PageState PageState { get; set; }
[Parameter]
public Module ModuleState { get; set; }
}

View File

@ -1,21 +0,0 @@
@attribute [StreamRendering(true)]
@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)" />
}
@code {
[Parameter]
public SiteState SiteState { get; set; }
[Parameter]
public PageState PageState { get; set; }
[Parameter]
public Module ModuleState { get; set; }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.0.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.0.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.Shared
@using Oqtane.Themes @using Oqtane.Themes
@using Oqtane.Extensions @using Oqtane.Extensions
@using System.Globalization
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IAntiforgery Antiforgery @inject IAntiforgery Antiforgery
@inject IConfigManager ConfigManager @inject IConfigManager ConfigManager
@ -39,7 +40,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<base href="/" /> <base href="/" />
<link rel="stylesheet" href="css/app.css" /> <link rel="stylesheet" href="css/app.css" />
@if (!string.IsNullOrEmpty(_PWAScript)) @if (_scripts.Contains("PWA Manifest"))
{ {
<link id="app-manifest" rel="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)" /> <Routes PageState="@_pageState" RenderMode="@_renderMode" Runtime="@_runtime" AntiForgeryToken="@_antiForgeryToken" AuthorizationToken="@_authorizationToken" Platform="@_platform" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(_runtime, _prerender)" />
} }
@if (!string.IsNullOrEmpty(_reconnectScript)) <script src="_framework/blazor.web.js"></script>
{
@((MarkupString)_reconnectScript)
}
@if (!string.IsNullOrEmpty(_PWAScript))
{
@((MarkupString)_PWAScript)
}
@((MarkupString)_bodyResources)
<script src="js/app.js"></script> <script src="js/app.js"></script>
<script src="js/loadjs.min.js"></script> <script src="js/loadjs.min.js"></script>
<script src="js/interop.js"></script> <script src="js/interop.js"></script>
<script src="_framework/blazor.web.js"></script>
@((MarkupString)_scripts)
@((MarkupString)_bodyResources)
} }
else else
{ {
@ -105,8 +99,7 @@
private string _headResources = ""; private string _headResources = "";
private string _bodyResources = ""; private string _bodyResources = "";
private string _styleSheets = ""; private string _styleSheets = "";
private string _PWAScript = ""; private string _scripts = "";
private string _reconnectScript = "";
private string _message = ""; private string _message = "";
private PageState _pageState; private PageState _pageState;
@ -175,23 +168,25 @@
CreateJwtToken(alias); CreateJwtToken(alias);
} }
// include stylesheets to prevent FOUC // includes resources
var resources = GetPageResources(alias, site, page, int.Parse(route.ModuleId), route.Action); var resources = GetPageResources(alias, site, page, int.Parse(route.ModuleId, CultureInfo.InvariantCulture), route.Action);
ManageStyleSheets(resources); ManageStyleSheets(resources);
ManageScripts(resources, alias);
// scripts // generate scripts
if (_renderMode == RenderModes.Static)
{
ManageScripts(resources, alias);
}
if (_renderMode == RenderModes.Interactive && _runtime == Runtimes.Server) if (_renderMode == RenderModes.Interactive && _runtime == Runtimes.Server)
{ {
_reconnectScript = CreateReconnectScript(); _scripts += CreateReconnectScript();
} }
if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null) 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); _headResources += ParseScripts(site.HeadContent);
_bodyResources += ParseScripts(site.BodyContent); _bodyResources += ParseScripts(site.BodyContent);
@ -329,14 +324,26 @@
int? userid = Context.User.UserId(); int? userid = Context.User.UserId();
userid = (userid == -1) ? null : 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; Visitor visitor = null;
bool addcookie = false;
var VisitorCookie = Constants.VisitorCookiePrefix + SiteId.ToString(); if (_visitorId <= 0)
if (!int.TryParse(Context.Request.Cookies[VisitorCookie], out _visitorId))
{ {
// if enabled use IP Address correlation // if enabled use IP Address correlation
_visitorId = -1;
var correlate = bool.Parse(settings.GetValue("VisitorCorrelation", "true")); var correlate = bool.Parse(settings.GetValue("VisitorCorrelation", "true"));
if (correlate) if (correlate)
{ {
@ -344,12 +351,12 @@
if (visitor != null) if (visitor != null)
{ {
_visitorId = visitor.VisitorId; _visitorId = visitor.VisitorId;
addcookie = true; setcookie = true;
} }
} }
} }
if (_visitorId == -1) if (_visitorId <= 0)
{ {
// create new visitor // create new visitor
visitor = new Visitor(); visitor = new Visitor();
@ -365,52 +372,59 @@
visitor.VisitedOn = DateTime.UtcNow; visitor.VisitedOn = DateTime.UtcNow;
visitor = VisitorRepository.AddVisitor(visitor); visitor = VisitorRepository.AddVisitor(visitor);
_visitorId = visitor.VisitorId; _visitorId = visitor.VisitorId;
addcookie = true; setcookie = true;
} }
else else
{ {
if (visitor == null) // check expiry
if (DateTime.UtcNow > expiry)
{ {
// get visitor if it was not previously loaded if (visitor == null)
visitor = VisitorRepository.GetVisitor(_visitorId);
}
if (visitor != null)
{
// update visitor
visitor.IPAddress = _remoteIPAddress;
visitor.UserAgent = useragent;
visitor.Language = language;
visitor.Url = url;
if (!string.IsNullOrEmpty(referrer))
{ {
visitor.Referrer = referrer; // get visitor if not previously loaded
visitor = VisitorRepository.GetVisitor(_visitorId);
} }
if (userid != null) if (visitor != null)
{ {
visitor.UserId = userid; // update visitor
visitor.IPAddress = _remoteIPAddress;
visitor.UserAgent = useragent;
visitor.Language = language;
visitor.Url = url;
if (!string.IsNullOrEmpty(referrer))
{
visitor.Referrer = referrer;
}
if (userid != null)
{
visitor.UserId = userid;
}
visitor.Visits += 1;
visitor.VisitedOn = DateTime.UtcNow;
VisitorRepository.UpdateVisitor(visitor);
setcookie = true;
}
else
{
// remove cookie if visitor does not exist
Context.Response.Cookies.Delete(visitorCookieName);
} }
visitor.Visits += 1;
visitor.VisitedOn = DateTime.UtcNow;
VisitorRepository.UpdateVisitor(visitor);
}
else
{
// remove cookie if VisitorId does not exist
Context.Response.Cookies.Delete(VisitorCookie);
} }
} }
// append cookie // set cookie
if (addcookie) if (setcookie)
{ {
expiry = DateTime.UtcNow.AddMinutes(int.Parse(settings.GetValue("VisitorDuration", "5")));
Context.Response.Cookies.Append( Context.Response.Cookies.Append(
VisitorCookie, visitorCookieName,
_visitorId.ToString(), $"{_visitorId}|{expiry}",
new CookieOptions() new CookieOptions()
{ {
Expires = DateTimeOffset.UtcNow.AddYears(1), Expires = DateTimeOffset.UtcNow.AddYears(10),
IsEssential = true IsEssential = true
} }
); );
} }
} }
@ -432,7 +446,7 @@
private string CreatePWAScript(Alias alias, Site site, Route route) private string CreatePWAScript(Alias alias, Site site, Route route)
{ {
return return Environment.NewLine +
"<script>" + Environment.NewLine + "<script>" + Environment.NewLine +
" // PWA Manifest" + Environment.NewLine + " // PWA Manifest" + Environment.NewLine +
" setTimeout(() => {" + Environment.NewLine + " setTimeout(() => {" + Environment.NewLine +
@ -468,14 +482,14 @@
" console.log('ServiceWorker Registration Failed ', err);" + Environment.NewLine + " console.log('ServiceWorker Registration Failed ', err);" + Environment.NewLine +
" });" + Environment.NewLine + " });" + Environment.NewLine +
" };" + Environment.NewLine + " };" + Environment.NewLine +
"</script>"; "</script>" + Environment.NewLine;
} }
private string CreateReconnectScript() private string CreateReconnectScript()
{ {
return return Environment.NewLine +
"<script>" + Environment.NewLine + "<script>" + Environment.NewLine +
" // Blazor Server Reconnect" + Environment.NewLine + " // Interactive Blazor Server Reconnect" + Environment.NewLine +
" new MutationObserver((mutations, observer) => {" + Environment.NewLine + " new MutationObserver((mutations, observer) => {" + Environment.NewLine +
" if (document.querySelector('#components-reconnect-modal h5 a')) {" + Environment.NewLine + " if (document.querySelector('#components-reconnect-modal h5 a')) {" + Environment.NewLine +
" async function attemptReload() {" + Environment.NewLine + " async function attemptReload() {" + Environment.NewLine +
@ -487,7 +501,26 @@
" setInterval(attemptReload, 5000);" + Environment.NewLine + " setInterval(attemptReload, 5000);" + Environment.NewLine +
" }" + Environment.NewLine + " }" + Environment.NewLine +
" }).observe(document.body, { childList: true, subtree: true });" + 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) private string ParseScripts(string content)
@ -536,7 +569,7 @@
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") + ((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") + ((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
((resource.ES6Module) ? " type=\"module\"" : "") + ((resource.ES6Module) ? " type=\"module\"" : "") +
" src =\"" + url + "\"></script>"; // src at end of element due to enhanced navigation patch algorithm " src=\"" + url + "\"></script>"; // src at end of element due to enhanced navigation patch algorithm
} }
else else
{ {
@ -566,13 +599,13 @@
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType)); var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType));
if (theme != null) 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 else
{ {
// fallback to default Oqtane theme // fallback to default Oqtane theme
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == Constants.DefaultTheme)); 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); var type = Type.GetType(themeType);
if (type != null) if (type != null)
@ -580,7 +613,7 @@
var obj = Activator.CreateInstance(type) as IThemeControl; var obj = Activator.CreateInstance(type) as IThemeControl;
if (obj != null) 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 = ""; var typename = "";
if (module.ModuleDefinition != null) 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 // handle default action
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction)) if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
@ -635,7 +668,7 @@
var obj = Activator.CreateInstance(moduletype) as IModuleControl; var obj = Activator.CreateInstance(moduletype) as IModuleControl;
if (obj != null) 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) if (action.ToLower() == "settings" && module.ModuleDefinition != null)
{ {
// settings components are embedded within a framework settings module // settings components are embedded within a framework settings module
@ -643,7 +676,7 @@
if (moduletype != null) if (moduletype != null)
{ {
obj = Activator.CreateInstance(moduletype) as IModuleControl; 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,32 +689,35 @@
{ {
if (module.ModuleDefinition?.Resources != null) 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; 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) if (resources != null)
{ {
foreach (var resource in resources) foreach (var resource in resources)
{ {
if (resource.Url.StartsWith("~")) if (rendermode == RenderModes.Static || resource.ResourceType == ResourceType.Stylesheet || resource.Level == ResourceLevel.Site)
{ {
resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/"); if (resource.Url.StartsWith("~"))
} {
if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl)) resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/");
{ }
resource.Url = alias.BaseUrl + resource.Url; if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
} {
resource.Url = alias.BaseUrl + resource.Url;
}
// ensure resource does not exist already // ensure resource does not exist already
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower())) if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
{ {
pageresources.Add(resource.Clone(level, name)); pageresources.Add(resource.Clone(level, name));
}
} }
} }
} }
@ -692,6 +728,7 @@
{ {
if (resources != null) if (resources != null)
{ {
// include stylesheets to prevent FOUC
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff"); string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
int count = 0; int count = 0;
foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Stylesheet))

View File

@ -760,7 +760,7 @@ namespace Oqtane.Controllers
{ {
if (!Directory.Exists(folderpath)) 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 }; var separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
string[] folders = folderpath.Split(separators, StringSplitOptions.RemoveEmptyEntries); string[] folders = folderpath.Split(separators, StringSplitOptions.RemoveEmptyEntries);
foreach (string folder in folders) foreach (string folder in folders)

View File

@ -76,5 +76,20 @@ namespace Oqtane.Controllers
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
} }
} }
[HttpDelete]
[Authorize(Roles = RoleNames.Admin)]
public void Delete(string siteId)
{
if (int.TryParse(siteId, out int parsedSiteId) && parsedSiteId == _alias.SiteId)
{
_logs.DeleteLogs(parsedSiteId, 0); // specifying zero for age results in all logs being deleted
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Log Delete Attempt {SiteId}", siteId);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
} }
} }

View File

@ -365,8 +365,8 @@ namespace Oqtane.Controllers
{ {
{ "FrameworkVersion", moduleDefinition.Version }, { "FrameworkVersion", moduleDefinition.Version },
{ "ClientReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" }, { "ClientReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" },
{ "ServerReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" }, { "ServerReference", $"<PackageReference Include=\"Oqtane.Server\" Version=\"{moduleDefinition.Version}\" />" },
{ "SharedReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" }, { "SharedReference", $"<PackageReference Include=\"Oqtane.Shared\" Version=\"{moduleDefinition.Version}\" />" },
}; };
}); });
} }

View File

@ -34,6 +34,7 @@ namespace Oqtane.Controllers
case "environment": case "environment":
systeminfo.Add("CLRVersion", Environment.Version.ToString()); systeminfo.Add("CLRVersion", Environment.Version.ToString());
systeminfo.Add("OSVersion", Environment.OSVersion.ToString()); systeminfo.Add("OSVersion", Environment.OSVersion.ToString());
systeminfo.Add("Process", (Environment.Is64BitProcess) ? "64 Bit" : "32 Bit");
systeminfo.Add("MachineName", Environment.MachineName); systeminfo.Add("MachineName", Environment.MachineName);
systeminfo.Add("WorkingSet", Environment.WorkingSet.ToString()); systeminfo.Add("WorkingSet", Environment.WorkingSet.ToString());
systeminfo.Add("TickCount", Environment.TickCount64.ToString()); systeminfo.Add("TickCount", Environment.TickCount64.ToString());

View File

@ -8,6 +8,7 @@ using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using System.Net; using System.Net;
using System; using System;
using System.Globalization;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -33,7 +34,7 @@ namespace Oqtane.Controllers
int SiteId; int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.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 else
{ {

View File

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

View File

@ -157,7 +157,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.ConfigureApplicationCookie(options => services.ConfigureApplicationCookie(options =>
{ {
options.Cookie.HttpOnly = true; options.Cookie.HttpOnly = true;
options.Cookie.SameSite = SameSiteMode.Strict; options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Events.OnRedirectToLogin = context => options.Events.OnRedirectToLogin = context =>
{ {

View File

@ -539,6 +539,8 @@ namespace Oqtane.Infrastructure
var identityUserManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>(); var identityUserManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>();
var tenant = tenants.GetTenants().FirstOrDefault(item => item.Name == install.TenantName); 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 site = new Site
{ {
@ -556,9 +558,9 @@ namespace Oqtane.Infrastructure
DefaultContainerType = (!string.IsNullOrEmpty(install.DefaultContainer)) ? install.DefaultContainer : Constants.DefaultContainer, DefaultContainerType = (!string.IsNullOrEmpty(install.DefaultContainer)) ? install.DefaultContainer : Constants.DefaultContainer,
AdminContainerType = (!string.IsNullOrEmpty(install.DefaultAdminContainer)) ? install.DefaultAdminContainer : Constants.DefaultAdminContainer, AdminContainerType = (!string.IsNullOrEmpty(install.DefaultAdminContainer)) ? install.DefaultAdminContainer : Constants.DefaultAdminContainer,
SiteTemplateType = install.SiteTemplate, SiteTemplateType = install.SiteTemplate,
RenderMode = (!string.IsNullOrEmpty(install.RenderMode)) ? install.RenderMode : _configManager.GetSection("RenderMode").Value, RenderMode = rendermode,
Runtime = (!string.IsNullOrEmpty(install.Runtime)) ? install.Runtime : _configManager.GetSection("Runtime").Value, Runtime = runtime,
Prerender = true, Prerender = (rendermode == RenderModes.Interactive),
Hybrid = false Hybrid = false
}; };
site = sites.AddSite(site); 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[] segments = entry.FullName.Split('/'); // ZipArchiveEntries always use unix path separator
string filename = Path.Combine(folder, string.Join(Path.DirectorySeparatorChar, segments, ignoreLeadingSegments, segments.Length - ignoreLeadingSegments)); 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 try
{ {
if (!Directory.Exists(Path.GetDirectoryName(filename))) if (!Directory.Exists(Path.GetDirectoryName(filename)))
@ -227,6 +233,7 @@ namespace Oqtane.Infrastructure
// an error occurred extracting the file // an error occurred extracting the file
filename = ""; filename = "";
} }
return filename; return filename;
} }

View File

@ -93,6 +93,7 @@ namespace Oqtane.Infrastructure
} }
var result = new StringBuilder(); var result = new StringBuilder();
source = source.Replace("[[", "[$_["); //avoid nested square bracket issue.
foreach (Match match in this.TokenizerRegex.Matches(source)) foreach (Match match in this.TokenizerRegex.Matches(source))
{ {
var key = match.Result("${key}"); var key = match.Result("${key}");
@ -126,7 +127,7 @@ namespace Oqtane.Infrastructure
result.Append(match.Result("${text}")); result.Append(match.Result("${text}"));
} }
} }
result.Replace("[$_", "["); //restore the changes.
return result.ToString(); return result.ToString();
} }

View File

@ -201,56 +201,54 @@ namespace Oqtane.Managers
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null) if (identityuser != null)
{ {
var valid = true;
if (!string.IsNullOrEmpty(user.Password)) if (!string.IsNullOrEmpty(user.Password))
{ {
var validator = new PasswordValidator<IdentityUser>(); var validator = new PasswordValidator<IdentityUser>();
var result = await validator.ValidateAsync(_identityUserManager, null, user.Password); var result = await validator.ValidateAsync(_identityUserManager, null, user.Password);
valid = result.Succeeded; if (result.Succeeded)
if (valid)
{ {
identityuser.PasswordHash = _identityUserManager.PasswordHasher.HashPassword(identityuser, user.Password); identityuser.PasswordHash = _identityUserManager.PasswordHasher.HashPassword(identityuser, user.Password);
await _identityUserManager.UpdateAsync(identityuser);
} }
} else
if (valid)
{
if (!string.IsNullOrEmpty(user.Password))
{ {
await _identityUserManager.UpdateAsync(identityuser); // requires password to be provided _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 (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 (!user.EmailConfirmed)
{
var alias = _tenantManager.GetAlias();
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
_notifications.AddNotification(notification);
}
else
{
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);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Reload);
user.Password = ""; // remove sensitive information
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
} }
else
if (user.Email != identityuser.Email)
{ {
_logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Update, "Unable To Update User {Username}. Password Does Not Meet Complexity Requirements.", user.Username); await _identityUserManager.SetEmailAsync(identityuser, user.Email);
user = null;
// if email address changed and it is not confirmed, verification is required for new email address
if (!user.EmailConfirmed)
{
var alias = _tenantManager.GetAlias();
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
_notifications.AddNotification(notification);
}
} }
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);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Reload);
user.Password = ""; // remove sensitive information
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
}
else
{
_logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Update, "Unable To Update User {Username}. User Does Not Exist.", user.Username);
user = null;
} }
return user; return user;

View File

@ -7,6 +7,7 @@ using Oqtane.Repository;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.Migrations.Framework; using Oqtane.Migrations.Framework;
using Oqtane.Documentation; using Oqtane.Documentation;
using System.Linq;
// ReSharper disable ConvertToUsingDeclaration // ReSharper disable ConvertToUsingDeclaration
@ -29,10 +30,11 @@ namespace Oqtane.Modules.HtmlText.Manager
public string ExportModule(Module module) public string ExportModule(Module module)
{ {
string content = ""; string content = "";
var htmlText = _htmlText.GetHtmlText(module.ModuleId); var htmltexts = _htmlText.GetHtmlTexts(module.ModuleId);
if (htmlText != null) 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; return content;
} }

View File

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

View File

@ -46,15 +46,27 @@ namespace Oqtane.Pages
{ {
var sitemap = new List<Sitemap>(); var sitemap = new List<Sitemap>();
// internal pages which should not be indexed
string[] internalPaths = { "login", "register", "reset", "404" };
// build site map // 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 moduleDefinitions = _moduleDefinitions.GetModuleDefinitions(_alias.SiteId).ToList();
var pageModules = _pageModules.GetPageModules(_alias.SiteId); var pageModules = _pageModules.GetPageModules(_alias.SiteId);
foreach (var page in _pages.GetPages(_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 rooturl = _alias.Protocol + (string.IsNullOrEmpty(_alias.Path) ? _alias.Name : _alias.Name.Substring(0, _alias.Name.IndexOf("/"))); var pageurl = rooturl;
sitemap.Add(new Sitemap { Url = rooturl + Utilities.NavigateUrl(_alias.Path, page.Path, ""), ModifiedOn = DateTime.UtcNow }); if (string.IsNullOrEmpty(page.Url))
{
pageurl += Utilities.NavigateUrl(_alias.Path, page.Path, "");
}
else
{
pageurl += (page.Url.StartsWith("/") ? "" : "/") + page.Url;
}
sitemap.Add(new Sitemap { Url = pageurl, ModifiedOn = DateTime.UtcNow });
foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId)) foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId))
{ {

View File

@ -59,14 +59,14 @@ namespace Oqtane.Repository
// delete logs in batches of 100 records // delete logs in batches of 100 records
var count = 0; var count = 0;
var purgedate = DateTime.UtcNow.AddDays(-age); var purgedate = DateTime.UtcNow.AddDays(-age);
var logs = db.Log.Where(item => item.SiteId == siteId && item.Level != "Error" && item.LogDate < purgedate) var logs = db.Log.Where(item => item.SiteId == siteId && item.LogDate < purgedate)
.OrderBy(item => item.LogDate).Take(100).ToList(); .OrderBy(item => item.LogDate).Take(100).ToList();
while (logs.Count > 0) while (logs.Count > 0)
{ {
count += logs.Count; count += logs.Count;
db.Log.RemoveRange(logs); db.Log.RemoveRange(logs);
db.SaveChanges(); db.SaveChanges();
logs = db.Log.Where(item => item.SiteId == siteId && item.Level != "Error" && item.LogDate < purgedate) logs = db.Log.Where(item => item.SiteId == siteId && item.LogDate < purgedate)
.OrderBy(item => item.LogDate).Take(100).ToList(); .OrderBy(item => item.LogDate).Take(100).ToList();
} }
return count; return count;

View File

@ -287,7 +287,9 @@ namespace Oqtane.Repository
{ {
ModuleDefinition moduledefinition; 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(); Type[] modulecontroltypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModuleControl))).ToArray();
foreach (Type modulecontroltype in modulecontroltypes) foreach (Type modulecontroltype in modulecontroltypes)
{ {
// Check if type should be ignored // Check if type should be ignored
@ -299,12 +301,9 @@ namespace Oqtane.Repository
int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType); int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
if (index == -1) if (index == -1)
{ {
// determine if this module implements IModule // determine if this component is part of a module which implements IModule
Type moduletype = assembly Type moduletype = moduletypes.FirstOrDefault(item => item.Namespace == modulecontroltype.Namespace);
.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace == modulecontroltype.Namespace || item.Namespace.StartsWith(modulecontroltype.Namespace + "."))
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule)));
if (moduletype != null) if (moduletype != null)
{ {
// get property values from IModule // get property values from IModule
@ -399,6 +398,22 @@ namespace Oqtane.Repository
moduledefinitions[index] = moduledefinition; 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; return moduledefinitions;
} }

View File

@ -165,22 +165,23 @@ namespace Oqtane.Repository
if (!serverstate.IsInitialized) if (!serverstate.IsInitialized)
{ {
var site = GetSite(alias.SiteId); var site = GetSite(alias.SiteId);
if (site != null)
// initialize theme Assemblies
site.Themes = _themeRepository.GetThemes().ToList();
// initialize module Assemblies
var moduleDefinitions = _moduleDefinitionRepository.GetModuleDefinitions(alias.SiteId);
// execute migrations
var version = ProcessSiteMigrations(alias, site);
version = ProcessPageTemplates(alias, site, moduleDefinitions, version);
if (site.Version != version)
{ {
site.Version = version; // initialize theme Assemblies
UpdateSite(site); site.Themes = _themeRepository.GetThemes().ToList();
}
// initialize module Assemblies
var moduleDefinitions = _moduleDefinitionRepository.GetModuleDefinitions(alias.SiteId);
// execute migrations
var version = ProcessSiteMigrations(alias, site);
version = ProcessPageTemplates(alias, site, moduleDefinitions, version);
if (site.Version != version)
{
site.Version = version;
UpdateSite(site);
}
}
serverstate.IsInitialized = true; serverstate.IsInitialized = true;
} }
} }
@ -411,7 +412,7 @@ namespace Oqtane.Repository
} }
else 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.ParentId = (parent != null) ? parent.PageId : null;
page.Path = page.Path.ToLower(); page.Path = page.Path.ToLower();
@ -487,7 +488,11 @@ namespace Oqtane.Repository
pageModule.Order = (pageTemplateModule.Order == 0) ? 1 : pageTemplateModule.Order; pageModule.Order = (pageTemplateModule.Order == 0) ? 1 : pageTemplateModule.Order;
pageModule.ContainerType = pageTemplateModule.ContainerType; pageModule.ContainerType = pageTemplateModule.ContainerType;
pageModule.IsDeleted = pageTemplateModule.IsDeleted; 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.AllPages = false;
pageModule.Module.IsDeleted = false; pageModule.Module.IsDeleted = false;
try try
@ -539,8 +544,11 @@ namespace Oqtane.Repository
try try
{ {
var module = _moduleRepository.GetModule(pageModule.ModuleId); var module = _moduleRepository.GetModule(pageModule.ModuleId);
var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype); if (module != null)
((IPortable)moduleobject).ImportModule(module, pageTemplateModule.Content, moduleDefinition.Version); {
var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
((IPortable)moduleobject).ImportModule(module, pageTemplateModule.Content, moduleDefinition.Version);
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

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

View File

@ -22,6 +22,7 @@ using Oqtane.UI;
using OqtaneSSR.Extensions; using OqtaneSSR.Extensions;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Oqtane.Providers; using Oqtane.Providers;
using Microsoft.AspNetCore.Cors.Infrastructure;
namespace Oqtane namespace Oqtane
{ {
@ -135,7 +136,7 @@ namespace Oqtane
{ {
// allow .NET MAUI client cross origin calls // allow .NET MAUI client cross origin calls
policy.WithOrigins("https://0.0.0.0", "http://0.0.0.0", "app://0.0.0.0") policy.WithOrigins("https://0.0.0.0", "http://0.0.0.0", "app://0.0.0.0")
.AllowAnyHeader().AllowCredentials(); .AllowAnyHeader().AllowAnyMethod().AllowCredentials();
}); });
}); });
@ -169,7 +170,7 @@ namespace Oqtane
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISyncManager sync, ILogger<Startup> logger) public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISyncManager sync, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ILogger<Startup> logger)
{ {
if (!string.IsNullOrEmpty(_configureServicesErrors)) if (!string.IsNullOrEmpty(_configureServicesErrors))
{ {
@ -198,7 +199,16 @@ namespace Oqtane
app.UseOqtaneLocalization(); app.UseOqtaneLocalization();
app.UseHttpsRedirection(); app.UseHttpsRedirection();
app.UseStaticFiles(); app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true,
OnPrepareResponse = (ctx) =>
{
var policy = corsPolicyProvider.GetPolicyAsync(ctx.Context, Constants.MauiCorsPolicy)
.ConfigureAwait(false).GetAwaiter().GetResult();
corsService.ApplyResult(corsService.EvaluatePolicy(ctx.Context, policy), ctx.Context.Response);
}
});
app.UseExceptionMiddleWare(); app.UseExceptionMiddleWare();
app.UseTenantResolution(); app.UseTenantResolution();
app.UseJwtAuthorization(); app.UseJwtAuthorization();
@ -206,6 +216,7 @@ namespace Oqtane
app.UseCors(); app.UseCors();
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseAntiforgery();
if (_useSwagger) if (_useSwagger)
{ {

View File

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

View File

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<AccelerateBuildsInVisualStudio>false</AccelerateBuildsInVisualStudio>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

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

View File

@ -108,12 +108,12 @@
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
if (_login != "-") if (_login != "-")
{ {
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login, true); settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login);
} }
if (_register != "-") if (_register != "-")
{ {
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register, true); settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register);
} }
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
} }

View File

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

View File

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<AccelerateBuildsInVisualStudio>false</AccelerateBuildsInVisualStudio>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -35,6 +35,9 @@ app {
} }
/* Action Dialog */ /* Action Dialog */
.app-actiondialog{
position: absolute;
}
.app-actiondialog .modal { .app-actiondialog .modal {
position: fixed; /* Stay in place */ position: fixed; /* Stay in place */
z-index: 9999; /* Sit on top */ z-index: 9999; /* Sit on top */

Some files were not shown because too many files have changed in this diff Show More