Merge remote-tracking branch 'oqtane/dev' into dev
This commit is contained in:
commit
4b17847ea5
26
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a bug report to help us improve the product
|
||||
title: "[BUG] "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Oqtane Info
|
||||
|
||||
Version - #.#.#
|
||||
Render Mode - Static
|
||||
Interactivity - Server
|
||||
Database - SQL Server
|
||||
|
||||
### Describe the bug
|
||||
|
||||
|
||||
### Expected Behavior
|
||||
|
||||
|
||||
### Steps To Reproduce
|
||||
|
||||
|
||||
### Anything else?
|
20
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Enhancement Request
|
||||
about: 'Suggest a product enhancement '
|
||||
title: "[ENH] "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Oqtane Info
|
||||
|
||||
Version - #.#.#
|
||||
Render Mode - Static
|
||||
Interactivity - Server
|
||||
Database - SQL Server
|
||||
|
||||
### Describe the enhancement
|
||||
|
||||
|
||||
### Anything else?
|
@ -170,6 +170,7 @@
|
||||
try
|
||||
{
|
||||
Folder folder;
|
||||
|
||||
if (_folderId != -1)
|
||||
{
|
||||
folder = await FolderService.GetFolderAsync(_folderId);
|
||||
@ -179,8 +180,6 @@
|
||||
folder = new Folder();
|
||||
}
|
||||
|
||||
folder.SiteId = PageState.Site.SiteId;
|
||||
|
||||
if (_parentId == -1)
|
||||
{
|
||||
folder.ParentId = null;
|
||||
@ -189,7 +188,15 @@
|
||||
{
|
||||
folder.ParentId = _parentId;
|
||||
}
|
||||
|
||||
// check for duplicate folder names
|
||||
if (_folders.Any(item => item.ParentId == folder.ParentId && item.Name == _name && item.FolderId != _folderId))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Folder.Duplicate"], MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
folder.SiteId = PageState.Site.SiteId;
|
||||
folder.Name = _name;
|
||||
folder.Type = _type;
|
||||
folder.ImageSizes = _imagesizes;
|
||||
|
@ -26,8 +26,8 @@ else
|
||||
<th style="width: 1px;"> </th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td>
|
||||
<td><ActionLink Action="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td>
|
||||
<td><ActionLink Action="Log" Text="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
|
||||
<td>@context.Name</td>
|
||||
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
|
||||
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>
|
||||
|
@ -63,7 +63,7 @@ else
|
||||
<th>@Localizer["Function"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
|
||||
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Text="Details" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
|
||||
<td class="@GetClass(context.Function)">@context.LogDate</td>
|
||||
<td class="@GetClass(context.Function)">@context.Level</td>
|
||||
<td class="@GetClass(context.Function)">@context.Feature</td>
|
||||
@ -86,47 +86,48 @@ else
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
<ActionDialog Header="Clear Events" Message="Are You Sure You Wish To Remove All Log Events?" Action="DeleteLogs" Class="btn btn-danger" OnClick="@(async () => await DeleteLogs())" ResourceKey="DeleteLogs" />
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
private string _level = "-";
|
||||
private string _function = "-";
|
||||
private string _rows = "10";
|
||||
private int _page = 1;
|
||||
private List<Log> _logs;
|
||||
private int _retention = 30;
|
||||
private string _level = "-";
|
||||
private string _function = "-";
|
||||
private string _rows = "10";
|
||||
private int _page = 1;
|
||||
private List<Log> _logs;
|
||||
private int _retention = 30;
|
||||
|
||||
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (UrlParameters.ContainsKey("level"))
|
||||
{
|
||||
_level = UrlParameters["level"];
|
||||
}
|
||||
if (UrlParameters.ContainsKey("function"))
|
||||
{
|
||||
_function = UrlParameters["function"];
|
||||
}
|
||||
if (UrlParameters.ContainsKey("rows"))
|
||||
{
|
||||
_rows = UrlParameters["rows"];
|
||||
}
|
||||
if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page))
|
||||
{
|
||||
_page = page;
|
||||
}
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (UrlParameters.ContainsKey("level"))
|
||||
{
|
||||
_level = UrlParameters["level"];
|
||||
}
|
||||
if (UrlParameters.ContainsKey("function"))
|
||||
{
|
||||
_function = UrlParameters["function"];
|
||||
}
|
||||
if (UrlParameters.ContainsKey("rows"))
|
||||
{
|
||||
_rows = UrlParameters["rows"];
|
||||
}
|
||||
if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page))
|
||||
{
|
||||
_page = page;
|
||||
}
|
||||
|
||||
await GetLogs();
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30"));
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -213,22 +214,37 @@ else
|
||||
return classname;
|
||||
}
|
||||
|
||||
private async Task SaveSiteSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true);
|
||||
private async Task SaveSiteSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true);
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteLogs()
|
||||
{
|
||||
try
|
||||
{
|
||||
await LogService.DeleteLogsAsync(PageState.Site.SiteId);
|
||||
await GetLogs();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting Logs {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.DeleteLogs"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPageChange(int page)
|
||||
{
|
||||
|
@ -32,7 +32,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" required />
|
||||
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -306,10 +306,10 @@
|
||||
_languages = _languages.OrderBy(item => item.Name).ToList();
|
||||
}
|
||||
|
||||
// Group modules by PageId
|
||||
// Get distinct PageIds where modules are present
|
||||
var distinctPageIds = PageState.Modules
|
||||
.Where(md => md.ModuleDefinition.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false)
|
||||
// get distinct pages where module exists
|
||||
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
var distinctPageIds = modules
|
||||
.Where(md => md.ModuleDefinition?.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false)
|
||||
.Select(md => md.PageId)
|
||||
.Distinct();
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IModuleService ModuleService
|
||||
@inject IModuleDefinitionService ModuleDefinitionService
|
||||
@inject IPackageService PackageService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@ -50,7 +51,7 @@ else
|
||||
<th style="width: 1px;"> </th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
|
||||
<td>
|
||||
@if (context.AssemblyName != Constants.ClientId)
|
||||
{
|
||||
@ -70,7 +71,7 @@ else
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
|
||||
@if (context.AssemblyName == Constants.ClientId || _modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
|
||||
{
|
||||
<span>@SharedLocalizer["Yes"]</span>
|
||||
}
|
||||
@ -99,6 +100,7 @@ else
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Module> _modules;
|
||||
private List<ModuleDefinition> _allModuleDefinitions;
|
||||
private List<ModuleDefinition> _moduleDefinitions;
|
||||
private List<Package> _packages;
|
||||
@ -111,6 +113,7 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
|
||||
await LoadModuleDefinitions();
|
||||
|
@ -94,7 +94,7 @@
|
||||
</div>
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Permissions" ResourceKey="Permissions">
|
||||
<TabPanel Name="Permissions" Heading="Permissions" ResourceKey="Permissions">
|
||||
@if (_permissions != null)
|
||||
{
|
||||
<div class="container">
|
||||
@ -126,9 +126,8 @@
|
||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||
</form>
|
||||
|
||||
@code {
|
||||
@code {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
public override string Title => "Module Settings";
|
||||
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
@ -144,7 +143,7 @@
|
||||
private PermissionGrid _permissionGrid;
|
||||
private Type _moduleSettingsType;
|
||||
private object _moduleSettings;
|
||||
private string _moduleSettingsTitle = "Module Settings";
|
||||
private string _moduleSettingsTitle;
|
||||
private RenderFragment ModuleSettingsComponent { get; set; }
|
||||
private Type _containerSettingsType;
|
||||
private object _containerSettings;
|
||||
@ -158,8 +157,10 @@
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
SetModuleTitle(Localizer["ModuleSettings.Title"]);
|
||||
_module = ModuleState.ModuleDefinition.Name;
|
||||
_title = ModuleState.Title;
|
||||
_moduleSettingsTitle = Localizer["ModuleSettings.Heading"];
|
||||
_pane = ModuleState.Pane;
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
|
||||
_containerType = ModuleState.ContainerType;
|
||||
@ -172,7 +173,8 @@
|
||||
modifiedon = ModuleState.ModifiedOn;
|
||||
_effectivedate = Utilities.UtcAsLocalDate(ModuleState.EffectiveDate);
|
||||
_expirydate = Utilities.UtcAsLocalDate(ModuleState.ExpiryDate);
|
||||
|
||||
|
||||
|
||||
if (ModuleState.ModuleDefinition != null)
|
||||
{
|
||||
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
|
||||
|
@ -198,12 +198,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
@if (_themeSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings">
|
||||
@_themeSettingsComponent
|
||||
</TabPanel>
|
||||
}
|
||||
</TabStrip>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
@ -238,9 +232,6 @@
|
||||
private string _bodycontent;
|
||||
private string _permissions = null;
|
||||
private PermissionGrid _permissionGrid;
|
||||
private Type _themeSettingsType;
|
||||
private object _themeSettings;
|
||||
private RenderFragment _themeSettingsComponent { get; set; }
|
||||
private bool _refresh = false;
|
||||
protected Page _parent = null;
|
||||
protected Dictionary<string, string> _icons;
|
||||
@ -281,7 +272,6 @@
|
||||
}
|
||||
_effectivedate = Utilities.UtcAsLocalDate(PageState.Page.EffectiveDate);
|
||||
_expirydate = Utilities.UtcAsLocalDate(PageState.Page.ExpiryDate);
|
||||
ThemeSettings();
|
||||
_initialized = true;
|
||||
}
|
||||
else
|
||||
@ -324,7 +314,6 @@
|
||||
_themetype = (string)e.Value;
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
_containertype = _containers.First().TypeName;
|
||||
ThemeSettings();
|
||||
StateHasChanged();
|
||||
|
||||
// if theme chosen is different than default site theme, display warning message to user
|
||||
@ -334,28 +323,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void ThemeSettings()
|
||||
{
|
||||
_themeSettingsType = null;
|
||||
_themeSettingsComponent = null;
|
||||
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
|
||||
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
|
||||
{
|
||||
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
|
||||
if (_themeSettingsType != null)
|
||||
{
|
||||
_themeSettingsComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, _themeSettingsType);
|
||||
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
|
||||
builder.AddComponentReferenceCapture(2, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
_refresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SavePage()
|
||||
{
|
||||
validated = true;
|
||||
@ -385,45 +352,34 @@
|
||||
page.ParentId = Int32.Parse(_parentid);
|
||||
}
|
||||
|
||||
// path can be a link to an external url
|
||||
if (!_path.Contains("://"))
|
||||
if (string.IsNullOrEmpty(_path))
|
||||
{
|
||||
if (string.IsNullOrEmpty(_path))
|
||||
{
|
||||
_path = _name;
|
||||
}
|
||||
_path = _name;
|
||||
}
|
||||
|
||||
(_path, string parameters) = Utilities.ParsePath(_path);
|
||||
|
||||
if (_path.Contains("/"))
|
||||
if (_path.Contains("/"))
|
||||
{
|
||||
if (_path.EndsWith("/") && _path != "/")
|
||||
{
|
||||
if (_path.EndsWith("/") && _path != "/")
|
||||
{
|
||||
_path = _path.Substring(0, _path.Length - 1);
|
||||
}
|
||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||
_path = _path.Substring(0, _path.Length - 1);
|
||||
}
|
||||
if (_parentid == "-1")
|
||||
{
|
||||
page.Path = Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId);
|
||||
if (parent.Path == string.Empty)
|
||||
{
|
||||
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
}
|
||||
page.Path += parameters;
|
||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||
}
|
||||
if (_parentid == "-1")
|
||||
{
|
||||
page.Path = Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
page.Path = _path;
|
||||
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId);
|
||||
if (parent.Path == string.Empty)
|
||||
{
|
||||
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
}
|
||||
|
||||
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
@ -493,18 +449,11 @@
|
||||
await logger.LogInformation("Page Added {Page}", page);
|
||||
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl, true);
|
||||
NavigationManager.NavigateTo(NavigateUrl(page.Path), true); // redirect to page added and reload
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!page.Path.Contains("://"))
|
||||
{
|
||||
NavigationManager.NavigateTo(page.Path); // redirect to new page created
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo(NavigateUrl("admin/pages"));
|
||||
}
|
||||
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -1,5 +1,6 @@
|
||||
@namespace Oqtane.Modules.Admin.Pages
|
||||
@using Oqtane.Interfaces
|
||||
@using System.Globalization
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IPageService PageService
|
||||
@ -362,7 +363,7 @@
|
||||
_parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
|
||||
}
|
||||
_children = new List<Page>();
|
||||
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid, CultureInfo.InvariantCulture))))
|
||||
{
|
||||
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
{
|
||||
@ -380,7 +381,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_path.Contains("/") & !_path.Contains("://"))
|
||||
if (_path.Contains("/"))
|
||||
{
|
||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||
}
|
||||
@ -414,7 +415,7 @@
|
||||
_permissions = _page.PermissionList;
|
||||
|
||||
// page modules
|
||||
_pageModules = PageState.Modules.Where(m => m.PageId == _page.PageId).ToList();
|
||||
_pageModules = PageState.Modules;
|
||||
|
||||
// audit
|
||||
_createdby = _page.CreatedBy;
|
||||
@ -529,45 +530,34 @@
|
||||
_page.ParentId = Int32.Parse(_parentid);
|
||||
}
|
||||
|
||||
// path can be a link to an external url
|
||||
if (!_path.Contains("://"))
|
||||
if (string.IsNullOrEmpty(_path))
|
||||
{
|
||||
if (string.IsNullOrEmpty(_path))
|
||||
{
|
||||
_path = _name;
|
||||
}
|
||||
_path = _name;
|
||||
}
|
||||
|
||||
(_path, string parameters) = Utilities.ParsePath(_path);
|
||||
|
||||
if (_path.Contains("/"))
|
||||
if (_path.Contains("/"))
|
||||
{
|
||||
if (_path.EndsWith("/") && _path != "/")
|
||||
{
|
||||
if (_path.EndsWith("/") && _path != "/")
|
||||
{
|
||||
_path = _path.Substring(0, _path.Length - 1);
|
||||
}
|
||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||
_path = _path.Substring(0, _path.Length - 1);
|
||||
}
|
||||
if (_parentid == "-1")
|
||||
{
|
||||
_page.Path = Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
|
||||
if (parent.Path == string.Empty)
|
||||
{
|
||||
_page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
}
|
||||
_page.Path += parameters;
|
||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||
}
|
||||
if (_parentid == "-1")
|
||||
{
|
||||
_page.Path = Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_page.Path = _path;
|
||||
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
|
||||
if (parent.Path == string.Empty)
|
||||
{
|
||||
_page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
}
|
||||
|
||||
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
@ -654,18 +644,11 @@
|
||||
await logger.LogInformation("Page Saved {Page}", _page);
|
||||
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl, true);
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_page.Path.Contains("://"))
|
||||
{
|
||||
NavigationManager.NavigateTo(NavigateUrl(), true); // redirect to page being edited
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo(NavigateUrl("admin/pages"));
|
||||
}
|
||||
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -17,7 +17,7 @@
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
|
||||
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
||||
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
|
||||
<td>@(new string('-', context.Level * 2))@(context.Name)</td>
|
||||
|
@ -22,7 +22,7 @@ else
|
||||
<th>@Localizer["Order"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditProfile" /></td>
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditProfile" /></td>
|
||||
<td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.Title</td>
|
||||
|
@ -20,9 +20,9 @@ else
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td>
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td>
|
||||
<td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td>
|
||||
<td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Users" /></td>
|
||||
<td><ActionLink Action="Users" Text="Users" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Users" /></td>
|
||||
<td>@context.Name</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
|
116
Oqtane.Client/Modules/Admin/SearchResults/Index.razor
Normal file
116
Oqtane.Client/Modules/Admin/SearchResults/Index.razor
Normal file
@ -0,0 +1,116 @@
|
||||
@using Oqtane.Services
|
||||
@using System.Net
|
||||
@namespace Oqtane.Modules.Admin.SearchResults
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ISearchResultsService SearchResultsService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<div class="search-result-container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<form method="post" @formname="SearchInputForm" @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">
|
||||
<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 SearchSortDirections _searchSortDirection = SearchSortDirections.Descending; //default sort by
|
||||
private SearchSortFields _searchSortField = SearchSortFields.Relevance;
|
||||
private string _keywords;
|
||||
private bool _loading;
|
||||
private SearchResults _searchResults;
|
||||
|
||||
[SupplyParameterFromForm(FormName = "SearchInputForm")]
|
||||
public string KeyWords { get => ""; set => _keywords = value; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (PageState.QueryString.ContainsKey("q"))
|
||||
{
|
||||
_keywords = WebUtility.UrlDecode(PageState.QueryString["q"]);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_keywords))
|
||||
{
|
||||
await PerformSearch();
|
||||
}
|
||||
}
|
||||
|
||||
private void Search()
|
||||
{
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, $"page=1&q={_keywords}"));
|
||||
}
|
||||
|
||||
private async Task PerformSearch()
|
||||
{
|
||||
_loading = true;
|
||||
StateHasChanged();
|
||||
|
||||
var searchQuery = new SearchQuery
|
||||
{
|
||||
SiteId = PageState.Site.SiteId,
|
||||
Alias = PageState.Alias,
|
||||
User = PageState.User,
|
||||
Keywords = _keywords,
|
||||
SortDirection = _searchSortDirection,
|
||||
SortField = _searchSortField,
|
||||
PageIndex = 0,
|
||||
PageSize = int.MaxValue
|
||||
};
|
||||
|
||||
_searchResults = await SearchResultsService.SearchAsync(ModuleState.ModuleId, searchQuery);
|
||||
|
||||
_loading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
23
Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs
Normal file
23
Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
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 = "Display Search Results",
|
||||
Version = Constants.Version,
|
||||
Categories = "Admin",
|
||||
Resources = new List<Resource>()
|
||||
{
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -319,7 +319,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="rendermode" class="form-select" @bind="@_rendermode" required>
|
||||
<select id="rendermode" class="form-select" value="@_rendermode" @onchange="(e => RenderModeChanged(e))" required>
|
||||
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
|
||||
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
|
||||
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
|
||||
@ -337,7 +337,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="prerender" HelpText="Specifies if interactive components should prerender their output" ResourceKey="Prerender">Prerender? </Label>
|
||||
<Label Class="col-sm-3" For="prerender" HelpText="Specifies if interactive components should prerender their output on the server" ResourceKey="Prerender">Prerender: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="prerender" class="form-select" @bind="@_prerender" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
@ -572,6 +572,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderModeChanged(ChangeEventArgs e)
|
||||
{
|
||||
_rendermode = (string)e.Value;
|
||||
switch (_rendermode)
|
||||
{
|
||||
case RenderModes.Interactive:
|
||||
_prerender = "True";
|
||||
break;
|
||||
case RenderModes.Static:
|
||||
_prerender = "False";
|
||||
break;
|
||||
case RenderModes.Headless:
|
||||
_prerender = "False";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSite()
|
||||
{
|
||||
validated = true;
|
||||
|
@ -26,6 +26,12 @@
|
||||
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="process" HelpText="Indicates if the current process is 32 bit or 64 bit" ResourceKey="Process">Process: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="process" class="form-control" @bind="@_process" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -62,12 +68,6 @@
|
||||
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tickcount" HelpText="Amount Of Time The Service Has Been Available And Operational" ResourceKey="TickCount">Service Uptime: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="tickcount" class="form-control" @bind="@_tickcount" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -165,13 +165,13 @@
|
||||
private string _version = string.Empty;
|
||||
private string _clrversion = string.Empty;
|
||||
private string _osversion = string.Empty;
|
||||
private string _machinename = string.Empty;
|
||||
private string _process = string.Empty;
|
||||
private string _machinename = string.Empty;
|
||||
private string _ipaddress = string.Empty;
|
||||
private string _environment = string.Empty;
|
||||
private string _contentrootpath = string.Empty;
|
||||
private string _webrootpath = string.Empty;
|
||||
private string _servertime = string.Empty;
|
||||
private string _tickcount = string.Empty;
|
||||
private string _workingset = string.Empty;
|
||||
private string _installationid = string.Empty;
|
||||
|
||||
@ -192,13 +192,13 @@
|
||||
{
|
||||
_clrversion = systeminfo["CLRVersion"].ToString();
|
||||
_osversion = systeminfo["OSVersion"].ToString();
|
||||
_machinename = systeminfo["MachineName"].ToString();
|
||||
_process = systeminfo["Process"].ToString();
|
||||
_machinename = systeminfo["MachineName"].ToString();
|
||||
_ipaddress = systeminfo["IPAddress"].ToString();
|
||||
_environment = systeminfo["Environment"].ToString();
|
||||
_contentrootpath = systeminfo["ContentRootPath"].ToString();
|
||||
_webrootpath = systeminfo["WebRootPath"].ToString();
|
||||
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
|
||||
_tickcount = TimeSpan.FromMilliseconds(Convert.ToInt64(systeminfo["TickCount"].ToString())).ToString();
|
||||
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ else
|
||||
<th> </th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditTheme" /></td>
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditTheme" /></td>
|
||||
<td>
|
||||
@if (context.AssemblyName != Constants.ClientId)
|
||||
{
|
||||
@ -63,7 +63,6 @@ else
|
||||
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
|
||||
}
|
||||
</td>
|
||||
<td></td>
|
||||
</Row>
|
||||
</Pager>
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ else
|
||||
<th>@Localizer["Requested"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
||||
<td><ActionDialog Header="Delete Url Mapping" Message="@string.Format(Localizer["Confirm.DeleteUrlMapping"], context.Url)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUrlMapping(context))" ResourceKey="DeleteUrlMapping" /></td>
|
||||
<td>
|
||||
<a href="@Utilities.TenantUrl(PageState.Alias, context.Url)">@context.Url</a>
|
||||
|
@ -226,11 +226,6 @@
|
||||
user.Password = _password;
|
||||
user.Email = email;
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
|
||||
user.PhotoFileId = null;
|
||||
if (user.PhotoFileId == -1)
|
||||
{
|
||||
user.PhotoFileId = null;
|
||||
}
|
||||
|
||||
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
|
||||
|
||||
|
@ -32,13 +32,13 @@ else
|
||||
</Header>
|
||||
<Row>
|
||||
<td>
|
||||
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
|
||||
<ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
|
||||
</td>
|
||||
<td>
|
||||
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" />
|
||||
</td>
|
||||
<td>
|
||||
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
|
||||
<ActionLink Action="Roles" Text="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
|
||||
</td>
|
||||
<td>@context.User.Username</td>
|
||||
<td>@context.User.DisplayName</td>
|
||||
|
@ -43,7 +43,7 @@ else
|
||||
<th>@Localizer["Created"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
|
||||
<td><ActionLink Action="Detail" Text="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
|
||||
<td>@context.IPAddress</td>
|
||||
<td>
|
||||
@if (context.UserId != null)
|
||||
@ -69,14 +69,20 @@ else
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="duration" HelpText="The duration of a browsing session considered to be a distinct visit (in minutes)" ResourceKey="Duration">Session Duration: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="duration" class="form-control" type="number" min="0" step="1" @bind="@_duration" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="filter" HelpText="Comma delimited list of terms which may exist in IP addresses, user agents, or languages identifying visitors which should not be tracked" ResourceKey="Filter">Filter: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="filter" class="form-control" @bind="@_filter" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||
</div>
|
||||
@ -103,7 +109,8 @@ else
|
||||
private int _page = 1;
|
||||
private List<Visitor> _visitors;
|
||||
private string _tracking;
|
||||
private string _filter = "";
|
||||
private int _duration = 5;
|
||||
private string _filter = "";
|
||||
private int _retention = 30;
|
||||
private string _correlation = "true";
|
||||
|
||||
@ -128,7 +135,8 @@ else
|
||||
|
||||
_tracking = PageState.Site.VisitorTracking.ToString();
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
|
||||
_duration = int.Parse(SettingService.GetSetting(settings, "VisitorDuration", "5"));
|
||||
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
|
||||
_retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30"));
|
||||
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
|
||||
}
|
||||
@ -179,7 +187,8 @@ else
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
|
||||
settings = SettingService.SetSetting(settings, "VisitorDuration", _duration.ToString(), true);
|
||||
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
|
||||
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention.ToString(), true);
|
||||
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
|
@ -35,11 +35,11 @@
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
|
||||
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button>
|
||||
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,7 +54,7 @@ else
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@Header</h5>
|
||||
<form method="post" @formname="@($"ActionDialogCloseForm{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="btn-close" aria-label="Close"></button>
|
||||
</form>
|
||||
</div>
|
||||
@ -65,12 +65,12 @@ else
|
||||
@if (!string.IsNullOrEmpty(Action))
|
||||
{
|
||||
<form method="post" @formname="@($"ActionDialogConfirmForm{Id}")" @onsubmit="Confirm" data-enhance>
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button>
|
||||
</form>
|
||||
}
|
||||
<form method="post" @formname="@($"ActionDialogCancelForm{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="btn btn-secondary">@SharedLocalizer["Cancel"]</button>
|
||||
</form>
|
||||
</div>
|
||||
@ -83,13 +83,13 @@ else
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
|
||||
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form method="post" @formname="@($"ActionDialogActionForm{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@ -101,6 +101,8 @@ else
|
||||
private bool _editmode = false;
|
||||
private bool _authorized = false;
|
||||
private string _iconSpan = string.Empty;
|
||||
private string _openIconSpan = string.Empty;
|
||||
private string _openText = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string Header { get; set; } // required
|
||||
@ -138,6 +140,9 @@ else
|
||||
[Parameter]
|
||||
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
|
||||
|
||||
[Parameter]
|
||||
public bool IconOnly { get; set; } // optional - specifies only icon in opening link
|
||||
|
||||
[Parameter]
|
||||
public string Id { get; set; } // optional - specifies a unique id for the compoment - required when there are multiple component instances on a page in static rendering
|
||||
|
||||
@ -157,6 +162,8 @@ else
|
||||
{
|
||||
Text = Action;
|
||||
}
|
||||
_openText = Text;
|
||||
|
||||
if (string.IsNullOrEmpty(Class))
|
||||
{
|
||||
Class = "btn btn-success";
|
||||
@ -169,11 +176,20 @@ else
|
||||
|
||||
if (!string.IsNullOrEmpty(IconName))
|
||||
{
|
||||
if (!IconName.Contains(" "))
|
||||
if (IconOnly)
|
||||
{
|
||||
_openText = string.Empty;
|
||||
}
|
||||
|
||||
// Check if IconName starts with "oi oi-"
|
||||
bool startsWithOiOi = IconName.StartsWith("oi oi-");
|
||||
|
||||
if (!startsWithOiOi && !IconName.Contains(" "))
|
||||
{
|
||||
IconName = "oi oi-" + IconName;
|
||||
}
|
||||
_iconSpan = $"<span class=\"{IconName}\"></span> ";
|
||||
_openIconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : " ")}";
|
||||
_iconSpan = $"<span class=\"{IconName}\"></span> ";
|
||||
}
|
||||
|
||||
Text = Localize(nameof(Text), Text);
|
||||
|
@ -145,7 +145,10 @@
|
||||
|
||||
if (!string.IsNullOrEmpty(IconName))
|
||||
{
|
||||
if (!IconName.Contains(" "))
|
||||
// Check if IconName starts with "oi oi-"
|
||||
bool startsWithOiOi = IconName.StartsWith("oi oi-");
|
||||
|
||||
if (!startsWithOiOi && !IconName.Contains(" "))
|
||||
{
|
||||
IconName = "oi oi-" + IconName;
|
||||
}
|
||||
|
@ -6,23 +6,24 @@
|
||||
{
|
||||
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
||||
@((MarkupString)Message)
|
||||
@if (PageState != null)
|
||||
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
@if (Type == MessageType.Error && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
||||
}
|
||||
<form method="post" @onsubmit="DismissModal" @formname="@_formname" data-enhance>
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="btn-close" aria-label="Close"></button>
|
||||
</form>
|
||||
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
||||
}
|
||||
@if (ModuleState.RenderMode == RenderModes.Static)
|
||||
{
|
||||
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private string _message = string.Empty;
|
||||
private string _classname = string.Empty;
|
||||
private string _formname = "ModuleMessageForm";
|
||||
|
||||
[Parameter]
|
||||
public string Message { get; set; }
|
||||
@ -30,32 +31,13 @@
|
||||
[Parameter]
|
||||
public MessageType Type { get; set; }
|
||||
|
||||
public void RefreshMessage(string message, MessageType type)
|
||||
{
|
||||
Message = message;
|
||||
Type = type;
|
||||
|
||||
UpdateClassName();
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (ModuleState != null)
|
||||
{
|
||||
_formname += ModuleState.PageModuleId.ToString();
|
||||
}
|
||||
}
|
||||
[Parameter]
|
||||
public RenderModeBoundary Parent { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
UpdateClassName();
|
||||
}
|
||||
|
||||
private void UpdateClassName()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Message))
|
||||
_message = Message;
|
||||
if (!string.IsNullOrEmpty(_message))
|
||||
{
|
||||
_classname = GetMessageType(Type);
|
||||
}
|
||||
@ -82,9 +64,15 @@
|
||||
|
||||
return classname;
|
||||
}
|
||||
|
||||
private void DismissModal()
|
||||
private void CloseMessage(MouseEventArgs e)
|
||||
{
|
||||
Message = "";
|
||||
if(Parent != null)
|
||||
{
|
||||
Parent.DismissMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
@if (!string.IsNullOrEmpty(SearchProperties))
|
||||
{
|
||||
<form autocomplete="off">
|
||||
<div class="input-group my-3">
|
||||
<div class="input-group my-3 @SearchBoxClass">
|
||||
<input type="text" id="pagersearch" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
|
||||
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
|
||||
@ -23,16 +23,16 @@
|
||||
{
|
||||
<ul class="pagination justify-content-center my-2">
|
||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@if (_pages > _displayPages && _displayPages > 1)
|
||||
{
|
||||
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
}
|
||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@for (int i = _startPage; i <= _endPage; i++)
|
||||
{
|
||||
@ -40,30 +40,30 @@
|
||||
if (pager == _page)
|
||||
{
|
||||
<li class="page-item app-pager-pointer active">
|
||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="page-item app-pager-pointer">
|
||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@if (_pages > _displayPages && _displayPages > 1)
|
||||
{
|
||||
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
}
|
||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
@ -73,8 +73,8 @@
|
||||
@if (!string.IsNullOrEmpty(SearchProperties))
|
||||
{
|
||||
<form method="post" autocomplete="off" @formname="PagerForm" @onsubmit="Search" data-enhance>
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<div class="input-group my-3">
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<div class="input-group my-3 @SearchBoxClass">
|
||||
<input type="text" id="pagersearch" name="_search" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
|
||||
<button type="submit" class="btn btn-primary">@SharedLocalizer["Search"]</button>
|
||||
<a class="btn btn-secondary" href="@PageUrl(1, "")">@SharedLocalizer["Reset"]</a>
|
||||
@ -86,16 +86,16 @@
|
||||
{
|
||||
<ul class="pagination justify-content-center my-2">
|
||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@if (_pages > _displayPages && _displayPages > 1)
|
||||
{
|
||||
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
}
|
||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@for (int i = _startPage; i <= _endPage; i++)
|
||||
{
|
||||
@ -103,30 +103,30 @@
|
||||
if (pager == _page)
|
||||
{
|
||||
<li class="page-item app-pager-pointer active">
|
||||
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="page-item app-pager-pointer">
|
||||
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@if (_pages > _displayPages && _displayPages > 1)
|
||||
{
|
||||
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
}
|
||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
@ -202,16 +202,16 @@
|
||||
{
|
||||
<ul class="pagination justify-content-center my-2">
|
||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@if (_pages > _displayPages && _displayPages > 1)
|
||||
{
|
||||
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
}
|
||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@for (int i = _startPage; i <= _endPage; i++)
|
||||
{
|
||||
@ -219,30 +219,30 @@
|
||||
if (pager == _page)
|
||||
{
|
||||
<li class="page-item app-pager-pointer active">
|
||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="page-item app-pager-pointer">
|
||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@if (_pages > _displayPages && _displayPages > 1)
|
||||
{
|
||||
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
}
|
||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
@ -250,16 +250,16 @@
|
||||
{
|
||||
<ul class="pagination justify-content-center my-2">
|
||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@if (_pages > _displayPages && _displayPages > 1)
|
||||
{
|
||||
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
}
|
||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@for (int i = _startPage; i <= _endPage; i++)
|
||||
{
|
||||
@ -267,30 +267,30 @@
|
||||
if (pager == _page)
|
||||
{
|
||||
<li class="page-item app-pager-pointer active">
|
||||
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="page-item app-pager-pointer">
|
||||
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@if (_pages > _displayPages && _displayPages > 1)
|
||||
{
|
||||
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
}
|
||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||
<a class="page-link shadow-none" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
@ -359,6 +359,9 @@
|
||||
[Parameter]
|
||||
public string SearchProperties { get; set; } // comma delimited list of property names to include in search
|
||||
|
||||
[Parameter]
|
||||
public string SearchBoxClass { get; set; } // class for Search box
|
||||
|
||||
[Parameter]
|
||||
public string Parameters { get; set; } // optional - querystring parameters in the form of "id=x&name=y" used in static render mode
|
||||
|
||||
|
@ -4,11 +4,11 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.Modules.Controls
|
||||
{
|
||||
public class RichTextEditorInterop
|
||||
public class QuillEditorInterop
|
||||
{
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
|
||||
public RichTextEditorInterop(IJSRuntime jsRuntime)
|
||||
public QuillEditorInterop(IJSRuntime jsRuntime)
|
||||
{
|
||||
_jsRuntime = jsRuntime;
|
||||
}
|
||||
@ -105,13 +105,25 @@ namespace Oqtane.Modules.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public Task InsertImage(ElementReference quillElement, string imageUrl, string altText)
|
||||
public ValueTask<int> GetCurrentCursor(ElementReference quillElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _jsRuntime.InvokeAsync<int>("Oqtane.RichTextEditor.getCurrentCursor", quillElement);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ValueTask<int>(Task.FromResult(0));
|
||||
}
|
||||
}
|
||||
|
||||
public Task InsertImage(ElementReference quillElement, string imageUrl, string altText, int editorIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
_jsRuntime.InvokeAsync<object>(
|
||||
"Oqtane.RichTextEditor.insertQuillImage",
|
||||
quillElement, imageUrl, altText);
|
||||
quillElement, imageUrl, altText, editorIndex);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch
|
365
Oqtane.Client/Modules/Controls/QuillTextEditor.razor
Normal file
365
Oqtane.Client/Modules/Controls/QuillTextEditor.razor
Normal file
@ -0,0 +1,365 @@
|
||||
@namespace Oqtane.Modules.Controls
|
||||
@inherits ModuleControlBase
|
||||
@implements ITextEditor
|
||||
@inject IStringLocalizer<QuillTextEditor> Localizer
|
||||
|
||||
<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 (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>
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
|
||||
private QuillEditorInterop interop;
|
||||
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]
|
||||
public bool AllowFileManagement{ get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool AllowRichText { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
public bool AllowRawHtml { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
public bool ReadOnly { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Placeholder { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Theme { get; set; } = "snow";
|
||||
|
||||
[Parameter]
|
||||
public string DebugLevel { get; set; } = "info";
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ToolbarContent { 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()
|
||||
{
|
||||
if (!AllowRichText)
|
||||
{
|
||||
_activetab = "Raw";
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool 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 Initialize(string content)
|
||||
{
|
||||
_richhtml = content;
|
||||
_rawhtml = content;
|
||||
_originalrawhtml = _rawhtml; // preserve for comparison later
|
||||
_originalrichhtml = "";
|
||||
_richhtml = content;
|
||||
_contentchanged = content != _originalrawhtml;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,120 +1,22 @@
|
||||
@using System.Text.RegularExpressions
|
||||
@using Microsoft.AspNetCore.Components.Rendering
|
||||
@using Microsoft.Extensions.DependencyInjection
|
||||
@namespace Oqtane.Modules.Controls
|
||||
@inherits ModuleControlBase
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<RichTextEditor> Localizer
|
||||
|
||||
<div class="row" style="margin-bottom: 50px;">
|
||||
<div class="col">
|
||||
<TabStrip ActiveTab="@_activetab">
|
||||
@if (AllowRichText)
|
||||
{
|
||||
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
|
||||
@if (_richfilemanager)
|
||||
{
|
||||
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
|
||||
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
||||
<br />
|
||||
}
|
||||
<div class="d-flex justify-content-center mb-2">
|
||||
@if (AllowFileManagement)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
|
||||
}
|
||||
@if (_richfilemanager)
|
||||
{
|
||||
@((MarkupString)" ")
|
||||
<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="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
|
||||
}
|
||||
</TabPanel>
|
||||
}
|
||||
</TabStrip>
|
||||
@_textEditorComponent
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private ElementReference _editorElement;
|
||||
private ElementReference _toolBar;
|
||||
private bool _richfilemanager = false;
|
||||
private FileManager _fileManager;
|
||||
private string _richhtml = string.Empty;
|
||||
private string _originalrichhtml = string.Empty;
|
||||
private bool _rawfilemanager = false;
|
||||
private string _rawhtml = string.Empty;
|
||||
private string _originalrawhtml = string.Empty;
|
||||
private string _message = string.Empty;
|
||||
private string _activetab = "Rich";
|
||||
private ITextEditorProvider _textEditorProvider;
|
||||
private RenderFragment _textEditorComponent;
|
||||
private ITextEditor _textEditor;
|
||||
|
||||
[Parameter]
|
||||
public string Content { get; set; }
|
||||
@ -123,7 +25,7 @@
|
||||
public bool ReadOnly { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public string Placeholder { get; set; } = "Enter Your Content...";
|
||||
public string Placeholder { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool AllowFileManagement { get; set; } = true;
|
||||
@ -139,152 +41,114 @@
|
||||
public RenderFragment ToolbarContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Theme { get; set; } = "snow";
|
||||
public string Theme { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string DebugLevel { get; set; } = "info";
|
||||
public string DebugLevel { get; set; }
|
||||
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
public override List<Resource> Resources { get; set; } = new List<Resource>();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" },
|
||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js" },
|
||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js" }
|
||||
};
|
||||
_textEditorProvider = await GetTextEditorProvider();
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
_richhtml = Content;
|
||||
_rawhtml = Content;
|
||||
_originalrawhtml = _rawhtml; // preserve for comparison later
|
||||
_originalrichhtml = "";
|
||||
|
||||
// Quill wraps content in <p> tags which can be used as a signal to set the active tab
|
||||
if (!string.IsNullOrEmpty(Content) && !Content.StartsWith("<p>") && AllowRawHtml)
|
||||
_textEditorComponent = (builder) =>
|
||||
{
|
||||
_activetab = "Raw";
|
||||
}
|
||||
CreateTextEditor(builder);
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
|
||||
if (AllowRichText)
|
||||
if(_textEditor != null)
|
||||
{
|
||||
var interop = new RichTextEditorInterop(JSRuntime);
|
||||
|
||||
if (firstRender)
|
||||
{
|
||||
await interop.CreateEditor(
|
||||
_editorElement,
|
||||
_toolBar,
|
||||
ReadOnly,
|
||||
Placeholder,
|
||||
Theme,
|
||||
DebugLevel);
|
||||
}
|
||||
|
||||
await interop.LoadEditorContent(_editorElement, _richhtml);
|
||||
|
||||
if (string.IsNullOrEmpty(_originalrichhtml))
|
||||
{
|
||||
// preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor)
|
||||
_originalrichhtml = await interop.GetHtml(_editorElement);
|
||||
}
|
||||
_textEditor.Initialize(Content);
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseRichFileManager()
|
||||
{
|
||||
_richfilemanager = false;
|
||||
_message = string.Empty;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public void CloseRawFileManager()
|
||||
{
|
||||
_rawfilemanager = false;
|
||||
_message = string.Empty;
|
||||
StateHasChanged();
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
public async Task<string> GetHtml()
|
||||
{
|
||||
// evaluate raw html content as first priority
|
||||
if (_rawhtml != _originalrawhtml)
|
||||
return await _textEditor.GetContent();
|
||||
}
|
||||
|
||||
private void CreateTextEditor(RenderTreeBuilder builder)
|
||||
{
|
||||
if(_textEditorProvider != null)
|
||||
{
|
||||
return _rawhtml;
|
||||
}
|
||||
else
|
||||
{
|
||||
var richhtml = "";
|
||||
if (AllowRichText)
|
||||
var editorType = Type.GetType(_textEditorProvider.EditorType);
|
||||
if (editorType != null)
|
||||
{
|
||||
// return rich text content if it has changed
|
||||
var interop = new RichTextEditorInterop(JSRuntime);
|
||||
richhtml = await interop.GetHtml(_editorElement);
|
||||
builder.OpenComponent(0, editorType);
|
||||
|
||||
//set editor parameters if available.
|
||||
var attributes = new Dictionary<string, object>
|
||||
{
|
||||
{ "AllowFileManagement", AllowFileManagement },
|
||||
{ "AllowRichText", AllowRichText },
|
||||
{ "AllowRawHtml", AllowRawHtml },
|
||||
{ "ReadOnly", ReadOnly }
|
||||
};
|
||||
|
||||
if(!string.IsNullOrEmpty(Theme))
|
||||
{
|
||||
attributes.Add("Theme", Theme);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(DebugLevel))
|
||||
{
|
||||
attributes.Add("DebugLevel", DebugLevel);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(Placeholder))
|
||||
{
|
||||
attributes.Add("Placeholder", Placeholder);
|
||||
}
|
||||
if(ToolbarContent != null)
|
||||
{
|
||||
attributes.Add("ToolbarContent", ToolbarContent);
|
||||
}
|
||||
|
||||
var index = 1;
|
||||
foreach(var name in attributes.Keys)
|
||||
{
|
||||
if (editorType.GetProperty(name) != null)
|
||||
{
|
||||
builder.AddAttribute(index++, name, attributes[name]);
|
||||
}
|
||||
}
|
||||
|
||||
builder.AddComponentReferenceCapture(index, (c) =>
|
||||
{
|
||||
_textEditor = (ITextEditor)c;
|
||||
});
|
||||
builder.CloseComponent();
|
||||
}
|
||||
// rich text value will only be blank if AllowRichText is disabled or the JS Interop method failed
|
||||
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml) && !string.IsNullOrEmpty(_originalrichhtml))
|
||||
{
|
||||
return richhtml;
|
||||
}
|
||||
else
|
||||
{
|
||||
// return original raw html content
|
||||
return _originalrawhtml;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InsertRichImage()
|
||||
{
|
||||
_message = string.Empty;
|
||||
if (_richfilemanager)
|
||||
{
|
||||
var file = _fileManager.GetFile();
|
||||
if (file != null)
|
||||
{
|
||||
var interop = new RichTextEditorInterop(JSRuntime);
|
||||
await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name));
|
||||
_richhtml = await interop.GetHtml(_editorElement);
|
||||
_richfilemanager = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = Localizer["Message.Require.Image"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_richfilemanager = true;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
private async Task<ITextEditorProvider> GetTextEditorProvider()
|
||||
{
|
||||
const string DefaultEditorName = "Quill";
|
||||
|
||||
public async Task InsertRawImage()
|
||||
{
|
||||
_message = string.Empty;
|
||||
if (_rawfilemanager)
|
||||
{
|
||||
var file = _fileManager.GetFile();
|
||||
if (file != null)
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
int pos = await interop.GetCaretPosition("rawhtmleditor");
|
||||
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\" class=\"img-fluid\">";
|
||||
_rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
|
||||
_rawfilemanager = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = Localizer["Message.Require.Image"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_rawfilemanager = true;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
var editorName = await GetTextEditorName(DefaultEditorName);
|
||||
var editorProviders = ServiceProvider.GetServices<ITextEditorProvider>();
|
||||
var editorProvider = editorProviders.FirstOrDefault(i => i.Name == editorName);
|
||||
if(editorProvider == null)
|
||||
{
|
||||
editorProvider = editorProviders.FirstOrDefault(i => i.Name == DefaultEditorName);
|
||||
}
|
||||
|
||||
return editorProvider;
|
||||
}
|
||||
|
||||
private async Task<string> GetTextEditorName(string defaultName)
|
||||
{
|
||||
const string EditorSettingName = "TextEditor";
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
return SettingService.GetSetting(settings, EditorSettingName, defaultName);
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-content @TabContentClass">
|
||||
<br />
|
||||
@ChildContent
|
||||
</div>
|
||||
@ -47,6 +47,9 @@
|
||||
[Parameter]
|
||||
public string Id { get; set; } // optional - used to uniquely identify an instance of a tab strip component (will be set automatically if no value provided)
|
||||
|
||||
[Parameter]
|
||||
public string TabContentClass { get; set; } // optional - to extend the TabContent div.
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Id))
|
||||
|
@ -37,6 +37,10 @@
|
||||
content = htmltext.Content;
|
||||
content = Utilities.FormatContent(content, PageState.Alias, "render");
|
||||
}
|
||||
else
|
||||
{
|
||||
content = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -52,6 +52,8 @@ namespace Oqtane.Modules
|
||||
|
||||
public virtual string RenderMode { get { return RenderModes.Interactive; } } // interactive by default
|
||||
|
||||
public virtual bool? Prerender { get { return null; } } // allows the Site Prerender property to be overridden
|
||||
|
||||
// url parameters
|
||||
public virtual string UrlParametersTemplate { get; set; }
|
||||
|
||||
@ -276,7 +278,6 @@ namespace Oqtane.Modules
|
||||
|
||||
public void AddModuleMessage(string message, MessageType type, string position)
|
||||
{
|
||||
ClearModuleMessage();
|
||||
RenderModeBoundary.AddModuleMessage(message, type, position);
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>5.1.0</Version>
|
||||
<Version>5.2.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -12,7 +12,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
@ -22,9 +22,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
11
Oqtane.Client/Providers/QuillTextEditorProvider.cs
Normal file
11
Oqtane.Client/Providers/QuillTextEditorProvider.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Oqtane.Interfaces;
|
||||
|
||||
namespace Oqtane.Providers
|
||||
{
|
||||
public class QuillTextEditorProvider : ITextEditorProvider
|
||||
{
|
||||
public string Name => "Quill";
|
||||
|
||||
public string EditorType => "Oqtane.Modules.Controls.QuillTextEditor, Oqtane.Client";
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@ -153,7 +153,7 @@
|
||||
<data name="Integrated" xml:space="preserve">
|
||||
<value>Integrated</value>
|
||||
</data>
|
||||
<data name="Encryption,Text" xml:space="preserve">
|
||||
<data name="Encryption.Text" xml:space="preserve">
|
||||
<value>Encryption:</value>
|
||||
</data>
|
||||
<data name="Encryption.HelpText" xml:space="preserve">
|
||||
|
@ -195,4 +195,7 @@
|
||||
<data name="Folder Management" xml:space="preserve">
|
||||
<value>Folder Management</value>
|
||||
</data>
|
||||
<data name="Message.Folder.Duplicate" xml:space="preserve">
|
||||
<value>Folder Name Specified Already Exists In Parent</value>
|
||||
</data>
|
||||
</root>
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@ -210,4 +210,16 @@
|
||||
<data name="Success.SaveSiteSettings" xml:space="preserve">
|
||||
<value>Settings Saved Successfully</value>
|
||||
</data>
|
||||
<data name="DeleteLogs.Header" xml:space="preserve">
|
||||
<value>Clear Events</value>
|
||||
</data>
|
||||
<data name="DeleteLogs.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Remove All Log Events?</value>
|
||||
</data>
|
||||
<data name="DeleteLogs.Text" xml:space="preserve">
|
||||
<value>Clear Events</value>
|
||||
</data>
|
||||
<data name="Error.DeleteLogs" xml:space="preserve">
|
||||
<value>Error Deleting Log Events</value>
|
||||
</data>
|
||||
</root>
|
@ -156,7 +156,7 @@
|
||||
<data name="Module.Text" xml:space="preserve">
|
||||
<value>Module:</value>
|
||||
</data>
|
||||
<data name="Module Settings" xml:space="preserve">
|
||||
<data name="ModuleSettings.Heading" xml:space="preserve">
|
||||
<value>Module Settings</value>
|
||||
</data>
|
||||
<data name="Pane.HelpText" xml:space="preserve">
|
||||
@ -177,4 +177,16 @@
|
||||
<data name="ExpiryDate.Text" xml:space="preserve">
|
||||
<value>Expiry Date: </value>
|
||||
</data>
|
||||
<data name="Permissions.Text" xml:space="preserve">
|
||||
<value>Permissions</value>
|
||||
</data>
|
||||
<data name="Permissions.Heading" xml:space="preserve">
|
||||
<value>Permissions</value>
|
||||
</data>
|
||||
<data name="ContainerSettings.Heading" xml:space="preserve">
|
||||
<value>Container Settings</value>
|
||||
</data>
|
||||
<data name="ModuleSettings.Title" xml:space="preserve">
|
||||
<value>Module Settings</value>
|
||||
</data>
|
||||
</root>
|
@ -147,4 +147,7 @@
|
||||
<data name="Title" xml:space="preserve">
|
||||
<value>Title</value>
|
||||
</data>
|
||||
<data name="Detail.Text" xml:space="preserve">
|
||||
<value>Detail</value>
|
||||
</data>
|
||||
</root>
|
129
Oqtane.Client/Resources/Modules/Admin/SearchResults/Index.resx
Normal file
129
Oqtane.Client/Resources/Modules/Admin/SearchResults/Index.resx
Normal file
@ -0,0 +1,129 @@
|
||||
<?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="NoResult" xml:space="preserve">
|
||||
<value>No Content Matches The Criteria Provided</value>
|
||||
</data>
|
||||
<data name="SearchLabel" xml:space="preserve">
|
||||
<value>Search:</value>
|
||||
</data>
|
||||
<data name="SearchPlaceholder" xml:space="preserve">
|
||||
<value>Search</value>
|
||||
</data>
|
||||
</root>
|
@ -277,10 +277,10 @@
|
||||
<value>UI Component Settings</value>
|
||||
</data>
|
||||
<data name="Prerender.HelpText" xml:space="preserve">
|
||||
<value>Specifies if interactive components should prerender their output</value>
|
||||
<value>Specifies if interactive components should prerender their output on the server</value>
|
||||
</data>
|
||||
<data name="Prerender.Text" xml:space="preserve">
|
||||
<value>Prerender? </value>
|
||||
<value>Prerender: </value>
|
||||
</data>
|
||||
<data name="RenderMode.HelpText" xml:space="preserve">
|
||||
<value>The default render mode for the site</value>
|
||||
|
@ -255,12 +255,6 @@
|
||||
<data name="MachineName.Text" xml:space="preserve">
|
||||
<value>Machine Name:</value>
|
||||
</data>
|
||||
<data name="TickCount.HelpText" xml:space="preserve">
|
||||
<value>Amount Of Time The Service Has Been Available And Operational</value>
|
||||
</data>
|
||||
<data name="TickCount.Text" xml:space="preserve">
|
||||
<value>Service Uptime:</value>
|
||||
</data>
|
||||
<data name="WebRootPath.HelpText" xml:space="preserve">
|
||||
<value>Server Web Root Path</value>
|
||||
</data>
|
||||
@ -294,4 +288,10 @@
|
||||
<data name="Error.ClearLog" xml:space="preserve">
|
||||
<value>Ann Error Occurred Clearing The System Log</value>
|
||||
</data>
|
||||
<data name="Process.HelpText" xml:space="preserve">
|
||||
<value>Indicates if the current process is 32 bit or 64 bit</value>
|
||||
</data>
|
||||
<data name="Process.Text" xml:space="preserve">
|
||||
<value>Process: </value>
|
||||
</data>
|
||||
</root>
|
@ -159,4 +159,7 @@
|
||||
<data name="Url" xml:space="preserve">
|
||||
<value>Url</value>
|
||||
</data>
|
||||
<data name="Edit.Text" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
</root>
|
@ -184,7 +184,7 @@
|
||||
<value>Number of days of visitor activity to retain</value>
|
||||
</data>
|
||||
<data name="Retention.Text" xml:space="preserve">
|
||||
<value>Retention (Days):</value>
|
||||
<value>Retention:</value>
|
||||
</data>
|
||||
<data name="Correlation.HelpText" xml:space="preserve">
|
||||
<value>Indicate if new visitors to this site should be correlated based on their IP Address</value>
|
||||
@ -192,4 +192,10 @@
|
||||
<data name="Correlation.Text" xml:space="preserve">
|
||||
<value>Correlate Visitors?</value>
|
||||
</data>
|
||||
<data name="Duration.HelpText" xml:space="preserve">
|
||||
<value>The duration of a browsing session considered to be a distinct visit (in minutes)</value>
|
||||
</data>
|
||||
<data name="Duration.Text" xml:space="preserve">
|
||||
<value>Session Duration:</value>
|
||||
</data>
|
||||
</root>
|
@ -117,13 +117,16 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="InsertImage" xml:space="preserve">
|
||||
<value>Insert Image</value>
|
||||
</data>
|
||||
<data name="Close" xml:space="preserve">
|
||||
<value>Close</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>
|
||||
</root>
|
@ -453,4 +453,10 @@
|
||||
<data name="RenderModeStatic" xml:space="preserve">
|
||||
<value>Static</value>
|
||||
</data>
|
||||
<data name="Disabled" xml:space="preserve">
|
||||
<value>Disabled</value>
|
||||
</data>
|
||||
<data name="Enabled" xml:space="preserve">
|
||||
<value>Enabled</value>
|
||||
</data>
|
||||
</root>
|
@ -198,4 +198,7 @@
|
||||
<data name="LocationTop" xml:space="preserve">
|
||||
<value>Top</value>
|
||||
</data>
|
||||
<data name="Module.CopyExisting" xml:space="preserve">
|
||||
<value>Copy Existing Module</value>
|
||||
</data>
|
||||
</root>
|
150
Oqtane.Client/Resources/Themes/Controls/ModuleActionsBase.resx
Normal file
150
Oqtane.Client/Resources/Themes/Controls/ModuleActionsBase.resx
Normal file
@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="DeleteModule" xml:space="preserve">
|
||||
<value>Delete Module</value>
|
||||
</data>
|
||||
<data name="ExportContent" xml:space="preserve">
|
||||
<value>Export Content</value>
|
||||
</data>
|
||||
<data name="ImportContent" xml:space="preserve">
|
||||
<value>Import Content</value>
|
||||
</data>
|
||||
<data name="ManageSettings" xml:space="preserve">
|
||||
<value>Manage Settings</value>
|
||||
</data>
|
||||
<data name="MoveDown" xml:space="preserve">
|
||||
<value>Move Down</value>
|
||||
</data>
|
||||
<data name="MoveToBottom" xml:space="preserve">
|
||||
<value>Move To Bottom</value>
|
||||
</data>
|
||||
<data name="MoveToTop" xml:space="preserve">
|
||||
<value>Move To Top</value>
|
||||
</data>
|
||||
<data name="MoveUp" xml:space="preserve">
|
||||
<value>MoveUp</value>
|
||||
</data>
|
||||
<data name="PublishModule" xml:space="preserve">
|
||||
<value>Publish Module</value>
|
||||
</data>
|
||||
<data name="UnpublishModule" xml:space="preserve">
|
||||
<value>Unpublish Module</value>
|
||||
</data>
|
||||
</root>
|
126
Oqtane.Client/Resources/Themes/Controls/Search.resx
Normal file
126
Oqtane.Client/Resources/Themes/Controls/Search.resx
Normal file
@ -0,0 +1,126 @@
|
||||
<?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="Search" xml:space="preserve">
|
||||
<value>Search</value>
|
||||
</data>
|
||||
<data name="SearchPlaceHolder" xml:space="preserve">
|
||||
<value>Search</value>
|
||||
</data>
|
||||
</root>
|
@ -29,6 +29,13 @@ namespace Oqtane.Services
|
||||
/// <returns></returns>
|
||||
Task<Log> GetLogAsync(int logId);
|
||||
|
||||
/// <summary>
|
||||
/// Clear the entire logs of the given site.
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteLogsAsync(int siteId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new log entry
|
||||
/// </summary>
|
||||
|
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> SearchAsync(int moduleId, SearchQuery searchQuery);
|
||||
}
|
||||
}
|
@ -46,6 +46,14 @@ namespace Oqtane.Services
|
||||
/// <returns></returns>
|
||||
Task DeleteSiteAsync(int siteId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of modules
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="pageId"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<Module>> GetModulesAsync(int siteId, int pageId);
|
||||
|
||||
[PrivateApi]
|
||||
[Obsolete("This method is deprecated.", false)]
|
||||
void SetAlias(Alias alias);
|
||||
|
@ -35,6 +35,11 @@ namespace Oqtane.Services
|
||||
return await GetJsonAsync<Log>($"{Apiurl}/{logId}");
|
||||
}
|
||||
|
||||
public async Task DeleteLogsAsync(int siteId)
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}?siteid={siteId}");
|
||||
}
|
||||
|
||||
public async Task Log(int? pageId, int? moduleId, int? userId, string category, string feature, LogFunction function, LogLevel level, Exception exception, string message, params object[] args)
|
||||
{
|
||||
await Log(null, pageId, moduleId, userId, category, feature, function, level, exception, message, args);
|
||||
|
@ -33,7 +33,7 @@ namespace Oqtane.Services
|
||||
|
||||
public async Task<Profile> UpdateProfileAsync(Profile profile)
|
||||
{
|
||||
return await PutJsonAsync<Profile>($"{Apiurl}/{profile.SiteId}", profile);
|
||||
return await PutJsonAsync<Profile>($"{Apiurl}/{profile.ProfileId}", profile);
|
||||
}
|
||||
public async Task DeleteProfileAsync(int profileId)
|
||||
{
|
||||
|
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> SearchAsync(int moduleId, SearchQuery searchQuery)
|
||||
{
|
||||
return await PostJsonAsync<SearchQuery, SearchResults>(CreateAuthorizationPolicyUrl(ApiUrl, EntityNames.Module, moduleId), searchQuery);
|
||||
}
|
||||
}
|
||||
}
|
@ -41,6 +41,11 @@ namespace Oqtane.Services
|
||||
await DeleteAsync($"{Apiurl}/{siteId}");
|
||||
}
|
||||
|
||||
public async Task<List<Module>> GetModulesAsync(int siteId, int pageId)
|
||||
{
|
||||
return await GetJsonAsync<List<Module>>($"{Apiurl}/modules/{siteId}/{pageId}");
|
||||
}
|
||||
|
||||
[Obsolete("This method is deprecated.", false)]
|
||||
public void SetAlias(Alias alias)
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Shared;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
@ -18,7 +19,7 @@ namespace Oqtane.Services
|
||||
|
||||
public async Task<List<Visitor>> GetVisitorsAsync(int siteId, DateTime fromDate)
|
||||
{
|
||||
List<Visitor> visitors = await GetJsonAsync<List<Visitor>>($"{Apiurl}?siteid={siteId}&fromdate={fromDate.ToString("dd-MMM-yyyy")}");
|
||||
List<Visitor> visitors = await GetJsonAsync<List<Visitor>>($"{Apiurl}?siteid={siteId}&fromdate={fromDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}");
|
||||
return visitors.OrderByDescending(item => item.VisitedOn).ToList();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
@namespace Oqtane.Themes
|
||||
@inherits ContainerBase
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<div class="app-admin-modal">
|
||||
<div class="modal" tabindex="-1" role="dialog">
|
||||
@ -8,10 +7,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><ModuleTitle /></h5>
|
||||
<form method="post" class="app-form-inline" @formname="AdminContainerForm" @onsubmit="@CloseModal" data-enhance>
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="btn-close" aria-label="Close"></button>
|
||||
</form>
|
||||
<a href="@_url" class="btn-close" aria-label="Close"></a>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ModuleInstance />
|
||||
@ -22,9 +18,11 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private void CloseModal()
|
||||
{
|
||||
NavigationManager.NavigateTo((!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl());
|
||||
private string _url;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
_url = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,13 +9,20 @@
|
||||
<div class="row flex-xl-nowrap gx-0">
|
||||
<div class="sidebar">
|
||||
<nav class="navbar">
|
||||
<Logo /><Menu Orientation="Vertical" />
|
||||
<Logo />
|
||||
<Menu Orientation="Vertical" />
|
||||
<Search CssClass="px-3 d-none d-md-block" />
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="main g-0">
|
||||
<div class="top-row px-4">
|
||||
<div class="ms-auto"><UserProfile /> <Login /> <ControlPanel LanguageDropdownAlignment="right" /></div>
|
||||
<div class="ms-auto">
|
||||
<Search CssClass="me-3 text-center bg-primary d-inline-block d-md-none" Position="Top" />
|
||||
<UserProfile />
|
||||
<Login />
|
||||
<ControlPanel LanguageDropdownAlignment="right" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row px-4">
|
||||
@ -31,9 +38,13 @@
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
{
|
||||
// obtained from https://cdnjs.com/libraries
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css", Integrity = "sha512-b2QcS5SsA8tZodcDtGRELiGv5SaKSk1vDHDaQRda0htPYWZ6046lr3kJ5bAAQdpV2mmA/4v0wQF9MyU6/pDIAg==", CrossOrigin = "anonymous" },
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css",
|
||||
Integrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==",
|
||||
CrossOrigin = "anonymous" },
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
|
||||
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js", Integrity = "sha512-X/YkDZyjTf4wyc2Vy16YGCPHwAY8rZJY+POgokZjQB2mhIRFJCckEGc6YyX9eNsPfn0PzThEuNs+uaomE5CO6A==", CrossOrigin = "anonymous", Location = ResourceLocation.Body }
|
||||
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js",
|
||||
Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==",
|
||||
CrossOrigin = "anonymous", Location = ResourceLocation.Body },
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,6 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<ModuleActionsInteractive PageState="@PageState" ModuleState="@ModuleState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" />
|
||||
<ModuleActionsInteractive PageState="@PageState" ModuleState="@ModuleState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, false)" />
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.UI;
|
||||
using System.Net;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
// ReSharper disable UnassignedGetOnlyAutoProperty
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
@ -20,6 +22,7 @@ namespace Oqtane.Themes.Controls
|
||||
[Inject] public NavigationManager NavigationManager { get; set; }
|
||||
[Inject] public IPageModuleService PageModuleService { get; set; }
|
||||
[Inject] public IModuleService ModuleService { get; set; }
|
||||
[Inject] public IStringLocalizer<ModuleActionsBase> Localizer { get; set; }
|
||||
|
||||
[Parameter] public PageState PageState { get; set; }
|
||||
[Parameter] public Module ModuleState { get; set; }
|
||||
@ -37,30 +40,30 @@ namespace Oqtane.Themes.Controls
|
||||
|
||||
if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
|
||||
{
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.Cog, Name = "Manage Settings", Action = async (u, m) => await Settings(u, m) });
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.Cog, Name = Localizer["ManageSettings"], Action = async (u, m) => await Settings(u, m) });
|
||||
|
||||
if (UserSecurity.ContainsRole(ModuleState.PermissionList, PermissionNames.View, RoleNames.Everyone))
|
||||
{
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.CircleX, Name = "Unpublish Module", Action = async (s, m) => await Unpublish(s, m) });
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.CircleX, Name = Localizer["UnpublishModule"], Action = async (s, m) => await Unpublish(s, m) });
|
||||
}
|
||||
else
|
||||
{
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.CircleCheck, Name = "Publish Module", Action = async (s, m) => await Publish(s, m) });
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.CircleCheck, Name = Localizer["PublishModule"], Action = async (s, m) => await Publish(s, m) });
|
||||
}
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.Trash, Name = "Delete Module", Action = async (u, m) => await DeleteModule(u, m) });
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.Trash, Name = Localizer["DeleteModule"], Action = async (u, m) => await DeleteModule(u, m) });
|
||||
|
||||
if (ModuleState.ModuleDefinition != null && ModuleState.ModuleDefinition.IsPortable)
|
||||
{
|
||||
actionList.Add(new ActionViewModel { Name = "" });
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.CloudUpload, Name = "Import Content", Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Import") });
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.CloudDownload, Name = "Export Content", Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Export") });
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.CloudUpload, Name = Localizer["ImportContent"], Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Import") });
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.CloudDownload, Name = Localizer["ExportContent"], Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Export") });
|
||||
}
|
||||
|
||||
actionList.Add(new ActionViewModel { Name = "" });
|
||||
|
||||
if (ModuleState.PaneModuleIndex > 0)
|
||||
{
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.DataTransferUpload, Name = "Move To Top", Action = async (s, m) => await MoveTop(s, m) });
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.DataTransferUpload, Name = Localizer["MoveToTop"], Action = async (s, m) => await MoveTop(s, m) });
|
||||
}
|
||||
|
||||
if (ModuleState.PaneModuleIndex > 0)
|
||||
|
@ -6,13 +6,13 @@
|
||||
|
||||
@if (ShowLanguageSwitcher)
|
||||
{
|
||||
<LanguageSwitcher DropdownAlignment="@LanguageDropdownAlignment" />
|
||||
<LanguageSwitcher ButtonClass="@ButtonClass" DropdownAlignment="@LanguageDropdownAlignment" />
|
||||
}
|
||||
|
||||
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
|
||||
{
|
||||
<form method="post" class="app-form-inline" @formname="EditModeForm" @onsubmit="@(async () => await ToggleEditMode(PageState.EditMode))" data-enhance>
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
@if (PageState.EditMode)
|
||||
{
|
||||
<button type="submit" class="btn @ButtonClass active" aria-pressed="true" autocomplete="off">
|
||||
@ -36,7 +36,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<ControlPanelInteractive PageState="@PageState" SiteState="@SiteState" ButtonClass="@ButtonClass" ContainerClass="@ContainerClass" HeaderClass="@HeaderClass" BodyClass="@BodyClass" ShowLanguageSwitcher="@ShowLanguageSwitcher" LanguageDropdownAlignment="@LanguageDropdownAlignment" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" />
|
||||
<ControlPanelInteractive PageState="@PageState" SiteState="@SiteState" ButtonClass="@ButtonClass" ContainerClass="@ContainerClass" HeaderClass="@HeaderClass" BodyClass="@BodyClass" ShowLanguageSwitcher="@ShowLanguageSwitcher" LanguageDropdownAlignment="@LanguageDropdownAlignment" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, false)" />
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId))
|
||||
foreach (var module in PageState.Modules)
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, module.PermissionList))
|
||||
{
|
||||
|
@ -15,6 +15,7 @@
|
||||
@inject ILogService LoggingService
|
||||
@inject IStringLocalizer<ControlPanelInteractive> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject IServiceProvider ServiceProvider
|
||||
|
||||
<button type="button" class="btn @ButtonClass ms-1" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel" @onclick="ClearMessage">
|
||||
<span class="oi oi-cog"></span>
|
||||
@ -93,9 +94,13 @@
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<label for="Module" class="control-label">@Localizer["Module.Manage"]</label>
|
||||
<select class="form-select" @bind="@_moduleType">
|
||||
<select class="form-select" @onchange="(e => ModuleTypeChanged(e))">
|
||||
<option value="new">@Localizer["Module.AddNew"]</option>
|
||||
<option value="existing">@Localizer["Module.AddExisting"]</option>
|
||||
@if (PageState.Page.UserId == null)
|
||||
{
|
||||
<option value="add">@Localizer["Module.AddExisting"]</option>
|
||||
<option value="copy">@Localizer["Module.CopyExisting"]</option>
|
||||
}
|
||||
</select>
|
||||
@if (_moduleType == "new")
|
||||
{
|
||||
@ -138,7 +143,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<select class="form-select mt-1" @onchange="(e => PageChanged(e))">
|
||||
<select class="form-select mt-1" value="@_pageId" @onchange="(e => PageChanged(e))">
|
||||
<option value="-"><@Localizer["Page.Select"]></option>
|
||||
@foreach (Page p in _pages)
|
||||
{
|
||||
@ -211,7 +216,7 @@
|
||||
|
||||
<div class="row d-flex">
|
||||
<div class="col">
|
||||
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-secondary col-12" @onclick=@(async () => await LogoutUser())>@Localizer["Logout"]</button>
|
||||
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-secondary col-12 mt-2" @onclick=@(async () => await LogoutUser())>@Localizer["Logout"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -291,7 +296,7 @@
|
||||
_containerType = PageState.Site.DefaultContainerType;
|
||||
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
|
||||
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
|
||||
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries)).Distinct().Where(item => item != "Headless").ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,14 +339,22 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void PageChanged(ChangeEventArgs e)
|
||||
private void ModuleTypeChanged(ChangeEventArgs e)
|
||||
{
|
||||
_moduleType = (string)e.Value;
|
||||
_pageId = "-";
|
||||
_moduleId = "-";
|
||||
}
|
||||
|
||||
private async Task PageChanged(ChangeEventArgs e)
|
||||
{
|
||||
_pageId = (string)e.Value;
|
||||
if (_pageId != "-")
|
||||
{
|
||||
_modules = PageState.Modules
|
||||
.Where(module => module.PageId == int.Parse(_pageId) &&
|
||||
UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
|
||||
_modules = await ModuleService.GetModulesAsync(PageState.Page.SiteId);
|
||||
_modules = _modules.Where(module => module.PageId == int.Parse(_pageId) &&
|
||||
UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList) &&
|
||||
(_moduleType == "add" || module.ModuleDefinition.IsPortable))
|
||||
.ToList();
|
||||
}
|
||||
_moduleId = "-";
|
||||
@ -354,6 +367,7 @@
|
||||
{
|
||||
if ((_moduleType == "new" && _moduleDefinitionName != "-") || (_moduleType != "new" && _moduleId != "-"))
|
||||
{
|
||||
var newModuleId = _moduleId != "-" ? int.Parse(_moduleId) : 0;
|
||||
if (_moduleType == "new")
|
||||
{
|
||||
Module module = new Module();
|
||||
@ -361,33 +375,37 @@
|
||||
module.PageId = PageState.Page.PageId;
|
||||
module.ModuleDefinitionName = _moduleDefinitionName;
|
||||
module.AllPages = false;
|
||||
|
||||
var permissions = new List<Permission>();
|
||||
if (_visibility == "view")
|
||||
{
|
||||
// set module view permissions to page view permissions
|
||||
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.View);
|
||||
}
|
||||
else
|
||||
{
|
||||
// set module view permissions to page edit permissions
|
||||
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.Edit);
|
||||
}
|
||||
// set module edit permissions to page edit permissions
|
||||
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.Edit, PermissionNames.Edit);
|
||||
module.PermissionList = permissions;
|
||||
module.PermissionList = GenerateDefaultPermissions(module.SiteId);
|
||||
|
||||
module = await ModuleService.AddModuleAsync(module);
|
||||
_moduleId = module.ModuleId.ToString();
|
||||
newModuleId = module.ModuleId;
|
||||
}
|
||||
else if (_moduleType == "copy")
|
||||
{
|
||||
var module = await ModuleService.GetModuleAsync(int.Parse(_moduleId));
|
||||
module.ModuleId = 0;
|
||||
module.SiteId = PageState.Site.SiteId;
|
||||
module.PageId = PageState.Page.PageId;
|
||||
module.AllPages = false;
|
||||
module.PermissionList = GenerateDefaultPermissions(module.SiteId);
|
||||
|
||||
module = await ModuleService.AddModuleAsync(module);
|
||||
var moduleContent = await ModuleService.ExportModuleAsync(int.Parse(_moduleId), PageState.Page.PageId);
|
||||
if (!string.IsNullOrEmpty(moduleContent))
|
||||
{
|
||||
await ModuleService.ImportModuleAsync(module.ModuleId, PageState.Page.PageId, moduleContent);
|
||||
}
|
||||
|
||||
newModuleId = module.ModuleId;
|
||||
}
|
||||
|
||||
var pageModule = new PageModule
|
||||
{
|
||||
PageId = PageState.Page.PageId,
|
||||
ModuleId = int.Parse(_moduleId),
|
||||
ModuleId = newModuleId,
|
||||
Title = _title
|
||||
};
|
||||
if (pageModule.Title == "")
|
||||
if (string.IsNullOrEmpty(pageModule.Title))
|
||||
{
|
||||
if (_moduleType == "new")
|
||||
{
|
||||
@ -412,9 +430,16 @@
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane);
|
||||
await UpdateSettingsAsync();
|
||||
|
||||
_message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>";
|
||||
_title = "";
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, ""));
|
||||
if (PageState.RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
_message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>";
|
||||
_title = "";
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, ""));
|
||||
}
|
||||
else // reload page in static rendering
|
||||
{
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, ""), true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -427,6 +452,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
private List<Permission> GenerateDefaultPermissions(int siteId)
|
||||
{
|
||||
var permissions = new List<Permission>();
|
||||
if (_visibility == "view")
|
||||
{
|
||||
// set module view permissions to page view permissions
|
||||
permissions = SetPermissions(permissions, siteId, PermissionNames.View, PermissionNames.View);
|
||||
}
|
||||
else
|
||||
{
|
||||
// set module view permissions to page edit permissions
|
||||
permissions = SetPermissions(permissions, siteId, PermissionNames.View, PermissionNames.Edit);
|
||||
}
|
||||
// set module edit permissions to page edit permissions
|
||||
permissions = SetPermissions(permissions, siteId, PermissionNames.Edit, PermissionNames.Edit);
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
private List<Permission> SetPermissions(List<Permission> permissions, int siteId, string modulePermission, string pagePermission)
|
||||
{
|
||||
foreach (var permission in PageState.Page.PermissionList.Where(item => item.PermissionName == pagePermission))
|
||||
@ -438,27 +482,19 @@
|
||||
|
||||
private void Navigate(string location)
|
||||
{
|
||||
Module module;
|
||||
int moduleId;
|
||||
switch (location)
|
||||
{
|
||||
case "Admin":
|
||||
// get admin dashboard moduleid
|
||||
module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule);
|
||||
if (module != null)
|
||||
{
|
||||
NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin", module.ModuleId, "Index", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
|
||||
}
|
||||
moduleId = int.Parse(PageState.Site.Settings[Constants.AdminDashboardModule]);
|
||||
NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin", moduleId, "Index", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
|
||||
break;
|
||||
case "Add":
|
||||
case "Edit":
|
||||
string url = "";
|
||||
// get page management moduleid
|
||||
module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.PageManagementModule);
|
||||
if (module != null)
|
||||
{
|
||||
url = Utilities.EditUrl(PageState.Alias.Path, "admin/pages", module.ModuleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}");
|
||||
NavigationManager.NavigateTo(url);
|
||||
}
|
||||
moduleId = int.Parse(PageState.Site.Settings[Constants.PageManagementModule]);
|
||||
NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin/pages", moduleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,29 @@
|
||||
@namespace Oqtane.Themes.Controls
|
||||
@inherits ThemeControlBase
|
||||
@using System.Globalization
|
||||
@using Microsoft.AspNetCore.Localization
|
||||
@using Microsoft.AspNetCore.Http
|
||||
@using Oqtane.Models
|
||||
@namespace Oqtane.Themes.Controls
|
||||
@inherits ThemeControlBase
|
||||
@inject ILanguageService LanguageService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@if (_supportedCultures?.Count() > 1)
|
||||
{
|
||||
<div class="btn-group pe-1" role="group">
|
||||
<button id="btnCultures" type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button id="btnCultures" type="button" class="btn @ButtonClass dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="oi oi-globe"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu @MenuAlignment" aria-labelledby="btnCultures">
|
||||
@foreach (var culture in _supportedCultures)
|
||||
{
|
||||
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="#" @onclick="@(async e => await SetCultureAsync(culture.Name))">@culture.DisplayName</a>
|
||||
@if (PageState.RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="#" @onclick="@(async e => await SetCultureAsync(culture.Name))" @onclick:preventDefault="true">@culture.DisplayName</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="@NavigateUrl(PageState.Page.Path, "culture=" + culture.Name)">@culture.DisplayName</a>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@ -23,9 +31,15 @@
|
||||
|
||||
@code{
|
||||
private IEnumerable<Culture> _supportedCultures;
|
||||
private string MenuAlignment = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string DropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right
|
||||
private string MenuAlignment = string.Empty;
|
||||
[Parameter]
|
||||
public string ButtonClass { get; set; } = "btn-outline-secondary";
|
||||
|
||||
[CascadingParameter]
|
||||
HttpContext HttpContext { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
@ -33,16 +47,26 @@
|
||||
|
||||
var languages = PageState.Languages;
|
||||
_supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
|
||||
|
||||
if (PageState.QueryString.ContainsKey("culture"))
|
||||
{
|
||||
var culture = PageState.QueryString["culture"];
|
||||
if (_supportedCultures.Any(item => item.Name == culture))
|
||||
{
|
||||
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||
HttpContext.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, new CookieOptions { Path = "/", Expires = DateTimeOffset.UtcNow.AddYears(365) });
|
||||
}
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""), forceLoad: true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetCultureAsync(string culture)
|
||||
{
|
||||
if (culture != CultureInfo.CurrentUICulture.Name)
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||
var interop = new Interop(JSRuntime);
|
||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
|
||||
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
else
|
||||
{
|
||||
<form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm">
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<input type="hidden" name="returnurl" value="@returnurl" />
|
||||
<button type="submit" class="btn btn-primary">@Localizer["Logout"]</button>
|
||||
</form>
|
||||
|
64
Oqtane.Client/Themes/Controls/Theme/Search.razor
Normal file
64
Oqtane.Client/Themes/Controls/Theme/Search.razor
Normal file
@ -0,0 +1,64 @@
|
||||
@namespace Oqtane.Themes.Controls
|
||||
@using System.Net
|
||||
@using Microsoft.AspNetCore.Http
|
||||
@inherits ThemeControlBase
|
||||
@inject IStringLocalizer<Search> Localizer
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IHttpContextAccessor HttpContext
|
||||
|
||||
@if (_searchResultsPage != null)
|
||||
{
|
||||
<span class="app-search @CssClass">
|
||||
<form method="post" class="app-form-inline" @formname="@($"{Position}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-value="_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 Position { get; set; } = "Main";
|
||||
|
||||
[Parameter]
|
||||
public string SearchResultPagePath { get; set; } = "search";
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if(!string.IsNullOrEmpty(SearchResultPagePath))
|
||||
{
|
||||
_searchResultsPage = PageState.Pages.FirstOrDefault(i => i.Path == SearchResultPagePath);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
}
|
||||
|
||||
private void PerformSearch()
|
||||
{
|
||||
var keywords = HttpContext.HttpContext.Request.Form["keywords"];
|
||||
if (!string.IsNullOrEmpty(keywords) && _searchResultsPage != null)
|
||||
{
|
||||
var url = NavigateUrl(_searchResultsPage.Path, $"q={keywords}");
|
||||
NavigationManager.NavigateTo(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,13 @@ namespace Oqtane.Themes.OqtaneTheme
|
||||
Resources = new List<Resource>()
|
||||
{
|
||||
// obtained from https://cdnjs.com/libraries
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.2/cyborg/bootstrap.min.css", Integrity = "sha512-RfNxVfFNFgqk9MXO4TCKXYXn9hgc+keHCg3xFFGbnp2q7Cifda+YYzMTDHwsQtNx4DuqIMgfvZead7XOtB9CDQ==", CrossOrigin = "anonymous" },
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css",
|
||||
Integrity = "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==",
|
||||
CrossOrigin = "anonymous" },
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
|
||||
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js", Integrity = "sha512-X/YkDZyjTf4wyc2Vy16YGCPHwAY8rZJY+POgokZjQB2mhIRFJCckEGc6YyX9eNsPfn0PzThEuNs+uaomE5CO6A==", CrossOrigin = "anonymous", Location = ResourceLocation.Body }
|
||||
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js",
|
||||
Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==",
|
||||
CrossOrigin = "anonymous", Location = ResourceLocation.Body },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -6,7 +6,12 @@
|
||||
<nav class="navbar navbar-dark bg-primary fixed-top">
|
||||
<Logo /><Menu Orientation="Horizontal" />
|
||||
<div class="controls ms-auto">
|
||||
<div class="controls-group"><UserProfile ShowRegister="@_register" /> <Login ShowLogin="@_login" /> <ControlPanel LanguageDropdownAlignment="right" /></div>
|
||||
<div class="controls-group">
|
||||
<Search CssClass="me-3 text-center bg-primary" />
|
||||
<UserProfile ShowRegister="@_register" />
|
||||
<Login ShowLogin="@_login" />
|
||||
<ControlPanel LanguageDropdownAlignment="right" />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="content">
|
||||
|
@ -121,15 +121,15 @@
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
if (_login != "-")
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login, true);
|
||||
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login);
|
||||
}
|
||||
if (_register != "-")
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register, true);
|
||||
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register);
|
||||
}
|
||||
if (_footer != "-")
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Footer", _footer, true);
|
||||
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Footer", _footer);
|
||||
}
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
}
|
||||
|
@ -3,8 +3,6 @@
|
||||
@using Oqtane.Shared
|
||||
@inject SiteState SiteState
|
||||
@implements IDisposable
|
||||
@* the following StreamRendering attribute is required - if it is removed the framework will not render the content in static rendering *@
|
||||
@attribute [StreamRendering]
|
||||
|
||||
@if (!string.IsNullOrEmpty(_title))
|
||||
{
|
||||
|
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;
|
@ -1,13 +1,17 @@
|
||||
@namespace Oqtane.UI
|
||||
@inject SiteState SiteState
|
||||
|
||||
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||
@if (_comment != null)
|
||||
{
|
||||
<StreamRenderingDisabled ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<StreamRenderingEnabled ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
|
||||
@((MarkupString)_comment)
|
||||
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static)
|
||||
{
|
||||
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, _prerender)" />
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
@ -20,6 +24,24 @@ else
|
||||
[CascadingParameter]
|
||||
private Module ModuleState { get; set; }
|
||||
|
||||
private bool _prerender;
|
||||
private string _comment;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
_prerender = ModuleState.Prerender ?? PageState.Site.Prerender;
|
||||
_comment = "<!-- rendermode: ";
|
||||
if (PageState.RenderMode == RenderModes.Static && ModuleState.RenderMode == RenderModes.Static)
|
||||
{
|
||||
_comment += RenderModes.Static;
|
||||
}
|
||||
else
|
||||
{
|
||||
_comment += $"{RenderModes.Interactive}:{PageState.Runtime} - prerender: {_prerender}";
|
||||
}
|
||||
_comment += " -->";
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("AddModuleMessage is deprecated. Use AddModuleMessage in ModuleBase instead.", false)]
|
||||
public void AddModuleMessage(string message, MessageType type)
|
||||
|
@ -9,6 +9,7 @@ namespace Oqtane.UI
|
||||
public Alias Alias { get; set; }
|
||||
public Site Site { get; set; }
|
||||
public Page Page { get; set; }
|
||||
public List<Module> Modules { get; set; }
|
||||
public User User { get; set; }
|
||||
public Uri Uri { get; set; }
|
||||
public Route Route { get; set; }
|
||||
@ -31,10 +32,6 @@ namespace Oqtane.UI
|
||||
{
|
||||
get { return Site.Pages; }
|
||||
}
|
||||
public List<Module> Modules
|
||||
{
|
||||
get { return Site.Modules; }
|
||||
}
|
||||
public List<Language> Languages
|
||||
{
|
||||
get { return Site.Languages; }
|
||||
|
@ -43,7 +43,7 @@ else
|
||||
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
foreach (Module module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId))
|
||||
foreach (Module module in PageState.Modules)
|
||||
{
|
||||
// set renderid - this allows the framework to determine which components should be rendered when PageState changes
|
||||
if (module.RenderId != PageState.RenderId)
|
||||
|
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;
|
@ -10,14 +10,19 @@
|
||||
{
|
||||
@if (ModuleType != null)
|
||||
{
|
||||
@((MarkupString)$"<!-- rendermode: {ModuleState.RenderMode} -->")
|
||||
<ModuleMessage @ref="moduleMessageTop" Message="@_messageContent" Type="@_messageType" />
|
||||
@if (!string.IsNullOrEmpty(_messageContent) && _messagePosition == "top")
|
||||
{
|
||||
<ModuleMessage Message="@_messageContent" Type="@_messageType" Parent="@this" />
|
||||
}
|
||||
@DynamicComponent
|
||||
@if (_progressIndicator)
|
||||
{
|
||||
<div class="app-progress-indicator"></div>
|
||||
}
|
||||
<ModuleMessage @ref="moduleMessageBottom" Message="@_messageContent" Type="@_messageType" />
|
||||
@if (!string.IsNullOrEmpty(_messageContent) && _messagePosition == "bottom")
|
||||
{
|
||||
<ModuleMessage Message="@_messageContent" Type="@_messageType" Parent="@this" />
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -42,8 +47,6 @@
|
||||
private string _messagePosition;
|
||||
private bool _progressIndicator = false;
|
||||
private string _error;
|
||||
private ModuleMessage moduleMessageTop;
|
||||
private ModuleMessage moduleMessageBottom;
|
||||
|
||||
[Parameter]
|
||||
public SiteState SiteState { get; set; }
|
||||
@ -104,12 +107,17 @@
|
||||
|
||||
public void AddModuleMessage(string message, MessageType type, string position)
|
||||
{
|
||||
_messageContent = message;
|
||||
_messageType = type;
|
||||
_messagePosition = position;
|
||||
_progressIndicator = false;
|
||||
if(message != _messageContent
|
||||
|| type != _messageType
|
||||
|| position != _messagePosition)
|
||||
{
|
||||
_messageContent = message;
|
||||
_messageType = type;
|
||||
_messagePosition = position;
|
||||
_progressIndicator = false;
|
||||
|
||||
Refresh();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowProgressIndicator()
|
||||
@ -124,25 +132,10 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void DismissMessage()
|
||||
public void DismissMessage()
|
||||
{
|
||||
_messageContent = "";
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
var updateTop = string.IsNullOrEmpty(_messageContent) || _messagePosition == "top";
|
||||
var updateBottom = string.IsNullOrEmpty(_messageContent) || _messagePosition == "bottom";
|
||||
|
||||
if (updateTop && moduleMessageTop != null)
|
||||
{
|
||||
moduleMessageTop.RefreshMessage(_messageContent, _messageType);
|
||||
}
|
||||
|
||||
if (updateBottom && moduleMessageBottom != null)
|
||||
{
|
||||
moduleMessageBottom.RefreshMessage(_messageContent, _messageType);
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected override async Task OnErrorAsync(Exception exception)
|
||||
|
@ -3,8 +3,6 @@
|
||||
@inject IInstallationService InstallationService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject SiteState SiteState
|
||||
@* the following StreamRendering attribute is required - if it is removed the framework will not render the content in static rendering *@
|
||||
@attribute [StreamRendering]
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
@using System.Diagnostics.CodeAnalysis
|
||||
@using System.Net
|
||||
@using Microsoft.AspNetCore.Http
|
||||
@using System.Globalization
|
||||
@namespace Oqtane.UI
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject SiteState SiteState
|
||||
@ -95,6 +96,7 @@
|
||||
{
|
||||
Site site = null;
|
||||
Page page = null;
|
||||
List<Module> modules = null;
|
||||
User user = null;
|
||||
var editmode = false;
|
||||
var refresh = false;
|
||||
@ -103,7 +105,7 @@
|
||||
_error = "";
|
||||
|
||||
Route route = new Route(_absoluteUri, SiteState.Alias.Path);
|
||||
int moduleid = int.Parse(route.ModuleId);
|
||||
int moduleid = int.Parse(route.ModuleId, CultureInfo.InvariantCulture);
|
||||
var action = route.Action;
|
||||
|
||||
var querystring = Utilities.ParseQueryString(route.Query);
|
||||
@ -171,9 +173,9 @@
|
||||
visitorId = PageState.VisitorId;
|
||||
}
|
||||
|
||||
if (PageState.RenderMode == RenderModes.Interactive)
|
||||
if (PageState != null && PageState.RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
// process any sync events (for synchrozing the client application with the server)
|
||||
// process any sync events (for synchronizing the client application with the server)
|
||||
var sync = await SyncService.GetSyncEventsAsync(lastsyncdate);
|
||||
lastsyncdate = sync.SyncDate;
|
||||
if (sync.SyncEvents.Any())
|
||||
@ -211,7 +213,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (PageState == null || refresh || PageState.Page.Path != route.PagePath)
|
||||
if (refresh || PageState == null || PageState.Page.Path != route.PagePath)
|
||||
{
|
||||
page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
@ -251,105 +253,108 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (page != null)
|
||||
// check if user is authorized to view page
|
||||
if (page != null && UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList)))
|
||||
{
|
||||
// check if user is authorized to view page
|
||||
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList)))
|
||||
// edit mode
|
||||
if (user != null)
|
||||
{
|
||||
// edit mode
|
||||
if (user != null)
|
||||
if (querystring.ContainsKey("editmode") && querystring["edit"] == "true")
|
||||
{
|
||||
if (querystring.ContainsKey("editmode") && querystring["edit"] == "true")
|
||||
editmode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
editmode = (page.PageId == ((user.Settings.ContainsKey("CP-editmode")) ? int.Parse(user.Settings["CP-editmode"], CultureInfo.InvariantCulture) : -1));
|
||||
if (!editmode)
|
||||
{
|
||||
editmode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
editmode = (page.PageId == ((user.Settings.ContainsKey("CP-editmode")) ? int.Parse(user.Settings["CP-editmode"]) : -1));
|
||||
if (!editmode)
|
||||
{
|
||||
var userSettings = new Dictionary<string, string> { { "CP-editmode", "-1" } };
|
||||
await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId);
|
||||
}
|
||||
var userSettings = new Dictionary<string, string> { { "CP-editmode", "-1" } };
|
||||
await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
// load additional metadata for current page
|
||||
page = ProcessPage(page, site, user, SiteState.Alias);
|
||||
}
|
||||
|
||||
// load additional metadata for modules
|
||||
(page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias);
|
||||
|
||||
// populate page state (which acts as a client-side cache for subsequent requests)
|
||||
_pagestate = new PageState
|
||||
{
|
||||
Alias = SiteState.Alias,
|
||||
Site = site,
|
||||
Page = page,
|
||||
User = user,
|
||||
Uri = new Uri(_absoluteUri, UriKind.Absolute),
|
||||
Route = route,
|
||||
QueryString = querystring,
|
||||
UrlParameters = route.UrlParameters,
|
||||
ModuleId = moduleid,
|
||||
Action = action,
|
||||
EditMode = editmode,
|
||||
LastSyncDate = lastsyncdate,
|
||||
RenderMode = RenderMode,
|
||||
Runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime),
|
||||
VisitorId = visitorId,
|
||||
RemoteIPAddress = SiteState.RemoteIPAddress,
|
||||
ReturnUrl = returnurl,
|
||||
IsInternalNavigation = _isInternalNavigation,
|
||||
RenderId = Guid.NewGuid(),
|
||||
Refresh = false
|
||||
};
|
||||
OnStateChange?.Invoke(_pagestate);
|
||||
|
||||
if (PageState.RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
await ScrollToFragment(_pagestate.Uri);
|
||||
}
|
||||
// get modules for current page
|
||||
if (refresh || PageState.Modules == null || !PageState.Modules.Any() || PageState.Modules.First().PageId != page.PageId)
|
||||
{
|
||||
modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Need to redirect 404 as page doesnot exist in a Permission or Timeframe
|
||||
if (route.PagePath != "404")
|
||||
{
|
||||
// redirect to 404 page
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", ""));
|
||||
}
|
||||
modules = PageState.Modules;
|
||||
}
|
||||
|
||||
// load additional metadata for current page
|
||||
page = ProcessPage(page, site, user, SiteState.Alias);
|
||||
|
||||
// load additional metadata for modules
|
||||
(page, modules) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias);
|
||||
|
||||
// populate page state (which acts as a client-side cache for subsequent requests)
|
||||
_pagestate = new PageState
|
||||
{
|
||||
Alias = SiteState.Alias,
|
||||
Site = site,
|
||||
Page = page,
|
||||
Modules = modules,
|
||||
User = user,
|
||||
Uri = new Uri(_absoluteUri, UriKind.Absolute),
|
||||
Route = route,
|
||||
QueryString = querystring,
|
||||
UrlParameters = route.UrlParameters,
|
||||
ModuleId = moduleid,
|
||||
Action = action,
|
||||
EditMode = editmode,
|
||||
LastSyncDate = lastsyncdate,
|
||||
RenderMode = RenderMode,
|
||||
Runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime),
|
||||
VisitorId = visitorId,
|
||||
RemoteIPAddress = SiteState.RemoteIPAddress,
|
||||
ReturnUrl = returnurl,
|
||||
IsInternalNavigation = _isInternalNavigation,
|
||||
RenderId = Guid.NewGuid(),
|
||||
Refresh = false
|
||||
};
|
||||
OnStateChange?.Invoke(_pagestate);
|
||||
|
||||
if (PageState.RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
await ScrollToFragment(_pagestate.Uri);
|
||||
}
|
||||
}
|
||||
else // page not found
|
||||
else
|
||||
{
|
||||
// look for url mapping
|
||||
var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath);
|
||||
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
|
||||
if (page == null)
|
||||
{
|
||||
var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl + route.Query;
|
||||
NavigationManager.NavigateTo(url, false);
|
||||
// check for url mapping
|
||||
var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath);
|
||||
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
|
||||
{
|
||||
var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl + route.Query;
|
||||
NavigationManager.NavigateTo(url, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else // not mapped
|
||||
else
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
// redirect to login page if user not logged in as they may need to be authenticated
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery)));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (route.PagePath != "404")
|
||||
{
|
||||
// redirect to 404 page
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", ""));
|
||||
}
|
||||
else
|
||||
{
|
||||
// redirect to home page as a fallback
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// page not found or user does not have sufficient access
|
||||
if (route.PagePath != "404")
|
||||
{
|
||||
// redirect to 404 page
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", ""));
|
||||
}
|
||||
else
|
||||
{
|
||||
// redirect to home page as a fallback
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -484,6 +489,7 @@
|
||||
// retrieve module component resources
|
||||
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
module.RenderMode = moduleobject.RenderMode;
|
||||
module.Prerender = moduleobject.Prerender;
|
||||
|
||||
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
|
||||
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
||||
@ -557,7 +563,7 @@
|
||||
{
|
||||
foreach (var resource in resources)
|
||||
{
|
||||
if (resource.Level != ResourceLevel.Site)
|
||||
if (resource.ResourceType == ResourceType.Stylesheet || resource.Level != ResourceLevel.Site)
|
||||
{
|
||||
if (resource.Url.StartsWith("~"))
|
||||
{
|
||||
|
@ -1,21 +0,0 @@
|
||||
@attribute [StreamRendering(false)]
|
||||
|
||||
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static)
|
||||
{
|
||||
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" />
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public SiteState SiteState { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public PageState PageState { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Module ModuleState { get; set; }
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
@attribute [StreamRendering(true)]
|
||||
|
||||
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static)
|
||||
{
|
||||
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" />
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public SiteState SiteState { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public PageState PageState { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Module ModuleState { get; set; }
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.1.0</Version>
|
||||
<Version>5.2.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -33,8 +33,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="MySql.Data" Version="8.3.0" />
|
||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.2" />
|
||||
<PackageReference Include="MySql.Data" Version="8.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.1.0</Version>
|
||||
<Version>5.2.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -34,8 +34,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.3" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.6" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.1.0</Version>
|
||||
<Version>5.2.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -33,7 +33,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.1.0</Version>
|
||||
<Version>5.2.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -33,7 +33,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<!-- <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks> -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
|
||||
<OutputType>Exe</OutputType>
|
||||
<Version>5.1.0</Version>
|
||||
<Version>5.2.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -14,7 +14,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane.Maui</RootNamespace>
|
||||
@ -31,7 +31,7 @@
|
||||
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>5.1.0</ApplicationDisplayVersion>
|
||||
<ApplicationDisplayVersion>5.2.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
|
||||
@ -65,15 +65,15 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.6" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.40" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.40" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.40" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Client</id>
|
||||
<version>5.1.0</version>
|
||||
<version>5.2.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
@ -21,4 +21,4 @@
|
||||
<file src="..\Oqtane.Client\bin\Release\net8.0\Oqtane.Client.pdb" target="lib\net8.0" />
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
||||
</package>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Framework</id>
|
||||
<version>5.1.0</version>
|
||||
<version>5.2.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -11,12 +11,12 @@
|
||||
<copyright>.NET Foundation</copyright>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v5.1.0/Oqtane.Framework.5.1.0.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</releaseNotes>
|
||||
<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.2.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane framework</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
||||
</package>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Server</id>
|
||||
<version>5.1.0</version>
|
||||
<version>5.2.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
@ -21,4 +21,4 @@
|
||||
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Server.pdb" target="lib\net8.0" />
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
||||
</package>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Shared</id>
|
||||
<version>5.1.0</version>
|
||||
<version>5.2.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
@ -21,4 +21,4 @@
|
||||
<file src="..\Oqtane.Shared\bin\Release\net8.0\Oqtane.Shared.pdb" target="lib\net8.0" />
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
||||
</package>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Updater</id>
|
||||
<version>5.1.0</version>
|
||||
<version>5.2.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
@ -20,4 +20,4 @@
|
||||
<file src="..\Oqtane.Updater\bin\Release\net8.0\publish\*.*" target="lib\net8.0" />
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
||||
</package>
|
||||
|
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.0.Install.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.0.Install.zip" -Force
|
||||
|
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.0.Upgrade.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.0.Upgrade.zip" -Force
|
||||
|
@ -16,6 +16,7 @@
|
||||
@using Oqtane.Shared
|
||||
@using Oqtane.Themes
|
||||
@using Oqtane.Extensions
|
||||
@using System.Globalization
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IAntiforgery Antiforgery
|
||||
@inject IConfigManager ConfigManager
|
||||
@ -39,7 +40,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
@if (!string.IsNullOrEmpty(_PWAScript))
|
||||
@if (_scripts.Contains("PWA Manifest"))
|
||||
{
|
||||
<link id="app-manifest" rel="manifest" />
|
||||
}
|
||||
@ -68,20 +69,13 @@
|
||||
<Routes PageState="@_pageState" RenderMode="@_renderMode" Runtime="@_runtime" AntiForgeryToken="@_antiForgeryToken" AuthorizationToken="@_authorizationToken" Platform="@_platform" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(_runtime, _prerender)" />
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(_reconnectScript))
|
||||
{
|
||||
@((MarkupString)_reconnectScript)
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(_PWAScript))
|
||||
{
|
||||
@((MarkupString)_PWAScript)
|
||||
}
|
||||
@((MarkupString)_bodyResources)
|
||||
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/loadjs.min.js"></script>
|
||||
<script src="js/interop.js"></script>
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
|
||||
@((MarkupString)_scripts)
|
||||
@((MarkupString)_bodyResources)
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -105,8 +99,7 @@
|
||||
private string _headResources = "";
|
||||
private string _bodyResources = "";
|
||||
private string _styleSheets = "";
|
||||
private string _PWAScript = "";
|
||||
private string _reconnectScript = "";
|
||||
private string _scripts = "";
|
||||
private string _message = "";
|
||||
private PageState _pageState;
|
||||
|
||||
@ -139,6 +132,7 @@
|
||||
_renderMode = site.RenderMode;
|
||||
_runtime = site.Runtime;
|
||||
_prerender = site.Prerender;
|
||||
var modules = new List<Module>();
|
||||
|
||||
Route route = new Route(url, alias.Path);
|
||||
var page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
|
||||
@ -163,6 +157,10 @@
|
||||
{
|
||||
HandlePageNotFound(site, page, route);
|
||||
}
|
||||
else
|
||||
{
|
||||
modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId);
|
||||
}
|
||||
|
||||
if (site.VisitorTracking)
|
||||
{
|
||||
@ -175,23 +173,25 @@
|
||||
CreateJwtToken(alias);
|
||||
}
|
||||
|
||||
// include stylesheets to prevent FOUC
|
||||
var resources = GetPageResources(alias, site, page, int.Parse(route.ModuleId), route.Action);
|
||||
// includes resources
|
||||
var resources = await GetPageResources(alias, site, page, modules, int.Parse(route.ModuleId, CultureInfo.InvariantCulture), route.Action);
|
||||
ManageStyleSheets(resources);
|
||||
ManageScripts(resources, alias);
|
||||
|
||||
// scripts
|
||||
if (_renderMode == RenderModes.Static)
|
||||
{
|
||||
ManageScripts(resources, alias);
|
||||
}
|
||||
// generate scripts
|
||||
if (_renderMode == RenderModes.Interactive && _runtime == Runtimes.Server)
|
||||
{
|
||||
_reconnectScript = CreateReconnectScript();
|
||||
_scripts += CreateReconnectScript();
|
||||
}
|
||||
if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null)
|
||||
{
|
||||
_PWAScript = CreatePWAScript(alias, site, route);
|
||||
_scripts += CreatePWAScript(alias, site, route);
|
||||
}
|
||||
@if (_renderMode == RenderModes.Static)
|
||||
{
|
||||
_scripts += CreateScrollPositionScript();
|
||||
}
|
||||
|
||||
_headResources += ParseScripts(site.HeadContent);
|
||||
_bodyResources += ParseScripts(site.BodyContent);
|
||||
|
||||
@ -220,12 +220,13 @@
|
||||
_language = _language.Replace("c=", "");
|
||||
}
|
||||
|
||||
// create initial PageState
|
||||
// create initial PageState
|
||||
_pageState = new PageState
|
||||
{
|
||||
Alias = alias,
|
||||
Site = site,
|
||||
Page = page,
|
||||
Modules = modules,
|
||||
User = null,
|
||||
Uri = new Uri(url, UriKind.Absolute),
|
||||
Route = route,
|
||||
@ -329,14 +330,26 @@
|
||||
int? userid = Context.User.UserId();
|
||||
userid = (userid == -1) ? null : userid;
|
||||
|
||||
// check if cookie already exists
|
||||
// get cookie value
|
||||
var visitorCookieName = Constants.VisitorCookiePrefix + SiteId.ToString();
|
||||
var visitorCookieValue = Context.Request.Cookies[visitorCookieName];
|
||||
DateTime expiry = DateTime.MinValue;
|
||||
if (visitorCookieValue != null && visitorCookieValue.Contains("|"))
|
||||
{
|
||||
var values = visitorCookieValue.Split('|');
|
||||
int.TryParse(values[0], out _visitorId);
|
||||
DateTime.TryParseExact(values[1], "M/d/yyyy hh:mm:ss tt", CultureInfo.InvariantCulture, DateTimeStyles.None, out expiry);
|
||||
}
|
||||
else // legacy cookie format
|
||||
{
|
||||
int.TryParse(visitorCookieValue, out _visitorId);
|
||||
}
|
||||
bool setcookie = false;
|
||||
Visitor visitor = null;
|
||||
bool addcookie = false;
|
||||
var VisitorCookie = Constants.VisitorCookiePrefix + SiteId.ToString();
|
||||
if (!int.TryParse(Context.Request.Cookies[VisitorCookie], out _visitorId))
|
||||
|
||||
if (_visitorId <= 0)
|
||||
{
|
||||
// if enabled use IP Address correlation
|
||||
_visitorId = -1;
|
||||
var correlate = bool.Parse(settings.GetValue("VisitorCorrelation", "true"));
|
||||
if (correlate)
|
||||
{
|
||||
@ -344,12 +357,12 @@
|
||||
if (visitor != null)
|
||||
{
|
||||
_visitorId = visitor.VisitorId;
|
||||
addcookie = true;
|
||||
setcookie = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_visitorId == -1)
|
||||
if (_visitorId <= 0)
|
||||
{
|
||||
// create new visitor
|
||||
visitor = new Visitor();
|
||||
@ -365,52 +378,59 @@
|
||||
visitor.VisitedOn = DateTime.UtcNow;
|
||||
visitor = VisitorRepository.AddVisitor(visitor);
|
||||
_visitorId = visitor.VisitorId;
|
||||
addcookie = true;
|
||||
setcookie = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (visitor == null)
|
||||
// check expiry
|
||||
if (DateTime.UtcNow > expiry)
|
||||
{
|
||||
// get visitor if it was not previously loaded
|
||||
visitor = VisitorRepository.GetVisitor(_visitorId);
|
||||
}
|
||||
if (visitor != null)
|
||||
{
|
||||
// update visitor
|
||||
visitor.IPAddress = _remoteIPAddress;
|
||||
visitor.UserAgent = useragent;
|
||||
visitor.Language = language;
|
||||
visitor.Url = url;
|
||||
if (!string.IsNullOrEmpty(referrer))
|
||||
if (visitor == null)
|
||||
{
|
||||
visitor.Referrer = referrer;
|
||||
// get visitor if not previously loaded
|
||||
visitor = VisitorRepository.GetVisitor(_visitorId);
|
||||
}
|
||||
if (userid != null)
|
||||
if (visitor != null)
|
||||
{
|
||||
visitor.UserId = userid;
|
||||
// update visitor
|
||||
visitor.IPAddress = _remoteIPAddress;
|
||||
visitor.UserAgent = useragent;
|
||||
visitor.Language = language;
|
||||
visitor.Url = url;
|
||||
if (!string.IsNullOrEmpty(referrer))
|
||||
{
|
||||
visitor.Referrer = referrer;
|
||||
}
|
||||
if (userid != null)
|
||||
{
|
||||
visitor.UserId = userid;
|
||||
}
|
||||
visitor.Visits += 1;
|
||||
visitor.VisitedOn = DateTime.UtcNow;
|
||||
VisitorRepository.UpdateVisitor(visitor);
|
||||
setcookie = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove cookie if visitor does not exist
|
||||
Context.Response.Cookies.Delete(visitorCookieName);
|
||||
}
|
||||
visitor.Visits += 1;
|
||||
visitor.VisitedOn = DateTime.UtcNow;
|
||||
VisitorRepository.UpdateVisitor(visitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove cookie if VisitorId does not exist
|
||||
Context.Response.Cookies.Delete(VisitorCookie);
|
||||
}
|
||||
}
|
||||
|
||||
// append cookie
|
||||
if (addcookie)
|
||||
// set cookie
|
||||
if (setcookie)
|
||||
{
|
||||
expiry = DateTime.UtcNow.AddMinutes(int.Parse(settings.GetValue("VisitorDuration", "5")));
|
||||
|
||||
Context.Response.Cookies.Append(
|
||||
VisitorCookie,
|
||||
_visitorId.ToString(),
|
||||
visitorCookieName,
|
||||
$"{_visitorId}|{expiry.ToString("M/d/yyyy hh:mm:ss tt", CultureInfo.InvariantCulture)}",
|
||||
new CookieOptions()
|
||||
{
|
||||
Expires = DateTimeOffset.UtcNow.AddYears(1),
|
||||
IsEssential = true
|
||||
}
|
||||
{
|
||||
Expires = DateTimeOffset.UtcNow.AddYears(10),
|
||||
IsEssential = true
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -432,7 +452,7 @@
|
||||
|
||||
private string CreatePWAScript(Alias alias, Site site, Route route)
|
||||
{
|
||||
return
|
||||
return Environment.NewLine +
|
||||
"<script>" + Environment.NewLine +
|
||||
" // PWA Manifest" + Environment.NewLine +
|
||||
" setTimeout(() => {" + Environment.NewLine +
|
||||
@ -468,14 +488,14 @@
|
||||
" console.log('ServiceWorker Registration Failed ', err);" + Environment.NewLine +
|
||||
" });" + Environment.NewLine +
|
||||
" };" + Environment.NewLine +
|
||||
"</script>";
|
||||
"</script>" + Environment.NewLine;
|
||||
}
|
||||
|
||||
private string CreateReconnectScript()
|
||||
{
|
||||
return
|
||||
return Environment.NewLine +
|
||||
"<script>" + Environment.NewLine +
|
||||
" // Blazor Server Reconnect" + Environment.NewLine +
|
||||
" // Interactive Blazor Server Reconnect" + Environment.NewLine +
|
||||
" new MutationObserver((mutations, observer) => {" + Environment.NewLine +
|
||||
" if (document.querySelector('#components-reconnect-modal h5 a')) {" + Environment.NewLine +
|
||||
" async function attemptReload() {" + Environment.NewLine +
|
||||
@ -487,7 +507,26 @@
|
||||
" setInterval(attemptReload, 5000);" + Environment.NewLine +
|
||||
" }" + Environment.NewLine +
|
||||
" }).observe(document.body, { childList: true, subtree: true });" + Environment.NewLine +
|
||||
"</script>";
|
||||
"</script>" + Environment.NewLine;
|
||||
}
|
||||
|
||||
private string CreateScrollPositionScript()
|
||||
{
|
||||
return Environment.NewLine +
|
||||
"<script>" + Environment.NewLine +
|
||||
" // Blazor Static Rendering Scroll Position" + Environment.NewLine +
|
||||
" window.interceptNavigation = () => {" + Environment.NewLine +
|
||||
" let currentUrl = window.location.href;" + Environment.NewLine +
|
||||
" Blazor.addEventListener('enhancedload', () => {" + Environment.NewLine +
|
||||
" let newUrl = window.location.href;" + Environment.NewLine +
|
||||
" if (currentUrl != newUrl) {" + Environment.NewLine +
|
||||
" window.scrollTo({ top: 0, left: 0, behavior: 'instant' });" + Environment.NewLine +
|
||||
" }" + Environment.NewLine +
|
||||
" currentUrl = newUrl;" + Environment.NewLine +
|
||||
" });" + Environment.NewLine +
|
||||
" };" + Environment.NewLine +
|
||||
" document.onload += window.interceptNavigation();" + Environment.NewLine +
|
||||
"</script>" + Environment.NewLine;
|
||||
}
|
||||
|
||||
private string ParseScripts(string content)
|
||||
@ -536,7 +575,7 @@
|
||||
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
|
||||
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
|
||||
((resource.ES6Module) ? " type=\"module\"" : "") +
|
||||
" src =\"" + url + "\"></script>"; // src at end of element due to enhanced navigation patch algorithm
|
||||
" src=\"" + url + "\"></script>"; // src at end of element due to enhanced navigation patch algorithm
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -558,7 +597,7 @@
|
||||
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>();
|
||||
|
||||
@ -566,13 +605,13 @@
|
||||
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType));
|
||||
if (theme != null)
|
||||
{
|
||||
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
|
||||
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// fallback to default Oqtane theme
|
||||
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == Constants.DefaultTheme));
|
||||
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
|
||||
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode);
|
||||
}
|
||||
var type = Type.GetType(themeType);
|
||||
if (type != null)
|
||||
@ -580,16 +619,16 @@
|
||||
var obj = Activator.CreateInstance(type) as IThemeControl;
|
||||
if (obj != null)
|
||||
{
|
||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace);
|
||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, site.RenderMode);
|
||||
}
|
||||
}
|
||||
|
||||
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 = "";
|
||||
if (module.ModuleDefinition != null)
|
||||
{
|
||||
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
|
||||
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode);
|
||||
|
||||
// handle default action
|
||||
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
|
||||
@ -635,7 +674,7 @@
|
||||
var obj = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
if (obj != null)
|
||||
{
|
||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
|
||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
||||
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
||||
{
|
||||
// settings components are embedded within a framework settings module
|
||||
@ -643,45 +682,51 @@
|
||||
if (moduletype != null)
|
||||
{
|
||||
obj = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
|
||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// site level resources for modules in site
|
||||
var modules = site.Modules.GroupBy(item => item.ModuleDefinition?.ModuleDefinitionName).Select(group => group.First()).ToList();
|
||||
foreach (var module in modules)
|
||||
if (site.RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
if (module.ModuleDefinition?.Resources != null)
|
||||
// site level resources for modules in site
|
||||
var sitemodules = await SiteService.GetModulesAsync(site.SiteId, -1);
|
||||
foreach (var module in sitemodules.GroupBy(item => item.ModuleDefinition?.ModuleDefinitionName).Select(group => group.First()).ToList())
|
||||
{
|
||||
resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name)
|
||||
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name, string rendermode)
|
||||
{
|
||||
if (resources != null)
|
||||
{
|
||||
foreach (var resource in resources)
|
||||
{
|
||||
if (resource.Url.StartsWith("~"))
|
||||
if (rendermode == RenderModes.Static || resource.ResourceType == ResourceType.Stylesheet || resource.Level == ResourceLevel.Site)
|
||||
{
|
||||
resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/");
|
||||
}
|
||||
if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
|
||||
{
|
||||
resource.Url = alias.BaseUrl + resource.Url;
|
||||
}
|
||||
if (resource.Url.StartsWith("~"))
|
||||
{
|
||||
resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/");
|
||||
}
|
||||
if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
|
||||
{
|
||||
resource.Url = alias.BaseUrl + resource.Url;
|
||||
}
|
||||
|
||||
// ensure resource does not exist already
|
||||
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
|
||||
{
|
||||
pageresources.Add(resource.Clone(level, name));
|
||||
// ensure resource does not exist already
|
||||
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
|
||||
{
|
||||
pageresources.Add(resource.Clone(level, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -692,6 +737,7 @@
|
||||
{
|
||||
if (resources != null)
|
||||
{
|
||||
// include stylesheets to prevent FOUC
|
||||
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
|
||||
int count = 0;
|
||||
foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
|
||||
|
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;
|
@ -760,7 +760,7 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
if (!Directory.Exists(folderpath))
|
||||
{
|
||||
string path = "";
|
||||
string path = folderpath.StartsWith(Path.DirectorySeparatorChar) ? Path.DirectorySeparatorChar.ToString() : string.Empty;
|
||||
var separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
|
||||
string[] folders = folderpath.Split(separators, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (string folder in folders)
|
||||
|
@ -76,5 +76,20 @@ namespace Oqtane.Controllers
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public void Delete(string siteId)
|
||||
{
|
||||
if (int.TryParse(siteId, out int parsedSiteId) && parsedSiteId == _alias.SiteId)
|
||||
{
|
||||
_logs.DeleteLogs(parsedSiteId, 0); // specifying zero for age results in all logs being deleted
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Log Delete Attempt {SiteId}", siteId);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Security;
|
||||
using System.Net;
|
||||
using System.Security.Policy;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
|
@ -365,8 +365,8 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
{ "FrameworkVersion", moduleDefinition.Version },
|
||||
{ "ClientReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" },
|
||||
{ "ServerReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" },
|
||||
{ "SharedReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" },
|
||||
{ "ServerReference", $"<PackageReference Include=\"Oqtane.Server\" Version=\"{moduleDefinition.Version}\" />" },
|
||||
{ "SharedReference", $"<PackageReference Include=\"Oqtane.Shared\" Version=\"{moduleDefinition.Version}\" />" },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
41
Oqtane.Server/Controllers/SearchResultsController.cs
Normal file
41
Oqtane.Server/Controllers/SearchResultsController.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
[Route(ControllerRoutes.ApiRoute)]
|
||||
public class SearchResultsController : ModuleControllerBase
|
||||
{
|
||||
private readonly ISearchService _searchService;
|
||||
|
||||
public SearchResultsController(ISearchService searchService, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor)
|
||||
{
|
||||
_searchService = searchService;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||
public async Task<Models.SearchResults> Post([FromBody] Models.SearchQuery searchQuery)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _searchService.SearchAsync(searchQuery);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Other, ex, "Fetch search results failed.", searchQuery);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -81,5 +81,12 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user