static Common() { BoldItalicTextInline.AddTripChars(_triggerList); BoldTextInline.AddTripChars(_triggerList); ItalicTextInline.AddTripChars(_triggerList); MarkdownLinkInline.AddTripChars(_triggerList); HyperlinkInline.AddTripChars(_triggerList); StrikethroughTextInline.AddTripChars(_triggerList); CodeInline.AddTripChars(_triggerList); ImageInline.AddTripChars(_triggerList); EmojiInline.AddTripChars(_triggerList); LinkAnchorInline.AddTripChars(_triggerList); DiscordInline.AddTripChars(_triggerList); SpoilerTextInline.AddTripChars(_triggerList); // Create an array of characters to search against using IndexOfAny. _tripCharacters = _triggerList.Select(trigger => trigger.FirstChar).Distinct().ToArray(); }
private void RenderDiscord(InlineCollection inlineCollection, DiscordInline element, RenderContext context) { if (!element.IsEmote) { var link = new Hyperlink(); // Register the link _linkRegister.RegisterNewHyperLink(link, element.Url); // Make a text block for the link Run linkText = new Run { Text = CollapseWhitespace(context, element.Text) }; link.Inlines.Add(linkText); // Add it to the current inlines inlineCollection.Add(link); } else { // in this case we just huck the emote inline instead inlineCollection.Add(new InlineUIContainer { Child = new Image { HorizontalAlignment = HorizontalAlignment.Center, MaxHeight = 30, MaxWidth = 30, Source = new BitmapImage { DecodePixelType = DecodePixelType.Logical, DecodePixelHeight = 30, UriSource = new Uri(element.Url), }, } }); } }
/// <summary> /// Finds the next inline element by matching trip chars and verifying the match. /// </summary> /// <param name="markdown"> The markdown text to parse. </param> /// <param name="start"> The position to start parsing. </param> /// <param name="end"> The position to stop parsing. </param> /// <param name="ignoreLinks"> Indicates whether to parse links. </param> /// <returns>Returns the next element</returns> private static InlineParseResult FindNextInlineElement(string markdown, int start, int end, bool ignoreLinks) { // Search for the next inline sequence. for (int pos = start; pos < end; pos++) { // IndexOfAny should be the fastest way to skip characters we don't care about. pos = markdown.IndexOfAny(_tripCharacters, pos, end - pos); if (pos < 0) { break; } // Find the trigger(s) that matched. char currentChar = markdown[pos]; foreach (InlineTripCharHelper currentTripChar in _triggerList) { // Check if our current char matches the suffix char. if (currentChar == currentTripChar.FirstChar) { // Don't match if the previous character was a backslash. if (pos > start && markdown[pos - 1] == '\\') { continue; } // If we are here we have a possible match. Call into the inline class to verify. InlineParseResult parseResult = null; switch (currentTripChar.Method) { case InlineParseMethod.BoldItalic: parseResult = BoldItalicTextInline.Parse(markdown, pos, end); break; case InlineParseMethod.LinkReference: parseResult = LinkAnchorInline.Parse(markdown, pos, end); break; case InlineParseMethod.Bold: parseResult = BoldTextInline.Parse(markdown, pos, end); break; case InlineParseMethod.Italic: parseResult = ItalicTextInline.Parse(markdown, pos, end); break; case InlineParseMethod.MarkdownLink: if (!ignoreLinks) { parseResult = MarkdownLinkInline.Parse(markdown, pos, end); } break; case InlineParseMethod.AngleBracketLink: if (!ignoreLinks) { parseResult = HyperlinkInline.ParseAngleBracketLink(markdown, pos, end); } break; case InlineParseMethod.Url: if (!ignoreLinks) { parseResult = HyperlinkInline.ParseUrl(markdown, pos, end); } break; case InlineParseMethod.RedditLink: if (!ignoreLinks) { parseResult = HyperlinkInline.ParseRedditLink(markdown, pos, end); } break; case InlineParseMethod.PartialLink: if (!ignoreLinks) { parseResult = HyperlinkInline.ParsePartialLink(markdown, pos, end); } break; case InlineParseMethod.Email: if (!ignoreLinks) { parseResult = HyperlinkInline.ParseEmailAddress(markdown, start, pos, end); } break; case InlineParseMethod.Strikethrough: parseResult = StrikethroughTextInline.Parse(markdown, pos, end); break; case InlineParseMethod.Spoiler: parseResult = SpoilerTextInline.Parse(markdown, pos, end); break; case InlineParseMethod.Code: parseResult = CodeInline.Parse(markdown, pos, end); break; case InlineParseMethod.Image: parseResult = ImageInline.Parse(markdown, pos, end); break; case InlineParseMethod.Emoji: parseResult = EmojiInline.Parse(markdown, pos, end); break; case InlineParseMethod.Discord: parseResult = DiscordInline.Parse(markdown, pos, end); break; } if (parseResult != null) { return(parseResult); } } } } // If we didn't find any elements we have a normal text block. // Let us consume the entire range. return(new InlineParseResult(TextRunInline.Parse(markdown, start, end), start, end)); }
protected override void RenderDiscord(DiscordInline element, IRenderContext context) { if (!(context is InlineRenderContext localContext)) { throw new RenderContextIncorrectException(); } if (Channel != null) { var guild = Channel.Guild; var client = Channel.Discord as DiscordClient; if (element.DiscordType != DiscordInline.MentionType.Emote) { var run = new Run() { FontWeight = FontWeights.Bold }; if (element.DiscordType == DiscordInline.MentionType.User) { var user = guild != null ? (guild.Members.TryGetValue(element.Id, out var memb) ? memb : null) : client.TryGetCachedUser(element.Id, out var u) ? u : null; if (user != null) { run.Text = IsSystemMessage ? user.DisplayName : $"@{user.DisplayName}"; run.Foreground = GetDiscordBrush(user.Color); } else { run.Text = $"<@{element.Id}>"; } } else if (element.DiscordType == DiscordInline.MentionType.Role) { if (Channel.Guild != null && Channel.Guild.Roles.TryGetValue(element.Id, out var role)) { run.Text = $"@{role.Name}"; run.Foreground = GetDiscordBrush(role.Color); } else { run.Text = $"<@&{element.Id}>"; } } else if (element.DiscordType == DiscordInline.MentionType.Channel) { if (client.TryGetCachedChannel(element.Id, out var channel) && !(channel is DiscordDmChannel)) { run.Text = $"#{channel.Name}"; } else { run.Text = $"#deleted-channel"; } } localContext.InlineCollection.Add(run); } else { var border = RootElement.FindParent <Border>(); var uri = $"https://cdn.discordapp.com/emojis/{element.Id}?size=128"; var ui = new InlineUIContainer() { FontSize = IsHuge ? 42 : 24 }; var size = IsHuge ? 48 : 24; var image = new EmojiControl() { Emoji = DiscordEmoji.FromGuildEmote(App.Discord, element.Id, element.Text), Size = size, MaxWidth = size * 3, RenderTransform = IsHuge ? null : new TranslateTransform() { Y = 8 } }; ToolTipService.SetToolTip(image, element.Text); ui.Child = image; RootElement.Margin = new Thickness(0, 0, 0, 4); localContext.InlineCollection.Add(ui); if (!IsHuge && RootElement.RenderTransform is not TranslateTransform tt) { RootElement.Margin = new Thickness(0, -8, 0, 0); } } } }
protected override void RenderDiscord(DiscordInline element, IRenderContext context) { if (!(context is InlineRenderContext localContext)) { throw new RenderContextIncorrectException(); } if (Channel != null) { var guild = Channel.Guild; var client = Channel.Discord as DiscordClient; if (element.DiscordType != DiscordInline.MentionType.Emote) { var run = new Run() { FontWeight = FontWeights.Bold }; if (element.DiscordType == DiscordInline.MentionType.User) { var user = guild != null ? (guild.Members.TryGetValue(element.Id, out var memb) ? memb : null) : client.UserCache.TryGetValue(element.Id, out var u) ? u : null; if (user != null) { var me = user as DiscordMember; run.Text = $"@{me?.DisplayName ?? user.Username}"; run.Foreground = me?.ColorBrush ?? Foreground; } else { run.Text = $"<@{element.Id}>"; } } else if (element.DiscordType == DiscordInline.MentionType.Role) { var role = client.Guilds.Values.SelectMany(g => g.Roles.Values).FirstOrDefault(r => r.Id == element.Id); if (role != null) { run.Text = $"@{role.Name}"; if (role.Color.Value != 0) { run.Foreground = new SolidColorBrush(Color.FromArgb(255, role.Color.R, role.Color.G, role.Color.B)); } } else { run.Text = $"<@&{element.Id}>"; } } else if (element.DiscordType == DiscordInline.MentionType.Channel) { if (client._channelCache.TryGetValue(element.Id, out var channel)) { run.Text = channel is DiscordDmChannel c ? $"@{c.Recipient.Username}#{c.Recipient.Id}" : $"#{channel.Name}"; } else { run.Text = $"#deleted-channel"; } } localContext.InlineCollection.Add(run); } else { var uri = $"https://cdn.discordapp.com/emojis/{element.Id}?size=32"; var ui = new InlineUIContainer() { FontSize = 24 }; var image = new Image() { Source = new BitmapImage(new Uri(uri)), Width = 24, Height = 24, Stretch = Stretch.Uniform, RenderTransform = new TranslateTransform() { Y = 4 } }; ToolTipService.SetToolTip(image, element.Text); ui.Child = image; RootElement.Margin = new Thickness(0, 0, 0, 4); localContext.InlineCollection.Add(ui); } } }
/// <summary> /// Renders a Discord element. /// </summary> /// <param name="element"> The parsed inline element to render. </param> /// <param name="context"> Persistent state. </param> protected abstract void RenderDiscord(DiscordInline element, IRenderContext context);