fix: slug template variable (#795)

This commit is contained in:
Daniel Lautzenheiser 2023-05-10 12:26:39 -04:00 committed by GitHub
parent d28c43e95a
commit 8ee9a464ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 884 additions and 32 deletions

View File

@ -71,6 +71,7 @@ collections:
label: FAQ
folder: _faqs
create: true
media_folder: '/_faqs_images/{{slug}}'
fields:
- label: Question
name: title
@ -78,6 +79,9 @@ collections:
- label: Answer
name: body
widget: markdown
- label: Image
name: image
widget: image
- name: posts
label: Posts
label_singular: Post
@ -475,3 +479,26 @@ collections:
- label: File
name: file
widget: file
- name: pages
label: Nested Pages
label_singular: 'Page'
folder: _nested_pages
create: true
media_folder: '/_nested_page_pictures/{{slug}}'
# adding a nested object will show the collection folder structure
nested:
depth: 100 # max depth to show in the collection tree
summary: '{{title}}' # optional summary for a tree node, defaults to the inferred title field
# adding a path object allows editing the path of entries
# moving an existing entry will move the entire sub tree of the entry to the new location
path: { label: 'Path', index_file: 'index' }
fields:
- label: Title
name: title
widget: string
- label: Image
name: image
widget: image
- label: Body
name: body
widget: markdown

View File

@ -833,7 +833,7 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
const newEntry = entryDraft.entry.newRecord ?? false;
const customPath = selectCustomPath(draft.entry, collection, rootSlug, config);
const customPath = selectCustomPath(draft.entry, collection, rootSlug, config.slug);
let dataFile: DataFile;
if (newEntry) {

View File

@ -6,7 +6,7 @@ import type { Options } from '../API';
describe('gitea API', () => {
beforeEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
global.fetch = jest.fn().mockRejectedValue(new Error('should not call fetch inside tests'));
});
@ -29,7 +29,7 @@ describe('gitea API', () => {
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
it('should fetch url with authorization header', async () => {

View File

@ -6,7 +6,7 @@ import type { Options } from '../API';
describe('github API', () => {
beforeEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
global.fetch = jest.fn().mockRejectedValue(new Error('should not call fetch inside tests'));
});
@ -65,7 +65,7 @@ describe('github API', () => {
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
it('should fetch url with authorization header', async () => {

View File

@ -5,7 +5,7 @@ global.fetch = jest.fn().mockRejectedValue(new Error('should not call fetch insi
describe('GitLab API', () => {
beforeEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
afterAll(() => {

View File

@ -63,7 +63,7 @@ const EditorControlPane = ({
const config = useAppSelector(selectConfig);
const defaultNestedPath = useMemo(
() => getNestedSlug(collection, entry, slug, config),
() => getNestedSlug(collection, entry, slug, config?.slug),
[collection, config, entry, slug],
);

View File

@ -5,6 +5,7 @@ import { sanitizeSlug } from './urlHelper';
import { selectIdentifier, selectInferredField } from './util/collection.util';
import { selectField } from './util/field.util';
import { set } from './util/object.util';
import { isEmpty } from './util/string.util';
import {
addFileTemplateFields,
compileStringTemplate,
@ -94,16 +95,21 @@ export function slugFormatter<EF extends BaseField = UnknownField>(
collection: Collection<EF>,
entryData: EntryData,
slugConfig?: Slug,
) {
): string {
const slugTemplate = collection.slug || '{{slug}}';
const identifier = get(entryData, keyToPathArray(selectIdentifier(collection)));
if (!identifier) {
const identifierField = selectIdentifier(collection);
if (!identifierField) {
throw new Error(
'Collection must have a field name that is a valid entry identifier, or must have `identifier_field` set',
);
}
const identifier = get(entryData, keyToPathArray(identifierField));
if (isEmpty(identifier)) {
return '';
}
const processSegment = getProcessSegment(slugConfig);
const date = new Date();
const slug = compileStringTemplate(slugTemplate, date, identifier, entryData, processSegment);
@ -122,10 +128,12 @@ export function summaryFormatter<EF extends BaseField>(
summaryTemplate: string,
entry: Entry,
collection: Collection<EF>,
slugConfig?: Slug,
) {
const slug = slugFormatter(collection, entry.data, slugConfig);
let entryData = entry.data;
const date = parseDateFromEntry(entry, selectInferredField(collection, 'date')) || null;
const identifier = get(entryData, keyToPathArray(selectIdentifier(collection)));
entryData =
addFileTemplateFields(entry.path, entryData, 'folder' in collection ? collection.folder : '') ??
@ -137,7 +145,7 @@ export function summaryFormatter<EF extends BaseField>(
if (entry.updatedOn && !selectField(collection, COMMIT_DATE)) {
entryData = set(entryData, COMMIT_DATE, entry.updatedOn);
}
const summary = compileStringTemplate(summaryTemplate, date, identifier, entryData);
const summary = compileStringTemplate(summaryTemplate, date, slug, entryData);
return summary;
}
@ -160,8 +168,10 @@ export function folderFormatter<EF extends BaseField>(
'folder' in collection ? collection.folder : '',
);
const date = parseDateFromEntry(entry, selectInferredField(collection, 'date')) || null;
const slug = slugFormatter(collection, entry.data, slugConfig);
const date = parseDateFromEntry(entry, selectInferredField(collection, 'date')) || null;
const processSegment = getProcessSegment(slugConfig, [defaultFolder, fields?.dirname as string]);
const mediaFolder = compileStringTemplate(folderTemplate, date, slug, fields, processSegment);

View File

@ -1,11 +1,12 @@
import url from 'url';
import urlJoin from 'url-join';
import diacritics from 'diacritics';
import sanitizeFilename from 'sanitize-filename';
import isString from 'lodash/isString';
import escapeRegExp from 'lodash/escapeRegExp';
import flow from 'lodash/flow';
import isString from 'lodash/isString';
import partialRight from 'lodash/partialRight';
import trim from 'lodash/trim';
import sanitizeFilename from 'sanitize-filename';
import url from 'url';
import urlJoin from 'url-join';
import type { Slug } from '../interface';
@ -124,5 +125,5 @@ export function sanitizeSlug(str: string, options?: Slug) {
}
export function joinUrlPath(base: string, ...path: string[]) {
return urlJoin(base, ...path);
return urlJoin(trim(base, '/'), ...path.map(p => trim(p, '/')));
}

View File

@ -2,7 +2,7 @@ import { createMockCollection } from '@staticcms/test/data/collections.mock';
import { createMockConfig } from '@staticcms/test/data/config.mock';
import { createMockEntry } from '@staticcms/test/data/entry.mock';
import { mockImageField as mockBaseImageField } from '@staticcms/test/data/fields.mock';
import { selectMediaFolder } from '../media.util';
import { selectMediaFilePublicPath, selectMediaFolder } from '../media.util';
import type { FileOrImageField, FolderCollection, UnknownField } from '@staticcms/core/interface';
@ -145,6 +145,80 @@ describe('media.util', () => {
'path/to/entry/path/to/some/other/media/i-am-a-title-fish',
);
});
it('should substitute slug from top level media_folder (always considered an absolute path)', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
slug: '{{fields.title}}-{{fields.name}}',
fields: [
{
name: 'title',
widget: 'string',
},
{
name: 'name',
widget: 'string',
},
mockBaseImageField,
],
}),
],
media_folder: 'path/to/media/folder/{{slug}}',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
const mockEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: { title: 'i am a title', name: 'fish' },
});
expect(selectMediaFolder(mockConfig, mockCollection, mockEntry, mockImageField)).toBe(
'path/to/media/folder/i-am-a-title-fish',
);
});
it('should not throw error when evaluating slug for new entry', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
media_folder: 'path/to/some/other/media/{{slug}}',
slug: '{{fields.title}}-{{fields.name}}',
fields: [
{
name: 'title',
widget: 'string',
},
{
name: 'name',
widget: 'string',
},
mockBaseImageField,
],
}),
],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
const mockEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: {},
newRecord: true,
});
expect(selectMediaFolder(mockConfig, mockCollection, mockEntry, mockImageField)).toBe(
'path/to/entry/path/to/some/other/media',
);
});
});
});
@ -238,6 +312,730 @@ describe('media.util', () => {
'path/to/some/other/media/i-am-a-title-fish',
);
});
it('should substitute slug from top level media_folder', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
slug: '{{fields.title}}-{{fields.name}}',
fields: [
{
name: 'title',
widget: 'string',
},
{
name: 'name',
widget: 'string',
},
mockBaseImageField,
],
}),
],
media_folder: '/path/to/media/folder/{{slug}}',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
const mockEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: { title: 'i am a title', name: 'fish' },
});
expect(selectMediaFolder(mockConfig, mockCollection, mockEntry, mockImageField)).toBe(
'path/to/media/folder/i-am-a-title-fish',
);
});
it('should not throw error when evaluating slug for new entry', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
media_folder: '/path/to/some/other/media/{{slug}}',
slug: '{{fields.title}}-{{fields.name}}',
fields: [
{
name: 'title',
widget: 'string',
},
{
name: 'name',
widget: 'string',
},
mockBaseImageField,
],
}),
],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
const mockEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: {},
newRecord: true,
});
expect(selectMediaFolder(mockConfig, mockCollection, mockEntry, mockImageField)).toBe(
'path/to/some/other/media',
);
});
});
});
});
});
describe('selectMediaFilePublicPath', () => {
const mockBaseCollection = createMockCollection<UnknownField>({
fields: [
{
name: 'title',
widget: 'string',
},
],
});
const mockBaseEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: {
title: 'I am a title',
},
});
describe('top level', () => {
it('should default to top level config public_folder', () => {
const mockConfig = createMockConfig({
collections: [mockBaseCollection],
media_folder: 'path/to/media/folder',
public_folder: 'path/to/public/folder',
});
expect(
selectMediaFilePublicPath(
mockConfig,
undefined,
'image.png',
undefined,
undefined,
undefined,
),
).toBe('path/to/public/folder/image.png');
});
it('should use media_folder as an absolute path if public_folder is not provided', () => {
const mockConfig = createMockConfig({
collections: [mockBaseCollection],
media_folder: 'path/to/media/folder',
});
expect(
selectMediaFilePublicPath(
mockConfig,
undefined,
'image.png',
undefined,
undefined,
undefined,
),
).toBe('/path/to/media/folder/image.png');
});
});
describe('entry', () => {
it('should default to top level config public_folder', () => {
const mockConfig = createMockConfig({
collections: [mockBaseCollection],
media_folder: 'path/to/media/folder',
public_folder: 'path/to/public/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockBaseEntry,
mockImageField,
),
).toBe('path/to/public/folder/image.png');
});
it('should default to top level config media_folder as an absolute path', () => {
const mockConfig = createMockConfig({
collections: [mockBaseCollection],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockBaseEntry,
mockImageField,
),
).toBe('/path/to/media/folder/image.png');
});
describe('relative path', () => {
it('should use collection public_folder if available', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
media_folder: 'path/to/collection/media/folder',
public_folder: 'path/to/collection/public/folder',
fields: [
{
name: 'title',
widget: 'string',
},
],
}),
],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockBaseEntry,
mockImageField,
),
).toBe('path/to/collection/public/folder/image.png');
});
it('should use collection media_folder if no public_folder is available', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
media_folder: 'path/to/collection/media/folder',
fields: [
{
name: 'title',
widget: 'string',
},
],
}),
],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockBaseEntry,
mockImageField,
),
).toBe('path/to/collection/media/folder/image.png');
});
describe('template variable', () => {
it('should substitute field value', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
media_folder: 'path/to/some/other/media/{{fields.title}}',
fields: [
{
name: 'title',
widget: 'string',
},
],
}),
],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockBaseEntry,
mockImageField,
),
).toBe('path/to/some/other/media/i-am-a-title/image.png');
});
it('should substitute slug', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
media_folder: 'path/to/some/other/media/{{slug}}',
slug: '{{fields.title}}-{{fields.name}}',
fields: [
{
name: 'title',
widget: 'string',
},
{
name: 'name',
widget: 'string',
},
mockBaseImageField,
],
}),
],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
const mockEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: { title: 'i am a title', name: 'fish' },
});
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockEntry,
mockImageField,
),
).toBe('path/to/some/other/media/i-am-a-title-fish/image.png');
});
it('should substitute slug from top level media_folder if no public_folders', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
slug: '{{fields.title}}-{{fields.name}}',
fields: [
{
name: 'title',
widget: 'string',
},
{
name: 'name',
widget: 'string',
},
mockBaseImageField,
],
}),
],
media_folder: 'path/to/media/folder/{{slug}}',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
const mockEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: { title: 'i am a title', name: 'fish' },
});
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockEntry,
mockImageField,
),
).toBe('/path/to/media/folder/i-am-a-title-fish/image.png');
});
it('should substitute slug from top level public_folder', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
slug: '{{fields.title}}-{{fields.name}}',
fields: [
{
name: 'title',
widget: 'string',
},
{
name: 'name',
widget: 'string',
},
mockBaseImageField,
],
}),
],
media_folder: 'path/to/media/folder/{{slug}}',
public_folder: 'path/to/public/folder/{{slug}}',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
const mockEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: { title: 'i am a title', name: 'fish' },
});
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockEntry,
mockImageField,
),
).toBe('path/to/public/folder/i-am-a-title-fish/image.png');
});
it('should not throw error when evaluating slug for new entry', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
media_folder: 'path/to/collection/media/folder/{{slug}}',
public_folder: 'path/to/collection/public/folder/{{slug}}',
slug: '{{fields.title}}-{{fields.name}}',
fields: [
{
name: 'title',
widget: 'string',
},
{
name: 'name',
widget: 'string',
},
mockBaseImageField,
],
}),
],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
const mockEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: {},
newRecord: true,
});
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockEntry,
mockImageField,
),
).toBe('path/to/collection/public/folder/image.png');
});
});
});
describe('absolute path', () => {
it('should use collection public_folder if available', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
media_folder: 'path/to/collection/media/folder',
public_folder: '/path/to/collection/public/folder',
fields: [
{
name: 'title',
widget: 'string',
},
],
}),
],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockBaseEntry,
mockImageField,
),
).toBe('/path/to/collection/public/folder/image.png');
});
it('should use collection media_folder if no public_folder is available', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
media_folder: '/path/to/collection/media/folder',
fields: [
{
name: 'title',
widget: 'string',
},
],
}),
],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockBaseEntry,
mockImageField,
),
).toBe('/path/to/collection/media/folder/image.png');
});
describe('template variable', () => {
it('should substitute field value', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
media_folder: '/path/to/some/other/media/{{fields.title}}',
fields: [
{
name: 'title',
widget: 'string',
},
],
}),
],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockBaseEntry,
mockImageField,
),
).toBe('/path/to/some/other/media/i-am-a-title/image.png');
});
it('should substitute slug', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
media_folder: '/path/to/some/other/media/{{slug}}',
slug: '{{fields.title}}-{{fields.name}}',
fields: [
{
name: 'title',
widget: 'string',
},
{
name: 'name',
widget: 'string',
},
mockBaseImageField,
],
}),
],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
const mockEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: { title: 'i am a title', name: 'fish' },
});
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockEntry,
mockImageField,
),
).toBe('/path/to/some/other/media/i-am-a-title-fish/image.png');
});
it('should substitute slug from top level media_folder if no public_folders', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
slug: '{{fields.title}}-{{fields.name}}',
fields: [
{
name: 'title',
widget: 'string',
},
{
name: 'name',
widget: 'string',
},
mockBaseImageField,
],
}),
],
media_folder: '/path/to/media/folder/{{slug}}',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
const mockEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: { title: 'i am a title', name: 'fish' },
});
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockEntry,
mockImageField,
),
).toBe('/path/to/media/folder/i-am-a-title-fish/image.png');
});
it('should substitute slug from top level public_folder', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
slug: '{{fields.title}}-{{fields.name}}',
fields: [
{
name: 'title',
widget: 'string',
},
{
name: 'name',
widget: 'string',
},
mockBaseImageField,
],
}),
],
media_folder: '/path/to/media/folder/{{slug}}',
public_folder: '/path/to/public/folder/{{slug}}',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
const mockEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: { title: 'i am a title', name: 'fish' },
});
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockEntry,
mockImageField,
),
).toBe('/path/to/public/folder/i-am-a-title-fish/image.png');
});
it('should not throw error when evaluating slug for new entry', () => {
const mockConfig = createMockConfig({
collections: [
createMockCollection<UnknownField>({
folder: 'base/folder',
media_folder: '/path/to/collection/media/folder/{{slug}}',
public_folder: '/path/to/collection/public/folder/{{slug}}',
slug: '{{fields.title}}-{{fields.name}}',
fields: [
{
name: 'title',
widget: 'string',
},
{
name: 'name',
widget: 'string',
},
mockBaseImageField,
],
}),
],
media_folder: 'path/to/media/folder',
});
const mockCollection = mockConfig.collections[0];
const mockImageField = (mockConfig.collections[0] as FolderCollection)
.fields[3] as FileOrImageField;
const mockEntry = createMockEntry({
path: 'path/to/entry/index.md',
data: {},
newRecord: true,
});
expect(
selectMediaFilePublicPath(
mockConfig,
mockCollection,
'image.png',
mockEntry,
mockImageField,
),
).toBe('/path/to/collection/public/folder/image.png');
});
});
});
});

View File

@ -236,7 +236,14 @@ export function selectMediaFolder<EF extends BaseField>(
field: MediaField | undefined,
currentFolder?: string,
) {
let mediaFolder = config['media_folder'] ?? '';
let mediaFolder = folderFormatter(
config.media_folder ?? '',
entryMap,
collection as Collection,
config.media_folder ?? '',
'media_folder',
config.slug,
);
if (currentFolder) {
mediaFolder = currentFolder;
@ -270,7 +277,15 @@ export function selectMediaFilePublicPath<EF extends BaseField>(
return mediaPath;
}
let publicFolder = config['public_folder']!;
let publicFolder = folderFormatter(
config.public_folder ?? '',
entryMap,
collection as Collection,
config.public_folder ?? '',
'public_folder',
config.slug,
);
let selectedPublicFolder = publicFolder;
const customPublicFolder = hasCustomFolder('public_folder', collection, entryMap?.slug, field);
@ -288,11 +303,12 @@ export function selectMediaFilePublicPath<EF extends BaseField>(
selectedPublicFolder = trim(currentFolder, '/').replace(trim(mediaFolder!, '/'), publicFolder);
}
if (isAbsolutePath(selectedPublicFolder)) {
return joinUrlPath(selectedPublicFolder, basename(mediaPath));
const finalPublicPath = joinUrlPath(selectedPublicFolder, basename(mediaPath));
if (selectedPublicFolder.startsWith('/')) {
return `/${finalPublicPath}`;
}
return joinUrlPath(selectedPublicFolder, basename(mediaPath));
return finalPublicPath;
}
export function selectMediaFilePath(

View File

@ -2,11 +2,11 @@ import trim from 'lodash/trim';
import { basename, dirname, extname, join } from 'path';
import { sanitizeSlug } from '../urlHelper';
import { stringTemplate } from '../widgets';
import { selectEntryCollectionTitle, selectFolderEntryExtension } from './collection.util';
import { isEmpty, isNotEmpty } from './string.util';
import { stringTemplate } from '../widgets';
import type { BaseField, Collection, Config, Entry } from '@staticcms/core/interface';
import type { BaseField, Collection, Entry, Slug } from '@staticcms/core/interface';
const { addFileTemplateFields } = stringTemplate;
@ -28,7 +28,7 @@ export function selectCustomPath(
entry: Entry,
collection: Collection,
rootSlug: string | undefined,
config: Config | undefined,
slugConfig: Slug | undefined,
): string | undefined {
if (!('nested' in collection) || !collection.nested?.path) {
return undefined;
@ -37,7 +37,7 @@ export function selectCustomPath(
const indexFile = collection.nested.path.index_file;
const extension = selectFolderEntryExtension(collection);
const slug = entry.meta?.path ?? getNestedSlug(collection, entry, rootSlug, config);
const slug = entry.meta?.path ?? getNestedSlug(collection, entry, rootSlug, slugConfig);
const customPath = join(collection.folder, slug, `${indexFile}.${extension}`);
return customPath;
@ -72,7 +72,7 @@ export function getNestedSlug(
collection: Collection,
entry: Entry,
slug: string | undefined,
config: Config | undefined,
slugConfig: Slug | undefined,
) {
if ('nested' in collection && collection.nested?.path) {
if (isNotEmpty(entry.slug)) {
@ -85,7 +85,7 @@ export function getNestedSlug(
return `${customPathFromSlug(collection, slug)}/${sanitizeSlug(
summarySlug.toLowerCase(),
config?.slug,
slugConfig,
)}`;
}
}

View File

@ -223,7 +223,7 @@ describe(RelationControl.name, () => {
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
interface RenderRelationControlOptions {
@ -653,7 +653,7 @@ describe(RelationControl.name, () => {
});
describe('parse options', () => {
fit('should default to valueField if displayFields is not set', async () => {
it('should default to valueField if displayFields is not set', async () => {
const field: RelationField = {
label: 'Relation',
name: 'relation',