feat(core): Add {{dirname}} to summary and preview_path (#4279)
This commit is contained in:
parent
8d00d17ad8
commit
576e4f0f1a
@ -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) },
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
});
|
||||
|
@ -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`.
|
||||
|
@ -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).
|
||||
|
Loading…
x
Reference in New Issue
Block a user