Builds a family tree from your file and folder locations. Lets you reference the next, previous, parent and all children of a particular resource. Very handy when used in conjunction with metalsmith-relative-links and [mustache-hbt-md], which lets you construct markdown with links to other pages, such as subpage listings or automatic navigation through pages.
All of the file objects are assigned an .ancestry
property, which is an object that contains the following properties:
fileObject.ancestry = {
// These refer to just the file object itself
basename: "index.html", // The base filename of the source file.
path: "test/index.html", // Full name of the source file.
self: { ... }, // The file object that has this ancestry.
// Up in the hierarchy.
parent: { ... }, // Parent of this file object. **
// Navigating member files of this folder.
members: [ ... ], // All file objects in the same folder.
memberIndex: 0, // The current file's index in members array.
firstMember: { ... }, // The first member (same as .members[0]).
lastMember: { ... }, // The last member.
nextMember: { ... }, // The next member in the list.
prevMember: { ... }, // The previous member in the list.
// Jumping to other folders at the same level.
siblings: [ ... ], // One file object per folder. **
siblingIndex: 2, // The current file's index in siblings array.
firstSibling: { ... }, // The first sibling (same as .siblings[0]).
lastSibling: { ... }, // The last sibling.
nextSibling: { ... }, // The next sibling in the list.
prevSibling: { ... }, // The previous sibling in the list.
// Descending in the directory tree.
children: [ ... ], // First member of each descendent. **
childrenByName: { ... }, // First member of each descendent, by filename. **
firstChild: { ... }, // The first child (same as .children[0]).
lastChild: { ... } // The last child.
}
**
- These items are arrays that contain single file objects that "stand for" the entire folder. The file that is there is the first one sorted, so it would always appear as .members[0]
. This is because one can not link to a folder because there are no folders in Metalsmith - only files.
Using this ancestry
object, you can navigate to other file objects that would be supplied to any Metalsmith plugin. When you use metalsmith-relative-links and supply its link function a file object, it can return the URI necessary to link two resources together.
All files in a directory tree are placed together. Here's a sample directory listing.
index.html
site.css
about/index.html
contact/index.html
contact/email.html
contact/in-person.html
contact/live-chat/index.html
contact/live-chat/chat.jar
contact/live-chat/chat.swf
contact/location/index.html
If you were to inspect the .ancestry
object that relates to contact/email.html
, it would look something like the following. Please note that the example uses the shorthand «filename»
to indicate we're linking to that specific file object.
{
basename: "email.html",
path: "contact/email.html",
self: «contact/email.html»,
// Up in the hierarchy.
parent: «index.html»,
root: «index.html»,
// Navigating member files of this folder.
memberIndex: 1,
members: [
«contact/index.html»
«contact/email.html»
«contact/in-person.html»
],
firstMember: «contact/index.html»,
lastMember: «contact/in-person.html»,
nextMember: «contact/in-person.html»,
prevMember: «contact/index.html»,
// Jumping to other folders at the same level.
siblingIndex: 1,
siblings: [
«about/index.html»,
«contact/index.html»
],
firstSibling: «about/index.html»,
lastSibling: «contact/index.html»,
nextSibling: null,
prevSibling: «about/index.html»,
// Descending in the directory tree.
children: [
«contact/live-chat/index.html»,
«contact/location/index.html»
],
childrenByName: {
"live-chat": «contact/live-chat/index.html»,
"location": «contact/location/index.html»
},
firstChild: «contact/live-chat/index.html»,
lastChild: «contact/location/index.html»
}
A word of caution: if you were processing a file and you had access to its metadata, then .ancestry.self
would be pointing back at itself. It's a circular link, so be very careful when you start dumping these objects to any logging system.
The real power shows up when you leverage these family links inside of your content. Combining metalsmith-hbt-md and metalsmith-relative-links, you can make a subpage listing. This example uses files that have title
and summary
in their file's frontmatter.
{{#ancestry.children}}
* [{{title}}]({{link.from ancestry.parent}}) - {{summary}}
{{/ancestry.children}}
If this was generated for the index.html
within the above file tree, you would see this in the rendered markdown:
* [About Us](about/) - All about the creators of this site.
* [Contact Information](contact/) - The various ways you can reach us.
Another way to use this same information would be to see the amount of progress through a particular set of instructions. With a guide and each step being a page, you could use .memberIndex
or .siblingIndex
to determine how close the user is to completion.
You could also use this for instruction pages or in a gallery.
This example uses the #if helper from Handlebars and navigates pages in the same folder.
Link generation is done using metalsmith-relative-links.
{{#if ancestry.previousSibling}}
[Previous]({{link.to ancestry.previousSibling}})
{{/if}}
{{#if ancestry.nextSibling}}
[Next]({{link.to ancestry.nextSibling}})
{{/if}}
If you use another system, maybe metalsmith-mustache-metadata can help.
This example uses that plugin and links to folders that have a shared parent folder.
Link generation is done using metalsmith-relative-links.
{{#ancestry.previousMember?}}
[Previous]({{link.to ancestry.previousMember}})
{{/ancestry.previousMember?}}
{{#ancestry.nextMember?}}
[Next]({{link.to ancestry.nextMember}})
{{/ancestry.nextMember?}}
npm
can do this for you.
npm install --save metalsmith-ancestry
Include this like you would include any other plugin. Here's a CLI example that also shows the default options. You don't need to specify any of these unless you want to change its value.
{
"plugins": {
"metalsmith-ancestry": {
"ancestryProperty": "ancestry",
"match": "**/*",
"matchOptions": {},
"reverse": false,
"sortBy": null,
"sortFilesFirst": "**/index.{htm,html,jade,md}"
}
}
}
And this is how you use it in JavaScript, with a small description of each option.
// Load this, just like other plugins.
var ancestry = require("metalsmith-ancestry");
// Then in your list of plugins you use it.
.use(ancestry())
// Alternately, you can specify options. The values shown here are
// the defaults.
.use(ancestry({
// Property name that gets the ancestry data object.
ancestryProperty: "ancestry",
// Pattern of files to match in case you want to limit processing
// to specific files.
match: "**/*",
// Options for matching files. See metalsmith-plugin-kit for
// more information.
matchOptions: {},
// Reverse the sorting of siblings.
reverse: false,
// How to sort siblings. See later documentation.
sortBy: null,
// Files that should be placed first, regardless. Explained below.
sortFilesFirst: "**/index.{htm,html,jade,md}"
})
This uses metalsmith-plugin-kit to match files. The .matchOptions
object can be filled with options to control how files are matched.
This is the most complex portion of the code. It is also the reason that sorting functions are exported with the plugin; those are covered later.
First, let's discuss the sortBy
option. It is allowed to accept any of the following:
null
- Don't worry about sorting and let the plugin do it for you automatically. The plugin sorts based onancestry.path
, case-insensitively."propertyName"
- All single strings are treated as property names and are sorted case-insensitively.["propName1", "propName2"]
- Sort by multiple property names. If the first property is identical in both file objects then it falls back to a second and third sort (as many as you define in the array).function (a, b) {...}
- Use the defined function for sorting file objects. You can compose one from functions you build and the sorting functions that the Ancestry module provides.
Because index files are typically the entry point and should be first, the sortFilesFirst
property in the options allows you to configure this. It also allows several types of inputs.
null
- This tells the plugin to sort with typical index files first. It matchesindex.htm
,index.html
,index.jade
andindex.md
."**/index.html"
- Strings match against the file usingmetalsmith-plugin-kit.filenameMatcher
. So you can specify exact filenames or patterns with wildcards./(^|\/|\\)index\.([^.]*)/
- Regular expression objects are allowed. This one would match any file starting withindex.
with any extension but only one extension.function (path) { ... }
- Define your own function for the utmost in control.[ matcher1, matcher2 ]
- An array whose values are any of the above would also work. In this way you can easily match multiple globs or a complex series of behavior with several functions.
In addition, you can easily reverse the sort by setting reverse: true
in the options.
On the ancestory
function are some sorting properties. They are mostly exposed for easy testing but you are welcome to use them.
sortFunction = ancestry.sortByProperty(name)
Returns a function to sort file objects by a specific property name. The sorting uses ancestry.sortStrings()
internally, so it is case insensitive.
files = [
{
contents: Buffer.from("Fake file contents"),
title: "Spring"
},
{
contents: Buffer.from("Fake file contents"),
title: "Summer"
},
{
contents: Buffer.from("Fake file contents"),
title: "Fall"
},
{
contents: Buffer.from("Fake file contents"),
title: "Winter"
}
];
files.sort(ancestry.sortByProperty("title"));
// Result order:
// Fall, Spring, Summer, Winter
sortFunction = ancestry.sortCombine(functionListArray)
Array.prototype.sort
only allows a single sorting function. .sortCombine()
lets you take multiple sort functions and it will merge them together into a single function. If the first function returns a tie, the next one is used and so forth.
arr.sort(ancestry.sortCombine([
firstSortFunction,
secondSortFunction
]));
sortFunction = ancestry.sortReverse(originalSortFunction)
Reverses any sort.
arr = [ "test1", "Table", "ta" ];
arr.sort(ancestry.sortReverse(ancestry.sortStrings));
console.log(arr); // [ "test1", "Table", "ta" ];
sortResult = ancestry.sortStrings(a, b)
Returns the value from sorting two strings, case-insensitively.
arr = [ "test1", "Table", "ta" ];
arr.sort(ancestry.sortStrings);
console.log(arr); // [ "ta", "Table", "test1" ];
The option .sortFilesFirst
no longer can accept objects with a .match
property. This appeared to be unused functionality.
If you used .matchOptions
and that controled how globs were used with .sortFilesFirst
, you now need to set the new property .sortFilesFirstOptions
.
Backwards compatible. Only added the .root
property.
Properties were renamed in order to avoid confusion with additional functionality. Change .first
, .last
, .next
and.previous
to .firstMember
, .lastMember
, .nextMember
and .prevMember
. Also, to make sure directories were the first-class citizens in the family tree, .siblings
was renamed to .members
.
Metalsmith Ancestry - Generate a hierarchical listing of files based on where they are in the file tree. Add useful navigation metadata to files in your Metalsmith build.
Example
var ancestry = require("metalsmith-ancestry");
// Create your Metalsmith instance and add this like other middleware.
metalsmith.use(ancestry({
// Options go here.
});
- metalsmith-ancestry
- module.exports(options) ⇒
function
⏏- ~sortCombine(sorts) ⇒
function
- ~sortStrings(a, b) ⇒
number
- ~sortReverse(sortFunction) ⇒
function
- ~sortByProperty(propName) ⇒
function
- ~sortByMatchingFilename(filesFirst, filesFirstOptions, ancestryProperty) ⇒
function
- ~buildSortFunction(sortBy, reverse, filesFirst, filesFirstOptions, ancestryProperty) ⇒
function
- ~sortMembers(filesByFolder, sortFn)
- ~assignParent(filesByFolder, ancestry)
- ~assignRoot(options, ancestry)
- ~assignRelativeLinks(ancestry, suffix, list)
- ~assignMemberLinks(ancestry)
- ~assignChildren(filesByFolder, sortFn, options, ancestry)
- ~assignSiblings(options, ancestry)
- ~assignIndexes(ancestry)
- ~metalsmithFile :
Object
- ~metalsmithFileMap :
Object.<string, metalsmithFile>
- ~ancestryOptions :
Object
- ~ancestry :
Object
- ~ancestrySortBy :
function
|string
|Array.<string>
|null
- ~sortCombine(sorts) ⇒
- module.exports(options) ⇒
Factory to build middleware for Metalsmith.
Kind: Exported function
Params
- options
module:metalsmith-ancestry~options
Return a function that chains together multiple sort functions.
Kind: inner method of module.exports
Params
- sorts
Array.<function()>
Returns the right value from sorting two strings. Strings are sorted case-insensitively.
Kind: inner method of module.exports
Params
- a
string
- b
string
Reverses a sort function.
Kind: inner method of module.exports
Params
- sortFunction
function
Return a function that will sort file objects by a property. This will sort numbers appropriately as long as both values are numbers. Otherwise, this falls back to a string-based sort.
Kind: inner method of module.exports
Params
- propName
string
Files that match should be sorted first.
Kind: inner method of module.exports
Params
- filesFirst
module:metalsmith-plugin-kit~matchList
- filesFirstOptions
module:metalsmith-plugin-kit~matchOptions
- ancestryProperty
string
module.exports~buildSortFunction(sortBy, reverse, filesFirst, filesFirstOptions, ancestryProperty) ⇒ function
Create the sorting function using the options that were supplied.
Kind: inner method of module.exports
Params
- sortBy
ancestrySortBy
- reverse
boolean
- filesFirst
module:metalsmith-plugin-kit~matchList
- filesFirstOptions
module:metalsmith-plugin-kit~matchOptions
- ancestryProperty
string
Sort all members so they are in the right order before we determine the nextMember and prevMember links.
Kind: inner method of module.exports
Params
- filesByFolder
Object.<string, Array>
- Ancestories grouped by their folder. - sortFn
function
- How children get sorted.
Link to the parent if one exists.
Kind: inner method of module.exports
Params
- filesByFolder
Object.<string, Array>
- Ancestories grouped by their folder. - ancestry
ancestry
Follow parent links up until there are no more, then link directly to the root.
Kind: inner method of module.exports
Params
- options
module:metalsmith-ancestry~options
- ancestry
ancestry
Assigns the next, prev, first, last links for a given type.
Kind: inner method of module.exports
Params
- ancestry
ancestry
- suffix
string
- list
Array.<files>
Assign the member related links
Kind: inner method of module.exports
Params
- ancestry
ancestry
Makes the list of children for each file object. The work is only done on the first member and copied to all subsequent members.
Kind: inner method of module.exports
Params
- filesByFolder
Object.<string, Array>
- Ancestories grouped by their folder. - sortFn
function
- How children get sorted. - options
module:metalsmith-ancestry~options
- ancestry
ancestry
Siblings is simply a copy of the parent's children.
Kind: inner method of module.exports
Params
- options
module:metalsmith-ancestry~options
- ancestry
ancestry
Assign siblingIndex and memberIndex
Kind: inner method of module.exports
Params
- ancestry
ancestry
This is a typical file object from Metalsmith.
Kind: inner typedef of module.exports
Properties
- contents
Buffer
- mode
string
An object whose values are metalsmithFile objects
Kind: inner typedef of module.exports
Options for this plugin.
Kind: inner typedef of module.exports
See: https://github.com/fidian/metalsmith-plugin-kit
Properties
- ancestryProperty
string
- The metadata property name to assign - match
module:metalsmith-plugin-kit~matchList
- Files to match. Defaults to all files. - matchOptions
module:metalsmith-plugin-kit~matchOptions
- Options controlling globbing behavior. - reverse
boolean
- sortBy
ancestrySortBy
- How to sort siblings. - sortFilesFirst
module:metalsmith-plugin-kit~matchList
- What files should come first in the sibling list. Defaults to index files withhtm
,html
,jade
, ormd
extensions. - sortFilesFirstOptions
module:metalsmith-plugin-kit~matchOptions
- Options controlling the globbing behavior ofsortFilesFirst
.
This represents the type of object that is added as metadata to each of the file objects.
Kind: inner typedef of module.exports
Properties
- basename
string
- file.ext - children
Array.<metalsmithFile>
- First member in subfolders. - childrenByName
metalsmithFileMap
- Find specific child folder. - firstChild
metalsmithFile
- firstMember
metalsmithFile
- firstSibling
metalsmithFile
- lastChild
metalsmithFile
- lastMember
metalsmithFile
- lastSibling
metalsmithFile
- memberIndex
number
- members
Array.<metalsmithFile>
- Files in same directory. - nextChild
metalsmithFile
- nextMember
metalsmithFile
- nextSibling
metalsmithFile
- parent
metalsmithFile
- path
string
- folder/file.ext - prevChild
metalsmithFile
- prevMember
metalsmithFile
- prevSibling
metalsmithFile
- root
metalsmithFile
- The top-most parent's parent's parent's parent. - self
metalsmithFile
- siblingIndex
number
- siblings
Array.<metalsmithFile>
- First member in adjacent directories.
Kind: inner typedef of module.exports
This uses Jasmine, Istanbul and ESLint for tests.
# Install all of the dependencies
npm install
# Run the tests
npm run test
This plugin is licensed under the MIT License with an additional non-advertising clause. See the full license text for information.