-
-
Notifications
You must be signed in to change notification settings - Fork 444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Q: Custom renderer for links? #706
Comments
I managed to figure it out. Well done with this library! Let me know if you have some tips: /// <summary>
/// A markdown renderer for Slack.
/// Slack supports basic markdown, but not a lot.
/// It also has some custom formats.
/// This renderer aims to output slack compatible(ish) markdown messages.
/// <see href="https://api.slack.com/reference/surfaces/formatting"/>
/// </summary>
public class SlackRenderer : RoundtripRenderer
{
/// <inheritdoc />
public SlackRenderer(TextWriter writer)
: base(writer)
{
if (!ObjectRenderers.Replace<LinkInlineRenderer>(new SlackLinkInlineRenderer()))
throw new InvalidOperationException("Replacement failed!");
}
}
/// <summary>
/// <see cref="SlackRenderer"/>
/// </summary>
public class SlackLinkInlineRenderer : LinkInlineRenderer
{
protected override void Write(RoundtripRenderer renderer, LinkInline link)
{
// See: https://api.slack.com/reference/surfaces/formatting#links-in-retrieved-messages
// Sample slack link: <http://www.example.com|This message *is* a link>
// TODO Images are not supported by slack
//if (link.IsImage)
//{
// renderer.Write('!');
//}
// TODO Spec: https://spec.commonmark.org/0.30/#full-reference-link
// Reference links are not yet supported. We could support them by storing links
// We can probably see the full link in the link property, and inject that instead (so all ref links will be just inline links).
// link text
renderer.Write('<');
if (link.Url != null)
{
renderer.Write(link.TriviaBeforeUrl);
renderer.Write(link.UnescapedUrl);
renderer.Write(link.TriviaBeforeUrl);
renderer.Write('|');
renderer.WriteChildren(link);
}
else
{
renderer.WriteChildren(link);
}
renderer.Write('>');
}
} |
@sommmen I also found myself in need of customizing how links are rendered, and I stumbled upon this issue, but while I have no problem modifying this code to my needs, I... don't actually know how to use it, how to turn it into an extension, or even just modify the pipeline with it directly. |
public static class SlackHelpers
{
public static string GetMarkDown(string input)
{
var pipeline = new MarkdownPipelineBuilder()
.EnableTrackTrivia()
.UsePipeTables()
.Build();
var document = Markdown.Parse(input, pipeline);
var writer = new StringWriter();
var renderer = new SlackRenderer(writer);
_ = renderer.Render(document);
writer.Flush();
return writer.ToString();
}
} /// <summary>
/// A markdown renderer for Slack.
/// Slack supports basic markdown, but not a lot.
/// It also has some custom formats.
/// This renderer aims to output slack compatible(ish) markdown messages.
/// <see href="https://api.slack.com/reference/surfaces/formatting"/>
/// </summary>
public class SlackRenderer : RoundtripRenderer
{
/// <inheritdoc />
public SlackRenderer(TextWriter writer)
: base(writer)
{
if (!ObjectRenderers.Replace<LinkInlineRenderer>(new SlackLinkInlineRenderer()))
throw new InvalidOperationException("Replacement failed!");
ObjectRenderers.Add(new SlackPipeTableRenderer());
if (!ObjectRenderers.Replace<HeadingRenderer>(new SlackHeadingRenderer()))
throw new InvalidOperationException("Replacement failed!");
if (!ObjectRenderers.Replace<EmphasisInlineRenderer>(new SlackEmphasisInlineRenderer()))
throw new InvalidOperationException("Replacement failed!");
}
/// <summary>
/// <see cref="SlackRenderer"/>
/// </summary>
public class SlackLinkInlineRenderer : LinkInlineRenderer
{
protected override void Write(RoundtripRenderer renderer, LinkInline link)
{
// See: https://api.slack.com/reference/surfaces/formatting#links-in-retrieved-messages
// Sample slack link: <http://www.example.com|This message *is* a link>
// TODO Images are not supported by slack
//if (link.IsImage)
//{
// renderer.Write('!');
//}
// NOTE Spec: https://spec.commonmark.org/0.30/#full-reference-link
// Reference links are not yet supported. We could support them by storing links
// We can probably see the full link in the link property, and inject that instead (so all ref links will be just inline links).
// link text
renderer.Write('<');
if (link.Url != null)
{
renderer.Write(link.TriviaBeforeUrl);
renderer.Write(link.UnescapedUrl);
renderer.Write(link.TriviaBeforeUrl);
renderer.Write('|');
renderer.WriteChildren(link);
}
else
{
renderer.WriteChildren(link);
}
renderer.Write('>');
}
}
private class SlackPipeTableRenderer : MarkdownObjectRenderer<SlackRenderer, Table>
{
#region Overrides of MarkdownObjectRenderer<SlackRenderer,Table>
/// <inheritdoc />
protected override void Write(SlackRenderer renderer, Table table)
{
renderer.Write(table.TriviaBefore);
// Table is wrapped in code block for it to render nice(ish) in slack
renderer.WriteLine("```");
var p = new int[table.ColumnDefinitions.Count];
foreach (var row in table.Cast<TableRow>())
{
for (var i = 0; i < table.ColumnDefinitions.Count; i++)
{
if (i < row.Count)
{
var cell = (TableCell)row[i];
if (p[i] < cell.Span.Length)
p[i] = cell.Span.Length;
}
}
}
foreach (var row in table.Cast<TableRow>())
{
foreach (var (def, cell, l) in table.ColumnDefinitions.Zip(row.Cast<TableCell>(), p))
{
var padding = Math.Max(0, l - cell.Span.Length);
Debug.Assert(def.Alignment != TableColumnAlign.Center, "NOT YET SUPPORTED!");
if (def.Alignment == TableColumnAlign.Right && padding > 0)
renderer.Write(new string(' ', padding));
renderer.Write(cell.TriviaBefore);
renderer.Write(cell);
renderer.Write(cell.TriviaAfter);
if (def.Alignment == TableColumnAlign.Left && padding > 0)
renderer.Write(new string(' ', padding));
if (cell != row.LastChild)
renderer.Write('\t');
}
renderer.WriteLine();
}
renderer.WriteLine("```");
renderer.Write(table.TriviaAfter);
}
#endregion
}
private class SlackHeadingRenderer : MarkdownObjectRenderer<SlackRenderer, HeadingBlock>
{
protected override void Write(SlackRenderer renderer, HeadingBlock obj)
{
// Slack does not support headings (as markdown text)
// So we simply bold any headings...
if (obj.IsSetext)
{
renderer.RenderLinesBefore(obj);
var headingChar = obj.Level == 1 ? '=' : '-';
var line = new string(headingChar, obj.HeaderCharCount);
renderer.WriteLeafInline(obj);
renderer.WriteLine(obj.SetextNewline);
renderer.Write(obj.TriviaBefore);
renderer.Write('*');
renderer.Write(line);
renderer.Write('*');
renderer.WriteLine(obj.NewLine);
renderer.Write(obj.TriviaAfter);
renderer.RenderLinesAfter(obj);
}
else
{
renderer.RenderLinesBefore(obj);
renderer.Write(obj.TriviaBefore);
renderer.Write(obj.TriviaAfterAtxHeaderChar);
renderer.Write('*');
renderer.WriteLeafInline(obj);
renderer.Write('*');
renderer.Write(obj.TriviaAfter);
renderer.WriteLine(obj.NewLine);
renderer.RenderLinesAfter(obj);
}
}
}
private class SlackEmphasisInlineRenderer : MarkdownObjectRenderer<SlackRenderer, EmphasisInline>
{
protected override void Write(SlackRenderer renderer, EmphasisInline obj)
{
// See: https://commonmark.org/help/tutorial/02-emphasis.html
// See: https://api.slack.com/reference/surfaces/formatting#visual-styles
var emphasisText = obj.DelimiterCount == 1 ? '_' : '*';
renderer.Write(emphasisText);
renderer.WriteChildren(obj);
renderer.Write(emphasisText);
}
}
} Good luck :) |
Hiya,
I have a markdown string that contains links.
I'm pushing this string to Slack, but unfortunately slack has a slightly different way of formatting links:
https://api.slack.com/reference/surfaces/formatting#linking-urls
What i'd like to do is parse my string with markdig and then render a markdown string.
This seems very much possible with markdig - i think i need a custom
MarkdownObjectRenderer
?I'm however a bit confused on where to even start, could someone give some guidance as how to write a custom render for some markdown objects?
What i'm thinking right now is to override the
NormalizeRenderer
, and replace theLinkInlineRenderer
with my own.The text was updated successfully, but these errors were encountered: