diff --git a/src/Filee.App/Services/BrowserDrags.cs b/src/Filee.App/Services/BrowserDrags.cs
index e663d53e..7a29a0f5 100644
--- a/src/Filee.App/Services/BrowserDrags.cs
+++ b/src/Filee.App/Services/BrowserDrags.cs
@@ -22,7 +22,7 @@ internal static class BrowserDrags
BrowserDragFormats.UniformResourceLocatorW, BrowserDragFormats.UniformResourceLocator, BrowserDragFormats.MozUrl,
BrowserDragFormats.NetscapeUrl, BrowserDragFormats.UriList, BrowserDragFormats.DownloadUrlFormat,
BrowserDragFormats.MozFilePromiseUrl, BrowserDragFormats.MozFilePromiseName, BrowserDragFormats.MacUrl,
- BrowserDragFormats.MacUrlName, BrowserDragFormats.Html, BrowserDragFormats.WindowsHtml, BrowserDragFormats.MacHtml,
+ BrowserDragFormats.MacUrlName, BrowserDragFormats.Html, BrowserDragFormats.WindowsHtml, BrowserDragFormats.MacHtml, BrowserDragFormats.PlainText,
];
/// Picture data some browsers put on a drag (macOS, X11), with the extension it is saved with.
@@ -39,7 +39,8 @@ private static readonly (string Format, string Extension)[] PictureFormats =
public static bool MayOffer(IDataTransfer data) =>
data.Formats.Any(f => f.Kind == DataFormatKind.Platform
&& (BrowserDragFormats.FileGroupDescriptorW.Equals(f.Identifier, StringComparison.OrdinalIgnoreCase)
- || BrowserDragContent.AddressFormatNames.Contains(f.Identifier, StringComparer.OrdinalIgnoreCase)));
+ || BrowserDragContent.AddressFormatNames.Contains(f.Identifier, StringComparer.OrdinalIgnoreCase)))
+ || InlineSvg.Read(data.TryGetText()) is not null;
///
/// The files of a drag from a browser (or another app that holds them only in memory), standing as placeholders;
@@ -96,7 +97,7 @@ public static bool MayOffer(IDataTransfer data) =>
files.Add(WebDropItem.FromStream(entry.Name, _ => throw new IOException("The file's contents weren't handed over.")));
}
- if (files.Count == 0 && !hasAddress)
+ if (files.Count == 0 && !hasAddress && !texts.ContainsKey(BrowserDragFormats.PlainText))
return null;
return new BrowserDragContent { Files = files, Texts = texts, Picture = Picture(data, texts) };
}
@@ -120,6 +121,8 @@ internal static bool IsTemporary(string path)
private static Dictionary Texts(IDataTransfer data)
{
var texts = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ if (data.TryGetText() is { } plain)
+ texts[BrowserDragFormats.PlainText] = plain;
foreach (var item in data.Items)
{
foreach (var format in item.Formats)
diff --git a/src/Filee.Core/Web/BrowserDrag.cs b/src/Filee.Core/Web/BrowserDrag.cs
index 256c7795..cbac9c73 100644
--- a/src/Filee.Core/Web/BrowserDrag.cs
+++ b/src/Filee.Core/Web/BrowserDrag.cs
@@ -9,7 +9,7 @@
// (".html", ".php", no extension). The server's answer is checked again when downloading.
// 5. Nothing else, but the drag carries the picture's pixels (a blob: address Filee can't fetch): those, as PNG.
//
-// Plain text is never read: a selected address is text, not a file.
+// A complete standalone SVG text selection is a picture too. Other plain text is not a file.
using Filee.Core.Formats;
@@ -61,6 +61,9 @@ public static IReadOnlyList Files(BrowserDragContent content)
if (handed.Count > 0)
return handed;
+ if (InlineSvg.Read(content.Text(BrowserDragFormats.PlainText)) is { } svg)
+ return [WebDropItem.FromInlineSvg(svg)];
+
var picture = content.Picture is not null;
var html = content.Text(BrowserDragFormats.Html) ?? content.Text(BrowserDragFormats.WindowsHtml) ?? content.Text(BrowserDragFormats.MacHtml);
if (BrowserDragFormats.ImageOnly(html) is { } src && Address(src, image: true, Base(content)) is { } image)
diff --git a/src/Filee.Core/Web/BrowserDragFormats.cs b/src/Filee.Core/Web/BrowserDragFormats.cs
index c8a8a34c..8bf912a8 100644
--- a/src/Filee.Core/Web/BrowserDragFormats.cs
+++ b/src/Filee.Core/Web/BrowserDragFormats.cs
@@ -44,6 +44,7 @@ public static partial class BrowserDragFormats
public const string Html = "text/html";
public const string WindowsHtml = "HTML Format";
public const string MacHtml = "public.html";
+ public const string PlainText = "text/plain";
// FILEDESCRIPTORW: dwFlags, clsid, sizel, pointl, dwFileAttributes, three FILETIMEs, nFileSizeHigh/Low,
// cFileName[MAX_PATH] (UTF-16) = 592 bytes, after a 4-byte count.
diff --git a/src/Filee.Core/Web/InlineSvg.cs b/src/Filee.Core/Web/InlineSvg.cs
new file mode 100644
index 00000000..9b01523e
--- /dev/null
+++ b/src/Filee.Core/Web/InlineSvg.cs
@@ -0,0 +1,103 @@
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Xml;
+using System.Xml.Linq;
+
+namespace Filee.Core.Web;
+
+/// A complete standalone SVG selection, never HTML extraction or entity decoding.
+public static partial class InlineSvg
+{
+ public const int MaxCharacters = 4 * 1024 * 1024;
+ private const string SvgNamespace = "http://www.w3.org/2000/svg";
+
+ public static byte[]? Read(string? source)
+ {
+ if (string.IsNullOrWhiteSpace(source) || source.Length > MaxCharacters)
+ return null;
+ source = source.Trim();
+ if (!source.StartsWith("