Compare commits

..

481 Commits

Author SHA1 Message Date
1f0347682e Merge pull request #4553 from oqtane/master
5.2.1 Release
2024-08-22 12:15:38 -04:00
1aee385679 Merge pull request #4552 from oqtane/dev
5.2.1 Release
2024-08-22 12:15:15 -04:00
5dd8191692 Merge pull request #4551 from sbwalker/dev
fixed required field validation in Search Results Settings
2024-08-22 12:10:34 -04:00
b6422f9b80 fixed required field validation in Search Results Settings 2024-08-22 12:10:15 -04:00
a6c2c9c92f Merge pull request #4550 from sbwalker/dev
use localized Yes/No values when displaying Site Urls Default? option
2024-08-22 10:53:43 -04:00
247c573a6e use localized Yes/No values when displaying Site Urls Default? option 2024-08-22 10:53:27 -04:00
aa435d6e94 Merge pull request #4548 from sbwalker/dev
fix #4546 - handle cache invalidation for site deletion
2024-08-22 08:26:44 -04:00
f6858c221b fix #4546 - handle cache invalidation for site deletion 2024-08-22 08:26:29 -04:00
437aa4510b Merge pull request #4547 from sbwalker/dev
fix #4545 - Site Settings - UI Component Settings changes not refreshed after saving
2024-08-22 08:09:18 -04:00
430572fb32 fix #4545 - Site Settings - UI Component Settings changes not refreshed after saving 2024-08-22 08:08:55 -04:00
9f0b755d6f Merge pull request #4543 from sbwalker/dev
fix login redirect issue in sub-site where user has navigated directly to login page
2024-08-21 15:17:47 -04:00
66acb55a57 fix login redirect issue in sub-site where user has navigated directly to login page 2024-08-21 15:17:29 -04:00
f936d4c36e Merge pull request #4542 from sbwalker/dev
fix #4536 - deleted modules appearing in Page Management - Modules panel
2024-08-20 16:18:45 -04:00
c3ddb8df56 fix #4536 - deleted modules appearing in Page Management - Modules panel 2024-08-20 16:18:31 -04:00
81ce920e69 Merge pull request #4541 from sbwalker/dev
fix issues in default template for Interactive Client (WebAssembly) scenarios
2024-08-20 15:34:05 -04:00
3cb875d139 fix issues in default template for Interactive Client (WebAssembly) scenarios 2024-08-20 15:33:46 -04:00
fdb217d5c6 Merge pull request #4540 from sbwalker/dev
set BaseAddress for IHttpClientFactory
2024-08-20 15:22:39 -04:00
085cae3b5f set BaseAddress for IHttpClientFactory 2024-08-20 15:22:27 -04:00
e2f99a1554 Merge pull request #4538 from sbwalker/dev
fix #4498 build ServerState Assemblies collection in a more thread safe manner
2024-08-20 14:03:02 -04:00
aee0c27da7 fix #4498 build ServerState Assemblies collection in a more thread safe manner 2024-08-20 14:02:37 -04:00
accbf4ad8b Merge pull request #4535 from sbwalker/dev
use existing SiteKey
2024-08-20 08:35:36 -04:00
0ac1901f6b use existing SiteKey 2024-08-20 08:35:23 -04:00
c0a0deea78 Merge pull request #4532 from sbwalker/dev
ensure form name is unique in ActionDialog
2024-08-19 16:58:48 -04:00
e3f099441c ensure form name is unique in ActionDialog 2024-08-19 16:58:33 -04:00
840dd25cd1 Merge pull request #4530 from sbwalker/dev
fix issues with ActionDialog in static rendering
2024-08-19 09:34:23 -04:00
a493969f9b fix issues with ActionDialog in static rendering 2024-08-19 09:34:08 -04:00
cca42a10a1 Merge pull request #4529 from sbwalker/dev
fix CSS
2024-08-19 09:05:00 -04:00
2f4aa98c3c fix CSS 2024-08-19 09:04:46 -04:00
175cb9588c Merge pull request #4524 from sbwalker/dev
prevent scroll position from resetting to top of page when querystring or hash changes
2024-08-16 15:01:43 -04:00
a8976e7559 prevent scroll position from resetting to top of page when querystring or hash changes 2024-08-16 15:01:25 -04:00
b663528fb0 Merge pull request #4521 from sbwalker/dev
add ability to extract zip file contents in File Management
2024-08-14 15:54:30 -04:00
1a2ad55677 add ability to extract zip file contents in File Management 2024-08-14 15:54:13 -04:00
513d2a88c0 Merge pull request #4520 from sbwalker/dev
move HtmlText caching from repository to service layer
2024-08-14 10:01:11 -04:00
57ef4c0396 move HtmlText caching from repository to service layer 2024-08-14 10:00:56 -04:00
e9599ca2f4 Merge pull request #4519 from sbwalker/dev
fix #4517 - index error due to duplicate records on upgrade
2024-08-14 08:19:28 -04:00
2fc6dbc222 fix #4517 - index error due to duplicate records on upgrade 2024-08-14 08:19:14 -04:00
225933c442 Merge pull request #4518 from sbwalker/dev
update nuspec files for 5.2.1
2024-08-14 08:12:46 -04:00
36e2f048d7 update nuspec files for 5.2.1 2024-08-14 08:12:31 -04:00
0e158bce59 Merge pull request #4515 from thabaum/update-version-5.2.1-and-dependencies
Fixes #4514 - Update version 5.2.1 and dependencies
2024-08-14 07:58:55 -04:00
202201fd31 Merge pull request #4516 from ijaz-saeed/dev
search settings translation entry
2024-08-14 07:55:11 -04:00
4f8c928f44 search settings translation entry 2024-08-14 11:57:06 +05:00
ada062cf00 Update version to 5.2.1 and dependencies 2024-08-13 15:53:45 -07:00
32dc12912a Update dependencies 2024-08-13 15:51:49 -07:00
671d21adf4 Update dependencies 2024-08-13 15:50:56 -07:00
4e3f8e4b67 Update dependencies 2024-08-13 15:49:15 -07:00
586bb62073 Update version to 5.2.1 and dependencies 2024-08-13 15:48:32 -07:00
151bf83ab1 Update version to 5.2.1 and dependencies 2024-08-13 15:46:45 -07:00
8c618edb5a Update version to 5.2.1 2024-08-13 15:45:23 -07:00
6d6b0cf8c9 Update version to 5.2.1 and dependencies 2024-08-13 15:44:45 -07:00
c610608890 Update version to 5.2.1 and dependencies 2024-08-13 15:43:27 -07:00
28da61daab Update version 5.2.1 and dependencies 2024-08-13 15:39:15 -07:00
cbfc90f60b Update Version To 5.2.1 and Dependencies To Latest 2024-08-13 15:36:20 -07:00
fec92959d4 Merge pull request #4513 from sbwalker/dev
move folder permissions grid to dedicated tab for consistency
2024-08-13 16:48:34 -04:00
bb00d81eba move folder permissions grid to dedicated tab for consistency 2024-08-13 16:48:22 -04:00
bb55644c06 Merge pull request #4512 from sbwalker/dev
improve file name and extension validation
2024-08-12 17:02:22 -04:00
16215847cd improve file name and extension validation 2024-08-12 17:02:07 -04:00
8ee9aed817 Merge pull request #4509 from sbwalker/dev
improve SettingService
2024-08-12 10:20:56 -04:00
515c6402b9 improve SettingService 2024-08-12 10:20:44 -04:00
d1b94ec203 Merge pull request #4507 from leigh-pointer/Permissions-4503
Fix for #4503 Module Custom Permissions not being shown
2024-08-10 17:54:51 -04:00
a037d9167e Fix for #4503 Module Custom Permissions not being shown 2024-08-10 21:25:32 +02:00
3054d33e62 Merge pull request #4493 from thabaum/set-samesite-lax-visitor-culture-cookies
Fix #4492: Updates Culture and Visitor cookies to use "Lax" SameSite and Secure Cookie Options
2024-08-10 14:08:01 -04:00
6651e641e1 Merge pull request #4505 from sbwalker/dev
add search reindex capability
2024-08-10 10:02:08 -04:00
35f873a342 add search reindex capability 2024-08-10 10:01:52 -04:00
2d03ff38a1 Merge pull request #4504 from sbwalker/dev
replace dynamic query with linq
2024-08-09 17:16:30 -04:00
44a3db417b replace dynamic query with linq 2024-08-09 17:16:17 -04:00
f9ca702a12 Merge pull request #4502 from sbwalker/dev
fix #4499 - page modules not loaded properly
2024-08-09 13:11:31 -04:00
4073ff38eb fix #4499 - page modules not loaded properly 2024-08-09 13:11:19 -04:00
f0e2c9f1b6 Merge pull request #4501 from sbwalker/dev
eliminate database call for authenticated users
2024-08-09 13:00:35 -04:00
cf040f51b5 eliminate database call for authenticated users 2024-08-09 13:00:20 -04:00
dcf919fb36 Adds AntiForgery Cookie setting options.Cookie.HttpOnly = true; 2024-08-08 12:24:42 -07:00
aa19b81a68 Merge pull request #4487 from leigh-pointer/TemplateUpdate
Update Theme Template to Bootstrap 5.3.3
2024-08-08 14:44:27 -04:00
db8d77365c Merge pull request #4479 from pollux/patch-1
Fix admin/pages not showing 404 for unauthorized users
2024-08-08 14:42:08 -04:00
280eaea84a Merge pull request #4497 from sbwalker/dev
improve search result performance and relevancy
2024-08-08 14:11:43 -04:00
340ef46469 improve search result performance and relevancy 2024-08-08 14:11:27 -04:00
a5f8651941 Revert previous cookie HttpOnly option 2024-08-07 16:24:18 -07:00
8a18ee548e Merge pull request #4494 from sbwalker/dev
add missing indexes
2024-08-07 16:55:48 -04:00
ef791aa22a add missing indexes 2024-08-07 16:55:35 -04:00
4bdf2e1cc0 Update AntiForgery Token Cookie Option to HTTPOnly = true; 2024-08-07 13:21:18 -07:00
ffa0ca9379 Updates Culture and Visitor cookies to use "Lax" SameSite and Secure cookie options 2024-08-07 11:52:53 -07:00
d3b3d46fc1 Return to standard Bootstrap 2024-08-07 20:01:41 +02:00
7e7dd8efa9 Update Theme Template to Bootstrap 5.3.3 2024-08-07 10:34:40 +02:00
b4506f1133 Merge pull request #4483 from sbwalker/dev
include "://" on default Alias Protocol for consistency
2024-08-05 07:51:07 -04:00
7350c79113 include "://" on default Alias Protocol for consistency 2024-08-05 07:50:54 -04:00
5f7b60d3f4 Merge pull request #4480 from leigh-pointer/Log2xHttp
Removed the extra "://" from the Log Manager
2024-08-05 07:50:06 -04:00
266495a611 Removed the extra ";//" from the Log Manager
{alias.Protocol} return with ";//"
2024-08-03 12:26:33 +02:00
f5b4a7e77b Fix admin/pages not showing 404 for unauthorized users 2024-08-02 11:49:55 +02:00
51ed0f6487 Merge pull request #4472 from sbwalker/dev
fix #4471 - search pages not being added on upgrade
2024-07-27 09:51:22 -04:00
bd70def18a fix #4471 - search pages not being added on upgrade 2024-07-27 09:51:02 -04:00
93a9cf3b31 Update README.md 2024-07-25 13:22:02 -04:00
751287999f Update README.md 2024-07-25 11:57:24 -04:00
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
206 changed files with 6994 additions and 2911 deletions

View File

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

View File

@ -40,10 +40,14 @@
</div> </div>
</div> </div>
</div> </div>
<br />
<button type="button" class="btn btn-success" @onclick="SaveFile">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveFile">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br /> @if (_name.ToLower().EndsWith(".zip"))
<br /> {
<button type="button" class="btn btn-primary mx-1" @onclick="UnzipFile">Unzip</button>
}
<br /><br />
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo> <AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
</form> </form>
} }
@ -126,4 +130,18 @@
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
private async Task UnzipFile()
{
try
{
await FileService.UnzipFileAsync(_fileId);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Unzipping File {FileId} {Error}", _fileId, ex.Message);
AddModuleMessage(Localizer["Error.File.Unzip"], MessageType.Error);
}
}
} }

View File

@ -8,67 +8,77 @@
@if (_folders != null) @if (_folders != null)
{ {
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <TabStrip>
<div class="container"> <TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
<div class="row mb-1 align-items-center"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<Label Class="col-sm-3" For="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label> <div class="container">
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<select id="parent" class="form-select" @bind="@_parentId" required> <Label Class="col-sm-3" For="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
@if (PageState.QueryString.ContainsKey("id")) <div class="col-sm-9">
{ <select id="parent" class="form-select" @bind="@_parentId" required>
<option value="-1">&lt;@Localizer["NoParent"]&gt;</option> @if (PageState.QueryString.ContainsKey("id"))
} {
@foreach (Folder folder in _folders) <option value="-1">&lt;@Localizer["NoParent"]&gt;</option>
{ }
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option> @foreach (Folder folder in _folders)
} {
</select> <option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="type" HelpText="Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users" ResourceKey="Type">Type: </Label>
<div class="col-sm-9">
@if (PageState.QueryString.ContainsKey("id"))
{
<input id="type" class="form-control" readonly @bind="@_type" />
}
else
{
<select id="type" class="form-select" @bind="@_type" required>
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
</select>
}
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes." ResourceKey="ImageSizes">Image Sizes: </Label>
<div class="col-sm-9">
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
<div class="col-sm-9">
<input id="capacity" class="form-control" @bind="@_capacity" required />
</div>
</div>
</div> </div>
</div> @if (PageState.QueryString.ContainsKey("id"))
<div class="row mb-1 align-items-center"> {
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label> <br />
<div class="col-sm-9"> <AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
<input id="name" class="form-control" @bind="@_name" maxlength="256" required /> }
</div> </form>
</div> </TabPanel>
<div class="row mb-1 align-items-center"> <TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
<Label Class="col-sm-3" For="type" HelpText="Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users" ResourceKey="Type">Type: </Label> <div class="container">
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
@if (PageState.QueryString.ContainsKey("id"))
{
<input id="type" class="form-control" readonly @bind="@_type" />
}
else
{
<select id="type" class="form-select" @bind="@_type" required>
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
</select>
}
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes." ResourceKey="ImageSizes">Image Sizes: </Label>
<div class="col-sm-9">
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
<div class="col-sm-9">
<input id="capacity" class="form-control" @bind="@_capacity" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-12">
<Label Class="col-sm-3" For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </Label>
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" PermissionList="@_permissions" @ref="_permissionGrid" /> <PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" PermissionList="@_permissions" @ref="_permissionGrid" />
</div> </div>
</div> </div>
</div> </TabPanel>
</form> </TabStrip>
<br /><br />
<br />
@if (!_isSystem) @if (!_isSystem)
{ {
<button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button>
@ -80,11 +90,6 @@
@((MarkupString)"&nbsp;") @((MarkupString)"&nbsp;")
<ActionDialog Header="Delete Folder" Message="Are You Sure You Wish To Delete This Folder?" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFolder())" ResourceKey="DeleteFolder" /> <ActionDialog Header="Delete Folder" Message="Are You Sure You Wish To Delete This Folder?" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFolder())" ResourceKey="DeleteFolder" />
} }
<br /><br />
@if (PageState.QueryString.ContainsKey("id"))
{
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
}
} }
@code { @code {
@ -170,6 +175,7 @@
try try
{ {
Folder folder; Folder folder;
if (_folderId != -1) if (_folderId != -1)
{ {
folder = await FolderService.GetFolderAsync(_folderId); folder = await FolderService.GetFolderAsync(_folderId);
@ -179,8 +185,6 @@
folder = new Folder(); folder = new Folder();
} }
folder.SiteId = PageState.Site.SiteId;
if (_parentId == -1) if (_parentId == -1)
{ {
folder.ParentId = null; folder.ParentId = null;
@ -190,6 +194,14 @@
folder.ParentId = _parentId; folder.ParentId = _parentId;
} }
// check for duplicate folder names
if (_folders.Any(item => item.ParentId == folder.ParentId && item.Name == _name && item.FolderId != _folderId))
{
AddModuleMessage(Localizer["Message.Folder.Duplicate"], MessageType.Warning);
return;
}
folder.SiteId = PageState.Site.SiteId;
folder.Name = _name; folder.Name = _name;
folder.Type = _type; folder.Type = _type;
folder.ImageSizes = _imagesizes; folder.ImageSizes = _imagesizes;

View File

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

View File

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

View File

@ -93,6 +93,7 @@
private string _code = string.Empty; private string _code = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override bool? Prerender => true;
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
@ -207,17 +208,20 @@
{ {
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username); await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
// return url is not specified if user navigated directly to login page
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
if (hybrid) if (hybrid)
{ {
// hybrid apps utilize an interactive login // hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged(); authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(PageState.ReturnUrl, true)); NavigationManager.NavigateTo(NavigateUrl(returnurl, true));
} }
else else
{ {
// post back to the Login page so that the cookies are set correctly // post back to the Login page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(PageState.ReturnUrl) }; var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(returnurl) };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/"); string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
await interop.SubmitForm(url, fields); await interop.SubmitForm(url, fields);
} }

View File

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

View File

@ -125,7 +125,7 @@
<TabPanel Name="Upload" ResourceKey="Upload" Heading="Upload"> <TabPanel Name="Upload" ResourceKey="Upload" Heading="Upload">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" /> <FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,12 @@
@namespace Oqtane.Modules.Admin.Pages @namespace Oqtane.Modules.Admin.Pages
@using Oqtane.Interfaces @using Oqtane.Interfaces
@using System.Globalization
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IPageService PageService @inject IPageService PageService
@inject IPageModuleService PageModuleService @inject IPageModuleService PageModuleService
@inject IThemeService ThemeService @inject IModuleService ModuleService
@inject IThemeService ThemeService
@inject ISystemService SystemService @inject ISystemService SystemService
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@ -30,7 +32,7 @@
<div class="col-sm-9"> <div class="col-sm-9">
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required> <select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option> <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) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList) && page.PageId != _pageId)
{ {
@ -301,6 +303,7 @@
private bool validated = false; private bool validated = false;
private List<ThemeControl> _themes = new List<ThemeControl>(); private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pages;
private int _pageId; private int _pageId;
private string _name; private string _name;
private string _currentparentid; private string _currentparentid;
@ -344,6 +347,7 @@
{ {
try try
{ {
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
_pageId = Int32.Parse(PageState.QueryString["id"]); _pageId = Int32.Parse(PageState.QueryString["id"]);
_page = await PageService.GetPageAsync(_pageId); _page = await PageService.GetPageAsync(_pageId);
_icons = await SystemService.GetIconsAsync(); _icons = await SystemService.GetIconsAsync();
@ -359,10 +363,10 @@
else else
{ {
_parentid = _page.ParentId.ToString(); _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>(); _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)) if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{ {
@ -414,7 +418,8 @@
_permissions = _page.PermissionList; _permissions = _page.PermissionList;
// page modules // page modules
_pageModules = PageState.Modules.Where(m => m.PageId == _page.PageId).ToList(); var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
_pageModules = modules.Where(item => item.PageId == _page.PageId && !item.IsDeleted).ToList();
// audit // audit
_createdby = _page.CreatedBy; _createdby = _page.CreatedBy;
@ -446,8 +451,8 @@
{ {
_parentid = (string)e.Value; _parentid = (string)e.Value;
_children = new List<Page>(); _children = new List<Page>();
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid)))) foreach (Page p in _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)) if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{ {
_children.Add(p); _children.Add(p);
@ -548,7 +553,7 @@
} }
else else
{ {
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId); Page parent = _pages.FirstOrDefault(item => item.PageId == _page.ParentId);
if (parent.Path == string.Empty) if (parent.Path == string.Empty)
{ {
_page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path); _page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
@ -559,7 +564,6 @@
} }
} }
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId)) if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId))
{ {
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning); AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
@ -581,11 +585,11 @@
_page.Order = 0; _page.Order = 0;
break; break;
case "<": case "<":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid); child = _pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) _page.Order = child.Order - 1; if (child != null) _page.Order = child.Order - 1;
break; break;
case ">": case ">":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid); child = _pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) _page.Order = child.Order + 1; if (child != null) _page.Order = child.Order + 1;
break; break;
case ">>": case ">>":
@ -643,11 +647,11 @@
await logger.LogInformation("Page Saved {Page}", _page); await logger.LogInformation("Page Saved {Page}", _page);
if (!string.IsNullOrEmpty(PageState.ReturnUrl)) if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{ {
NavigationManager.NavigateTo(PageState.ReturnUrl, true); NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
} }
else else
{ {
NavigationManager.NavigateTo(NavigateUrl(), true); // redirect to page being edited NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
} }
} }
else else

View File

@ -5,11 +5,11 @@
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @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" /> <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> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
@ -17,7 +17,7 @@
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td> <td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td> <td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td> <td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
<td>@(new string('-', context.Level * 2))@(context.Name)</td> <td>@(new string('-', context.Level * 2))@(context.Name)</td>
@ -28,6 +28,21 @@
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; 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) private async Task DeletePage(Page page)
{ {
try try

View File

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

View File

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

View File

@ -0,0 +1,121 @@
@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>
<ActionDialog Header="Reindex" Message="Are You Sure You Wish To Reindex Search Content?" Action="Reindex" Class="btn btn-danger" OnClick="@(async () => await Reindex())" ResourceKey="Reindex" />
<br /><br />
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private string _searchProvider;
private string _enabled = "True";
private string _lastIndexedOn = "";
private string _ignorePages = "";
private string _ignoreEntities = "";
private string _minimumWordLength = "3";
private string _ignoreWords = "the,be,to,of,and,a,i,in,that,have,it,for,not,on,with,he,as,you,do,at,this,but,his,by,from,they,we,say,her,she,or,an,will,my,one,all,would,there,their,what,so,up,out,if,about,who,get,which,go,me,when,make,can,like,time,no,just,him,know,take,people,into,year,your,good,some,could,them,see,other,than,then,now,look,only,come,its,over,think,also,back,after,use,two,how,our,work,first,well,way,even,new,want,because,any,these,give,day,most,us";
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", _enabled);
_lastIndexedOn = SettingService.GetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn);
_ignorePages = SettingService.GetSetting(settings, "Search_IgnorePages", _ignorePages);
_ignoreEntities = SettingService.GetSetting(settings, "Search_IgnoreEntities", _ignoreEntities);
_minimumWordLength = SettingService.GetSetting(settings, "Search_MininumWordLength", _minimumWordLength);
_ignoreWords = SettingService.GetSetting(settings, "Search_IgnoreWords", _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);
}
}
private async Task Reindex()
{
try
{
_lastIndexedOn = DateTime.MinValue.ToString();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
AddModuleMessage(Localizer["Message.Reindex"], 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={WebUtility.UrlEncode(_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" />
</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" />
</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 @namespace Oqtane.Modules.Admin.Site
@inherits ModuleBase @inherits ModuleBase
@using System.Text.RegularExpressions @using System.Text.RegularExpressions
@using Microsoft.Extensions.DependencyInjection
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject ISiteService SiteService @inject ISiteService SiteService
@inject IPageService PageService
@inject ITenantService TenantService @inject ITenantService TenantService
@inject IDatabaseService DatabaseService @inject IDatabaseService DatabaseService
@inject IAliasService AliasService @inject IAliasService AliasService
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IServiceProvider ServiceProvider
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject INotificationService NotificationService @inject INotificationService NotificationService
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@ -27,7 +30,7 @@
<div class="col-sm-9"> <div class="col-sm-9">
<select id="homepage" class="form-select" @bind="@_homepageid" required> <select id="homepage" class="form-select" @bind="@_homepageid" required>
<option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option> <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)) if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
{ {
@ -74,7 +77,7 @@
<div class="row mb-1 align-items-center"> <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> <Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<FileManager FileId="@_logofileid" Filter="@_ImageFiles" @ref="_logofilemanager" /> <FileManager FileId="@_logofileid" Filter="@_imageFiles" @ref="_logofilemanager" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -125,18 +128,32 @@
</div> </div>
</div> </div>
</Section> </Section>
<Section Name="FileExtensions" Heading="File Extensions" ResourceKey="FileExtensions"> <Section Name="Functionality" Heading="Functionality" ResourceKey="Functionality">
<div class="container"> <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"> <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> <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"> <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> </div>
<div class="row mb-1 align-items-center"> <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> <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"> <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> </div>
</div> </div>
@ -292,7 +309,7 @@
} }
</td> </td>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.IsDefault</td> <td>@((context.IsDefault) ? SharedLocalizer["Yes"] : SharedLocalizer["No"])</td>
} }
else else
{ {
@ -319,7 +336,7 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label> <Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="rendermode" class="form-select" @bind="@_rendermode" required> <select id="rendermode" class="form-select" value="@_rendermode" @onchange="(e => RenderModeChanged(e))" required>
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option> <option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option> <option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option> <option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
@ -337,7 +354,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="prerender" HelpText="Specifies if interactive components should prerender their output" ResourceKey="Prerender">Prerender? </Label> <Label Class="col-sm-3" For="prerender" HelpText="Specifies if interactive components should prerender their output on the server" ResourceKey="Prerender">Prerender: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="prerender" class="form-select" @bind="@_prerender" required> <select id="prerender" class="form-select" @bind="@_prerender" required>
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
@ -394,12 +411,15 @@
private bool _initialized = false; private bool _initialized = false;
private List<ThemeControl> _themes = new List<ThemeControl>(); private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pages;
private string _name = string.Empty; private string _name = string.Empty;
private string _homepageid = "-"; private string _homepageid = "-";
private string _isdeleted; private string _isdeleted;
private string _sitemap = ""; private string _sitemap = "";
private string _siteguid = ""; private string _siteguid = "";
private string _version = ""; private string _version = "";
private int _logofileid = -1; private int _logofileid = -1;
private FileManager _logofilemanager; private FileManager _logofilemanager;
private int _faviconfileid = -1; private int _faviconfileid = -1;
@ -407,8 +427,15 @@
private string _themetype = ""; private string _themetype = "";
private string _containertype = ""; private string _containertype = "";
private string _admincontainertype = ""; 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 _headcontent = string.Empty;
private string _bodycontent = string.Empty; private string _bodycontent = string.Empty;
private string _smtphost = string.Empty; private string _smtphost = string.Empty;
private string _smtpport = string.Empty; private string _smtpport = string.Empty;
private string _smtpssl = "False"; private string _smtpssl = "False";
@ -419,25 +446,28 @@
private string _smtpsender = string.Empty; private string _smtpsender = string.Empty;
private string _smtprelay = "False"; private string _smtprelay = "False";
private string _smtpenabled = "True"; private string _smtpenabled = "True";
private string _ImageFiles = string.Empty;
private string _UploadableFiles = string.Empty;
private int _retention = 30; private int _retention = 30;
private string _pwaisenabled; private string _pwaisenabled;
private int _pwaappiconfileid = -1; private int _pwaappiconfileid = -1;
private FileManager _pwaappiconfilemanager; private FileManager _pwaappiconfilemanager;
private int _pwasplashiconfileid = -1; private int _pwasplashiconfileid = -1;
private FileManager _pwasplashiconfilemanager; private FileManager _pwasplashiconfilemanager;
private List<Alias> _aliases; private List<Alias> _aliases;
private int _aliasid = -1; private int _aliasid = -1;
private string _aliasname; private string _aliasname;
private string _defaultalias; private string _defaultalias;
private string _rendermode = RenderModes.Interactive; private string _rendermode = RenderModes.Interactive;
private string _runtime = Runtimes.Server; private string _runtime = Runtimes.Server;
private string _prerender = "True"; private string _prerender = "True";
private string _hybrid = "False"; private string _hybrid = "False";
private string _tenant = string.Empty; private string _tenant = string.Empty;
private string _database = string.Empty; private string _database = string.Empty;
private string _connectionstring = string.Empty; private string _connectionstring = string.Empty;
private string _createdby; private string _createdby;
private DateTime _createdon; private DateTime _createdon;
private string _modifiedby; private string _modifiedby;
@ -454,6 +484,10 @@
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null) if (site != null)
{ {
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
_name = site.Name; _name = site.Name;
if (site.HomePageId != null) if (site.HomePageId != null)
{ {
@ -480,23 +514,23 @@
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer; _containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer; _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 // page content
_headcontent = site.HeadContent; _headcontent = site.HeadContent;
_bodycontent = site.BodyContent; _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 // SMTP
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty); _smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty); _smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False"); _smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
@ -508,11 +542,16 @@
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True"); _smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30")); _retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
// file extensions // PWA
_ImageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles); _pwaisenabled = site.PwaIsEnabled.ToString();
_ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles; if (site.PwaAppIconFileId != null)
_UploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles); {
_UploadableFiles = (string.IsNullOrEmpty(_UploadableFiles)) ? Constants.UploadableFiles : _UploadableFiles; _pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
// aliases // aliases
await GetAliases(); 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() private async Task SaveSite()
{ {
validated = true; validated = true;
@ -656,160 +712,161 @@
} }
} }
site = await SiteService.UpdateSiteAsync(site); site = await SiteService.UpdateSiteAsync(site);
// SMTP // 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, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true); settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true); settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true); settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true); settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true); settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true); settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true); settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
// file extensions // functionality
settings = SettingService.SetSetting(settings, "ImageFiles", (_ImageFiles != Constants.ImageFiles) ? _ImageFiles.Replace(" ", "") : "", false); settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);
settings = SettingService.SetSetting(settings, "UploadableFiles", (_UploadableFiles != Constants.UploadableFiles) ? _UploadableFiles.Replace(" ", "") : "", false); 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 NavigationManager.NavigateTo(NavigateUrl(), true); // reload
} }
} }
else else
{ {
AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error.SaveSite"], MessageType.Error); AddModuleMessage(Localizer["Error.SaveSite"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
private async Task DeleteSite() private async Task DeleteSite()
{ {
try try
{ {
var aliases = await AliasService.GetAliasesAsync(); var aliases = await AliasService.GetAliasesAsync();
if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId)) if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId))
{ {
await SiteService.DeleteSiteAsync(PageState.Site.SiteId); await SiteService.DeleteSiteAsync(PageState.Site.SiteId);
await logger.LogInformation("Site Deleted {SiteId}", 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)) foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
{ {
await AliasService.DeleteAliasAsync(alias.AliasId); await AliasService.DeleteAliasAsync(alias.AliasId);
} }
var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId); var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId);
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true); NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true);
} }
else else
{ {
AddModuleMessage(Localizer["Message.FailAuth.DeleteSite"], MessageType.Warning); AddModuleMessage(Localizer["Message.FailAuth.DeleteSite"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); await logger.LogError(ex, "Error Deleting Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error.DeleteSite"], MessageType.Error); AddModuleMessage(Localizer["Error.DeleteSite"], MessageType.Error);
} }
} }
private async Task SendEmail() private async Task SendEmail()
{ {
if (_smtphost != "" && _smtpport != "" && _smtpsender != "") if (_smtphost != "" && _smtpport != "" && _smtpsender != "")
{ {
try try
{ {
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true); settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true); settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true); settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true); settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
await logger.LogInformation("Site SMTP Settings Saved"); 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.")); 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); AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
await ScrollToPageTop(); await ScrollToPageTop();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Testing SMTP Configuration"); await logger.LogError(ex, "Error Testing SMTP Configuration");
AddModuleMessage(Localizer["Error.Smtp.TestConfig"], MessageType.Error); AddModuleMessage(Localizer["Error.Smtp.TestConfig"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Message.Required.Smtp"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.Smtp"], MessageType.Warning);
} }
} }
private void ToggleSMTPPassword() private void ToggleSMTPPassword()
{ {
if (_smtppasswordtype == "password") if (_smtppasswordtype == "password")
{ {
_smtppasswordtype = "text"; _smtppasswordtype = "text";
_togglesmtppassword = SharedLocalizer["HidePassword"]; _togglesmtppassword = SharedLocalizer["HidePassword"];
} }
else else
{ {
_smtppasswordtype = "password"; _smtppasswordtype = "password";
_togglesmtppassword = SharedLocalizer["ShowPassword"]; _togglesmtppassword = SharedLocalizer["ShowPassword"];
} }
} }
private async Task GetAliases() private async Task GetAliases()
{ {
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
_aliases = await AliasService.GetAliasesAsync(); _aliases = await AliasService.GetAliasesAsync();
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList(); _aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList();
} }
} }
private void AddAlias() private void AddAlias()
{ {
_aliases.Add(new Alias { AliasId = 0, Name = "", IsDefault = false }); _aliases.Add(new Alias { AliasId = 0, Name = "", IsDefault = false });
_aliasid = 0; _aliasid = 0;
_aliasname = ""; _aliasname = "";
_defaultalias = "False"; _defaultalias = "False";
StateHasChanged(); StateHasChanged();
} }
private void EditAlias(Alias alias) private void EditAlias(Alias alias)
{ {
_aliasid = alias.AliasId; _aliasid = alias.AliasId;
_aliasname = alias.Name; _aliasname = alias.Name;
_defaultalias = alias.IsDefault.ToString(); _defaultalias = alias.IsDefault.ToString();
StateHasChanged(); StateHasChanged();
} }
private async Task DeleteAlias(Alias alias) private async Task DeleteAlias(Alias alias)
{ {
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
await AliasService.DeleteAliasAsync(alias.AliasId); await AliasService.DeleteAliasAsync(alias.AliasId);
await GetAliases(); await GetAliases();
StateHasChanged(); StateHasChanged();
} }
} }
private async Task SaveAlias() private async Task SaveAlias()
{ {
@ -861,11 +918,11 @@
} }
} }
private async Task CancelAlias() private async Task CancelAlias()
{ {
await GetAliases(); await GetAliases();
_aliasid = -1; _aliasid = -1;
_aliasname = ""; _aliasname = "";
StateHasChanged(); StateHasChanged();
} }
} }

View File

@ -125,7 +125,7 @@
<TabPanel Name="Upload" ResourceKey="Upload"> <TabPanel Name="Upload" ResourceKey="Upload">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" /> <FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,11 +35,11 @@
{ {
if (Disabled) if (Disabled)
{ {
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button> <button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
} }
else else
{ {
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button> <button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
} }
} }
} }
@ -51,25 +51,25 @@ else
<div class="modal" tabindex="-1" role="dialog"> <div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <form class="app-form-inline" method="post" @formname="@($"ActionDialogCloseForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
<h5 class="modal-title">@Header</h5> <input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<form method="post" @formname="@($"ActionDialogCloseForm{Id}")" @onsubmit="DisplayModal" data-enhance> <div class="modal-header">
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <h5 class="modal-title">@Header</h5>
<button type="submit" class="btn-close" aria-label="Close"></button> <button type="submit" class="btn-close" aria-label="Close"></button>
</form> </div>
</div> </form>
<div class="modal-body"> <div class="modal-body">
<p>@Message</p> <p>@Message</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@if (!string.IsNullOrEmpty(Action)) @if (!string.IsNullOrEmpty(Action))
{ {
<form method="post" @formname="@($"ActionDialogConfirmForm{Id}")" @onsubmit="Confirm" data-enhance> <form method="post" @formname="@($"ActionDialogConfirmForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="Confirm" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button> <button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button>
</form> </form>
} }
<form method="post" @formname="@($"ActionDialogCancelForm{Id}")" @onsubmit="DisplayModal" data-enhance> <form method="post" @formname="@($"ActionDialogCancelForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn btn-secondary">@SharedLocalizer["Cancel"]</button> <button type="submit" class="btn btn-secondary">@SharedLocalizer["Cancel"]</button>
</form> </form>
@ -83,13 +83,13 @@ else
{ {
if (Disabled) if (Disabled)
{ {
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button> <button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
} }
else else
{ {
<form method="post" @formname="@($"ActionDialogActionForm{Id}")" @onsubmit="DisplayModal" data-enhance> <form method="post" class="app-form-inline" @formname="@($"ActionDialogActionForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button> <button type="submit" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
</form> </form>
} }
} }
@ -101,6 +101,8 @@ else
private bool _editmode = false; private bool _editmode = false;
private bool _authorized = false; private bool _authorized = false;
private string _iconSpan = string.Empty; private string _iconSpan = string.Empty;
private string _openIconSpan = string.Empty;
private string _openText = string.Empty;
[Parameter] [Parameter]
public string Header { get; set; } // required public string Header { get; set; } // required
@ -138,6 +140,9 @@ else
[Parameter] [Parameter]
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
[Parameter]
public bool IconOnly { get; set; } // optional - specifies only icon in opening link
[Parameter] [Parameter]
public string Id { get; set; } // optional - specifies a unique id for the compoment - required when there are multiple component instances on a page in static rendering public string Id { get; set; } // optional - specifies a unique id for the compoment - required when there are multiple component instances on a page in static rendering
@ -157,6 +162,7 @@ else
{ {
Text = Action; Text = Action;
} }
if (string.IsNullOrEmpty(Class)) if (string.IsNullOrEmpty(Class))
{ {
Class = "btn btn-success"; Class = "btn btn-success";
@ -169,20 +175,32 @@ else
if (!string.IsNullOrEmpty(IconName)) 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; IconName = "oi oi-" + IconName;
} }
_iconSpan = $"<span class=\"{IconName}\"></span>&nbsp;"; _openIconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : "&nbsp")}";
_iconSpan = $"<span class=\"{IconName}\"></span>&nbsp";
} }
Text = Localize(nameof(Text), Text); Text = Localize(nameof(Text), Text);
Header = Localize(nameof(Header), Header); Header = Localize(nameof(Header), Header);
Message = Localize(nameof(Message), Message); Message = Localize(nameof(Message), Message);
_openText = Text;
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList; _permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
_authorized = IsAuthorized(); _authorized = IsAuthorized();
if (string.IsNullOrEmpty(Id)) Id = "1";
if (PageState.QueryString.ContainsKey("dialog")) if (PageState.QueryString.ContainsKey("dialog"))
{ {
_visible = (PageState.QueryString["dialog"] == Id); _visible = (PageState.QueryString["dialog"] == Id);

View File

@ -145,7 +145,10 @@
if (!string.IsNullOrEmpty(IconName)) 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; IconName = "oi oi-" + IconName;
} }

View File

@ -314,7 +314,6 @@
await SetImage(); await SetImage();
await OnSelect.InvokeAsync(FileId); await OnSelect.InvokeAsync(FileId);
StateHasChanged(); StateHasChanged();
} }
private async Task SetImage() private async Task SetImage()

View File

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

View File

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

View File

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

View File

@ -111,7 +111,7 @@
[Parameter] [Parameter]
public List<Permission> PermissionList { get; set; } public List<Permission> PermissionList { get; set; }
protected override async Task OnInitializedAsync() protected override async Task OnParametersSetAsync()
{ {
if (!string.IsNullOrEmpty(Permissions)) if (!string.IsNullOrEmpty(Permissions))
{ {

View File

@ -4,11 +4,11 @@ using System.Threading.Tasks;
namespace Oqtane.Modules.Controls namespace Oqtane.Modules.Controls
{ {
public class RichTextEditorInterop public class QuillEditorInterop
{ {
private readonly IJSRuntime _jsRuntime; private readonly IJSRuntime _jsRuntime;
public RichTextEditorInterop(IJSRuntime jsRuntime) public QuillEditorInterop(IJSRuntime jsRuntime)
{ {
_jsRuntime = 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 try
{ {
_jsRuntime.InvokeAsync<object>( _jsRuntime.InvokeAsync<object>(
"Oqtane.RichTextEditor.insertQuillImage", "Oqtane.RichTextEditor.insertQuillImage",
quillElement, imageUrl, altText); quillElement, imageUrl, altText, editorIndex);
return Task.CompletedTask; return Task.CompletedTask;
} }
catch catch

View File

@ -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,127 +1,22 @@
@using System.Text.RegularExpressions @using System.Text.RegularExpressions
@using Microsoft.AspNetCore.Components.Rendering
@using Microsoft.Extensions.DependencyInjection
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleControlBase @inherits ModuleControlBase
@inject IServiceProvider ServiceProvider
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IStringLocalizer<RichTextEditor> Localizer @inject IStringLocalizer<RichTextEditor> Localizer
<div class="row" style="margin-bottom: 50px;"> <div class="row" style="margin-bottom: 50px;">
<div class="col"> <div class="col">
<TabStrip ActiveTab="@_activetab"> @_textEditorComponent
@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="@_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>
}
</TabStrip>
</div> </div>
</div> </div>
@code { @code {
private bool _initialized = false; private string _textEditorType;
private RenderFragment _textEditorComponent;
private RichTextEditorInterop interop; private ITextEditor _textEditor;
private FileManager _fileManager;
private string _activetab = "Rich";
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;
[Parameter] [Parameter]
public string Content { get; set; } public string Content { get; set; }
@ -133,198 +28,100 @@
public string Placeholder { get; set; } public string Placeholder { get; set; }
[Parameter] [Parameter]
public bool AllowFileManagement { get; set; } = true; public string Provider { get; set; }
[Parameter] [Parameter(CaptureUnmatchedValues = true)]
public bool AllowRichText { get; set; } = 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>()
{
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() protected override void OnInitialized()
{ {
interop = new RichTextEditorInterop(JSRuntime); _textEditorType = GetTextEditorType();
if (string.IsNullOrEmpty(Placeholder))
{
Placeholder = Localizer["Placeholder"];
}
} }
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
_richhtml = Content; _textEditorComponent = (builder) =>
_rawhtml = Content;
_originalrawhtml = _rawhtml; // preserve for comparison later
_originalrichhtml = "";
_contentchanged = true; // identifies when Content parameter has changed
if (!AllowRichText)
{ {
_activetab = "Raw"; CreateTextEditor(builder);
} };
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
await base.OnAfterRenderAsync(firstRender); if(_textEditor != null)
if (AllowRichText)
{ {
if (firstRender) _textEditor.Initialize(Content);
{
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);
}
else
{
// preserve changed content on re-render event
var richhtml = await interop.GetHtml(_editorElement);
if (richhtml != _richhtml)
{
_richhtml = richhtml;
await interop.LoadEditorContent(_editorElement, _richhtml);
}
}
}
}
_contentchanged = false;
} }
}
public void CloseRichFileManager() await base.OnAfterRenderAsync(firstRender);
{
_richfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
public void CloseRawFileManager()
{
_rawfilemanager = false;
_message = string.Empty;
StateHasChanged();
} }
public async Task<string> GetHtml() public async Task<string> GetHtml()
{ {
// evaluate raw html content as first priority return await _textEditor.GetContent();
if (_rawhtml != _originalrawhtml) }
{
return _rawhtml;
}
else
{
var richhtml = "";
if (AllowRichText) private void CreateTextEditor(RenderTreeBuilder builder)
{
if(!string.IsNullOrEmpty(_textEditorType))
{
var editorType = Type.GetType(_textEditorType);
if (editorType != null)
{ {
richhtml = await interop.GetHtml(_editorElement); builder.OpenComponent(0, editorType);
}
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml)) var attributes = new Dictionary<string, object>
{
// convert Quill's empty content to empty string
if (richhtml == "<p><br></p>")
{ {
richhtml = string.Empty; { "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];
}
}
} }
return richhtml;
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();
} }
else }
}
private string GetTextEditorType()
{
const string EditorSettingName = "TextEditor";
if(!string.IsNullOrEmpty(Provider))
{
var provider = ServiceProvider.GetServices<ITextEditor>().FirstOrDefault(i => i.Name.Equals(Provider, StringComparison.OrdinalIgnoreCase));
if(provider != null)
{ {
// return original raw html content return Utilities.GetFullTypeName(provider.GetType().AssemblyQualifiedName);
return _originalrawhtml;
} }
} }
}
public async Task InsertRichImage() return SettingService.GetSetting(PageState.Site.Settings, EditorSettingName, Constants.DefaultTextEditor);
{ }
_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));
_richhtml = await interop.GetHtml(_editorElement);
_richfilemanager = false;
}
else
{
_message = Localizer["Message.Require.Image"];
}
}
else
{
_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();
}
} }

View File

@ -23,7 +23,7 @@
</li> </li>
} }
</ul> </ul>
<div class="tab-content"> <div class="tab-content @TabContentClass">
<br /> <br />
@ChildContent @ChildContent
</div> </div>
@ -47,6 +47,9 @@
[Parameter] [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) 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() protected override void OnInitialized()
{ {
if (string.IsNullOrEmpty(Id)) 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"> <TabPanel Name="Edit" Heading="Edit" ResourceKey="Edit">
@if (_content != null) @if (_content != null)
{ {
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" AllowRawHtml="@_allowrawhtml" @ref="@RichTextEditorHtml"></RichTextEditor> <RichTextEditor Content="@_content" @ref="@RichTextEditorHtml"></RichTextEditor>
<br /> <br />
<button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@ -47,44 +47,34 @@
</TabStrip> </TabStrip>
@code { @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>() private RichTextEditor RichTextEditorHtml;
{ private string _content = null;
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" }, private string _createdby;
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" } private DateTime _createdon;
}; private string _modifiedby;
private DateTime _modifiedon;
private List<Models.HtmlText> _htmltexts;
private string _view = "";
private RichTextEditor RichTextEditorHtml; protected override async Task OnInitializedAsync()
private bool _allowfilemanagement; {
private bool _allowrawhtml; try
private string _content = null; {
private string _createdby; await LoadContent();
private DateTime _createdon; }
private string _modifiedby; catch (Exception ex)
private DateTime _modifiedon; {
private List<Models.HtmlText> _htmltexts; await logger.LogError(ex, "Error Loading Content {Error}", ex.Message);
private string _view = ""; AddModuleMessage(Localizer["Error.Content.Load"], MessageType.Error);
}
}
protected override async Task OnInitializedAsync() private async Task LoadContent()
{ {
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()
{
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId); var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null) if (htmltext != null)
{ {

View File

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

View File

@ -15,7 +15,7 @@ namespace Oqtane.Modules.HtmlText
Version = "1.0.1", Version = "1.0.1",
ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server", ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server",
ReleaseVersions = "1.0.0,1.0.1", ReleaseVersions = "1.0.0,1.0.1",
SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client", SettingsType = string.Empty,
Resources = new List<Resource>() Resources = new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" } 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 string RenderMode { get { return RenderModes.Interactive; } } // interactive by default
public virtual bool? Prerender { get { return null; } } // allows the Site Prerender property to be overridden
// url parameters // url parameters
public virtual string UrlParametersTemplate { get; set; } public virtual string UrlParametersTemplate { get; set; }
@ -276,7 +278,6 @@ namespace Oqtane.Modules
public void AddModuleMessage(string message, MessageType type, string position) public void AddModuleMessage(string message, MessageType type, string position)
{ {
ClearModuleMessage();
RenderModeBoundary.AddModuleMessage(message, type, position); RenderModeBoundary.AddModuleMessage(message, type, position);
} }

View File

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

View File

@ -150,14 +150,11 @@
<data name="Name.HelpText" xml:space="preserve"> <data name="Name.HelpText" xml:space="preserve">
<value>Enter the folder name</value> <value>Enter the folder name</value>
</data> </data>
<data name="Permissions.HelpText" xml:space="preserve">
<value>Select the permissions you want for the folder</value>
</data>
<data name="Parent.Text" xml:space="preserve"> <data name="Parent.Text" xml:space="preserve">
<value>Parent: </value> <value>Parent: </value>
</data> </data>
<data name="Permissions.Text" xml:space="preserve"> <data name="Permissions.Heading" xml:space="preserve">
<value>Permissions: </value> <value>Permissions</value>
</data> </data>
<data name="DeleteFolder.Header" xml:space="preserve"> <data name="DeleteFolder.Header" xml:space="preserve">
<value>Delete Folder</value> <value>Delete Folder</value>
@ -195,4 +192,10 @@
<data name="Folder Management" xml:space="preserve"> <data name="Folder Management" xml:space="preserve">
<value>Folder Management</value> <value>Folder Management</value>
</data> </data>
<data name="Message.Folder.Duplicate" xml:space="preserve">
<value>Folder Name Specified Already Exists In Parent</value>
</data>
<data name="Settings.Heading" xml:space="preserve">
<value>Settings</value>
</data>
</root> </root>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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="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>
<data name="Message.Reindex" xml:space="preserve">
<value>The search index will be rebuilt for this site. Please be patient during the reindexing process.</value>
</data>
<data name="Reindex.Text" xml:space="preserve">
<value>Reindex</value>
</data>
<data name="Reindex.Header" xml:space="preserve">
<value>Reindex</value>
</data>
<data name="Reindex.Message" xml:space="preserve">
<value>Are You Sure You Wish To Reindex Search Content?</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -117,16 +117,16 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="AllowFileManagement.HelpText" xml:space="preserve"> <data name="NoCriteria" xml:space="preserve">
<value>Specify If Editors Can Upload and Select Files</value> <value>You Must Provide Some Search Criteria</value>
</data> </data>
<data name="AllowFileManagement.Text" xml:space="preserve"> <data name="NoResult" xml:space="preserve">
<value>Allow File Management: </value> <value>No Content Matches The Criteria Provided</value>
</data> </data>
<data name="AllowRawHtml.HelpText" xml:space="preserve"> <data name="SearchLabel" xml:space="preserve">
<value>Specify If Editors Can Enter Raw HTML</value> <value>Search:</value>
</data> </data>
<data name="AllowRawHtml.Text" xml:space="preserve"> <data name="SearchPlaceholder" xml:space="preserve">
<value>Allow Raw HTML:</value> <value>Search</value>
</data> </data>
</root> </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> <value>UI Component Settings</value>
</data> </data>
<data name="Prerender.HelpText" xml:space="preserve"> <data name="Prerender.HelpText" xml:space="preserve">
<value>Specifies if interactive components should prerender their output</value> <value>Specifies if interactive components should prerender their output on the server</value>
</data> </data>
<data name="Prerender.Text" xml:space="preserve"> <data name="Prerender.Text" xml:space="preserve">
<value>Prerender? </value> <value>Prerender: </value>
</data> </data>
<data name="RenderMode.HelpText" xml:space="preserve"> <data name="RenderMode.HelpText" xml:space="preserve">
<value>The default render mode for the site</value> <value>The default render mode for the site</value>
@ -402,9 +402,6 @@
<data name="Retention.Text" xml:space="preserve"> <data name="Retention.Text" xml:space="preserve">
<value>Retention (Days):</value> <value>Retention (Days):</value>
</data> </data>
<data name="FileExtensions.Heading" xml:space="preserve">
<value>File Extensions</value>
</data>
<data name="ImageExtensions.HelpText" xml:space="preserve"> <data name="ImageExtensions.HelpText" xml:space="preserve">
<value>Enter a comma separated list of image file extensions</value> <value>Enter a comma separated list of image file extensions</value>
</data> </data>
@ -429,4 +426,13 @@
<data name="Runtime.Text" xml:space="preserve"> <data name="Runtime.Text" xml:space="preserve">
<value>Interactivity:</value> <value>Interactivity:</value>
</data> </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> </root>

View File

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

View File

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

View File

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

@ -213,6 +213,9 @@
<data name="Reset" xml:space="preserve"> <data name="Reset" xml:space="preserve">
<value>Reset</value> <value>Reset</value>
</data> </data>
<data name="Search Settings" xml:space="preserve">
<value>Search Settings</value>
</data>
<data name="Search" xml:space="preserve"> <data name="Search" xml:space="preserve">
<value>Search</value> <value>Search</value>
</data> </data>
@ -453,4 +456,22 @@
<data name="RenderModeStatic" xml:space="preserve"> <data name="RenderModeStatic" xml:space="preserve">
<value>Static</value> <value>Static</value>
</data> </data>
<data name="Disabled" xml:space="preserve">
<value>Disabled</value>
</data>
<data name="Enabled" xml:space="preserve">
<value>Enabled</value>
</data>
<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> </root>

View File

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

View File

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

View File

@ -117,16 +117,10 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="InsertImage" xml:space="preserve"> <data name="Search" xml:space="preserve">
<value>Insert Image</value> <value>Search</value>
</data> </data>
<data name="Close" xml:space="preserve"> <data name="SearchPlaceHolder" xml:space="preserve">
<value>Close</value> <value>Search</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>
</root> </root>

View File

@ -75,5 +75,10 @@ namespace Oqtane.Services
{ {
return await GetByteArrayAsync($"{Apiurl}/download/{fileId}"); return await GetByteArrayAsync($"{Apiurl}/download/{fileId}");
} }
public async Task UnzipFileAsync(int fileId)
{
await PutAsync($"{Apiurl}/unzip/{fileId}");
}
} }
} }

View File

@ -92,5 +92,13 @@ namespace Oqtane.Services
/// </param> /// </param>
/// <returns></returns> /// <returns></returns>
Task<List<File>> GetFilesAsync(int siteId, string folderPath); Task<List<File>> GetFilesAsync(int siteId, string folderPath);
/// <summary>
/// Unzips the contents of a zip file
/// </summary>
/// <param name="fileId">Reference to the <see cref="File"/></param>
/// </param>
/// <returns></returns>
Task UnzipFileAsync(int fileId);
} }
} }

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

@ -179,6 +179,17 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task UpdateSettingsAsync(Dictionary<string, string> settings, string entityName, int entityId); Task UpdateSettingsAsync(Dictionary<string, string> settings, string entityName, int entityId);
/// <summary>
/// Updates setting for a given entityName and Id
/// </summary>
/// <param name="entityName"></param>
/// <param name="entityId"></param>
/// <param name="settingName"></param>
/// <param name="settingValue"></param>
/// <param name="isPrivate"></param>
/// <returns></returns>
Task AddOrUpdateSettingAsync(string entityName, int entityId, string settingName, string settingValue, bool isPrivate);
/// <summary> /// <summary>
/// Returns a specific setting /// Returns a specific setting
/// </summary> /// </summary>

View File

@ -46,6 +46,14 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task DeleteSiteAsync(int siteId); 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] [PrivateApi]
[Obsolete("This method is deprecated.", false)] [Obsolete("This method is deprecated.", false)]
void SetAlias(Alias alias); void SetAlias(Alias alias);

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

@ -35,6 +35,7 @@ namespace Oqtane.Services
if (_factory != null) if (_factory != null)
{ {
var client = _factory.CreateClient("oqtane"); var client = _factory.CreateClient("oqtane");
client.BaseAddress = new Uri(_siteState.Alias.Protocol + _siteState.Alias.Name);
if (!client.DefaultRequestHeaders.Contains(Constants.AntiForgeryTokenHeaderName) && _siteState != null && !string.IsNullOrEmpty(_siteState.AntiForgeryToken)) if (!client.DefaultRequestHeaders.Contains(Constants.AntiForgeryTokenHeaderName) && _siteState != null && !string.IsNullOrEmpty(_siteState.AntiForgeryToken))
{ {
client.DefaultRequestHeaders.Add(Constants.AntiForgeryTokenHeaderName, _siteState.AntiForgeryToken); client.DefaultRequestHeaders.Add(Constants.AntiForgeryTokenHeaderName, _siteState.AntiForgeryToken);

View File

@ -12,7 +12,7 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")] [PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class SettingService : ServiceBase, ISettingService public class SettingService : ServiceBase, ISettingService
{ {
public SettingService(HttpClient http, SiteState siteState) : base(http, siteState) { } public SettingService(HttpClient http, SiteState siteState) : base(http, siteState) {}
private string Apiurl => CreateApiUrl("Setting"); private string Apiurl => CreateApiUrl("Setting");
@ -134,7 +134,7 @@ namespace Oqtane.Services
public async Task<Dictionary<string, string>> GetSettingsAsync(string entityName, int entityId) public async Task<Dictionary<string, string>> GetSettingsAsync(string entityName, int entityId)
{ {
var dictionary = new Dictionary<string, string>(); var dictionary = new Dictionary<string, string>();
var settings = await GetJsonAsync<List<Setting>>($"{Apiurl}?entityname={entityName}&entityid={entityId}"); var settings = await GetSettingsAsync(entityName, entityId, "");
if (settings != null) if (settings != null)
{ {
foreach (Setting setting in settings.OrderBy(item => item.SettingName).ToList()) foreach (Setting setting in settings.OrderBy(item => item.SettingName).ToList())
@ -147,7 +147,7 @@ namespace Oqtane.Services
public async Task UpdateSettingsAsync(Dictionary<string, string> settings, string entityName, int entityId) public async Task UpdateSettingsAsync(Dictionary<string, string> settings, string entityName, int entityId)
{ {
var settingsList = await GetJsonAsync<List<Setting>>($"{Apiurl}?entityname={entityName}&entityid={entityId}"); var settingsList = await GetSettingsAsync(entityName, entityId, "");
foreach (KeyValuePair<string, string> kvp in settings) foreach (KeyValuePair<string, string> kvp in settings)
{ {
@ -192,14 +192,14 @@ namespace Oqtane.Services
} }
} }
public async Task AddOrUpdateSettingAsync(string entityName, int entityId, string settingName, string settingValue, bool isPrivate)
{
await PutAsync($"{Apiurl}/{entityName}/{entityId}/{settingName}/{settingValue}/{isPrivate}");
}
public async Task DeleteSettingAsync(string entityName, int entityId, string settingName) public async Task DeleteSettingAsync(string entityName, int entityId, string settingName)
{ {
var settings = await GetJsonAsync<List<Setting>>($"{Apiurl}?entityname={entityName}&entityid={entityId}"); await DeleteAsync($"{Apiurl}/{entityName}/{entityId}/{settingName}");
var setting = settings.FirstOrDefault(item => item.SettingName == settingName);
if (setting != null)
{
await DeleteAsync($"{Apiurl}/{setting.SettingId}/{entityName}");
}
} }
public async Task<List<Setting>> GetSettingsAsync(string entityName, int entityId, string settingName) public async Task<List<Setting>> GetSettingsAsync(string entityName, int entityId, string settingName)

View File

@ -1,7 +1,6 @@
using Oqtane.Models; using Oqtane.Models;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Net.Http; using System.Net.Http;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using Oqtane.Shared; using Oqtane.Shared;
using System; using System;
@ -41,6 +40,11 @@ namespace Oqtane.Services
await DeleteAsync($"{Apiurl}/{siteId}"); 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)] [Obsolete("This method is deprecated.", false)]
public void SetAlias(Alias alias) public void SetAlias(Alias alias)
{ {

View File

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

View File

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

View File

@ -9,13 +9,19 @@
<div class="row flex-xl-nowrap gx-0"> <div class="row flex-xl-nowrap gx-0">
<div class="sidebar"> <div class="sidebar">
<nav class="navbar"> <nav class="navbar">
<Logo /><Menu Orientation="Vertical" /> <Logo />
<Menu Orientation="Vertical" />
</nav> </nav>
</div> </div>
<div class="main g-0"> <div class="main g-0">
<div class="top-row px-4"> <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>
<div class="container"> <div class="container">
<div class="row px-4"> <div class="row px-4">
@ -31,9 +37,13 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
// obtained from https://cdnjs.com/libraries // 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.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) @if (PageState.RenderMode == RenderModes.Interactive)
{ {
<ModuleActionsInteractive PageState="@PageState" ModuleState="@ModuleState" /> <ModuleActionsInteractive PageState="@_pageState" ModuleState="@ModuleState" />
} }
else else
{ {
<ModuleActionsInteractive PageState="@PageState" ModuleState="@ModuleState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" /> <ModuleActionsInteractive PageState="@_pageState" ModuleState="@ModuleState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, false)" />
}
}
@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.Security;
using Oqtane.Services; using Oqtane.Services;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI;
using System.Net; using System.Net;
using Microsoft.Extensions.Localization;
using Oqtane.UI;
// ReSharper disable UnassignedGetOnlyAutoProperty // ReSharper disable UnassignedGetOnlyAutoProperty
// ReSharper disable MemberCanBePrivate.Global // ReSharper disable MemberCanBePrivate.Global
@ -20,6 +21,7 @@ namespace Oqtane.Themes.Controls
[Inject] public NavigationManager NavigationManager { get; set; } [Inject] public NavigationManager NavigationManager { get; set; }
[Inject] public IPageModuleService PageModuleService { get; set; } [Inject] public IPageModuleService PageModuleService { get; set; }
[Inject] public IModuleService ModuleService { get; set; } [Inject] public IModuleService ModuleService { get; set; }
[Inject] public IStringLocalizer<ModuleActionsBase> Localizer { get; set; }
[Parameter] public PageState PageState { get; set; } [Parameter] public PageState PageState { get; set; }
[Parameter] public Module ModuleState { get; set; } [Parameter] public Module ModuleState { get; set; }
@ -37,30 +39,30 @@ namespace Oqtane.Themes.Controls
if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{ {
actionList.Add(new ActionViewModel { Icon = Icons.Cog, Name = "Manage Settings", Action = async (u, m) => await Settings(u, m) }); actionList.Add(new ActionViewModel { Icon = Icons.Cog, Name = Localizer["ManageSettings"], Action = async (u, m) => await Settings(u, m) });
if (UserSecurity.ContainsRole(ModuleState.PermissionList, PermissionNames.View, RoleNames.Everyone)) if (UserSecurity.ContainsRole(ModuleState.PermissionList, PermissionNames.View, RoleNames.Everyone))
{ {
actionList.Add(new ActionViewModel { Icon = Icons.CircleX, Name = "Unpublish Module", Action = async (s, m) => await Unpublish(s, m) }); actionList.Add(new ActionViewModel { Icon = Icons.CircleX, Name = Localizer["UnpublishModule"], Action = async (s, m) => await Unpublish(s, m) });
} }
else else
{ {
actionList.Add(new ActionViewModel { Icon = Icons.CircleCheck, Name = "Publish Module", Action = async (s, m) => await Publish(s, m) }); actionList.Add(new ActionViewModel { Icon = Icons.CircleCheck, Name = Localizer["PublishModule"], Action = async (s, m) => await Publish(s, m) });
} }
actionList.Add(new ActionViewModel { Icon = Icons.Trash, Name = "Delete Module", Action = async (u, m) => await DeleteModule(u, m) }); actionList.Add(new ActionViewModel { Icon = Icons.Trash, Name = Localizer["DeleteModule"], Action = async (u, m) => await DeleteModule(u, m) });
if (ModuleState.ModuleDefinition != null && ModuleState.ModuleDefinition.IsPortable) if (ModuleState.ModuleDefinition != null && ModuleState.ModuleDefinition.IsPortable)
{ {
actionList.Add(new ActionViewModel { Name = "" }); actionList.Add(new ActionViewModel { Name = "" });
actionList.Add(new ActionViewModel { Icon = Icons.CloudUpload, Name = "Import Content", Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Import") }); actionList.Add(new ActionViewModel { Icon = Icons.CloudUpload, Name = Localizer["ImportContent"], Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Import") });
actionList.Add(new ActionViewModel { Icon = Icons.CloudDownload, Name = "Export Content", Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Export") }); actionList.Add(new ActionViewModel { Icon = Icons.CloudDownload, Name = Localizer["ExportContent"], Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Export") });
} }
actionList.Add(new ActionViewModel { Name = "" }); actionList.Add(new ActionViewModel { Name = "" });
if (ModuleState.PaneModuleIndex > 0) if (ModuleState.PaneModuleIndex > 0)
{ {
actionList.Add(new ActionViewModel { Icon = Icons.DataTransferUpload, Name = "Move To Top", Action = async (s, m) => await MoveTop(s, m) }); actionList.Add(new ActionViewModel { Icon = Icons.DataTransferUpload, Name = Localizer["MoveToTop"], Action = async (s, m) => await MoveTop(s, m) });
} }
if (ModuleState.PaneModuleIndex > 0) if (ModuleState.PaneModuleIndex > 0)

View File

@ -6,7 +6,7 @@
@if (ShowLanguageSwitcher) @if (ShowLanguageSwitcher)
{ {
<LanguageSwitcher DropdownAlignment="@LanguageDropdownAlignment" /> <LanguageSwitcher ButtonClass="@ButtonClass" DropdownAlignment="@LanguageDropdownAlignment" />
} }
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))) @if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
@ -32,11 +32,11 @@
{ {
@if (PageState.RenderMode == RenderModes.Interactive) @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 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] [Parameter]
public string LanguageDropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right public string LanguageDropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right
private PageState _pageState;
private bool _canViewAdminDashboard = false; private bool _canViewAdminDashboard = false;
private bool _showEditMode = false; private bool _showEditMode = false;
@ -73,7 +74,7 @@
} }
else 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)) 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() private bool CanViewAdminDashboard()
@ -115,8 +134,7 @@
if (PageState.User != null) if (PageState.User != null)
{ {
// preserve edit mode for authenticated users // preserve edit mode for authenticated users
var userSettings = new Dictionary<string, string> { { "CP-editmode", (PageState.EditMode) ? PageState.Page.PageId.ToString() : "-1" } }; await SettingService.AddOrUpdateSettingAsync(EntityNames.User, PageState.User.UserId, "CP-editmode", (PageState.EditMode) ? PageState.Page.PageId.ToString() : "-1", false);
await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId);
} }
// preserve other querystring parameters // preserve other querystring parameters

View File

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

View File

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

View File

@ -59,7 +59,7 @@ namespace Oqtane.Themes.Controls
logouturl = Utilities.TenantUrl(PageState.Alias, "/pages/logout/"); logouturl = Utilities.TenantUrl(PageState.Alias, "/pages/logout/");
// verify anonymous users can access current page // 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; 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={WebUtility.UrlEncode(_keywords)}");
NavigationManager.NavigateTo(url);
}
}
}

View File

@ -17,9 +17,13 @@ namespace Oqtane.Themes.OqtaneTheme
Resources = new List<Resource>() Resources = new List<Resource>()
{ {
// obtained from https://cdnjs.com/libraries // 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.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"> <nav class="navbar navbar-dark bg-primary fixed-top">
<Logo /><Menu Orientation="Horizontal" /> <Logo /><Menu Orientation="Horizontal" />
<div class="controls ms-auto"> <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> </div>
</nav> </nav>
<div class="content"> <div class="content">

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 @namespace Oqtane.UI
@inject SiteState SiteState @inject SiteState SiteState
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static) @if (_comment != null)
{ {
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" /> @((MarkupString)_comment)
} @if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static)
else {
{ <RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" /> }
else
{
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, _prerender)" />
}
} }
@code { @code {
// this component is on the static side of the render mode boundary
// it passes state as serializable parameters across the boundary
[CascadingParameter] [CascadingParameter]
protected PageState PageState { get; set; } protected PageState PageState { get; set; }
[CascadingParameter] [CascadingParameter]
private Module ModuleState { get; set; } private Module ModuleState { get; set; }
private bool _prerender;
private string _comment;
protected override void OnParametersSet()
{
_prerender = ModuleState.Prerender ?? PageState.Site.Prerender;
_comment = "<!-- rendermode: ";
if (PageState.RenderMode == RenderModes.Static && ModuleState.RenderMode == RenderModes.Static)
{
_comment += RenderModes.Static;
}
else
{
_comment += $"{RenderModes.Interactive}:{PageState.Runtime} - prerender: {_prerender}";
}
_comment += " -->";
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)] [Obsolete("AddModuleMessage is deprecated. Use AddModuleMessage in ModuleBase instead.", false)]
public void AddModuleMessage(string message, MessageType type) public void AddModuleMessage(string message, MessageType type)

View File

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

View File

@ -43,7 +43,7 @@ else
DynamicComponent = builder => 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 // set renderid - this allows the framework to determine which components should be rendered when PageState changes
if (module.RenderId != PageState.RenderId) 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) @if (ModuleType != null)
{ {
@((MarkupString)$"<!-- rendermode: {ModuleState.RenderMode} -->") @if (!string.IsNullOrEmpty(_messageContent) && _messagePosition == "top")
<ModuleMessage @ref="moduleMessageTop" Message="@_messageContent" Type="@_messageType" /> {
<ModuleMessage Message="@_messageContent" Type="@_messageType" Parent="@this" />
}
@DynamicComponent @DynamicComponent
@if (_progressIndicator) @if (_progressIndicator)
{ {
<div class="app-progress-indicator"></div> <div class="app-progress-indicator"></div>
} }
<ModuleMessage @ref="moduleMessageBottom" Message="@_messageContent" Type="@_messageType" /> @if (!string.IsNullOrEmpty(_messageContent) && _messagePosition == "bottom")
{
<ModuleMessage Message="@_messageContent" Type="@_messageType" Parent="@this" />
}
} }
} }
else else
@ -42,8 +47,6 @@
private string _messagePosition; private string _messagePosition;
private bool _progressIndicator = false; private bool _progressIndicator = false;
private string _error; private string _error;
private ModuleMessage moduleMessageTop;
private ModuleMessage moduleMessageBottom;
[Parameter] [Parameter]
public SiteState SiteState { get; set; } public SiteState SiteState { get; set; }
@ -104,12 +107,17 @@
public void AddModuleMessage(string message, MessageType type, string position) public void AddModuleMessage(string message, MessageType type, string position)
{ {
_messageContent = message; if(message != _messageContent
_messageType = type; || type != _messageType
_messagePosition = position; || position != _messagePosition)
_progressIndicator = false; {
_messageContent = message;
_messageType = type;
_messagePosition = position;
_progressIndicator = false;
Refresh(); StateHasChanged();
}
} }
public void ShowProgressIndicator() public void ShowProgressIndicator()
@ -124,25 +132,10 @@
StateHasChanged(); StateHasChanged();
} }
private void DismissMessage() public void DismissMessage()
{ {
_messageContent = ""; _messageContent = "";
} StateHasChanged();
private void Refresh()
{
var updateTop = string.IsNullOrEmpty(_messageContent) || _messagePosition == "top";
var updateBottom = string.IsNullOrEmpty(_messageContent) || _messagePosition == "bottom";
if (updateTop && moduleMessageTop != null)
{
moduleMessageTop.RefreshMessage(_messageContent, _messageType);
}
if (updateBottom && moduleMessageBottom != null)
{
moduleMessageBottom.RefreshMessage(_messageContent, _messageType);
}
} }
protected override async Task OnErrorAsync(Exception exception) protected override async Task OnErrorAsync(Exception exception)

View File

@ -1,6 +1,8 @@
@using System.Diagnostics.CodeAnalysis @using System.Diagnostics.CodeAnalysis
@using System.Net @using System.Net
@using Microsoft.AspNetCore.Http @using Microsoft.AspNetCore.Http
@using System.Globalization
@using System.Security.Claims
@namespace Oqtane.UI @namespace Oqtane.UI
@inject AuthenticationStateProvider AuthenticationStateProvider @inject AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState @inject SiteState SiteState
@ -95,6 +97,7 @@
{ {
Site site = null; Site site = null;
Page page = null; Page page = null;
List<Module> modules = null;
User user = null; User user = null;
var editmode = false; var editmode = false;
var refresh = false; var refresh = false;
@ -103,7 +106,7 @@
_error = ""; _error = "";
Route route = new Route(_absoluteUri, SiteState.Alias.Path); Route route = new Route(_absoluteUri, SiteState.Alias.Path);
int moduleid = int.Parse(route.ModuleId); int moduleid = int.Parse(route.ModuleId, CultureInfo.InvariantCulture);
var action = route.Action; var action = route.Action;
var querystring = Utilities.ParseQueryString(route.Query); var querystring = Utilities.ParseQueryString(route.Query);
@ -157,7 +160,8 @@
if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == "sitekey" && item.Value == SiteState.Alias.SiteKey)) if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == "sitekey" && item.Value == SiteState.Alias.SiteKey))
{ {
// get user // 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) if (user != null)
{ {
user.IsAuthenticated = authState.User.Identity.IsAuthenticated; user.IsAuthenticated = authState.User.Identity.IsAuthenticated;
@ -211,7 +215,7 @@
return; 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)); page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
} }
@ -252,31 +256,41 @@
} }
// check if user is authorized to view page // check if user is authorized to view page
if (page != null && UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList))) if (page != null && UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) && (Utilities.IsEffectiveAndNotExpired(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList)))
{ {
// edit mode // edit mode
if (user != null) if (user != null)
{ {
if (querystring.ContainsKey("editmode") && querystring["edit"] == "true") var editpageid = user.Settings.ContainsKey("CP-editmode") ? int.Parse(user.Settings["CP-editmode"], CultureInfo.InvariantCulture) : -1;
if ((querystring.ContainsKey("edit") && querystring["edit"] == "true") || page.PageId == editpageid)
{ {
editmode = true; editmode = true;
} }
else else
{ {
editmode = (page.PageId == ((user.Settings.ContainsKey("CP-editmode")) ? int.Parse(user.Settings["CP-editmode"]) : -1)); if (editpageid != -1)
if (!editmode)
{ {
var userSettings = new Dictionary<string, string> { { "CP-editmode", "-1" } }; // reset edit mode page
await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId); await SettingService.AddOrUpdateSettingAsync(EntityNames.User, user.UserId, "CP-editmode", "-1", false);
} }
} }
} }
// 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
{
modules = PageState.Modules;
}
// load additional metadata for current page // load additional metadata for current page
page = ProcessPage(page, site, user, SiteState.Alias); page = ProcessPage(page, site, user, SiteState.Alias);
// load additional metadata for modules // load additional metadata for modules
(page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias); (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) // populate page state (which acts as a client-side cache for subsequent requests)
_pagestate = new PageState _pagestate = new PageState
@ -284,6 +298,7 @@
Alias = SiteState.Alias, Alias = SiteState.Alias,
Site = site, Site = site,
Page = page, Page = page,
Modules = modules,
User = user, User = user,
Uri = new Uri(_absoluteUri, UriKind.Absolute), Uri = new Uri(_absoluteUri, UriKind.Absolute),
Route = route, Route = route,
@ -476,6 +491,7 @@
// retrieve module component resources // retrieve module component resources
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
module.RenderMode = moduleobject.RenderMode; module.RenderMode = moduleobject.RenderMode;
module.Prerender = moduleobject.Prerender;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace); page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
if (action.ToLower() == "settings" && module.ModuleDefinition != null) if (action.ToLower() == "settings" && module.ModuleDefinition != null)
@ -549,7 +565,7 @@
{ {
foreach (var resource in resources) foreach (var resource in resources)
{ {
if (resource.Level != ResourceLevel.Site) if (resource.ResourceType == ResourceType.Stylesheet || resource.Level != ResourceLevel.Site)
{ {
if (resource.Url.StartsWith("~")) if (resource.Url.StartsWith("~"))
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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