feat: clickable URLs in chat log

Adds a parsing step when constructing `Message` objects that scans the
message content for anything that looks URL-like, and inserts new
`TextChunk`s into the message content with a URIPayload set.

Hovering over a URL shows an on-hover effect. Clicking a URL opens it in
the default browser. Right clicking shows the hostname, with an option
to open and an option to copy the URL to the clipboard.
This commit is contained in:
Dean Sheather
2024-04-09 00:49:15 +10:00
parent fed420901c
commit 4701bb3f6d
10 changed files with 295 additions and 4 deletions
+5
View File
@@ -1,6 +1,7 @@
using ChatTwo.Code;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using System.Text;
namespace ChatTwo.Util;
@@ -100,6 +101,10 @@ internal static class ChunkUtil {
var reader = new BinaryReader(new MemoryStream(rawPayload.Data[4..]));
var id = GetInteger(reader);
link = new AchievementPayload(id);
} else if (rawPayload.Data.Length > 5 && rawPayload.Data[1] == 0x27 && rawPayload.Data[3] == 0x07) {
// uri payload
var uri = new Uri(Encoding.UTF8.GetString(rawPayload.Data[4..]));
link = new URIPayload(uri);
} else if (Equals(rawPayload, RawPayload.LinkTerminator)) {
link = null;
}
+3 -3
View File
@@ -197,16 +197,16 @@ internal static class ImGuiUtil {
}
}
internal static void WarningText(string text) {
internal static void WarningText(string text, bool wrap = true) {
var style = StyleModel.GetConfiguredStyle() ?? StyleModel.GetFromCurrent();
var dalamudOrange = style.BuiltInColors?.DalamudOrange;
if (dalamudOrange != null) {
ImGui.PushStyleColor(ImGuiCol.Text, dalamudOrange.Value);
}
ImGui.PushTextWrapPos();
if (wrap) ImGui.PushTextWrapPos();
ImGui.TextUnformatted(text);
ImGui.PopTextWrapPos();
if (wrap) ImGui.PopTextWrapPos();
if (dalamudOrange != null) {
ImGui.PopStyleColor();
+48
View File
@@ -37,3 +37,51 @@ internal class AchievementPayload : Payload {
throw new NotImplementedException();
}
}
internal class URIPayload(Uri uri) : Payload
{
public override PayloadType Type => (PayloadType) 0x52;
public Uri Uri { get; init; } = uri;
private static readonly string[] ExpectedSchemes = ["http", "https"];
private static readonly string DefaultScheme = "https";
/// <summary>
/// Create a URIPayload from a raw URI string. If the URI does not have a
/// scheme, it will default to https://.
/// </summary>
/// <exception cref="UriFormatException">
/// If the URI is invalid, or if the scheme is not supported.
/// </exception>
public static URIPayload ResolveURI(string rawURI)
{
ArgumentNullException.ThrowIfNull(rawURI);
// Check for expected scheme ://, if not add https://
foreach (var scheme in ExpectedSchemes)
{
if (rawURI.StartsWith($"{scheme}://"))
{
return new URIPayload(new Uri(rawURI));
}
}
if (rawURI.Contains("://"))
{
throw new UriFormatException($"Unsupported scheme in URL: {rawURI}");
}
return new URIPayload(new Uri($"{DefaultScheme}://{rawURI}"));
}
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
{
throw new NotImplementedException();
}
protected override byte[] EncodeImpl()
{
throw new NotImplementedException();
}
}