feat: add support for Obsidian-style callouts
This commit is contained in:
parent
16618cc135
commit
01031057e0
37
OliverBooth/Markdown/Callout/CalloutBlock.cs
Normal file
37
OliverBooth/Markdown/Callout/CalloutBlock.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using Markdig.Helpers;
|
||||||
|
using Markdig.Syntax;
|
||||||
|
|
||||||
|
namespace OliverBooth.Markdown.Callout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a callout block.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class CalloutBlock : QuoteBlock
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CalloutBlock" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the callout.</param>
|
||||||
|
public CalloutBlock(StringSlice type) : base(null)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the title of the callout.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The title of the callout.</value>
|
||||||
|
public StringSlice Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the trailing whitespace trivia.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The trailing whitespace trivia.</value>
|
||||||
|
public StringSlice TrailingWhitespaceTrivia { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the type of the callout.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The type of the callout.</value>
|
||||||
|
public StringSlice Type { get; set; }
|
||||||
|
}
|
32
OliverBooth/Markdown/Callout/CalloutExtension.cs
Normal file
32
OliverBooth/Markdown/Callout/CalloutExtension.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using Markdig;
|
||||||
|
using Markdig.Parsers.Inlines;
|
||||||
|
using Markdig.Renderers;
|
||||||
|
using Markdig.Renderers.Html;
|
||||||
|
|
||||||
|
namespace OliverBooth.Markdown.Callout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension for adding Obsidian-style callouts to a Markdown pipeline.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class CalloutExtension : IMarkdownExtension
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||||
|
{
|
||||||
|
var parser = pipeline.InlineParsers.Find<CalloutInlineParser>();
|
||||||
|
if (parser is null)
|
||||||
|
{
|
||||||
|
pipeline.InlineParsers.InsertBefore<LinkInlineParser>(new CalloutInlineParser());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||||
|
{
|
||||||
|
var blockRenderer = renderer.ObjectRenderers.FindExact<CalloutRenderer>();
|
||||||
|
if (blockRenderer is null)
|
||||||
|
{
|
||||||
|
renderer.ObjectRenderers.InsertBefore<QuoteBlockRenderer>(new CalloutRenderer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
OliverBooth/Markdown/Callout/CalloutInlineParser.cs
Normal file
171
OliverBooth/Markdown/Callout/CalloutInlineParser.cs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Cysharp.Text;
|
||||||
|
using Markdig.Helpers;
|
||||||
|
using Markdig.Parsers;
|
||||||
|
using Markdig.Renderers.Html;
|
||||||
|
using Markdig.Syntax;
|
||||||
|
|
||||||
|
namespace OliverBooth.Markdown.Callout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An inline parser for Obsidian-style callouts (<c>[!NOTE]</c> etc.)
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class CalloutInlineParser : InlineParser
|
||||||
|
{
|
||||||
|
// ugly hack to access internal method
|
||||||
|
private static readonly MethodInfo ReplaceParentContainerMethod =
|
||||||
|
typeof(InlineProcessor).GetMethod("ReplaceParentContainer", BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||||
|
|
||||||
|
// but we can at least make it a bit nicer to access
|
||||||
|
private static readonly Action<InlineProcessor, ContainerBlock, ContainerBlock> ReplaceParentContainer =
|
||||||
|
(processor, before, after) => ReplaceParentContainerMethod.Invoke(processor, [before, after]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CalloutInlineParser" /> class.
|
||||||
|
/// </summary>
|
||||||
|
public CalloutInlineParser()
|
||||||
|
{
|
||||||
|
OpeningCharacters = ['['];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||||
|
{
|
||||||
|
// We expect the alert to be the first child of a quote block. Example:
|
||||||
|
// > [!NOTE]
|
||||||
|
// > This is a note
|
||||||
|
if (processor.Block is not ParagraphBlock { Parent: QuoteBlock quoteBlock } paragraphBlock ||
|
||||||
|
paragraphBlock.Inline?.FirstChild != null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSlice cache = slice;
|
||||||
|
char current = slice.NextChar();
|
||||||
|
|
||||||
|
if (current != '!')
|
||||||
|
{
|
||||||
|
slice = cache;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = slice.NextChar(); // skip !
|
||||||
|
|
||||||
|
int start = slice.Start;
|
||||||
|
int end = start;
|
||||||
|
|
||||||
|
while (current.IsAlphaUpper())
|
||||||
|
{
|
||||||
|
end = slice.Start;
|
||||||
|
current = slice.NextChar();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current != ']' || start == end)
|
||||||
|
{
|
||||||
|
slice = cache;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var type = new StringSlice(slice.Text, start, end);
|
||||||
|
current = slice.NextChar(); // skip ]
|
||||||
|
start = slice.Start;
|
||||||
|
|
||||||
|
ReadTitle(current, ref slice, out StringSlice title, start, out end);
|
||||||
|
|
||||||
|
var callout = new CalloutBlock(type)
|
||||||
|
{
|
||||||
|
Span = quoteBlock.Span,
|
||||||
|
TrailingWhitespaceTrivia = new StringSlice(slice.Text, start, end),
|
||||||
|
Line = quoteBlock.Line,
|
||||||
|
Column = quoteBlock.Column,
|
||||||
|
Title = title
|
||||||
|
};
|
||||||
|
|
||||||
|
AddAttributes(callout, type);
|
||||||
|
ReplaceQuoteBlock(processor, quoteBlock, callout);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReadTitle(char startChar, ref StringSlice slice, out StringSlice title, int start, out int end)
|
||||||
|
{
|
||||||
|
using Utf16ValueStringBuilder builder = ZString.CreateStringBuilder();
|
||||||
|
|
||||||
|
char current = startChar;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (current is not ('\0' or '\r' or '\n'))
|
||||||
|
{
|
||||||
|
builder.Append(current);
|
||||||
|
current = slice.NextChar();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
end = slice.Start;
|
||||||
|
if (HandleCharacter(ref slice, ref end, ref current))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
title = new StringSlice(builder.ToString(), 0, builder.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HandleCharacter(ref StringSlice slice, ref int end, ref char current)
|
||||||
|
{
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case '\r':
|
||||||
|
current = slice.NextChar(); // skip \r
|
||||||
|
|
||||||
|
if (current is not ('\0' or '\n'))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
end = slice.Start;
|
||||||
|
if (current == '\n')
|
||||||
|
{
|
||||||
|
slice.NextChar(); // skip \n
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\n':
|
||||||
|
slice.NextChar(); // skip \n
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddAttributes(IMarkdownObject callout, StringSlice type)
|
||||||
|
{
|
||||||
|
HtmlAttributes attributes = callout.GetAttributes();
|
||||||
|
attributes.AddClass("callout");
|
||||||
|
attributes.AddProperty("data-callout", type.AsSpan().ToString().ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReplaceQuoteBlock(InlineProcessor processor, QuoteBlock quoteBlock, CalloutBlock callout)
|
||||||
|
{
|
||||||
|
ContainerBlock? parentQuoteBlock = quoteBlock.Parent;
|
||||||
|
if (parentQuoteBlock is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int indexOfQuoteBlock = parentQuoteBlock.IndexOf(quoteBlock);
|
||||||
|
parentQuoteBlock[indexOfQuoteBlock] = callout;
|
||||||
|
|
||||||
|
while (quoteBlock.Count > 0)
|
||||||
|
{
|
||||||
|
var block = quoteBlock[0];
|
||||||
|
quoteBlock.RemoveAt(0);
|
||||||
|
callout.Add(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplaceParentContainerMethod.Invoke(processor, [quoteBlock, callout]);
|
||||||
|
// ReplaceParentContainer(processor, quoteBlock, callout);
|
||||||
|
}
|
||||||
|
}
|
82
OliverBooth/Markdown/Callout/CalloutRenderer.cs
Normal file
82
OliverBooth/Markdown/Callout/CalloutRenderer.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using Humanizer;
|
||||||
|
using Markdig.Renderers;
|
||||||
|
using Markdig.Renderers.Html;
|
||||||
|
|
||||||
|
namespace OliverBooth.Markdown.Callout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an HTML renderer which renders a <see cref="CalloutBlock" />.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class CalloutRenderer : HtmlObjectRenderer<CalloutBlock>
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, string> CalloutTypes = new()
|
||||||
|
{
|
||||||
|
["NOTE"] = "pencil",
|
||||||
|
["ABSTRACT"] = "clipboard-list",
|
||||||
|
["INFO"] = "info",
|
||||||
|
["TODO"] = "circle-check",
|
||||||
|
["TIP"] = "flame",
|
||||||
|
["SUCCESS"] = "check",
|
||||||
|
["QUESTION"] = "circle-help",
|
||||||
|
["WARNING"] = "triangle-alert",
|
||||||
|
["FAILURE"] = "x",
|
||||||
|
["DANGER"] = "zap",
|
||||||
|
["BUG"] = "bug",
|
||||||
|
["EXAMPLE"] = "list",
|
||||||
|
["CITE"] = "quote",
|
||||||
|
["UPDATE"] = "calendar-check",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Write(HtmlRenderer renderer, CalloutBlock block)
|
||||||
|
{
|
||||||
|
renderer.EnsureLine();
|
||||||
|
if (renderer.EnableHtmlForBlock)
|
||||||
|
{
|
||||||
|
RenderAsHtml(renderer, block);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RenderAsText(renderer, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.EnsureLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenderAsHtml(HtmlRenderer renderer, CalloutBlock block)
|
||||||
|
{
|
||||||
|
string title = block.Title.Text;
|
||||||
|
ReadOnlySpan<char> type = block.Type.AsSpan();
|
||||||
|
Span<char> upperType = stackalloc char[type.Length];
|
||||||
|
type.ToUpperInvariant(upperType);
|
||||||
|
|
||||||
|
if (!CalloutTypes.TryGetValue(upperType.ToString(), out string? lucideClass))
|
||||||
|
{
|
||||||
|
lucideClass = "pencil";
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeString = type.ToString().ToLowerInvariant();
|
||||||
|
|
||||||
|
renderer.Write($"<div class=\"callout\" data-callout=\"{typeString}\">");
|
||||||
|
renderer.Write("<div class=\"callout-title\"><i data-lucide=\"");
|
||||||
|
renderer.Write(lucideClass);
|
||||||
|
renderer.Write("\"></i> ");
|
||||||
|
|
||||||
|
renderer.Write(title.Length == 0 ? typeString.Humanize(LetterCasing.Sentence) : title);
|
||||||
|
renderer.WriteLine("</div>");
|
||||||
|
|
||||||
|
renderer.WriteChildren(block);
|
||||||
|
|
||||||
|
renderer.WriteLine("</div>");
|
||||||
|
renderer.EnsureLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenderAsText(HtmlRenderer renderer, CalloutBlock block)
|
||||||
|
{
|
||||||
|
string title = block.Title.Text;
|
||||||
|
ReadOnlySpan<char> type = block.Type.AsSpan();
|
||||||
|
renderer.WriteLine(title.Length == 0 ? type.ToString().ToUpperInvariant() : title.ToUpperInvariant());
|
||||||
|
renderer.WriteChildren(block);
|
||||||
|
renderer.EnsureLine();
|
||||||
|
}
|
||||||
|
}
|
21
OliverBooth/Markdown/MarkdownExtensions.cs
Normal file
21
OliverBooth/Markdown/MarkdownExtensions.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using Markdig;
|
||||||
|
using OliverBooth.Markdown.Callout;
|
||||||
|
|
||||||
|
namespace OliverBooth.Markdown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="MarkdownPipelineBuilder" />.
|
||||||
|
/// </summary>
|
||||||
|
internal static class MarkdownExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Uses this extension to enable Obsidian-style callouts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pipeline">The pipeline.</param>
|
||||||
|
/// <returns>The modified pipeline.</returns>
|
||||||
|
public static MarkdownPipelineBuilder UseCallouts(this MarkdownPipelineBuilder pipeline)
|
||||||
|
{
|
||||||
|
pipeline.Extensions.AddIfNotAlready<CalloutExtension>();
|
||||||
|
return pipeline;
|
||||||
|
}
|
||||||
|
}
|
@ -122,6 +122,7 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.js" integrity="sha512-aoZChv+8imY/U1O7KIHXvO87EOzCuKO0GhFtpD6G2Cyjo/xPeTgdf3/bchB10iB+AojMTDkMHDPLKNxPJVqDcw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.js" integrity="sha512-aoZChv+8imY/U1O7KIHXvO87EOzCuKO0GhFtpD6G2Cyjo/xPeTgdf3/bchB10iB+AojMTDkMHDPLKNxPJVqDcw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/js/all.min.js" integrity="sha512-uKQ39gEGiyUJl4AI6L+ekBdGKpGw4xJ55+xyJG7YFlJokPNYegn9KwQ3P8A7aFQAUtUsAQHep+d/lrGqrbPIDQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/js/all.min.js" integrity="sha512-uKQ39gEGiyUJl4AI6L+ekBdGKpGw4xJ55+xyJG7YFlJokPNYegn9KwQ3P8A7aFQAUtUsAQHep+d/lrGqrbPIDQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.8/handlebars.min.js" integrity="sha512-E1dSFxg+wsfJ4HKjutk/WaCzK7S2wv1POn1RRPGh8ZK+ag9l244Vqxji3r6wgz9YBf6+vhQEYJZpSjqWFPg9gg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.8/handlebars.min.js" integrity="sha512-E1dSFxg+wsfJ4HKjutk/WaCzK7S2wv1POn1RRPGh8ZK+ag9l244Vqxji3r6wgz9YBf6+vhQEYJZpSjqWFPg9gg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
<script src="~/js/prism.min.js" asp-append-version="true" data-manual></script>
|
<script src="~/js/prism.min.js" asp-append-version="true" data-manual></script>
|
||||||
<script src="~/js/app.min.js" asp-append-version="true"></script>
|
<script src="~/js/app.min.js" asp-append-version="true"></script>
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ using Markdig;
|
|||||||
using OliverBooth.Data.Blog;
|
using OliverBooth.Data.Blog;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Data.Web;
|
||||||
using OliverBooth.Extensions;
|
using OliverBooth.Extensions;
|
||||||
|
using OliverBooth.Markdown;
|
||||||
|
using OliverBooth.Markdown.Callout;
|
||||||
using OliverBooth.Markdown.Template;
|
using OliverBooth.Markdown.Template;
|
||||||
using OliverBooth.Markdown.Timestamp;
|
using OliverBooth.Markdown.Timestamp;
|
||||||
using OliverBooth.Services;
|
using OliverBooth.Services;
|
||||||
@ -24,7 +26,31 @@ builder.Logging.AddSerilog();
|
|||||||
builder.Services.AddSingleton(provider => new MarkdownPipelineBuilder()
|
builder.Services.AddSingleton(provider => new MarkdownPipelineBuilder()
|
||||||
.Use<TimestampExtension>()
|
.Use<TimestampExtension>()
|
||||||
.Use(new TemplateExtension(provider.GetRequiredService<ITemplateService>()))
|
.Use(new TemplateExtension(provider.GetRequiredService<ITemplateService>()))
|
||||||
.UseAdvancedExtensions()
|
|
||||||
|
// we have our own "alert blocks"
|
||||||
|
.UseCallouts()
|
||||||
|
|
||||||
|
// advanced extensions. add explicitly to avoid UseAlertBlocks
|
||||||
|
.UseAbbreviations()
|
||||||
|
.UseAutoIdentifiers()
|
||||||
|
.UseCitations()
|
||||||
|
.UseCustomContainers()
|
||||||
|
.UseDefinitionLists()
|
||||||
|
.UseEmphasisExtras()
|
||||||
|
.UseFigures()
|
||||||
|
.UseFooters()
|
||||||
|
.UseFootnotes()
|
||||||
|
.UseGridTables()
|
||||||
|
.UseMathematics()
|
||||||
|
.UseMediaLinks()
|
||||||
|
.UsePipeTables()
|
||||||
|
.UseListExtras()
|
||||||
|
.UseTaskLists()
|
||||||
|
.UseDiagrams()
|
||||||
|
.UseAutoLinks()
|
||||||
|
.UseGenericAttributes() // must be last as it is one parser that is modifying other parsers
|
||||||
|
|
||||||
|
// no more advanced extensions
|
||||||
.UseBootstrap()
|
.UseBootstrap()
|
||||||
.UseEmojiAndSmiley()
|
.UseEmojiAndSmiley()
|
||||||
.UseSmartyPants()
|
.UseSmartyPants()
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@import "markdown";
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
background: #121212;
|
background: #121212;
|
||||||
color: #f5f5f5;
|
color: #f5f5f5;
|
||||||
|
142
src/scss/markdown-callouts.scss
Normal file
142
src/scss/markdown-callouts.scss
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
$callout-bg-blue: #1b2735;
|
||||||
|
$callout-bg-cyan: #233232;
|
||||||
|
$callout-bg-green: #223026;
|
||||||
|
$callout-bg-orange: #332a21;
|
||||||
|
$callout-bg-red: #352223;
|
||||||
|
$callout-bg-purple: #2c2835;
|
||||||
|
$callout-bg-grey: #2b2b2b;
|
||||||
|
|
||||||
|
$callout-fg-blue: #157aff;
|
||||||
|
$callout-fg-cyan: #53dfdd;
|
||||||
|
$callout-fg-green: #44cf6e;
|
||||||
|
$callout-fg-orange: #e9973f;
|
||||||
|
$callout-fg-red: #fb464c;
|
||||||
|
$callout-fg-purple: #a882ff;
|
||||||
|
$callout-fg-grey: #9e9e9e;
|
||||||
|
|
||||||
|
.callout {
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="note"] {
|
||||||
|
background-color: $callout-bg-blue;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="abstract"] {
|
||||||
|
background-color: $callout-bg-cyan;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-cyan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="info"] {
|
||||||
|
background-color: $callout-bg-blue;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="todo"] {
|
||||||
|
background-color: $callout-bg-blue;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="tip"] {
|
||||||
|
background-color: $callout-bg-cyan;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-cyan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="success"] {
|
||||||
|
background-color: $callout-bg-green;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="question"] {
|
||||||
|
background-color: $callout-bg-orange;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-orange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="warning"] {
|
||||||
|
background-color: $callout-bg-orange;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-orange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="failure"] {
|
||||||
|
background-color: $callout-bg-red;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="danger"] {
|
||||||
|
background-color: $callout-bg-red;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="bug"] {
|
||||||
|
background-color: $callout-bg-red;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="example"] {
|
||||||
|
background-color: $callout-bg-purple;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-purple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="cite"] {
|
||||||
|
background-color: $callout-bg-grey;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-callout="update"] {
|
||||||
|
background-color: $callout-bg-blue;
|
||||||
|
|
||||||
|
.callout-title {
|
||||||
|
color: $callout-fg-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.lucide {
|
||||||
|
width: 16px;
|
||||||
|
}
|
1
src/scss/markdown.scss
Normal file
1
src/scss/markdown.scss
Normal file
@ -0,0 +1 @@
|
|||||||
|
@import "markdown-callouts";
|
@ -6,8 +6,11 @@ import BlogPost from "./BlogPost";
|
|||||||
|
|
||||||
declare const Handlebars: any;
|
declare const Handlebars: any;
|
||||||
declare const Prism: any;
|
declare const Prism: any;
|
||||||
|
declare const lucide: any;
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
Prism.languages.extend('markup', {});
|
Prism.languages.extend('markup', {});
|
||||||
Prism.languages.hex = {
|
Prism.languages.hex = {
|
||||||
'number': {
|
'number': {
|
||||||
|
Loading…
Reference in New Issue
Block a user