@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
Oqtane.Client/Modules/Admin/Files/ModuleInfo.cs
Normal file
19
Oqtane.Client/Modules/Admin/Files/ModuleInfo.cs
Normal 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"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -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">
|
||||||
@ -44,6 +42,9 @@ else
|
|||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<ActionLink Action="Log" Class="btn btn-secondary" Text="View All Logs" ResourceKey="ViewLogs" />
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@ -53,13 +54,18 @@ else
|
|||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
var status = string.Empty;
|
var status = string.Empty;
|
||||||
@ -146,7 +152,7 @@ else
|
|||||||
|
|
||||||
private async Task Refresh()
|
private async Task Refresh()
|
||||||
{
|
{
|
||||||
_jobs = await JobService.GetJobsAsync();
|
await GetJobs();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,10 @@ else
|
|||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Refresh()
|
||||||
|
{
|
||||||
|
await GetJobLogs();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>()
|
||||||
{
|
{
|
||||||
|
@ -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>
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
@ -307,13 +308,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get distinct pages where module exists
|
// get distinct pages where module exists
|
||||||
var distinctPageIds = PageState.Modules
|
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||||
|
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();
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -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();
|
||||||
|
@ -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)
|
||||||
|
{
|
||||||
|
foreach (Page p in _pages)
|
||||||
{
|
{
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
|
||||||
{
|
{
|
||||||
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -154,10 +157,12 @@
|
|||||||
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"]);
|
SetModuleTitle(Localizer["ModuleSettings.Title"]);
|
||||||
|
|
||||||
_module = ModuleState.ModuleDefinition.Name;
|
_module = ModuleState.ModuleDefinition.Name;
|
||||||
_title = ModuleState.Title;
|
_title = ModuleState.Title;
|
||||||
_moduleSettingsTitle = Localizer["ModuleSettings.Heading"];
|
_moduleSettingsTitle = Localizer["ModuleSettings.Heading"];
|
||||||
@ -173,7 +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)
|
||||||
{
|
{
|
||||||
|
@ -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"><@Localizer["SiteRoot"]></option>
|
<option value="-1"><@Localizer["SiteRoot"]></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))
|
||||||
{
|
{
|
||||||
@ -213,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";
|
||||||
@ -243,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"]);
|
||||||
@ -263,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))
|
||||||
{
|
{
|
||||||
@ -293,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))
|
||||||
{
|
{
|
||||||
@ -371,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);
|
||||||
@ -382,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);
|
||||||
@ -402,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 ">>":
|
||||||
@ -449,7 +451,7 @@
|
|||||||
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(page.Path, true); // redirect to page added and reload
|
NavigationManager.NavigateTo(NavigateUrl(page.Path), true); // redirect to page added and reload
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -31,7 +31,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"><@Localizer["SiteRoot"]></option>
|
<option value="-1"><@Localizer["SiteRoot"]></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)
|
||||||
{
|
{
|
||||||
@ -302,6 +302,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;
|
||||||
@ -345,6 +346,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();
|
||||||
@ -360,10 +362,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, CultureInfo.InvariantCulture))))
|
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))
|
||||||
{
|
{
|
||||||
@ -415,7 +417,7 @@
|
|||||||
_permissions = _page.PermissionList;
|
_permissions = _page.PermissionList;
|
||||||
|
|
||||||
// page modules
|
// page modules
|
||||||
_pageModules = PageState.Modules.Where(m => m.PageId == _page.PageId).ToList();
|
_pageModules = PageState.Modules;
|
||||||
|
|
||||||
// audit
|
// audit
|
||||||
_createdby = _page.CreatedBy;
|
_createdby = _page.CreatedBy;
|
||||||
@ -447,7 +449,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 (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
{
|
{
|
||||||
@ -549,7 +551,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);
|
||||||
@ -560,7 +562,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);
|
||||||
@ -582,11 +583,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 ">>":
|
||||||
|
@ -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;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@ -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
|
||||||
|
102
Oqtane.Client/Modules/Admin/Search/Index.razor
Normal file
102
Oqtane.Client/Modules/Admin/Search/Index.razor
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Search
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="searchprovider" HelpText="Specify the search provider for this site" ResourceKey="SearchProvider">Search Provider: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="searchprovider" class="form-control" @bind="@_searchProvider" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="enabled" HelpText="Specify if search indexing is enabled" ResourceKey="Enabled">Indexing Enabled? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="enabled" class="form-select" @bind="@_enabled">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="lastindexedon" HelpText="The date/time which the site was last indexed on" ResourceKey="LastIndexedOn">Last Indexed: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="lastindexedon" class="form-control" @bind="@_lastIndexedOn" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="ignorepages" HelpText="Comma delimited list of pages which should be ignored (based on their path)" ResourceKey="IgnorePages">Ignore Pages: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="ignorepages" class="form-control" @bind="@_ignorePages" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="ignoreentities" HelpText="Comma delimited list of entities which should be ignored" ResourceKey="IgnoreEntities">Ignore Entities: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="ignoreentities" class="form-control" @bind="@_ignoreEntities" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="minimumwordlength" HelpText="Minimum length of a word to be indexed" ResourceKey="MinimumWordLength">Word Length: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="minimumwordlength" class="form-control" type="number" min="0" step="1" @bind="@_minimumWordLength" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="ignorewords" HelpText="Comma delimited list of words which should be ignored" ResourceKey="IgnoreWords">Ignore Words: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="ignorewords" class="form-control" @bind="@_ignoreWords" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
private string _searchProvider;
|
||||||
|
private string _enabled;
|
||||||
|
private string _lastIndexedOn;
|
||||||
|
private string _ignorePages;
|
||||||
|
private string _ignoreEntities;
|
||||||
|
private string _minimumWordLength;
|
||||||
|
private string _ignoreWords;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_searchProvider = SettingService.GetSetting(settings, "Search_SearchProvider", Constants.DefaultSearchProviderName);
|
||||||
|
_enabled = SettingService.GetSetting(settings, "Search_Enabled", "True");
|
||||||
|
_lastIndexedOn = SettingService.GetSetting(settings, "Search_LastIndexedOn", "");
|
||||||
|
_ignorePages = SettingService.GetSetting(settings, "Search_IgnorePages", "");
|
||||||
|
_ignoreEntities = SettingService.GetSetting(settings, "Search_IgnoreEntities", "");
|
||||||
|
_minimumWordLength = SettingService.GetSetting(settings, "Search_MininumWordLength", "3");
|
||||||
|
_ignoreWords = SettingService.GetSetting(settings, "Search_IgnoreWords", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_SearchProvider", _searchProvider);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_Enabled", _enabled, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_IgnorePages", _ignorePages, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_IgnoreEntities", _ignoreEntities, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_MininumWordLength", _minimumWordLength, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_IgnoreWords", _ignoreWords, true);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
AddModuleMessage(Localizer["Success.Save"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Search Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
141
Oqtane.Client/Modules/Admin/SearchResults/Index.razor
Normal file
141
Oqtane.Client/Modules/Admin/SearchResults/Index.razor
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
@using Oqtane.Services
|
||||||
|
@using System.Net
|
||||||
|
@namespace Oqtane.Modules.Admin.SearchResults
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ISearchResultsService SearchResultsService
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="search-result-container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<form method="post" @formname="SearchResultsForm" @onsubmit="Search" data-enhance>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text">@Localizer["SearchLabel"]</span>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<input type="text" name="keywords" class="form-control shadow-none" maxlength="50"
|
||||||
|
aria-label="Keywords"
|
||||||
|
placeholder="@Localizer["SearchPlaceholder"]"
|
||||||
|
@bind="@_keywords">
|
||||||
|
<button class="btn btn-primary" type="submit">@SharedLocalizer["Search"]</button>
|
||||||
|
<a class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Reset"]</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 mb-3">
|
||||||
|
@if (_loading)
|
||||||
|
{
|
||||||
|
<div class="app-progress-indicator"></div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (_searchResults != null && _searchResults.Results != null)
|
||||||
|
{
|
||||||
|
if (_searchResults.Results.Any())
|
||||||
|
{
|
||||||
|
<Pager Items="@_searchResults?.Results"
|
||||||
|
Format="Grid"
|
||||||
|
Columns="1"
|
||||||
|
Toolbar="Bottom"
|
||||||
|
Parameters="@($"q={_keywords}")">
|
||||||
|
<Row>
|
||||||
|
<div class="search-item mb-2">
|
||||||
|
<h4 class="mb-1"><a href="@context.Url">@context.Title</a></h4>
|
||||||
|
<p class="mb-0 text-muted">@((MarkupString)context.Snippet)</p>
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-info show mt-3" role="alert">
|
||||||
|
@Localizer["NoResult"]
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public override string RenderMode => RenderModes.Static;
|
||||||
|
|
||||||
|
private string _includeEntities;
|
||||||
|
private string _excludeEntities;
|
||||||
|
private string _fromDate;
|
||||||
|
private string _toDate;
|
||||||
|
private string _pageSize;
|
||||||
|
private string _sortField;
|
||||||
|
private string _sortOrder;
|
||||||
|
private string _bodyLength;
|
||||||
|
|
||||||
|
private string _keywords;
|
||||||
|
private SearchResults _searchResults;
|
||||||
|
private bool _loading;
|
||||||
|
|
||||||
|
[SupplyParameterFromForm(FormName = "SearchResultsForm")]
|
||||||
|
public string KeyWords { get => ""; set => _keywords = value; }
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_includeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_IncludeEntities", "");
|
||||||
|
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
|
||||||
|
_fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", DateTime.MinValue.ToString());
|
||||||
|
_toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", DateTime.MaxValue.ToString());
|
||||||
|
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", int.MaxValue.ToString());
|
||||||
|
_sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance");
|
||||||
|
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
|
||||||
|
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
|
||||||
|
|
||||||
|
if (_keywords == null && PageState.QueryString.ContainsKey("q"))
|
||||||
|
{
|
||||||
|
_keywords = WebUtility.UrlDecode(PageState.QueryString["q"]);
|
||||||
|
await PerformSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Search()
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, $"page=1&q={_keywords}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PerformSearch()
|
||||||
|
{
|
||||||
|
_loading = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_keywords))
|
||||||
|
{
|
||||||
|
var searchQuery = new SearchQuery
|
||||||
|
{
|
||||||
|
SiteId = PageState.Site.SiteId,
|
||||||
|
Alias = PageState.Alias,
|
||||||
|
Keywords = _keywords,
|
||||||
|
IncludeEntities = _includeEntities,
|
||||||
|
ExcludeEntities = _excludeEntities,
|
||||||
|
FromDate = (!string.IsNullOrEmpty(_fromDate)) ? DateTime.Parse(_fromDate) : DateTime.MinValue,
|
||||||
|
ToDate = (!string.IsNullOrEmpty(_toDate)) ? DateTime.Parse(_toDate) : DateTime.MaxValue,
|
||||||
|
PageSize = (!string.IsNullOrEmpty(_pageSize)) ? int.Parse(_pageSize) : int.MaxValue,
|
||||||
|
PageIndex = 0,
|
||||||
|
SortField = (!string.IsNullOrEmpty(_sortField)) ? (SearchSortField)Enum.Parse(typeof(SearchSortField), _sortField) : SearchSortField.Relevance,
|
||||||
|
SortOrder = (!string.IsNullOrEmpty(_sortOrder)) ? (SearchSortOrder)Enum.Parse(typeof(SearchSortOrder), _sortOrder) : SearchSortOrder.Descending,
|
||||||
|
BodyLength = (!string.IsNullOrEmpty(_bodyLength)) ? int.Parse(_bodyLength) : 255
|
||||||
|
};
|
||||||
|
|
||||||
|
_searchResults = await SearchResultsService.GetSearchResultsAsync(searchQuery);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["NoCriteria"], MessageType.Info, "bottom");
|
||||||
|
}
|
||||||
|
|
||||||
|
_loading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
19
Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs
Normal file
19
Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs
Normal 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"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
123
Oqtane.Client/Modules/Admin/SearchResults/Settings.razor
Normal file
123
Oqtane.Client/Modules/Admin/SearchResults/Settings.razor
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.SearchResults
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@implements Oqtane.Interfaces.ISettingsControl
|
||||||
|
@inject IStringLocalizer<Settings> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="includeentities" ResourceKey="IncludeEntities" ResourceType="@resourceType" HelpText="Comma delimited list of entities to include in the search results. By default all entities will be included.">Include Entities: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="includeentities" type="text" class="form-control" @bind="@_includeEntities" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="excludeentities" ResourceKey="ExcludeEntities" ResourceType="@resourceType" HelpText="Comma delimited list of entities to exclude from search results. By default no entities will be excluded.">Exclude Entities: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="excludeentities" class="form-control" @bind="@_excludeEntities" required></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="daterange" ResourceKey="DateRange" ResourceType="@resourceType" HelpText="Enter the date range for search results. The default includes all content.">Date Range: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="date" class="form-control" @bind="@_fromDate" />
|
||||||
|
<span class="input-group-text">@Localizer["To"]</span>
|
||||||
|
<input type="date" class="form-control" @bind="@_toDate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="pagesize" ResourceKey="PageSize" ResourceType="@resourceType" HelpText="The maximum number of search results to retrieve. The default is unlimited.">Page Size: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="pagesize" type="text" class="form-control" @bind="@_pageSize" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="sortfield" ResourceKey="SortField" ResourceType="@resourceType" HelpText="Specify the default sort field">Sort By: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="softfield" class="form-select" @bind="@_sortField">
|
||||||
|
<option value="Relevance">@Localizer["Relevance"]</option>
|
||||||
|
<option value="Title">@Localizer["Title"]</option>
|
||||||
|
<option value="LastModified">@Localizer["LastModified"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="sortorder" ResourceKey="SortOrder" ResourceType="@resourceType" HelpText="Specify the default sort order">Sort Order: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="softorder" class="form-select" @bind="@_sortOrder">
|
||||||
|
<option value="Ascending">@Localizer["Ascending"]</option>
|
||||||
|
<option value="Descending">@Localizer["Descending"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="bodylength" ResourceKey="BodyLength" ResourceType="@resourceType" HelpText="The number of characters displayed for each search result summary. The default is 255 characters.">Body Size: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="bodylength" type="text" class="form-control" @bind="@_bodyLength" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string resourceType = "Oqtane.Modules.Admin.SearchResults.Settings, Oqtane.Client"; // for localization
|
||||||
|
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
|
||||||
|
private string _includeEntities;
|
||||||
|
private string _excludeEntities;
|
||||||
|
private DateTime? _fromDate = null;
|
||||||
|
private DateTime? _toDate = null;
|
||||||
|
private string _pageSize;
|
||||||
|
private string _sortField;
|
||||||
|
private string _sortOrder;
|
||||||
|
private string _bodyLength;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_includeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_IncludeEntities", "");
|
||||||
|
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
|
||||||
|
var fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", "");
|
||||||
|
_fromDate = (string.IsNullOrEmpty(fromDate)) ? null : DateTime.Parse(fromDate);
|
||||||
|
var toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", "");
|
||||||
|
_toDate = (string.IsNullOrEmpty(toDate)) ? null : DateTime.Parse(toDate);
|
||||||
|
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", "");
|
||||||
|
_sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance");
|
||||||
|
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
|
||||||
|
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_IncludeEntities", _includeEntities);
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_ExcludeEntities", _excludeEntities);
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_From", _fromDate.ToString());
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_To", _toDate.ToString());
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_PageSize", _pageSize);
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_SortField", _sortField);
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_SortOrder", _sortOrder);
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_BodyLength", _bodyLength);
|
||||||
|
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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="-"><@SharedLocalizer["Not Specified"]></option>
|
<option value="-"><@SharedLocalizer["Not Specified"]></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>
|
||||||
@ -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();
|
||||||
@ -688,9 +727,10 @@
|
|||||||
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);
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -162,7 +162,6 @@ else
|
|||||||
{
|
{
|
||||||
Text = Action;
|
Text = Action;
|
||||||
}
|
}
|
||||||
_openText = Text;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Class))
|
if (string.IsNullOrEmpty(Class))
|
||||||
{
|
{
|
||||||
@ -181,7 +180,10 @@ else
|
|||||||
_openText = string.Empty;
|
_openText = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@ -193,6 +195,7 @@ else
|
|||||||
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();
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -37,8 +37,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void OnChange(ChangeEventArgs e)
|
protected void OnChange(ChangeEventArgs e)
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(e.Value.ToString()))
|
|
||||||
{
|
{
|
||||||
Value = e.Value.ToString();
|
Value = e.Value.ToString();
|
||||||
if (ValueChanged.HasDelegate)
|
if (ValueChanged.HasDelegate)
|
||||||
@ -47,4 +45,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
@ -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>
|
||||||
@ -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>
|
||||||
@ -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
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
578
Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor
Normal file
578
Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor
Normal 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)" ")
|
||||||
|
<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)" ")
|
||||||
|
<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,128 +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)" ")
|
|
||||||
<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)" ")
|
|
||||||
<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;
|
|
||||||
private int _editorIndex;
|
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Content { get; set; }
|
public string Content { get; set; }
|
||||||
@ -134,203 +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 = "";
|
|
||||||
|
|
||||||
if (Content != _originalrawhtml)
|
|
||||||
{
|
{
|
||||||
_contentchanged = true; // identifies when Content parameter has changed
|
CreateTextEditor(builder);
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!AllowRichText)
|
|
||||||
{
|
|
||||||
_activetab = "Raw";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
|
if(_textEditor != null)
|
||||||
|
{
|
||||||
|
_textEditor.Initialize(Content);
|
||||||
|
}
|
||||||
|
|
||||||
await base.OnAfterRenderAsync(firstRender);
|
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);
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
_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)
|
}
|
||||||
|
|
||||||
|
private void CreateTextEditor(RenderTreeBuilder builder)
|
||||||
{
|
{
|
||||||
return _rawhtml;
|
if(!string.IsNullOrEmpty(_textEditorType))
|
||||||
|
{
|
||||||
|
var editorType = Type.GetType(_textEditorType);
|
||||||
|
if (editorType != null)
|
||||||
|
{
|
||||||
|
builder.OpenComponent(0, editorType);
|
||||||
|
|
||||||
|
var attributes = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "Placeholder", Placeholder },
|
||||||
|
{ "ReadOnly", ReadOnly }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (AdditionalAttributes != null)
|
||||||
|
{
|
||||||
|
foreach(var key in AdditionalAttributes.Keys)
|
||||||
|
{
|
||||||
|
if(!attributes.ContainsKey(key))
|
||||||
|
{
|
||||||
|
attributes.Add(key, AdditionalAttributes[key]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var richhtml = "";
|
attributes[key] = AdditionalAttributes[key];
|
||||||
|
|
||||||
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()
|
var index = 1;
|
||||||
|
foreach(var name in attributes.Keys)
|
||||||
{
|
{
|
||||||
_message = string.Empty;
|
if (editorType.GetProperty(name) != null)
|
||||||
if (_richfilemanager)
|
|
||||||
{
|
{
|
||||||
var file = _fileManager.GetFile();
|
builder.AddAttribute(index++, name, attributes[name]);
|
||||||
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()
|
builder.AddComponentReferenceCapture(index, (c) =>
|
||||||
{
|
{
|
||||||
_message = string.Empty;
|
_textEditor = (ITextEditor)c;
|
||||||
if (_rawfilemanager)
|
});
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetTextEditorType()
|
||||||
{
|
{
|
||||||
var file = _fileManager.GetFile();
|
const string EditorSettingName = "TextEditor";
|
||||||
if (file != null)
|
|
||||||
|
if(!string.IsNullOrEmpty(Provider))
|
||||||
{
|
{
|
||||||
var interop = new Interop(JSRuntime);
|
var provider = ServiceProvider.GetServices<ITextEditor>().FirstOrDefault(i => i.Name.Equals(Provider, StringComparison.OrdinalIgnoreCase));
|
||||||
int pos = await interop.GetCaretPosition(_rawhtmlid);
|
if(provider != null)
|
||||||
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"];
|
return Utilities.GetFullTypeName(provider.GetType().AssemblyQualifiedName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
return SettingService.GetSetting(PageState.Site.Settings, EditorSettingName, Constants.DefaultTextEditor);
|
||||||
_rawfilemanager = true;
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
33
Oqtane.Client/Modules/Controls/TextAreaTextEditor.razor
Normal file
33
Oqtane.Client/Modules/Controls/TextAreaTextEditor.razor
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
@ -51,15 +51,7 @@
|
|||||||
|
|
||||||
public override string Title => "Edit Html/Text";
|
public override string Title => "Edit Html/Text";
|
||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
|
||||||
{
|
|
||||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" },
|
|
||||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" }
|
|
||||||
};
|
|
||||||
|
|
||||||
private RichTextEditor RichTextEditorHtml;
|
private RichTextEditor RichTextEditorHtml;
|
||||||
private bool _allowfilemanagement;
|
|
||||||
private bool _allowrawhtml;
|
|
||||||
private string _content = null;
|
private string _content = null;
|
||||||
private string _createdby;
|
private string _createdby;
|
||||||
private DateTime _createdon;
|
private DateTime _createdon;
|
||||||
@ -72,8 +64,6 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_allowfilemanagement = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true"));
|
|
||||||
_allowrawhtml = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true"));
|
|
||||||
await LoadContent();
|
await LoadContent();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -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" }
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.2</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>
|
||||||
@ -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.2</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>
|
||||||
<RootNamespace>Oqtane</RootNamespace>
|
<RootNamespace>Oqtane</RootNamespace>
|
||||||
@ -22,9 +22,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.7" />
|
||||||
<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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
@ -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>
|
168
Oqtane.Client/Resources/Modules/Admin/Search/Index.resx
Normal file
168
Oqtane.Client/Resources/Modules/Admin/Search/Index.resx
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Enabled.Text" xml:space="preserve">
|
||||||
|
<value>Enabled? </value>
|
||||||
|
</data>
|
||||||
|
<data name="Enabled.HelpText" xml:space="preserve">
|
||||||
|
<value>Specify if search indexing is enabled</value>
|
||||||
|
</data>
|
||||||
|
<data name="LastIndexedOn.Text" xml:space="preserve">
|
||||||
|
<value>Last Indexed: </value>
|
||||||
|
</data>
|
||||||
|
<data name="LastIndexedOn.HelpText" xml:space="preserve">
|
||||||
|
<value>The date/time which the site was last indexed on</value>
|
||||||
|
</data>
|
||||||
|
<data name="IgnorePages.Text" xml:space="preserve">
|
||||||
|
<value>Ignore Pages: </value>
|
||||||
|
</data>
|
||||||
|
<data name="IgnorePages.HelpText" xml:space="preserve">
|
||||||
|
<value>Comma delimited list of pages which should be ignored (based on page path)</value>
|
||||||
|
</data>
|
||||||
|
<data name="IgnoreEntities.Text" xml:space="preserve">
|
||||||
|
<value>Ignore Entities: </value>
|
||||||
|
</data>
|
||||||
|
<data name="IgnoreEntities.HelpText" xml:space="preserve">
|
||||||
|
<value>Comma delimited list of entities which should be ignored</value>
|
||||||
|
</data>
|
||||||
|
<data name="MinimumWordLength.Text" xml:space="preserve">
|
||||||
|
<value>Word Length: </value>
|
||||||
|
</data>
|
||||||
|
<data name="MinimumWordLength.HelpText" xml:space="preserve">
|
||||||
|
<value>Minimum length of a word to be indexed</value>
|
||||||
|
</data>
|
||||||
|
<data name="IgnoreWords.Text" xml:space="preserve">
|
||||||
|
<value>Ignore Words: </value>
|
||||||
|
</data>
|
||||||
|
<data name="IgnoreWords.HelpText" xml:space="preserve">
|
||||||
|
<value>Comma delimited list of words which should be ignored</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Save" xml:space="preserve">
|
||||||
|
<value>Search Settings Saved Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Error.Save" xml:space="preserve">
|
||||||
|
<value>Error Saving Search Settings</value>
|
||||||
|
</data>
|
||||||
|
<data name="SearchProvider.HelpText" xml:space="preserve">
|
||||||
|
<value>Specify the search provider for this site</value>
|
||||||
|
</data>
|
||||||
|
<data name="SearchProvider.Text" xml:space="preserve">
|
||||||
|
<value>Search Provider:</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
@ -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>
|
@ -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>
|
@ -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>
|
174
Oqtane.Client/Resources/Modules/Controls/QuillJSTextEditor.resx
Normal file
174
Oqtane.Client/Resources/Modules/Controls/QuillJSTextEditor.resx
Normal 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>
|
@ -459,4 +459,16 @@
|
|||||||
<data name="Enabled" xml:space="preserve">
|
<data name="Enabled" xml:space="preserve">
|
||||||
<value>Enabled</value>
|
<value>Enabled</value>
|
||||||
</data>
|
</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>
|
@ -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>
|
13
Oqtane.Client/Services/Interfaces/ISearchResultsService.cs
Normal file
13
Oqtane.Client/Services/Interfaces/ISearchResultsService.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
23
Oqtane.Client/Services/SearchResultsService.cs
Normal file
23
Oqtane.Client/Services/SearchResultsService.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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)
|
||||||
|
@ -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 },
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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, false)" />
|
<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
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,10 +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 static System.Runtime.InteropServices.JavaScript.JSType;
|
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
|
using Oqtane.UI;
|
||||||
|
|
||||||
// ReSharper disable UnassignedGetOnlyAutoProperty
|
// ReSharper disable UnassignedGetOnlyAutoProperty
|
||||||
// ReSharper disable MemberCanBePrivate.Global
|
// ReSharper disable MemberCanBePrivate.Global
|
||||||
|
@ -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, false)" />
|
<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()
|
||||||
|
@ -28,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">
|
||||||
@ -248,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;
|
||||||
@ -278,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(',', StringSplitOptions.RemoveEmptyEntries)).Distinct().Where(item => item != "Headless").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;
|
||||||
@ -339,20 +314,24 @@
|
|||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ModuleTypeChanged(ChangeEventArgs e)
|
private async Task ModuleTypeChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_moduleType = (string)e.Value;
|
_moduleType = (string)e.Value;
|
||||||
|
if (_moduleType != "new")
|
||||||
|
{
|
||||||
|
_pages = await PageService.GetPagesAsync(PageState.Page.SiteId);
|
||||||
|
}
|
||||||
_pageId = "-";
|
_pageId = "-";
|
||||||
_moduleId = "-";
|
_moduleId = "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PageChanged(ChangeEventArgs e)
|
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))
|
(_moduleType == "add" || module.ModuleDefinition.IsPortable))
|
||||||
.ToList();
|
.ToList();
|
||||||
@ -371,7 +350,7 @@
|
|||||||
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;
|
||||||
@ -384,7 +363,7 @@
|
|||||||
{
|
{
|
||||||
var module = await ModuleService.GetModuleAsync(int.Parse(_moduleId));
|
var module = await ModuleService.GetModuleAsync(int.Parse(_moduleId));
|
||||||
module.ModuleId = 0;
|
module.ModuleId = 0;
|
||||||
module.SiteId = PageState.Site.SiteId;
|
module.SiteId = PageState.Page.SiteId;
|
||||||
module.PageId = PageState.Page.PageId;
|
module.PageId = PageState.Page.PageId;
|
||||||
module.AllPages = false;
|
module.AllPages = false;
|
||||||
module.PermissionList = GenerateDefaultPermissions(module.SiteId);
|
module.PermissionList = GenerateDefaultPermissions(module.SiteId);
|
||||||
@ -482,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -517,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":
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
61
Oqtane.Client/Themes/Controls/Theme/Search.razor
Normal file
61
Oqtane.Client/Themes/Controls/Theme/Search.razor
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
@namespace Oqtane.Themes.Controls
|
||||||
|
@using System.Net
|
||||||
|
@using Microsoft.AspNetCore.Http
|
||||||
|
@inherits ThemeControlBase
|
||||||
|
@inject IStringLocalizer<Search> Localizer
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
@if (_searchResultsPage != null)
|
||||||
|
{
|
||||||
|
<span class="app-search @CssClass">
|
||||||
|
<form method="post" class="app-form-inline" @formname="@($"SearchForm")" @onsubmit="@PerformSearch" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<input type="text" name="keywords" maxlength="50"
|
||||||
|
class="form-control d-inline-block pe-5 shadow-none"
|
||||||
|
@bind="_keywords"
|
||||||
|
placeholder="@Localizer["SearchPlaceHolder"]"
|
||||||
|
aria-label="Search" />
|
||||||
|
<button type="submit" class="btn btn-search">
|
||||||
|
<span class="oi oi-magnifying-glass align-middle"></span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private Page _searchResultsPage;
|
||||||
|
private string _keywords = "";
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string CssClass { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string SearchResultPagePath { get; set; } = "search";
|
||||||
|
|
||||||
|
[CascadingParameter]
|
||||||
|
HttpContext HttpContext { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromForm(FormName = "SearchForm")]
|
||||||
|
public string KeyWords { get => ""; set => _keywords = value; }
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if(!string.IsNullOrEmpty(SearchResultPagePath))
|
||||||
|
{
|
||||||
|
_searchResultsPage = PageState.Pages.FirstOrDefault(i => i.Path == SearchResultPagePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PerformSearch()
|
||||||
|
{
|
||||||
|
if (_searchResultsPage != null)
|
||||||
|
{
|
||||||
|
var url = NavigateUrl(_searchResultsPage.Path, $"q={_keywords}");
|
||||||
|
NavigationManager.NavigateTo(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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 },
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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">
|
||||||
|
13
Oqtane.Client/UI/ModuleInstance.placeholder.cs
Normal file
13
Oqtane.Client/UI/ModuleInstance.placeholder.cs
Normal 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;
|
@ -15,9 +15,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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; }
|
||||||
|
|
||||||
@ -30,6 +27,7 @@
|
|||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
_prerender = ModuleState.Prerender ?? PageState.Site.Prerender;
|
_prerender = ModuleState.Prerender ?? PageState.Site.Prerender;
|
||||||
|
|
||||||
_comment = "<!-- rendermode: ";
|
_comment = "<!-- rendermode: ";
|
||||||
if (PageState.RenderMode == RenderModes.Static && ModuleState.RenderMode == RenderModes.Static)
|
if (PageState.RenderMode == RenderModes.Static && ModuleState.RenderMode == RenderModes.Static)
|
||||||
{
|
{
|
||||||
@ -40,6 +38,13 @@
|
|||||||
_comment += $"{RenderModes.Interactive}:{PageState.Runtime} - prerender: {_prerender}";
|
_comment += $"{RenderModes.Interactive}:{PageState.Runtime} - prerender: {_prerender}";
|
||||||
}
|
}
|
||||||
_comment += " -->";
|
_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>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
13
Oqtane.Client/UI/RenderModeBoundary.placeholder.cs
Normal file
13
Oqtane.Client/UI/RenderModeBoundary.placeholder.cs
Normal 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;
|
@ -2,6 +2,7 @@
|
|||||||
@using System.Net
|
@using System.Net
|
||||||
@using Microsoft.AspNetCore.Http
|
@using Microsoft.AspNetCore.Http
|
||||||
@using System.Globalization
|
@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
|
||||||
@ -96,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;
|
||||||
@ -158,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;
|
||||||
@ -212,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));
|
||||||
}
|
}
|
||||||
@ -253,7 +256,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
@ -273,11 +276,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
@ -285,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,
|
||||||
|
@ -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("<");
|
// remove scripts
|
||||||
|
var index = content.IndexOf("<script");
|
||||||
while (index >= 0)
|
while (index >= 0)
|
||||||
{
|
{
|
||||||
var element = content.Substring(index, content.IndexOf(">", index) - index + 1);
|
content = content.Remove(index, content.IndexOf("</script>") + 9 - index);
|
||||||
if (!string.IsNullOrEmpty(element) && (PageState.RenderMode == RenderModes.Static || (!element.ToLower().StartsWith("<script") && !element.ToLower().StartsWith("</script"))))
|
index = content.IndexOf("<script");
|
||||||
{
|
|
||||||
if (!headcontent.Contains(element))
|
|
||||||
{
|
|
||||||
headcontent += element + "\n";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
index = content.IndexOf("<", index + 1);
|
headcontent += content + "\n";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return headcontent;
|
return headcontent;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Version>5.1.2</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.2</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>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Version>5.1.2</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.2</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>
|
||||||
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
<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.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.7" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Version>5.1.2</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.2</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,7 +33,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Version>5.1.2</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.2</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,7 +33,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -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.2</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>
|
||||||
@ -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.2</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>
|
||||||
<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.2</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>5.2.0</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,11 +65,11 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
|
||||||
<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.5" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.6" />
|
||||||
<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.40" />
|
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.40" />
|
||||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.40" />
|
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.40" />
|
||||||
|
@ -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.2</version>
|
<version>5.2.0</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.2</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane</tags>
|
<tags>oqtane</tags>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
@ -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.2</version>
|
<version>5.2.0</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.2/Oqtane.Framework.5.1.2.Upgrade.zip</projectUrl>
|
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v5.2.0/Oqtane.Framework.5.2.0.Upgrade.zip</projectUrl>
|
||||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane framework</tags>
|
<tags>oqtane framework</tags>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Server</id>
|
<id>Oqtane.Server</id>
|
||||||
<version>5.1.2</version>
|
<version>5.2.0</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.2</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane</tags>
|
<tags>oqtane</tags>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Shared</id>
|
<id>Oqtane.Shared</id>
|
||||||
<version>5.1.2</version>
|
<version>5.2.0</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.2</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane</tags>
|
<tags>oqtane</tags>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Updater</id>
|
<id>Oqtane.Updater</id>
|
||||||
<version>5.1.2</version>
|
<version>5.2.0</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.2</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane</tags>
|
<tags>oqtane</tags>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
@ -1 +1 @@
|
|||||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.2.Install.zip" -Force
|
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.0.Install.zip" -Force
|
||||||
|
@ -1 +1 @@
|
|||||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.2.Upgrade.zip" -Force
|
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.0.Upgrade.zip" -Force
|
||||||
|
@ -132,6 +132,7 @@
|
|||||||
_renderMode = site.RenderMode;
|
_renderMode = site.RenderMode;
|
||||||
_runtime = site.Runtime;
|
_runtime = site.Runtime;
|
||||||
_prerender = site.Prerender;
|
_prerender = site.Prerender;
|
||||||
|
var modules = new List<Module>();
|
||||||
|
|
||||||
Route route = new Route(url, alias.Path);
|
Route route = new Route(url, alias.Path);
|
||||||
var page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
|
var page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
|
||||||
@ -156,6 +157,10 @@
|
|||||||
{
|
{
|
||||||
HandlePageNotFound(site, page, route);
|
HandlePageNotFound(site, page, route);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId);
|
||||||
|
}
|
||||||
|
|
||||||
if (site.VisitorTracking)
|
if (site.VisitorTracking)
|
||||||
{
|
{
|
||||||
@ -169,7 +174,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// includes resources
|
// includes resources
|
||||||
var resources = GetPageResources(alias, site, page, int.Parse(route.ModuleId, CultureInfo.InvariantCulture), route.Action);
|
var resources = await GetPageResources(alias, site, page, modules, int.Parse(route.ModuleId, CultureInfo.InvariantCulture), route.Action);
|
||||||
ManageStyleSheets(resources);
|
ManageStyleSheets(resources);
|
||||||
ManageScripts(resources, alias);
|
ManageScripts(resources, alias);
|
||||||
|
|
||||||
@ -221,6 +226,7 @@
|
|||||||
Alias = alias,
|
Alias = alias,
|
||||||
Site = site,
|
Site = site,
|
||||||
Page = page,
|
Page = page,
|
||||||
|
Modules = modules,
|
||||||
User = null,
|
User = null,
|
||||||
Uri = new Uri(url, UriKind.Absolute),
|
Uri = new Uri(url, UriKind.Absolute),
|
||||||
Route = route,
|
Route = route,
|
||||||
@ -332,7 +338,7 @@
|
|||||||
{
|
{
|
||||||
var values = visitorCookieValue.Split('|');
|
var values = visitorCookieValue.Split('|');
|
||||||
int.TryParse(values[0], out _visitorId);
|
int.TryParse(values[0], out _visitorId);
|
||||||
DateTime.TryParse(values[1], out expiry);
|
DateTime.TryParseExact(values[1], "M/d/yyyy hh:mm:ss tt", CultureInfo.InvariantCulture, DateTimeStyles.None, out expiry);
|
||||||
}
|
}
|
||||||
else // legacy cookie format
|
else // legacy cookie format
|
||||||
{
|
{
|
||||||
@ -419,7 +425,7 @@
|
|||||||
|
|
||||||
Context.Response.Cookies.Append(
|
Context.Response.Cookies.Append(
|
||||||
visitorCookieName,
|
visitorCookieName,
|
||||||
$"{_visitorId}|{expiry}",
|
$"{_visitorId}|{expiry.ToString("M/d/yyyy hh:mm:ss tt", CultureInfo.InvariantCulture)}",
|
||||||
new CookieOptions()
|
new CookieOptions()
|
||||||
{
|
{
|
||||||
Expires = DateTimeOffset.UtcNow.AddYears(10),
|
Expires = DateTimeOffset.UtcNow.AddYears(10),
|
||||||
@ -574,8 +580,17 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// use custom element which can execute script on every page transition
|
// use custom element which can execute script on every page transition
|
||||||
|
@if (string.IsNullOrEmpty(resource.Integrity) && string.IsNullOrEmpty(resource.CrossOrigin))
|
||||||
|
{
|
||||||
return "<page-script src=\"" + resource.Url + "\"></page-script>";
|
return "<page-script src=\"" + resource.Url + "\"></page-script>";
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// use modulepreload for external resources
|
||||||
|
return "<link rel=\"modulepreload\" href=\"" + resource.Url + "\" integrity=\"" + resource.Integrity + "\" crossorigin=\"" + resource.CrossOrigin + "\" />\n" +
|
||||||
|
"<page-script src=\"" + resource.Url + "\"></page-script>";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -591,7 +606,7 @@
|
|||||||
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)));
|
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Resource> GetPageResources(Alias alias, Site site, Page page, int moduleid, string action)
|
private async Task<List<Resource>> GetPageResources(Alias alias, Site site, Page page, List<Module> modules, int moduleid, string action)
|
||||||
{
|
{
|
||||||
var resources = new List<Resource>();
|
var resources = new List<Resource>();
|
||||||
|
|
||||||
@ -617,7 +632,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Module module in site.Modules.Where(item => item.PageId == page.PageId || item.ModuleId == moduleid))
|
foreach (Module module in modules.Where(item => item.PageId == page.PageId || item.ModuleId == moduleid))
|
||||||
{
|
{
|
||||||
var typename = "";
|
var typename = "";
|
||||||
if (module.ModuleDefinition != null)
|
if (module.ModuleDefinition != null)
|
||||||
@ -683,15 +698,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (site.RenderMode == RenderModes.Interactive)
|
||||||
|
{
|
||||||
// site level resources for modules in site
|
// site level resources for modules in site
|
||||||
var modules = site.Modules.GroupBy(item => item.ModuleDefinition?.ModuleDefinitionName).Select(group => group.First()).ToList();
|
var sitemodules = await SiteService.GetModulesAsync(site.SiteId, -1);
|
||||||
foreach (var module in modules)
|
foreach (var module in sitemodules.GroupBy(item => item.ModuleDefinition?.ModuleDefinitionName).Select(group => group.First()).ToList())
|
||||||
{
|
{
|
||||||
if (module.ModuleDefinition?.Resources != null)
|
if (module.ModuleDefinition?.Resources != null)
|
||||||
{
|
{
|
||||||
resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode);
|
resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
|
16
Oqtane.Server/Components/_Placeholder.cs
Normal file
16
Oqtane.Server/Components/_Placeholder.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using Oqtane.Documentation;
|
||||||
|
|
||||||
|
namespace Oqtane.Components;
|
||||||
|
|
||||||
|
[PrivateApi]
|
||||||
|
public class _Placeholder;
|
@ -9,7 +9,6 @@ using Oqtane.Infrastructure;
|
|||||||
using Oqtane.Repository;
|
using Oqtane.Repository;
|
||||||
using Oqtane.Security;
|
using Oqtane.Security;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Policy;
|
|
||||||
|
|
||||||
namespace Oqtane.Controllers
|
namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
|
@ -194,7 +194,7 @@ namespace Oqtane.Controllers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (moduletype.GetInterface("IInstallable") != null)
|
if (moduletype.GetInterface(nameof(IInstallable)) != null)
|
||||||
{
|
{
|
||||||
_tenantManager.SetTenant(tenant.TenantId);
|
_tenantManager.SetTenant(tenant.TenantId);
|
||||||
var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
|
var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
|
||||||
|
42
Oqtane.Server/Controllers/SearchResultsController.cs
Normal file
42
Oqtane.Server/Controllers/SearchResultsController.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Oqtane.Enums;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Services;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Controllers
|
||||||
|
{
|
||||||
|
[Route(ControllerRoutes.ApiRoute)]
|
||||||
|
public class SearchResultsController : Controller
|
||||||
|
{
|
||||||
|
private readonly ISearchService _searchService;
|
||||||
|
private readonly ILogManager _logger;
|
||||||
|
private readonly Alias _alias;
|
||||||
|
|
||||||
|
public SearchResultsController(ISearchService searchService, ILogManager logger, ITenantManager tenantManager)
|
||||||
|
{
|
||||||
|
_searchService = searchService;
|
||||||
|
_logger = logger;
|
||||||
|
_alias = tenantManager.GetAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<SearchResults> Post([FromBody] SearchQuery searchQuery)
|
||||||
|
{
|
||||||
|
if (ModelState.IsValid && searchQuery.SiteId == _alias.SiteId)
|
||||||
|
{
|
||||||
|
return await _searchService.GetSearchResultsAsync(searchQuery);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Search Results Post Attempt {SearchQuery}", searchQuery);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -81,5 +81,12 @@ namespace Oqtane.Controllers
|
|||||||
{
|
{
|
||||||
await _siteService.DeleteSiteAsync(id);
|
await _siteService.DeleteSiteAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET api/<controller>/modules/5/6
|
||||||
|
[HttpGet("modules/{siteId}/{pageId}")]
|
||||||
|
public async Task<IEnumerable<Module>> GetModules(int siteId, int pageId)
|
||||||
|
{
|
||||||
|
return await _siteService.GetModulesAsync(siteId, pageId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,15 +72,13 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET api/<controller>/name/{name}/{email}?siteid=x
|
// GET api/<controller>/username/{username}?siteid=x
|
||||||
[HttpGet("name/{name}/{email}")]
|
[HttpGet("username/{username}")]
|
||||||
public User Get(string name, string email, string siteid)
|
public User Get(string username, string siteid)
|
||||||
{
|
{
|
||||||
if (int.TryParse(siteid, out int SiteId) && SiteId == _tenantManager.GetAlias().SiteId)
|
if (int.TryParse(siteid, out int SiteId) && SiteId == _tenantManager.GetAlias().SiteId)
|
||||||
{
|
{
|
||||||
name = (name == "-") ? "" : name;
|
User user = _userManager.GetUser(username, SiteId);
|
||||||
email = (email == "-") ? "" : email;
|
|
||||||
User user = _userManager.GetUser(name, email, SiteId);
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||||
@ -95,7 +93,36 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Get Attempt {Username} {Email} {SiteId}", name, email, siteid);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Get Attempt {Username} {SiteId}", username, siteid);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET api/<controller>/name/{username}/{email}?siteid=x
|
||||||
|
[HttpGet("search/{username}/{email}")]
|
||||||
|
public User Get(string username, string email, string siteid)
|
||||||
|
{
|
||||||
|
if (int.TryParse(siteid, out int SiteId) && SiteId == _tenantManager.GetAlias().SiteId)
|
||||||
|
{
|
||||||
|
username = (username == "-") ? "" : username;
|
||||||
|
email = (email == "-") ? "" : email;
|
||||||
|
User user = _userManager.GetUser(username, email, SiteId);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<Setting> settings = _settings.GetSettings(EntityNames.User, user.UserId).ToList();
|
||||||
|
user.Settings = settings.Where(item => !item.IsPrivate || _userPermissions.GetUser(User).UserId == user.UserId)
|
||||||
|
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
||||||
|
}
|
||||||
|
return Filter(user);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Get Attempt {Username} {Email} {SiteId}", username, email, siteid);
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -340,14 +367,11 @@ namespace Oqtane.Controllers
|
|||||||
if (user.IsAuthenticated)
|
if (user.IsAuthenticated)
|
||||||
{
|
{
|
||||||
user.Username = User.Identity.Name;
|
user.Username = User.Identity.Name;
|
||||||
if (User.HasClaim(item => item.Type == ClaimTypes.NameIdentifier))
|
user.UserId = User.UserId();
|
||||||
{
|
|
||||||
user.UserId = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value);
|
|
||||||
}
|
|
||||||
string roles = "";
|
string roles = "";
|
||||||
foreach (var claim in User.Claims.Where(item => item.Type == ClaimTypes.Role))
|
foreach (var roleName in User.Roles())
|
||||||
{
|
{
|
||||||
roles += claim.Value + ";";
|
roles += roleName + ";";
|
||||||
}
|
}
|
||||||
if (roles != "") roles = ";" + roles;
|
if (roles != "") roles = ";" + roles;
|
||||||
user.Roles = roles;
|
user.Roles = roles;
|
||||||
|
@ -33,14 +33,10 @@ namespace Oqtane.Extensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Roles(this ClaimsPrincipal claimsPrincipal)
|
public static string[] Roles(this ClaimsPrincipal claimsPrincipal)
|
||||||
{
|
{
|
||||||
var roles = "";
|
return claimsPrincipal.Claims.Where(item => item.Type == ClaimTypes.Role)
|
||||||
foreach (var claim in claimsPrincipal.Claims.Where(item => item.Type == ClaimTypes.Role))
|
.Select(item => item.Value).ToArray();
|
||||||
{
|
|
||||||
roles += ((roles == "") ? "" : ";") + claim.Value;
|
|
||||||
}
|
|
||||||
return roles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string SiteKey(this ClaimsPrincipal claimsPrincipal)
|
public static string SiteKey(this ClaimsPrincipal claimsPrincipal)
|
||||||
|
@ -7,6 +7,7 @@ namespace Oqtane.Extensions
|
|||||||
{
|
{
|
||||||
public static class HttpContextExtensions
|
public static class HttpContextExtensions
|
||||||
{
|
{
|
||||||
|
// this method should only be used in scenarios where HttpContent exists (ie. within Controllers)
|
||||||
public static Alias GetAlias(this HttpContext context)
|
public static Alias GetAlias(this HttpContext context)
|
||||||
{
|
{
|
||||||
if (context != null && context.Items.ContainsKey(Constants.HttpContextAliasKey))
|
if (context != null && context.Items.ContainsKey(Constants.HttpContextAliasKey))
|
||||||
@ -16,6 +17,7 @@ namespace Oqtane.Extensions
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this method should only be used in scenarios where HttpContent exists (ie. within Controllers)
|
||||||
public static Dictionary<string, string> GetSiteSettings(this HttpContext context)
|
public static Dictionary<string, string> GetSiteSettings(this HttpContext context)
|
||||||
{
|
{
|
||||||
if (context != null && context.Items.ContainsKey(Constants.HttpContextSiteSettingsKey))
|
if (context != null && context.Items.ContainsKey(Constants.HttpContextSiteSettingsKey))
|
||||||
|
@ -19,8 +19,10 @@ using Microsoft.Extensions.Logging;
|
|||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Infrastructure.Interfaces;
|
using Oqtane.Infrastructure.Interfaces;
|
||||||
|
using Oqtane.Interfaces;
|
||||||
using Oqtane.Managers;
|
using Oqtane.Managers;
|
||||||
using Oqtane.Modules;
|
using Oqtane.Modules;
|
||||||
|
using Oqtane.Providers;
|
||||||
using Oqtane.Repository;
|
using Oqtane.Repository;
|
||||||
using Oqtane.Security;
|
using Oqtane.Security;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
@ -97,6 +99,13 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
||||||
services.AddScoped<IVisitorService, VisitorService>();
|
services.AddScoped<IVisitorService, VisitorService>();
|
||||||
services.AddScoped<ISyncService, SyncService>();
|
services.AddScoped<ISyncService, SyncService>();
|
||||||
|
services.AddScoped<ISearchResultsService, SearchResultsService>();
|
||||||
|
services.AddScoped<ISearchService, SearchService>();
|
||||||
|
services.AddScoped<ISearchProvider, DatabaseSearchProvider>();
|
||||||
|
|
||||||
|
// providers
|
||||||
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
||||||
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.TextAreaTextEditor>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
@ -131,6 +140,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddTransient<ILanguageRepository, LanguageRepository>();
|
services.AddTransient<ILanguageRepository, LanguageRepository>();
|
||||||
services.AddTransient<IVisitorRepository, VisitorRepository>();
|
services.AddTransient<IVisitorRepository, VisitorRepository>();
|
||||||
services.AddTransient<IUrlMappingRepository, UrlMappingRepository>();
|
services.AddTransient<IUrlMappingRepository, UrlMappingRepository>();
|
||||||
|
services.AddTransient<ISearchContentRepository, SearchContentRepository>();
|
||||||
|
|
||||||
// managers
|
// managers
|
||||||
services.AddTransient<IDBContextDependencies, DBContextDependencies>();
|
services.AddTransient<IDBContextDependencies, DBContextDependencies>();
|
||||||
|
@ -452,7 +452,7 @@ namespace Oqtane.Infrastructure
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (moduleType.GetInterface("IInstallable") != null)
|
if (moduleType.GetInterface(nameof(IInstallable)) != null)
|
||||||
{
|
{
|
||||||
tenantManager.SetTenant(tenant.TenantId);
|
tenantManager.SetTenant(tenant.TenantId);
|
||||||
var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType) as IInstallable;
|
var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType) as IInstallable;
|
||||||
|
@ -19,12 +19,8 @@ namespace Oqtane.Infrastructure.EventSubscribers
|
|||||||
// when site entities change (ie. site, pages, modules, etc...) a site refresh event is raised and the site cache item needs to be refreshed
|
// when site entities change (ie. site, pages, modules, etc...) a site refresh event is raised and the site cache item needs to be refreshed
|
||||||
if (syncEvent.EntityName == EntityNames.Site && syncEvent.Action == SyncEventActions.Refresh)
|
if (syncEvent.EntityName == EntityNames.Site && syncEvent.Action == SyncEventActions.Refresh)
|
||||||
{
|
{
|
||||||
_cache.Remove($"site:{syncEvent.TenantId}:{syncEvent.EntityId}*", true);
|
_cache.Remove($"site:{syncEvent.TenantId}:{syncEvent.EntityId}");
|
||||||
}
|
_cache.Remove($"modules:{syncEvent.TenantId}:{syncEvent.EntityId}");
|
||||||
// when user is modified (ie. roles) a a site reload event is raised and the site cache item for the user needs to be refreshed
|
|
||||||
if (syncEvent.EntityName == EntityNames.User && syncEvent.Action == SyncEventActions.Reload)
|
|
||||||
{
|
|
||||||
_cache.Remove($"site:{syncEvent.TenantId}:{syncEvent.SiteId}:{syncEvent.EntityId}", true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// when a site entity is updated, the hosting model may have changed so the client assemblies cache items need to be refreshed
|
// when a site entity is updated, the hosting model may have changed so the client assemblies cache items need to be refreshed
|
||||||
|
@ -149,7 +149,7 @@ namespace Oqtane.Infrastructure
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// error
|
// error
|
||||||
log += ex.Message + "<br />";
|
log += $"NotificationId: {notification.NotificationId} - {ex.Message}<br />";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
259
Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs
Normal file
259
Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Oqtane.Extensions;
|
||||||
|
using Oqtane.Interfaces;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Repository;
|
||||||
|
using Oqtane.Services;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Infrastructure
|
||||||
|
{
|
||||||
|
public class SearchIndexJob : HostedServiceBase
|
||||||
|
{
|
||||||
|
private const string SearchLastIndexedOnSetting = "Search_LastIndexedOn";
|
||||||
|
private const string SearchEnabledSetting = "Search_Enabled";
|
||||||
|
private const string SearchIgnorePagesSetting = "Search_IgnorePages";
|
||||||
|
private const string SearchIgnoreEntitiesSetting = "Search_IgnoreEntities";
|
||||||
|
|
||||||
|
public SearchIndexJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
|
||||||
|
{
|
||||||
|
Name = "Search Index Job";
|
||||||
|
Frequency = "m"; // run every minute.
|
||||||
|
Interval = 1;
|
||||||
|
IsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<string> ExecuteJobAsync(IServiceProvider provider)
|
||||||
|
{
|
||||||
|
string log = "";
|
||||||
|
|
||||||
|
// get services
|
||||||
|
var siteRepository = provider.GetRequiredService<ISiteRepository>();
|
||||||
|
var settingRepository = provider.GetRequiredService<ISettingRepository>();
|
||||||
|
var tenantManager = provider.GetRequiredService<ITenantManager>();
|
||||||
|
var pageRepository = provider.GetRequiredService<IPageRepository>();
|
||||||
|
var pageModuleRepository = provider.GetRequiredService<IPageModuleRepository>();
|
||||||
|
var searchService = provider.GetRequiredService<ISearchService>();
|
||||||
|
|
||||||
|
var sites = siteRepository.GetSites().ToList();
|
||||||
|
foreach (var site in sites)
|
||||||
|
{
|
||||||
|
log += $"Indexing Site: {site.Name}<br />";
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
var siteSettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
||||||
|
|
||||||
|
if (!Convert.ToBoolean(siteSettings.GetValue(SearchEnabledSetting, "true")))
|
||||||
|
{
|
||||||
|
log += $"Indexing Disabled<br />";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tenantId = tenantManager.GetTenant().TenantId;
|
||||||
|
tenantManager.SetAlias(tenantId, site.SiteId);
|
||||||
|
|
||||||
|
var currentTime = DateTime.UtcNow;
|
||||||
|
var lastIndexedOn = Convert.ToDateTime(siteSettings.GetValue(SearchLastIndexedOnSetting, DateTime.MinValue.ToString()));
|
||||||
|
|
||||||
|
var ignorePages = siteSettings.GetValue(SearchIgnorePagesSetting, "").Split(',');
|
||||||
|
var ignoreEntities = siteSettings.GetValue(SearchIgnoreEntitiesSetting, "").Split(',');
|
||||||
|
|
||||||
|
var pages = pageRepository.GetPages(site.SiteId);
|
||||||
|
var pageModules = pageModuleRepository.GetPageModules(site.SiteId);
|
||||||
|
var searchContents = new List<SearchContent>();
|
||||||
|
|
||||||
|
// index pages
|
||||||
|
foreach (var page in pages)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(page.Path) && (Constants.InternalPagePaths.Contains(page.Path) || ignorePages.Contains(page.Path)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
bool removed = false;
|
||||||
|
|
||||||
|
if (page.ModifiedOn >= lastIndexedOn && !ignoreEntities.Contains(EntityNames.Page))
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
removed = page.IsDeleted || !Utilities.IsEffectiveAndNotExpired(page.EffectiveDate, page.ExpiryDate);
|
||||||
|
|
||||||
|
var searchContent = new SearchContent
|
||||||
|
{
|
||||||
|
SiteId = page.SiteId,
|
||||||
|
EntityName = EntityNames.Page,
|
||||||
|
EntityId = page.PageId.ToString(),
|
||||||
|
Title = !string.IsNullOrEmpty(page.Title) ? page.Title : page.Name,
|
||||||
|
Description = string.Empty,
|
||||||
|
Body = string.Empty,
|
||||||
|
Url = $"{(!string.IsNullOrEmpty(page.Path) && !page.Path.StartsWith("/") ? "/" : "")}{page.Path}",
|
||||||
|
Permissions = $"{EntityNames.Page}:{page.PageId}",
|
||||||
|
ContentModifiedBy = page.ModifiedBy,
|
||||||
|
ContentModifiedOn = page.ModifiedOn,
|
||||||
|
AdditionalContent = string.Empty,
|
||||||
|
CreatedOn = DateTime.UtcNow,
|
||||||
|
IsDeleted = removed,
|
||||||
|
TenantId = tenantId
|
||||||
|
};
|
||||||
|
searchContents.Add(searchContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// index modules
|
||||||
|
foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId))
|
||||||
|
{
|
||||||
|
if (pageModule.ModifiedOn >= lastIndexedOn && !changed)
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchable = false;
|
||||||
|
if (pageModule.Module.ModuleDefinition != null && pageModule.Module.ModuleDefinition.ServerManagerType != "")
|
||||||
|
{
|
||||||
|
Type type = Type.GetType(pageModule.Module.ModuleDefinition.ServerManagerType);
|
||||||
|
if (type?.GetInterface(nameof(ISearchable)) != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
searchable = true;
|
||||||
|
|
||||||
|
// determine if reindexing is necessary
|
||||||
|
var lastindexedon = (changed) ? DateTime.MinValue : lastIndexedOn;
|
||||||
|
|
||||||
|
// index module content
|
||||||
|
var serverManager = (ISearchable)ActivatorUtilities.CreateInstance(provider, type);
|
||||||
|
var searchcontents = await serverManager.GetSearchContentsAsync(pageModule, lastindexedon);
|
||||||
|
if (searchcontents != null)
|
||||||
|
{
|
||||||
|
foreach (var searchContent in searchcontents)
|
||||||
|
{
|
||||||
|
if (!ignoreEntities.Contains(searchContent.EntityName))
|
||||||
|
{
|
||||||
|
ValidateSearchContent(searchContent, pageModule, tenantId, removed);
|
||||||
|
searchContents.Add(searchContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
log += $"Error Indexing Module {pageModule.Module.ModuleDefinition.Name} - {ex.Message}<br />";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!searchable && changed && !ignoreEntities.Contains(EntityNames.Module))
|
||||||
|
{
|
||||||
|
// module does not implement ISearchable
|
||||||
|
var searchContent = new SearchContent();
|
||||||
|
ValidateSearchContent(searchContent, pageModule, tenantId, removed);
|
||||||
|
searchContents.Add(searchContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save search contents
|
||||||
|
log += await searchService.SaveSearchContentsAsync(searchContents, siteSettings);
|
||||||
|
log += $"Items Indexed: {searchContents.Count}<br />";
|
||||||
|
|
||||||
|
// update last indexed on
|
||||||
|
SaveSearchLastIndexedOn(settingRepository, site.SiteId, currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void ValidateSearchContent(SearchContent searchContent, PageModule pageModule, int tenantId, bool removed)
|
||||||
|
{
|
||||||
|
// set default values
|
||||||
|
searchContent.SiteId = pageModule.Module.SiteId;
|
||||||
|
searchContent.TenantId = tenantId;
|
||||||
|
searchContent.CreatedOn = DateTime.UtcNow;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(searchContent.EntityName))
|
||||||
|
{
|
||||||
|
searchContent.EntityName = EntityNames.Module;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(searchContent.EntityId))
|
||||||
|
{
|
||||||
|
searchContent.EntityId = pageModule.ModuleId.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(searchContent.Title))
|
||||||
|
{
|
||||||
|
searchContent.Title = string.Empty;
|
||||||
|
if (!string.IsNullOrEmpty(pageModule.Title))
|
||||||
|
{
|
||||||
|
searchContent.Title = pageModule.Title;
|
||||||
|
}
|
||||||
|
else if (pageModule.Page != null)
|
||||||
|
{
|
||||||
|
searchContent.Title = !string.IsNullOrEmpty(pageModule.Page.Title) ? pageModule.Page.Title : pageModule.Page.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchContent.Description == null) { searchContent.Description = string.Empty;}
|
||||||
|
if (searchContent.Body == null) { searchContent.Body = string.Empty; }
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(searchContent.Url))
|
||||||
|
{
|
||||||
|
searchContent.Url = string.Empty;
|
||||||
|
if (pageModule.Page != null)
|
||||||
|
{
|
||||||
|
searchContent.Url = $"{(!string.IsNullOrEmpty(pageModule.Page.Path) && !pageModule.Page.Path.StartsWith("/") ? "/" : "")}{pageModule.Page.Path}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(searchContent.Permissions))
|
||||||
|
{
|
||||||
|
searchContent.Permissions = $"{EntityNames.Module}:{pageModule.ModuleId},{EntityNames.Page}:{pageModule.PageId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(searchContent.ContentModifiedBy))
|
||||||
|
{
|
||||||
|
searchContent.ContentModifiedBy = pageModule.ModifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchContent.ContentModifiedOn == DateTime.MinValue)
|
||||||
|
{
|
||||||
|
searchContent.ContentModifiedOn = pageModule.ModifiedOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(searchContent.AdditionalContent))
|
||||||
|
{
|
||||||
|
searchContent.AdditionalContent = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removed || pageModule.IsDeleted || !Utilities.IsEffectiveAndNotExpired(pageModule.EffectiveDate, pageModule.ExpiryDate))
|
||||||
|
{
|
||||||
|
searchContent.IsDeleted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveSearchLastIndexedOn(ISettingRepository settingRepository, int siteId, DateTime lastIndexedOn)
|
||||||
|
{
|
||||||
|
var setting = settingRepository.GetSetting(EntityNames.Site, siteId, SearchLastIndexedOnSetting);
|
||||||
|
if (setting == null)
|
||||||
|
{
|
||||||
|
setting = new Setting
|
||||||
|
{
|
||||||
|
EntityName = EntityNames.Site,
|
||||||
|
EntityId = siteId,
|
||||||
|
SettingName = SearchLastIndexedOnSetting,
|
||||||
|
SettingValue = Convert.ToString(lastIndexedOn),
|
||||||
|
};
|
||||||
|
settingRepository.AddSetting(setting);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setting.SettingValue = Convert.ToString(lastIndexedOn);
|
||||||
|
settingRepository.UpdateSetting(setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -203,20 +203,22 @@ namespace Oqtane.Infrastructure
|
|||||||
}
|
}
|
||||||
if (Enum.Parse<LogLevel>(log.Level) >= notifylevel)
|
if (Enum.Parse<LogLevel>(log.Level) >= notifylevel)
|
||||||
{
|
{
|
||||||
|
var subject = $"Site {log.Level} Notification";
|
||||||
|
string body = $"Log Message: {log.Message}";
|
||||||
|
|
||||||
var alias = _tenantManager.GetAlias();
|
var alias = _tenantManager.GetAlias();
|
||||||
foreach (var userrole in _userRoles.GetUserRoles(log.SiteId.Value))
|
if (alias != null)
|
||||||
{
|
{
|
||||||
if (userrole.Role.Name == RoleNames.Host)
|
subject = $"{alias.Name} Site {log.Level} Notification";
|
||||||
|
body = $"Log Message: {log.Message}<br /><br />Please visit {alias.Protocol}://{alias.Name}/admin/log?id={log.LogId} for more information";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var userrole in _userRoles.GetUserRoles(RoleNames.Host, log.SiteId.Value))
|
||||||
{
|
{
|
||||||
var subject = $"{alias.Name} Site {log.Level} Notification";
|
|
||||||
var url = $"{_accessor.HttpContext.Request.Scheme}://{alias.Name}/admin/log?id={log.LogId}";
|
|
||||||
string body = $"Log Message: {log.Message}<br /><br />Please visit {url} for more information";
|
|
||||||
var notification = new Notification(log.SiteId.Value, userrole.User, subject, body);
|
var notification = new Notification(log.SiteId.Value, userrole.User, subject, body);
|
||||||
_notifications.AddNotification(notification);
|
_notifications.AddNotification(notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
774
Oqtane.Server/Infrastructure/SiteTemplates/AdminSiteTemplate.cs
Normal file
774
Oqtane.Server/Infrastructure/SiteTemplates/AdminSiteTemplate.cs
Normal file
@ -0,0 +1,774 @@
|
|||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
using Oqtane.Documentation;
|
||||||
|
|
||||||
|
namespace Oqtane.SiteTemplates
|
||||||
|
{
|
||||||
|
[PrivateApi("Mark Site-Template classes as private, since it's not very useful in the public docs")]
|
||||||
|
public class AdminSiteTemplate : ISiteTemplate
|
||||||
|
{
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return "Admin Site Template"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PageTemplate> CreateSite(Site site)
|
||||||
|
{
|
||||||
|
var pageTemplates = new List<PageTemplate>();
|
||||||
|
var seed = 1000; // order
|
||||||
|
|
||||||
|
// user pages
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Login",
|
||||||
|
Parent = "",
|
||||||
|
Path = "login",
|
||||||
|
Order = seed + 1,
|
||||||
|
Icon = Icons.LockLocked,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Login.Index).ToModuleDefinitionName(), Title = "User Login", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Register",
|
||||||
|
Parent = "",
|
||||||
|
Path = "register",
|
||||||
|
Order = seed + 3,
|
||||||
|
Icon = Icons.Person,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Register.Index).ToModuleDefinitionName(), Title = "User Registration", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Reset",
|
||||||
|
Parent = "",
|
||||||
|
Path = "reset",
|
||||||
|
Order = seed + 5,
|
||||||
|
Icon = Icons.Person,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Reset.Index).ToModuleDefinitionName(), Title = "Password Reset", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Profile",
|
||||||
|
Parent = "",
|
||||||
|
Path = "profile",
|
||||||
|
Order = seed + 7,
|
||||||
|
Icon = Icons.Person,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Registered, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UserProfile.Index).ToModuleDefinitionName(), Title = "User Profile", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Registered, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Search",
|
||||||
|
Parent = "",
|
||||||
|
Path = "search",
|
||||||
|
Order = seed + 9,
|
||||||
|
Icon = Icons.MagnifyingGlass,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission> {
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule> {
|
||||||
|
new PageTemplateModule { ModuleDefinitionName = typeof(Oqtane.Modules.Admin.SearchResults.Index).ToModuleDefinitionName(), Title = "Search", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission> {
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Not Found",
|
||||||
|
Parent = "",
|
||||||
|
Path = "404",
|
||||||
|
Order = seed + 11,
|
||||||
|
Icon = Icons.X,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Not Found", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission> {
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = "<p>The page you requested does not exist or you do not have sufficient rights to view it.</p>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// admin pages
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Admin",
|
||||||
|
Parent = "",
|
||||||
|
Path = "admin",
|
||||||
|
Order = seed + 51,
|
||||||
|
Icon = "",
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Dashboard.Index).ToModuleDefinitionName(), Title = "Admin Dashboard", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Site Settings",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 1,
|
||||||
|
Path = "admin/site",
|
||||||
|
Icon = Icons.Home,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Site.Index).ToModuleDefinitionName(), Title = "Site Settings", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Page Management",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 3,
|
||||||
|
Path = "admin/pages",
|
||||||
|
Icon = Icons.Layers,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Registered, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Pages.Index).ToModuleDefinitionName(), Title = "Page Management", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "User Management",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 5,
|
||||||
|
Path = "admin/users",
|
||||||
|
Icon = Icons.People,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Users.Index).ToModuleDefinitionName(), Title = "User Management", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Profile Management",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 7,
|
||||||
|
Path = "admin/profiles",
|
||||||
|
Icon = Icons.Person,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Profiles.Index).ToModuleDefinitionName(), Title = "Profile Management", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Role Management",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 9,
|
||||||
|
Path = "admin/roles",
|
||||||
|
Icon = Icons.LockLocked,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Roles.Index).ToModuleDefinitionName(), Title = "Role Management", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "File Management",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 11,
|
||||||
|
Path = "admin/files",
|
||||||
|
Icon = Icons.File,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Files.Index).ToModuleDefinitionName(), Title = "File Management", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Recycle Bin",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 13,
|
||||||
|
Path = "admin/recyclebin",
|
||||||
|
Icon = Icons.Trash,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.RecycleBin.Index).ToModuleDefinitionName(), Title = "Recycle Bin", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Url Mappings",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 15,
|
||||||
|
Path = "admin/urlmappings",
|
||||||
|
Icon = Icons.LinkBroken,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UrlMappings.Index).ToModuleDefinitionName(), Title = "Url Mappings", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Visitor Management",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 17,
|
||||||
|
Path = "admin/visitors",
|
||||||
|
Icon = Icons.Eye,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Search Settings",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 19,
|
||||||
|
Path = "admin/search",
|
||||||
|
Icon = Icons.MagnifyingGlass,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Search.Index).ToModuleDefinitionName(), Title = "Search Settings", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// host pages (order starts at 51)
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Event Log",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 51,
|
||||||
|
Path = "admin/log",
|
||||||
|
Icon = Icons.List,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Logs.Index).ToModuleDefinitionName(), Title = "Event Log", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Site Management",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 53,
|
||||||
|
Path = "admin/sites",
|
||||||
|
Icon = Icons.Globe,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sites.Index).ToModuleDefinitionName(), Title = "Site Management", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Module Management",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 55,
|
||||||
|
Path = "admin/modules",
|
||||||
|
Icon = Icons.Browser,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.ModuleDefinitions.Index).ToModuleDefinitionName(), Title = "Module Management", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Theme Management",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 57,
|
||||||
|
Path = "admin/themes",
|
||||||
|
Icon = Icons.Brush,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Themes.Index).ToModuleDefinitionName(), Title = "Theme Management", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Language Management",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 59,
|
||||||
|
Path = "admin/languages",
|
||||||
|
Icon = Icons.Text,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Languages.Index).ToModuleDefinitionName(), Title = "Language Management", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Scheduled Jobs",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 61,
|
||||||
|
Path = "admin/jobs",
|
||||||
|
Icon = Icons.Timer,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Jobs.Index).ToModuleDefinitionName(), Title = "Scheduled Jobs", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "Sql Management",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 63,
|
||||||
|
Path = "admin/sql",
|
||||||
|
Icon = Icons.Spreadsheet,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sql.Index).ToModuleDefinitionName(), Title = "Sql Management", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "System Info",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 65,
|
||||||
|
Path = "admin/system",
|
||||||
|
Icon = Icons.MedicalCross,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.SystemInfo.Index).ToModuleDefinitionName(), Title = "System Info", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pageTemplates.Add(new PageTemplate
|
||||||
|
{
|
||||||
|
Name = "System Update",
|
||||||
|
Parent = "Admin",
|
||||||
|
Order = 67,
|
||||||
|
Path = "admin/update",
|
||||||
|
Icon = Icons.Aperture,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
{
|
||||||
|
new PageTemplateModule
|
||||||
|
{
|
||||||
|
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Upgrade.Index).ToModuleDefinitionName(), Title = "System Update", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Host, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Host, true)
|
||||||
|
},
|
||||||
|
Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return pageTemplates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -66,6 +66,9 @@ namespace Oqtane.Infrastructure
|
|||||||
case "5.1.0":
|
case "5.1.0":
|
||||||
Upgrade_5_1_0(tenant, scope);
|
Upgrade_5_1_0(tenant, scope);
|
||||||
break;
|
break;
|
||||||
|
case "5.2.0":
|
||||||
|
Upgrade_5_2_0(tenant, scope);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,10 +139,11 @@ namespace Oqtane.Infrastructure
|
|||||||
|
|
||||||
private void Upgrade_3_0_1(Tenant tenant, IServiceScope scope)
|
private void Upgrade_3_0_1(Tenant tenant, IServiceScope scope)
|
||||||
{
|
{
|
||||||
var pageTemplates = new List<PageTemplate>();
|
var pageTemplates = new List<PageTemplate>
|
||||||
|
|
||||||
pageTemplates.Add(new PageTemplate
|
|
||||||
{
|
{
|
||||||
|
new PageTemplate
|
||||||
|
{
|
||||||
|
Update = false,
|
||||||
Name = "Url Mappings",
|
Name = "Url Mappings",
|
||||||
Parent = "Admin",
|
Parent = "Admin",
|
||||||
Order = 33,
|
Order = 33,
|
||||||
@ -165,10 +169,10 @@ namespace Oqtane.Infrastructure
|
|||||||
Content = ""
|
Content = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
new PageTemplate
|
||||||
pageTemplates.Add(new PageTemplate
|
|
||||||
{
|
{
|
||||||
|
Update = false,
|
||||||
Name = "Visitor Management",
|
Name = "Visitor Management",
|
||||||
Parent = "Admin",
|
Parent = "Admin",
|
||||||
Order = 35,
|
Order = 35,
|
||||||
@ -194,13 +198,10 @@ namespace Oqtane.Infrastructure
|
|||||||
Content = ""
|
Content = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
|
|
||||||
foreach (Site site in sites.GetSites().ToList())
|
|
||||||
{
|
|
||||||
sites.CreatePages(site, pageTemplates, null);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AddPagesToSites(scope, pageTemplates);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Upgrade_3_1_3(Tenant tenant, IServiceScope scope)
|
private void Upgrade_3_1_3(Tenant tenant, IServiceScope scope)
|
||||||
@ -383,7 +384,70 @@ namespace Oqtane.Infrastructure
|
|||||||
Debug.WriteLine($"Oqtane Error: Error In 5.1.0 Upgrade Logic - {ex}");
|
Debug.WriteLine($"Oqtane Error: Error In 5.1.0 Upgrade Logic - {ex}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Upgrade_5_2_0(Tenant tenant, IServiceScope scope)
|
||||||
|
{
|
||||||
|
var pageTemplates = new List<PageTemplate>
|
||||||
|
{
|
||||||
|
new PageTemplate
|
||||||
|
{
|
||||||
|
Update = false,
|
||||||
|
Name = "Search",
|
||||||
|
Parent = "",
|
||||||
|
Path = "search",
|
||||||
|
Icon = Icons.MagnifyingGlass,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission> {
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule> {
|
||||||
|
new PageTemplateModule { ModuleDefinitionName = typeof(Oqtane.Modules.Admin.SearchResults.Index).ToModuleDefinitionName(), Title = "Search", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission> {
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new PageTemplate
|
||||||
|
{
|
||||||
|
Update = false,
|
||||||
|
Name = "Search Settings",
|
||||||
|
Parent = "",
|
||||||
|
Path = "admin/search",
|
||||||
|
Icon = Icons.MagnifyingGlass,
|
||||||
|
IsNavigation = false,
|
||||||
|
IsPersonalizable = false,
|
||||||
|
PermissionList = new List<Permission> {
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
},
|
||||||
|
PageTemplateModules = new List<PageTemplateModule> {
|
||||||
|
new PageTemplateModule { ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Search.Index).ToModuleDefinitionName(), Title = "Search Settings", Pane = PaneNames.Default,
|
||||||
|
PermissionList = new List<Permission> {
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AddPagesToSites(scope, pageTemplates);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddPagesToSites(IServiceScope scope, List<PageTemplate> pageTemplates)
|
||||||
|
{
|
||||||
|
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
|
||||||
|
foreach (var site in sites.GetSites().ToList())
|
||||||
|
{
|
||||||
|
sites.CreatePages(site, pageTemplates, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using Oqtane.Enums;
|
using Oqtane.Enums;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
@ -25,15 +26,15 @@ namespace Oqtane.Managers
|
|||||||
private readonly ITenantManager _tenantManager;
|
private readonly ITenantManager _tenantManager;
|
||||||
private readonly INotificationRepository _notifications;
|
private readonly INotificationRepository _notifications;
|
||||||
private readonly IFolderRepository _folders;
|
private readonly IFolderRepository _folders;
|
||||||
private readonly IFileRepository _files;
|
|
||||||
private readonly IProfileRepository _profiles;
|
private readonly IProfileRepository _profiles;
|
||||||
private readonly ISettingRepository _settings;
|
private readonly ISettingRepository _settings;
|
||||||
|
private readonly ISiteRepository _sites;
|
||||||
private readonly ISyncManager _syncManager;
|
private readonly ISyncManager _syncManager;
|
||||||
private readonly ILogManager _logger;
|
private readonly ILogManager _logger;
|
||||||
|
private readonly IMemoryCache _cache;
|
||||||
private readonly IStringLocalizer<UserManager> _localizer;
|
private readonly IStringLocalizer<UserManager> _localizer;
|
||||||
private readonly ISiteRepository _siteRepo;
|
|
||||||
|
|
||||||
public UserManager(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, IFileRepository files, IProfileRepository profiles, ISettingRepository settings, ISyncManager syncManager, ILogManager logger, IStringLocalizer<UserManager> localizer, ISiteRepository siteRepo)
|
public UserManager(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, IProfileRepository profiles, ISettingRepository settings, ISiteRepository sites, ISyncManager syncManager, ILogManager logger, IMemoryCache cache, IStringLocalizer<UserManager> localizer)
|
||||||
{
|
{
|
||||||
_users = users;
|
_users = users;
|
||||||
_roles = roles;
|
_roles = roles;
|
||||||
@ -43,17 +44,21 @@ namespace Oqtane.Managers
|
|||||||
_tenantManager = tenantManager;
|
_tenantManager = tenantManager;
|
||||||
_notifications = notifications;
|
_notifications = notifications;
|
||||||
_folders = folders;
|
_folders = folders;
|
||||||
_files = files;
|
|
||||||
_profiles = profiles;
|
_profiles = profiles;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
|
_sites = sites;
|
||||||
_syncManager = syncManager;
|
_syncManager = syncManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_cache = cache;
|
||||||
_localizer = localizer;
|
_localizer = localizer;
|
||||||
_siteRepo = siteRepo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public User GetUser(int userid, int siteid)
|
public User GetUser(int userid, int siteid)
|
||||||
{
|
{
|
||||||
|
var alias = _tenantManager.GetAlias();
|
||||||
|
return _cache.GetOrCreate($"user:{userid}:{alias.SiteKey}", entry =>
|
||||||
|
{
|
||||||
|
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
|
||||||
User user = _users.GetUser(userid);
|
User user = _users.GetUser(userid);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
@ -61,11 +66,18 @@ namespace Oqtane.Managers
|
|||||||
user.Roles = GetUserRoles(user.UserId, user.SiteId);
|
user.Roles = GetUserRoles(user.UserId, user.SiteId);
|
||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public User GetUser(string username, int siteid)
|
public User GetUser(string username, int siteid)
|
||||||
{
|
{
|
||||||
return GetUser(username, "", siteid);
|
User user = _users.GetUser(username);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
user.SiteId = siteid;
|
||||||
|
user.Roles = GetUserRoles(user.UserId, user.SiteId);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public User GetUser(string username, string email, int siteid)
|
public User GetUser(string username, string email, int siteid)
|
||||||
@ -84,6 +96,8 @@ namespace Oqtane.Managers
|
|||||||
string roles = "";
|
string roles = "";
|
||||||
List<UserRole> userroles = _userRoles.GetUserRoles(userId, siteId).ToList();
|
List<UserRole> userroles = _userRoles.GetUserRoles(userId, siteId).ToList();
|
||||||
foreach (UserRole userrole in userroles)
|
foreach (UserRole userrole in userroles)
|
||||||
|
{
|
||||||
|
if (Utilities.IsEffectiveAndNotExpired(userrole.EffectiveDate, userrole.ExpiryDate))
|
||||||
{
|
{
|
||||||
roles += userrole.Role.Name + ";";
|
roles += userrole.Role.Name + ";";
|
||||||
if (userrole.Role.Name == RoleNames.Host && !userroles.Any(item => item.Role.Name == RoleNames.Admin))
|
if (userrole.Role.Name == RoleNames.Host && !userroles.Any(item => item.Role.Name == RoleNames.Admin))
|
||||||
@ -95,6 +109,7 @@ namespace Oqtane.Managers
|
|||||||
roles += RoleNames.Registered + ";";
|
roles += RoleNames.Registered + ";";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (roles != "") roles = ";" + roles;
|
if (roles != "") roles = ";" + roles;
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
@ -153,7 +168,7 @@ namespace Oqtane.Managers
|
|||||||
|
|
||||||
if (User != null)
|
if (User != null)
|
||||||
{
|
{
|
||||||
string siteName = _siteRepo.GetSite(user.SiteId).Name;
|
string siteName = _sites.GetSite(user.SiteId).Name;
|
||||||
if (!user.EmailConfirmed)
|
if (!user.EmailConfirmed)
|
||||||
{
|
{
|
||||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||||
@ -201,6 +216,8 @@ namespace Oqtane.Managers
|
|||||||
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
||||||
if (identityuser != null)
|
if (identityuser != null)
|
||||||
{
|
{
|
||||||
|
var alias = _tenantManager.GetAlias();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(user.Password))
|
if (!string.IsNullOrEmpty(user.Password))
|
||||||
{
|
{
|
||||||
var validator = new PasswordValidator<IdentityUser>();
|
var validator = new PasswordValidator<IdentityUser>();
|
||||||
@ -224,7 +241,6 @@ namespace Oqtane.Managers
|
|||||||
// if email address changed and it is not confirmed, verification is required for new email address
|
// if email address changed and it is not confirmed, verification is required for new email address
|
||||||
if (!user.EmailConfirmed)
|
if (!user.EmailConfirmed)
|
||||||
{
|
{
|
||||||
var alias = _tenantManager.GetAlias();
|
|
||||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||||
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
|
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
|
||||||
@ -242,6 +258,7 @@ namespace Oqtane.Managers
|
|||||||
user = _users.UpdateUser(user);
|
user = _users.UpdateUser(user);
|
||||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Update);
|
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Reload);
|
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Reload);
|
||||||
|
_cache.Remove($"user:{user.UserId}:{alias.SiteKey}");
|
||||||
user.Password = ""; // remove sensitive information
|
user.Password = ""; // remove sensitive information
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
|
||||||
}
|
}
|
||||||
@ -324,7 +341,7 @@ namespace Oqtane.Managers
|
|||||||
_users.UpdateUser(user);
|
_users.UpdateUser(user);
|
||||||
var alias = _tenantManager.GetAlias();
|
var alias = _tenantManager.GetAlias();
|
||||||
string url = alias.Protocol + alias.Name;
|
string url = alias.Protocol + alias.Name;
|
||||||
string siteName = _siteRepo.GetSite(alias.SiteId).Name;
|
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||||
string subject = _localizer["TwoFactorEmailSubject"];
|
string subject = _localizer["TwoFactorEmailSubject"];
|
||||||
subject = subject.Replace("[SiteName]", siteName);
|
subject = subject.Replace("[SiteName]", siteName);
|
||||||
string body = _localizer["TwoFactorEmailBody"].Value;
|
string body = _localizer["TwoFactorEmailBody"].Value;
|
||||||
@ -376,7 +393,7 @@ namespace Oqtane.Managers
|
|||||||
user = _users.GetUser(user.Username);
|
user = _users.GetUser(user.Username);
|
||||||
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
|
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
|
||||||
string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||||
string siteName = _siteRepo.GetSite(alias.SiteId).Name;
|
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||||
string subject = _localizer["UserLockoutEmailSubject"];
|
string subject = _localizer["UserLockoutEmailSubject"];
|
||||||
subject = subject.Replace("[SiteName]", siteName);
|
subject = subject.Replace("[SiteName]", siteName);
|
||||||
string body = _localizer["UserLockoutEmailBody"].Value;
|
string body = _localizer["UserLockoutEmailBody"].Value;
|
||||||
@ -429,7 +446,7 @@ namespace Oqtane.Managers
|
|||||||
user = _users.GetUser(user.Username);
|
user = _users.GetUser(user.Username);
|
||||||
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
|
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
|
||||||
string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
string url = alias.Protocol + alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||||
string siteName = _siteRepo.GetSite(alias.SiteId).Name;
|
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||||
string subject = _localizer["ForgotPasswordEmailSubject"];
|
string subject = _localizer["ForgotPasswordEmailSubject"];
|
||||||
subject = subject.Replace("[SiteName]", siteName);
|
subject = subject.Replace("[SiteName]", siteName);
|
||||||
string body = _localizer["ForgotPasswordEmailBody"].Value;
|
string body = _localizer["ForgotPasswordEmailBody"].Value;
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
||||||
|
using Oqtane.Databases.Interfaces;
|
||||||
|
|
||||||
|
namespace Oqtane.Migrations.EntityBuilders
|
||||||
|
{
|
||||||
|
public class SearchContentEntityBuilder : BaseEntityBuilder<SearchContentEntityBuilder>
|
||||||
|
{
|
||||||
|
private const string _entityTableName = "SearchContent";
|
||||||
|
private readonly PrimaryKey<SearchContentEntityBuilder> _primaryKey = new("PK_SearchContent", x => x.SearchContentId);
|
||||||
|
|
||||||
|
public SearchContentEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||||
|
{
|
||||||
|
EntityTableName = _entityTableName;
|
||||||
|
PrimaryKey = _primaryKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SearchContentEntityBuilder BuildTable(ColumnsBuilder table)
|
||||||
|
{
|
||||||
|
SearchContentId = AddAutoIncrementColumn(table, "SearchContentId");
|
||||||
|
SiteId = AddIntegerColumn(table, "SiteId");
|
||||||
|
EntityName = AddStringColumn(table, "EntityName", 50);
|
||||||
|
EntityId = AddStringColumn(table, "EntityId", 50);
|
||||||
|
Title = AddStringColumn(table, "Title", 200);
|
||||||
|
Description = AddMaxStringColumn(table, "Description");
|
||||||
|
Body = AddMaxStringColumn(table, "Body");
|
||||||
|
Url = AddStringColumn(table, "Url", 500);
|
||||||
|
Permissions = AddStringColumn(table, "Permissions", 100);
|
||||||
|
ContentModifiedBy = AddStringColumn(table, "ContentModifiedBy", 256);
|
||||||
|
ContentModifiedOn = AddDateTimeColumn(table, "ContentModifiedOn");
|
||||||
|
AdditionalContent = AddMaxStringColumn(table, "AdditionalContent");
|
||||||
|
CreatedOn = AddDateTimeColumn(table, "CreatedOn");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> SearchContentId { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> SiteId { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> EntityName { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> EntityId { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> Title { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> Description { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> Body { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> Url { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> Permissions { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> ContentModifiedBy { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> ContentModifiedOn { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> AdditionalContent { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> CreatedOn { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
||||||
|
using Oqtane.Databases.Interfaces;
|
||||||
|
|
||||||
|
namespace Oqtane.Migrations.EntityBuilders
|
||||||
|
{
|
||||||
|
public class SearchContentPropertyEntityBuilder : BaseEntityBuilder<SearchContentPropertyEntityBuilder>
|
||||||
|
{
|
||||||
|
private const string _entityTableName = "SearchContentProperty";
|
||||||
|
private readonly PrimaryKey<SearchContentPropertyEntityBuilder> _primaryKey = new("PK_SearchContentProperty", x => x.PropertyId);
|
||||||
|
private readonly ForeignKey<SearchContentPropertyEntityBuilder> _searchContentForeignKey = new("FK_SearchContentProperty_SearchContent", x => x.SearchContentId, "SearchContent", "SearchContentId", ReferentialAction.Cascade);
|
||||||
|
|
||||||
|
public SearchContentPropertyEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||||
|
{
|
||||||
|
EntityTableName = _entityTableName;
|
||||||
|
PrimaryKey = _primaryKey;
|
||||||
|
|
||||||
|
ForeignKeys.Add(_searchContentForeignKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SearchContentPropertyEntityBuilder BuildTable(ColumnsBuilder table)
|
||||||
|
{
|
||||||
|
PropertyId = AddAutoIncrementColumn(table, "PropertyId");
|
||||||
|
SearchContentId = AddIntegerColumn(table, "SearchContentId");
|
||||||
|
Name = AddStringColumn(table, "Name", 50);
|
||||||
|
Value = AddStringColumn(table, "Value", 50);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> PropertyId { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> SearchContentId { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> Name { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> Value { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
||||||
|
using Oqtane.Databases.Interfaces;
|
||||||
|
|
||||||
|
namespace Oqtane.Migrations.EntityBuilders
|
||||||
|
{
|
||||||
|
public class SearchContentWordEntityBuilder : BaseEntityBuilder<SearchContentWordEntityBuilder>
|
||||||
|
{
|
||||||
|
private const string _entityTableName = "SearchContentWord";
|
||||||
|
private readonly PrimaryKey<SearchContentWordEntityBuilder> _primaryKey = new("PK_SearchContentWord", x => x.SearchContentWordId);
|
||||||
|
private readonly ForeignKey<SearchContentWordEntityBuilder> _foreignKey1 = new("FK_SearchContentWord_SearchContent", x => x.SearchContentId, "SearchContent", "SearchContentId", ReferentialAction.Cascade);
|
||||||
|
private readonly ForeignKey<SearchContentWordEntityBuilder> _foreignKey2 = new("FK_SearchContentWord_SearchWord", x => x.SearchWordId, "SearchWord", "SearchWordId", ReferentialAction.Cascade);
|
||||||
|
|
||||||
|
public SearchContentWordEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||||
|
{
|
||||||
|
EntityTableName = _entityTableName;
|
||||||
|
PrimaryKey = _primaryKey;
|
||||||
|
ForeignKeys.Add(_foreignKey1);
|
||||||
|
ForeignKeys.Add(_foreignKey2);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SearchContentWordEntityBuilder BuildTable(ColumnsBuilder table)
|
||||||
|
{
|
||||||
|
SearchContentWordId = AddAutoIncrementColumn(table, "SearchContentWordId");
|
||||||
|
SearchContentId = AddIntegerColumn(table, "SearchContentId");
|
||||||
|
SearchWordId = AddIntegerColumn(table, "SearchWordId");
|
||||||
|
Count = AddIntegerColumn(table, "Count");
|
||||||
|
CreatedOn = AddDateTimeColumn(table, "CreatedOn");
|
||||||
|
ModifiedOn = AddDateTimeColumn(table, "ModifiedOn");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> SearchContentWordId { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> SearchContentId { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> SearchWordId { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> Count { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> CreatedOn { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> ModifiedOn { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
||||||
|
using Oqtane.Databases.Interfaces;
|
||||||
|
|
||||||
|
namespace Oqtane.Migrations.EntityBuilders
|
||||||
|
{
|
||||||
|
public class SearchWordEntityBuilder : BaseEntityBuilder<SearchWordEntityBuilder>
|
||||||
|
{
|
||||||
|
private const string _entityTableName = "SearchWord";
|
||||||
|
private readonly PrimaryKey<SearchWordEntityBuilder> _primaryKey = new("PK_SearchWord", x => x.SearchWordId);
|
||||||
|
|
||||||
|
public SearchWordEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||||
|
{
|
||||||
|
EntityTableName = _entityTableName;
|
||||||
|
PrimaryKey = _primaryKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SearchWordEntityBuilder BuildTable(ColumnsBuilder table)
|
||||||
|
{
|
||||||
|
SearchWordId = AddAutoIncrementColumn(table, "SearchWordId");
|
||||||
|
Word = AddStringColumn(table, "Word", 255);
|
||||||
|
CreatedOn = AddDateTimeColumn(table, "CreatedOn");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> SearchWordId { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> Word { get; private set; }
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> CreatedOn { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
50
Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs
Normal file
50
Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Oqtane.Databases.Interfaces;
|
||||||
|
using Oqtane.Migrations.EntityBuilders;
|
||||||
|
using Oqtane.Repository;
|
||||||
|
|
||||||
|
namespace Oqtane.Migrations.Tenant
|
||||||
|
{
|
||||||
|
[DbContext(typeof(TenantDBContext))]
|
||||||
|
[Migration("Tenant.05.02.00.01")]
|
||||||
|
public class AddSearchTables : MultiDatabaseMigration
|
||||||
|
{
|
||||||
|
public AddSearchTables(IDatabase database) : base(database)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
var searchContentEntityBuilder = new SearchContentEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
searchContentEntityBuilder.Create();
|
||||||
|
|
||||||
|
var searchContentPropertyEntityBuilder = new SearchContentPropertyEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
searchContentPropertyEntityBuilder.Create();
|
||||||
|
|
||||||
|
var searchWordEntityBuilder = new SearchWordEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
searchWordEntityBuilder.Create();
|
||||||
|
searchWordEntityBuilder.AddIndex("IX_SearchWord", "Word", true);
|
||||||
|
|
||||||
|
var searchContentWordEntityBuilder = new SearchContentWordEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
searchContentWordEntityBuilder.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
var searchContentWordEntityBuilder = new SearchContentWordEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
searchContentWordEntityBuilder.Drop();
|
||||||
|
|
||||||
|
var searchWordEntityBuilder = new SearchWordEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
searchWordEntityBuilder.DropIndex("IX_SearchWord");
|
||||||
|
searchWordEntityBuilder.Drop();
|
||||||
|
|
||||||
|
var searchContentPropertyEntityBuilder = new SearchContentPropertyEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
searchContentPropertyEntityBuilder.Drop();
|
||||||
|
|
||||||
|
var searchContentEntityBuilder = new SearchContentEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
searchContentEntityBuilder.Drop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs
Normal file
75
Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Repository;
|
||||||
|
using Oqtane.Interfaces;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Oqtane.Modules.Admin.Files.Manager
|
||||||
|
{
|
||||||
|
public class FileManager : ISearchable
|
||||||
|
{
|
||||||
|
private readonly IFolderRepository _folderRepository;
|
||||||
|
private readonly IFileRepository _fileRepository;
|
||||||
|
private const string DocumentExtensions = ".txt,.htm,.html";
|
||||||
|
|
||||||
|
public FileManager(IFolderRepository folderRepository, IFileRepository fileRepository)
|
||||||
|
{
|
||||||
|
_folderRepository = folderRepository;
|
||||||
|
_fileRepository = fileRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<SearchContent>> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn)
|
||||||
|
{
|
||||||
|
var searchContents = new List<SearchContent>();
|
||||||
|
|
||||||
|
var folders = _folderRepository.GetFolders(pageModule.Module.SiteId);
|
||||||
|
foreach ( var folder in folders)
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
bool removed = false;
|
||||||
|
|
||||||
|
if (folder.ModifiedOn >= lastIndexedOn)
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
removed = folder.IsDeleted.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var files = _fileRepository.GetFiles(folder.FolderId);
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
if (file.ModifiedOn >= lastIndexedOn || changed)
|
||||||
|
{
|
||||||
|
var path = folder.Path + file.Name;
|
||||||
|
|
||||||
|
var body = "";
|
||||||
|
if (DocumentExtensions.Contains(Path.GetExtension(file.Name)))
|
||||||
|
{
|
||||||
|
// get the contents of the file
|
||||||
|
body = System.IO.File.ReadAllText(_fileRepository.GetFilePath(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchContent = new SearchContent
|
||||||
|
{
|
||||||
|
SiteId = folder.SiteId,
|
||||||
|
EntityName = EntityNames.File,
|
||||||
|
EntityId = file.FileId.ToString(),
|
||||||
|
Title = path,
|
||||||
|
Body = body,
|
||||||
|
Url = $"{Constants.FileUrl}{folder.Path}{file.Name}",
|
||||||
|
Permissions = $"{EntityNames.Folder}:{folder.FolderId}",
|
||||||
|
ContentModifiedBy = file.ModifiedBy,
|
||||||
|
ContentModifiedOn = file.ModifiedOn,
|
||||||
|
IsDeleted = (removed || file.IsDeleted.Value)
|
||||||
|
};
|
||||||
|
searchContents.Add(searchContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(searchContents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,20 +8,30 @@ using Oqtane.Shared;
|
|||||||
using Oqtane.Migrations.Framework;
|
using Oqtane.Migrations.Framework;
|
||||||
using Oqtane.Documentation;
|
using Oqtane.Documentation;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Oqtane.Interfaces;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
// ReSharper disable ConvertToUsingDeclaration
|
// ReSharper disable ConvertToUsingDeclaration
|
||||||
|
|
||||||
namespace Oqtane.Modules.HtmlText.Manager
|
namespace Oqtane.Modules.HtmlText.Manager
|
||||||
{
|
{
|
||||||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||||
public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable
|
public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable, ISearchable
|
||||||
{
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly IHtmlTextRepository _htmlText;
|
private readonly IHtmlTextRepository _htmlText;
|
||||||
private readonly IDBContextDependencies _DBContextDependencies;
|
private readonly IDBContextDependencies _DBContextDependencies;
|
||||||
private readonly ISqlRepository _sqlRepository;
|
private readonly ISqlRepository _sqlRepository;
|
||||||
|
|
||||||
public HtmlTextManager(IHtmlTextRepository htmlText, IDBContextDependencies DBContextDependencies, ISqlRepository sqlRepository)
|
public HtmlTextManager(
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
IHtmlTextRepository htmlText,
|
||||||
|
IDBContextDependencies DBContextDependencies,
|
||||||
|
ISqlRepository sqlRepository)
|
||||||
{
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
_htmlText = htmlText;
|
_htmlText = htmlText;
|
||||||
_DBContextDependencies = DBContextDependencies;
|
_DBContextDependencies = DBContextDependencies;
|
||||||
_sqlRepository = sqlRepository;
|
_sqlRepository = sqlRepository;
|
||||||
@ -39,6 +49,28 @@ namespace Oqtane.Modules.HtmlText.Manager
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<List<SearchContent>> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn)
|
||||||
|
{
|
||||||
|
var searchContents = new List<SearchContent>();
|
||||||
|
|
||||||
|
var htmltexts = _htmlText.GetHtmlTexts(pageModule.ModuleId);
|
||||||
|
if (htmltexts != null && htmltexts.Any())
|
||||||
|
{
|
||||||
|
var htmltext = htmltexts.OrderByDescending(item => item.CreatedOn).First();
|
||||||
|
if (htmltext.CreatedOn >= lastIndexedOn)
|
||||||
|
{
|
||||||
|
searchContents.Add(new SearchContent
|
||||||
|
{
|
||||||
|
Body = htmltext.Content,
|
||||||
|
ContentModifiedBy = htmltext.CreatedBy,
|
||||||
|
ContentModifiedOn = htmltext.CreatedOn
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(searchContents);
|
||||||
|
}
|
||||||
|
|
||||||
public void ImportModule(Module module, string content, string version)
|
public void ImportModule(Module module, string content, string version)
|
||||||
{
|
{
|
||||||
content = WebUtility.HtmlDecode(content);
|
content = WebUtility.HtmlDecode(content);
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
<Version>5.1.2</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>
|
||||||
@ -11,7 +11,7 @@
|
|||||||
<Copyright>.NET Foundation</Copyright>
|
<Copyright>.NET Foundation</Copyright>
|
||||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2</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>
|
||||||
<RootNamespace>Oqtane</RootNamespace>
|
<RootNamespace>Oqtane</RootNamespace>
|
||||||
@ -33,19 +33,20 @@
|
|||||||
<EmbeddedResource Include="Scripts\MigrateTenant.sql" />
|
<EmbeddedResource Include="Scripts\MigrateTenant.sql" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.5" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.61" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.7" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.5" />
|
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.7" />
|
||||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.8" />
|
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.8" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user