private TemplateContext CreateTemplateContext(IReadOnlyDictionary <string, object>?constants = null)
        {
            // Template context
            var templateContext = new TemplateContext
            {
                MemberRenamer   = m => m.Name,
                MemberFilter    = m => true,
                LoopLimit       = int.MaxValue,
                StrictVariables = true
            };

            // Model
            var scriptObject = new ScriptObject();

            // Constants
            scriptObject.SetValue("Context", Context, true);
            scriptObject.SetValue("CoreStyleSheet", GetCoreStyleSheetCode(), true);
            scriptObject.SetValue("ThemeStyleSheet", GetThemeStyleSheetCode(_themeName), true);
            scriptObject.SetValue("HighlightJsStyleName", $"solarized-{_themeName.ToLowerInvariant()}", true);

            // Additional constants
            if (constants != null)
            {
                foreach (var(member, value) in constants)
                {
                    scriptObject.SetValue(member, value, true);
                }
            }

            // Functions
            scriptObject.Import("FormatDate",
                                new Func <DateTimeOffset, string>(d => SharedRenderingLogic.FormatDate(d, Context.DateFormat)));

            scriptObject.Import("FormatMarkdown",
                                new Func <string, string>(m => HtmlRenderingLogic.FormatMarkdown(Context, m)));

            scriptObject.Import("GetUserColor", new Func <Guild, User, string>(Guild.GetUserColor));

            scriptObject.Import("GetUserNick", new Func <Guild, User, string>(Guild.GetUserNick));

            // Push model
            templateContext.PushGlobal(scriptObject);

            // Push output
            templateContext.PushOutput(new TextWriterOutput(_writer));

            return(templateContext);
        }
        public override async Task WriteMessageAsync(Message message)
        {
            // If message group is empty or the given message can be grouped, buffer the given message
            if (!_messageGroupBuffer.Any() || HtmlRenderingLogic.CanBeGrouped(_messageGroupBuffer.Last(), message))
            {
                _messageGroupBuffer.Add(message);
            }
            // Otherwise, flush the group and render messages
            else
            {
                await RenderCurrentMessageGroupAsync();

                _messageGroupBuffer.Clear();
                _messageGroupBuffer.Add(message);
            }
        }
        public override async Task RenderMessageAsync(Message message)
        {
            // Render leading block if it's the first entry
            if (!_isLeadingBlockRendered)
            {
                await RenderLeadingBlockAsync();

                _isLeadingBlockRendered = true;
            }

            // If message group is empty or the given message can be grouped, buffer the given message
            if (!_messageGroupBuffer.Any() || HtmlRenderingLogic.CanBeGrouped(_messageGroupBuffer.Last(), message))
            {
                _messageGroupBuffer.Add(message);
            }
            // Otherwise, flush the group and render messages
            else
            {
                await RenderCurrentMessageGroupAsync();

                _messageGroupBuffer.Clear();
                _messageGroupBuffer.Add(message);
            }
        }