lib/qiita/markdown/filters/truncate.rb
module Qiita
module Markdown
module Filters
# A filter for truncating a document without breaking the document
# structure.
#
# You can pass `:length` and `:omission` option to :truncate context.
#
# @example
# Truncate.new(doc, truncate: { length: 50, omission: '... (continued)' })
class Truncate < HTML::Pipeline::Filter
DEFAULT_OPTIONS = {
length: 100,
omission: "…".freeze,
}.freeze
def call
@current_length = 0
@previous_char_was_blank = false
traverse(doc) do |node|
if exceeded?
node.remove
elsif node.text?
process_text_node(node)
end
end
doc
end
private
# Traverse the given node recursively in the depth-first order.
# Note that we cannot use Nokogiri::XML::Node#traverse
# since it traverses the node's descendants _before_ the node itself.
# https://github.com/sparklemotion/nokogiri/blob/v1.6.6.2/lib/nokogiri/xml/node.rb#L571-L574
def traverse(node, &block)
yield(node)
node.children.each do |child_node|
traverse(child_node, &block)
end
end
def exceeded?
@current_length > max_length
end
def process_text_node(node)
node.content.each_char.with_index do |char, index|
current_char_is_blank = char.strip.empty?
@current_length += 1 if !@previous_char_was_blank || !current_char_is_blank
@previous_char_was_blank = current_char_is_blank
if exceeded?
node.content = node.content.slice(0...(index - omission.size)) + omission
break
end
end
end
def max_length
options[:length]
end
def omission
options[:omission] || "".freeze
end
def options
@options ||= DEFAULT_OPTIONS.merge(context[:truncate] || {})
end
end
end
end
end