diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 3ef62ff220..20ac261daa 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -57,6 +57,8 @@ }, { "text": "TODO Application", + "isLazyExpandable": true, + "path": "tutorials/todo", "items": [ { "text": "Overview", @@ -75,6 +77,8 @@ }, { "text": "Book Store Application", + "isLazyExpandable": true, + "path": "tutorials/book-store", "items": [ { "text": "Overview", @@ -155,6 +159,8 @@ }, { "text": "Modular Monolith Application", + "isLazyExpandable": true, + "path": "tutorials/modular-crm/index.md", "items": [ { "text": "Overview", @@ -1893,7 +1899,170 @@ }, { "text": "Microservice Solution", - "path": "solution-templates/microservice" + "isLazyExpandable": true, + "path": "solution-templates/microservice", + "items":[ + { + "text": "Overview", + "path": "solution-templates/microservice" + }, + { + "text": "Solution Structure", + "path": "solution-templates/microservice/solution-structure.md" + }, + { + "text": "Main Components", + "items": [ + { + "text": "Overview", + "path": "solution-templates/microservice/main-components" + }, + { + "text": "Microservices", + "path": "solution-templates/microservice/microservices.md" + }, + { + "text": "API Gateways", + "path": "solution-templates/microservice/api-gateways.md" + }, + { + "text": "Web Applications", + "path": "solution-templates/microservice/web-applications.md" + }, + { + "text": "Mobile Applications", + "path": "solution-templates/microservice/mobile-applications.md" + } + ] + }, + { + "text": "Built-In Features", + "items": [ + { + "text": "Overview", + "path": "solution-templates/microservice/built-in-features.md" + }, + { + "text": "Authentication", + "path": "solution-templates/microservice/authentication.md" + }, + { + "text": "Database configurations", + "path": "solution-templates/microservice/database-configurations.md" + }, + { + "text": "Logging (with Serilog and Elasticsearch)", + "path": "solution-templates/microservice/logging.md" + }, + { + "text": "Monitoring (with Prometheus and Grafana)", + "path": "solution-templates/microservice/monitoring.md" + }, + { + "text": "Swagger integration", + "path": "solution-templates/microservice/swagger.md" + }, + { + "text": "Permission management", + "path": "solution-templates/microservice/permission-management.md" + }, + { + "text": "Feature management", + "path": "solution-templates/microservice/feature-management.md" + }, + { + "text": "Localization system", + "path": "solution-templates/microservice/localization-system.md" + }, + { + "text": "Background Jobs", + "path": "solution-templates/microservice/background-jobs.md" + }, + { + "text": "Background Workers", + "path": "solution-templates/microservice/background-workers.md" + }, + { + "text": "Distributed Locking", + "path": "solution-templates/microservice/distributed-locking.md" + }, + { + "text": "Distributed Cache", + "path": "solution-templates/microservice/distributed-cache.md" + }, + { + "text": "Multi-Tenancy", + "path": "solution-templates/microservice/multi-tenancy.md" + }, + { + "text": "BLOB Storing", + "path": "solution-templates/microservice/blob-storing.md" + }, + { + "text": "CORS configuration", + "path": "solution-templates/microservice/cors-configuration.md" + } + ] + }, + { + "text": "Communication", + "items":[ + { + "text": "Overview", + "path": "solution-templates/microservice/communication.md" + }, + { + "text": "HTTP API Calls", + "path": "solution-templates/microservice/http-api-calls.md" + }, + { + "text": "gRPC Calls", + "path": "solution-templates/microservice/grpc-calls.md" + }, + { + "text": "Distributed Events", + "path": "solution-templates/microservice/distributed-events.md" + } + ] + }, + { + "text": "Helm Charts and Kubernetes", + "path": "solution-templates/microservice/helm-charts-and-kubernetes.md" + }, + { + "text": "Guides", + "items": [ + { + "text": "Overview", + "path": "solution-templates/microservice/guides.md" + }, + { + "text": "Adding new microservices", + "path": "solution-templates/microservice/adding-new-microservices.md" + }, + { + "text": "Adding new applications", + "path": "solution-templates/microservice/adding-new-applications.md" + }, + { + "text": "Adding new API gateways", + "path": "solution-templates/microservice/adding-new-api-gateways.md" + }, + { + "text": "Mono-repo vs multiple repository approaches", + "path": "solution-templates/microservice/mono-repo-vs-multiple-repository-approaches.md" + }, + { + "text": "Authoring unit and integration tests", + "path": "solution-templates/microservice/authoring-unit-and-integration-tests.md" + }, + { + "text": "How to use with ABP Suite", + "path": "solution-templates/microservice/how-to-use-with-abp-suite.md" + } + ] + } + ] }, { "text": "Application Module", @@ -1981,6 +2150,8 @@ }, { "text": "IdentityServer", + "isLazyExpandable": true, + "path": "modules/identity-server.md", "items": [ { "text": "Overview", @@ -2003,6 +2174,8 @@ }, { "text": "OpenIddict", + "isLazyExpandable": true, + "path": "modules/openiddict.md", "items": [ { "text": "Overview", diff --git a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs index 231a8cddba..f19b9f41df 100644 --- a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs +++ b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs @@ -16,6 +16,9 @@ namespace Volo.Docs.Documents [JsonPropertyName("items")] public List Items { get; set; } + [JsonPropertyName("isLazyExpandable")] + public bool IsLazyExpandable { get; set; } + [JsonPropertyName("isIndex")] public bool IsIndex { get; set; } diff --git a/modules/docs/src/Volo.Docs.Web/Areas/Documents/DocumentNavigationController.cs b/modules/docs/src/Volo.Docs.Web/Areas/Documents/DocumentNavigationController.cs new file mode 100644 index 0000000000..cac52e8d5f --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Areas/Documents/DocumentNavigationController.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading.Tasks; +using Asp.Versioning; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Docs.Areas.Models.DocumentNavigation; +using Volo.Docs.Documents; +using Volo.Docs.Utils; + +namespace Volo.Docs.Areas.Documents; + +[RemoteService(Name = DocsRemoteServiceConsts.RemoteServiceName)] +[Area(DocsRemoteServiceConsts.ModuleName)] +[ControllerName("DocumentNavigation")] +[Route("/docs/document-navigation")] +public class DocumentNavigationController : AbpController +{ + private readonly IDocumentAppService _documentAppService; + private readonly IDocsLinkGenerator _docsLinkGenerator; + + public DocumentNavigationController(IDocumentAppService documentAppService, IDocsLinkGenerator docsLinkGenerator) + { + _documentAppService = documentAppService; + _docsLinkGenerator = docsLinkGenerator; + } + + [HttpGet] + [Route("")] + public virtual async Task GetNavigationAsync(GetNavigationNodeWithLinkModel input) + { + var navigationNode = await _documentAppService.GetNavigationAsync(new GetNavigationDocumentInput + { + LanguageCode = input.LanguageCode, + Version = input.Version, + ProjectId = input.ProjectId + }); + + NormalPath(navigationNode, input); + + return navigationNode; + } + + protected virtual void NormalPath(NavigationNode node, GetNavigationNodeWithLinkModel input) + { + if (node.HasChildItems) + { + foreach (var item in node.Items) + { + NormalPath(item, input); + } + } + + if (UrlHelper.IsExternalLink(node.Path)) + { + return; + } + + node.Path = RemoveFileExtensionFromPath(node.Path, input.ProjectFormat); + if (node.Path.IsNullOrWhiteSpace()) + { + node.Path = "javascript:;"; + return; + } + + node.Path = _docsLinkGenerator.GenerateLink(input.ProjectName, input.LanguageCode, input.RouteVersion, node.Path); + } + + private string RemoveFileExtensionFromPath(string path, string projectFormat) + { + if (path == null) + { + return null; + } + + return path.EndsWith("." + projectFormat) + ? path.Left(path.Length - projectFormat.Length - 1) + : path; + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs index 6d2cb8c9ba..e6c75d0161 100644 --- a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs +++ b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs @@ -72,11 +72,14 @@ namespace Volo.Docs.Areas.Documents.TagHelpers var isAnyNodeOpenedInThisLevel = IsAnyNodeOpenedInThisLevel(node); - node.Items?.ForEach(innerNode => + if (!node.IsLazyExpandable || isAnyNodeOpenedInThisLevel) { - content += GetParentNode(innerNode, isAnyNodeOpenedInThisLevel); - }); - + node.Items?.ForEach(innerNode => + { + content += GetParentNode(innerNode, isAnyNodeOpenedInThisLevel); + }); + } + var result = node.IsEmpty ? content : GetLeafNode(node, content); return result; @@ -121,6 +124,11 @@ namespace Volo.Docs.Areas.Documents.TagHelpers listItemCss += " selected-tree"; } + if (node.IsLazyExpandable) + { + listItemCss += " lazy-expand"; + } + string listInnerItem; if (node.Path.IsNullOrEmpty() && node.IsLeaf) { diff --git a/modules/docs/src/Volo.Docs.Web/Areas/Models/DocumentNavigation/GetNavigationNodeWithLinkModel.cs b/modules/docs/src/Volo.Docs.Web/Areas/Models/DocumentNavigation/GetNavigationNodeWithLinkModel.cs new file mode 100644 index 0000000000..085cc2de0e --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Areas/Models/DocumentNavigation/GetNavigationNodeWithLinkModel.cs @@ -0,0 +1,28 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; +using Volo.Docs.Language; +using Volo.Docs.Projects; + +namespace Volo.Docs.Areas.Models.DocumentNavigation; + +public class GetNavigationNodeWithLinkModel +{ + public Guid ProjectId { get; set; } + + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxVersionNameLength))] + public string Version { get; set; } + + [Required] + [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxLanguageCodeLength))] + public string LanguageCode { get; set; } + + [Required] + public string ProjectName { get; set; } + + [Required] + public string ProjectFormat { get; set; } + + [Required] + public string RouteVersion { get; set; } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index 976154c630..fb1f232cb3 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -53,6 +53,17 @@ @if (Model.LoadSuccess) { + diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js index 41896bb2fd..0cfbdff32f 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js @@ -1,5 +1,76 @@ +var doc = doc || {}; + (function ($) { $(function () { + + doc.lazyExpandableNavigation = { + isAllLoaded: false, + findNode : function(text, href, node){ + if(node.text === text && node.path === href && node.isLazyExpandable){ + return node; + } + if(node.items){ + for (let i = 0; i < node.items.length; i++) { + var result = doc.lazyExpandableNavigation.findNode(text, href, node.items[i]); + if(result){ + return result; + } + } + } + return null; + }, + renderNodeAsHtml : function($lazyLiElement, node, isRootLazyNode){ + if(node.isEmpty){ + return; + } + + var textCss = node.path === "javascript:;" ? "": "tree-toggle"; + var uiCss = isRootLazyNode ? "" : "style='display: none;'"; + var $ul = $(``); + var $li = $(`
  • `); + + $li.append(` ${node.text}`) + + if(node.isLazyExpandable){ + $li.addClass("lazy-expand"); + }else if(node.hasChildItems){ + node.items.forEach(function(item){ + doc.lazyExpandableNavigation.renderNodeAsHtml($li, item, false); + }); + } + + $ul.append($li); + $lazyLiElement.append($ul) + + window.Toc.helpers.initNavEvent(); + }, + loadAll : function(lazyLiElements){ + if(doc.lazyExpandableNavigation.isAllLoaded){ + return; + } + for(var i = 0; i < lazyLiElements.length; i++){ + var $li = $(lazyLiElements[i]); + if($li.has("ul").length === 0){ + var $a = $li.find("a"); + var node = doc.lazyExpandableNavigation.findNode($a.text(), $a.attr("href"), doc.project.navigation); + node.items.forEach(item => { + doc.lazyExpandableNavigation.renderNodeAsHtml($li, item, true); + }) + } + + var childLazyLiElements = $li.find("li.lazy-expand"); + if(childLazyLiElements.length > 0){ + doc.lazyExpandableNavigation.isAllLoaded = false; + doc.lazyExpandableNavigation.loadAll(childLazyLiElements); + } + + initLazyExpandNavigation(); + } + + doc.lazyExpandableNavigation.isAllLoaded = true; + } + } + var initNavigationFilter = function (navigationContainerId) { var $navigation = $('#' + navigationContainerId); @@ -23,6 +94,8 @@ return; } + doc.lazyExpandableNavigation.loadAll($navigation.find("li.lazy-expand")); + var filteredItems = $navigation .find('li > a') .filter(function () { @@ -98,6 +171,23 @@ }); }; + var initDocProject = function(){ + abp.ajax({ + type :"GET", + url: '/docs/document-navigation', + data: { + projectId: doc.project.id, + version: doc.project.version, + routeVersion: doc.project.routeVersion, + languageCode: doc.project.languageCode, + projectName: doc.project.name, + projectFormat: doc.project.format + } + }).done(data => { + doc.project.navigation = data; + }) + } + var initSocialShareLinks = function () { var pageHeader = $('.docs-body').find('h1, h2').first().text(); @@ -272,6 +362,26 @@ } }; + var initLazyExpandNavigation = function(){ + $("li .lazy-expand").off('click'); + $("li .lazy-expand").on('click', function(){ + var $this = $(this); + if($this.has("ul").length > 0){ + return; + } + + var $a = $this.find("a"); + var node = doc.lazyExpandableNavigation.findNode($a.text(), $a.attr("href") , doc.project.navigation); + node.items.forEach(item => { + doc.lazyExpandableNavigation.renderNodeAsHtml($this, item, true); + }) + + initLazyExpandNavigation(); + }); + } + + initDocProject(); + initNavigationFilter('sidebar-scroll'); initAnchorTags('.docs-page .docs-body'); @@ -279,6 +389,8 @@ initSocialShareLinks(); initSections(); + + initLazyExpandNavigation(); Element.prototype.querySelector = function (selector) { var result = $(this).find(decodeURI(selector)); diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Scripts/vs.js b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Scripts/vs.js index 74767bf56b..5208ac99c0 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Scripts/vs.js +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Scripts/vs.js @@ -1,23 +1,7 @@ (function ($) { $(function () { - $('li:not(.last-link) a.tree-toggle').click(function () { - $(this).parent().children('ul.tree').toggle(100); - $(this).closest('li').toggleClass('selected-tree'); - }); - - $('li:not(.last-link) span.plus-icon i.fa-chevron-right').click( - function () { - var $element = $(this).parent(); - var $filter = $('.docs-version #filter'); - if ($filter && $filter.val() != ''){ - return; - } - - $element.parent().children('ul.tree').toggle(100); - $element.closest('li').toggleClass('selected-tree'); - } - ); + window.Toc.helpers.initNavEvent(); var scrollTopBtn = $('.scroll-top-btn'); var enoughHeight = $('.docs-sidebar-wrapper > .docs-top').height(); @@ -159,6 +143,30 @@ $li.append($a); return $li; }; + + window.Toc.helpers.initNavEvent = function () { + $('li:not(.last-link) a.tree-toggle').off('click'); + $('li:not(.last-link) span.plus-icon i.fa-chevron-right').off('click'); + + $('li:not(.last-link) a.tree-toggle').click(function () { + $(this).parent().children('ul.tree').toggle(100); + $(this).closest('li').toggleClass('selected-tree'); + }); + + $('li:not(.last-link) span.plus-icon i.fa-chevron-right').click( + function () { + var $element = $(this).parent(); + var $filter = $('.docs-version #filter'); + + if ($filter && $filter.val() != ''){ + return; + } + + $element.parent().children('ul.tree').toggle(100); + $element.closest('li').toggleClass('selected-tree'); + } + ); + } function docsCriteria() { var docsContentWidth = $('.docs-content').width() - 74;