Skip to content

Commit 9c49c99

Browse files
committed
Hyperlink websites in messages
1 parent 98dd6de commit 9c49c99

File tree

1 file changed

+63
-2
lines changed

1 file changed

+63
-2
lines changed

Signal-Windows/Controls/Message.xaml.cs

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
using System.Collections.Generic;
55
using System.ComponentModel;
66
using System.Diagnostics;
7+
using System.Text.RegularExpressions;
78
using Windows.Storage;
89
using Windows.UI.Xaml;
910
using Windows.UI.Xaml.Controls;
11+
using Windows.UI.Xaml.Documents;
1012
using Windows.UI.Xaml.Media;
1113

1214
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
@@ -15,6 +17,9 @@ namespace Signal_Windows.Controls
1517
{
1618
public sealed partial class Message : UserControl, INotifyPropertyChanged
1719
{
20+
private const string UrlRegexString = @"(?i)\b((?:https?:(?:/{1,3}|[a-z0-9%])|[a-z0-9.\-]+[.](?:com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)/)(?:[^\s()<>{}\[\]]+|\([^\s()]*?\([^\s()]+\)[^\s()]*?\)|\([^\s]+?\))+(?:\([^\s()]*?\([^\s()]+\)[^\s()]*?\)|\([^\s]+?\)|[^\s`!()\[\]{};:'"".,<>?«»“”‘’])|(?:(?<!@)[a-z0-9]+(?:[.\-][a-z0-9]+)*[.](?:com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)\b/?(?!@)))";
21+
private static Regex urlRegex = new Regex(UrlRegexString);
22+
1823
public event PropertyChangedEventHandler PropertyChanged;
1924

2025
public SignalMessageContainer Model
@@ -53,7 +58,7 @@ private void UpdateUI()
5358
{
5459
if (Model != null)
5560
{
56-
MessageContentTextBlock.Text = Model.Message.Content.Content;
61+
UpdateMessageTextBlock();
5762
if (Model.Message.Author == null)
5863
{
5964
MessageAuthor.Visibility = Visibility.Collapsed;
@@ -119,6 +124,62 @@ private void UpdateUI()
119124
}
120125
}
121126

127+
private void UpdateMessageTextBlock()
128+
{
129+
string messageText = Model.Message.Content.Content;
130+
var matches = urlRegex.Matches(messageText);
131+
if (matches.Count == 0)
132+
{
133+
MessageContentTextBlock.Text = messageText;
134+
}
135+
else
136+
{
137+
MessageContentTextBlock.Inlines.Clear();
138+
int previousIndex = 0;
139+
int currentIndex = 0;
140+
foreach (Match match in matches)
141+
{
142+
// First create a Run of the text before the link
143+
currentIndex = match.Index;
144+
var length = currentIndex - previousIndex;
145+
if (length > 0)
146+
{
147+
Run run = new Run();
148+
run.Text = messageText.Substring(previousIndex, currentIndex - previousIndex);
149+
MessageContentTextBlock.Inlines.Add(run);
150+
}
151+
152+
// Now add the hyperlink
153+
string link = match.Value;
154+
Hyperlink hyperlink = new Hyperlink();
155+
Run hyperlinkRun = new Run();
156+
hyperlinkRun.Text = link;
157+
try
158+
{
159+
hyperlink.NavigateUri = new Uri(link);
160+
}
161+
catch (Exception)
162+
{
163+
continue;
164+
}
165+
hyperlink.UnderlineStyle = UnderlineStyle.Single;
166+
hyperlink.Inlines.Add(hyperlinkRun);
167+
MessageContentTextBlock.Inlines.Add(hyperlink);
168+
previousIndex = currentIndex + match.Length;
169+
currentIndex = previousIndex;
170+
}
171+
172+
// Then finish up by adding the rest of the message text to the TextBox
173+
var restLength = messageText.Length - currentIndex;
174+
if (restLength > 0)
175+
{
176+
Run restRun = new Run();
177+
restRun.Text = messageText.Substring(currentIndex, restLength);
178+
MessageContentTextBlock.Inlines.Add(restRun);
179+
}
180+
}
181+
}
182+
122183
private void MessageBox_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
123184
{
124185
UpdateUI();
@@ -136,4 +197,4 @@ internal bool HandleUpdate(SignalMessage updatedMessage)
136197
return updatedMessage.Status != SignalMessageStatus.Received;
137198
}
138199
}
139-
}
200+
}

0 commit comments

Comments
 (0)