Compare commits

...

473 Commits

Author SHA1 Message Date
d433850cbf Merge pull request #4469 from oqtane/master
5.2.0 release
2024-07-25 11:49:12 -04:00
c93e70e2dc Merge pull request #4468 from oqtane/dev
5.2.0 release
2024-07-25 11:48:50 -04:00
a129dd989a Merge pull request #4467 from sbwalker/dev
remove Settings button logic from QuillJS text editor interop
2024-07-25 11:13:19 -04:00
40999c3ff4 remove Settings button logic from QuillJS text editor interop 2024-07-25 11:12:58 -04:00
18a01d672c Merge pull request #4466 from sbwalker/dev
resolve localization issue in ActionDialog
2024-07-25 09:24:34 -04:00
3648f99920 resolve localization issue in ActionDialog 2024-07-25 09:24:21 -04:00
e823412f56 Merge pull request #4465 from leigh-pointer/NotifyLogging
Small Notification Job update
2024-07-25 08:47:00 -04:00
d090f446c9 Small Notification Job update
This update adds the NotificationId to the log to help track down any errors.
2024-07-25 13:11:05 +02:00
19985d1742 Merge pull request #4464 from sbwalker/dev
resolve issue with default Blazor theme
2024-07-24 13:00:03 -04:00
ab52251116 resolve issue with default Blazor theme 2024-07-24 12:59:40 -04:00
acb6c0187c Merge pull request #4461 from sbwalker/dev
add missing localization for Search Results Settings
2024-07-23 14:44:30 -04:00
90f9c24720 add missing localization for Search Results Settings 2024-07-23 14:44:14 -04:00
415bec4646 Merge pull request #4460 from sbwalker/dev
prevent breaking change for interactive components referencing PageState.Pages
2024-07-23 12:48:13 -04:00
5559f20511 prevent breaking change for interactive components referencing PageState.Pages 2024-07-23 12:47:54 -04:00
6ea3399829 Merge pull request #4458 from sbwalker/dev
use PageState.Site.Settings rather than reloading settings from database
2024-07-23 07:45:32 -04:00
9e3df97737 use PageState.Site.Settings rather than reloading settings from database 2024-07-23 07:45:13 -04:00
0ac40a4d77 Merge pull request #4456 from leigh-pointer/SixLabors.ImageSharp
ENH update to 3.1.5 #4455 Vulnerabilities detected in 3.1.4
2024-07-23 07:13:45 -04:00
24bf5e8102 Merge pull request #4454 from mdmontesinos/dev
fix: set Rich active tab for Quill text editor
2024-07-23 07:13:24 -04:00
56eebb03c7 Merge pull request #4457 from sbwalker/dev
change IsEffectiiveOrExpired to IsEffectiveAndNotExpired
2024-07-23 07:11:31 -04:00
1cd4d6d0df change IsEffectiiveOrExpired to IsEffectiveAndNotExpired 2024-07-23 07:08:26 -04:00
22d4a8232a ENH update to 3.1.5 #4455 Vulnerabilities detected in 3.1.4 2024-07-23 13:03:03 +02:00
48076c25bf fix: set Rich active tab for Quill text editor 2024-07-23 09:18:58 +02:00
478a308e73 Merge pull request #4453 from sbwalker/dev
fix #4284 - handle user role effective and expiry date
2024-07-22 21:09:52 -04:00
8ca2f0a49f fix #4284 - handle user role effective and expiry date 2024-07-22 21:09:35 -04:00
4a35d7364b Merge pull request #4452 from sbwalker/dev
remove ITextEditorProvider interface
2024-07-22 13:31:39 -04:00
8b2e55a969 remove ITextEditorProvider interface 2024-07-22 13:31:24 -04:00
9b14b70687 Merge pull request #4451 from sbwalker/dev
fix #4450 QuillJSTextEditor settings
2024-07-22 08:11:24 -04:00
1c01087eda fix #4450 QuillJSTextEditor settings 2024-07-22 08:11:09 -04:00
5137e5a301 Merge pull request #4449 from sbwalker/dev
add documentation
2024-07-21 09:19:00 -04:00
0fea8365b8 add documentation 2024-07-21 09:18:41 -04:00
116a615b84 Merge pull request #4448 from sbwalker/dev
change Ignore Paths to Ignore Pages
2024-07-21 09:10:23 -04:00
ef272dd6a8 change Ignore Paths to Ignore Pages 2024-07-21 09:10:01 -04:00
4462ae9cae Merge pull request #4447 from sbwalker/dev
moved Search Provider setting to Search Settings
2024-07-21 08:59:41 -04:00
66ffad0b4e moved Search Provider setting to Search Settings 2024-07-21 08:59:23 -04:00
81e0fc940c Merge pull request #4446 from sbwalker/dev
add Search Provider to Site Settings
2024-07-21 08:49:38 -04:00
3e8794db1b add Search Provider to Site Settings 2024-07-21 08:49:18 -04:00
617622d4d8 Merge pull request #4445 from sbwalker/dev
add Functionality section to Site Settings
2024-07-21 08:16:10 -04:00
a4240f972b add Functionality section to Site Settings 2024-07-21 08:15:51 -04:00
42feff8882 Merge pull request #4443 from sbwalker/dev
more localization changes
2024-07-20 21:52:56 -04:00
70edd9686f more localization changes 2024-07-20 21:52:41 -04:00
d298cb2e1c Merge pull request #4442 from sbwalker/dev
fix localization for QuillJSTextEditor
2024-07-20 21:43:57 -04:00
3fef3f2dc3 fix localization for QuillJSTextEditor 2024-07-20 21:43:38 -04:00
c85f9d6ae4 Merge pull request #4440 from sbwalker/dev
remove unecessary using
2024-07-20 19:39:58 -04:00
85e7ac7cd7 remove unecessary using 2024-07-20 19:39:45 -04:00
5c7db61a7e Merge pull request #4439 from sbwalker/dev
improve validation of seach content
2024-07-20 19:18:06 -04:00
497f9ca0b1 improve validation of seach content 2024-07-20 19:17:47 -04:00
0c50f7a322 Merge pull request #4438 from sbwalker/dev
allow page-script to support exterrnal JavaScript
2024-07-19 15:42:35 -04:00
740bcbd12c allow page-script to support exterrnal JavaScript 2024-07-19 15:42:20 -04:00
c92be4f270 Merge pull request #4437 from sbwalker/dev
revert modification done for testing purposes only
2024-07-19 15:31:24 -04:00
e2a7271ab2 revert modification done for testing purposes only 2024-07-19 15:31:11 -04:00
64766713fa Merge pull request #4436 from sbwalker/dev
add ability to manage search results settings
2024-07-19 12:56:14 -04:00
59bba83b1d add ability to manage search results settings 2024-07-19 12:55:59 -04:00
8ac1217165 Merge pull request #4435 from sbwalker/dev
allow <style> tags to be injected using HeadContent
2024-07-18 15:01:43 -04:00
5443629ec5 allow <style> tags to be injected using HeadContent 2024-07-18 15:01:20 -04:00
8f9b41cb62 Merge pull request #4434 from sbwalker/dev
use JSInterop for loading QuillJSTextEditor stylesheet
2024-07-18 13:17:58 -04:00
7df5eba775 use JSInterop for loading QuillJSTextEditor stylesheet 2024-07-18 13:17:23 -04:00
e29c6ac593 Merge pull request #4433 from sbwalker/dev
improve search user experience
2024-07-18 11:50:07 -04:00
4f3190bf73 improve search user experience 2024-07-18 11:49:42 -04:00
f0878fccb5 Merge pull request #4432 from sbwalker/dev
fix ISearchable implementation in default module template
2024-07-18 11:10:39 -04:00
b0e121a53f fix ISearchable implementation in default module template 2024-07-18 11:10:24 -04:00
eda48ab0e6 Merge pull request #4431 from sbwalker/dev
QuillJSTextEditor setting improvements
2024-07-18 11:03:13 -04:00
6a1014d8c1 QuillJSTextEditor setting improvements 2024-07-18 11:01:01 -04:00
e0f87315bc Merge pull request #4430 from sbwalker/dev
set Prerender on Login component
2024-07-17 20:52:28 -04:00
f2c5dca5e7 set Prerender on Login component 2024-07-17 20:52:13 -04:00
3c435a804f Merge pull request #4429 from sbwalker/dev
remove hardcoded names when using GetInterface()
2024-07-17 19:53:06 -04:00
7ee6775251 remove hardcoded names when using GetInterface() 2024-07-17 19:52:44 -04:00
98adc2ecc1 Merge pull request #4428 from sbwalker/dev
allow search content permissions to support roles
2024-07-17 19:34:34 -04:00
45afbbdac6 allow search content permissions to support roles 2024-07-17 19:34:19 -04:00
a18260747b Merge pull request #4427 from sbwalker/dev
add Refresh option for Job Logs
2024-07-17 17:12:38 -04:00
0c80e28754 add Refresh option for Job Logs 2024-07-17 17:12:24 -04:00
719bc374ac Merge pull request #4426 from sbwalker/dev
add localization to search settings
2024-07-17 16:34:16 -04:00
d7a290c595 add localization to search settings 2024-07-17 16:34:02 -04:00
4b2bd33baa Merge pull request #4425 from sbwalker/dev
update theme template to .NET 8.0.7
2024-07-17 16:23:30 -04:00
efbe4d697c update theme template to .NET 8.0.7 2024-07-17 16:23:15 -04:00
a8662bdb8b Merge pull request #4424 from sbwalker/dev
use Task.FromResult()
2024-07-17 16:22:16 -04:00
d822225465 use Task.FromResult() 2024-07-17 16:22:01 -04:00
c6373ef582 Merge pull request #4423 from sbwalker/dev
update module template to .NET 8.0.7
2024-07-17 16:17:30 -04:00
5a2af6d0f9 update module template to .NET 8.0.7 2024-07-17 16:17:10 -04:00
52000d6a41 Merge pull request #4422 from sbwalker/dev
update to .NET 8.0.7
2024-07-17 15:10:54 -04:00
befa13eaf2 update to .NET 8.0.7 2024-07-17 15:10:40 -04:00
4ac68a81a3 Merge pull request #4421 from sbwalker/dev
search optimizations
2024-07-17 13:58:03 -04:00
71e472f330 search optimizations 2024-07-17 13:57:47 -04:00
ada8809ec0 Merge pull request #4420 from oqtane/revert-4417-dev
Revert "disable prerendering by default for static rendered sites"
2024-07-17 12:13:35 -04:00
25ea518266 Revert "revert #4250 which disabled prerendering by default for static rendered sites" 2024-07-17 12:12:58 -04:00
f3720c3b94 Merge pull request #4419 from sbwalker/dev
improve PageState trimming
2024-07-17 11:53:22 -04:00
b942a84b15 improve PageState trimming 2024-07-17 11:53:04 -04:00
574ca90229 Merge pull request #4418 from sbwalker/dev
add missing properties to Clone method
2024-07-17 11:34:15 -04:00
5610a14e49 add missing properties to Clone method 2024-07-17 11:34:01 -04:00
c5bc62e6df Merge pull request #4417 from sbwalker/dev
revert #4250 which disabled prerendering by default for static rendered sites
2024-07-17 11:22:18 -04:00
e9f6a85cad revert #4250 which disabled prerendering by default for static rendered sites 2024-07-17 11:20:27 -04:00
8921011b27 Merge pull request #4416 from sbwalker/dev
performance improvement in Control Panel to only load list of pages when necessary
2024-07-17 11:13:46 -04:00
90ef3f6c94 performance improvement in Control Panel to only load list of pages when necessary 2024-07-17 11:13:27 -04:00
e84c75f4a8 Merge pull request #4414 from pollux/patch-1
Update Oqtane.Shared.csproj
2024-07-17 11:09:59 -04:00
68e20cd860 Merge pull request #4415 from sbwalker/dev
include Search Settings
2024-07-17 11:09:00 -04:00
76bdcea4b1 include Search Settings 2024-07-17 11:08:43 -04:00
f758cb7e6e Update Oqtane.Shared.csproj
Fixes CVE-2024-30105
2024-07-17 12:46:49 +02:00
1108477810 Merge pull request #4413 from sbwalker/dev
make SearchResults API consistent with other core APIs
2024-07-16 16:55:09 -04:00
deb6a9e51c make SearchResults API consistent with other core APIs 2024-07-16 16:54:55 -04:00
9660f20b87 Merge pull request #4412 from sbwalker/dev
performance optimization to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries
2024-07-16 16:21:57 -04:00
4d26468ede performance optimization to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries 2024-07-16 16:21:35 -04:00
125a0979d5 Merge pull request #4408 from sbwalker/dev
removed unused constants
2024-07-15 10:11:31 -04:00
98bdfd3dbe removed unused constants 2024-07-15 10:11:14 -04:00
ea72880e74 Merge pull request #4407 from sbwalker/dev
resolve security issue in Search
2024-07-15 10:08:04 -04:00
17fec7d6e1 resolve security issue in Search 2024-07-15 10:07:48 -04:00
d9de64604e Merge pull request #4406 from sbwalker/dev
fix #4401 - avoid mutating Site object in cache
2024-07-15 08:37:46 -04:00
6275ab23ff fix #4401 - avoid mutating Site object in cache 2024-07-15 08:37:23 -04:00
8c0271643d Merge pull request #4405 from sbwalker/dev
testing search indexing of files
2024-07-13 09:28:25 -04:00
c3f041dc87 testing search indexing of files 2024-07-13 09:28:02 -04:00
80e5e84341 Merge pull request #4404 from sbwalker/dev
only include pages in index if they do not have any modules
2024-07-12 10:53:25 -04:00
938eee80a9 only include pages in index if they do not have any modules 2024-07-12 10:52:58 -04:00
7abc2289de Merge pull request #4402 from sbwalker/dev
search modifications
2024-07-12 10:33:34 -04:00
bb79b9ed74 search modifications 2024-07-12 10:33:17 -04:00
1e89a8625c Merge pull request #4400 from sbwalker/dev
fix #4397 - remove incorrect help text
2024-07-11 13:39:19 -04:00
90b0f04b3c fix #4397 - remove incorrect help text 2024-07-11 13:39:05 -04:00
0f019cd9b6 Merge pull request #4399 from sbwalker/dev
fix #4398 - InputList component not handling scenario where input is reset to nothing
2024-07-11 13:36:14 -04:00
1209739398 fix #4398 - InputList component not handling scenario where input is reset to nothing 2024-07-11 13:35:46 -04:00
b99db2b353 Merge pull request #4394 from sbwalker/dev
more Site Settings for search configuration
2024-07-08 16:59:10 -04:00
f057688e7d more Site Settings for search configuration 2024-07-08 16:58:55 -04:00
12ae2d0c76 Merge pull request #4393 from sbwalker/dev
site settings to configure indexing
2024-07-08 14:33:54 -04:00
9d91d5a127 site settings to configure indexing 2024-07-08 14:33:16 -04:00
6015f0887a Merge pull request #4389 from sbwalker/dev
fix #4384 - app_offline https link
2024-07-06 08:38:52 -04:00
d4c473d7b3 fix #4384 - app_offline https link 2024-07-06 08:38:33 -04:00
2f3978deed Merge pull request #4374 from zyhfish/task/fix-issue-4358
Fix #4358: RichTextEditor Provider Abstraction.
2024-07-06 08:32:19 -04:00
34af53a15b Merge branch 'dev' into task/fix-issue-4358 2024-07-06 08:32:10 -04:00
a4eb3d7a0b Merge pull request #4388 from sbwalker/dev
search refactoring
2024-07-06 07:58:35 -04:00
5b46dd7293 search refactoring 2024-07-06 07:58:04 -04:00
Ben
acbe000f97 avoid race condition issue. 2024-07-03 17:59:40 +08:00
Ben
599071b68b avoid race condition issue. 2024-07-03 17:07:47 +08:00
Ben
2bacee919d update the settings UI. 2024-07-03 12:26:36 +08:00
50d35e4196 Merge pull request #4383 from sbwalker/dev
remove unnecessary using
2024-07-02 15:53:40 -04:00
e321998b85 remove unnecessary using 2024-07-02 15:53:26 -04:00
340c02b2af Merge pull request #4382 from sbwalker/dev
remove unnecessary database call to GetPage
2024-07-02 15:45:59 -04:00
69a295fe57 remove unnecessary database call to GetPage 2024-07-02 15:45:44 -04:00
64830aae9f Merge pull request #4381 from sbwalker/dev
use PageModule in ISearchable
2024-07-02 14:50:42 -04:00
8969b1273f use PageModule in ISearchable 2024-07-02 14:50:26 -04:00
475faf7943 Merge pull request #4380 from sbwalker/dev
fix #4375 - deleted pages not being filtered
2024-07-02 11:05:42 -04:00
45b1d405a6 fix #4375 - deleted pages not being filtered 2024-07-02 11:05:29 -04:00
Ben
f60c8078e4 cleanup the code. 2024-07-02 09:55:38 +08:00
Ben
6701e49f9a move the editor settings into editor self control. 2024-07-02 09:50:53 +08:00
0efb3c3284 Merge pull request #4376 from sbwalker/dev
ensure UniqueKey is unique by including TenantId and SiteId
2024-07-01 16:20:15 -04:00
aaf3cdfdac ensure UniqueKey is unique by including TenantId and SiteId 2024-07-01 16:19:58 -04:00
Ben
e00c261777 Fix #4358: RichTextEditor Provider Abstraction. 2024-07-01 17:11:26 +08:00
1eafed755d Merge pull request #4372 from sbwalker/dev
provide default Permissions value
2024-06-28 17:31:48 -04:00
7f6a08ae50 provide default Permissions value 2024-06-28 17:31:33 -04:00
9901816fb9 Merge pull request #4371 from sbwalker/dev
ensure ModuleDefinition exists
2024-06-28 17:26:58 -04:00
3cbe6c1e95 ensure ModuleDefinition exists 2024-06-28 17:26:46 -04:00
679c99274e Merge pull request #4370 from sbwalker/dev
change EntityId to string
2024-06-28 16:25:11 -04:00
b6fa0f1ff6 change EntityId to string 2024-06-28 16:24:56 -04:00
9ff64b95e1 Merge pull request #4369 from sbwalker/dev
modify query property names
2024-06-28 16:15:42 -04:00
3a9885abd8 modify query property names 2024-06-28 16:15:28 -04:00
503210942c Merge pull request #4368 from sbwalker/dev
breaking search modifications into smaller PRs
2024-06-28 15:44:11 -04:00
0178e015e3 breaking search modifications into smaller PRs 2024-06-28 15:43:54 -04:00
7604992c35 Merge pull request #4361 from 2sic-forks/bug/4360
fix docfx build issues #4360
2024-06-28 09:01:45 -04:00
ee9e551788 Merge pull request #4365 from sbwalker/dev
reposition Search input in themes
2024-06-27 17:18:54 -04:00
22063248ca reposition Search input in themes 2024-06-27 17:18:41 -04:00
e4ce18b35c Merge pull request #4364 from sbwalker/dev
eager load Page associated to PageModule
2024-06-27 17:05:35 -04:00
532890674e eager load Page associated to PageModule 2024-06-27 17:05:22 -04:00
791a3b67e6 fix docfx build issues 2024-06-27 19:47:13 +02:00
84b560ef29 Merge pull request #4356 from sbwalker/dev
fix #4353 - add defensive logic when sending notifications and improve performance
2024-06-26 09:09:29 -04:00
03f081f3f4 fix #4353 - add defensive logic when sending notifications and improve performance 2024-06-26 09:09:06 -04:00
08213ae86e Merge pull request #4352 from sbwalker/dev
fix #4349 - adding module in subsite in Interactive render mode
2024-06-24 16:27:35 -04:00
73abc511a8 fix #4349 - adding module in subsite in Interactive render mode 2024-06-24 16:26:55 -04:00
1c943cc259 Merge pull request #4350 from sbwalker/dev
fix #4339 - add page not redirecting to correct url in subsite
2024-06-24 11:00:01 -04:00
af62a89a6b fix #4339 - add page not redirecting to correct url in subsite 2024-06-24 10:59:34 -04:00
af7ca5b897 Merge pull request #4338 from sbwalker/dev
use List instead of IList, remove "List" from method names. remove unnecessary using statements
2024-06-11 10:39:03 -04:00
27356ef747 use List instead of IList, remove "List" from method names. remove unnecessary using statements 2024-06-11 10:38:44 -04:00
b27f80ef87 Merge pull request #4337 from leigh-pointer/SearchResultsCat
Updated SearchResults Categories to Admin
2024-06-11 10:28:56 -04:00
b4aa73fc64 Updated SearchResults Categories to Admin 2024-06-11 15:21:12 +02:00
bbf444572b Merge pull request #4336 from sbwalker/dev
removed IHttpContextAccessor as it shoudl not be used in Blazor,  fixed form handling, added Reset button to consistent with other framework search options, used SharedLocalizer, removed unused localization keys
2024-06-11 08:39:28 -04:00
b3706574de removed IHttpContextAccessor as it shoudl not be used in Blazor, fixed form handling, added Reset button to consistent with other framework search options, used SharedLocalizer, removed unused localization keys 2024-06-11 08:38:51 -04:00
83062f8bfb Merge pull request #4335 from sbwalker/dev
fix ISearchable method name in module template
2024-06-11 07:37:25 -04:00
1d7fcfdaa1 fix ISearchable method name in module template 2024-06-11 07:37:02 -04:00
58ab12b4cb Merge pull request #4328 from leigh-pointer/UpdateTemplates
Update Project Templates from 8.0.5 - 8.0.6
2024-06-11 07:35:02 -04:00
28c649629f Merge pull request #4334 from leigh-pointer/Bootstrap5.3.3
Upgrade to Bootstrap 5.3.3
2024-06-11 07:32:10 -04:00
5ec190225d Upgrade to Bootstrap 5.3.3
Oqtane ThemeInfo updated
Blazor Default.razor updated
2024-06-11 09:51:48 +02:00
0e0d404997 Update [Module]Manager.cs 2024-06-11 09:11:25 +02:00
3f16b908ca Merge remote-tracking branch 'upstream/dev' into UpdateTemplates 2024-06-11 08:49:28 +02:00
54549b261c Merge pull request #4333 from sbwalker/dev
change IList to List for consistency with rest of framework
2024-06-10 17:17:33 -04:00
8ce07ced9e change IList to List for consistency with rest of framework 2024-06-10 17:17:20 -04:00
37a5144e8f Merge pull request #4332 from sbwalker/dev
update app constant to 5.2.0
2024-06-10 16:50:45 -04:00
59fed7dda8 update app constant to 5.2.0 2024-06-10 16:50:33 -04:00
0a85a2868c Merge pull request #4331 from sbwalker/dev
fix issue with primary key on SearchContentWord table
2024-06-10 16:47:45 -04:00
0d493b3250 fix issue with primary key on SearchContentWord table 2024-06-10 16:47:32 -04:00
74530d4b1e Merge pull request #4330 from sbwalker/dev
remove unnecessary using statements
2024-06-10 16:23:10 -04:00
7548c52e21 remove unnecessary using statements 2024-06-10 16:22:58 -04:00
fa49844c64 Merge pull request #4329 from sbwalker/dev
remove List from method name to conform to Oqtane naming conventions
2024-06-10 16:17:19 -04:00
3508ae1e0a remove List from method name to conform to Oqtane naming conventions 2024-06-10 16:17:05 -04:00
f013ee64a2 Updated Module Template with ISearchable implementation 2024-06-10 22:10:39 +02:00
af3da7ca6e update to template.json files to align with Oqtane version 2024-06-10 21:54:44 +02:00
cb728f65b3 Update Project Templates from 8.0.5 - 8.0.6 2024-06-10 21:50:30 +02:00
af6af190cc Merge pull request #4325 from thabaum/update-package-dependences-v5.2.0
Fixes #4324: Updates package dependences and prepares v5.2.0 release
2024-06-10 15:06:08 -04:00
cbcc8455ca Merge pull request #4326 from sbwalker/dev
refactored to move AdminSiteTemplate out of SiteRepository
2024-06-10 14:55:21 -04:00
af35fb79fe refactored to move AdminSiteTemplate out of SiteRepository 2024-06-10 14:55:06 -04:00
0515aaa946 Prepare v5.2.0 Release 2024-06-10 10:48:46 -07:00
1d00330e7a Prepare v5.2.0 Release and Package Dependencies 2024-06-10 10:48:18 -07:00
3fa6dcea16 Prepare v5.2.0 Release and Package Dependencies 2024-06-10 10:47:03 -07:00
51425cac4a Prepare v5.2.0 Release 2024-06-10 10:44:40 -07:00
7ce61a5d2b Prepare v5.2.0 Release 2024-06-10 10:43:54 -07:00
5e5caa979b Prepare v5.2.0 Release 2024-06-10 10:43:19 -07:00
a719518f8f Prepare v5.2.0 Release 2024-06-10 10:42:57 -07:00
d0e5aef443 Prepare v5.2.0 Release 2024-06-10 10:42:32 -07:00
b82a811c33 Prepare v5.2.0 Release and Package Dependencies 2024-06-10 10:41:51 -07:00
3356dcf8f7 Prepare v5.2.0 Release 2024-06-10 10:41:10 -07:00
8f1cc26537 Prepare v5.2.0 Release 2024-06-10 10:40:50 -07:00
27dafa83ac Prepare Update v5.2.0 Package Dependencies 2024-06-10 10:38:52 -07:00
c1be1f329f Prepare Update v5.2.0 Package Dependencies 2024-06-10 10:37:51 -07:00
c757cef549 Prepare Update v5.2.0 Package Dependencies 2024-06-10 10:37:21 -07:00
da9e4c026c Prepare Update v5.2.0 Package Dependencies 2024-06-10 10:37:04 -07:00
5821d67e69 Prepare Update v5.2.0 Package Dependencies 2024-06-10 10:36:47 -07:00
ed14f6d13f Prepare Update v5.2.0 Package Dependencies 2024-06-10 10:35:43 -07:00
35bdd8b4ef Prepare Update v5.2.0 Package Dependencies 2024-06-10 10:35:16 -07:00
9cf2d30e77 Prepare Update v5.2.0 2024-06-10 10:32:30 -07:00
a2140a3b7b Update v5.2.0 Package Dependencies 2024-06-10 10:30:37 -07:00
900d026bcb Update v5.2.0 Package Dependencies 2024-06-10 10:29:58 -07:00
68604ec15a Update Oqtane.Database.PostgreSQL.csproj Package Dependencies 2024-06-10 10:28:51 -07:00
f7c0ebb8d3 Update Oqtane.Database.MySQL.csproj prepare 5.2.0 2024-06-10 10:27:53 -07:00
8be5d0c72d Update Oqtane.Database.MySQL.csproj Package Dependencies 2024-06-10 10:27:15 -07:00
15c8b724e6 Update Oqtane.Client.csproj Package Dependencies 2024-06-10 10:24:52 -07:00
d6949200f9 Merge pull request #4323 from sbwalker/dev
move Search page/module to Admin template so that it is always provisioned
2024-06-10 12:33:31 -04:00
1c2abe794a move Search page/module to Admin template so that it is always provisioned 2024-06-10 12:33:09 -04:00
0bcf393586 Merge pull request #4320 from sbwalker/dev
search refactoring
2024-06-08 16:24:32 -04:00
bc0573918f search refactoring 2024-06-08 16:14:56 -04:00
175675ad99 Merge pull request #4317 from zyhfish/task/fix-issue-4316
Fix #4316: add text editor interfaces.
2024-06-07 14:44:40 -04:00
Ben
b00c2afc46 Fix #4316: move the raw html editor into quill editor instance. 2024-06-07 08:35:42 +08:00
Ben
c125a7fe07 Fix #4316: add text editor interfaces. 2024-06-06 16:39:35 +08:00
a42ab32436 Merge pull request #4310 from zyhfish/task/fix-searchbox-responsive-issue
Fix #4309: make searchbox responsive.
2024-06-05 07:48:55 -04:00
797a64976e Merge pull request #4311 from fonsecaf/fix-cookie-date-culture-format
Fix Cookie Date Conversion to Respect Culture and Format
2024-06-05 07:48:43 -04:00
ac377a8b68 Modified date parsing and formatting to use invariant culture, ensuring consistency and preventing non-ASCII characters in HTTP headers. 2024-06-05 13:39:31 +10:00
Ben
842b7b1402 Fix #4309: make searchbox responsive. 2024-06-05 10:27:38 +08:00
d449396ad5 Merge pull request #4304 from zyhfish/task/add-search-function
#4303: add search function.
2024-06-04 16:52:10 -04:00
532a87d064 Merge pull request #4307 from leigh-pointer/OqtaneControls
Oqtane controls updates
2024-06-04 16:50:53 -04:00
Ben
e1cdc7b387 return the words count to calculate the ranking. 2024-06-04 21:57:50 +08:00
Ben
d9d917e267 set search result page path to be parameter. 2024-06-04 21:09:25 +08:00
8048788042 Oqtane controls updates
ActionDialog and ActionLink now allow other icon sets whilst still adhering to the legacy "oi oi-" icon set.
Pager added SearchBoxClass Class parameter to the Search div.
TabStrip added a TabContentClass Class parameter to the tab content div.
2024-06-04 12:21:59 +02:00
Ben
790fc88e47 using correct module id value. 2024-06-04 17:50:29 +08:00
Ben
7f970d489f refactoring the code. 2024-06-04 17:32:31 +08:00
Ben
9d85ca07f4 #4303: add search function. 2024-06-03 21:19:42 +08:00
d75e3acdf3 Merge pull request #4302 from sbwalker/dev
changes as a result of #4299 related to  PageState.Modules
2024-06-03 07:42:46 -04:00
694cda0e99 changes as a result of #4299 related to PageState.Modules 2024-06-03 07:42:22 -04:00
94f134c6a7 Merge pull request #4301 from zyhfish/task/fix-add-page-issue
Fix Add Page Issue
2024-06-01 09:04:28 -04:00
Ben
83f329d93c Fix Add Page Issue 2024-06-01 09:08:43 +08:00
de49387fca Merge pull request #4300 from sbwalker/dev
remove LoadTestingSiteTemplate
2024-05-31 16:51:45 -04:00
e5567f2f46 remove LoadTestingSiteTemplate 2024-05-31 16:51:27 -04:00
80f545f3d5 Merge pull request #4299 from sbwalker/dev
scalability improvements
2024-05-31 16:23:50 -04:00
06f0cc70b8 scalability improvements 2024-05-31 16:23:36 -04:00
cf6b7544b0 Update README.md 2024-05-28 15:20:37 -04:00
d511c6334a Update README.md 2024-05-28 15:20:15 -04:00
0224fd6d54 Update README.md 2024-05-28 15:17:27 -04:00
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
206 changed files with 6650 additions and 2712 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

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components.Authorization;
using Oqtane.Interfaces;
using Oqtane.Providers;
using Oqtane.Services;
using Oqtane.Shared;
@ -51,6 +52,10 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<IVisitorService, VisitorService>();
services.AddScoped<ISyncService, SyncService>();
// providers
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.TextAreaTextEditor>();
return services;
}
}

View File

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

View File

@ -0,0 +1,19 @@
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.Admin.Files
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "File Management",
Description = "File Management",
Version = Constants.Version,
Categories = "Admin",
ServerManagerType = "Oqtane.Modules.Admin.Files.Manager.FileManager, Oqtane.Server"
};
}
}

View File

@ -10,9 +10,7 @@
}
else
{
<ActionLink Action="Log" Class="btn btn-secondary" Text="View Logs" ResourceKey="ViewJobs" />
<button type="button" class="btn btn-secondary" @onclick="(async () => await Refresh())">@Localizer["Refresh.Text"]</button>
<br />
<button type="button" class="btn btn-secondary" @onclick="Refresh">@Localizer["Refresh.Text"]</button>
<br />
<Pager Items="@_jobs" SearchProperties="Name">
@ -26,8 +24,8 @@ else
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td>
<td><ActionLink Action="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td>
<td><ActionLink Action="Log" Text="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
<td>@context.Name</td>
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>
@ -44,21 +42,29 @@ else
</td>
</Row>
</Pager>
<br />
<ActionLink Action="Log" Class="btn btn-secondary" Text="View All Logs" ResourceKey="ViewLogs" />
}
@code {
private List<Job> _jobs;
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
protected override async Task OnInitializedAsync()
{
_jobs = await JobService.GetJobsAsync();
await GetJobs();
if (_jobs.Count == 0)
{
AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning);
}
}
}
private async Task GetJobs()
{
_jobs = await JobService.GetJobsAsync();
}
private string DisplayStatus(bool isEnabled, bool isExecuting)
{
@ -146,7 +152,7 @@ else
private async Task Refresh()
{
_jobs = await JobService.GetJobsAsync();
await GetJobs();
StateHasChanged();
}
}

View File

@ -10,6 +10,9 @@
}
else
{
<button type="button" class="btn btn-secondary" @onclick="Refresh">@Localizer["Refresh"]</button>
<br /><br />
<Pager Items="@_jobLogs">
<Header>
<th>@SharedLocalizer["Name"]</th>
@ -35,6 +38,11 @@ else
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnParametersSetAsync()
{
await GetJobLogs();
}
private async Task GetJobLogs()
{
_jobLogs = await JobLogService.GetJobLogsAsync();
@ -67,4 +75,10 @@ else
return status;
}
private async Task Refresh()
{
await GetJobLogs();
StateHasChanged();
}
}

View File

@ -93,6 +93,7 @@
private string _code = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override bool? Prerender => true;
public override List<Resource> Resources => new List<Resource>()
{

View File

@ -63,7 +63,7 @@ else
<th>@Localizer["Function"]</th>
</Header>
<Row>
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Text="Details" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
<td class="@GetClass(context.Function)">@context.LogDate</td>
<td class="@GetClass(context.Function)">@context.Level</td>
<td class="@GetClass(context.Function)">@context.Feature</td>
@ -86,47 +86,48 @@ else
</div>
</div>
<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>
</TabStrip>
}
@code {
private string _level = "-";
private string _function = "-";
private string _rows = "10";
private int _page = 1;
private List<Log> _logs;
private int _retention = 30;
private string _level = "-";
private string _function = "-";
private string _rows = "10";
private int _page = 1;
private List<Log> _logs;
private int _retention = 30;
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnParametersSetAsync()
{
try
{
if (UrlParameters.ContainsKey("level"))
{
_level = UrlParameters["level"];
}
if (UrlParameters.ContainsKey("function"))
{
_function = UrlParameters["function"];
}
if (UrlParameters.ContainsKey("rows"))
{
_rows = UrlParameters["rows"];
}
if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page))
{
_page = page;
}
protected override async Task OnParametersSetAsync()
{
try
{
if (UrlParameters.ContainsKey("level"))
{
_level = UrlParameters["level"];
}
if (UrlParameters.ContainsKey("function"))
{
_function = UrlParameters["function"];
}
if (UrlParameters.ContainsKey("rows"))
{
_rows = UrlParameters["rows"];
}
if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page))
{
_page = page;
}
await GetLogs();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30"));
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30"));
}
catch (Exception ex)
{
@ -213,22 +214,37 @@ else
return classname;
}
private async Task SaveSiteSettings()
{
try
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true);
private async Task SaveSiteSettings()
{
try
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
}
}
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
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)
{

View File

@ -125,7 +125,7 @@
<TabPanel Name="Upload" ResourceKey="Upload" Heading="Upload">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Module: </Label>
<Label Class="col-sm-3" HelpText="Upload one or more module packages." ResourceKey="Module">Module: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div>

View File

@ -10,6 +10,7 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject IPageModuleService PageModuleService
@inject IModuleService ModuleService
@inject IPageService PageService
@if (_initialized)
{
@ -32,7 +33,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
<div class="col-sm-9">
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" required />
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" />
</div>
</div>
<div class="row mb-1 align-items-center">
@ -306,15 +307,16 @@
_languages = _languages.OrderBy(item => item.Name).ToList();
}
// Group modules by PageId
// Get distinct PageIds where modules are present
var distinctPageIds = PageState.Modules
.Where(md => md.ModuleDefinition.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false)
// get distinct pages where module exists
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
var distinctPageIds = modules
.Where(md => md.ModuleDefinition?.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false)
.Select(md => md.PageId)
.Distinct();
// Filter and retrieve the corresponding pages
_pagesWithModules = PageState.Pages
// retrieve the pages which contain the module
var pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
_pagesWithModules = pages
.Where(pg => distinctPageIds.Contains(pg.PageId) && pg.IsDeleted == false)
.ToList();

View File

@ -1,6 +1,7 @@
@namespace Oqtane.Modules.Admin.ModuleDefinitions
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IModuleService ModuleService
@inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService
@inject IStringLocalizer<Index> Localizer
@ -50,7 +51,7 @@ else
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
<td>
@if (context.AssemblyName != Constants.ClientId)
{
@ -70,7 +71,7 @@ else
}
</td>
<td>
@if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
@if (context.AssemblyName == Constants.ClientId || _modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
{
<span>@SharedLocalizer["Yes"]</span>
}
@ -99,6 +100,7 @@ else
}
@code {
private List<Module> _modules;
private List<ModuleDefinition> _allModuleDefinitions;
private List<ModuleDefinition> _moduleDefinitions;
private List<Package> _packages;
@ -111,6 +113,7 @@ else
{
try
{
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
await LoadModuleDefinitions();

View File

@ -3,6 +3,7 @@
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IThemeService ThemeService
@inject IPageService PageService
@inject IModuleService ModuleService
@inject IPageModuleService PageModuleService
@inject IStringLocalizer<Settings> Localizer
@ -79,14 +80,16 @@
}
else
{
foreach (Page p in PageState.Pages)
if (_pages != null)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
foreach (Page p in _pages)
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
}
}
}
}
</select>
</div>
@ -94,7 +97,7 @@
</div>
}
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<TabPanel Name="Permissions" Heading="Permissions" ResourceKey="Permissions">
@if (_permissions != null)
{
<div class="container">
@ -126,9 +129,8 @@
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
</form>
@code {
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings";
private ElementReference form;
private bool validated = false;
@ -144,7 +146,7 @@
private PermissionGrid _permissionGrid;
private Type _moduleSettingsType;
private object _moduleSettings;
private string _moduleSettingsTitle = "Module Settings";
private string _moduleSettingsTitle;
private RenderFragment ModuleSettingsComponent { get; set; }
private Type _containerSettingsType;
private object _containerSettings;
@ -155,11 +157,15 @@
private DateTime modifiedon;
private DateTime? _effectivedate = null;
private DateTime? _expirydate = null;
private List<Page> _pages;
protected override void OnInitialized()
protected override async Task OnInitializedAsync()
{
SetModuleTitle(Localizer["ModuleSettings.Title"]);
_module = ModuleState.ModuleDefinition.Name;
_title = ModuleState.Title;
_moduleSettingsTitle = Localizer["ModuleSettings.Heading"];
_pane = ModuleState.Pane;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType;
@ -172,6 +178,7 @@
modifiedon = ModuleState.ModifiedOn;
_effectivedate = Utilities.UtcAsLocalDate(ModuleState.EffectiveDate);
_expirydate = Utilities.UtcAsLocalDate(ModuleState.ExpiryDate);
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
if (ModuleState.ModuleDefinition != null)
{

View File

@ -26,7 +26,7 @@
<div class="col-sm-9">
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in PageState.Pages)
@foreach (Page page in _pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
{
@ -198,12 +198,6 @@
</div>
</div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings">
@_themeSettingsComponent
</TabPanel>
}
</TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
@ -219,6 +213,7 @@
private bool validated = false;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pages;
private int _pageId;
private string _name;
private string _parentid = "-1";
@ -238,9 +233,6 @@
private string _bodycontent;
private string _permissions = null;
private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment _themeSettingsComponent { get; set; }
private bool _refresh = false;
protected Page _parent = null;
protected Dictionary<string, string> _icons;
@ -252,6 +244,8 @@
{
try
{
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
if (PageState.QueryString.ContainsKey("id"))
{
_pageId = Int32.Parse(PageState.QueryString["id"]);
@ -272,7 +266,7 @@
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = PageState.Site.DefaultContainerType;
_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 _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
@ -281,7 +275,6 @@
}
_effectivedate = Utilities.UtcAsLocalDate(PageState.Page.EffectiveDate);
_expirydate = Utilities.UtcAsLocalDate(PageState.Page.ExpiryDate);
ThemeSettings();
_initialized = true;
}
else
@ -303,7 +296,7 @@
{
_parentid = (string)e.Value;
_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 _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
@ -324,7 +317,6 @@
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = _containers.First().TypeName;
ThemeSettings();
StateHasChanged();
// if theme chosen is different than default site theme, display warning message to user
@ -334,28 +326,6 @@
}
}
private void ThemeSettings()
{
_themeSettingsType = null;
_themeSettingsComponent = null;
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
if (_themeSettingsType != null)
{
_themeSettingsComponent = builder =>
{
builder.OpenComponent(0, _themeSettingsType);
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
builder.AddComponentReferenceCapture(2, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
builder.CloseComponent();
};
}
_refresh = true;
}
}
private async Task SavePage()
{
validated = true;
@ -385,48 +355,36 @@
page.ParentId = Int32.Parse(_parentid);
}
// path can be a link to an external url
if (!_path.Contains("://"))
if (string.IsNullOrEmpty(_path))
{
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(_path.LastIndexOf("/") + 1);
_path = _path.Substring(0, _path.Length - 1);
}
if (_parentid == "-1")
{
page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
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;
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (_parentid == "-1")
{
page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
page.Path = _path;
Page parent = _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);
if (_pages.Any(item => item.Path == page.Path))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
@ -446,11 +404,11 @@
page.Order = 0;
break;
case "<":
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
child = _pages.Where(item => item.PageId == _childid).FirstOrDefault();
page.Order = child.Order - 1;
break;
case ">":
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
child = _pages.Where(item => item.PageId == _childid).FirstOrDefault();
page.Order = child.Order + 1;
break;
case ">>":
@ -493,18 +451,11 @@
await logger.LogInformation("Page Added {Page}", page);
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(PageState.ReturnUrl, true);
NavigationManager.NavigateTo(NavigateUrl(page.Path), true); // redirect to page added and reload
}
else
{
if (!page.Path.Contains("://"))
{
NavigationManager.NavigateTo(page.Path); // redirect to new page created
}
else
{
NavigationManager.NavigateTo(NavigateUrl("admin/pages"));
}
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
}
}
else

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Admin.Pages
@using Oqtane.Interfaces
@using System.Globalization
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IPageService PageService
@ -30,7 +31,7 @@
<div class="col-sm-9">
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in PageState.Pages)
@foreach (Page page in _pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList) && page.PageId != _pageId)
{
@ -301,6 +302,7 @@
private bool validated = false;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pages;
private int _pageId;
private string _name;
private string _currentparentid;
@ -344,6 +346,7 @@
{
try
{
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
_pageId = Int32.Parse(PageState.QueryString["id"]);
_page = await PageService.GetPageAsync(_pageId);
_icons = await SystemService.GetIconsAsync();
@ -359,10 +362,10 @@
else
{
_parentid = _page.ParentId.ToString();
_parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
_parent = _pages.FirstOrDefault(item => item.PageId == _page.ParentId);
}
_children = new List<Page>();
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
foreach (Page p in _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))
{
@ -380,7 +383,7 @@
}
else
{
if (_path.Contains("/") & !_path.Contains("://"))
if (_path.Contains("/"))
{
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
@ -414,7 +417,7 @@
_permissions = _page.PermissionList;
// page modules
_pageModules = PageState.Modules.Where(m => m.PageId == _page.PageId).ToList();
_pageModules = PageState.Modules;
// audit
_createdby = _page.CreatedBy;
@ -446,8 +449,8 @@
{
_parentid = (string)e.Value;
_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 _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
{
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
@ -529,48 +532,36 @@
_page.ParentId = Int32.Parse(_parentid);
}
// path can be a link to an external url
if (!_path.Contains("://"))
if (string.IsNullOrEmpty(_path))
{
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(_path.LastIndexOf("/") + 1);
_path = _path.Substring(0, _path.Length - 1);
}
if (_parentid == "-1")
{
_page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
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;
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (_parentid == "-1")
{
_page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
_page.Path = _path;
Page parent = _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);
if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId))
{
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
@ -592,11 +583,11 @@
_page.Order = 0;
break;
case "<":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
child = _pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) _page.Order = child.Order - 1;
break;
case ">":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
child = _pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) _page.Order = child.Order + 1;
break;
case ">>":
@ -654,18 +645,11 @@
await logger.LogInformation("Page Saved {Page}", _page);
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(PageState.ReturnUrl, true);
NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
}
else
{
if (!_page.Path.Contains("://"))
{
NavigationManager.NavigateTo(NavigateUrl(), true); // redirect to page being edited
}
else
{
NavigationManager.NavigateTo(NavigateUrl("admin/pages"));
}
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
}
}
else

View File

@ -5,11 +5,11 @@
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.Pages != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
@if (_pages != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" />
<Pager Items="@PageState.Pages.Where(item => !item.IsDeleted)" SearchProperties="Name">
<Pager Items="@_pages.Where(item => !item.IsDeleted)" SearchProperties="Name">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
@ -17,7 +17,7 @@
<th>@SharedLocalizer["Name"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
<td>@(new string('-', context.Level * 2))@(context.Name)</td>
@ -28,6 +28,21 @@
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private List<Page> _pages;
protected override async Task OnInitializedAsync()
{
try
{
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Pages {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
}
}
private async Task DeletePage(Page page)
{
try

View File

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

View File

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

View File

@ -0,0 +1,102 @@
@namespace Oqtane.Modules.Admin.Search
@inherits ModuleBase
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="searchprovider" HelpText="Specify the search provider for this site" ResourceKey="SearchProvider">Search Provider: </Label>
<div class="col-sm-9">
<input id="searchprovider" class="form-control" @bind="@_searchProvider" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="enabled" HelpText="Specify if search indexing is enabled" ResourceKey="Enabled">Indexing Enabled? </Label>
<div class="col-sm-9">
<select id="enabled" class="form-select" @bind="@_enabled">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="lastindexedon" HelpText="The date/time which the site was last indexed on" ResourceKey="LastIndexedOn">Last Indexed: </Label>
<div class="col-sm-9">
<input id="lastindexedon" class="form-control" @bind="@_lastIndexedOn" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="ignorepages" HelpText="Comma delimited list of pages which should be ignored (based on their path)" ResourceKey="IgnorePages">Ignore Pages: </Label>
<div class="col-sm-9">
<textarea id="ignorepages" class="form-control" @bind="@_ignorePages" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="ignoreentities" HelpText="Comma delimited list of entities which should be ignored" ResourceKey="IgnoreEntities">Ignore Entities: </Label>
<div class="col-sm-9">
<textarea id="ignoreentities" class="form-control" @bind="@_ignoreEntities" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="minimumwordlength" HelpText="Minimum length of a word to be indexed" ResourceKey="MinimumWordLength">Word Length: </Label>
<div class="col-sm-9">
<input id="minimumwordlength" class="form-control" type="number" min="0" step="1" @bind="@_minimumWordLength" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="ignorewords" HelpText="Comma delimited list of words which should be ignored" ResourceKey="IgnoreWords">Ignore Words: </Label>
<div class="col-sm-9">
<textarea id="ignorewords" class="form-control" @bind="@_ignoreWords" rows="3"></textarea>
</div>
</div>
</div>
<br /><br />
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
<br /><br />
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private string _searchProvider;
private string _enabled;
private string _lastIndexedOn;
private string _ignorePages;
private string _ignoreEntities;
private string _minimumWordLength;
private string _ignoreWords;
protected override async Task OnInitializedAsync()
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_searchProvider = SettingService.GetSetting(settings, "Search_SearchProvider", Constants.DefaultSearchProviderName);
_enabled = SettingService.GetSetting(settings, "Search_Enabled", "True");
_lastIndexedOn = SettingService.GetSetting(settings, "Search_LastIndexedOn", "");
_ignorePages = SettingService.GetSetting(settings, "Search_IgnorePages", "");
_ignoreEntities = SettingService.GetSetting(settings, "Search_IgnoreEntities", "");
_minimumWordLength = SettingService.GetSetting(settings, "Search_MininumWordLength", "3");
_ignoreWords = SettingService.GetSetting(settings, "Search_IgnoreWords", "");
}
private async Task Save()
{
try
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "Search_SearchProvider", _searchProvider);
settings = SettingService.SetSetting(settings, "Search_Enabled", _enabled, true);
settings = SettingService.SetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn, true);
settings = SettingService.SetSetting(settings, "Search_IgnorePages", _ignorePages, true);
settings = SettingService.SetSetting(settings, "Search_IgnoreEntities", _ignoreEntities, true);
settings = SettingService.SetSetting(settings, "Search_MininumWordLength", _minimumWordLength, true);
settings = SettingService.SetSetting(settings, "Search_IgnoreWords", _ignoreWords, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
AddModuleMessage(Localizer["Success.Save"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Search Settings {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
}
}
}

View File

@ -0,0 +1,141 @@
@using Oqtane.Services
@using System.Net
@namespace Oqtane.Modules.Admin.SearchResults
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ISearchResultsService SearchResultsService
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="search-result-container">
<div class="row">
<div class="col">
<form method="post" @formname="SearchResultsForm" @onsubmit="Search" data-enhance>
<div class="input-group mb-3">
<span class="input-group-text">@Localizer["SearchLabel"]</span>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<input type="text" name="keywords" class="form-control shadow-none" maxlength="50"
aria-label="Keywords"
placeholder="@Localizer["SearchPlaceholder"]"
@bind="@_keywords">
<button class="btn btn-primary" type="submit">@SharedLocalizer["Search"]</button>
<a class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Reset"]</a>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-md-12 mb-3">
@if (_loading)
{
<div class="app-progress-indicator"></div>
}
else
{
@if (_searchResults != null && _searchResults.Results != null)
{
if (_searchResults.Results.Any())
{
<Pager Items="@_searchResults?.Results"
Format="Grid"
Columns="1"
Toolbar="Bottom"
Parameters="@($"q={_keywords}")">
<Row>
<div class="search-item mb-2">
<h4 class="mb-1"><a href="@context.Url">@context.Title</a></h4>
<p class="mb-0 text-muted">@((MarkupString)context.Snippet)</p>
</div>
</Row>
</Pager>
}
else
{
<div class="alert alert-info show mt-3" role="alert">
@Localizer["NoResult"]
</div>
}
}
<div class="clearfix"></div>
}
</div>
</div>
</div>
@code {
public override string RenderMode => RenderModes.Static;
private string _includeEntities;
private string _excludeEntities;
private string _fromDate;
private string _toDate;
private string _pageSize;
private string _sortField;
private string _sortOrder;
private string _bodyLength;
private string _keywords;
private SearchResults _searchResults;
private bool _loading;
[SupplyParameterFromForm(FormName = "SearchResultsForm")]
public string KeyWords { get => ""; set => _keywords = value; }
protected override async Task OnInitializedAsync()
{
_includeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_IncludeEntities", "");
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
_fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", DateTime.MinValue.ToString());
_toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", DateTime.MaxValue.ToString());
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", int.MaxValue.ToString());
_sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance");
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
if (_keywords == null && PageState.QueryString.ContainsKey("q"))
{
_keywords = WebUtility.UrlDecode(PageState.QueryString["q"]);
await PerformSearch();
}
}
private void Search()
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, $"page=1&q={_keywords}"));
}
private async Task PerformSearch()
{
_loading = true;
StateHasChanged();
if (!string.IsNullOrEmpty(_keywords))
{
var searchQuery = new SearchQuery
{
SiteId = PageState.Site.SiteId,
Alias = PageState.Alias,
Keywords = _keywords,
IncludeEntities = _includeEntities,
ExcludeEntities = _excludeEntities,
FromDate = (!string.IsNullOrEmpty(_fromDate)) ? DateTime.Parse(_fromDate) : DateTime.MinValue,
ToDate = (!string.IsNullOrEmpty(_toDate)) ? DateTime.Parse(_toDate) : DateTime.MaxValue,
PageSize = (!string.IsNullOrEmpty(_pageSize)) ? int.Parse(_pageSize) : int.MaxValue,
PageIndex = 0,
SortField = (!string.IsNullOrEmpty(_sortField)) ? (SearchSortField)Enum.Parse(typeof(SearchSortField), _sortField) : SearchSortField.Relevance,
SortOrder = (!string.IsNullOrEmpty(_sortOrder)) ? (SearchSortOrder)Enum.Parse(typeof(SearchSortOrder), _sortOrder) : SearchSortOrder.Descending,
BodyLength = (!string.IsNullOrEmpty(_bodyLength)) ? int.Parse(_bodyLength) : 255
};
_searchResults = await SearchResultsService.GetSearchResultsAsync(searchQuery);
}
else
{
AddModuleMessage(Localizer["NoCriteria"], MessageType.Info, "bottom");
}
_loading = false;
StateHasChanged();
}
}

View File

@ -0,0 +1,19 @@
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.Admin.SearchResults
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Search Results",
Description = "Search Results",
Categories = "Admin",
Version = Constants.Version,
SettingsType = "Oqtane.Modules.Admin.SearchResults.Settings, Oqtane.Client"
};
}
}

View File

@ -0,0 +1,123 @@
@namespace Oqtane.Modules.Admin.SearchResults
@inherits ModuleBase
@inject ISettingService SettingService
@implements Oqtane.Interfaces.ISettingsControl
@inject IStringLocalizer<Settings> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="includeentities" ResourceKey="IncludeEntities" ResourceType="@resourceType" HelpText="Comma delimited list of entities to include in the search results. By default all entities will be included.">Include Entities: </Label>
<div class="col-sm-9">
<input id="includeentities" type="text" class="form-control" @bind="@_includeEntities" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="excludeentities" ResourceKey="ExcludeEntities" ResourceType="@resourceType" HelpText="Comma delimited list of entities to exclude from search results. By default no entities will be excluded.">Exclude Entities: </Label>
<div class="col-sm-9">
<input id="excludeentities" class="form-control" @bind="@_excludeEntities" required></input>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="daterange" ResourceKey="DateRange" ResourceType="@resourceType" HelpText="Enter the date range for search results. The default includes all content.">Date Range: </Label>
<div class="col-sm-9">
<div class="input-group">
<input type="date" class="form-control" @bind="@_fromDate" />
<span class="input-group-text">@Localizer["To"]</span>
<input type="date" class="form-control" @bind="@_toDate" />
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pagesize" ResourceKey="PageSize" ResourceType="@resourceType" HelpText="The maximum number of search results to retrieve. The default is unlimited.">Page Size: </Label>
<div class="col-sm-9">
<input id="pagesize" type="text" class="form-control" @bind="@_pageSize" />
</div>
</div>
<hr />
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sortfield" ResourceKey="SortField" ResourceType="@resourceType" HelpText="Specify the default sort field">Sort By: </Label>
<div class="col-sm-9">
<select id="softfield" class="form-select" @bind="@_sortField">
<option value="Relevance">@Localizer["Relevance"]</option>
<option value="Title">@Localizer["Title"]</option>
<option value="LastModified">@Localizer["LastModified"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sortorder" ResourceKey="SortOrder" ResourceType="@resourceType" HelpText="Specify the default sort order">Sort Order: </Label>
<div class="col-sm-9">
<select id="softorder" class="form-select" @bind="@_sortOrder">
<option value="Ascending">@Localizer["Ascending"]</option>
<option value="Descending">@Localizer["Descending"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="bodylength" ResourceKey="BodyLength" ResourceType="@resourceType" HelpText="The number of characters displayed for each search result summary. The default is 255 characters.">Body Size: </Label>
<div class="col-sm-9">
<input id="bodylength" type="text" class="form-control" @bind="@_bodyLength" />
</div>
</div>
</div>
</form>
@code {
private string resourceType = "Oqtane.Modules.Admin.SearchResults.Settings, Oqtane.Client"; // for localization
private ElementReference form;
private bool validated = false;
private string _includeEntities;
private string _excludeEntities;
private DateTime? _fromDate = null;
private DateTime? _toDate = null;
private string _pageSize;
private string _sortField;
private string _sortOrder;
private string _bodyLength;
protected override void OnInitialized()
{
try
{
_includeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_IncludeEntities", "");
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
var fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", "");
_fromDate = (string.IsNullOrEmpty(fromDate)) ? null : DateTime.Parse(fromDate);
var toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", "");
_toDate = (string.IsNullOrEmpty(toDate)) ? null : DateTime.Parse(toDate);
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", "");
_sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance");
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
}
catch (Exception ex)
{
AddModuleMessage(ex.Message, MessageType.Error);
}
}
public async Task UpdateSettings()
{
try
{
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
settings = SettingService.SetSetting(settings, "SearchResults_IncludeEntities", _includeEntities);
settings = SettingService.SetSetting(settings, "SearchResults_ExcludeEntities", _excludeEntities);
settings = SettingService.SetSetting(settings, "SearchResults_From", _fromDate.ToString());
settings = SettingService.SetSetting(settings, "SearchResults_To", _toDate.ToString());
settings = SettingService.SetSetting(settings, "SearchResults_PageSize", _pageSize);
settings = SettingService.SetSetting(settings, "SearchResults_SortField", _sortField);
settings = SettingService.SetSetting(settings, "SearchResults_SortOrder", _sortOrder);
settings = SettingService.SetSetting(settings, "SearchResults_BodyLength", _bodyLength);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
{
AddModuleMessage(ex.Message, MessageType.Error);
}
}
}

View File

@ -1,13 +1,16 @@
@namespace Oqtane.Modules.Admin.Site
@inherits ModuleBase
@using System.Text.RegularExpressions
@using Microsoft.Extensions.DependencyInjection
@inject NavigationManager NavigationManager
@inject ISiteService SiteService
@inject IPageService PageService
@inject ITenantService TenantService
@inject IDatabaseService DatabaseService
@inject IAliasService AliasService
@inject IThemeService ThemeService
@inject ISettingService SettingService
@inject IServiceProvider ServiceProvider
@inject IStringLocalizer<Index> Localizer
@inject INotificationService NotificationService
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -27,7 +30,7 @@
<div class="col-sm-9">
<select id="homepage" class="form-select" @bind="@_homepageid" required>
<option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (Page page in PageState.Pages)
@foreach (Page page in _pages)
{
if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
{
@ -74,7 +77,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9">
<FileManager FileId="@_logofileid" Filter="@_ImageFiles" @ref="_logofilemanager" />
<FileManager FileId="@_logofileid" Filter="@_imageFiles" @ref="_logofilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
@ -125,18 +128,32 @@
</div>
</div>
</Section>
<Section Name="FileExtensions" Heading="File Extensions" ResourceKey="FileExtensions">
<Section Name="Functionality" Heading="Functionality" ResourceKey="Functionality">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="textEditor" HelpText="Select the text editor for the site" ResourceKey="TextEditor">Text Editor: </Label>
<div class="col-sm-9">
<select id="textEditor" class="form-select" @bind="@_textEditor" required>
@if (_textEditors != null)
{
@foreach (var textEditor in _textEditors)
{
<option value="@textEditor.Value">@textEditor.Key</option>
}
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="imageExt" HelpText="Enter a comma separated list of image file extensions" ResourceKey="ImageExtensions">Image Extensions: </Label>
<div class="col-sm-9">
<input id="imageExt" spellcheck="false" class="form-control" @bind="@_ImageFiles" />
<input id="imageExt" spellcheck="false" class="form-control" @bind="@_imageFiles" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="uploadableFileExt" HelpText="Enter a comma separated list of uploadable file extensions" ResourceKey="UploadableFileExtensions">Uploadable File Extensions: </Label>
<div class="col-sm-9">
<input id="uploadableFileExt" spellcheck="false" class="form-control" @bind="@_UploadableFiles" />
<input id="uploadableFileExt" spellcheck="false" class="form-control" @bind="@_uploadableFiles" />
</div>
</div>
</div>
@ -319,7 +336,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label>
<div class="col-sm-9">
<select id="rendermode" class="form-select" @bind="@_rendermode" required>
<select id="rendermode" class="form-select" value="@_rendermode" @onchange="(e => RenderModeChanged(e))" required>
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
@ -337,7 +354,7 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="prerender" HelpText="Specifies if interactive components should prerender their output" ResourceKey="Prerender">Prerender? </Label>
<Label Class="col-sm-3" For="prerender" HelpText="Specifies if interactive components should prerender their output on the server" ResourceKey="Prerender">Prerender: </Label>
<div class="col-sm-9">
<select id="prerender" class="form-select" @bind="@_prerender" required>
<option value="True">@SharedLocalizer["Yes"]</option>
@ -394,12 +411,15 @@
private bool _initialized = false;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pages;
private string _name = string.Empty;
private string _homepageid = "-";
private string _isdeleted;
private string _sitemap = "";
private string _siteguid = "";
private string _version = "";
private int _logofileid = -1;
private FileManager _logofilemanager;
private int _faviconfileid = -1;
@ -407,8 +427,15 @@
private string _themetype = "";
private string _containertype = "";
private string _admincontainertype = "";
private Dictionary<string, string> _textEditors = new Dictionary<string, string>();
private string _textEditor = "";
private string _imageFiles = string.Empty;
private string _uploadableFiles = string.Empty;
private string _headcontent = string.Empty;
private string _bodycontent = string.Empty;
private string _smtphost = string.Empty;
private string _smtpport = string.Empty;
private string _smtpssl = "False";
@ -419,25 +446,28 @@
private string _smtpsender = string.Empty;
private string _smtprelay = "False";
private string _smtpenabled = "True";
private string _ImageFiles = string.Empty;
private string _UploadableFiles = string.Empty;
private int _retention = 30;
private string _pwaisenabled;
private int _pwaappiconfileid = -1;
private FileManager _pwaappiconfilemanager;
private int _pwasplashiconfileid = -1;
private FileManager _pwasplashiconfilemanager;
private List<Alias> _aliases;
private int _aliasid = -1;
private string _aliasname;
private string _defaultalias;
private string _rendermode = RenderModes.Interactive;
private string _runtime = Runtimes.Server;
private string _prerender = "True";
private string _hybrid = "False";
private string _tenant = string.Empty;
private string _database = string.Empty;
private string _connectionstring = string.Empty;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
@ -454,6 +484,10 @@
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
_name = site.Name;
if (site.HomePageId != null)
{
@ -480,23 +514,23 @@
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
// functionality
var textEditors = ServiceProvider.GetServices<ITextEditor>();
foreach (var textEditor in textEditors)
{
_textEditors.Add(textEditor.Name, Utilities.GetFullTypeName(textEditor.GetType().AssemblyQualifiedName));
}
_textEditor = SettingService.GetSetting(settings, "TextEditor", Constants.DefaultTextEditor);
_imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
_uploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
// page content
_headcontent = site.HeadContent;
_bodycontent = site.BodyContent;
// PWA
_pwaisenabled = site.PwaIsEnabled.ToString();
if (site.PwaAppIconFileId != null)
{
_pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
// SMTP
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
@ -508,11 +542,16 @@
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
// file extensions
_ImageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
_ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles;
_UploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
_UploadableFiles = (string.IsNullOrEmpty(_UploadableFiles)) ? Constants.UploadableFiles : _UploadableFiles;
// PWA
_pwaisenabled = site.PwaIsEnabled.ToString();
if (site.PwaAppIconFileId != null)
{
_pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
// aliases
await GetAliases();
@ -572,6 +611,23 @@
}
}
private void RenderModeChanged(ChangeEventArgs e)
{
_rendermode = (string)e.Value;
switch (_rendermode)
{
case RenderModes.Interactive:
_prerender = "True";
break;
case RenderModes.Static:
_prerender = "False";
break;
case RenderModes.Headless:
_prerender = "False";
break;
}
}
private async Task SaveSite()
{
validated = true;
@ -656,160 +712,161 @@
}
}
site = await SiteService.UpdateSiteAsync(site);
site = await SiteService.UpdateSiteAsync(site);
// SMTP
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
// file extensions
settings = SettingService.SetSetting(settings, "ImageFiles", (_ImageFiles != Constants.ImageFiles) ? _ImageFiles.Replace(" ", "") : "", false);
settings = SettingService.SetSetting(settings, "UploadableFiles", (_UploadableFiles != Constants.UploadableFiles) ? _UploadableFiles.Replace(" ", "") : "", false);
// functionality
settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);
settings = SettingService.SetSetting(settings, "ImageFiles", (_imageFiles != Constants.ImageFiles) ? _imageFiles.Replace(" ", "") : "", false);
settings = SettingService.SetSetting(settings, "UploadableFiles", (_uploadableFiles != Constants.UploadableFiles) ? _uploadableFiles.Replace(" ", "") : "", false);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await logger.LogInformation("Site Settings Saved {Site}", site);
await logger.LogInformation("Site Settings Saved {Site}", site);
NavigationManager.NavigateTo(NavigateUrl(), true); // reload
}
}
else
{
AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error.SaveSite"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
NavigationManager.NavigateTo(NavigateUrl(), true); // reload
}
}
else
{
AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error.SaveSite"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task DeleteSite()
{
try
{
var aliases = await AliasService.GetAliasesAsync();
if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId))
{
await SiteService.DeleteSiteAsync(PageState.Site.SiteId);
await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId);
private async Task DeleteSite()
{
try
{
var aliases = await AliasService.GetAliasesAsync();
if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId))
{
await SiteService.DeleteSiteAsync(PageState.Site.SiteId);
await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId);
foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
}
foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
}
var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId);
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true);
}
else
{
AddModuleMessage(Localizer["Message.FailAuth.DeleteSite"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error.DeleteSite"], MessageType.Error);
}
}
var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId);
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true);
}
else
{
AddModuleMessage(Localizer["Message.FailAuth.DeleteSite"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error.DeleteSite"], MessageType.Error);
}
}
private async Task SendEmail()
{
if (_smtphost != "" && _smtpport != "" && _smtpsender != "")
{
try
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
await logger.LogInformation("Site SMTP Settings Saved");
private async Task SendEmail()
{
if (_smtphost != "" && _smtpport != "" && _smtpsender != "")
{
try
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
await logger.LogInformation("Site SMTP Settings Saved");
await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly."));
AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly."));
AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
await ScrollToPageTop();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Testing SMTP Configuration");
AddModuleMessage(Localizer["Error.Smtp.TestConfig"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["Message.Required.Smtp"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Testing SMTP Configuration");
AddModuleMessage(Localizer["Error.Smtp.TestConfig"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["Message.Required.Smtp"], MessageType.Warning);
}
}
private void ToggleSMTPPassword()
{
if (_smtppasswordtype == "password")
{
_smtppasswordtype = "text";
_togglesmtppassword = SharedLocalizer["HidePassword"];
}
else
{
_smtppasswordtype = "password";
_togglesmtppassword = SharedLocalizer["ShowPassword"];
}
}
private void ToggleSMTPPassword()
{
if (_smtppasswordtype == "password")
{
_smtppasswordtype = "text";
_togglesmtppassword = SharedLocalizer["HidePassword"];
}
else
{
_smtppasswordtype = "password";
_togglesmtppassword = SharedLocalizer["ShowPassword"];
}
}
private async Task GetAliases()
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_aliases = await AliasService.GetAliasesAsync();
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList();
}
}
private async Task GetAliases()
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_aliases = await AliasService.GetAliasesAsync();
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList();
}
}
private void AddAlias()
{
_aliases.Add(new Alias { AliasId = 0, Name = "", IsDefault = false });
_aliasid = 0;
_aliasname = "";
_defaultalias = "False";
StateHasChanged();
}
private void AddAlias()
{
_aliases.Add(new Alias { AliasId = 0, Name = "", IsDefault = false });
_aliasid = 0;
_aliasname = "";
_defaultalias = "False";
StateHasChanged();
}
private void EditAlias(Alias alias)
{
_aliasid = alias.AliasId;
_aliasname = alias.Name;
_defaultalias = alias.IsDefault.ToString();
StateHasChanged();
}
private void EditAlias(Alias alias)
{
_aliasid = alias.AliasId;
_aliasname = alias.Name;
_defaultalias = alias.IsDefault.ToString();
StateHasChanged();
}
private async Task DeleteAlias(Alias alias)
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
await GetAliases();
StateHasChanged();
}
}
private async Task DeleteAlias(Alias alias)
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
await GetAliases();
StateHasChanged();
}
}
private async Task SaveAlias()
{
@ -861,11 +918,11 @@
}
}
private async Task CancelAlias()
{
await GetAliases();
_aliasid = -1;
_aliasname = "";
StateHasChanged();
}
private async Task CancelAlias()
{
await GetAliases();
_aliasid = -1;
_aliasname = "";
StateHasChanged();
}
}

View File

@ -26,6 +26,12 @@
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
</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">
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
<div class="col-sm-9">
@ -62,12 +68,6 @@
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
</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">
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
<div class="col-sm-9">
@ -165,13 +165,13 @@
private string _version = string.Empty;
private string _clrversion = 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 _environment = string.Empty;
private string _contentrootpath = string.Empty;
private string _webrootpath = string.Empty;
private string _servertime = string.Empty;
private string _tickcount = string.Empty;
private string _workingset = string.Empty;
private string _installationid = string.Empty;
@ -192,13 +192,13 @@
{
_clrversion = systeminfo["CLRVersion"].ToString();
_osversion = systeminfo["OSVersion"].ToString();
_machinename = systeminfo["MachineName"].ToString();
_process = systeminfo["Process"].ToString();
_machinename = systeminfo["MachineName"].ToString();
_ipaddress = systeminfo["IPAddress"].ToString();
_environment = systeminfo["Environment"].ToString();
_contentrootpath = systeminfo["ContentRootPath"].ToString();
_webrootpath = systeminfo["WebRootPath"].ToString();
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
_tickcount = TimeSpan.FromMilliseconds(Convert.ToInt64(systeminfo["TickCount"].ToString())).ToString();
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
}

View File

@ -125,7 +125,7 @@
<TabPanel Name="Upload" ResourceKey="Upload">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more theme packages. Once they are uploaded click Install to complete the installation." ResourceKey="Theme">Theme: </Label>
<Label Class="col-sm-3" HelpText="Upload one or more theme packages." ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div>

View File

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

View File

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

View File

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

View File

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

View File

@ -43,7 +43,7 @@ else
<th>@Localizer["Created"]</th>
</Header>
<Row>
<td><ActionLink Action="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
<td><ActionLink Action="Detail" Text="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
<td>@context.IPAddress</td>
<td>
@if (context.UserId != null)
@ -69,14 +69,20 @@ else
</select>
</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>
<div class="col-sm-9">
<textarea id="filter" class="form-control" @bind="@_filter" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention (Days): </Label>
<Label Class="col-sm-3" For="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention: </Label>
<div class="col-sm-9">
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
</div>
@ -103,7 +109,8 @@ else
private int _page = 1;
private List<Visitor> _visitors;
private string _tracking;
private string _filter = "";
private int _duration = 5;
private string _filter = "";
private int _retention = 30;
private string _correlation = "true";
@ -128,7 +135,8 @@ else
_tracking = PageState.Site.VisitorTracking.ToString();
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"));
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
}
@ -179,7 +187,8 @@ else
await SiteService.UpdateSiteAsync(site);
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, "VisitorCorrelation", _correlation, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);

View File

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

View File

@ -99,7 +99,7 @@
if (!string.IsNullOrEmpty(Text))
{
_text = Localize(nameof(Text), _text);
_text = Localize(nameof(Text), Text);
}
else
{
@ -145,7 +145,10 @@
if (!string.IsNullOrEmpty(IconName))
{
if (!IconName.Contains(" "))
// Check if IconName starts with "oi oi-"
bool startsWithOiOi = IconName.StartsWith("oi oi-");
if (!startsWithOiOi && !IconName.Contains(" "))
{
IconName = "oi oi-" + IconName;
}

View File

@ -38,13 +38,10 @@
protected void OnChange(ChangeEventArgs e)
{
if (!string.IsNullOrEmpty(e.Value.ToString()))
Value = e.Value.ToString();
if (ValueChanged.HasDelegate)
{
Value = e.Value.ToString();
if (ValueChanged.HasDelegate)
{
ValueChanged.InvokeAsync(Value);
}
ValueChanged.InvokeAsync(Value);
}
}
}

View File

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

View File

@ -11,7 +11,7 @@
@if (!string.IsNullOrEmpty(SearchProperties))
{
<form autocomplete="off">
<div class="input-group my-3">
<div class="input-group my-3 @SearchBoxClass">
<input type="text" id="pagersearch" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
@ -23,16 +23,16 @@
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
@ -40,30 +40,30 @@
if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
@ -73,8 +73,8 @@
@if (!string.IsNullOrEmpty(SearchProperties))
{
<form method="post" autocomplete="off" @formname="PagerForm" @onsubmit="Search" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<div class="input-group my-3">
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<div class="input-group my-3 @SearchBoxClass">
<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>
<a class="btn btn-secondary" href="@PageUrl(1, "")">@SharedLocalizer["Reset"]</a>
@ -86,16 +86,16 @@
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
@ -103,30 +103,30 @@
if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
@ -202,16 +202,16 @@
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
@ -219,30 +219,30 @@
if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
@ -250,16 +250,16 @@
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
@ -267,30 +267,30 @@
if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
<a class="page-link shadow-none" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
@ -359,6 +359,9 @@
[Parameter]
public string SearchProperties { get; set; } // comma delimited list of property names to include in search
[Parameter]
public string SearchBoxClass { get; set; } // class for Search box
[Parameter]
public string Parameters { get; set; } // optional - querystring parameters in the form of "id=x&name=y" used in static render mode

View File

@ -4,11 +4,11 @@ using System.Threading.Tasks;
namespace Oqtane.Modules.Controls
{
public class RichTextEditorInterop
public class QuillEditorInterop
{
private readonly IJSRuntime _jsRuntime;
public RichTextEditorInterop(IJSRuntime jsRuntime)
public QuillEditorInterop(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
@ -105,13 +105,25 @@ namespace Oqtane.Modules.Controls
}
}
public Task InsertImage(ElementReference quillElement, string imageUrl, string altText)
public ValueTask<int> GetCurrentCursor(ElementReference quillElement)
{
try
{
return _jsRuntime.InvokeAsync<int>("Oqtane.RichTextEditor.getCurrentCursor", quillElement);
}
catch
{
return new ValueTask<int>(Task.FromResult(0));
}
}
public Task InsertImage(ElementReference quillElement, string imageUrl, string altText, int editorIndex)
{
try
{
_jsRuntime.InvokeAsync<object>(
"Oqtane.RichTextEditor.insertQuillImage",
quillElement, imageUrl, altText);
quillElement, imageUrl, altText, editorIndex);
return Task.CompletedTask;
}
catch

View File

@ -0,0 +1,578 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@implements ITextEditor
@inject ISettingService SettingService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<QuillJSTextEditor> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="quill-text-editor">
<TabStrip ActiveTab="@_activetab">
@if (_allowRichText)
{
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
@if (_richfilemanager)
{
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
<div class="d-flex justify-content-center mb-2">
@if (_allowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
}
@if (_richfilemanager)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
}
</div>
<div class="row">
<div class="col">
<div @ref="@_toolBar">
@if (!string.IsNullOrEmpty(_toolbarContent))
{
@((MarkupString)_toolbarContent)
}
else
{
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
}
</div>
<div @ref="@_editorElement"></div>
</div>
</div>
</TabPanel>
}
@if (_allowRawHtml)
{
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
@if (_rawfilemanager)
{
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
<div class="d-flex justify-content-center mb-2">
@if (_allowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
}
@if (_rawfilemanager)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseRawFileManager">@Localizer["Close"]</button>
}
</div>
@if (ReadOnly)
{
<textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
}
else
{
<textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
}
</TabPanel>
}
@if (_allowSettings)
{
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
<div class="quill-text-editor-settings">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="Scope" ResourceKey="Scope" ResourceType="@resourceType" HelpText="Specify if settings are scoped to the module or site">Scope: </Label>
<div class="col-sm-9">
<select id="Scope" class="form-select" value="@_scopeSetting" @onchange="(e => ScopeChanged(e))">
<option value="Module">@SharedLocalizer["Module"]</option>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<option value="Site">@SharedLocalizer["Site"]</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="AllowRichText" ResourceKey="AllowRichText" ResourceType="@resourceType" HelpText="Specify if editors can use the Rich Text Editor">Rich Text Editor? </Label>
<div class="col-sm-9">
<select id="AllowRichText" class="form-select" @bind="@_allowRichTextSetting" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="AllowRawHtml" ResourceKey="AllowRawHtml" ResourceType="@resourceType" HelpText="Specify if editors can use the Raw HTML Editor">Raw HTML Editor? </Label>
<div class="col-sm-9">
<select id="AllowRawHtml" class="form-select" @bind="@_allowRawHtmlSetting" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="AllowFileManagement" ResourceKey="AllowFileManagement" ResourceType="@resourceType" HelpText="Specify if editors can upload and insert images">Insert Images? </Label>
<div class="col-sm-9">
<select id="AllowFileManagement" class="form-select" @bind="@_allowFileManagementSetting" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="Theme" ResourceKey="Theme" ResourceType="@resourceType" HelpText="Specify the Rich Text Editor's theme">Theme: </Label>
<div class="col-sm-9">
<input type="text" id="Theme" class="form-control" @bind="_themeSetting" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="DebugLevel" ResourceKey="DebugLevel" ResourceType="@resourceType" HelpText="Specify the Debug Level">Debug Level: </Label>
<div class="col-sm-9">
<select id="DebugLevel" class="form-select" @bind="_debugLevelSetting">
@foreach (var level in _debugLevels)
{
<option value="@level">@level</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="ToolbarContent" ResourceKey="ToolbarContent" ResourceType="@resourceType" HelpText="Specify any toolbar content to customize the Rich Text Editor">Toolbar Content: </Label>
<div class="col-sm-9">
<textarea id="ToolbarContent" class="form-control" @bind="_toolbarContentSetting" rows="3" />
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9 offset-sm-3">
<button type="button" class="btn btn-success" @onclick="@(async () => await UpdateSettings())">@Localizer["SaveSettings"]</button>
</div>
</div>
</div>
</TabPanel>
}
</TabStrip>
</div>
@code {
public string Name => "QuillJS";
private string resourceType = "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client";
private bool _settingsLoaded;
private bool _initialized = false;
private QuillEditorInterop _interop;
private FileManager _fileManager;
private string _activetab = "Rich";
private bool _allowSettings = false;
private bool _allowFileManagement = false;
private bool _allowRawHtml = false;
private bool _allowRichText = false;
private string _theme = "snow";
private string _debugLevel = "info";
private string _toolbarContent = string.Empty;
private string _scopeSetting = "Module";
private string _allowFileManagementSetting = "False";
private string _allowRawHtmlSetting = "False";
private string _allowRichTextSetting = "False";
private string _themeSetting = "snow";
private string _debugLevelSetting = "info";
private string _toolbarContentSetting = string.Empty;
private ElementReference _editorElement;
private ElementReference _toolBar;
private bool _richfilemanager = false;
private string _richhtml = string.Empty;
private string _originalrichhtml = string.Empty;
private bool _rawfilemanager = false;
private string _rawhtmlid = "RawHtmlEditor_" + Guid.NewGuid().ToString("N");
private string _rawhtml = string.Empty;
private string _originalrawhtml = string.Empty;
private string _message = string.Empty;
private bool _contentchanged = false;
private int _editorIndex;
private List<string> _debugLevels = new List<string> { "info", "log", "warn", "error" };
[Parameter]
public bool ReadOnly { get; set; }
[Parameter]
public string Placeholder { get; set; }
// the following parameters were supported by the original RichTextEditor and can be passed as optional static parameters
[Parameter]
public bool? AllowFileManagement { get; set; }
[Parameter]
public bool? AllowRichText { get; set; }
[Parameter]
public bool? AllowRawHtml { get; set; }
[Parameter]
public string Theme { get; set; }
[Parameter]
public string DebugLevel { get; set; }
public override List<Resource> Resources { get; set; } = new List<Resource>()
{
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", Location = ResourceLocation.Body },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body }
};
protected override void OnInitialized()
{
_interop = new QuillEditorInterop(JSRuntime);
if (string.IsNullOrEmpty(Placeholder))
{
Placeholder = Localizer["Placeholder"];
}
}
protected override void OnParametersSet()
{
LoadSettings();
if (!_allowRichText)
{
_activetab = "Raw";
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// include CSS theme
var interop = new Interop(JSRuntime);
await interop.IncludeLink("", "stylesheet", $"css/quill/quill.{_theme}.css", "text/css", "", "", "");
}
await base.OnAfterRenderAsync(firstRender);
if (_allowRichText)
{
if (firstRender)
{
await _interop.CreateEditor(
_editorElement,
_toolBar,
ReadOnly,
Placeholder,
_theme,
_debugLevel);
await _interop.LoadEditorContent(_editorElement, _richhtml);
// preserve a copy of the content (Quill sanitizes content so we need to retrieve it from the editor as it may have been modified)
_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);
_contentchanged = false;
}
else
{
// preserve changed content on re-render event
var richhtml = await _interop.GetHtml(_editorElement);
if (richhtml != _richhtml)
{
_richhtml = richhtml;
await _interop.LoadEditorContent(_editorElement, _richhtml);
}
}
}
}
}
}
public void Initialize(string content)
{
_richhtml = content;
_rawhtml = content;
_originalrichhtml = "";
_richhtml = content;
if (!_contentchanged)
{
_contentchanged = content != _originalrawhtml;
}
_originalrawhtml = _rawhtml; // preserve for comparison later
StateHasChanged();
}
public async Task<string> GetContent()
{
// evaluate raw html content as first priority
if (_rawhtml != _originalrawhtml)
{
return _rawhtml;
}
else
{
var richhtml = "";
if (_allowRichText)
{
richhtml = await _interop.GetHtml(_editorElement);
}
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml))
{
// convert Quill's empty content to empty string
if (richhtml == "<p><br></p>")
{
richhtml = string.Empty;
}
return richhtml;
}
else
{
// return original raw html content
return _originalrawhtml;
}
}
}
public void CloseRichFileManager()
{
_richfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
public void CloseRawFileManager()
{
_rawfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
public async Task<string> GetHtml()
{
// evaluate raw html content as first priority
if (_rawhtml != _originalrawhtml)
{
return _rawhtml;
}
else
{
var richhtml = "";
if (_allowRichText)
{
richhtml = await _interop.GetHtml(_editorElement);
}
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml))
{
// convert Quill's empty content to empty string
if (richhtml == "<p><br></p>")
{
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)
{
await _interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name), _editorIndex);
_richhtml = await _interop.GetHtml(_editorElement);
_richfilemanager = false;
}
else
{
_message = Localizer["Message.Require.Image"];
}
}
else
{
_editorIndex = await _interop.GetCurrentCursor(_editorElement);
_richfilemanager = true;
}
StateHasChanged();
}
public async Task InsertRawImage()
{
_message = string.Empty;
if (_rawfilemanager)
{
var file = _fileManager.GetFile();
if (file != null)
{
var interop = new Interop(JSRuntime);
int pos = await interop.GetCaretPosition(_rawhtmlid);
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);
_rawfilemanager = false;
}
else
{
_message = Localizer["Message.Require.Image"];
}
}
else
{
_rawfilemanager = true;
}
StateHasChanged();
}
private void ScopeChanged(ChangeEventArgs e)
{
_scopeSetting = (string)e.Value;
LoadSettings();
}
private void LoadSettings(bool reload = false)
{
try
{
if (!_settingsLoaded || reload)
{
_allowFileManagement = bool.Parse(GetSetting("Component", "QuillTextEditor_AllowFileManagement", "True"));
_allowRawHtml = bool.Parse(GetSetting("Component", "QuillTextEditor_AllowRawHtml", "True"));
_allowRichText = bool.Parse(GetSetting("Component", "QuillTextEditor_AllowRichText", "True"));
_theme = GetSetting("Component", "QuillTextEditor_Theme", "snow");
_debugLevel = GetSetting("Component", "QuillTextEditor_DebugLevel", "info");
_toolbarContent = GetSetting("Component", "QuillTextEditor_ToolbarContent", string.Empty);
// optional static parameter overrides
if (AllowFileManagement != null) _allowFileManagement = AllowFileManagement.Value;
if (AllowRichText != null) _allowRichText = AllowRichText.Value;
if (AllowRawHtml != null) _allowRawHtml = AllowRawHtml.Value;
if (!string.IsNullOrEmpty(Theme)) _theme = Theme;
if (!string.IsNullOrEmpty(DebugLevel)) _debugLevel = DebugLevel;
}
_allowSettings = PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList);
if (_allowSettings)
{
_allowFileManagementSetting = GetSetting(_scopeSetting, "QuillTextEditor_AllowFileManagement", "True");
_allowRawHtmlSetting = GetSetting(_scopeSetting, "QuillTextEditor_AllowRawHtml", "True");
_allowRichTextSetting = GetSetting(_scopeSetting, "QuillTextEditor_AllowRichText", "True");
_themeSetting = GetSetting(_scopeSetting, "QuillTextEditor_Theme", "snow");
_debugLevelSetting = GetSetting(_scopeSetting, "QuillTextEditor_DebugLevel", "info");
_toolbarContentSetting = GetSetting(_scopeSetting, "QuillTextEditor_ToolbarContent", string.Empty);
}
_settingsLoaded = true;
}
catch (Exception ex)
{
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private string GetSetting(string scope, string settingName, string defaultValue)
{
var settingValue = "";
switch (scope)
{
case "Component":
settingValue = SettingService.GetSetting(PageState.Site.Settings, settingName, defaultValue);
settingValue = SettingService.GetSetting(ModuleState.Settings, settingName, settingValue);
break;
case "Site":
settingValue = SettingService.GetSetting(PageState.Site.Settings, settingName, defaultValue);
break;
case "Module":
settingValue = SettingService.GetSetting(ModuleState.Settings, settingName, defaultValue);
break;
}
return settingValue;
}
private async Task UpdateSettings()
{
try
{
if (_scopeSetting == "Site" && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowFileManagement", _allowFileManagementSetting);
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRawHtml", _allowRawHtmlSetting);
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRichText", _allowRichTextSetting);
settings = SettingService.SetSetting(settings, "QuillTextEditor_Theme", _themeSetting);
settings = SettingService.SetSetting(settings, "QuillTextEditor_DebugLevel", _debugLevelSetting);
settings = SettingService.SetSetting(settings, "QuillTextEditor_ToolbarContent", _toolbarContentSetting);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
}
else if (_scopeSetting == "Module")
{
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowFileManagement", _allowFileManagementSetting);
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRawHtml", _allowRawHtmlSetting);
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRichText", _allowRichTextSetting);
settings = SettingService.SetSetting(settings, "QuillTextEditor_Theme", _themeSetting);
settings = SettingService.SetSetting(settings, "QuillTextEditor_DebugLevel", _debugLevelSetting);
settings = SettingService.SetSetting(settings, "QuillTextEditor_ToolbarContent", _toolbarContentSetting);
await SettingService.UpdateModuleSettingsAsync(settings,ModuleState.ModuleId);
}
LoadSettings(true);
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
catch (Exception ex)
{
AddModuleMessage(ex.Message, MessageType.Error);
}
}
}

View File

@ -1,120 +1,22 @@
@using System.Text.RegularExpressions
@using Microsoft.AspNetCore.Components.Rendering
@using Microsoft.Extensions.DependencyInjection
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@inject IServiceProvider ServiceProvider
@inject ISettingService SettingService
@inject IStringLocalizer<RichTextEditor> Localizer
<div class="row" style="margin-bottom: 50px;">
<div class="col">
<TabStrip ActiveTab="@_activetab">
@if (AllowRichText)
{
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
@if (_richfilemanager)
{
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
<div class="d-flex justify-content-center mb-2">
@if (AllowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
}
@if (_richfilemanager)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
}
</div>
<div class="row">
<div class="col">
<div @ref="@_toolBar">
@if (ToolbarContent != null)
{
@ToolbarContent
}
else
{
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
}
</div>
<div @ref="@_editorElement">
</div>
</div>
</div>
</TabPanel>
}
@if (AllowRawHtml)
{
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
@if (_rawfilemanager)
{
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
<div class="d-flex justify-content-center mb-2">
@if (AllowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
}
@if (_rawfilemanager)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseRawFileManager">@Localizer["Close"]</button>
}
</div>
@if (ReadOnly)
{
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
}
else
{
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
}
</TabPanel>
}
</TabStrip>
@_textEditorComponent
</div>
</div>
@code {
private ElementReference _editorElement;
private ElementReference _toolBar;
private bool _richfilemanager = false;
private FileManager _fileManager;
private string _richhtml = string.Empty;
private string _originalrichhtml = string.Empty;
private bool _rawfilemanager = false;
private string _rawhtml = string.Empty;
private string _originalrawhtml = string.Empty;
private string _message = string.Empty;
private string _activetab = "Rich";
private string _textEditorType;
private RenderFragment _textEditorComponent;
private ITextEditor _textEditor;
[Parameter]
public string Content { get; set; }
@ -123,168 +25,103 @@
public bool ReadOnly { get; set; } = false;
[Parameter]
public string Placeholder { get; set; } = "Enter Your Content...";
public string Placeholder { get; set; }
[Parameter]
public bool AllowFileManagement { get; set; } = true;
public string Provider { get; set; }
[Parameter]
public bool AllowRichText { get; set; } = true;
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
[Parameter]
public bool AllowRawHtml { get; set; } = true;
// parameters only applicable to rich text editor
[Parameter]
public RenderFragment ToolbarContent { get; set; }
[Parameter]
public string Theme { get; set; } = "snow";
[Parameter]
public string DebugLevel { get; set; } = "info";
public override List<Resource> Resources => new List<Resource>()
protected override void OnInitialized()
{
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js" }
};
_textEditorType = GetTextEditorType();
}
protected override void OnParametersSet()
{
_richhtml = Content;
_rawhtml = Content;
_originalrawhtml = _rawhtml; // preserve for comparison later
_originalrichhtml = "";
// Quill wraps content in <p> tags which can be used as a signal to set the active tab
if (!string.IsNullOrEmpty(Content) && !Content.StartsWith("<p>") && AllowRawHtml)
_textEditorComponent = (builder) =>
{
_activetab = "Raw";
}
CreateTextEditor(builder);
};
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (AllowRichText)
if(_textEditor != null)
{
var interop = new RichTextEditorInterop(JSRuntime);
if (firstRender)
{
await interop.CreateEditor(
_editorElement,
_toolBar,
ReadOnly,
Placeholder,
Theme,
DebugLevel);
}
await interop.LoadEditorContent(_editorElement, _richhtml);
if (string.IsNullOrEmpty(_originalrichhtml))
{
// 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);
}
_textEditor.Initialize(Content);
}
}
public void CloseRichFileManager()
{
_richfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
public void CloseRawFileManager()
{
_rawfilemanager = false;
_message = string.Empty;
StateHasChanged();
await base.OnAfterRenderAsync(firstRender);
}
public async Task<string> GetHtml()
{
// evaluate raw html content as first priority
if (_rawhtml != _originalrawhtml)
return await _textEditor.GetContent();
}
private void CreateTextEditor(RenderTreeBuilder builder)
{
if(!string.IsNullOrEmpty(_textEditorType))
{
return _rawhtml;
}
else
{
var richhtml = "";
if (AllowRichText)
var editorType = Type.GetType(_textEditorType);
if (editorType != null)
{
// return rich text content if it has changed
var interop = new RichTextEditorInterop(JSRuntime);
richhtml = await interop.GetHtml(_editorElement);
builder.OpenComponent(0, editorType);
var attributes = new Dictionary<string, object>
{
{ "Placeholder", Placeholder },
{ "ReadOnly", ReadOnly }
};
if (AdditionalAttributes != null)
{
foreach(var key in AdditionalAttributes.Keys)
{
if(!attributes.ContainsKey(key))
{
attributes.Add(key, AdditionalAttributes[key]);
}
else
{
attributes[key] = AdditionalAttributes[key];
}
}
}
var index = 1;
foreach(var name in attributes.Keys)
{
if (editorType.GetProperty(name) != null)
{
builder.AddAttribute(index++, name, attributes[name]);
}
}
builder.AddComponentReferenceCapture(index, (c) =>
{
_textEditor = (ITextEditor)c;
});
builder.CloseComponent();
}
// 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()
{
_message = string.Empty;
if (_richfilemanager)
{
var file = _fileManager.GetFile();
if (file != null)
{
var interop = new RichTextEditorInterop(JSRuntime);
await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name));
_richhtml = await interop.GetHtml(_editorElement);
_richfilemanager = false;
}
else
{
_message = Localizer["Message.Require.Image"];
}
}
else
{
_richfilemanager = true;
}
StateHasChanged();
}
private string GetTextEditorType()
{
const string EditorSettingName = "TextEditor";
public async Task InsertRawImage()
{
_message = string.Empty;
if (_rawfilemanager)
{
var file = _fileManager.GetFile();
if (file != null)
{
var interop = new Interop(JSRuntime);
int pos = await interop.GetCaretPosition("rawhtmleditor");
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);
_rawfilemanager = false;
}
else
{
_message = Localizer["Message.Require.Image"];
}
}
else
{
_rawfilemanager = true;
}
StateHasChanged();
}
if(!string.IsNullOrEmpty(Provider))
{
var provider = ServiceProvider.GetServices<ITextEditor>().FirstOrDefault(i => i.Name.Equals(Provider, StringComparison.OrdinalIgnoreCase));
if(provider != null)
{
return Utilities.GetFullTypeName(provider.GetType().AssemblyQualifiedName);
}
}
return SettingService.GetSetting(PageState.Site.Settings, EditorSettingName, Constants.DefaultTextEditor);
}
}

View File

@ -23,7 +23,7 @@
</li>
}
</ul>
<div class="tab-content">
<div class="tab-content @TabContentClass">
<br />
@ChildContent
</div>
@ -47,6 +47,9 @@
[Parameter]
public string Id { get; set; } // optional - used to uniquely identify an instance of a tab strip component (will be set automatically if no value provided)
[Parameter]
public string TabContentClass { get; set; } // optional - to extend the TabContent div.
protected override void OnInitialized()
{
if (string.IsNullOrEmpty(Id))

View File

@ -0,0 +1,33 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@implements ITextEditor
<div class="text-area-editor">
<textarea @bind="_content" @ref="_editor" placeholder="@Placeholder" readonly="@ReadOnly" />
</div>
@code {
public string Name => "TextArea";
private ElementReference _editor;
private string _content;
[Parameter]
public bool ReadOnly { get; set; }
[Parameter]
public string Placeholder { get; set; }
public void Initialize(string content)
{
_content = content;
StateHasChanged();
}
public async Task<string> GetContent()
{
await Task.CompletedTask;
return _content;
}
}

View File

@ -13,7 +13,7 @@
<TabPanel Name="Edit" Heading="Edit" ResourceKey="Edit">
@if (_content != null)
{
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" AllowRawHtml="@_allowrawhtml" @ref="@RichTextEditorHtml"></RichTextEditor>
<RichTextEditor Content="@_content" @ref="@RichTextEditorHtml"></RichTextEditor>
<br />
<button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@ -47,44 +47,34 @@
</TabStrip>
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Edit Html/Text";
public override string Title => "Edit Html/Text";
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" }
};
private RichTextEditor RichTextEditorHtml;
private string _content = null;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private List<Models.HtmlText> _htmltexts;
private string _view = "";
private RichTextEditor RichTextEditorHtml;
private bool _allowfilemanagement;
private bool _allowrawhtml;
private string _content = null;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private List<Models.HtmlText> _htmltexts;
private string _view = "";
protected override async Task OnInitializedAsync()
{
try
{
await LoadContent();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Content {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Content.Load"], MessageType.Error);
}
}
protected override async Task OnInitializedAsync()
{
try
{
_allowfilemanagement = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true"));
_allowrawhtml = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true"));
await LoadContent();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Content {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Content.Load"], MessageType.Error);
}
}
private async Task LoadContent()
{
private async Task LoadContent()
{
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{

View File

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

View File

@ -15,7 +15,7 @@ namespace Oqtane.Modules.HtmlText
Version = "1.0.1",
ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server",
ReleaseVersions = "1.0.0,1.0.1",
SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client",
SettingsType = string.Empty,
Resources = new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" }

View File

@ -1,61 +0,0 @@
@namespace Oqtane.Modules.HtmlText
@inherits ModuleBase
@inject ISettingService SettingService
@implements Oqtane.Interfaces.ISettingsControl
@inject IStringLocalizer<Settings> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="files" ResourceKey="AllowFileManagement" ResourceType="@resourceType" HelpText="Specify If Editors Can Upload and Select Files">Allow File Management: </Label>
<div class="col-sm-9">
<select id="files" class="form-select" @bind="@_allowfilemanagement">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="files" ResourceKey="AllowRawHtml" ResourceType="@resourceType" HelpText="Specify If Editors Can Enter Raw HTML">Allow Raw HTML: </Label>
<div class="col-sm-9">
<select id="files" class="form-select" @bind="@_allowrawhtml">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
@code {
private string resourceType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"; // for localization
private string _allowfilemanagement;
private string _allowrawhtml;
protected override void OnInitialized()
{
try
{
_allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true");
_allowrawhtml = SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true");
}
catch (Exception ex)
{
AddModuleMessage(ex.Message, MessageType.Error);
}
}
public async Task UpdateSettings()
{
try
{
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement);
settings = SettingService.SetSetting(settings, "AllowRawHtml", _allowrawhtml);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
{
AddModuleMessage(ex.Message, MessageType.Error);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -144,8 +144,8 @@
<data name="Month" xml:space="preserve">
<value>Month(s)</value>
</data>
<data name="ViewJobs.Text" xml:space="preserve">
<value>View Logs</value>
<data name="ViewLogs.Text" xml:space="preserve">
<value>View All Logs</value>
</data>
<data name="Frequency" xml:space="preserve">
<value>Frequency</value>

View File

@ -132,4 +132,7 @@
<data name="Failed" xml:space="preserve">
<value>Failed</value>
</data>
<data name="Refresh" xml:space="preserve">
<value>Refresh</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -210,4 +210,16 @@
<data name="Success.SaveSiteSettings" xml:space="preserve">
<value>Settings Saved Successfully</value>
</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>

View File

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

View File

@ -138,4 +138,7 @@
<data name="EditPage.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Error.Page.Load" xml:space="preserve">
<value>Error Loading Pages</value>
</data>
</root>

View File

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

View File

@ -0,0 +1,168 @@
<?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="Enabled.Text" xml:space="preserve">
<value>Enabled? </value>
</data>
<data name="Enabled.HelpText" xml:space="preserve">
<value>Specify if search indexing is enabled</value>
</data>
<data name="LastIndexedOn.Text" xml:space="preserve">
<value>Last Indexed: </value>
</data>
<data name="LastIndexedOn.HelpText" xml:space="preserve">
<value>The date/time which the site was last indexed on</value>
</data>
<data name="IgnorePages.Text" xml:space="preserve">
<value>Ignore Pages: </value>
</data>
<data name="IgnorePages.HelpText" xml:space="preserve">
<value>Comma delimited list of pages which should be ignored (based on page path)</value>
</data>
<data name="IgnoreEntities.Text" xml:space="preserve">
<value>Ignore Entities: </value>
</data>
<data name="IgnoreEntities.HelpText" xml:space="preserve">
<value>Comma delimited list of entities which should be ignored</value>
</data>
<data name="MinimumWordLength.Text" xml:space="preserve">
<value>Word Length: </value>
</data>
<data name="MinimumWordLength.HelpText" xml:space="preserve">
<value>Minimum length of a word to be indexed</value>
</data>
<data name="IgnoreWords.Text" xml:space="preserve">
<value>Ignore Words: </value>
</data>
<data name="IgnoreWords.HelpText" xml:space="preserve">
<value>Comma delimited list of words which should be ignored</value>
</data>
<data name="Success.Save" xml:space="preserve">
<value>Search Settings Saved Successfully</value>
</data>
<data name="Error.Save" xml:space="preserve">
<value>Error Saving Search Settings</value>
</data>
<data name="SearchProvider.HelpText" xml:space="preserve">
<value>Specify the search provider for this site</value>
</data>
<data name="SearchProvider.Text" xml:space="preserve">
<value>Search Provider:</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -117,16 +117,16 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AllowFileManagement.HelpText" xml:space="preserve">
<value>Specify If Editors Can Upload and Select Files</value>
<data name="NoCriteria" xml:space="preserve">
<value>You Must Provide Some Search Criteria</value>
</data>
<data name="AllowFileManagement.Text" xml:space="preserve">
<value>Allow File Management: </value>
<data name="NoResult" xml:space="preserve">
<value>No Content Matches The Criteria Provided</value>
</data>
<data name="AllowRawHtml.HelpText" xml:space="preserve">
<value>Specify If Editors Can Enter Raw HTML</value>
<data name="SearchLabel" xml:space="preserve">
<value>Search:</value>
</data>
<data name="AllowRawHtml.Text" xml:space="preserve">
<value>Allow Raw HTML:</value>
<data name="SearchPlaceholder" xml:space="preserve">
<value>Search</value>
</data>
</root>

View File

@ -0,0 +1,180 @@
<?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="Ascending" xml:space="preserve">
<value>Ascending</value>
</data>
<data name="BodyLength.HelpText" xml:space="preserve">
<value>The number of characters displayed for each search result summary. The default is 255 characters.</value>
</data>
<data name="BodyLength.Text" xml:space="preserve">
<value>Body Size:</value>
</data>
<data name="DateRange.HelpText" xml:space="preserve">
<value>Enter the date range for search results. The default includes all content.</value>
</data>
<data name="DateRange.Text" xml:space="preserve">
<value>Date Range:</value>
</data>
<data name="Descending" xml:space="preserve">
<value>Descending</value>
</data>
<data name="ExcludeEntities.HelpText" xml:space="preserve">
<value>Comma delimited list of entities to exclude from search results. By default no entities will be excluded.</value>
</data>
<data name="ExcludeEntities.Text" xml:space="preserve">
<value>Exlude Entities:</value>
</data>
<data name="IncludeEntities.HelpText" xml:space="preserve">
<value>Comma delimited list of entities to include in the search results. By default all entities will be included.</value>
</data>
<data name="IncludeEntities.Text" xml:space="preserve">
<value>Include Entities:</value>
</data>
<data name="LastModified" xml:space="preserve">
<value>LastModified</value>
</data>
<data name="PageSize.HelpText" xml:space="preserve">
<value>The maximum number of search results to retrieve. The default is unlimited.</value>
</data>
<data name="PageSize.Text" xml:space="preserve">
<value>Page Size:</value>
</data>
<data name="Relevance" xml:space="preserve">
<value>Relevance</value>
</data>
<data name="SortField.HelpText" xml:space="preserve">
<value>Specify the default sort field</value>
</data>
<data name="SortField.Text" xml:space="preserve">
<value>Sort By:</value>
</data>
<data name="SortOrder.HelpText" xml:space="preserve">
<value>Specify the default sort order</value>
</data>
<data name="SortOrder.Text" xml:space="preserve">
<value>Sort Order:</value>
</data>
<data name="Title" xml:space="preserve">
<value>Title</value>
</data>
<data name="To" xml:space="preserve">
<value>To</value>
</data>
</root>

View File

@ -277,10 +277,10 @@
<value>UI Component Settings</value>
</data>
<data name="Prerender.HelpText" xml:space="preserve">
<value>Specifies if interactive components should prerender their output</value>
<value>Specifies if interactive components should prerender their output on the server</value>
</data>
<data name="Prerender.Text" xml:space="preserve">
<value>Prerender? </value>
<value>Prerender: </value>
</data>
<data name="RenderMode.HelpText" xml:space="preserve">
<value>The default render mode for the site</value>
@ -402,9 +402,6 @@
<data name="Retention.Text" xml:space="preserve">
<value>Retention (Days):</value>
</data>
<data name="FileExtensions.Heading" xml:space="preserve">
<value>File Extensions</value>
</data>
<data name="ImageExtensions.HelpText" xml:space="preserve">
<value>Enter a comma separated list of image file extensions</value>
</data>
@ -429,4 +426,13 @@
<data name="Runtime.Text" xml:space="preserve">
<value>Interactivity:</value>
</data>
<data name="TextEditor.HelpText" xml:space="preserve">
<value>Select the text editor for the site</value>
</data>
<data name="TextEditor.Text" xml:space="preserve">
<value>Text Editor:</value>
</data>
<data name="Functionality" xml:space="preserve">
<value>Functionality</value>
</data>
</root>

View File

@ -255,12 +255,6 @@
<data name="MachineName.Text" xml:space="preserve">
<value>Machine Name:</value>
</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">
<value>Server Web Root Path</value>
</data>
@ -294,4 +288,10 @@
<data name="Error.ClearLog" xml:space="preserve">
<value>Ann Error Occurred Clearing The System Log</value>
</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>

View File

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

View File

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

View File

@ -0,0 +1,174 @@
<?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="AllowFileManagement.HelpText" xml:space="preserve">
<value>Specify if editors can upload and insert images</value>
</data>
<data name="AllowFileManagement.Text" xml:space="preserve">
<value>Insert Images?</value>
</data>
<data name="AllowRawHtml.HelpText" xml:space="preserve">
<value>Specify if editors can use the Raw HTML Editor</value>
</data>
<data name="AllowRawHtml.Text" xml:space="preserve">
<value>Raw HTML Editor?</value>
</data>
<data name="AllowRichText.HelpText" xml:space="preserve">
<value>Specify if editors can use the Rich Text Editor</value>
</data>
<data name="AllowRichText.Text" xml:space="preserve">
<value>Rich Text Editor? </value>
</data>
<data name="Close" xml:space="preserve">
<value>Close</value>
</data>
<data name="DebugLevel.HelpText" xml:space="preserve">
<value>Specify the Debug Level</value>
</data>
<data name="DebugLevel.Text" xml:space="preserve">
<value>Debug Level:</value>
</data>
<data name="InsertImage" xml:space="preserve">
<value>Insert Image</value>
</data>
<data name="Message.Require.Image" xml:space="preserve">
<value>You Must Select An Image To Insert</value>
</data>
<data name="Placeholder" xml:space="preserve">
<value>Enter Your Content...</value>
</data>
<data name="SaveSettings" xml:space="preserve">
<value>Save Settings</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="Theme.HelpText" xml:space="preserve">
<value>Specify the Rich Text Editor's theme</value>
</data>
<data name="Theme.Text" xml:space="preserve">
<value>Theme:</value>
</data>
<data name="ToolbarContent.HelpText" xml:space="preserve">
<value>Specify any toolbar content to customize the Rich Text Editor</value>
</data>
<data name="ToolbarContent.Text" xml:space="preserve">
<value>Toolbar Content:</value>
</data>
</root>

View File

@ -453,4 +453,22 @@
<data name="RenderModeStatic" xml:space="preserve">
<value>Static</value>
</data>
<data name="Disabled" xml:space="preserve">
<value>Disabled</value>
</data>
<data name="Enabled" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Module" xml:space="preserve">
<value>Module</value>
</data>
<data name="Page" xml:space="preserve">
<value>Page</value>
</data>
<data name="Site" xml:space="preserve">
<value>Site</value>
</data>
<data name="User" xml:space="preserve">
<value>User</value>
</data>
</root>

View File

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

View File

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

View File

@ -117,13 +117,10 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="InsertImage" xml:space="preserve">
<value>Insert Image</value>
<data name="Search" xml:space="preserve">
<value>Search</value>
</data>
<data name="Close" xml:space="preserve">
<value>Close</value>
</data>
<data name="Message.Require.Image" xml:space="preserve">
<value>You Must Select An Image To Insert</value>
<data name="SearchPlaceHolder" xml:space="preserve">
<value>Search</value>
</data>
</root>

View File

@ -29,6 +29,13 @@ namespace Oqtane.Services
/// <returns></returns>
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>
/// Creates a new log entry
/// </summary>

View File

@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Oqtane.Documentation;
using Oqtane.Models;
namespace Oqtane.Services
{
[PrivateApi("Mark SearchResults classes as private, since it's not very useful in the public docs")]
public interface ISearchResultsService
{
Task<SearchResults> GetSearchResultsAsync(SearchQuery searchQuery);
}
}

View File

@ -46,6 +46,14 @@ namespace Oqtane.Services
/// <returns></returns>
Task DeleteSiteAsync(int siteId);
/// <summary>
/// Returns a list of modules
/// </summary>
/// <param name="siteId"></param>
/// <param name="pageId"></param>
/// <returns></returns>
Task<List<Module>> GetModulesAsync(int siteId, int pageId);
[PrivateApi]
[Obsolete("This method is deprecated.", false)]
void SetAlias(Alias alias);

View File

@ -35,6 +35,11 @@ namespace Oqtane.Services
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)
{
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)
{
return await PutJsonAsync<Profile>($"{Apiurl}/{profile.SiteId}", profile);
return await PutJsonAsync<Profile>($"{Apiurl}/{profile.ProfileId}", profile);
}
public async Task DeleteProfileAsync(int profileId)
{

View File

@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Shared;
namespace Oqtane.Services
{
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class SearchResultsService : ServiceBase, ISearchResultsService, IClientService
{
public SearchResultsService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private string ApiUrl => CreateApiUrl("SearchResults");
public async Task<SearchResults> GetSearchResultsAsync(SearchQuery searchQuery)
{
return await PostJsonAsync<SearchQuery, SearchResults>(ApiUrl, searchQuery);
}
}
}

View File

@ -1,7 +1,6 @@
using Oqtane.Models;
using System.Threading.Tasks;
using System.Net.Http;
using System.Linq;
using System.Collections.Generic;
using Oqtane.Shared;
using System;
@ -41,6 +40,11 @@ namespace Oqtane.Services
await DeleteAsync($"{Apiurl}/{siteId}");
}
public async Task<List<Module>> GetModulesAsync(int siteId, int pageId)
{
return await GetJsonAsync<List<Module>>($"{Apiurl}/modules/{siteId}/{pageId}");
}
[Obsolete("This method is deprecated.", false)]
public void SetAlias(Alias alias)
{

View File

@ -31,7 +31,7 @@ namespace Oqtane.Services
public async Task<User> GetUserAsync(string username, int siteId)
{
return await GetUserAsync(username, "", siteId);
return await GetJsonAsync<User>($"{Apiurl}/username/{username}?siteid={siteId}");
}
public async Task<User> GetUserAsync(string username, string email, int siteId)

View File

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

View File

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

View File

@ -9,13 +9,19 @@
<div class="row flex-xl-nowrap gx-0">
<div class="sidebar">
<nav class="navbar">
<Logo /><Menu Orientation="Vertical" />
<Logo />
<Menu Orientation="Vertical" />
</nav>
</div>
<div class="main g-0">
<div class="top-row px-4">
<div class="ms-auto"><UserProfile /> <Login /> <ControlPanel LanguageDropdownAlignment="right" /></div>
<div class="ms-auto">
<Search CssClass="me-3 text-center d-inline-block" />
<UserProfile />
<Login />
<ControlPanel LanguageDropdownAlignment="right" />
</div>
</div>
<div class="container">
<div class="row px-4">
@ -31,9 +37,13 @@
public override List<Resource> Resources => new List<Resource>()
{
// obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css", Integrity = "sha512-b2QcS5SsA8tZodcDtGRELiGv5SaKSk1vDHDaQRda0htPYWZ6046lr3kJ5bAAQdpV2mmA/4v0wQF9MyU6/pDIAg==", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css",
Integrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==",
CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js", Integrity = "sha512-X/YkDZyjTf4wyc2Vy16YGCPHwAY8rZJY+POgokZjQB2mhIRFJCckEGc6YyX9eNsPfn0PzThEuNs+uaomE5CO6A==", CrossOrigin = "anonymous", Location = ResourceLocation.Body }
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js",
Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==",
CrossOrigin = "anonymous", Location = ResourceLocation.Body },
};
}

View File

@ -6,10 +6,26 @@
{
@if (PageState.RenderMode == RenderModes.Interactive)
{
<ModuleActionsInteractive PageState="@PageState" ModuleState="@ModuleState" />
<ModuleActionsInteractive PageState="@_pageState" ModuleState="@ModuleState" />
}
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)" />
}
}
@code {
private PageState _pageState;
protected override void OnParametersSet()
{
// trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries
_pageState = new PageState
{
Alias = PageState.Alias,
Page = PageState.Page,
User = PageState.User,
EditMode = PageState.EditMode
};
}
}

View File

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

View File

@ -6,13 +6,13 @@
@if (ShowLanguageSwitcher)
{
<LanguageSwitcher DropdownAlignment="@LanguageDropdownAlignment" />
<LanguageSwitcher ButtonClass="@ButtonClass" DropdownAlignment="@LanguageDropdownAlignment" />
}
@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>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
@if (PageState.EditMode)
{
<button type="submit" class="btn @ButtonClass active" aria-pressed="true" autocomplete="off">
@ -32,11 +32,11 @@
{
@if (PageState.RenderMode == RenderModes.Interactive)
{
<ControlPanelInteractive PageState="@PageState" SiteState="@SiteState" ButtonClass="@ButtonClass" ContainerClass="@ContainerClass" HeaderClass="@HeaderClass" BodyClass="@BodyClass" ShowLanguageSwitcher="@ShowLanguageSwitcher" LanguageDropdownAlignment="@LanguageDropdownAlignment" />
<ControlPanelInteractive PageState="@_pageState" SiteState="@SiteState" ButtonClass="@ButtonClass" ContainerClass="@ContainerClass" HeaderClass="@HeaderClass" BodyClass="@BodyClass" ShowLanguageSwitcher="@ShowLanguageSwitcher" LanguageDropdownAlignment="@LanguageDropdownAlignment" CanViewAdminDashboard="@_canViewAdminDashboard" />
}
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" CanViewAdminDashboard="@_canViewAdminDashboard" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, false)" />
}
}
@ -59,6 +59,7 @@
[Parameter]
public string LanguageDropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right
private PageState _pageState;
private bool _canViewAdminDashboard = false;
private bool _showEditMode = false;
@ -73,7 +74,7 @@
}
else
{
foreach (var module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId))
foreach (var module in PageState.Modules)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, module.PermissionList))
{
@ -82,6 +83,24 @@
}
}
}
// trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries
_pageState = new PageState
{
Alias = PageState.Alias,
Site = new Site
{
DefaultContainerType = PageState.Site.DefaultContainerType,
Settings = PageState.Site.Settings,
Themes = PageState.Site.Themes
},
Page = PageState.Page,
User = PageState.User,
Uri = PageState.Uri,
Route = PageState.Route,
RenderMode = PageState.RenderMode,
Runtime = PageState.Runtime
};
}
private bool CanViewAdminDashboard()

View File

@ -15,6 +15,7 @@
@inject ILogService LoggingService
@inject IStringLocalizer<ControlPanelInteractive> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject IServiceProvider ServiceProvider
<button type="button" class="btn @ButtonClass ms-1" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel" @onclick="ClearMessage">
<span class="oi oi-cog"></span>
@ -27,7 +28,7 @@
</div>
<div class="@BodyClass">
<div class="container-fluid">
@if (_canViewAdminDashboard)
@if (CanViewAdminDashboard)
{
<div class="row d-flex">
<div class="col">
@ -93,9 +94,13 @@
<div class="row">
<div class="col text-center">
<label for="Module" class="control-label">@Localizer["Module.Manage"]</label>
<select class="form-select" @bind="@_moduleType">
<select class="form-select" @onchange="(e => ModuleTypeChanged(e))">
<option value="new">@Localizer["Module.AddNew"]</option>
<option value="existing">@Localizer["Module.AddExisting"]</option>
@if (PageState.Page.UserId == null)
{
<option value="add">@Localizer["Module.AddExisting"]</option>
<option value="copy">@Localizer["Module.CopyExisting"]</option>
}
</select>
@if (_moduleType == "new")
{
@ -138,7 +143,7 @@
}
else
{
<select class="form-select mt-1" @onchange="(e => PageChanged(e))">
<select class="form-select mt-1" value="@_pageId" @onchange="(e => PageChanged(e))">
<option value="-">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page p in _pages)
{
@ -211,7 +216,7 @@
<div class="row d-flex">
<div class="col">
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-secondary col-12" @onclick=@(async () => await LogoutUser())>@Localizer["Logout"]</button>
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-secondary col-12 mt-2" @onclick=@(async () => await LogoutUser())>@Localizer["Logout"]</button>
</div>
</div>
</div>
@ -243,7 +248,9 @@
[Parameter]
public string LanguageDropdownAlignment { get; set; }
private bool _canViewAdminDashboard = false;
[Parameter]
public bool CanViewAdminDashboard { get; set; }
private bool _deleteConfirmation = false;
private List<string> _categories = new List<string>();
private List<ModuleDefinition> _allModuleDefinitions;
@ -273,44 +280,17 @@
// repopulate the SiteState service based on the values passed in the SiteState parameter (this is how state is marshalled across the render mode boundary)
ComponentSiteState.Hydrate(SiteState);
_canViewAdminDashboard = CanViewAdminDashboard();
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{
LoadSettingsAsync();
_pages?.Clear();
foreach (Page p in PageState.Pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_pages.Add(p);
}
}
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containerType = PageState.Site.DefaultContainerType;
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Page.SiteId);
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries)).Distinct().Where(item => item != "Headless").ToList();
}
}
private bool CanViewAdminDashboard()
{
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
if (admin != null)
{
foreach (var page in PageState.Pages.Where(item => item.ParentId == admin?.PageId))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
{
return true;
}
}
}
return false;
}
private void CategoryChanged(ChangeEventArgs e)
{
_category = (string)e.Value;
@ -334,14 +314,26 @@
StateHasChanged();
}
private void PageChanged(ChangeEventArgs e)
private async Task ModuleTypeChanged(ChangeEventArgs e)
{
_moduleType = (string)e.Value;
if (_moduleType != "new")
{
_pages = await PageService.GetPagesAsync(PageState.Page.SiteId);
}
_pageId = "-";
_moduleId = "-";
}
private async Task PageChanged(ChangeEventArgs e)
{
_pageId = (string)e.Value;
if (_pageId != "-")
{
_modules = PageState.Modules
.Where(module => module.PageId == int.Parse(_pageId) &&
UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
_modules = await ModuleService.GetModulesAsync(PageState.Page.SiteId);
_modules = _modules.Where(module => module.PageId == int.Parse(_pageId) &&
UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList) &&
(_moduleType == "add" || module.ModuleDefinition.IsPortable))
.ToList();
}
_moduleId = "-";
@ -354,40 +346,45 @@
{
if ((_moduleType == "new" && _moduleDefinitionName != "-") || (_moduleType != "new" && _moduleId != "-"))
{
var newModuleId = _moduleId != "-" ? int.Parse(_moduleId) : 0;
if (_moduleType == "new")
{
Module module = new Module();
module.SiteId = PageState.Site.SiteId;
module.SiteId = PageState.Page.SiteId;
module.PageId = PageState.Page.PageId;
module.ModuleDefinitionName = _moduleDefinitionName;
module.AllPages = false;
var permissions = new List<Permission>();
if (_visibility == "view")
{
// set module view permissions to page view permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.View);
}
else
{
// set module view permissions to page edit permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.Edit);
}
// set module edit permissions to page edit permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.Edit, PermissionNames.Edit);
module.PermissionList = permissions;
module.PermissionList = GenerateDefaultPermissions(module.SiteId);
module = await ModuleService.AddModuleAsync(module);
_moduleId = module.ModuleId.ToString();
newModuleId = module.ModuleId;
}
else if (_moduleType == "copy")
{
var module = await ModuleService.GetModuleAsync(int.Parse(_moduleId));
module.ModuleId = 0;
module.SiteId = PageState.Page.SiteId;
module.PageId = PageState.Page.PageId;
module.AllPages = false;
module.PermissionList = GenerateDefaultPermissions(module.SiteId);
module = await ModuleService.AddModuleAsync(module);
var moduleContent = await ModuleService.ExportModuleAsync(int.Parse(_moduleId), PageState.Page.PageId);
if (!string.IsNullOrEmpty(moduleContent))
{
await ModuleService.ImportModuleAsync(module.ModuleId, PageState.Page.PageId, moduleContent);
}
newModuleId = module.ModuleId;
}
var pageModule = new PageModule
{
PageId = PageState.Page.PageId,
ModuleId = int.Parse(_moduleId),
ModuleId = newModuleId,
Title = _title
};
if (pageModule.Title == "")
if (string.IsNullOrEmpty(pageModule.Title))
{
if (_moduleType == "new")
{
@ -412,9 +409,16 @@
await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane);
await UpdateSettingsAsync();
_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, ""));
if (PageState.RenderMode == RenderModes.Interactive)
{
_message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>";
_title = "";
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, ""));
}
else // reload page in static rendering
{
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, ""), true);
}
}
else
{
@ -427,6 +431,25 @@
}
}
private List<Permission> GenerateDefaultPermissions(int siteId)
{
var permissions = new List<Permission>();
if (_visibility == "view")
{
// set module view permissions to page view permissions
permissions = SetPermissions(permissions, siteId, PermissionNames.View, PermissionNames.View);
}
else
{
// set module view permissions to page edit permissions
permissions = SetPermissions(permissions, siteId, PermissionNames.View, PermissionNames.Edit);
}
// set module edit permissions to page edit permissions
permissions = SetPermissions(permissions, siteId, PermissionNames.Edit, PermissionNames.Edit);
return permissions;
}
private List<Permission> SetPermissions(List<Permission> permissions, int siteId, string modulePermission, string pagePermission)
{
foreach (var permission in PageState.Page.PermissionList.Where(item => item.PermissionName == pagePermission))
@ -438,27 +461,19 @@
private void Navigate(string location)
{
Module module;
int moduleId;
switch (location)
{
case "Admin":
// get admin dashboard moduleid
module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule);
if (module != null)
{
NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin", module.ModuleId, "Index", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
}
moduleId = int.Parse(PageState.Site.Settings[Constants.AdminDashboardModule]);
NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin", moduleId, "Index", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
break;
case "Add":
case "Edit":
string url = "";
// get page management moduleid
module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.PageManagementModule);
if (module != null)
{
url = Utilities.EditUrl(PageState.Alias.Path, "admin/pages", module.ModuleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}");
NavigationManager.NavigateTo(url);
}
moduleId = int.Parse(PageState.Site.Settings[Constants.PageManagementModule]);
NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin/pages", moduleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}"));
break;
}
}
@ -473,11 +488,11 @@
case "publish":
if (!permissions.Any(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Everyone))
{
permissions.Add(new Permission(PageState.Site.SiteId, EntityNames.Page, PageState.Page.PageId, PermissionNames.View, RoleNames.Everyone, null, true));
permissions.Add(new Permission(PageState.Page.SiteId, EntityNames.Page, PageState.Page.PageId, PermissionNames.View, RoleNames.Everyone, null, true));
}
if (!permissions.Any(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Registered))
{
permissions.Add(new Permission(PageState.Site.SiteId, EntityNames.Page, PageState.Page.PageId, PermissionNames.View, RoleNames.Registered, null, true));
permissions.Add(new Permission(PageState.Page.SiteId, EntityNames.Page, PageState.Page.PageId, PermissionNames.View, RoleNames.Registered, null, true));
}
break;
case "unpublish":

View File

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

View File

@ -16,7 +16,7 @@
else
{
<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" />
<button type="submit" class="btn btn-primary">@Localizer["Logout"]</button>
</form>

View File

@ -59,7 +59,7 @@ namespace Oqtane.Themes.Controls
logouturl = Utilities.TenantUrl(PageState.Alias, "/pages/logout/");
// verify anonymous users can access current page
if (UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.PermissionList) && Utilities.IsPageModuleVisible(PageState.Page.EffectiveDate, PageState.Page.ExpiryDate))
if (UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.PermissionList) && Utilities.IsEffectiveAndNotExpired(PageState.Page.EffectiveDate, PageState.Page.ExpiryDate))
{
returnurl = PageState.Route.PathAndQuery;
}

View File

@ -0,0 +1,61 @@
@namespace Oqtane.Themes.Controls
@using System.Net
@using Microsoft.AspNetCore.Http
@inherits ThemeControlBase
@inject IStringLocalizer<Search> Localizer
@inject NavigationManager NavigationManager
@if (_searchResultsPage != null)
{
<span class="app-search @CssClass">
<form method="post" class="app-form-inline" @formname="@($"SearchForm")" @onsubmit="@PerformSearch" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<input type="text" name="keywords" maxlength="50"
class="form-control d-inline-block pe-5 shadow-none"
@bind="_keywords"
placeholder="@Localizer["SearchPlaceHolder"]"
aria-label="Search" />
<button type="submit" class="btn btn-search">
<span class="oi oi-magnifying-glass align-middle"></span>
</button>
</form>
</span>
}
@code {
private Page _searchResultsPage;
private string _keywords = "";
[Parameter]
public string CssClass { get; set; }
[Parameter]
public string SearchResultPagePath { get; set; } = "search";
[CascadingParameter]
HttpContext HttpContext { get; set; }
[SupplyParameterFromForm(FormName = "SearchForm")]
public string KeyWords { get => ""; set => _keywords = value; }
protected override void OnInitialized()
{
if(!string.IsNullOrEmpty(SearchResultPagePath))
{
_searchResultsPage = PageState.Pages.FirstOrDefault(i => i.Path == SearchResultPagePath);
}
}
private void PerformSearch()
{
if (_searchResultsPage != null)
{
var url = NavigateUrl(_searchResultsPage.Path, $"q={_keywords}");
NavigationManager.NavigateTo(url);
}
}
}

View File

@ -17,9 +17,13 @@ namespace Oqtane.Themes.OqtaneTheme
Resources = new List<Resource>()
{
// obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.2/cyborg/bootstrap.min.css", Integrity = "sha512-RfNxVfFNFgqk9MXO4TCKXYXn9hgc+keHCg3xFFGbnp2q7Cifda+YYzMTDHwsQtNx4DuqIMgfvZead7XOtB9CDQ==", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css",
Integrity = "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==",
CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js", Integrity = "sha512-X/YkDZyjTf4wyc2Vy16YGCPHwAY8rZJY+POgokZjQB2mhIRFJCckEGc6YyX9eNsPfn0PzThEuNs+uaomE5CO6A==", CrossOrigin = "anonymous", Location = ResourceLocation.Body }
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js",
Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==",
CrossOrigin = "anonymous", Location = ResourceLocation.Body },
}
};
}

View File

@ -6,7 +6,12 @@
<nav class="navbar navbar-dark bg-primary fixed-top">
<Logo /><Menu Orientation="Horizontal" />
<div class="controls ms-auto">
<div class="controls-group"><UserProfile ShowRegister="@_register" /> <Login ShowLogin="@_login" /> <ControlPanel LanguageDropdownAlignment="right" /></div>
<div class="controls-group">
<Search CssClass="me-3 text-center bg-primary" />
<UserProfile ShowRegister="@_register" />
<Login ShowLogin="@_login" />
<ControlPanel LanguageDropdownAlignment="right" />
</div>
</div>
</nav>
<div class="content">

View File

@ -121,15 +121,15 @@
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
if (_login != "-")
{
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login, true);
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login);
}
if (_register != "-")
{
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register, true);
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register);
}
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);
}

View File

@ -3,8 +3,6 @@
@using Oqtane.Shared
@inject SiteState SiteState
@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))
{

View File

@ -0,0 +1,13 @@
// This is just a placeholder file
// It is necessary for the documentation to successfully build this project.
// Reason is that docfx will run the .net compiler and find references
// to this class in the project.
// But since the real class is just a .razor file, ATM docfx will fail.
//
// Note added 2024-06-27 by @iJungleboy.
// We hope that as .net and docfx improve, the razor-compiler will work in that scenario
// as well, and this file can be removed.
namespace Oqtane.UI;
public partial class ModuleInstance;

View File

@ -1,25 +1,52 @@
@namespace Oqtane.UI
@inject SiteState SiteState
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
@if (_comment != null)
{
<StreamRenderingDisabled ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
}
else
{
<StreamRenderingEnabled ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
@((MarkupString)_comment)
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static)
{
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
}
else
{
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, _prerender)" />
}
}
@code {
// this component is on the static side of the render mode boundary
// it passes state as serializable parameters across the boundary
[CascadingParameter]
protected PageState PageState { get; set; }
[CascadingParameter]
private Module ModuleState { get; set; }
private bool _prerender;
private string _comment;
protected override void OnParametersSet()
{
_prerender = ModuleState.Prerender ?? PageState.Site.Prerender;
_comment = "<!-- rendermode: ";
if (PageState.RenderMode == RenderModes.Static && ModuleState.RenderMode == RenderModes.Static)
{
_comment += RenderModes.Static;
}
else
{
_comment += $"{RenderModes.Interactive}:{PageState.Runtime} - prerender: {_prerender}";
}
_comment += " -->";
if (PageState.RenderMode == RenderModes.Static && ModuleState.RenderMode == RenderModes.Interactive)
{
// trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries
// please note that this performance optimization results in the PageState.Pages property not being available for use in Interactive components
PageState.Site.Pages = new List<Page>();
}
}
[Obsolete("AddModuleMessage is deprecated. Use AddModuleMessage in ModuleBase instead.", false)]
public void AddModuleMessage(string message, MessageType type)

View File

@ -9,6 +9,7 @@ namespace Oqtane.UI
public Alias Alias { get; set; }
public Site Site { get; set; }
public Page Page { get; set; }
public List<Module> Modules { get; set; }
public User User { get; set; }
public Uri Uri { get; set; }
public Route Route { get; set; }
@ -29,15 +30,11 @@ namespace Oqtane.UI
public List<Page> Pages
{
get { return Site.Pages; }
}
public List<Module> Modules
{
get { return Site.Modules; }
get { return Site?.Pages; }
}
public List<Language> Languages
{
get { return Site.Languages; }
get { return Site?.Languages; }
}
}
}

View File

@ -43,7 +43,7 @@ else
DynamicComponent = builder =>
{
foreach (Module module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId))
foreach (Module module in PageState.Modules)
{
// set renderid - this allows the framework to determine which components should be rendered when PageState changes
if (module.RenderId != PageState.RenderId)

View File

@ -0,0 +1,13 @@
// This is just a placeholder file
// It is necessary for the documentation to successfully build this project.
// Reason is that docfx will run the .net compiler and find references
// to this class in the project.
// But since the real class is just a .razor file, ATM docfx will fail.
//
// Note added 2024-06-27 by @iJungleboy.
// We hope that as .net and docfx improve, the razor-compiler will work in that scenario
// as well, and this file can be removed.
namespace Oqtane.UI;
public partial class RenderModeBoundary;

View File

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

View File

@ -3,8 +3,6 @@
@inject IInstallationService InstallationService
@inject IJSRuntime JSRuntime
@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)
{

View File

@ -1,6 +1,8 @@
@using System.Diagnostics.CodeAnalysis
@using System.Net
@using Microsoft.AspNetCore.Http
@using System.Globalization
@using System.Security.Claims
@namespace Oqtane.UI
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState
@ -95,6 +97,7 @@
{
Site site = null;
Page page = null;
List<Module> modules = null;
User user = null;
var editmode = false;
var refresh = false;
@ -103,7 +106,7 @@
_error = "";
Route route = new Route(_absoluteUri, SiteState.Alias.Path);
int moduleid = int.Parse(route.ModuleId);
int moduleid = int.Parse(route.ModuleId, CultureInfo.InvariantCulture);
var action = route.Action;
var querystring = Utilities.ParseQueryString(route.Query);
@ -124,12 +127,20 @@
{
if (querystring.ContainsKey("reload") && querystring["reload"] == "post")
{
// post back so that the cookies are set correctly - required on any change to the principal
var interop = new Interop(JSRuntime);
var fields = new { returnurl = "/" + NavigationManager.ToBaseRelativePath(_absoluteUri) };
string url = Utilities.TenantUrl(SiteState.Alias, "/pages/external/");
await interop.SubmitForm(url, fields);
return;
if (PageState.RenderMode == RenderModes.Interactive)
{
// post back so that the cookies are set correctly - required on any change to the principal
var interop = new Interop(JSRuntime);
var fields = new { returnurl = "/" + NavigationManager.ToBaseRelativePath(_absoluteUri) };
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
{
@ -149,7 +160,8 @@
if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == "sitekey" && item.Value == SiteState.Alias.SiteKey))
{
// get user
user = await UserService.GetUserAsync(authState.User.Identity.Name, SiteState.Alias.SiteId);
var userid = int.Parse(authState.User.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value);
user = await UserService.GetUserAsync(userid, SiteState.Alias.SiteId);
if (user != null)
{
user.IsAuthenticated = authState.User.Identity.IsAuthenticated;
@ -163,9 +175,9 @@
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);
lastsyncdate = sync.SyncDate;
if (sync.SyncEvents.Any())
@ -203,7 +215,7 @@
return;
}
if (PageState == null || refresh || PageState.Page.Path != route.PagePath)
if (refresh || PageState == null || PageState.Page.Path != route.PagePath)
{
page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
}
@ -243,105 +255,108 @@
}
}
if (page != null)
// check if user is authorized to view page
if (page != null && UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) && (Utilities.IsEffectiveAndNotExpired(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList)))
{
// check if user is authorized to view page
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList)))
// edit mode
if (user != null)
{
// edit mode
if (user != null)
if (querystring.ContainsKey("editmode") && querystring["edit"] == "true")
{
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;
}
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);
}
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
(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);
}
// get modules for current page
if (refresh || PageState.Modules == null || !PageState.Modules.Any() || PageState.Modules.First().PageId != page.PageId)
{
modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId);
}
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", ""));
}
modules = PageState.Modules;
}
// load additional metadata for current page
page = ProcessPage(page, site, user, SiteState.Alias);
// load additional metadata for modules
(page, modules) = ProcessModules(page, 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,
Modules = modules,
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 // page not found
else
{
// look for url mapping
var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath);
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
if (page == null)
{
var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl + route.Query;
NavigationManager.NavigateTo(url, false);
// check 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);
return;
}
}
else // not mapped
else
{
if (user == null)
{
// 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)));
return;
}
else
{
if (route.PagePath != "404")
{
// redirect to 404 page
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", ""));
}
else
{
// redirect to home page as a fallback
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));
}
}
}
// 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", ""));
}
else
{
// redirect to home page as a fallback
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));
}
}
}
@ -476,6 +491,7 @@
// retrieve module component resources
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
module.RenderMode = moduleobject.RenderMode;
module.Prerender = moduleobject.Prerender;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
@ -549,7 +565,7 @@
{
foreach (var resource in resources)
{
if (resource.Level != ResourceLevel.Site)
if (resource.ResourceType == ResourceType.Stylesheet || resource.Level != ResourceLevel.Site)
{
if (resource.Url.StartsWith("~"))
{

View File

@ -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

@ -66,21 +66,17 @@
{
if (!string.IsNullOrEmpty(content))
{
// format head content, remove scripts, and filter duplicate elements
content = content.Replace("\n", "");
var index = content.IndexOf("<");
while (index >= 0)
if (PageState.RenderMode == RenderModes.Interactive)
{
var element = content.Substring(index, content.IndexOf(">", index) - index + 1);
if (!string.IsNullOrEmpty(element) && (PageState.RenderMode == RenderModes.Static || (!element.ToLower().StartsWith("<script") && !element.ToLower().StartsWith("</script"))))
// remove scripts
var index = content.IndexOf("<script");
while (index >= 0)
{
if (!headcontent.Contains(element))
{
headcontent += element + "\n";
}
content = content.Remove(index, content.IndexOf("</script>") + 9 - index);
index = content.IndexOf("<script");
}
index = content.IndexOf("<", index + 1);
}
headcontent += content + "\n";
}
return headcontent;
}

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