Using heading from page as title

Headings,

I prefer to write them like that, as a proper heading instead of just an item among others in the frontmatter. But i still want them to be used as the page’s <title>

To fix that the two functions below can be added to config.rb. Then add <%= pagetitle %> or %title= pagetitle in the layout.erb or layout.haml.

If there is a title: in the frontmatter it will be used – as usual. But if it is missing the first h1 heading on the page will be used instead.

Since the title is not actually added to the sitemap resource, this will not work with stuff that needs it there. I have not tested it with the blogging extension.

# Get the title/main heading of the page
#
# Useful when constructing the <title> of the page.
#
# If there is a title in the frontmatter, that will be used.
# Otherwise, will search for the first h1-heading, and use that.
#
# The title can be dynamic if collected from the heading, so erb/haml can be used.
# Example: <h1>Articles for <%= year %></h1>
#
# Limitations:
# This seams to be a rather expensive operation, so when listing lots of articles it will take noticeable time.
# Maybe the result should be cached, possibly in sitemap. I've made a first try at it, but unsuccessfully.
# I'll have to learn more about the sitemap to get it to work.
#
# @param [Middleman::Sitemap::Resource] page_to_get_title_from    The page that you want the title from
# @return [String] The title or h1 of the page
#
def title(page_to_get_title_from = current_page)
  if page_to_get_title_from.data.title
    # The title is in the metadata
    page_to_get_title_from.data.title # Return
  else
    # No title in metadata, using the first h1 from the content
    content = page_to_get_title_from.render({:layout => false})     # Renders the <body>-part to html
          # (Note: at first I tried just .render(), but since that includes the title, it became an endless loop)
    match = content.match(/<h1>(.*?)<\/h1>/)   # Search for first h1

    if match
      # Caching the result – Not implemented
      #   This did not do the trick: page_to_get_title_from.add_metadata({:page => {'title' => escape_html(match[1])}})

      return escape_html(match[1])
    else
      nil  # Return text of first h1, or nil if none found
    end
  end
end



# Produces a text suitable to put in the <title> tag of the page
#
# Use in layout.erb like this:  <title><%= pagetitle %></title>
# In layout.haml: %title= pagetitle
#
# You can provide your own sitename or use the site's directory name.
#
# Note: Amicus defines a 'page-title', that does a similar thing.
#
# @return [String] A text suitable to put in the <title> tag of the page
def pagetitle

  # Sitename
  sitename = 'anvandbart.se'  # Provide your own name here. Other options: :directory_name and :none
  if sitename == :directory_name
    sitename = Pathname.new(current_page.app.root_path).basename.to_s  # Uses the name of the sites directory
  end

  # Combine with title
  title_from_page = title
  if title_from_page
    if sitename == :none
      return title_from_page
    else
      return "#{title_from_page} | #{sitename}"
    end
  else
    # No title:-metadata or h1 title existed in the page
    if sitename == :none
      return '' # Keep empty or change to whatever you prefer, for example 'Middleman'
    else
      return sitename
    end
  end
end

After reading some more, specially Adam Luikart’s short but excellent overview, I think the best place for this function is in a extension, changing the sitemap when it’s just been loaded.

I’ll take another stab at it when I have time.

2 Likes

Here’s a shorter helper that does something similar. (In fact, it even falls back to a friendly version of the filename if all else fails.)

  # Use the title from frontmatter metadata,
  # or peek into the page to find the H1,
  # or fallback to a filename-based-title
  def discover_title(page = current_page)
    page.data.title || page.render({layout: false}).match(/<h1>(.*?)<\/h1>/) do |m|
      m ? m[1] : page.url.split(/\//).last.titleize
    end
  end

And then in the layout HAML, I have:

    %title
      - page_title = discover_title
      = "#{page_title} &mdash;" if page_title
      = data.site.name

(As I maintain multiple sites, I use data/site.yml for site configurations (like title, copyright string info, deploy configs, etc.). ‘data.site.name’ is the website’s name. Substitute it with sitename or whatever variable you may use.)

1 Like

I use something much simpler – a helper method for the heading of the page.

def page_title(site_name, separator = ' – ')
  local_title = current_page.data.title or page_heading
  [local_title, site_name].compact.join(separator)
end

def page_heading(title = nil, options = {})
  if title
    @page_heading = title
    content_tag(:h1, title, options)
  else
    @page_heading
  end
end

Then, in layout.erb:

<title><%= page_title 'My Awesome Site' %></title>

And on my Markdown page (e.g. about.md.erb):

---
title: This will override
---

<%= page_heading 'This will be an H1 and set the title at the same time' %>

Some regular **Markdown** here.

You can also omit the = to set the page title without outputting an <h1> tag.

2 Likes

Thanks… this was helpful for me. I’m new with ruby and I got stuck on a couple dumb things, so I figured I’d clarify them in case it helps anybody else.

  • The .titleize method isn’t native to Middleman, but can be included
    via the titleize gem. It looks like it’s a part of ruby on rails.
  • If your page source comes from straight markdown, then depending on your markdown engine you may need to adjust the regular expression. Mine is building id’s into my header tags (like this: <h1 id=“introduction”>Introduction</h1>), so I adjusted my expression like this: /<h.+>(.*?)</h1>/
  • You can output the title using erb like so: <title><%= discover_title %></title>