Skip to content

Instantly share code, notes, and snippets.

@synth
Created May 17, 2025 05:40
Show Gist options
  • Save synth/9e6fc4182a56c1a2c5791d83a71858b3 to your computer and use it in GitHub Desktop.
Save synth/9e6fc4182a56c1a2c5791d83a71858b3 to your computer and use it in GitHub Desktop.
Html to Slack Markdown Converter
require 'nokogiri'
class SlackMarkdownConverter
class << self
def convert(html, options = {})
converter = new(options)
converter.convert(html)
end
end
def initialize(options = {})
@options = options
@include_images = options.fetch(:include_images, false)
end
def convert(html)
return '' if html.nil? || html.empty?
doc = Nokogiri::HTML.fragment(html)
process_node(doc)
end
private
def process_node(node)
result = ''
node.children.each do |child|
result += process_element(child)
end
# Clean up extra whitespace
result.gsub(/\n{3,}/, "\n\n").strip
end
def process_element(element)
case element.name
when 'text'
element.text
when 'br'
"\n"
when 'p'
process_node(element) + "\n\n"
when 'strong', 'b', 'em', 'i', 'code'
process_formatting_element(element)
when 'pre'
"```\n#{process_node(element)}\n```"
when 'blockquote'
process_blockquote(element)
when 'a'
process_link(element)
when 'ul', 'ol'
process_list_container(element)
when 'li'
process_node(element) + "\n"
when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
"*#{process_node(element)}*\n\n"
when 'img'
@include_images ? process_image(element) : ''
when 'div', 'span'
process_node(element)
when 'hr'
"---\n"
else
# For any other tags, just process their contents
process_node(element)
end
end
def process_formatting_element(element)
case element.name
when 'strong', 'b'
"*#{process_node(element)}*"
when 'em', 'i'
"_#{process_node(element)}_"
when 'code'
"`#{process_node(element)}`"
end
end
def process_list_container(element)
marker = element.name == 'ul' ? '• ' : lambda { |i| "#{i + 1}. " }
process_list(element, marker)
end
def process_blockquote(node)
content = process_node(node)
lines = content.split("\n")
quoted_lines = lines.map { |line| "> #{line}" }
quoted_lines.join("\n") + "\n\n"
end
def process_link(node)
text = process_node(node)
href = node['href']
# If text is the same as href or just a basic placeholder, show href only
if text.strip == href || text.strip.downcase == 'link'
"<#{href}>"
else
"<#{href}|#{text}>"
end
end
def process_list(node, marker)
result = "\n"
node.children.each_with_index do |child, i|
next unless child.name == 'li'
prefix = marker.is_a?(Proc) ? marker.call(i) : marker
content = process_node(child).strip
result += "#{prefix}#{content}\n"
end
result + "\n"
end
def process_image(node)
alt_text = node['alt'] || 'image'
src = node['src']
"<#{src}|#{alt_text}>"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment