mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-17 05:57:59 +00:00
Fixed local source links being refused by Word and LibreOffice
This commit is contained in:
6 files changed
+125
-7
No files matched your search
@@ -55,8 +55,11 @@ public static class IContentExtensions
|
||||
/// </remarks>
|
||||
/// <param name="content">The content to read.</param>
|
||||
/// <param name="markdown">The Markdown text including its sources, or an empty string when there is none.</param>
|
||||
/// <param name="keepPageAnchors">Whether a link into a local file may name its page. Only a
|
||||
/// format whose reader stumbles over such a link says no here; the clipboard and every text
|
||||
/// format keep the page.</param>
|
||||
/// <returns>True, when this content carries Markdown text.</returns>
|
||||
public static bool TryGetExportMarkdown(this IContent content, out string markdown)
|
||||
public static bool TryGetExportMarkdown(this IContent content, out string markdown, bool keepPageAnchors = true)
|
||||
{
|
||||
if (content is not ContentText text)
|
||||
{
|
||||
@@ -65,7 +68,7 @@ public static class IContentExtensions
|
||||
}
|
||||
|
||||
var answer = text.Text.Trim();
|
||||
var sources = text.Sources.ToExportMarkdown();
|
||||
var sources = text.Sources.ToExportMarkdown(keepPageAnchors);
|
||||
if (sources.Length == 0)
|
||||
{
|
||||
markdown = answer;
|
||||
|
||||
@@ -204,6 +204,26 @@ public static class FileExportFormatExtensions
|
||||
_ => WITHOUT_BYTE_ORDER_MARK,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a link into a local file may name the page it points at.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A page is named by the fragment of the link, the way the PDF open parameters call for. A
|
||||
/// browser and a PDF reader follow that and open the document on the page; Word and LibreOffice
|
||||
/// take the fragment for part of the file name, look for a file which does not exist, and refuse
|
||||
/// the link altogether. There the page is dropped, so the link at least opens the document --
|
||||
/// which page it was stays in the title of the source. Verified on 2026-09-15 with LibreOffice
|
||||
/// on an exported .odt. A format added later keeps the page unless it is known to stumble too.
|
||||
/// </remarks>
|
||||
/// <param name="format">The format.</param>
|
||||
/// <returns>True, when a reader of this format follows such a link.</returns>
|
||||
public static bool FollowsPageAnchors(this FileExportFormat format) => format switch
|
||||
{
|
||||
FileExportFormat.MICROSOFT_WORD or FileExportFormat.OPEN_DOCUMENT_TEXT => false,
|
||||
|
||||
_ => true,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name Pandoc knows the format by.
|
||||
/// </summary>
|
||||
|
||||
@@ -118,7 +118,7 @@ public static class PandocExport
|
||||
// We read the text before we ask for a path: when there is nothing to convert, the user
|
||||
// should learn that right away instead of picking a file first and getting an error afterwards.
|
||||
//
|
||||
if (!markdownContent.TryGetExportMarkdown(out var markdownText))
|
||||
if (!markdownContent.TryGetExportMarkdown(out var markdownText, format.FollowsPageAnchors()))
|
||||
{
|
||||
LOGGER.LogWarning("Cannot export the content as {ExportFormat}, because it carries no text.", format);
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("Only text messages can be exported.")));
|
||||
|
||||
@@ -138,8 +138,9 @@ public static partial class SourceExtensions
|
||||
/// Converts a list of sources to a markdown-formatted string.
|
||||
/// </summary>
|
||||
/// <param name="sources">The list of sources to convert.</param>
|
||||
/// <param name="keepPageAnchors">Whether a link into a local file may name its page; see the method below.</param>
|
||||
/// <returns>A markdown-formatted string representing the sources.</returns>
|
||||
public static string ToMarkdown(this IList<Source> sources)
|
||||
public static string ToMarkdown(this IList<Source> sources, bool keepPageAnchors = true)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var group in sources.GroupSources())
|
||||
@@ -152,8 +153,9 @@ public static partial class SourceExtensions
|
||||
|
||||
foreach (var numberedSource in group.Sources)
|
||||
{
|
||||
var url = keepPageAnchors ? numberedSource.Source.URL : WithoutPageAnchor(numberedSource.Source.URL);
|
||||
sb.Append($"- [{numberedSource.Number}] ");
|
||||
AppendMarkdownLink(sb, numberedSource.Source.Title, numberedSource.Source.URL);
|
||||
AppendMarkdownLink(sb, numberedSource.Source.Title, url);
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
@@ -161,6 +163,29 @@ public static partial class SourceExtensions
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes the page off a link into a local file, for a reader which cannot follow it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Everything a local link carries in its fragment is dropped, not only a page: a chunk is no
|
||||
/// use to any reader either, and what breaks such a link is the fragment itself rather than what
|
||||
/// stands in it. A web address keeps its fragment untouched, because there the fragment is part
|
||||
/// of the address and naming a section of a page is exactly what it is for.
|
||||
/// </remarks>
|
||||
/// <param name="url">The link of the source.</param>
|
||||
/// <returns>The link without its fragment, or the link itself when it carries none.</returns>
|
||||
private static string WithoutPageAnchor(string url)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
return url;
|
||||
|
||||
var cleanedUrl = url.Trim().Replace("\r", string.Empty).Replace("\n", string.Empty);
|
||||
if (!Uri.TryCreate(cleanedUrl, UriKind.Absolute, out var absoluteUri) || !absoluteUri.IsFile || absoluteUri.Fragment.Length == 0)
|
||||
return url;
|
||||
|
||||
return absoluteUri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Fragment, UriFormat.UriEscaped);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a list of sources to a markdown-formatted string, headed by a title of its own.
|
||||
/// </summary>
|
||||
@@ -171,10 +196,11 @@ public static partial class SourceExtensions
|
||||
/// for this and the chat does not.
|
||||
/// </remarks>
|
||||
/// <param name="sources">The list of sources to convert.</param>
|
||||
/// <param name="keepPageAnchors">Whether a link into a local file may name its page.</param>
|
||||
/// <returns>A markdown-formatted string representing the sources, or an empty string when there are none.</returns>
|
||||
public static string ToExportMarkdown(this IList<Source> sources)
|
||||
public static string ToExportMarkdown(this IList<Source> sources, bool keepPageAnchors = true)
|
||||
{
|
||||
var sourcesMarkdown = sources.ToMarkdown();
|
||||
var sourcesMarkdown = sources.ToMarkdown(keepPageAnchors);
|
||||
if (string.IsNullOrWhiteSpace(sourcesMarkdown))
|
||||
return string.Empty;
|
||||
|
||||
|
||||
Reference in new issue
Block a user