metalsmith-taxonomy
Version:
Metalsmith plugin that organizes files into taxonomy trees in global metadata and auto-generates taxonomy page objects
329 lines (262 loc) • 15 kB
Markdown
# Metalsmith taxonomy
Metalsmith plugin that organizes files into taxonomy trees in global metadata and auto-generates taxonomy page objects.
[![metalsmith: plugin][metalsmith-badge]][metalsmith-url]
[![npm: version][npm-badge]][npm-url]
[![ci: build][ci-badge]][ci-url]
[![code coverage][codecov-badge]][codecov-url]
[![license: LGPL-3.0][license-badge]][license-url]
## Features
- can pre-filter files by pattern
- adds `taxonomies` tree to global metadata
- supports multiple taxonomy namespaces (e.g. blog, products, etc.)
- works well in combination with metalsmith-filemetadata, collections and permalinks
- automatically generates overview, taxonomy & taxonomy term pages (e.g. taxonomies > categories > category)
## Install
NPM:
```bash
npm i -D metalsmith-taxonomy
```
Yarn:
```bash
yarn add metalsmith-taxonomy
```
## Usage
### Quickstart
Simplest usage without parameters or with default parameters (all examples below yield the same results).
```js
var default_taxonomy_set = {
pattern: '**/*.{md,html}',
namespace: null,
pages: ['index', 'taxonomy', 'term'],
taxonomies: ['tags', 'category']
};
metalsmith.use(taxonomy());
// is the same as
metalsmith.use(taxonomy(default_taxonomy_set));
// or the same as
metalsmith.use(taxonomy([default_taxonomy_set]);
```
### Options
Pass one or more taxonomy sets to the plugin.
A taxonomy set is an object with the following properties:
| Property | Type | Default | Description |
| :----------- | :-------------- | :-------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pattern` | `string\|array` | `**/*.{md,html}` | One or more [glob patterns][1] supported by [multimatch][2]. |
| `namespace` | `string` | `null` | Subkey in `metadata.taxonomies[namespace]` in which the taxonomy tree of this set will be stored. |
| `pages` | `boolean` | `true` | If `true`, a taxonomy set index page, pages for each taxonomy in the set, and each term of each taxonomy in the set will be generated. If `false`, no pages will be generated. |
| | `array` | | An array with one or more of: `index`, `taxonomy`, `term` allows limiting the type of pages generated. |
| `taxonomies` | `array` | `['category','tags']` | An array containing all file metadata keys to use as taxonomies |
| | `Object` | | Passing an object allows more flexibility in mapping taxonomy terms and values. The object's _keys_ will become the _taxonomy names_, and its _values_ will match metadata file keys. The file metadata key can also be a keypath (e.g. `meta.keywords`), and the file metadata value can also be an array. |
### Global metadata
If you had two files with the following content:
<table>
<tr>
<th><code>blog/article1.md</code></th>
<th><code>blog/article2.md</code></th>
</tr>
<tr>
<td><pre>---
title: Article 1
category: category1
tags:
- tag1
- tag2
---</pre></td>
<td><pre>---
title: Article 2
category: category2
tags:
- tag2
---</pre></td>
</tr>
</table>
The [Quickstart](#quickstart) example will generate the following results in global metadata:
```js
{
...metadata,
taxonomies: {
tags: {
tag1: [article1],
tag2: [article1, article2]
},
category: {
category1: [article1],
category2: [article2]
}
}
}
```
### Auto-generated index, taxonomy, and term pages
If the `pages` property of a taxonomy set is not an empty array, or `false`, metalsmith-taxonomy will auto-generate pages at the following paths:
| Page type | Path | Example |
| :--------- | :-------------------------------- | :---------------------------- |
| `index` | `:namespace.html` | blog.html |
| `taxonomy` | `:namespace/:taxonomy.html` | blog/category.html |
| `term` | `:namespace/:taxonomy/:term.html` | blog/category/metalsmith.html |
If `namespace` is not defined, the `index` page path will default to `index.html`.
With the [Quickstart](#quickstart) example, metalsmith-taxonomy will generate:
```js
{
'index.html',
'category.html',
'category/category1.html',
'category/category2.html',
'tags.html',
'tags/tag1.html',
'tags/tag2.html',
...other_files
};
```
Generated file objects get the following metadata:
#### Index page metadata
| Property | Type | Description |
| :----------- | :----------------- | :------------------------------------------------------------------------------------------------------ |
| `type` | `'taxonomy:index'` | Page type |
| `path` | `string` | Destination path of the page |
| `namespace` | `null\|string` | Namespace passed in taxonomy set |
| `taxonomies` | `object` | Copy of the object at `metadata.taxonomies[namespace]` (or `metadata.taxonomies` if `namespace===null`) |
| `contents` | `string` | Empty string |
#### Taxonomy page metadata
| Property | Type | Description |
| :----------- | :-------------------- | :------------------------------------------------------------------------------------------------------ |
| `type` | `'taxonomy:taxonomy'` | Page type |
| `path` | `string` | Destination path of the page |
| `namespace` | `null\|string` | Namespace passed in taxonomy set |
| `taxonomy` | `string` | Name of the current taxonomy |
| `terms` | `array` | Array with the terms found for the current taxonomy |
| `taxonomies` | `object` | Copy of the object at `metadata.taxonomies[namespace]` (or `metadata.taxonomies` if `namespace===null`) |
| `contents` | `string` | Empty string |
#### Term page metadata
| Property | Type | Description |
| :----------- | :---------------- | :------------------------------------------------------------------------------------------------------ |
| `type` | `'taxonomy:term'` | Page type |
| `path` | `string` | Destination path of the page |
| `namespace` | `null\|string` | Namespace passed in taxonomy set |
| `taxonomy` | `string` | Name of the current taxonomy |
| `terms` | `array` | Array with the terms found for the current taxonomy |
| `taxonomies` | `object` | Copy of the object at `metadata.taxonomies[namespace]` (or `metadata.taxonomies` if `namespace===null`) |
| `contents` | `string` | Empty string |
#### Adding extra metadata to the generated pages
If a file already exists at the target path of a generated page, the generated metadata will be merged into the existing metadata of that file. Extra metadata can also be added with plugins like [metalsmith-filemetadata][5] further in the plugin chain.
### Sorting the term matches
By default, the files will be sorted as they are read from the filesystem (alphabetically by file name).
The data available to metadata is a _reference_ to the items under `taxonomies[namespace][taxonomy][term]`,
so you could use [metalsmith-keymaster](https://github.com/MorganConrad/metalsmith-keymaster) or a custom plugin to sort the data:
```js
metalsmith.use(taxonomy).use(function (files, metalsmith) {
var taxonomies = metalsmith.metadata().taxonomies;
Object.keys(taxonomies.tags).forEach(function (tagName) {
taxonomies.tags[tagName].sort(function (a, b) {
return a.order < b.order ? -1 : a.order > b.order ? 1 : 0;
});
});
});
```
The example above shows how to sort all term collections under the `tag` taxonomy by an `order` property defined in each file's metadata.
### Custom metadata & rendering with metalsmith-layouts/filemetadata/default-values
You can use [metalsmith-filemetadata][5] to add custom data to the generated file objects by pattern,
e.g. to specify a `layout` property to be used later in the chain by [metalsmith-layouts][6].
```js
var taxonomy = require('metalsmith-taxonomy')({
namespace: 'taxonomies',
taxonomies: ['category', 'tags']
});
var filemetadata = require('metalsmith-filemetadata')([
{
pattern: 'taxonomies/**/*.html',
metadata: { layout: 'taxonomy-term.hbs' }
},
{
pattern: 'taxonomies/*.html',
metadata: { layout: 'taxonomy.hbs' }
},
{
pattern: 'taxonomies.html',
metadata: { layout: 'taxonomy-index.hbs' }
}
]);
var layouts = require('metalsmith-layouts')({
directory: 'src/layouts',
default: 'default.hbs',
pattern: '**/*.{md,html}'
});
metalsmith.use(taxonomy).use(filemetadata).use(layouts);
```
[metalsmith-default-values](https://github.com/metalsmith/metalsmith-default-values) works exactly the same as above.
### Custom or nested page paths with metalsmith-permalinks
You can use [metalsmith-permalinks][4] to move or nest taxonomy pages:
```js
var taxonomy = require('metalsmith-taxonomy')({
pages: ['index', 'taxonomy', 'term'],
taxonomies: ['category', 'tags']
});
var collections = require('metalsmith-collections')({
posts: 'posts/**/*.md'
});
var permalinks = {
linksets: [
{
match: { collection: 'posts' },
pattern: 'posts/:category/:title'
},
{
match: { type: 'taxonomy:index' },
pattern: 'posts'
},
{
match: { type: 'taxonomy:term', taxonomy: 'category', namespace: 'blog' },
pattern: 'posts/:category'
},
{
match: { type: 'taxonomy:taxonomy' },
pattern: 'posts/:taxonomy'
},
{
match: { type: 'taxonomy:term', namespace: 'blog' },
pattern: 'posts/:category'
}
]
};
metalsmith.use(taxonomy).use(collections).use(permalinks);
```
with the example files from [Global metadata](#global-metadata) would result in a directory tree like:
```
└── posts
├── index.html --> index page
├── tags
| ├── index.html --> taxonomy page
| ├── tag1
| | └── index.html --> term page
| └── tag2
| └── index.html --> term page
├── category
| └── index.html --> taxonomy page
├── category1
| ├── index.html --> term page
| └── article-1
| └── index.html
└── category2
├── index.html --> term page
└── article-2
└── index.html
```
## Examples
Clone this repository and navigate to the [`example/licenses`](example/open-source-licenses) or more advanced [`example/blog`](example/blog) directory, run npm install & npm start.
## License
[LGPL v0.3](./LICENSE)
[1]: https://en.wikipedia.org/wiki/Glob_%28programming%29 'glob patterns on Wikipedia'
[2]: https://github.com/sindresorhus/multimatch 'multimatch on Github'
[3]: https://github.com/segmentio/metalsmith-collections 'metalsmith-collections on Github'
[4]: https://github.com/segmentio/metalsmith-permalinks 'metalsmith-permalinks on Github'
[5]: https://github.com/dpobel/metalsmith-filemetadata 'metalsmith-filemetadata on Github'
[6]: https://github.com/metalsmith/metalsmith-layouts 'metalsmith-layouts on Github'
[npm-badge]: https://img.shields.io/npm/v/metalsmith-taxonomy
[npm-url]: https://www.npmjs.com/package/metalsmith-taxonomy
[ci-badge]: https://github.com/metalsmith/metalsmith/actions/workflows/test.yml/badge.svg
[ci-url]: https://github.com/metalsmith/metalsmith/actions/workflows/test.yml
[license-badge]: https://img.shields.io/github/license/webketje/metalsmith-taxonomy
[license-url]: https://choosealicense.com/licenses/lgpl-3.0/
[codecov-badge]: https://img.shields.io/coveralls/github/webketje/metalsmith-taxonomy
[codecov-url]: https://coveralls.io/github/webketje/metalsmith-taxonomy
[metalsmith-badge]: https://img.shields.io/badge/metalsmith-plugin-green.svg?longCache=true
[metalsmith-url]: https://metalsmith.io/