Browse Source

Handle anchor links

pull/22430/head
liangshiwei 1 year ago
parent
commit
e96f601641
  1. 2
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/DeletePdfFileInput.cs
  2. 2
      modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Projects/ProjectAdminAppService.cs
  3. 2
      modules/docs/src/Volo.Docs.Admin.HttpApi.Client/ClientProxies/docs-admin-generate-proxy.json
  4. 2
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/generatePdf.js
  5. 2
      modules/docs/src/Volo.Docs.Admin.Web/wwwroot/client-proxies/docs-admin-proxy.js
  6. 14
      modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs
  7. 0
      modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Utils/UrlHelper.cs
  8. 2
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Pdf/BlobDocumentPdfFileStore.cs
  9. 1
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Pdf/DocsDocumentPdfGeneratorOptions.cs
  10. 113
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Pdf/DocumentPdfGenerator.cs
  11. 6
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Pdf/IText/ITextPdfRenderer.cs
  12. 65
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Pdf/Markdig/AnchorLinkRenderer.cs
  13. 27
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Pdf/Markdig/AnchorLinkResolverExtension.cs

2
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/DeletePdfFileInput.cs

@ -4,7 +4,7 @@ namespace Volo.Docs.Admin.Projects;
public class DeletePdfFileInput public class DeletePdfFileInput
{ {
public Guid Id { get; set; } public Guid ProjectId { get; set; }
public string Version { get; set; } public string Version { get; set; }

2
modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Projects/ProjectAdminAppService.cs

@ -185,7 +185,7 @@ namespace Volo.Docs.Admin.Projects
public virtual async Task DeletePdfFileAsync(DeletePdfFileInput input) public virtual async Task DeletePdfFileAsync(DeletePdfFileInput input)
{ {
await _documentPdfCache.RemoveAsync(DocsDocumentPdfCacheItem.CalculateCacheKey(input.Id, input.Version, input.LanguageCode)); await _documentPdfCache.RemoveAsync(DocsDocumentPdfCacheItem.CalculateCacheKey(input.ProjectId, input.Version, input.LanguageCode));
} }
} }
} }

2
modules/docs/src/Volo.Docs.Admin.HttpApi.Client/ClientProxies/docs-admin-generate-proxy.json

@ -1021,7 +1021,7 @@
"parameters": [ "parameters": [
{ {
"nameOnMethod": "input", "nameOnMethod": "input",
"name": "Id", "name": "ProjectId",
"jsonName": null, "jsonName": null,
"type": "System.Guid", "type": "System.Guid",
"typeSimple": "string", "typeSimple": "string",

2
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/generatePdf.js

@ -24,6 +24,7 @@ $(function () {
$("#GeneratePdfBtn").click(function () { $("#GeneratePdfBtn").click(function () {
var $btn = $(this); var $btn = $(this);
$btn.buttonBusy(true); $btn.buttonBusy(true);
$("#GenerateAndDownloadPdfBtn").buttonBusy(true);
var input = { var input = {
projectId: $("#ProjectId").val(), projectId: $("#ProjectId").val(),
version: $("#Version").val(), version: $("#Version").val(),
@ -36,6 +37,7 @@ $(function () {
if (jqXHR.status === 200) { if (jqXHR.status === 200) {
abp.message.success("PDF generated successfully."); abp.message.success("PDF generated successfully.");
$btn.buttonBusy(false); $btn.buttonBusy(false);
$("#GenerateAndDownloadPdfBtn").buttonBusy(false);
} else { } else {
abp.ajax.handleErrorStatusCode(jqXHR.status); abp.ajax.handleErrorStatusCode(jqXHR.status);
} }

2
modules/docs/src/Volo.Docs.Admin.Web/wwwroot/client-proxies/docs-admin-proxy.js

@ -138,7 +138,7 @@
volo.docs.admin.projectsAdmin.deletePdfFile = function(input, ajaxParams) { volo.docs.admin.projectsAdmin.deletePdfFile = function(input, ajaxParams) {
return abp.ajax($.extend(true, { return abp.ajax($.extend(true, {
url: abp.appPath + 'api/docs/admin/projects/DeletePdfFile' + abp.utils.buildQueryString([{ name: 'id', value: input.id }, { name: 'version', value: input.version }, { name: 'languageCode', value: input.languageCode }]) + '', url: abp.appPath + 'api/docs/admin/projects/DeletePdfFile' + abp.utils.buildQueryString([{ name: 'projectId', value: input.projectId }, { name: 'version', value: input.version }, { name: 'languageCode', value: input.languageCode }]) + '',
type: 'DELETE', type: 'DELETE',
dataType: null dataType: null
}, ajaxParams)); }, ajaxParams));

14
modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs

@ -17,6 +17,7 @@ using Volo.Docs.Caching;
using Volo.Docs.Common.Projects; using Volo.Docs.Common.Projects;
using Volo.Docs.Documents.FullSearch.Elastic; using Volo.Docs.Documents.FullSearch.Elastic;
using Volo.Docs.Projects; using Volo.Docs.Projects;
using Volo.Docs.Utils;
using Volo.Extensions; using Volo.Extensions;
namespace Volo.Docs.Documents namespace Volo.Docs.Documents
@ -282,7 +283,7 @@ namespace Volo.Docs.Documents
private List<string> GetDocumentLinks(NavigationNode node, List<string> documentUrls, string prefix, private List<string> GetDocumentLinks(NavigationNode node, List<string> documentUrls, string prefix,
string shortName, DocumentWithoutDetails document) string shortName, DocumentWithoutDetails document)
{ {
if (!IsExternalLink(node.Path)) if (!UrlHelper.IsExternalLink(node.Path))
{ {
documentUrls.AddIfNotContains( documentUrls.AddIfNotContains(
NormalizePath(prefix, node.Path, shortName, document) NormalizePath(prefix, node.Path, shortName, document)
@ -320,17 +321,6 @@ namespace Volo.Docs.Documents
: path; : path;
} }
private static bool IsExternalLink(string path)
{
if (path.IsNullOrEmpty())
{
return false;
}
return path.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
path.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
}
public virtual async Task<DocumentParametersDto> GetParametersAsync(GetParametersDocumentInput input) public virtual async Task<DocumentParametersDto> GetParametersAsync(GetParametersDocumentInput input)
{ {
var project = await _projectRepository.GetAsync(input.ProjectId); var project = await _projectRepository.GetAsync(input.ProjectId);

0
modules/docs/src/Volo.Docs.Web/Utils/UrlHelper.cs → modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Utils/UrlHelper.cs

2
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Pdf/BlobDocumentPdfFileStore.cs

@ -28,7 +28,7 @@ public class BlobDocumentPdfFileStore : IDocumentPdfFileStore, ITransientDepende
public virtual async Task SetAsync(Project project, string version, string languageCode, Stream stream) public virtual async Task SetAsync(Project project, string version, string languageCode, Stream stream)
{ {
await BlobContainer.SaveAsync(Options.Value.CalculatePdfFileName(project, version, languageCode), stream); await BlobContainer.SaveAsync(Options.Value.CalculatePdfFileName(project, version, languageCode), stream, true);
await Cache.SetAsync(DocsDocumentPdfCacheItem.CalculateCacheKey(project.Id, version, languageCode), new DocsDocumentPdfCacheItem(), await Cache.SetAsync(DocsDocumentPdfCacheItem.CalculateCacheKey(project.Id, version, languageCode), new DocsDocumentPdfCacheItem(),
new DistributedCacheEntryOptions new DistributedCacheEntryOptions

1
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Pdf/DocsDocumentPdfGeneratorOptions.cs

@ -64,7 +64,6 @@ public class DocsDocumentPdfGeneratorOptions
pre { pre {
position: relative; position: relative;
padding: 16px; padding: 16px;
padding-top: calc(16px * 2.5);
border-radius: 8px; border-radius: 8px;
border: 1px solid #e2e8f0; border: 1px solid #e2e8f0;
} }

113
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Pdf/DocumentPdfGenerator.cs

@ -4,7 +4,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using Markdig; using Markdig;
@ -14,10 +13,10 @@ using Microsoft.Extensions.Options;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Content; using Volo.Abp.Content;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading; using Volo.Docs.Documents.Pdf.Markdig;
using Volo.Docs.Documents.Pdf.IText;
using Volo.Docs.Documents.Rendering; using Volo.Docs.Documents.Rendering;
using Volo.Docs.Projects; using Volo.Docs.Projects;
using Volo.Docs.Utils;
using Volo.Extensions; using Volo.Extensions;
namespace Volo.Docs.Documents.Pdf; namespace Volo.Docs.Documents.Pdf;
@ -33,10 +32,7 @@ public class DocumentPdfGenerator : IDocumentPdfGenerator, ITransientDependency
protected ILogger<DocumentPdfGenerator> Logger { get; set; } protected ILogger<DocumentPdfGenerator> Logger { get; set; }
protected IDocumentSource DocumentSource { get; set; } protected IDocumentSource DocumentSource { get; set; }
protected DocumentParams DocumentParams { get; set; } protected DocumentParams DocumentParams { get; set; }
protected List<PdfDocumentNode> AllPdfDocumentNodes { get; set; } = []; protected List<PdfDocumentNode> AllPdfDocumentNodes { get; } = [];
protected MarkdownPipeline MarkdownPipeline { get; } = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
private readonly SemaphoreSlim _syncSemaphore;
public DocumentPdfGenerator( public DocumentPdfGenerator(
IDocumentSourceFactory documentStoreFactory, IDocumentSourceFactory documentStoreFactory,
@ -53,33 +49,29 @@ public class DocumentPdfGenerator : IDocumentPdfGenerator, ITransientDependency
DocumentPdfFileStore = documentPdfFileStore; DocumentPdfFileStore = documentPdfFileStore;
PdfRenderer = pdfRenderer; PdfRenderer = pdfRenderer;
Logger = NullLogger<DocumentPdfGenerator>.Instance; Logger = NullLogger<DocumentPdfGenerator>.Instance;
_syncSemaphore = new SemaphoreSlim(1, 1);
} }
public virtual async Task<IRemoteStreamContent> GenerateAsync(Project project, string version, string languageCode) public virtual async Task<IRemoteStreamContent> GenerateAsync(Project project, string version, string languageCode)
{ {
using (await _syncSemaphore.LockAsync()) var fileName = Options.Value.CalculatePdfFileName(project, version, languageCode);
{ var fileStream = await DocumentPdfFileStore.GetOrNullAsync(project, version, languageCode);
var fileName = Options.Value.CalculatePdfFileName(project, version, languageCode);
var fileStream = await DocumentPdfFileStore.GetOrNullAsync(project, version, languageCode);
if (fileStream != null)
{
return new RemoteStreamContent(fileStream, fileName, "application/pdf");
}
DocumentSource = DocumentStoreFactory.Create(project.DocumentStoreType);
DocumentParams = await GetDocumentParamsAsync(project, version, languageCode);
var navigation = await GetNavigationAsync(project, version, languageCode);
await SetAllPdfDocumentNodesAsync(navigation.Items, project, version, languageCode); if (fileStream != null)
var htmlContent = await ConvertDocumentsToHtmlAsync(AllPdfDocumentNodes); {
return new RemoteStreamContent(fileStream, fileName, "application/pdf");
}
var pdfStream = await PdfRenderer.GeneratePdfAsync(htmlContent, AllPdfDocumentNodes); DocumentSource = DocumentStoreFactory.Create(project.DocumentStoreType);
await DocumentPdfFileStore.SetAsync(project, version, languageCode, pdfStream); DocumentParams = await GetDocumentParamsAsync(project, version, languageCode);
var navigation = await GetNavigationAsync(project, version, languageCode);
await SetAllPdfDocumentNodesAsync(navigation.Items, project, version, languageCode);
var htmlContent = await ConvertDocumentsToHtmlAsync(AllPdfDocumentNodes);
return new RemoteStreamContent(pdfStream, fileName, "application/pdf"); var pdfStream = await PdfRenderer.GeneratePdfAsync(htmlContent, AllPdfDocumentNodes);
} await DocumentPdfFileStore.SetAsync(project, version, languageCode, pdfStream);
return new RemoteStreamContent(pdfStream, fileName, "application/pdf");
} }
protected virtual async Task<string> ConvertDocumentsToHtmlAsync(List<PdfDocumentNode> pdfDocumentNodes) protected virtual async Task<string> ConvertDocumentsToHtmlAsync(List<PdfDocumentNode> pdfDocumentNodes)
@ -91,7 +83,7 @@ public class DocumentPdfGenerator : IDocumentPdfGenerator, ITransientDependency
if (pdfDocumentNode.Document != null) if (pdfDocumentNode.Document != null)
{ {
var renderedDocument = await RenderDocumentAsync(pdfDocumentNode); var renderedDocument = await RenderDocumentAsync(pdfDocumentNode);
renderedDocument = NormalizeHtmlContent(pdfDocumentNode, Markdown.ToHtml(renderedDocument, MarkdownPipeline)); renderedDocument = NormalizeHtmlContent(pdfDocumentNode, Markdown.ToHtml(renderedDocument, GetMarkdownPipeline(pdfDocumentNode)));
contentBuilder.AppendLine(renderedDocument); contentBuilder.AppendLine(renderedDocument);
} }
@ -106,7 +98,8 @@ public class DocumentPdfGenerator : IDocumentPdfGenerator, ITransientDependency
protected virtual async Task<string> RenderDocumentAsync(PdfDocumentNode pdfDocumentNode) protected virtual async Task<string> RenderDocumentAsync(PdfDocumentNode pdfDocumentNode)
{ {
var renderedDocument = await DocumentSectionRenderer.RenderAsync(pdfDocumentNode.Document.Content, pdfDocumentNode.RenderParameters); var content = NormalizeMarkdownContent(pdfDocumentNode.Document.Content);
var renderedDocument = await DocumentSectionRenderer.RenderAsync(content, pdfDocumentNode.RenderParameters);
if (pdfDocumentNode.RenderParameters != null) if (pdfDocumentNode.RenderParameters != null)
{ {
renderedDocument = SetDocumentTitle(renderedDocument, pdfDocumentNode.Title); renderedDocument = SetDocumentTitle(renderedDocument, pdfDocumentNode.Title);
@ -136,8 +129,8 @@ public class DocumentPdfGenerator : IDocumentPdfGenerator, ITransientDependency
Title = navigation.Text, Title = navigation.Text,
IgnoreOnOutline = navigation.Path == Options.Value.CoverPagePath IgnoreOnOutline = navigation.Path == Options.Value.CoverPagePath
}; };
if (!navigation.Path.IsNullOrWhiteSpace()) if (!navigation.Path.IsNullOrWhiteSpace() && !navigation.HasChildItems)
{ {
var path = NormalizeNavigationPath(navigation.Path); var path = NormalizeNavigationPath(navigation.Path);
var document = await GetDocumentAsync(project, path, version, languageCode); var document = await GetDocumentAsync(project, path, version, languageCode);
@ -147,19 +140,12 @@ public class DocumentPdfGenerator : IDocumentPdfGenerator, ITransientDependency
pdfDocumentNode.Document = document; pdfDocumentNode.Document = document;
pdfDocumentNode.Title = GetDocumentTitle(navigation.Text, document.Content, firstParameterCombination); pdfDocumentNode.Title = GetDocumentTitle(navigation.Text, document.Content, firstParameterCombination);
pdfDocumentNode.Id = GetDocumentId(navigation.Path, firstParameterCombination); pdfDocumentNode.Id = GetDocumentId(path, firstParameterCombination, true);
pdfDocumentNode.RenderParameters = firstParameterCombination; pdfDocumentNode.RenderParameters = firstParameterCombination;
if(parameters.Count <= 1) if(parameters.Count <= 1)
{ {
if (parentPdfDocumentNode == null) AddParameterCombinationsDocuments(parentPdfDocumentNode, parameterCombinationsDocuments);
{
AllPdfDocumentNodes.AddRange(parameterCombinationsDocuments);
}
else
{
parentPdfDocumentNode.Children.AddRange(parameterCombinationsDocuments);
}
} }
else else
{ {
@ -170,7 +156,7 @@ public class DocumentPdfGenerator : IDocumentPdfGenerator, ITransientDependency
{ {
Document = document, Document = document,
Title = GetDocumentTitle(navigation.Text, document.Content, parameterCombination), Title = GetDocumentTitle(navigation.Text, document.Content, parameterCombination),
Id = GetDocumentId(navigation.Path, parameterCombination), Id = GetDocumentId(path, parameterCombination, false),
RenderParameters = parameterCombination RenderParameters = parameterCombination
}); });
} }
@ -190,6 +176,11 @@ public class DocumentPdfGenerator : IDocumentPdfGenerator, ITransientDependency
{ {
await SetAllPdfDocumentNodesAsync(navigation.Items, project, version, languageCode, pdfDocumentNode); await SetAllPdfDocumentNodesAsync(navigation.Items, project, version, languageCode, pdfDocumentNode);
} }
if (navigation == navigations.Last())
{
AddParameterCombinationsDocuments(parentPdfDocumentNode, parameterCombinationsDocuments);
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -198,6 +189,18 @@ public class DocumentPdfGenerator : IDocumentPdfGenerator, ITransientDependency
} }
} }
private void AddParameterCombinationsDocuments(PdfDocumentNode parentPdfDocumentNode, List<PdfDocumentNode> parameterCombinationsDocuments)
{
if (parentPdfDocumentNode == null)
{
AllPdfDocumentNodes.AddRange(parameterCombinationsDocuments);
}
else
{
parentPdfDocumentNode.Children.AddRange(parameterCombinationsDocuments);
}
}
private string NormalizeNavigationPath(string path) private string NormalizeNavigationPath(string path)
{ {
return !path.EndsWith(".md") ? Path.Combine(path, "index.md") : path; return !path.EndsWith(".md") ? Path.Combine(path, "index.md") : path;
@ -330,15 +333,15 @@ public class DocumentPdfGenerator : IDocumentPdfGenerator, ITransientDependency
var paramValues = parameters.Select(x => $"{DocumentParams.Parameters.FirstOrDefault(p => p.Name == x.Key)?.DisplayName ?? x.Key}: {x.Value}").ToList(); var paramValues = parameters.Select(x => $"{DocumentParams.Parameters.FirstOrDefault(p => p.Name == x.Key)?.DisplayName ?? x.Key}: {x.Value}").ToList();
return titleLine.TrimStart('#').Trim() + $" ({string.Join(", ", paramValues)})"; return titleLine.TrimStart('#').Trim() + $" ({string.Join(", ", paramValues)})";
} }
private string GetDocumentId(string path, DocumentRenderParameters parameters) private string GetDocumentId(string path, DocumentRenderParameters parameters, bool isFirstCombinationDocument)
{ {
var id = path.Replace(".md",string.Empty).Replace("/","-").Replace(" ", "-").ToLower(); var id = path.Replace(".md",string.Empty).Replace("/","-").Replace(" ", "-").ToLower();
if (parameters != null) if (parameters != null && !isFirstCombinationDocument)
{ {
id = $"{id}{parameters.Select(x => $"{x.Key}_{x.Value}").JoinAsString("-")}"; id = $"{id}{parameters.Select(x => $"{x.Key}_{x.Value}").JoinAsString("-")}";
} }
return id; return id;
} }
@ -354,12 +357,12 @@ public class DocumentPdfGenerator : IDocumentPdfGenerator, ITransientDependency
{ {
return Regex.Replace(htmlContent, @"(<img\s+[^>]*)src=""([^""]*)""([^>]*>)", delegate (Match match) return Regex.Replace(htmlContent, @"(<img\s+[^>]*)src=""([^""]*)""([^>]*>)", delegate (Match match)
{ {
if (IsExternalLink(match.Groups[2].Value)) if (UrlHelper.IsExternalLink(match.Groups[2].Value))
{ {
return match.Value; return match.Value;
} }
var rootUrl = IsExternalLink(pdfDocumentNode.Document.RawRootUrl) var rootUrl = UrlHelper.IsExternalLink(pdfDocumentNode.Document.RawRootUrl)
? pdfDocumentNode.Document.RawRootUrl.EnsureEndsWith('/') ? pdfDocumentNode.Document.RawRootUrl.EnsureEndsWith('/')
: Options.Value.BaseUrl.EnsureEndsWith('/') + pdfDocumentNode.Document.RawRootUrl.TrimStart('/').EnsureEndsWith('/'); : Options.Value.BaseUrl.EnsureEndsWith('/') + pdfDocumentNode.Document.RawRootUrl.TrimStart('/').EnsureEndsWith('/');
var newImageSource = rootUrl + var newImageSource = rootUrl +
@ -370,10 +373,18 @@ public class DocumentPdfGenerator : IDocumentPdfGenerator, ITransientDependency
}, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Multiline); }, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Multiline);
} }
private bool IsExternalLink(string link) private string NormalizeMarkdownContent(string content)
{
var pattern = @"````json\s*//$begin:math:display$doc-nav$end:math:display$[\s\S]*?````";
return Regex.Replace(content, pattern, string.Empty, RegexOptions.IgnoreCase);
}
private MarkdownPipeline GetMarkdownPipeline(PdfDocumentNode pdfDocumentNode)
{ {
return link.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || return new MarkdownPipelineBuilder()
link.StartsWith("https://", StringComparison.OrdinalIgnoreCase); .UseAdvancedExtensions()
.Use(new AnchorLinkResolverExtension(pdfDocumentNode))
.Build();
} }
} }

6
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Pdf/IText/ITextPdfRenderer.cs

@ -21,7 +21,7 @@ public class ITextPdfRenderer : IPdfRenderer ,ITransientDependency
Options = options; Options = options;
} }
public virtual Task<MemoryStream> GeneratePdfAsync(string htmlContent, List<PdfDocumentNode> pdfDocumentNodes) public virtual async Task<MemoryStream> GeneratePdfAsync(string htmlContent, List<PdfDocumentNode> pdfDocumentNodes)
{ {
var pdfStream = new MemoryStream(); var pdfStream = new MemoryStream();
var pdfWrite = new PdfWriter(pdfStream); var pdfWrite = new PdfWriter(pdfStream);
@ -38,6 +38,8 @@ public class ITextPdfRenderer : IPdfRenderer ,ITransientDependency
var tagWorkerFactory = new HtmlIdTagWorkerFactory(pdfDocument); var tagWorkerFactory = new HtmlIdTagWorkerFactory(pdfDocument);
converter.SetTagWorkerFactory(tagWorkerFactory); converter.SetTagWorkerFactory(tagWorkerFactory);
await File.WriteAllTextAsync("/Users/liangshiweis/codes/test.html", htmlBuilder.ToString());
HtmlConverter.ConvertToDocument(htmlBuilder.ToString(), pdfDocument, converter); HtmlConverter.ConvertToDocument(htmlBuilder.ToString(), pdfDocument, converter);
tagWorkerFactory.AddNamedDestinations(); tagWorkerFactory.AddNamedDestinations();
@ -46,7 +48,7 @@ public class ITextPdfRenderer : IPdfRenderer ,ITransientDependency
textDocument.Close(); textDocument.Close();
pdfStream.Position = 0; pdfStream.Position = 0;
return Task.FromResult(pdfStream); return pdfStream;
} }
private void BuildPdfOutlines(PdfOutline parentOutline, List<PdfDocumentNode> pdfDocumentNodes) private void BuildPdfOutlines(PdfOutline parentOutline, List<PdfDocumentNode> pdfDocumentNodes)

65
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Pdf/Markdig/AnchorLinkRenderer.cs

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using Volo.Docs.Utils;
namespace Volo.Docs.Documents.Pdf.Markdig;
public class AnchorLinkRenderer : LinkInlineRenderer
{
private readonly PdfDocumentNode _documentNode;
public AnchorLinkRenderer(PdfDocumentNode documentNode)
{
_documentNode = documentNode;
}
protected override void Write(HtmlRenderer renderer, LinkInline link)
{
if (UrlHelper.IsExternalLink(link.Url) || link.Url.IsNullOrWhiteSpace() || link.IsImage)
{
base.Write(renderer, link);
return;
}
var anchor = ResolveRelativeMarkdownPath(_documentNode.Document.Name, link.Url)
.Replace(".md",string.Empty).Replace("/","-").Replace(" ", "-").ToLower();
renderer.Write("<a href=\"#").Write(anchor).Write("\">");
renderer.Write(link.FirstChild?.ToString() ?? anchor);
renderer.Write("</a>");
}
private string ResolveRelativeMarkdownPath(string currentPath, string relativePath)
{
currentPath = currentPath.EnsureStartsWith('/');
relativePath = relativePath.EnsureStartsWith('/');
var currentDir = currentPath.Substring(0, currentPath.LastIndexOf('/'));
var baseParts = currentDir.Split('/', StringSplitOptions.RemoveEmptyEntries).ToList();
var relativeParts = relativePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
foreach (var part in relativeParts)
{
if (part == "..")
{
if (baseParts.Count > 0)
{
baseParts.RemoveAt(baseParts.Count - 1);
}
}
else if (part != ".")
{
baseParts.Add(part);
}
}
return string.Join("/", baseParts);
}
}

27
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Pdf/Markdig/AnchorLinkResolverExtension.cs

@ -0,0 +1,27 @@
using Markdig;
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
namespace Volo.Docs.Documents.Pdf.Markdig;
public class AnchorLinkResolverExtension : IMarkdownExtension
{
private readonly PdfDocumentNode _documentNode;
public AnchorLinkResolverExtension(PdfDocumentNode documentNode)
{
_documentNode = documentNode;
}
public void Setup(MarkdownPipelineBuilder pipeline)
{
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is HtmlRenderer htmlRenderer)
{
htmlRenderer.ObjectRenderers.Replace<LinkInlineRenderer>(new AnchorLinkRenderer(_documentNode));
}
}
}
Loading…
Cancel
Save