From ad364a3c488b2b70051ec1facd7584953881e5d0 Mon Sep 17 00:00:00 2001 From: Wil Stead Date: Mon, 25 Nov 2024 08:40:38 -0500 Subject: [PATCH] More services --- .github/workflows/publish.yml | 2 +- README.md | 4 + docs/CHANGELOG.md | 16 ++ src/Client/Configuration/ServiceExtensions.cs | 23 +- .../Configuration/WikiBlazorClientOptions.cs | 222 ++++++++++++++++++ src/Client/Configuration/WikiBlazorOptions.cs | 182 +------------- src/Client/Services/ArticleRenderManager.cs | 105 +++++++++ src/Client/Services/IArticleRenderManager.cs | 104 ++++++++ src/Client/Services/IOfflineManager.cs | 36 +++ src/Client/Services/OfflineManager.cs | 38 +++ src/Client/Services/WikiDataService.cs | 38 ++- src/Client/Tavenem.Wiki.Blazor.Client.csproj | 2 +- src/Server/Configuration/ServiceExtensions.cs | 31 +-- .../Configuration/WikiBlazorServerOptions.cs | 26 +- 14 files changed, 600 insertions(+), 229 deletions(-) create mode 100644 src/Client/Configuration/WikiBlazorClientOptions.cs create mode 100644 src/Client/Services/ArticleRenderManager.cs create mode 100644 src/Client/Services/IArticleRenderManager.cs create mode 100644 src/Client/Services/IOfflineManager.cs create mode 100644 src/Client/Services/OfflineManager.cs diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4d5385a..7288fa3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,6 +1,6 @@ name: publish env: - VERSION: '0.10.1-preview' + VERSION: '0.11.0-preview' PRERELEASE: true on: push: diff --git a/README.md b/README.md index aa9cef8..a51b2c9 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,10 @@ In order to use Tavenem.Wiki.Blazor, the following steps should be taken: If both the server and the local data store are unavailable, the wiki will remain operational, but will show no content and will not allow any content to be added. No automatic synchronization occurs from the local data store to the server (for instance when an offline client reestablishes network connectivity). If your app model requires synchronization of offline content to a server, that logic must be implemented separately. + + When providing a configuration function (rather than a preconfigured options instance) the following additional properties are available: + - `ArticleRenderManager`: an instance of `IArticleRenderManager`. The overloads of `ConfigureArticleRenderManager` also allow configuring this from dependency injection. If omitted, an instance of the default `ArticleRenderManager` will be used, which always returns `null` for all members. + - `OfflineManager`: an instance of `IOfflineManager`. The overloads of `ConfigureOfflineManager` also allow configuring this from dependency injection. If omitted, an instance of the default `OfflineManager` will be used, which always returns `false` for all members. 1. Add a page with the following content to your client: ```csharp @page "/wiki/{*route}" diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e302362..4a5ca1f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 0.11.0-preview +### Added +- `IArticleRenderManager` and `ArticleRenderManager` to handle custom layout and rendering of wiki articles +- `IOfflineManager` and `OfflineManager` to control offline editing capabilities +### Removed +- `WikiBlazorOptions.ArticleEndMatter` (replaced by `IArticleRenderManager`) +- `WikiBlazorOptions.ArticleEndMatterRenderMode` (replaced by `IArticleRenderManager`) +- `WikiBlazorOptions.ArticleFrontMatter` (replaced by `IArticleRenderManager`) +- `WikiBlazorOptions.ArticleFrontMatterRenderMode` (replaced by `IArticleRenderManager`) +- `WikiBlazorOptions.CanEditOffline` (replaced by `IOfflineManager`) +- `WikiBlazorOptions.IsOfflineDomain` (replaced by `IOfflineManager`) + +## 0.10.2-preview +### Updated +- Update dependencies + ## 0.10.1-preview ### Fixed - Preview popups diff --git a/src/Client/Configuration/ServiceExtensions.cs b/src/Client/Configuration/ServiceExtensions.cs index 8ee9108..a17f756 100644 --- a/src/Client/Configuration/ServiceExtensions.cs +++ b/src/Client/Configuration/ServiceExtensions.cs @@ -1,4 +1,5 @@ using Tavenem.Wiki.Blazor.Client; +using Tavenem.Wiki.Blazor.Client.Configuration; namespace Microsoft.Extensions.DependencyInjection; @@ -16,7 +17,13 @@ public static class ServiceExtensions /// The instance. public static IServiceCollection AddWikiClient( this IServiceCollection services, - WikiBlazorOptions? options = null) => (options ?? new()).Add(services); + WikiBlazorOptions? options = null) + { + var configuredOptions = options is null + ? new() + : new WikiBlazorClientOptions(options); + return configuredOptions.Add(services); + } /// /// Add the required services for Tavenem.Wiki.Blazor. @@ -28,10 +35,11 @@ public static IServiceCollection AddWikiClient( public static IServiceCollection AddWikiClient( this IServiceCollection services, WikiBlazorOptions options, - Action config) + Action config) { - config.Invoke(options); - return services.AddWikiClient(options); + var configuredOptions = new WikiBlazorClientOptions(options); + config.Invoke(configuredOptions); + return services.AddWikiClient(configuredOptions); } /// @@ -42,5 +50,10 @@ public static IServiceCollection AddWikiClient( /// The instance. public static IServiceCollection AddWikiClient( this IServiceCollection services, - Action config) => services.AddWikiClient(new(), config); + Action config) + { + var options = new WikiBlazorClientOptions(); + config.Invoke(options); + return options.Add(services); + } } diff --git a/src/Client/Configuration/WikiBlazorClientOptions.cs b/src/Client/Configuration/WikiBlazorClientOptions.cs new file mode 100644 index 0000000..1529395 --- /dev/null +++ b/src/Client/Configuration/WikiBlazorClientOptions.cs @@ -0,0 +1,222 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Diagnostics.CodeAnalysis; +using Tavenem.Wiki.Blazor.Client.Services; + +namespace Tavenem.Wiki.Blazor.Client.Configuration; + +/// +/// Options for configuring Tavenem.Wiki.Blazor.Client. +/// +public class WikiBlazorClientOptions() : WikiBlazorOptions +{ + private IArticleRenderManager? _articleRenderManager; + private Func? _articleRenderManagerConfig; + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + private Type? _articleRenderManagerType; + + private IOfflineManager? _offlineManager; + private Func? _offlineManagerConfig; + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + private Type? _offlineManagerType; + + /// + /// + /// Supply an instance of . + /// + /// + /// May be omitted to use the default . + /// + /// + public IArticleRenderManager? ArticleRenderManager + { + get => _articleRenderManager; + set + { + _articleRenderManager = value; + _articleRenderManagerConfig = null; + _articleRenderManagerType = null; + } + } + + /// + /// + /// Supply an instance of . + /// + /// + /// May be omitted to use the default . + /// + /// + public IOfflineManager? OfflineManager + { + get => _offlineManager; + set + { + _offlineManager = value; + _offlineManagerConfig = null; + _offlineManagerType = null; + } + } + + /// + /// Constructs a new instance of . + /// + /// + /// An instance of from which to copy settings. + /// + public WikiBlazorClientOptions(WikiBlazorOptions other) : this() + { + AboutPageTitle = other.AboutPageTitle; + AppBar = other.AppBar; + AppBarRenderMode = other.AppBarRenderMode; + CategoriesTitle = other.CategoriesTitle; + CategoryNamespace = other.CategoryNamespace; + CompactLayout = other.CompactLayout; + CompactRouteHostPart = other.CompactRouteHostPart; + CompactRouteHostPosition = other.CompactRouteHostPosition; + CompactRoutePort = other.CompactRoutePort; + ContactPageTitle = other.ContactPageTitle; + ContentsPageTitle = other.ContentsPageTitle; + CopyrightPageTitle = other.CopyrightPageTitle; + CustomAdminNamespaces = other.CustomAdminNamespaces; + CustomReservedNamespaces = other.CustomReservedNamespaces; + DefaultAnonymousPermission = other.DefaultAnonymousPermission; + DefaultRegisteredPermission = other.DefaultRegisteredPermission; + DefaultTableOfContentsDepth = other.DefaultTableOfContentsDepth; + DefaultTableOfContentsTitle = other.DefaultTableOfContentsTitle; + DomainArchivePermission = other.DomainArchivePermission; + FileNamespace = other.FileNamespace; + GroupNamespace = other.GroupNamespace; + HelpPageTitle = other.HelpPageTitle; + InteractiveRenderMode = other.InteractiveRenderMode; + LinkTemplate = other.LinkTemplate; + LoginPath = other.LoginPath; + MainLayout = other.MainLayout; + MainPageTitle = other.MainPageTitle; + MaxFileSize = other.MaxFileSize; + MinimumTableOfContentsHeadings = other.MinimumTableOfContentsHeadings; + PolicyPageTitle = other.PolicyPageTitle; + Postprocessors = other.Postprocessors; + ScriptNamespace = other.ScriptNamespace; + SiteName = other.SiteName; + SystemNamespace = other.SystemNamespace; + TenorAPIKey = other.TenorAPIKey; + TransclusionNamespace = other.TransclusionNamespace; + UserDomains = other.UserDomains; + UserNamespace = other.UserNamespace; + WikiLinkPrefix = other.WikiLinkPrefix; + WikiServerApiRoute = other.WikiServerApiRoute; + } + + /// + public override IServiceCollection Add(IServiceCollection services) + { + AddArticleRenderManager(services); + AddOfflineManager(services); + + return base.Add(services); + } + + /// + /// + /// Supply a type of . + /// + /// + /// May be omitted to use the default . + /// + /// + public void ConfigureArticleRenderManager( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type? type) + { + _articleRenderManagerConfig = null; + _articleRenderManager = null; + _articleRenderManagerType = type; + } + + /// + /// + /// Supply a function which returns an instance of . + /// + /// + /// May be omitted to use the default . + /// + /// + public void ConfigureArticleRenderManager(Func config) + { + _articleRenderManagerConfig = config; + _articleRenderManager = null; + _articleRenderManagerType = null; + } + + /// + /// + /// Supply a type of . + /// + /// + /// May be omitted to use the default . + /// + /// + public void ConfigureOfflineManager( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type? type) + { + _offlineManagerConfig = null; + _offlineManager = null; + _offlineManagerType = type; + } + + /// + /// + /// Supply a function which returns an instance of . + /// + /// + /// May be omitted to use the default . + /// + /// + public void ConfigureOfflineManager(Func config) + { + _offlineManagerConfig = config; + _offlineManager = null; + _offlineManagerType = null; + } + + private void AddArticleRenderManager(IServiceCollection services) + { + if (ArticleRenderManager is not null) + { + services.AddScoped(_ => ArticleRenderManager); + } + else if (_articleRenderManagerConfig is not null) + { + services.AddScoped(_articleRenderManagerConfig); + } + else if (_articleRenderManagerType is not null) + { + services.AddScoped(typeof(IArticleRenderManager), _articleRenderManagerType); + } + else + { + services.AddScoped(); + } + } + + private void AddOfflineManager(IServiceCollection services) + { + if (OfflineManager is not null) + { + services.AddScoped(_ => OfflineManager); + } + else if (_offlineManagerConfig is not null) + { + services.AddScoped(_offlineManagerConfig); + } + else if (_offlineManagerType is not null) + { + services.AddScoped(typeof(IOfflineManager), _offlineManagerType); + } + else + { + services.AddScoped(); + } + } +} diff --git a/src/Client/Configuration/WikiBlazorOptions.cs b/src/Client/Configuration/WikiBlazorOptions.cs index 5eb9b1d..a9c185a 100644 --- a/src/Client/Configuration/WikiBlazorOptions.cs +++ b/src/Client/Configuration/WikiBlazorOptions.cs @@ -8,50 +8,6 @@ namespace Tavenem.Wiki.Blazor.Client; -/// -/// Gets the type of a component for a given wiki page. -/// -/// The page for which to get a component type. -/// The type of a component. -[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] -public delegate Type? GetArticleComponent(Page page); - -/// -/// Gets the render mode of a component for a given wiki page. -/// -/// The page for which to get a component's render mode. -/// -/// The render mode of a component, or for static rendering. -/// -public delegate IComponentRenderMode? GetArticleRenderMode(Page page); - -/// -/// Determines whether the given content may be edited locally. -/// -/// The title of the content to be edited. -/// The namespace of the content to be edited. -/// The domain of the content to be edited (if any). -/// -/// if the content can be edited locally; otherwise . -/// -/// -/// Locally means in the local instance, rather than via the . -/// -public delegate ValueTask CanEditOfflineFunc(string title, string wikiNamespace, string? domain); - -/// -/// A function which determines whether the given domain should always be retrieved from the local -/// , and never from the server. -/// -/// A wiki domain name. -/// -/// if the content should always be retrieved from the local ; if the content should be retrieved from the server -/// when possible. -/// -public delegate ValueTask IsOfflineDomainFunc(string domain); - /// /// Various customization and configuration options for the wiki system. /// @@ -84,37 +40,6 @@ public class WikiBlazorOptions : WikiOptions /// public const string DefaultLinkTemplate = "onmousemove=\"wikiblazor.showPreview(event, '{LINK}');\" onmouseleave=\"wikiblazor.hidePreview();\""; - /// - /// A function which gets the type of a component which should be displayed after the content of - /// the given wiki article (before the category list). - /// - public GetArticleComponent? ArticleEndMatter { get; set; } - - /// - /// A function which gets the render mode of the component indicated by . - /// - public GetArticleRenderMode? ArticleEndMatterRenderMode { get; set; } - - /// - /// A function which gets the type of a component which should be displayed before the content - /// of the given wiki article (after the subtitle). - /// - public GetArticleComponent? ArticleFrontMatter { get; set; } - - /// - /// A function which gets the render mode of the component indicated by . - /// - public GetArticleRenderMode? ArticleFrontMatterRenderMode { get; set; } - - /// - /// Can be set to a function which determines whether content may be edited locally. - /// - /// - /// If this function is not defined, no content may be edited locally (i.e. local content may - /// only be viewed). - /// - public CanEditOfflineFunc? CanEditOffline { get; set; } - /// /// /// The type of layout used when requesting a compact version of a wiki page. Wiki pages will be @@ -216,12 +141,6 @@ public class WikiBlazorOptions : WikiOptions /// public IComponentRenderMode? InteractiveRenderMode { get; set; } = RenderMode.InteractiveWebAssembly; - /// - /// A function which determines whether the given domain should always be retrieved from the - /// local , and never from the . - /// - public IsOfflineDomainFunc? IsOfflineDomain { get; set; } - /// /// /// The relative path to the site's login page. @@ -306,6 +225,9 @@ public virtual IServiceCollection Add(IServiceCollection services) { InteractiveRenderSettings.InteractiveRenderMode = InteractiveRenderMode; + services.TryAddScoped(); + services.TryAddScoped(); + services.TryAddScoped(_ => this); services.AddScoped(_ => this); @@ -315,102 +237,4 @@ public virtual IServiceCollection Add(IServiceCollection services) .AddScoped() .AddScoped(); } - - /// - /// Gets the type of a component which should be displayed after the content of the given wiki - /// article (before the category list). - /// - /// A wiki article. - /// - /// A component instance (or ). - /// - /// - /// The following parameters will be supplied to the component, if they exist: - /// - /// - /// Name - /// Value - /// - /// - /// Article - /// - /// The currently displayed (may be ). - /// - /// - /// - /// CanEdit - /// - /// A boolean indicating whether the current user has permission to edit the displayed . Note that this may be even if the article or the - /// user are . - /// - /// - /// - /// User - /// - /// The current (may be ). - /// - /// - /// - /// - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - public Type? GetArticleEndMatter(Page article) => ArticleEndMatter?.Invoke(article); - - /// - /// Gets the render mode of the component indicated by . - /// - /// A wiki article. - /// - /// The render mode of a component, or for static rendering. - /// - public IComponentRenderMode? GetArticleEndMatterRenderMode(Page article) => ArticleEndMatterRenderMode?.Invoke(article); - - /// - /// Gets the type of a component which should be displayed before the content of the given wiki - /// article (after the subtitle). - /// - /// A wiki article. - /// - /// A component instance (or ). - /// - /// - /// The following parameters will be supplied to the component, if they exist: - /// - /// - /// Name - /// Value - /// - /// - /// Article - /// - /// The currently displayed (may be ). - /// - /// - /// - /// CanEdit - /// - /// A boolean indicating whether the current user has permission to edit the displayed . Note that this may be even if the article or the - /// user are . - /// - /// - /// - /// User - /// - /// The current (may be ). - /// - /// - /// - /// - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - public Type? GetArticleFrontMatter(Page article) => ArticleFrontMatter?.Invoke(article); - - /// - /// Gets the render mode of the component indicated by . - /// - /// A wiki article. - /// - /// The render mode of a component, or for static rendering. - /// - public IComponentRenderMode? GetArticleFrontMatterRenderMode(Page article) => ArticleFrontMatterRenderMode?.Invoke(article); } diff --git a/src/Client/Services/ArticleRenderManager.cs b/src/Client/Services/ArticleRenderManager.cs new file mode 100644 index 0000000..6fe46ff --- /dev/null +++ b/src/Client/Services/ArticleRenderManager.cs @@ -0,0 +1,105 @@ +using Microsoft.AspNetCore.Components; +using System.Diagnostics.CodeAnalysis; + +namespace Tavenem.Wiki.Blazor.Client.Services; + +/// +/// A default implementation of which always returns . +/// +public class ArticleRenderManager : IArticleRenderManager +{ + /// + /// Gets the type of a component which should be displayed after the content of the given wiki + /// article (before the category list). + /// + /// The page for which to get a component type. + /// The type of a component. + /// + /// The following parameters will be supplied to the component, if they exist: + /// + /// + /// Name + /// Value + /// + /// + /// Article + /// + /// The currently displayed (may be ). + /// + /// + /// + /// CanEdit + /// + /// A boolean indicating whether the current user has permission to edit the displayed . Note that this may be even if the article or the + /// user are . + /// + /// + /// + /// User + /// + /// The current (may be ). + /// + /// + /// + /// + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + public Type? GetArticleEndMatter(Page page) => null; + + /// + /// Gets the render mode of the component indicated by . + /// + /// The page for which to get a component's render mode. + /// + /// The render mode of a component, or for static rendering. + /// + public IComponentRenderMode? GetArticleEndMatterRenderMode(Page page) => null; + + /// + /// Gets the type of a component which should be displayed before the content of the given wiki + /// article (after the subtitle). + /// + /// The page for which to get a component type. + /// The type of a component. + /// + /// The following parameters will be supplied to the component, if they exist: + /// + /// + /// Name + /// Value + /// + /// + /// Article + /// + /// The currently displayed (may be ). + /// + /// + /// + /// CanEdit + /// + /// A boolean indicating whether the current user has permission to edit the displayed . Note that this may be even if the article or the + /// user are . + /// + /// + /// + /// User + /// + /// The current (may be ). + /// + /// + /// + /// + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + public Type? GetArticleFrontMatter(Page page) => null; + + /// + /// Gets the render mode of the component indicated by . + /// + /// The page for which to get a component's render mode. + /// + /// The render mode of a component, or for static rendering. + /// + public IComponentRenderMode? GetArticleFrontMatterRenderMode(Page page) => null; +} diff --git a/src/Client/Services/IArticleRenderManager.cs b/src/Client/Services/IArticleRenderManager.cs new file mode 100644 index 0000000..704e103 --- /dev/null +++ b/src/Client/Services/IArticleRenderManager.cs @@ -0,0 +1,104 @@ +using Microsoft.AspNetCore.Components; +using System.Diagnostics.CodeAnalysis; + +namespace Tavenem.Wiki.Blazor.Client.Services; + +/// +/// Controls the layout and rendering of wiki articles. +/// +public interface IArticleRenderManager +{ + /// + /// Gets the type of a component which should be displayed after the content of the given wiki + /// article (before the category list). + /// + /// The page for which to get a component type. + /// The type of a component. + /// + /// The following parameters will be supplied to the component, if they exist: + /// + /// + /// Name + /// Value + /// + /// + /// Article + /// + /// The currently displayed (may be ). + /// + /// + /// + /// CanEdit + /// + /// A boolean indicating whether the current user has permission to edit the displayed . Note that this may be even if the article or the + /// user are . + /// + /// + /// + /// User + /// + /// The current (may be ). + /// + /// + /// + /// + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + Type? GetArticleEndMatter(Page page); + + /// + /// Gets the render mode of the component indicated by . + /// + /// The page for which to get a component's render mode. + /// + /// The render mode of a component, or for static rendering. + /// + IComponentRenderMode? GetArticleEndMatterRenderMode(Page page); + + /// + /// Gets the type of a component which should be displayed before the content of the given wiki + /// article (after the subtitle). + /// + /// The page for which to get a component type. + /// The type of a component. + /// + /// The following parameters will be supplied to the component, if they exist: + /// + /// + /// Name + /// Value + /// + /// + /// Article + /// + /// The currently displayed (may be ). + /// + /// + /// + /// CanEdit + /// + /// A boolean indicating whether the current user has permission to edit the displayed . Note that this may be even if the article or the + /// user are . + /// + /// + /// + /// User + /// + /// The current (may be ). + /// + /// + /// + /// + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + Type? GetArticleFrontMatter(Page page); + + /// + /// Gets the render mode of the component indicated by . + /// + /// The page for which to get a component's render mode. + /// + /// The render mode of a component, or for static rendering. + /// + IComponentRenderMode? GetArticleFrontMatterRenderMode(Page page); +} diff --git a/src/Client/Services/IOfflineManager.cs b/src/Client/Services/IOfflineManager.cs new file mode 100644 index 0000000..7872cb4 --- /dev/null +++ b/src/Client/Services/IOfflineManager.cs @@ -0,0 +1,36 @@ +using Tavenem.DataStorage; + +namespace Tavenem.Wiki.Blazor.Client.Services; + +/// +/// Determines whether actions can be taken offline. +/// +public interface IOfflineManager +{ + /// + /// Determines whether the given content may be edited locally. + /// + /// The title of the content to be edited. + /// The namespace of the content to be edited. + /// The domain of the content to be edited (if any). + /// + /// if the content can be edited locally; otherwise . + /// + /// + /// Locally means in the local instance, rather than via the . + /// + ValueTask CanEditOfflineAsync(string title, string wikiNamespace, string? domain); + + /// + /// A function which determines whether the given domain should always be retrieved from the local + /// , and never from the server. + /// + /// A wiki domain name. + /// + /// if the content should always be retrieved from the local ; if the content should be retrieved from the server + /// when possible. + /// + ValueTask IsOfflineDomainAsync(string domain); +} diff --git a/src/Client/Services/OfflineManager.cs b/src/Client/Services/OfflineManager.cs new file mode 100644 index 0000000..6d59b6a --- /dev/null +++ b/src/Client/Services/OfflineManager.cs @@ -0,0 +1,38 @@ +using Tavenem.DataStorage; + +namespace Tavenem.Wiki.Blazor.Client.Services; + +/// +/// A default implementation of which always returns . +/// +public class OfflineManager : IOfflineManager +{ + /// + /// Determines whether the given content may be edited locally. Always returns in the default implementation. + /// + /// The title of the content to be edited. + /// The namespace of the content to be edited. + /// The domain of the content to be edited (if any). + /// + /// . + /// + /// + /// Locally means in the local instance, rather than via the . + /// + public ValueTask CanEditOfflineAsync(string title, string wikiNamespace, string? domain) + => ValueTask.FromResult(false); + + /// + /// A function which determines whether the given domain should always be retrieved from the + /// local , and never from the server. Always returns in the default implementation. + /// + /// A wiki domain name. + /// + /// . + /// + public ValueTask IsOfflineDomainAsync(string domain) => ValueTask.FromResult(false); +} diff --git a/src/Client/Services/WikiDataService.cs b/src/Client/Services/WikiDataService.cs index 21f7350..ed4cd98 100644 --- a/src/Client/Services/WikiDataService.cs +++ b/src/Client/Services/WikiDataService.cs @@ -34,6 +34,8 @@ public class WikiDataService( private readonly IDataStore? _dataStore = serviceProvider.GetService(); private readonly IWikiGroupManager? _groupManager = serviceProvider.GetService(); private readonly ILogger _logger = loggerFactory.CreateLogger("Wiki"); + private readonly IPageManager? _pageManager = serviceProvider.GetService(); + private readonly IPermissionManager? _permissionManager = serviceProvider.GetService(); private readonly IWikiUserManager? _userManager = serviceProvider.GetService(); /// @@ -95,6 +97,7 @@ public async Task EditAsync(ClaimsPrincipal? user, EditRequest request) wikiOptions, _userManager, _groupManager, + _permissionManager, request.OriginalTitle ?? request.Title, wikiUser, true); @@ -190,6 +193,7 @@ public async Task EditAsync(ClaimsPrincipal? user, EditRequest request) wikiOptions, _userManager, _groupManager, + _permissionManager, wikiUser, request.Title, request.Markdown, @@ -202,6 +206,7 @@ public async Task EditAsync(ClaimsPrincipal? user, EditRequest request) allAllowedViewerGroups, null, request.OriginalTitle, + _pageManager, _cache); if (!success) { @@ -215,6 +220,7 @@ public async Task EditAsync(ClaimsPrincipal? user, EditRequest request) wikiOptions, _userManager, _groupManager, + _permissionManager, wikiUser, result.Title, null, @@ -227,6 +233,7 @@ public async Task EditAsync(ClaimsPrincipal? user, EditRequest request) allAllowedViewerGroups, request.Title, null, + _pageManager, _cache); if (!redirectSuccess) { @@ -299,11 +306,14 @@ public async Task GetArchiveAsync( if (hasDomain) { var domainPermission = WikiPermission.None; - if (wikiOptions.GetDomainPermission is not null) + if (_permissionManager is not null) { - domainPermission = await wikiOptions - .GetDomainPermission - .Invoke(wikiUser.Id, domain!); + var managerDomainPermission = await _permissionManager + .GetDomainPermissionAsync(wikiUser.Id, domain!); + if (managerDomainPermission.HasValue) + { + domainPermission = managerDomainPermission.Value; + } } if (wikiUser.AllowedViewDomains?.Contains(domain!) == true) { @@ -351,6 +361,7 @@ public async Task GetCategoryAsync( wikiOptions, _userManager, _groupManager, + _permissionManager, title, wikiUser); if (!response.Permission.HasFlag(WikiPermission.Read)) @@ -386,6 +397,7 @@ public async Task GetEditInfoAsync( wikiOptions, _userManager, _groupManager, + _permissionManager, title, wikiUser); if ((result.Permission & WikiPermission.ReadWrite) != WikiPermission.ReadWrite) @@ -436,6 +448,7 @@ public async Task GetGroupPageAsync(ClaimsPrincipal? user, string tit var result = await _dataStore.GetGroupPageAsync( wikiOptions, _groupManager, + _permissionManager, title, wikiUser); if (!result.Permission.HasFlag(WikiPermission.Read)) @@ -473,6 +486,7 @@ public async Task GetGroupPageAsync(ClaimsPrincipal? user, string tit wikiOptions, _userManager, _groupManager, + _permissionManager, request, wikiUser); if (result is null) @@ -565,6 +579,7 @@ public async Task GetItemAsync( wikiOptions, _userManager, _groupManager, + _permissionManager, title, firstTime, secondTime, @@ -576,6 +591,7 @@ public async Task GetItemAsync( wikiOptions, _userManager, _groupManager, + _permissionManager, title, wikiUser, noRedirect, @@ -587,6 +603,7 @@ public async Task GetItemAsync( wikiOptions, _userManager, _groupManager, + _permissionManager, title, wikiUser, noRedirect); @@ -638,6 +655,7 @@ public async Task> GetListAsync(SpecialListRequest request) wikiOptions, _userManager, _groupManager, + _permissionManager, title, wikiUser); if (!result.Exists @@ -680,6 +698,7 @@ public async Task> GetTalkAsync( wikiOptions, _userManager, _groupManager, + _permissionManager, title, wikiUser, noRedirect); @@ -774,6 +793,7 @@ public async Task GetUserPageAsync(ClaimsPrincipal? user, string title wikiOptions, _userManager, _groupManager, + _permissionManager, title, wikiUser); if (!result.Permission.HasFlag(WikiPermission.Read)) @@ -995,6 +1015,7 @@ public async Task PostTalkAsync( wikiOptions, _userManager, _groupManager, + _permissionManager, title, wikiUser); if (!result.Permission.HasFlag(WikiPermission.Read)) @@ -1130,6 +1151,7 @@ public async Task RestoreArchiveAsync(ClaimsPrincipal? user, Archive archive) var permission = await _dataStore.GetPermissionAsync( wikiOptions, _groupManager, + _permissionManager, page, wikiUser); if ((permission & RequiredPermissions) != RequiredPermissions) @@ -1139,7 +1161,7 @@ public async Task RestoreArchiveAsync(ClaimsPrincipal? user, Archive archive) } } - await archive.RestoreAsync(_dataStore, wikiOptions, wikiUser.Id, null, _cache); + await archive.RestoreAsync(_dataStore, wikiOptions, wikiUser.Id, null, _pageManager, _cache); } /// @@ -1234,6 +1256,7 @@ public async Task SearchAsync( await _dataStore.SearchWikiAsync( wikiOptions, _groupManager, + _permissionManager, request, wikiUser, _cache), @@ -1382,6 +1405,7 @@ public async Task UploadAsync( wikiOptions, _userManager, _groupManager, + _permissionManager, title, wikiUser, true); @@ -1492,6 +1516,7 @@ public async Task UploadAsync( wikiOptions, _userManager, _groupManager, + _permissionManager, options.OriginalTitle.Value, wikiUser, true); @@ -1565,6 +1590,7 @@ await originalWikiFile.UpdateAsync( allAllowedEditorGroups, allAllowedViewerGroups, options.LeaveRedirect ? title : null, + _pageManager, _cache); } catch (Exception ex) @@ -1621,6 +1647,7 @@ await wikiFile.UpdateAsync( allAllowedEditorGroups, allAllowedViewerGroups, null, + _pageManager, _cache); } catch (Exception ex) @@ -1654,6 +1681,7 @@ await wikiFile.UpdateAsync( allAllowedEditorGroups, allAllowedViewerGroups, null, + _pageManager, _cache); } catch (Exception ex) diff --git a/src/Client/Tavenem.Wiki.Blazor.Client.csproj b/src/Client/Tavenem.Wiki.Blazor.Client.csproj index 41ddbf7..32444a2 100644 --- a/src/Client/Tavenem.Wiki.Blazor.Client.csproj +++ b/src/Client/Tavenem.Wiki.Blazor.Client.csproj @@ -49,7 +49,7 @@ - + diff --git a/src/Server/Configuration/ServiceExtensions.cs b/src/Server/Configuration/ServiceExtensions.cs index 3a71a09..e478df2 100644 --- a/src/Server/Configuration/ServiceExtensions.cs +++ b/src/Server/Configuration/ServiceExtensions.cs @@ -1,15 +1,10 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection.Extensions; using System.Text.Json.Serialization.Metadata; using Tavenem.Wiki; using Tavenem.Wiki.Blazor; using Tavenem.Wiki.Blazor.Client; -using Tavenem.Wiki.Blazor.Client.Services; -using Tavenem.Wiki.Blazor.Server.Authorization; using Tavenem.Wiki.Blazor.Server.Configuration; -using Tavenem.Wiki.Blazor.Services; namespace Microsoft.Extensions.DependencyInjection; @@ -33,26 +28,10 @@ public static IServiceCollection AddWikiServer( this IServiceCollection services, WikiBlazorOptions? options = null) { - options ??= new(); - - InteractiveRenderSettings.InteractiveRenderMode = options.InteractiveRenderMode; - - services.TryAddScoped(_ => options); - - return services - .AddHttpContextAccessor() - .AddScoped() - .AddScoped() - .AddScoped() - .AddSingleton() - .AddSingleton() - .AddScoped(_ => options) - .AddTavenemFramework() - .AddScoped() - .AddScoped() - .AddScoped() - .AddMemoryCache() - .AddWikiJsonContext(); + var configuredOptions = options is null + ? new() + : new WikiBlazorServerOptions(options); + return configuredOptions.Add(services); } /// diff --git a/src/Server/Configuration/WikiBlazorServerOptions.cs b/src/Server/Configuration/WikiBlazorServerOptions.cs index 24fb7dc..3bb3d71 100644 --- a/src/Server/Configuration/WikiBlazorServerOptions.cs +++ b/src/Server/Configuration/WikiBlazorServerOptions.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Tavenem.Wiki.Blazor.Client; +using Tavenem.Wiki.Blazor.Client.Configuration; using Tavenem.Wiki.Blazor.Server.Authorization; using Tavenem.Wiki.Blazor.Services; @@ -9,7 +10,7 @@ namespace Tavenem.Wiki.Blazor.Server.Configuration; /// /// Options for configuring Tavenem.Wiki.Blazor.Server. /// -public class WikiBlazorServerOptions() : WikiBlazorOptions +public class WikiBlazorServerOptions() : WikiBlazorClientOptions { private IFileManager? _fileManager; private Func? _fileManagerConfig; @@ -113,11 +114,6 @@ public WikiBlazorServerOptions(WikiBlazorOptions other) : this() AboutPageTitle = other.AboutPageTitle; AppBar = other.AppBar; AppBarRenderMode = other.AppBarRenderMode; - ArticleEndMatter = other.ArticleEndMatter; - ArticleEndMatterRenderMode = other.ArticleEndMatterRenderMode; - ArticleFrontMatter = other.ArticleFrontMatter; - ArticleFrontMatterRenderMode = other.ArticleFrontMatterRenderMode; - CanEditOffline = other.CanEditOffline; CategoriesTitle = other.CategoriesTitle; CategoryNamespace = other.CategoryNamespace; CompactLayout = other.CompactLayout; @@ -135,21 +131,15 @@ public WikiBlazorServerOptions(WikiBlazorOptions other) : this() DefaultTableOfContentsTitle = other.DefaultTableOfContentsTitle; DomainArchivePermission = other.DomainArchivePermission; FileNamespace = other.FileNamespace; - GetDomainPermission = other.GetDomainPermission; GroupNamespace = other.GroupNamespace; HelpPageTitle = other.HelpPageTitle; InteractiveRenderMode = other.InteractiveRenderMode; - IsOfflineDomain = other.IsOfflineDomain; LinkTemplate = other.LinkTemplate; LoginPath = other.LoginPath; MainLayout = other.MainLayout; MainPageTitle = other.MainPageTitle; MaxFileSize = other.MaxFileSize; MinimumTableOfContentsHeadings = other.MinimumTableOfContentsHeadings; - OnCreated = other.OnCreated; - OnDeleted = other.OnDeleted; - OnEdited = other.OnEdited; - OnRenamed = other.OnRenamed; PolicyPageTitle = other.PolicyPageTitle; Postprocessors = other.Postprocessors; ScriptNamespace = other.ScriptNamespace; @@ -163,6 +153,18 @@ public WikiBlazorServerOptions(WikiBlazorOptions other) : this() WikiServerApiRoute = other.WikiServerApiRoute; } + /// + /// Constructs a new instance of . + /// + /// + /// An instance of from which to copy settings. + /// + public WikiBlazorServerOptions(WikiBlazorClientOptions other) : this(other as WikiBlazorOptions) + { + ArticleRenderManager = other.ArticleRenderManager; + OfflineManager = other.OfflineManager; + } + /// /// /// Supply a type of .