refactor: use fork of MdXaml for Markdown
This commit is contained in:
parent
787bf93dbd
commit
bfa603d55a
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "MdXaml"]
|
||||||
|
path = MdXaml
|
||||||
|
url = https://github.com/ascclemens/MdXaml
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit e2b314e3ab37d7ba492d65653538f5e622380da1
|
|
@ -22,6 +22,12 @@
|
||||||
SourceInitialized="MainWindow_OnSourceInitialized"
|
SourceInitialized="MainWindow_OnSourceInitialized"
|
||||||
Closing="MainWindow_OnClosing"
|
Closing="MainWindow_OnClosing"
|
||||||
d:DataContext="{d:DesignInstance local:MainWindow}">
|
d:DataContext="{d:DesignInstance local:MainWindow}">
|
||||||
|
<local:XivChatWindow.CommandBindings>
|
||||||
|
<CommandBinding Command="local:MainWindow.OpenLink"
|
||||||
|
Executed="OpenLink_Execute"
|
||||||
|
CanExecute="OpenLink_CanExecute" />
|
||||||
|
</local:XivChatWindow.CommandBindings>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
@ -123,6 +125,34 @@ namespace XIVChat_Desktop {
|
||||||
this.App.Connection?.ChangeChannel(param);
|
this.App.Connection?.ChangeChannel(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly RoutedUICommand OpenLink = new(
|
||||||
|
"OpenLink",
|
||||||
|
"OpenLink",
|
||||||
|
typeof(MainWindow)
|
||||||
|
);
|
||||||
|
|
||||||
|
private void OpenLink_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
|
||||||
|
e.CanExecute = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenLink_Execute(object sender, ExecutedRoutedEventArgs e) {
|
||||||
|
if (!(e.Parameter is string param)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var uri = new Uri(param);
|
||||||
|
if (uri.Scheme == "http" || uri.Scheme == "https") {
|
||||||
|
Process.Start(new ProcessStartInfo {
|
||||||
|
FileName = uri.ToString(),
|
||||||
|
UseShellExecute = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Console.WriteLine(ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public App App => (App) Application.Current;
|
public App App => (App) Application.Current;
|
||||||
|
|
|
@ -1,448 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Documents;
|
|
||||||
|
|
||||||
namespace XIVChat_Desktop {
|
|
||||||
public static class Markdown {
|
|
||||||
private readonly static char[] Delimiters = {
|
|
||||||
'*', '_', '~',
|
|
||||||
};
|
|
||||||
|
|
||||||
public static IEnumerable<Inline> MarkdownToInlines(string input) {
|
|
||||||
var nodes = ParseNodes(input);
|
|
||||||
return NodesToInlines(nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ContainerNode ParseNodes(string input) {
|
|
||||||
var root = new ContainerNode();
|
|
||||||
DelimiterNode? run = null;
|
|
||||||
char? last = null;
|
|
||||||
var escaping = false;
|
|
||||||
|
|
||||||
var segment = new StringBuilder();
|
|
||||||
|
|
||||||
void AddTextRun() {
|
|
||||||
if (segment.Length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
root.AddChild(new TextNode(root, segment.ToString()));
|
|
||||||
segment.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProcessExistingRun() {
|
|
||||||
// determine the flank
|
|
||||||
run.DetermineFlank();
|
|
||||||
|
|
||||||
// if flank is still unknown, this is an invalid run
|
|
||||||
var isUnknown = run.flank == DelimiterNode.Flank.Unknown;
|
|
||||||
// also if strikethrough does not contain two delimiters, this is an invalid run
|
|
||||||
var notLongEnough = run.character == '~' && run.length < 2;
|
|
||||||
if (isUnknown || notLongEnough) {
|
|
||||||
// return these characters to the string
|
|
||||||
for (var i = 0; i < run.length; i++) {
|
|
||||||
segment.Append(run.character);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the run
|
|
||||||
goto RemoveRun;
|
|
||||||
}
|
|
||||||
|
|
||||||
// at this point, we're going to add the run, so also add the text if necessary
|
|
||||||
AddTextRun();
|
|
||||||
|
|
||||||
// add the run to the list
|
|
||||||
root.AddChild(run);
|
|
||||||
|
|
||||||
RemoveRun:
|
|
||||||
// remove the run from working area
|
|
||||||
run = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var chars = input.ToCharArray();
|
|
||||||
foreach (var c in chars) {
|
|
||||||
var append = true;
|
|
||||||
|
|
||||||
if (c == '\\' && !escaping) {
|
|
||||||
escaping = true;
|
|
||||||
append = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// these characters can form delimiter runs
|
|
||||||
if (Delimiters.Contains(c) && !escaping) {
|
|
||||||
// don't add this character to the text segment
|
|
||||||
append = false;
|
|
||||||
|
|
||||||
if (run != null && run.character != c) {
|
|
||||||
run.following = c;
|
|
||||||
ProcessExistingRun();
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a run if necessary
|
|
||||||
run ??= new DelimiterNode(root, c) {
|
|
||||||
preceding = last,
|
|
||||||
length = 0,
|
|
||||||
originalLength = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// increase the run's length
|
|
||||||
run.length += 1;
|
|
||||||
run.originalLength += 1;
|
|
||||||
|
|
||||||
// skip to the end
|
|
||||||
goto Finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the last character was a delimiter but this character is not
|
|
||||||
// we know that this character is not a delimiter because of the goto
|
|
||||||
if (run != null && last != null && Delimiters.Contains((char)last)) {
|
|
||||||
// set the following character to this character
|
|
||||||
run.following = c;
|
|
||||||
|
|
||||||
ProcessExistingRun();
|
|
||||||
}
|
|
||||||
|
|
||||||
Finish:
|
|
||||||
last = c;
|
|
||||||
if (!append) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (escaping) {
|
|
||||||
escaping = false;
|
|
||||||
|
|
||||||
if (!c.IsAsciiPunctuation()) {
|
|
||||||
segment.Append('\\');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
segment.Append(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-add backslashes as final character
|
|
||||||
if (escaping) {
|
|
||||||
segment.Append('\\');
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we ended on a run, process it
|
|
||||||
if (run != null) {
|
|
||||||
ProcessExistingRun();
|
|
||||||
}
|
|
||||||
|
|
||||||
AddTextRun();
|
|
||||||
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<Inline> NodesToInlines(MarkdownNodeWithChildren root) {
|
|
||||||
var openersBottom = new Dictionary<int, Dictionary<char, LinkedListNode<DelimiterNode>?>>();
|
|
||||||
for (var i = 0; i < 3; i++) {
|
|
||||||
openersBottom[i] = new Dictionary<char, LinkedListNode<DelimiterNode>?>();
|
|
||||||
foreach (var c in Delimiters) {
|
|
||||||
openersBottom[i][c] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var delims = new LinkedList<DelimiterNode>();
|
|
||||||
var node = root.Children.First;
|
|
||||||
while (node != null) {
|
|
||||||
if (node.Value is DelimiterNode dRun) {
|
|
||||||
delims.AddLast(dRun);
|
|
||||||
}
|
|
||||||
|
|
||||||
node = node.Next;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no emphasis to process, so just return all nodes as-is
|
|
||||||
if (delims.Count == 0) {
|
|
||||||
return root.Children.Select(child => child.ToInline());
|
|
||||||
}
|
|
||||||
|
|
||||||
// move forward looking for closers
|
|
||||||
var closer = delims.First;
|
|
||||||
while (closer != null) {
|
|
||||||
if (!closer.Value.CanClose) {
|
|
||||||
closer = closer.Next;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// found first emphasis closer. now look back for first matching opener
|
|
||||||
var opener = closer.Previous;
|
|
||||||
var openerFound = false;
|
|
||||||
var bottom = openersBottom[closer.Value.originalLength % 3][closer.Value.character];
|
|
||||||
while (opener != null && opener != bottom) {
|
|
||||||
var oddMatch =
|
|
||||||
(closer.Value.CanOpen || opener.Value.CanClose) &&
|
|
||||||
closer.Value.originalLength % 3 != 0 &&
|
|
||||||
(opener.Value.originalLength + closer.Value.originalLength) % 3 == 0;
|
|
||||||
if (opener.Value.character == closer.Value.character && opener.Value.CanOpen && !oddMatch) {
|
|
||||||
openerFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
opener = opener.Previous;
|
|
||||||
}
|
|
||||||
|
|
||||||
var oldCloser = closer;
|
|
||||||
|
|
||||||
// emphasis and strikethrough
|
|
||||||
// https://spec.commonmark.org/0.29/#emphasis-and-strong-emphasis
|
|
||||||
// https://github.github.com/gfm/#strikethrough-extension-
|
|
||||||
if (Delimiters.Contains(closer.Value.character)) {
|
|
||||||
if (!openerFound) {
|
|
||||||
closer = closer.Next;
|
|
||||||
} else {
|
|
||||||
if (opener == null) {
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
MarkdownNodeWithChildren emph;
|
|
||||||
|
|
||||||
var isStrike = closer.Value.character == '~';
|
|
||||||
if (isStrike) {
|
|
||||||
opener.Value.length -= 2;
|
|
||||||
closer.Value.length -= 2;
|
|
||||||
|
|
||||||
emph = new StrikethroughNode(root);
|
|
||||||
} else {
|
|
||||||
var useDelims = closer.Value.length >= 2 && opener.Value.length >= 2 ? 2 : 1;
|
|
||||||
|
|
||||||
opener.Value.length -= useDelims;
|
|
||||||
closer.Value.length -= useDelims;
|
|
||||||
|
|
||||||
emph = useDelims == 1 ? (MarkdownNodeWithChildren)new EmphasisNode(root) : new StrongNode(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
var runsOpener = root.Children.Find(opener.Value);
|
|
||||||
var runsCloser = root.Children.Find(closer.Value);
|
|
||||||
var tmp = runsOpener!.Next;
|
|
||||||
while (tmp != null && tmp != runsCloser) {
|
|
||||||
emph.AddChild(tmp.Value);
|
|
||||||
|
|
||||||
var oldTmp = tmp;
|
|
||||||
tmp = tmp.Next;
|
|
||||||
root.Children.Remove(oldTmp);
|
|
||||||
if (oldTmp.Value is DelimiterNode dNode) {
|
|
||||||
delims.Remove(dNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
root.Children.AddAfter(runsOpener, emph);
|
|
||||||
|
|
||||||
if (opener.Value.length == 0) {
|
|
||||||
delims.Remove(opener);
|
|
||||||
root.Children.Remove(runsOpener);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closer.Value.length == 0) {
|
|
||||||
closer = closer.Next;
|
|
||||||
delims.Remove(oldCloser);
|
|
||||||
root.Children.Remove(runsCloser!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (openerFound) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
openersBottom[oldCloser.Value.originalLength % 3][oldCloser.Value.character] = oldCloser.Previous;
|
|
||||||
|
|
||||||
if (!oldCloser.Value.CanOpen) {
|
|
||||||
delims.Remove(oldCloser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return root.Children.Select(child => child.ToInline());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class MarkdownNode {
|
|
||||||
public MarkdownNode? Parent { get; set; }
|
|
||||||
|
|
||||||
protected MarkdownNode(MarkdownNode? parent = null) {
|
|
||||||
this.Parent = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Inline ToInline();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TextNode : MarkdownNode {
|
|
||||||
public string Text { get; set; }
|
|
||||||
|
|
||||||
public TextNode(MarkdownNode parent, string text) : base(parent) {
|
|
||||||
this.Text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Inline ToInline() => new Run(this.Text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DelimiterNode : MarkdownNode {
|
|
||||||
public enum Flank {
|
|
||||||
Unknown,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Both,
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly char character;
|
|
||||||
public char? preceding;
|
|
||||||
public char? following;
|
|
||||||
public int originalLength;
|
|
||||||
public int length;
|
|
||||||
public Flank flank = Flank.Unknown;
|
|
||||||
|
|
||||||
public bool CanOpen => this.CalcCanOpen();
|
|
||||||
public bool CanClose => this.CalcCanClose();
|
|
||||||
|
|
||||||
public DelimiterNode(MarkdownNode parent, char character) : base(parent) {
|
|
||||||
this.character = character;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Inline ToInline() {
|
|
||||||
var text = new StringBuilder(this.length);
|
|
||||||
|
|
||||||
for (var i = 0; i < this.length; i++) {
|
|
||||||
text.Append(this.character);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Run(text.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CalcCanOpen() {
|
|
||||||
switch (this.character) {
|
|
||||||
case '*' when (this.flank == Flank.Left || this.flank == Flank.Both):
|
|
||||||
case '_' when (this.flank == Flank.Left || (this.flank == Flank.Both && this.preceding?.IsAsciiPunctuation() == true)):
|
|
||||||
case '~' when (this.length >= 2 && (this.flank == Flank.Left || this.flank == Flank.Both)):
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CalcCanClose() {
|
|
||||||
switch (this.character) {
|
|
||||||
case '*' when (this.flank == Flank.Right || this.flank == Flank.Both):
|
|
||||||
case '_' when (this.flank == Flank.Right || (this.flank == Flank.Both && this.following?.IsAsciiPunctuation() == true)):
|
|
||||||
case '~' when (this.length >= 2 && (this.flank == Flank.Right || this.flank == Flank.Both)):
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DetermineFlank() {
|
|
||||||
var followedByWhitespace = this.following?.IsWhitespace() ?? true;
|
|
||||||
var followedByPunctuation = this.following?.IsAsciiPunctuation() ?? false;
|
|
||||||
var precededByWhitespace = this.preceding?.IsWhitespace() ?? true;
|
|
||||||
var precededByPunctuation = this.preceding?.IsAsciiPunctuation() ?? false;
|
|
||||||
|
|
||||||
var isLeft = false;
|
|
||||||
var isRight = false;
|
|
||||||
|
|
||||||
// A left-flanking delimiter run is a delimiter run that is (1) not followed by Unicode whitespace, and
|
|
||||||
// either (2a) not followed by a punctuation character, or (2b) followed by a punctuation character and
|
|
||||||
// preceded by Unicode whitespace or a punctuation character. For purposes of this definition, the beginning
|
|
||||||
// and the end of the line count as Unicode whitespace.
|
|
||||||
if (!followedByWhitespace && (!followedByPunctuation || (followedByPunctuation && (precededByWhitespace || precededByPunctuation)))) {
|
|
||||||
isLeft = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A right-flanking delimiter run is a delimiter run that is (1) not preceded by Unicode whitespace, and
|
|
||||||
// either (2a) not preceded by a punctuation character, or (2b) preceded by a punctuation character and
|
|
||||||
// followed by Unicode whitespace or a punctuation character. For purposes of this definition, the beginning
|
|
||||||
// and the end of the line count as Unicode whitespace.
|
|
||||||
if (!precededByWhitespace && (!precededByPunctuation || (precededByPunctuation && (followedByWhitespace || followedByPunctuation)))) {
|
|
||||||
isRight = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLeft && isRight) {
|
|
||||||
this.flank = Flank.Both;
|
|
||||||
} else if (isLeft) {
|
|
||||||
this.flank = Flank.Left;
|
|
||||||
} else if (isRight) {
|
|
||||||
this.flank = Flank.Right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class EmphasisNode : MarkdownNodeWithChildren {
|
|
||||||
public EmphasisNode(MarkdownNode parent) : base(parent) { }
|
|
||||||
|
|
||||||
public override Inline ToInline() {
|
|
||||||
var span = new Span {
|
|
||||||
FontStyle = FontStyles.Italic,
|
|
||||||
};
|
|
||||||
span.Inlines.AddRange(this.Children.Select(child => child.ToInline()));
|
|
||||||
return span;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StrongNode : MarkdownNodeWithChildren {
|
|
||||||
public StrongNode(MarkdownNode parent) : base(parent) { }
|
|
||||||
|
|
||||||
public override Inline ToInline() {
|
|
||||||
var span = new Span {
|
|
||||||
FontWeight = FontWeights.Bold,
|
|
||||||
};
|
|
||||||
span.Inlines.AddRange(this.Children.Select(child => child.ToInline()));
|
|
||||||
return span;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StrikethroughNode : MarkdownNodeWithChildren {
|
|
||||||
public StrikethroughNode(MarkdownNode parent) : base(parent) { }
|
|
||||||
|
|
||||||
public override Inline ToInline() {
|
|
||||||
var span = new Span();
|
|
||||||
span.TextDecorations.Add(TextDecorations.Strikethrough);
|
|
||||||
span.Inlines.AddRange(this.Children.Select(child => child.ToInline()));
|
|
||||||
return span;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class MarkdownNodeWithChildren : MarkdownNode {
|
|
||||||
public LinkedList<MarkdownNode> Children { get; } = new LinkedList<MarkdownNode>();
|
|
||||||
|
|
||||||
protected MarkdownNodeWithChildren(MarkdownNode? parent) : base(parent) { }
|
|
||||||
|
|
||||||
public void AddChild(MarkdownNode node) {
|
|
||||||
node.Parent = this;
|
|
||||||
this.Children.AddLast(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Replace this node with its children in its parent's children.
|
|
||||||
/// </summary>
|
|
||||||
public void PopUp() {
|
|
||||||
if (!(this.Parent is MarkdownNodeWithChildren parent)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var us = parent.Children.Find(this);
|
|
||||||
|
|
||||||
if (us == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var child in this.Children) {
|
|
||||||
child.Parent = parent;
|
|
||||||
parent.Children.AddBefore(us, child);
|
|
||||||
parent.Children.Remove(us);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ContainerNode : MarkdownNodeWithChildren {
|
|
||||||
public ContainerNode(MarkdownNode? parent = null) : base(parent) { }
|
|
||||||
|
|
||||||
public override Inline ToInline() {
|
|
||||||
var span = new Span();
|
|
||||||
span.Inlines.AddRange(this.Children.Select(child => child.ToInline()));
|
|
||||||
return span;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,12 +6,16 @@ using System.Windows.Controls;
|
||||||
using System.Windows.Documents;
|
using System.Windows.Documents;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
using MdXaml;
|
||||||
using XIVChatCommon.Message;
|
using XIVChatCommon.Message;
|
||||||
using XIVChatCommon.Message.Server;
|
using XIVChatCommon.Message.Server;
|
||||||
|
|
||||||
namespace XIVChat_Desktop {
|
namespace XIVChat_Desktop {
|
||||||
public class MessageFormatter {
|
public static class MessageFormatter {
|
||||||
private static readonly BitmapFrame FontIcon = BitmapFrame.Create(new Uri("pack://application:,,,/Resources/fonticon_ps4.tex.png"));
|
private static readonly BitmapFrame FontIcon = BitmapFrame.Create(new Uri("pack://application:,,,/Resources/fonticon_ps4.tex.png"));
|
||||||
|
private static readonly Markdown Markdown = new() {
|
||||||
|
HyperlinkCommand = MainWindow.OpenLink,
|
||||||
|
};
|
||||||
|
|
||||||
public static IEnumerable<Inline> ChunksToTextBlock(ServerMessage message, double lineHeight, bool processMarkdown, bool showTimestamp) {
|
public static IEnumerable<Inline> ChunksToTextBlock(ServerMessage message, double lineHeight, bool processMarkdown, bool showTimestamp) {
|
||||||
var elements = new List<Inline>();
|
var elements = new List<Inline>();
|
||||||
|
@ -37,7 +41,7 @@ namespace XIVChat_Desktop {
|
||||||
var style = textChunk.Italic ? FontStyles.Italic : FontStyles.Normal;
|
var style = textChunk.Italic ? FontStyles.Italic : FontStyles.Normal;
|
||||||
|
|
||||||
if (processMarkdown) {
|
if (processMarkdown) {
|
||||||
var inlines = Markdown.MarkdownToInlines(textChunk.Content);
|
var inlines = Markdown.RunSpanGamut(textChunk.Content);
|
||||||
|
|
||||||
foreach (var inline in inlines) {
|
foreach (var inline in inlines) {
|
||||||
inline.Foreground = brush;
|
inline.Foreground = brush;
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MdXaml\MdXaml\MdXaml.csproj" />
|
||||||
<ProjectReference Include="..\XIVChatCommon\XIVChatCommon.csproj" />
|
<ProjectReference Include="..\XIVChatCommon\XIVChatCommon.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XIVChat Desktop", "XIVChat
|
||||||
EndProject
|
EndProject
|
||||||
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "XIVChat Desktop Installer", "XIVChat Desktop Installer\XIVChat Desktop Installer.wixproj", "{6017ABE2-E82F-476D-B3DA-18B634E092F2}"
|
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "XIVChat Desktop Installer", "XIVChat Desktop Installer\XIVChat Desktop Installer.wixproj", "{6017ABE2-E82F-476D-B3DA-18B634E092F2}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MdXaml", "MdXaml\MdXaml\MdXaml.csproj", "{084B2C7D-8C0B-4774-A6E1-D804EE1A6D24}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -33,6 +35,10 @@ Global
|
||||||
{D2773EAE-6B9E-4017-9681-585AAB5A99F4}.Release|Any CPU.Build.0 = Release|Any CPU
|
{D2773EAE-6B9E-4017-9681-585AAB5A99F4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{6017ABE2-E82F-476D-B3DA-18B634E092F2}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{6017ABE2-E82F-476D-B3DA-18B634E092F2}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{6017ABE2-E82F-476D-B3DA-18B634E092F2}.Release|Any CPU.ActiveCfg = Release|x64
|
{6017ABE2-E82F-476D-B3DA-18B634E092F2}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
|
{084B2C7D-8C0B-4774-A6E1-D804EE1A6D24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{084B2C7D-8C0B-4774-A6E1-D804EE1A6D24}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{084B2C7D-8C0B-4774-A6E1-D804EE1A6D24}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{084B2C7D-8C0B-4774-A6E1-D804EE1A6D24}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
Loading…
Reference in New Issue