Class: Mill::Resource::Text

Inherits:
Mill::Resource show all
Includes:
HTMLHelpers
Defined in:
lib/mill/resources/text.rb

Direct Known Subclasses

Index

Constant Summary collapse

FileTypes =
%w{
  text/plain
  text/html
  text/markdown
}

Constants included from HTMLHelpers

HTMLHelpers::LinkElementsXPath

Instance Attribute Summary collapse

Attributes inherited from Mill::Resource

#content, #date, #input_file, #node, #output_file, #path, #public, #site

Instance Method Summary collapse

Methods included from HTMLHelpers

#check_errors, #find_link_elements, #google_analytics, #html_document, #html_fragment, #link_if, #parse_html, #parse_html_fragment, #replace_element

Methods inherited from Mill::Resource

#absolute_uri, #build, #change_frequency, #children, #parent, #public?, #redirect?, #save, #siblings, #tag_uri, #text?, #uri

Constructor Details

#initialize(title: nil, summary: nil, author: nil, public: true, output_file: nil, **args) ⇒ Text

Returns a new instance of Text.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/mill/resources/text.rb', line 21

def initialize(title: nil, summary: nil, author: nil, public: true, output_file: nil, **args)
  @title = title
  @summary = summary
  @author = author
  @type = nil
  super(
    public: public,
    output_file: output_file&.replace_extension('.html'),
    **args)
  if @path
    @path.sub!(%r{\.html$}, '') if @site&.shorten_uris
    @path.sub!(%r{(.*)index$}, '\1')
  end
end

Instance Attribute Details

#authorObject

Returns the value of attribute author.



18
19
20
# File 'lib/mill/resources/text.rb', line 18

def author
  @author
end

#summaryObject

Returns the value of attribute summary.



17
18
19
# File 'lib/mill/resources/text.rb', line 17

def summary
  @summary
end

#titleObject

Returns the value of attribute title.



16
17
18
# File 'lib/mill/resources/text.rb', line 16

def title
  @title
end

#typeObject

Returns the value of attribute type.



19
20
21
# File 'lib/mill/resources/text.rb', line 19

def type
  @type
end

Instance Method Details



144
145
146
147
148
149
150
151
# File 'lib/mill/resources/text.rb', line 144

def add_external_link_targets(rel='noopener')
  @content.xpath('//a').each do |a|
    if a['href'] && a['href'] =~ /^\w+:/
      a['target'] = '_blank'
      a['rel'] = rel
    end
  end
end

#add_image_sizesObject



159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/mill/resources/text.rb', line 159

def add_image_sizes
  @content.xpath('//img').each do |img|
    # skip elements that already have width/height defined
    next if img[:width] || img[:height]
    img_link = Addressable::URI.parse(img['src'])
    raise Error, "No link in <img> element: #{img.to_s}" if img_link.nil? || img_link.empty?
    next if img_link.host
    img_uri = uri + img_link
    img_resource = @site.find_resource(img_uri) or raise Error, "Can't find image for #{img_uri}"
    img[:width], img[:height] = img_resource.width, img_resource.height
  end
end

#body(&block) ⇒ Object



125
126
127
128
129
130
131
132
133
134
# File 'lib/mill/resources/text.rb', line 125

def body(&block)
  html_fragment do |html|
    html.body do
      if (elem = content_body)
        html << elem.children.to_html
      end
      yield(html) if block_given?
    end
  end
end

#content_bodyObject



140
141
142
# File 'lib/mill/resources/text.rb', line 140

def content_body
  @content && @content.at_xpath('/html/body')
end

#content_headObject



136
137
138
# File 'lib/mill/resources/text.rb', line 136

def content_head
  @content && @content.at_xpath('/html/head')
end

#feed_contentObject



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/mill/resources/text.rb', line 191

def feed_content
  if (body = content_body)
    # If we have a main element (<div class="main"> or <main>), use that.
    # Otherwise, use the body, but delete header/footer/nav divs or elements.
    if (main = body.at_xpath('//div[@id="main"]')) || (main = body.at_xpath('//main'))
      main.children
    elsif (article = body.at_xpath('//article'))
      article.children
    else
      body_elem = body.clone
      %w{header nav masthead footer}.each do |name|
        if (elem = body_elem.at_xpath("//div[@id=\"#{name}\"]")) || (elem = body_elem.at_xpath("//#{name}"))
          elem.remove
        end
      end
      body_elem.children
    end
  else
    warn "Warning: Resource #{path} (#{self.class}) has no content"
    nil
  end
end

#final_contentObject



99
100
101
102
103
104
105
106
# File 'lib/mill/resources/text.rb', line 99

def final_content
  html_document(@site&.html_version || :html5) do |doc|
    doc.html(lang: 'en') do |html|
      html.parent << head
      html.parent << body
    end
  end.to_html
end

#head(&block) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/mill/resources/text.rb', line 108

def head(&block)
  html_fragment do |html|
    html.head do
      head = content_head
      if (title = @title || (head && head.at_xpath('title')))
        html.title { html << title.to_html }
      end
      yield(html) if block_given?
      if head
        head.children.reject { |e| e.text? || e.name == 'title' }.each do |e|
          html << e.to_html
        end
      end
    end
  end
end

#inspectObject



36
37
38
39
40
41
42
43
# File 'lib/mill/resources/text.rb', line 36

def inspect
  super + ", title: %p, summary: %p, author: %p, type: %p" % [
    @title,
    @summary,
    @author,
    @type,
  ]
end

#loadObject



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/mill/resources/text.rb', line 45

def load
  super
  if @input_file
    @content = @input_file.read
    @type = case @input_file.extname.downcase
    when '.md', '.mdown', '.markdown'
      :markdown
    when '.textile'
      :textile
    when '.txt'
      :pre
    when '.htm', '.html'
      :html
    else
      raise "Unknown text type: #{@input_file}"
    end
    if @type != :html
      parse_text_header
      @content = (@content || '').to_html(mode: @type, multiline: true)
      @type = :html
    end
    begin
      @content = parse_html(@content)
    rescue Error => e
      raise e, "#{@input_file}: #{e}"
    end
    parse_html_header
  end
end

#parse_html_headerObject



75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/mill/resources/text.rb', line 75

def parse_html_header
  unless @title
    if (title_elem = @content.at_xpath('/html/head/title'))
      @title = title_elem.text
    else
      @title = @path
    end
  end
  @content.xpath('/html/head/meta[@name]').each do |meta|
    send("#{meta['name']}=", meta['content'])
  end
end

#parse_text_headerObject



88
89
90
91
92
93
94
95
96
97
# File 'lib/mill/resources/text.rb', line 88

def parse_text_header
  if @content.split(/\n/, 2).first =~ /^\w+:\s+/
    header, @content = @content.split(/\n\n/, 2)
    header.split(/\n/).map do |line|
      key, value = line.strip.split(/:\s*/, 2)
      key = key.gsub('-', '_').downcase.to_sym
      send("#{key}=", value)
    end
  end
end

#remove_commentsObject



153
154
155
156
157
# File 'lib/mill/resources/text.rb', line 153

def remove_comments
  @content.xpath('//comment()').each do |comment|
    comment.remove
  end
end


172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/mill/resources/text.rb', line 172

def shorten_links
  find_link_elements(@content).each do |attribute|
    elem = attribute.parent
    link_uri = Addressable::URI.parse(attribute.value) or raise Error, "Can't parse #{attribute.value.inspect} from #{xpath.inspect}"
    link_uri = uri + link_uri
    if link_uri.relative?
      self_uri = uri.normalize
      self_uri.scheme = 'http'
      link_uri.scheme = 'http'
      attribute.value = self_uri.route_to(link_uri)
      # ;;warn "[#{path}] shortened link #{elem.name}/@#{attribute.name}: #{link_uri} => #{attribute.value}"
    end
  end
end