feat(core): Add {{dirname}} to summary and preview_path (#4279)

This commit is contained in:
Keegan Lillo 2020-09-28 23:20:07 +10:00 committed by GitHub
parent 8d00d17ad8
commit 576e4f0f1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 149 additions and 10 deletions

View File

@ -474,6 +474,7 @@ export class Backend {
label: loadedEntry.file.label,
author: loadedEntry.file.author,
updatedOn: loadedEntry.file.updatedOn,
meta: { path: prepareMetaPath(loadedEntry.file.path, collection) },
},
),
);

View File

@ -402,6 +402,38 @@ describe('formatters', () => {
).toBe('https://www.example.com/posts/title.md');
});
it('should compile the dirname template value to empty in a regular collection', () => {
expect(
previewUrlFormatter(
'https://www.example.com',
Map({
folder: '_portfolio',
preview_path: 'portfolio/{{dirname}}',
}),
'backendSlug',
slugConfig,
Map({ data: Map({}), path: '_portfolio/i-am-the-slug.md' }),
),
).toBe('https://www.example.com/portfolio/');
});
it('should compile dirname template value when in a nested collection', () => {
expect(
previewUrlFormatter(
'https://www.example.com',
Map({
folder: '_portfolio',
preview_path: 'portfolio/{{dirname}}',
nested: { depth: 100 },
meta: { path: { widget: 'string', label: 'Path', index_file: 'index' } },
}),
'backendSlug',
slugConfig,
Map({ data: Map({}), path: '_portfolio/drawing/i-am-the-slug/index.md' }),
),
).toBe('https://www.example.com/portfolio/drawing/i-am-the-slug');
});
it('should log error and ignore preview_path when date is missing', () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
expect(
@ -449,6 +481,46 @@ describe('formatters', () => {
summaryFormatter('{{title}}-{{year}}-{{filename}}.{{extension}}', entry, collection),
).toBe('title-2020-post.md');
});
it('should handle the dirname variable in a regular collection', () => {
const { selectInferedField } = require('../../reducers/collections');
selectInferedField.mockReturnValue('date');
const date = new Date('2020-01-02T13:28:27.679Z');
const entry = fromJS({
path: '_portfolio/drawing.md',
data: { date, title: 'title' },
});
const collection = fromJS({
folder: '_portfolio',
fields: [{ name: 'date', widget: 'date' }],
});
expect(summaryFormatter('{{dirname}}/{{title}}-{{year}}', entry, collection)).toBe(
'/title-2020',
);
});
it('should handle the dirname variable in a nested collection', () => {
const { selectInferedField } = require('../../reducers/collections');
selectInferedField.mockReturnValue('date');
const date = new Date('2020-01-02T13:28:27.679Z');
const entry = fromJS({
path: '_portfolio/drawing/index.md',
data: { date, title: 'title' },
});
const collection = fromJS({
folder: '_portfolio',
nested: { depth: 100 },
meta: { path: { widget: 'string', label: 'Path', index_file: 'index' } },
fields: [{ name: 'date', widget: 'date' }],
});
expect(summaryFormatter('{{dirname}}/{{title}}-{{year}}', entry, collection)).toBe(
'drawing/title-2020',
);
});
});
describe('folderFormatter', () => {
@ -519,5 +591,50 @@ describe('formatters', () => {
),
).toBe('md');
});
it('should compile dirname template value in a regular collection', () => {
const entry = fromJS({
path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
data: { category: 'Hosting And Deployment' },
});
const collection = fromJS({
folder: 'content/en/',
});
expect(
folderFormatter(
'{{dirname}}',
entry,
collection,
'static/images',
'media_folder',
slugConfig,
),
).toBe('hosting-and-deployment');
});
it('should compile dirname template value in a nested collection', () => {
const entry = fromJS({
path: '_portfolio/drawing/i-am-the-slug/index.md',
data: { category: 'Hosting And Deployment' },
});
const collection = fromJS({
folder: '_portfolio',
nested: { depth: 100 },
meta: { path: { widget: 'string', label: 'Path', index_file: 'index' } },
fields: [{ name: 'date', widget: 'date' }],
});
expect(
folderFormatter(
'{{dirname}}',
entry,
collection,
'static/images',
'media_folder',
slugConfig,
),
).toBe('drawing/i-am-the-slug');
});
});
});

View File

@ -103,8 +103,12 @@ export const prepareSlug = (slug: string) => {
);
};
export const getProcessSegment = (slugConfig: SlugConfig) =>
flow([value => String(value), prepareSlug, partialRight(sanitizeSlug, slugConfig)]);
export const getProcessSegment = (slugConfig: SlugConfig, ignoreValues: string[] = []) => {
return (value: string) =>
ignoreValues.includes(value)
? value
: flow([value => String(value), prepareSlug, partialRight(sanitizeSlug, slugConfig)])(value);
};
export const slugFormatter = (
collection: Collection,
@ -164,14 +168,14 @@ export const previewUrlFormatter = (
const basePath = trimEnd(baseUrl, '/');
const pathTemplate = collection.get('preview_path') as string;
let fields = entry.get('data') as Map<string, string>;
fields = addFileTemplateFields(entry.get('path'), fields);
fields = addFileTemplateFields(entry.get('path'), fields, collection.get('folder'));
const dateFieldName =
collection.get('preview_path_date_field') || selectInferedField(collection, 'date');
const date = parseDateFromEntry((entry as unknown) as Map<string, unknown>, dateFieldName);
// Prepare and sanitize slug variables only, leave the rest of the
// `preview_path` template as is.
const processSegment = getProcessSegment(slugConfig);
const processSegment = getProcessSegment(slugConfig, [fields.get('dirname')]);
let compiledPath;
try {
@ -207,7 +211,7 @@ export const summaryFormatter = (
) || null;
const identifier = entryData.getIn(keyToPathArray(selectIdentifier(collection) as string));
entryData = addFileTemplateFields(entry.get('path'), entryData);
entryData = addFileTemplateFields(entry.get('path'), entryData, collection.get('folder'));
// allow commit information in summary template
if (entry.get('author') && !selectField(collection, COMMIT_AUTHOR)) {
entryData = entryData.set(COMMIT_AUTHOR, entry.get('author'));
@ -232,7 +236,7 @@ export const folderFormatter = (
}
let fields = (entry.get('data') as Map<string, string>).set(folderKey, defaultFolder);
fields = addFileTemplateFields(entry.get('path'), fields);
fields = addFileTemplateFields(entry.get('path'), fields, collection.get('folder'));
const date =
parseDateFromEntry(
@ -240,14 +244,14 @@ export const folderFormatter = (
selectInferedField(collection, 'date'),
) || null;
const identifier = fields.getIn(keyToPathArray(selectIdentifier(collection) as string));
const processSegment = getProcessSegment(slugConfig);
const processSegment = getProcessSegment(slugConfig, [defaultFolder, fields.get('dirname')]);
const mediaFolder = compileStringTemplate(
folderTemplate,
date,
identifier,
fields,
(value: string) => (value === defaultFolder ? defaultFolder : processSegment(value)),
processSegment,
);
return mediaFolder;

View File

@ -1,6 +1,6 @@
import moment from 'moment';
import { Map } from 'immutable';
import { basename, extname } from 'path';
import { basename, extname, dirname } from 'path';
import { get, trimEnd } from 'lodash';
const FIELD_PREFIX = 'fields.';
@ -169,14 +169,28 @@ export function extractTemplateVars(template: string) {
});
}
export const addFileTemplateFields = (entryPath: string, fields: Map<string, string>) => {
/**
* Appends `dirname`, `filename` and `extension` to the provided `fields` map.
* @param entryPath
* @param fields
* @param folder - optionally include a folder that the dirname will be relative to.
* eg: `addFileTemplateFields('foo/bar/baz.ext', fields, 'foo')`
* will result in: `{ dirname: 'bar', filename: 'baz', extension: 'ext' }`
*/
export const addFileTemplateFields = (
entryPath: string,
fields: Map<string, string>,
folder = '',
) => {
if (!entryPath) {
return fields;
}
const extension = extname(entryPath);
const filename = basename(entryPath, extension);
const dirnameExcludingFolder = dirname(entryPath).replace(new RegExp(`^(/?)${folder}/?`), '$1');
fields = fields.withMutations(map => {
map.set('dirname', dirnameExcludingFolder);
map.set('filename', filename);
map.set('extension', extension === '' ? extension : extension.substr(1));
});

View File

@ -248,6 +248,7 @@ And for the image field being populated with a value of `image.png`.
Supports all of the [`slug` templates](/docs/configuration-options#slug) and:
- `{{dirname}}` The path to the file's parent directory, relative to the collection's `folder`.
- `{{filename}}` The file name without the extension part.
- `{{extension}}` The file extension.
- `{{media_folder}}` The global `media_folder`.

View File

@ -293,6 +293,7 @@ Template tags are the same as those for [slug](#slug), with the following except
* `{{slug}}` is the entire slug for the current entry (not just the url-safe identifier, as is the case with [`slug` configuration](#slug))
* The date based template tags, such as `{{year}}` and `{{month}}`, are pulled from a date field in your entry, and may require additional configuration - see [`preview_path_date_field`](#preview_path_date_field) for details. If a date template tag is used and no date can be found, `preview_path` will be ignored.
* `{{dirname}}` The path to the file's parent directory, relative to the collection's `folder`.
* `{{filename}}` The file name without the extension part.
* `{{extension}}` The file extension.
@ -373,6 +374,7 @@ This setting allows the customization of the collection list view. Similar to th
Template tags are the same as those for [slug](#slug), with the following additions:
* `{{dirname}}` The path to the file's parent directory, relative to the collection's `folder`.
* `{{filename}}` The file name without the extension part.
* `{{extension}}` The file extension.
* `{{commit_date}}` The file commit date on supported backends (git based backends).