Class: JekyllGFMAdmonitions::GFMAdmonitionConverter
- Inherits:
-
Jekyll::Generator
- Object
- Jekyll::Generator
- JekyllGFMAdmonitions::GFMAdmonitionConverter
- Defined in:
- lib/jekyll-gfm-admonitions.rb
Overview
GFMAdmonitionConverter is a Jekyll generator that converts custom admonition blocks in markdown (e.g., ‘> [!IMPORTANT]`) into styled HTML alert boxes with icons.
This generator processes both posts and pages, replacing admonition syntax with HTML markup that includes appropriate iconography and CSS styling.
CSS injection can be disabled via _config.yml:
gfm_admonitions:
inject_css: false
Class Attribute Summary collapse
-
.admonition_pages ⇒ Object
readonly
Returns the value of attribute admonition_pages.
-
.inject_css ⇒ Object
Returns the value of attribute inject_css.
Class Method Summary collapse
Instance Method Summary collapse
- #admonition_html(type, title, text, icon) ⇒ Object
- #convert_admonitions(doc) ⇒ Object
- #generate(site) ⇒ Object
- #init_converter(site) ⇒ Object
- #process_collections(site) ⇒ Object
- #process_doc(doc) ⇒ Object
- #process_doc_content(doc) ⇒ Object
- #process_pages(site) ⇒ Object
Class Attribute Details
.admonition_pages ⇒ Object (readonly)
Returns the value of attribute admonition_pages.
33 34 35 |
# File 'lib/jekyll-gfm-admonitions.rb', line 33 def admonition_pages @admonition_pages end |
.inject_css ⇒ Object
Returns the value of attribute inject_css.
34 35 36 |
# File 'lib/jekyll-gfm-admonitions.rb', line 34 def inject_css @inject_css end |
Class Method Details
.reset! ⇒ Object
36 37 38 39 |
# File 'lib/jekyll-gfm-admonitions.rb', line 36 def reset! @admonition_pages = [] @inject_css = true end |
Instance Method Details
#admonition_html(type, title, text, icon) ⇒ Object
155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/jekyll-gfm-admonitions.rb', line 155 def admonition_html(type, title, text, icon) body = @markdown.convert(text) body = body.gsub(/href="(?!https?:\/\/)([^"]*?)\.md(#[^"]*?)?"/) do anchor = ::Regexp.last_match(2) || '' "href=\"#{::Regexp.last_match(1)}.html#{anchor}\"" end "<div class='markdown-alert markdown-alert-#{type}'>" \ "<p class='markdown-alert-title'>#{icon} #{title}</p>" \ "#{body}" \ "</div>" end |
#convert_admonitions(doc) ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/jekyll-gfm-admonitions.rb', line 135 def convert_admonitions(doc) doc.content.gsub!(/^([^\S\n]*)>[^\S\n]*\[!(IMPORTANT|NOTE|WARNING|TIP|CAUTION)\]([^\n]*)\n((?:\1[^\S\n]*>[^\S\n]*[^\n]*(?:\n|$))(?:(?![^\S\n]*>[^\S\n]*\[!)\1[^\S\n]*>[^\S\n]*[^\n]*(?:\n|$))*)/) do initial_indent = ::Regexp.last_match(1) type = ::Regexp.last_match(2).downcase title = ::Regexp.last_match(3).strip.empty? ? type.capitalize : ::Regexp.last_match(3).strip # Strip the blockquote prefix from each line. Per CommonMark, a `>` # marker consumes at most ONE following space, so we only remove a # single space here. Consuming all whitespace would flatten the # indentation that distinguishes nested list items (see issue #20). text = ::Regexp.last_match(4).gsub(/^#{Regexp.escape(initial_indent)}[^\S\n]*>[^\S\n]?/, '').strip icon = Octicons::Octicon.new(ADMONITION_ICONS[type]).to_svg html = admonition_html(type, title, text, icon) initial_indent.empty? ? html : html.gsub(/^/, initial_indent) end # Ensure a blank line exists after each admonition block to prevent Markdown parsing issues. doc.content.gsub!(/(<\/div>)(?!\n\n)/, "\\1\n\n") end |
#generate(site) ⇒ Object
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/jekyll-gfm-admonitions.rb', line 44 def generate(site) self.class.reset! inject_css_setting = site.config.dig('gfm_admonitions', 'inject_css') self.class.inject_css = (inject_css_setting != false) init_converter(site) process_collections(site) process_pages(site) Jekyll.logger.info 'GFMA:', "Converted admonitions in #{self.class.admonition_pages.length} file(s)." if self.class.inject_css Jekyll.logger.debug 'GFMA:', 'CSS injection enabled.' else Jekyll.logger.info 'GFMA:', 'CSS injection disabled (gfm_admonitions.inject_css: false).' end end |
#init_converter(site) ⇒ Object
62 63 64 65 66 67 68 |
# File 'lib/jekyll-gfm-admonitions.rb', line 62 def init_converter(site) @markdown = site.converters.find { |c| c.is_a?(Jekyll::Converters::Markdown) } return if @markdown raise 'Markdown converter not found. Please ensure that you have a markdown' \ ' converter configured in your Jekyll site.' end |
#process_collections(site) ⇒ Object
70 71 72 73 74 75 76 77 |
# File 'lib/jekyll-gfm-admonitions.rb', line 70 def process_collections(site) site.collections.each do |name, collection| collection.docs.each do |doc| Jekyll.logger.debug 'GFMA:', "Processing collection '#{name}' document '#{doc.path}' (#{doc.content.length} characters)." process_doc_content(doc) end end end |
#process_doc(doc) ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/jekyll-gfm-admonitions.rb', line 98 def process_doc(doc) # Return early if content is empty return if doc.content.empty? # If the content is frozen, we need to duplicate it so that we can modify it doc.content = doc.content.dup if doc.content.frozen? code_blocks = [] # Temporarily replace fenced code blocks by a tag, so that we don't process any # admonitions inside of code blocks. doc.content.gsub!(/(?:^|\n)(?<!>)\s*```.*?```/m) do |match| code_blocks << match "```{{CODE_BLOCK_#{code_blocks.length - 1}}}```" end indented_blocks = [] # Temporarily replace 4-space/tab indented code blocks (CommonMark §4.4). # These must be preceded by a blank line or appear at start of content — # indented code blocks cannot interrupt a paragraph. doc.content.gsub!(/(\A|\n\n)((?:(?:[ ]{4,}|\t)[^\n]*(?:\n|\z))+)/) do anchor = ::Regexp.last_match(1) block = ::Regexp.last_match(2) indented_blocks << block "#{anchor}{{INDENTED_CODE_BLOCK_#{indented_blocks.length - 1}}}" end convert_admonitions(doc) # Restore indented code blocks, then fenced code blocks. doc.content.gsub!(/\{\{INDENTED_CODE_BLOCK_(\d+)\}\}/) do indented_blocks[::Regexp.last_match(1).to_i] end doc.content.gsub!(/```\{\{CODE_BLOCK_(\d+)}}```/) do code_blocks[::Regexp.last_match(1).to_i] end end |
#process_doc_content(doc) ⇒ Object
86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/jekyll-gfm-admonitions.rb', line 86 def process_doc_content(doc) original_content = doc.content.dup process_doc(doc) return unless doc.content != original_content # Store a reference to all the pages we modified, to inject the CSS post render # (otherwise GitHub Pages sanitizes the CSS into plaintext). # Only track when CSS injection is enabled. self.class.admonition_pages << doc if self.class.inject_css end |
#process_pages(site) ⇒ Object
79 80 81 82 83 84 |
# File 'lib/jekyll-gfm-admonitions.rb', line 79 def process_pages(site) site.pages.each do |page| Jekyll.logger.debug 'GFMA:', "Processing page '#{page.path}' (#{page.content.length} characters)." process_doc_content(page) end end |