What is a Page?

A Page in ruhoh is the base resource type for delivering content at a given URL. Usually this content is a literal file and by default the URL is just the 'clean' path to the file as it exists in your directory.

All page-like resources can have a layout, sub-layout, custom permalink, categories, tags and other features that make a page act like a page.

Create a Page

Page stubs can be created using the Ruhoh command-line client. Here's the basic format:

$ ruhoh <collection> <command> <argument 1> <argument 2>

All collections default to pages collections, so the <collection> argument can be anything you want.

The format to create a new page:

$ ruhoh <collection> new <title>

Example:

$ ruhoh essays new 'Hello world'

If the essays collection doesn't exist, ruhoh will create it:

  • essays
    • hello-world.md

Notice the title automatically gets normalized to a filename.

Create a page without specifying a title:

$ ruhoh essays new
  • about
    • untitled.md

Create a page within a subdirectory:

$ ruhoh pages new projects/android
  • essays
    • projects
      • android.md

Create a Draft

Drafts work exactly the same as a page except they are never included into your compiled (production) website. Conversely only published drafts are compiled.

Drafts may be created by using the draft command of the collection's command-line tool:

Create a page draft:

$ ruhoh pages draft

Create a post draft:

$ ruhoh posts draft

A file is created within the collection used named untitled-draft-n where n is an iterated number:

  • posts
    • drafts
      • untitled-draft-1.md

Optionally pass in a title:

$ ruhoh posts draft "The Greatest Draft Ever"
  • posts
    • drafts
      • the-greatest-draft-ever.md

Publish Drafts

To publish a draft, simply remove the file from its parent drafts folder.

  • posts
    • the-greatest-draft-ever.md
    • drafts

Working with Drafts in Preview Mode

since 2.1

Drafts are hidden from their collection's data but they are still accessible manually via their URL.

Uses ruhoh's built in dashboard to view all your drafts: http://localhost:9292/dash

Draft Filenames

Since draft files are automatically named untitled-1, untitled-2, and so forth, it quickly becomes annoying to have to manually update the filename to the draft's eventual title.

Use the ruhoh command line client to convert all untitled-n files to their corresponding titles if set:

$ ruhoh pages titleize
...
$ ruhoh posts titleize

This command looks for any resource file beginning with untitled, then attempts to rename it, but only if a title has been set.

Top Metadata

When creating a page ruhoh generates a file with content similar to:

---
title:
date: '2012-12-12'
description:
---

This metadata is known as Top Metadata. This metadata is parsed as YAML so it must be valid YAML. Validate your YAML if you get YAML parse errors

JSON

since 2.4

Top Metadata can now be written in JSON:

{
  "layout": "yay", 
  "description": null, 
  "tags": [
    "apple", 
    "orange"
  ], 
  "date": "2012-12-12", 
  "title": "Hello", 
  "categories": [
    "random"
  ]
}

... The rest of the page body is here ...

Format requirements:

  1. The syntax { starts the top metadata block and must be the very first line at the very first character position.
  2. The resultant object must be valid JSON. Validate your JSON if you get JSON parse errors.
  3. The syntax } ends the JSON object.

Note Ruhoh can only recognize this metadata if the JSON format is valid.

Categories

Add Categories

Add one or more categories to a page by including them into the page's YAML meta-data. This is the YAML block is at the top of the file:

---
title: a nice title
categories: code
---

A category can be multiple levels deep:

categories: "code/android/games"

This defines one category named code/android/games.

Also note that code, and code/android will not exist unless you explicitly define them as categories themselves.

To place the page in multiple categories you'll need to pass in an Array:

---
title: a nice title
categories: ['code/android/games', 'game-downloads']
---

or

---
title: a nice title
categories :
  - 'code/android/games'
  - 'game-downloads'
---

Tags

Add Tags

Add one or more tags to a page by including them into the page's YAML meta-data. This is the YAML block at the top of the file:

---
title: a nice title
tags: javascript
---

To add multiple tags, use an Array:

---
title: a nice title
tags: [javascript, tutorials, expert]
---

or

---
title: a nice title
tags: 
  - javascript
  - tutorials
  - expert
---

Collection (view)

All the pages you create are available as a pages collection in your View. The pages collection is modeled by its CollectionView class and contains methods and logic that act on that collection.

In your templates it looks like this:

{{ pages.some_method }}

You can see that the methods are namespaced by the given collection. If you create posts you would access the posts collection like this:

{{ posts.some_method }}

pages.all

pages.all maps to an Array containing all pages each modeled as a Page::ModelView object. Use mustache syntax to iterate over the array:

  <ul>
  {{# pages.all }}
    <li><a href="{{url}}">{{title}}</a></li>
  {{/ pages.all }}
  </ul>

pages.categories

pages.categories is a namespace for accessing categories defined on the given page.

pages.categories.all

To return a list of all categories for all pages use pages.categories.all

<ul class="tag_box inline">
{{# pages.categories.all }}
  <li>
    Category: <a href="{{ url }}">{{ name }} <span>{{ count }}</span></a>
    <h4>Pages</h4>
    <ul>
    {{# pages?to_pages }}
      <li><a href="{{ url }}">{{ title }}</a></li>
    {{/ pages?to_pages }}
    </ul>
  </li>
{{/ pages.categories.all }}
</ul>

pages.categories.my-category-name

To return a specific category, just call that category by name: pages.categories.my-category-name

<ul class="tag_box inline">
{{# pages.categories.my-category-name }}
  <li>
    Category: <a href="{{ url }}">{{ name }} <span>{{ count }}</span></a>
    <h4>Pages</h4>
    <ul>
    {{# pages?to_pages }}
      <li><a href="{{ url }}">{{ title }}</a></li>
    {{/ pages?to_pages }}
    </ul>
  </li>
{{/ pages.categories.my-category-name }}
</ul>

pages.tags

pages.tags is a namespace for accessing tags defined on the given page.

pages.tags.all

To return a list of all tags for all pages use pages.tags.all

<ul class="tag_box inline">
{{# pages.tags.all }}
  <li>
    Tag: <a href="{{ url }}">{{ name }} <span>{{ count }}</span></a>
    <h4>Pages</h4>
    <ul>
    {{# pages?to_pages }}
      <li><a href="{{ url }}">{{ title }}</a></li>
    {{/ pages?to_pages }}
    </ul>
  </li>
{{/ pages.tags.all }}
</ul>

pages.tags.my-tag-name

To return a specific tag, just call that tag by name: pages.tags.my-tag-name

<ul class="tag_box inline">
{{# pages.tags.my-tag-name }}
  <li>
    Tag: <a href="{{ url }}">{{ name }} <span>{{ count }}</span></a>
    <h4>Pages</h4>
    <ul>
    {{# pages?to_pages }}
      <li><a href="{{ url }}">{{ title }}</a></li>
    {{/ pages?to_pages }}
    </ul>
  </li>
{{/ pages.tags.my-tag-name }}
</ul>

pages.latest

The pages.latest helper method is the same as pages.all but is limited to the latest n pages as configured

Use mustache syntax to iterate over the array:

<ul>
{{# pages.latest }}
  <li>
    <a href="{{url}}">{{title}}</a>
    {{{ content }}}
  </li>
{{/ pages.latest }}
</ul>

pages.paginator

The paginator method intelligently renders chunks of pages per a given page. It knows which page it is on so there is no need to pass a specific page number.

To initiate the "root" page (page #1) use pages.paginator:

<h2>Paginated Pages</h2>

{{# pages.paginator }}
<div class="page">
  <h3 class="title"><a href="{{url}}">{{title}}</a> <span class="date">{{ date }}</span></h3>

  {{{ summary }}}

  <div class="more">
    <a href="{{url}}" class="btn">read more..</a>
  </div>
</div>
{{/ pages.paginator }}

pages.paginator_navigation

Use pages.paginator_navigation to display the page navigation links:

<ul>
{{#pages.paginator_navigation}}
  <li>
    {{^is_active_page}}
      <a href="{{url}}">{{name}}</a>
    {{/is_active_page}}
    
    {{#is_active_page}}
      <a href="{{url}}" style="color:red">{{name}}</a>
    {{/is_active_page}}
  </li>
{{/pages.paginator_navigation}}
</ul>

The links link to special pagination pages which use (by default) the paginator.html layout, which just calls pages.paginator again but it internally knows which pagination chunk to show.

See the paginator configuration docs below for more details.

Model (view)

Each and every page you create is modeled in the View by a ModelView class for that given page.

The collections should always return ModelView class instances if they are available:

  <ul>
  {{# pages.all }}
    <li><a href="{{url}}">{{title}}</a></li>
    <!-- 
      The methods called here are called on an instance of a page ModelView object.
    -->
    {{ date }}
    {{ some_custom_method }}
    ...
  {{/ pages.all }}
  </ul>

Metadata

The Model View holds a page's basic attributes as follows:

Attribute Description
page.id The page's globally unique id. The id is the relative path to the file on disk.
page.url The pages generated url/permalink.
page.title The page title as determined from the filename or set explicitly in the page's YAML meta-data.
page.date The page date as determined from the filename or set explicitly in the page's YAML meta-data.
page.layout The page layout as set in the page's YAML meta-data.
page.sub_layout The page sub_layout as processed by ruhoh
page.master_layout The page master_layout as processed by ruhoh. May not be set if the post uses only one layout.

Example Usage:

  <h1>{{ page.title }}</h1>
  <p>{{ page.date }}</p>
  <p>
    Link: <a href="{{page.url}}">{{page.title}}</a>
  </p>

Top Metadata

Note that any data added to a page's YAML meta-data is available in the page object.

---
title: Oh Happy Day
icon : sun
days :
  - monday
  - tuesday
---  

Above, we've set special attributes icon and days which are now availble:

Attribute Description
page.icon String 'sun'
page.days Array ['monday', 'tuesday']

page.content

page.content provides the pages content body with all templating logic parsed and the markup/markdown language converted.

Note the triple mustaches since we expect HTML to be output and do not want to escape characters.

{{{page.content}}}

page.summary

Special thanks to @stebalien for the summary implementation =).

The summary helper method displays a summary of your page's contents. Summary may be used within any block of iterated pages:

  <ul>
  {{# pages.all }}
    <li>
      <a href="{{url}}">{{title}}</a>
      {{{ summary }}}
    </li>
  {{/ pages.all }}
  </ul>

Summary may also be used for the current page object:

<h2><a href="{{url}}">{{title}}</a></h2>

{{{ page.summary }}}

Usage

summary_lines

By default ruhoh will do a basic line-count truncation. The summary will be made up of the first n lines of your page. Ruhoh does "smart" truncation in that it will break only after the last full HTML DOM node is returned.

See the configuration section at the end of this page for summary configuration parameters.

Explicit DOM node

Specify an explicit "summary" DOM node and ruhoh will return that as the summary instead.

Wrap any content within an HTML tag with class "summary":

In the days when everybody started fair, ...

<div class="summary">
  Quick Summary of my post!
</div>

... Best Beloved, the Leopard lived in a place called the High Veldt.

Note this HTML node can be anywhere in the page, it does not have to be at the beginning.

page.categories

Returns all the categories defined on the given page. Categories are represented as objects in the form:

{
  "name" => "category name", 
  "count" => 12, 
  "url" => '/categories#category-name'
}

List categories on the current page.

  <ul>
  {{# page.categories }}
    <li><a href="{{url}}">{{name}} <span>{{count}}</span></a></li>
  {{/ page.categories }}
  </ul>

List categories on a collection of pages.

  {{# pages.all }}
    <h3>{{title}}</h3>
    <h4>Categories</h4>
    <ul>
    {{# categories }}
      <li><a href="{{url}}">{{name}} <span>{{count}}</span></a></li>
    {{/ categories }}
    </ul>
  {{/ pages.all }}

page.tags

Returns all the tags defined on the given page. Tags are represented as objects in the form:

{
  "name" => "tag name",
  "count" => 12,
  "url" => '/tags#tag-name'
}

List tags on the current page.

  <ul>
  {{# page.tags }}
    <li><a href="{{url}}">{{name}} <span>{{count}}</span></a></li>
  {{/ page.tags }}
  </ul>

List tags on a collection of pages.

  {{# pages.all }}
    <h3>{{title}}</h3>
    <h4>Tags</h4>
    <ul>
    {{# tags }}
      <li><a href="{{url}}">{{name}} <span>{{count}}</span></a></li>
    {{/ tags }}
    </ul>
  {{/ pages.all }}

page.next

Returns the next or newer page in the list of all possible page resources. Nothing is returned if there is no newer page.

{{# page.next }}
  Newer: <a href="{{ url }}">{{ title }}</a></li>
{{/ page.next }}

page.previous

Returns the previous or older page in the list of all possible page resources. Nothing is returned if there is no previous page.

{{# page.previous }}
  Older: <a href="{{ url }}">{{ title }}</a></li>
{{/ page.previous }}

page.is_active_page

Returns Boolean true/false for whether or not the page is the currently displayed page or "active page".

This is useful for adding styling to denote the active element in a navigation list:

  <ul>
{{# pages.all }}
  {{# is_active_page }}
    <li class="active"><a href="{{ url }}" class="active">{{ title }}</a></li>
  {{/ is_active_page }}
  {{^ is_active_page }}
    <li><a href="{{ url }}">{{ title }}</a></li>
  {{/ is_active_page }}
{{/ pages.all }}
  </ul>

Configure

Layout

All pages have a global default layout value that will be used when the layout parameter is not specifically set in the page file's YAML meta-data. The default "default global layout value" for pages is set to: page.

Manually set a custom default layout in the config:

pages :
  layout : 'custom-page-layout'

Remember this is just a default. It allows you to not always have to specify a layout param in every page file. However layout values in page files always take precedence.

Permalink

Default Permalinks

By default all normal pages URLs will simply model their respective physical filepaths:

FILE: pages/personal/about-me.md => URL: /pages/personal/about-me

URLs are "pretty" by default; they omit the file extension e.g.:

/some-page vs /some-page.html or /some-page/index.html.

To preserve the "normal" style you can configure it in config.yml:

Preserve old-school paths for all pages:

pages:
  permalink: 'preserve'

Preserve the old-school paths on a per page basis:

# pages/some-page.md
---
title: some-page
descripton: you bet
permalink: 'preserve' # <-----------
---  

Custom Permalinks

Pages may also define a custom permalink format:

# config.yml

pages :
  permalink : '/pages/:title'

posts :
  permalink : '/:categories/:title'

Permalink Variables

since 2.6 Any arbitrary page metadata may now be used in the permalink format.

Special format variables:

Variable Description
:year Year from the page’s filename
:month Month from the page’s filename
:day Day from the page’s filename
:path The page file's path relative to the base of your website.
:relative_path The page file's path relative to its name-spaced directory.
:filename The page file's filename (path is not included).
:categories The specified categories for this page. If more than one category is set, only the first one is used. If no categories exist, the URL omits this parameter.
:i_month Month from the page’s filename without leading zeros.
:i_day Day from the page’s filename without leading zeros.

Permalink Examples

Given the post filename: 2009-04-29-green-milk-tea.md
with categories: ['california/food', 'dairy']

Permalink Format Output
(default) /2009/04/29/green-milk-tea.html
/:categories/:title /california/food/green-milk-tea/index.html
/:month-:day-:year/:title.html /04-29-2009/green-milk-tea.html
/blog/:year/:month/:day/:title /blog/2009/04/29/green-milk-tea/index.html

Per-Page Permalinks

Set a custom permalink format on a per-page basis via the pages's YAML meta-data:

# pages/my-cool-page.md
---
title: My Cool Page
permalink: '/blog/:month/:year:/:title'
---

Literal Per-Page Permalinks

Set a literal permalink by omitting variable tokens:

# pages/my-cool-post.md
---
title: My Cool Page
permalink: 'legacy-blog/123/RandomFolder/category/my_cool_page'
---

Literal permalinks are useful when importing legacy blog pages/posts from other blogging systems, since you'd want to preserve existing links.

NOTES

Each node of a literal permalink is still passed through CGI::escape() to properly build a valid URL. Please let me know if this causes any problems, or does not fit your use-case.

Date Format

since 2.5

If a page has a date attribute it can be displayed via the mustache syntax:

{{ page.date }}

The display format for this date can be customized by setting "date_format" in the global config file:

  • config.yml

The default format is:

"date_format" : "%Y-%m-%d"

Customize this format by using the directives outlined here: http://www.ruby-doc.org/core-2.0.0/Time.html#method-i-strftime

Example

To output the format: May 04, 2012

Specify this format directive in config.yml:

"date_format": "%B %d, %Y"

Note This is a global setting and will be set for all collections. This is not as consistant with the default collection API but you'd very likely want to have uniform date formatting across your site.

Sort Order

All pages are sorted alphabetically by title name in ascending order by default. The sort order is honored when iterating through a collection:

{{# essays.all }}
  {{ title }}
{{/ essays.all }}

Sorting can be customized on a per-collection basis by updating config.yml:

  • config.yml

Title

Sort by title, but in descending order:

"pages" :
  "sort" : ["title", "desc"]

Date

Sort by date in descending order:

"pages" :
  "sort" : ["date", "desc"]

This is useful for generating a traditional blog posts collection.

Note all pages in the collection are now required to have a parseable date in their Top Metadata or ruhoh will raise an error.

Custom

Any attribute can be used for sorting. Custom attributes are sorted via coercion. This means if you define integer values the sorter will order by number. If you define strings the sorter will order alphabetically.

Sort by a custom attribute named "position" in descending order:

"pages" :
  "sort" : ["position", "asc"]

File Extension

By default, Ruhoh uses the .md markdown extension. You can change the default extension for any resource via the config.yml file:

#config.yml
posts:
  permalink: "/:categories/:title/"
  ext: ".html" # <-- set custom default (always include leading period)

pages:
  summary_lines: 1
  ext: ".txt" # <-- set custom default (always include leading period)

Now generating pages produces:

$ ruhoh pages new projects/android
  • posts
  • pages
    • projects
      • android.txt

Summary

The summary is rendered using a "line count" parameter that will intelligently process the first n lines of your page. A custom summary line-count may be specified in the config file:

pages :
  stop_at_header: false # default is false
  summary_lines : 30   # default is 20

stop_at_header

If stop_at_header is set it takes precedence over summary_lines.

If stop_at_header is true the summary will stop before the first header element (h1, h2, h3, h4, h5, header, hgroup). This allows you to add a small introductory paragraph at the start of your pages.

If stop_at_header is a number the summary will stop before the nth header element.

Exclude

To exclude page files from the being processed by ruhoh, pass a String representation of a regular expression to the exclude parameter:

pages:
  exclude: '~$'

Optionally pass multiple values via Array:

pages:
  exclude: ['~$', '^rotten-pages\/']

Strings get converted to a regular expression using: Regexp.new(str) so encapsulating forward slashes are not needed. Remember to also escape necessary special characters.

Latest

pages.latest displays the latest n pages. Set a custom limit in the config file:

pages :
  latest : 5   # default is 10

Paginator

All collections have their own paginator. The default settings are outlined in the config.yml example below:

# config.yml
posts:
  paginator:
    enable: false
    # The url your paginated pages will be located: e.g /posts/index/1, /posts/index/2, /posts/index/3
    # Always start with a forward slash, as ruhoh will internally respect any base_path you set.
    url: "/posts/index/"
    per_page: 5
    # Set the custom page you want page#1 of your posts paginator to link to. (default: /posts/index/1)
    # Note the default is technically <paginator.url>/1 just as the other paginated pages.
    # It is up to you to display the paginator_pagination links somewhere in this case.
    #
    # root_page has been set to the root for a more traditional style blog feel.
    root_page: '/'
attribute description
enable true/false to enable/disable the paginator for the given collection. defaults to true.
url URL namespace for paginated pages e.g: /posts/index/1, /posts/index/2
per_page Number of posts per page
root_page Where the root page (page #1) is. Will be '/' assuming you want the traditional blog style.
layout The layout to use for every paginated page.

Remember to set configuration settings relative to the collection's config namespace.

RSS

An RSS feed is automatically generated for each collection when compiling your blog. By default the latest 20 posts are added to the feed. Configure this amount using rss:

posts :
  rss:
    enable: true # set to false to disable rss
    limit: 30   # default is 20
    # The url to prepend to the rss.xml feed, e.g.: /posts/rss.xml
    # Defaults to: "/<resource_name>/"
    # Always start with a forward slash, as ruhoh will internally respect any base_path you set.
    # url: "/posts/"

Scaffold

The generation tasks outlined above clone a collection-specific "scaffold" file. Ruhoh looks in the currently used collection's folder for a file named _scaffold.html (the extension can be anything).

  • posts
  • pages
    • _scaffold.html

The default scaffold file for all pages collections looks like this:

---
title:
date: '{{DATE}}'
description:
tags: []
---

You can specify any customizations you wish:

---
title:
date: '{{DATE}}'
description:
categories: ["default", "categories"]
---
<h1>For some reason I think all posts should have this header.</h1>

Now ruhoh will use your custom _scaffold file to create new pages for that collection.

You can also overwrite the global default pages _scaffold.html file by placing it in the base of your directory:

  • _scaffold.html
  • posts