-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathREADME.pre
318 lines (228 loc) · 13 KB
/
README.pre
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
metalsmith-ancestry
===================
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.
[![npm version][npm-badge]][npm-link]
[![Build Status][travis-badge]][travis-link]
[![Dependencies][dependencies-badge]][dependencies-link]
[![Dev Dependencies][devdependencies-badge]][devdependencies-link]
[![codecov.io][codecov-badge]][codecov-link]
What It Does
------------
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?}}
Installation
------------
`npm` can do this for you.
npm install --save metalsmith-ancestry
Usage
-----
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.
### Sorting Members
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 on `ancestry.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 matches `index.htm`, `index.html`, `index.jade` and `index.md`.
* `"**/index.html"` - Strings match against the file using `metalsmith-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 with `index.` 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.
### Sorting Functions
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" ];
Upgrading
---------
### 1.2.0 -> 1.3.0
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`.
### 1.1.0 -> 1.2.0
Backwards compatible. Only added the `.root` property.
### 1.0.0 -> 1.1.0
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`.
API
---