diff --git a/modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs b/modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs index d46ae6e021..8d54eaae90 100644 --- a/modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs +++ b/modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs @@ -33,6 +33,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; using Volo.Abp.Account; using Volo.Abp.Validation.Localization; +using Volo.Docs.Documents.FullSearch.Elastic; namespace VoloDocs.Web { @@ -71,6 +72,8 @@ namespace VoloDocs.Web options.RoutePrefix = null; }); + Configure(options => { options.Enable = true; }); + Configure(options => { options.ConnectionStrings.Default = configuration["ConnectionString"]; diff --git a/modules/docs/app/VoloDocs.Web/appsettings.json b/modules/docs/app/VoloDocs.Web/appsettings.json index 3d89a0a1e3..44eb9adc30 100644 --- a/modules/docs/app/VoloDocs.Web/appsettings.json +++ b/modules/docs/app/VoloDocs.Web/appsettings.json @@ -1,4 +1,8 @@ { "ConnectionString": "Server=localhost;Database=VoloDocs;Trusted_Connection=True;MultipleActiveResultSets=true", - "LogoUrl": "/assets/images/Logo.png" + "LogoUrl": "/assets/images/Logo.png", + + "ElasticSearch": { + "Url": "http://172.25.134.11:9200" + } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/IDocumentAdminAppService.cs b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/IDocumentAdminAppService.cs index 6adab29c74..1f18c0e64b 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/IDocumentAdminAppService.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/IDocumentAdminAppService.cs @@ -8,5 +8,7 @@ namespace Volo.Docs.Admin.Documents Task PullAllAsync(PullAllDocumentInput input); Task PullAsync(PullDocumentInput input); + + Task ReindexAsync(); } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs index 255fed246f..845062148b 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using Volo.Abp.Application.Services; using Volo.Abp.Caching; using Volo.Docs.Documents; +using Volo.Docs.Documents.FullSearch.Elastic; using Volo.Docs.Projects; namespace Volo.Docs.Admin.Documents @@ -18,16 +19,19 @@ namespace Volo.Docs.Admin.Documents private readonly IDocumentRepository _documentRepository; private readonly IDocumentSourceFactory _documentStoreFactory; private readonly IDistributedCache _documentUpdateCache; + private readonly IDocumentFullSearch _documentFullSearch; public DocumentAdminAppService(IProjectRepository projectRepository, IDocumentRepository documentRepository, IDocumentSourceFactory documentStoreFactory, - IDistributedCache documentUpdateCache) + IDistributedCache documentUpdateCache, + IDocumentFullSearch documentFullSearch) { _projectRepository = projectRepository; _documentRepository = documentRepository; _documentStoreFactory = documentStoreFactory; _documentUpdateCache = documentUpdateCache; + _documentFullSearch = documentFullSearch; } public async Task PullAllAsync(PullAllDocumentInput input) @@ -80,6 +84,15 @@ namespace Volo.Docs.Admin.Documents await UpdateDocumentUpdateInfoCache(sourceDocument); } + public async Task ReindexAsync() + { + var docs = await _documentRepository.GetListAsync(); + foreach (var doc in docs) + { + await _documentFullSearch.AddOrUpdateAsync(doc); + } + } + private async Task UpdateDocumentUpdateInfoCache(Document document) { var cacheKey = $"DocumentUpdateInfo{document.ProjectId}#{document.Name}#{document.LanguageCode}#{document.Version}"; diff --git a/modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocumentsAdminController.cs b/modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocumentsAdminController.cs index 75f610d3e5..1dd1327266 100644 --- a/modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocumentsAdminController.cs +++ b/modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocumentsAdminController.cs @@ -32,5 +32,12 @@ namespace Volo.Docs.Admin { return _documentAdminAppService.PullAsync(input); } + + [HttpPost] + [Route("Reindex")] + public Task ReindexAsync() + { + return _documentAdminAppService.ReindexAsync(); + } } } diff --git a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentSearchInput.cs b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentSearchInput.cs new file mode 100644 index 0000000000..b3e40367d0 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentSearchInput.cs @@ -0,0 +1,19 @@ +using System; + +namespace Volo.Docs.Documents +{ + public class DocumentSearchInput + { + public string Context { get; set; } + + public Guid ProjectId { get; set; } + + public string LanguageCode { get; set; } + + public string Version { get; set; } + + public int? SkipCount { get; set; } + + public int? MaxResultCount { get; set; } + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentSearchOutput.cs b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentSearchOutput.cs new file mode 100644 index 0000000000..43875db527 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentSearchOutput.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Volo.Docs.Documents +{ + public class DocumentSearchOutput + { + public DocumentSearchOutput() + { + Highlight = new List(); + } + + public string Name { get; set; } + + public string FileName { get; set; } + + public string Version { get; set; } + + public string LanguageCode { get; set; } + + public List Highlight { get; set; } + } +} diff --git a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/IDocumentAppService.cs b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/IDocumentAppService.cs index b1195e0254..68f62abce5 100644 --- a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/IDocumentAppService.cs +++ b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/IDocumentAppService.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Services; @@ -14,5 +15,7 @@ namespace Volo.Docs.Documents Task GetParametersAsync(GetParametersDocumentInput input); Task GetResourceAsync(GetDocumentResourceInput input); + + Task> SearchAsync(DocumentSearchInput input); } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs index 7ea04ec0c0..77bb82579d 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Volo.Abp.Caching; +using Volo.Docs.Documents.FullSearch.Elastic; using Volo.Docs.Projects; namespace Volo.Docs.Documents @@ -20,6 +21,8 @@ namespace Volo.Docs.Documents protected IDistributedCache ResourceCache { get; } protected IDistributedCache DocumentUpdateCache { get; } protected IHostEnvironment HostEnvironment { get; } + private readonly IDocumentFullSearch _documentFullSearch; + public DocumentAppService( IProjectRepository projectRepository, IDocumentRepository documentRepository, @@ -27,7 +30,8 @@ namespace Volo.Docs.Documents IDistributedCache languageCache, IDistributedCache resourceCache, IDistributedCache documentUpdateCache, - IHostEnvironment hostEnvironment) + IHostEnvironment hostEnvironment, + IDocumentFullSearch documentFullSearch) { _projectRepository = projectRepository; _documentRepository = documentRepository; @@ -36,6 +40,7 @@ namespace Volo.Docs.Documents ResourceCache = resourceCache; DocumentUpdateCache = documentUpdateCache; HostEnvironment = hostEnvironment; + _documentFullSearch = documentFullSearch; } public virtual async Task GetAsync(GetDocumentInput input) @@ -124,6 +129,22 @@ namespace Volo.Docs.Documents ); } + public async Task> SearchAsync(DocumentSearchInput input) + { + var project = await _projectRepository.GetAsync(input.ProjectId); + + var esDocs = await _documentFullSearch.SearchAsync(input.Context, project.Id, input.LanguageCode, input.Version); + + return esDocs.Select(esDoc => new DocumentSearchOutput//TODO: auto map + { + Name = esDoc.Name, + FileName = esDoc.FileName, + Version = esDoc.Version, + LanguageCode = esDoc.LanguageCode, + Highlight = esDoc.Highlight + }).ToList(); + } + public async Task GetParametersAsync(GetParametersDocumentInput input) { var project = await _projectRepository.GetAsync(input.ProjectId); diff --git a/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj b/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj index 234832ef1a..c2540754a3 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj +++ b/modules/docs/src/Volo.Docs.Domain/Volo.Docs.Domain.csproj @@ -18,6 +18,7 @@ + diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs index 81f681403d..ba2ee6018d 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs @@ -1,10 +1,14 @@ using System; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp; using Volo.Abp.Domain; using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.Threading; using Volo.Abp.VirtualFileSystem; using Volo.Docs.Documents; +using Volo.Docs.Documents.FullSearch.Elastic; using Volo.Docs.FileSystem.Documents; using Volo.Docs.GitHub.Documents; using Volo.Docs.Localization; @@ -43,5 +47,17 @@ namespace Volo.Docs client.Timeout = TimeSpan.FromMilliseconds(15000); }); } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + using (var scope = context.ServiceProvider.CreateScope()) + { + if (scope.ServiceProvider.GetRequiredService>().Value.Enable) + { + var documentFullSearch = scope.ServiceProvider.GetRequiredService(); + AsyncHelper.RunSync(() => documentFullSearch.CreateIndexIfNeededAsync()); + } + } + } } } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DefaultElasticClientProvider.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DefaultElasticClientProvider.cs new file mode 100644 index 0000000000..5db064fd5f --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DefaultElasticClientProvider.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using Nest; +using Volo.Abp.DependencyInjection; + +namespace Volo.Docs.Documents.FullSearch.Elastic +{ + public class DefaultElasticClientProvider : IElasticClientProvider, ISingletonDependency + { + protected readonly DocsElasticSearchOptions Options; + protected readonly IConfiguration Configuration; + + public DefaultElasticClientProvider(IOptions options, IConfiguration configuration) + { + Configuration = configuration; + Options = options.Value; + } + + public virtual IElasticClient GetClient() + { + var node = new Uri(Configuration["ElasticSearch:Url"]); + var settings = new ConnectionSettings(node).DefaultIndex(Options.IndexName); + return new ElasticClient(settings); + } + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DocsElasticSearchOptions.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DocsElasticSearchOptions.cs new file mode 100644 index 0000000000..40bbf66b08 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DocsElasticSearchOptions.cs @@ -0,0 +1,15 @@ +namespace Volo.Docs.Documents.FullSearch.Elastic +{ + public class DocsElasticSearchOptions + { + public DocsElasticSearchOptions() + { + Enable = false; + IndexName = "abp_documents"; + } + + public bool Enable { get; set; } + + public string IndexName { get; set; } + } +} diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DocumentChangedEventHandler.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DocumentChangedEventHandler.cs new file mode 100644 index 0000000000..3d1bd50a59 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/DocumentChangedEventHandler.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.EventBus; + +namespace Volo.Docs.Documents.FullSearch.Elastic +{ + public class DocumentChangedEventHandler : ILocalEventHandler>, + ILocalEventHandler>, + ILocalEventHandler>, + ITransientDependency + { + private readonly DocsElasticSearchOptions _options; + private readonly IDocumentFullSearch _documentFullSearch; + + public DocumentChangedEventHandler(IDocumentFullSearch documentFullSearch, IOptions options) + { + _documentFullSearch = documentFullSearch; + _options = options.Value; + } + + public async Task HandleEventAsync(EntityCreatedEventData eventData) + { + await AddOrUpdate(eventData.Entity); + } + + public async Task HandleEventAsync(EntityUpdatedEventData eventData) + { + await AddOrUpdate(eventData.Entity); + } + + private async Task AddOrUpdate(Document document) + { + if (_options.Enable) + { + await _documentFullSearch.AddOrUpdateAsync(document); + } + } + + public async Task HandleEventAsync(EntityDeletedEventData eventData) + { + if (_options.Enable) + { + await _documentFullSearch.DeleteAsync(eventData.Entity.Id); + } + } + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/ElasticDocumentFullSearch.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/ElasticDocumentFullSearch.cs new file mode 100644 index 0000000000..2fdbe27d1a --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/ElasticDocumentFullSearch.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Elasticsearch.Net; +using Microsoft.Extensions.Options; +using Nest; +using Volo.Abp; +using Volo.Abp.Domain.Services; + +namespace Volo.Docs.Documents.FullSearch.Elastic +{ + public class ElasticDocumentFullSearch : DomainService, IDocumentFullSearch + { + private readonly IElasticClientProvider _clientProvider; + private readonly DocsElasticSearchOptions _options; + + public ElasticDocumentFullSearch(IElasticClientProvider clientProvider, IOptions options) + { + _clientProvider = clientProvider; + _options = options.Value; + + if (!_options.Enable) + { + //TODO: localize message + throw new BusinessException("", ""); + } + } + + public async Task CreateIndexIfNeededAsync(CancellationToken cancellationToken = default) + { + var client = _clientProvider.GetClient(); + + var existsResponse = await client.Indices.ExistsAsync(_options.IndexName, ct: cancellationToken); + + HandleError(existsResponse); + + if (!existsResponse.Exists) + { + HandleError(await client.Indices.CreateAsync(_options.IndexName, c => c + .Map(m => + { + return m + .Properties(p => p + .Keyword(x => x.Name(d => d.Id)) + .Keyword(x => x.Name(d => d.ProjectId)) + .Keyword(x => x.Name(d => d.Name)) + .Keyword(x => x.Name(d => d.FileName)) + .Keyword(x => x.Name(d => d.Version)) + .Keyword(x => x.Name(d => d.LanguageCode)) + .Text(x => x.Name(d => d.Content))); + }), cancellationToken)); + } + } + + public async Task AddOrUpdateAsync(Document document, CancellationToken cancellationToken = default) + { + var client = _clientProvider.GetClient(); + + var existsResponse = await client.DocumentExistsAsync(DocumentPath.Id(document.Id), + x => x.Index(_options.IndexName), cancellationToken); + + HandleError(existsResponse); + + var esDocument = new EsDocument + { + Id = document.Id, + ProjectId = document.ProjectId, + Name = document.Name, + FileName = document.FileName, + Content = document.Content, + LanguageCode = document.LanguageCode, + Version = document.Version + }; + + if (!existsResponse.Exists) + { + HandleError(await client.IndexAsync(esDocument, + x => x.Id(document.Id).Index(_options.IndexName), cancellationToken)); + } + else + { + HandleError(await client.UpdateAsync(DocumentPath.Id(document.Id), + x => x.Doc(esDocument).Index(_options.IndexName), cancellationToken)); + } + + } + + public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) + { + HandleError(await _clientProvider.GetClient() + .DeleteAsync(DocumentPath.Id(id), x => x.Index(_options.IndexName), cancellationToken)); + } + + public async Task> SearchAsync(string context, Guid projectId, string languageCode, + string version, int? skipCount = null, int? maxResultCount = null, + CancellationToken cancellationToken = default) + { + var request = new SearchRequest + { + Size = maxResultCount ?? 10, + From = skipCount ?? 0, + Query = new BoolQuery + { + Must = new QueryContainer[] + { + new MatchQuery + { + Field = "content", + Query = context + } + }, + Filter = new QueryContainer[] + { + new BoolQuery + { + Must = new QueryContainer[] + { + new TermQuery + { + Field = "projectId", + Value = projectId + }, + new TermQuery + { + Field = "version", + Value = version + }, + new TermQuery + { + Field = "languageCode", + Value = languageCode + } + } + } + } + }, + Highlight = new Highlight + { + PreTags = new[] { "" }, + PostTags = new[] { "" }, + Fields = new Dictionary + { + { + "content", new HighlightField() + } + } + } + }; + + //var json = _clientProvider.GetClient().RequestResponseSerializer.SerializeToString(request); + var response = await _clientProvider.GetClient().SearchAsync(request, cancellationToken); + + HandleError(response); + + var docs = new List(); + foreach (var hit in response.Hits) + { + var doc = hit.Source; + if (hit.Highlight.ContainsKey("content")) + { + doc.Highlight = new List(); + doc.Highlight.AddRange(hit.Highlight["content"]); + } + docs.Add(doc); + } + + return docs; + } + + protected void HandleError(IElasticsearchResponse response) + { + if (!response.ApiCall.Success) + { + throw response.ApiCall.OriginalException; + } + } + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/EsDocument.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/EsDocument.cs new file mode 100644 index 0000000000..df78744361 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/EsDocument.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Docs.Documents.FullSearch.Elastic +{ + public class EsDocument + { + public EsDocument() + { + Highlight = new List(); + } + + public Guid Id { get; set; } + + public Guid ProjectId { get; set; } + + public string Name { get; set; } + + public string FileName { get; set; } + + public string Version { get; set; } + + public string LanguageCode { get; set; } + + public string Content { get; set; } + + public List Highlight { get; set; } + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/IDocumentFullSearch.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/IDocumentFullSearch.cs new file mode 100644 index 0000000000..615f5be4b4 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/IDocumentFullSearch.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Volo.Docs.Documents.FullSearch.Elastic +{ + public interface IDocumentFullSearch + { + Task CreateIndexIfNeededAsync(CancellationToken cancellationToken = default); + + Task AddOrUpdateAsync(Document document, CancellationToken cancellationToken = default); + + Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); + + Task> SearchAsync(string context, Guid projectId, string languageCode, + string version, int? skipCount = null, int? maxResultCount = null, + CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/IElasticClientProvider.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/IElasticClientProvider.cs new file mode 100644 index 0000000000..e1424bfbd6 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/IElasticClientProvider.cs @@ -0,0 +1,9 @@ +using Nest; + +namespace Volo.Docs.Documents.FullSearch.Elastic +{ + public interface IElasticClientProvider + { + IElasticClient GetClient(); + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.HttpApi/Volo/Docs/Documents/DocsDocumentController.cs b/modules/docs/src/Volo.Docs.HttpApi/Volo/Docs/Documents/DocsDocumentController.cs index a066a92f23..3384ee3314 100644 --- a/modules/docs/src/Volo.Docs.HttpApi/Volo/Docs/Documents/DocsDocumentController.cs +++ b/modules/docs/src/Volo.Docs.HttpApi/Volo/Docs/Documents/DocsDocumentController.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp; using Volo.Abp.AspNetCore.Mvc; @@ -46,6 +47,13 @@ namespace Volo.Docs.Documents return DocumentAppService.GetResourceAsync(input); } + [HttpPost] + [Route("search")] + public Task> SearchAsync(DocumentSearchInput input) + { + return DocumentAppService.SearchAsync(input); + } + [HttpGet] [Route("parameters")] public Task GetParametersAsync(GetParametersDocumentInput input) diff --git a/modules/docs/src/Volo.Docs.Web/DocsWebModule.cs b/modules/docs/src/Volo.Docs.Web/DocsWebModule.cs index 5b7309a310..26031f53de 100644 --- a/modules/docs/src/Volo.Docs.Web/DocsWebModule.cs +++ b/modules/docs/src/Volo.Docs.Web/DocsWebModule.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -61,6 +61,7 @@ namespace Volo.Docs options.Conventions.AddPageRoute("/Documents/Project/Index", routePrefix + "{projectName}"); options.Conventions.AddPageRoute("/Documents/Project/Index", routePrefix + "{languageCode}/{projectName}"); options.Conventions.AddPageRoute("/Documents/Project/Index", routePrefix + "{languageCode}/{projectName}/{version}/{*documentName}"); + options.Conventions.AddPageRoute("/Documents/Search", routePrefix + "search/{languageCode}/{projectName}/{version}/{*keyword}"); }); context.Services.AddAutoMapperObjectMapper(); 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 7029cc410a..9da501c102 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 @@ -135,6 +135,18 @@ } +
+
+
+
+ +
+ + +
+
+
+
@@ -142,7 +154,7 @@
- +
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 9cbfd547ab..8f53f1cf6e 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,4 +1,4 @@ -(function ($) { +(function ($) { $(function () { var initNavigationFilter = function (navigationContainerId) { @@ -56,13 +56,19 @@ }); }; - $(".docs-page .docs-tree-list input[type='search']").keyup(function (e) { + $("#filter").keyup(function (e) { filterDocumentItems(e.target.value); if (e.key === "Enter") { gotoFilteredDocumentIfThereIsOnlyOne(); } }); + + $("#fullsearch").keyup(function (e) { + if (e.key === "Enter") { + window.open($(this).data("fullsearch-url") + this.value); + } + }); }; var initAnchorTags = function (container) { diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml new file mode 100644 index 0000000000..0e518ddf0e --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml @@ -0,0 +1,67 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Layout +@using Volo.Abp.AspNetCore.Mvc.UI.Theming +@using Volo.Docs.Localization +@using Volo.Docs.Pages.Documents +@inject IHtmlLocalizer L +@inject IThemeManager ThemeManager +@inject IPageLayout PageLayout +@model SearchModel +@{ + Layout = ThemeManager.CurrentTheme.GetEmptyLayout(); +} +@section styles { + +} + +
+
+ + +
+ +
+
Search results
+ + @foreach (var docs in Model.SearchOutputs) + { +
+
+
+ + @functions + { + string RemoveFileExtensionFromPath(string path) + { + if (path == null) + { + return null; + } + + return path.EndsWith("." + @Model.Project.Format) + ? path.Left(path.Length - Model.Project.Format.Length - 1) + : path; + } + } + + +

@RemoveFileExtensionFromPath(docs.Name)

+ + @foreach (var highlight in docs.Highlight) + { +

@Html.Raw(highlight)

+ } + +
+
+
+ } +
+
\ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml.cs new file mode 100644 index 0000000000..531f0201f1 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Search.cshtml.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Volo.Docs.Documents; +using Volo.Docs.HtmlConverting; +using Volo.Docs.Models; +using Volo.Docs.Projects; + +namespace Volo.Docs.Pages.Documents +{ + public class SearchModel : PageModel + { + [BindProperty(SupportsGet = true)] + public string ProjectName { get; set; } + + [BindProperty(SupportsGet = true)] + public string Version { get; set; } + + [BindProperty(SupportsGet = true)] + public string LanguageCode { get; set; } + + [BindProperty(SupportsGet = true)] + public string KeyWord { get; set; } + + public ProjectDto Project { get; set; } + + private readonly IProjectAppService _projectAppService; + private readonly IDocumentAppService _documentAppService; + + public SearchModel(IProjectAppService projectAppService, + IDocumentAppService documentAppService) + { + _projectAppService = projectAppService; + _documentAppService = documentAppService; + } + + public List SearchOutputs { get; set; } = new List(); + + public async Task OnGetAsync(string keyword) + { + KeyWord = keyword; + + Project = await _projectAppService.GetAsync(ProjectName); + + var output = await _projectAppService.GetVersionsAsync(Project.ShortName); + + var versions = output.Items.ToList(); + + if (versions.Any() && string.Equals(Version, DocsAppConsts.Latest, StringComparison.OrdinalIgnoreCase)) + { + Version = versions.First().Name; + } + + SearchOutputs = await _documentAppService.SearchAsync(new DocumentSearchInput + { + ProjectId = Project.Id, + Context = KeyWord, + LanguageCode = LanguageCode, + Version = Version + }); + + return Page(); + } + } +} \ No newline at end of file