From b63f5eec353156fe4b3f6d1b8ec6f55cd01af85f Mon Sep 17 00:00:00 2001 From: Daniel Lautzenheiser Date: Mon, 11 Dec 2023 10:37:03 -0500 Subject: [PATCH] fix: media public path conversion (#1003) --- packages/core/src/backend.ts | 3 +- packages/core/src/constants/mediaLibrary.ts | 2 + packages/core/src/lib/formatters.ts | 4 +- .../src/lib/util/__tests__/media.util.spec.ts | 868 +++++++++++++++++- packages/core/src/lib/util/entry.util.ts | 30 +- packages/core/src/lib/util/media.util.ts | 53 +- 6 files changed, 920 insertions(+), 40 deletions(-) diff --git a/packages/core/src/backend.ts b/packages/core/src/backend.ts index 4e77a5dc..663a5961 100644 --- a/packages/core/src/backend.ts +++ b/packages/core/src/backend.ts @@ -6,6 +6,7 @@ import isError from 'lodash/isError'; import uniq from 'lodash/uniq'; import { dirname } from 'path'; +import { DRAFT_MEDIA_FILES } from './constants/mediaLibrary'; import { resolveFormat } from './formats/formats'; import { commitMessageFormatter, slugFormatter } from './lib/formatters'; import { @@ -44,7 +45,7 @@ import { selectMediaFolders, } from './lib/util/collection.util'; import filterEntries from './lib/util/filter.util'; -import { DRAFT_MEDIA_FILES, selectMediaFilePublicPath } from './lib/util/media.util'; +import { selectMediaFilePublicPath } from './lib/util/media.util'; import { selectCustomPath, slugFromCustomPath } from './lib/util/nested.util'; import { isNullish } from './lib/util/null.util'; import { fileSearch, sortByScore } from './lib/util/search.util'; diff --git a/packages/core/src/constants/mediaLibrary.ts b/packages/core/src/constants/mediaLibrary.ts index 9d128de2..fa85995a 100644 --- a/packages/core/src/constants/mediaLibrary.ts +++ b/packages/core/src/constants/mediaLibrary.ts @@ -6,3 +6,5 @@ export const MEDIA_CARD_MARGIN = 10; export const MEDIA_LIBRARY_PADDING = 20; export const MAX_LINK_DISPLAY_LENGTH = 28; + +export const DRAFT_MEDIA_FILES = 'DRAFT_MEDIA_FILES'; diff --git a/packages/core/src/lib/formatters.ts b/packages/core/src/lib/formatters.ts index 00fb2726..34a345e8 100644 --- a/packages/core/src/lib/formatters.ts +++ b/packages/core/src/lib/formatters.ts @@ -149,8 +149,8 @@ export function summaryFormatter( if (entry.updatedOn && !selectField(collection, COMMIT_DATE)) { entryData = set(entryData, COMMIT_DATE, entry.updatedOn); } - const summary = compileStringTemplate(summaryTemplate, date, slug, entryData); - return summary; + + return compileStringTemplate(summaryTemplate, date, slug, entryData); } export function folderFormatter( diff --git a/packages/core/src/lib/util/__tests__/media.util.spec.ts b/packages/core/src/lib/util/__tests__/media.util.spec.ts index f49e9f60..138ba38d 100644 --- a/packages/core/src/lib/util/__tests__/media.util.spec.ts +++ b/packages/core/src/lib/util/__tests__/media.util.spec.ts @@ -473,7 +473,6 @@ describe('media.util', () => { 'path/to/media/folder/image.png', undefined, undefined, - undefined, ), ).toBe('path/to/public/folder/image.png'); }); @@ -491,7 +490,6 @@ describe('media.util', () => { 'path/to/media/folder/image.png', undefined, undefined, - undefined, ), ).toBe('/path/to/media/folder/image.png'); }); @@ -607,6 +605,80 @@ describe('media.util', () => { ).toBe('path/to/collection/media/folder/image.png'); }); + it('should handle folder collections path', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + folder: 'base/folder', + media_folder: '', + public_folder: '', + path: '{{slug}}/index', + 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, + 'base/folder/i-am-a-title/image.png', + { + ...mockBaseEntry, + path: 'base/folder/i-am-a-title/index.md', + }, + mockImageField, + ), + ).toBe('image.png'); + }); + + it('should handle folder collections path for new entry', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + folder: 'base/folder', + media_folder: '', + public_folder: '', + path: '{{slug}}/index', + 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, + 'base/folder/DRAFT_MEDIA_FILES/image.png', + { + ...mockBaseEntry, + path: '', + }, + mockImageField, + ), + ).toBe('image.png'); + }); + describe('template variable', () => { it('should substitute field value', () => { const mockConfig = createMockConfig({ @@ -720,7 +792,7 @@ describe('media.util', () => { selectMediaFilePublicPath( mockConfig, mockCollection, - '/path/to/media/folder/i-am-a-title-fish/image.png', + 'path/to/media/folder/i-am-a-title-fish/image.png', mockEntry, mockImageField, ), @@ -1097,5 +1169,795 @@ describe('media.util', () => { }); }); }); + + describe('with folder support', () => { + const mockBaseCollection = createMockCollection({ + 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, + 'path/to/media/folder/current/folder/image.png', + undefined, + undefined, + 'path/to/media/folder/current/folder', + ), + ).toBe('path/to/public/folder/current/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, + 'path/to/media/folder/current/folder/image.png', + undefined, + undefined, + 'path/to/media/folder/current/folder', + ), + ).toBe('/path/to/media/folder/current/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, + 'path/to/media/folder/current/folder/image.png', + mockBaseEntry, + mockImageField, + 'path/to/media/folder/current/folder', + ), + ).toBe('path/to/public/folder/current/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, + 'path/to/media/folder/current/folder/image.png', + mockBaseEntry, + mockImageField, + 'path/to/media/folder/current/folder', + ), + ).toBe('/path/to/media/folder/current/folder/image.png'); + }); + + describe('relative path', () => { + it('should handle folder collections path', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + folder: 'base/folder', + media_folder: '', + public_folder: '', + path: '{{slug}}/index', + 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, + 'path/to/folder/current/folder/image.png', + { + ...mockBaseEntry, + path: 'path/to/folder/index.md', + }, + mockImageField, + 'path/to/folder/current/folder', + ), + ).toBe('current/folder/image.png'); + }); + + it('should handle folder collections path for new entry', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + folder: 'base/folder', + media_folder: '', + public_folder: '', + path: '{{slug}}/index', + 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, + 'base/folder/DRAFT_MEDIA_FILES/current/folder/image.png', + { + ...mockBaseEntry, + path: '', + }, + mockImageField, + 'base/folder/DRAFT_MEDIA_FILES/current/folder', + ), + ).toBe('current/folder/image.png'); + }); + + it('should use collection public_folder if available', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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, + 'path/to/collection/media/folder/current/folder/image.png', + mockBaseEntry, + mockImageField, + 'path/to/collection/media/folder/current/folder', + ), + ).toBe('path/to/collection/public/folder/current/folder/image.png'); + }); + + it('should use collection media_folder if no public_folder is available', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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, + 'path/to/collection/media/folder/current/folder/image.png', + mockBaseEntry, + mockImageField, + 'path/to/collection/media/folder/current/folder', + ), + ).toBe('path/to/collection/media/folder/current/folder/image.png'); + }); + + it('should handle folder collections path', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + folder: 'base/folder', + media_folder: '', + public_folder: '', + path: '{{slug}}/index', + 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, + 'base/folder/i-am-a-title/current/folder/image.png', + { + ...mockBaseEntry, + path: 'base/folder/i-am-a-title/index.md', + }, + mockImageField, + 'base/folder/i-am-a-title/current/folder', + ), + ).toBe('current/folder/image.png'); + }); + + describe('template variable', () => { + it('should substitute field value', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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, + 'path/to/some/other/media/i-am-a-title/current/folder/image.png', + mockBaseEntry, + mockImageField, + 'path/to/some/other/media/i-am-a-title/current/folder', + ), + ).toBe('path/to/some/other/media/i-am-a-title/current/folder/image.png'); + }); + + it('should substitute slug', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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', + slug: 'i-am-a-title-fish', + data: { title: 'i am a title', name: 'fish' }, + }); + + expect( + selectMediaFilePublicPath( + mockConfig, + mockCollection, + 'path/to/some/other/media/i-am-a-title-fish/current/folder/image.png', + mockEntry, + mockImageField, + 'path/to/some/other/media/i-am-a-title-fish/current/folder', + ), + ).toBe('path/to/some/other/media/i-am-a-title-fish/current/folder/image.png'); + }); + + it('should substitute slug from top level media_folder if no public_folders', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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', + slug: 'i-am-a-title-fish', + data: { title: 'i am a title', name: 'fish' }, + }); + + expect( + selectMediaFilePublicPath( + mockConfig, + mockCollection, + 'path/to/media/folder/i-am-a-title-fish/current/folder/image.png', + mockEntry, + mockImageField, + 'path/to/media/folder/i-am-a-title-fish/current/folder', + ), + ).toBe('/path/to/media/folder/i-am-a-title-fish/current/folder/image.png'); + }); + + it('should substitute slug from top level public_folder', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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', + slug: 'i-am-a-title-fish', + data: { title: 'i am a title', name: 'fish' }, + }); + + expect( + selectMediaFilePublicPath( + mockConfig, + mockCollection, + 'path/to/media/folder/i-am-a-title-fish/current/folder/image.png', + mockEntry, + mockImageField, + 'path/to/media/folder/i-am-a-title-fish/current/folder', + ), + ).toBe('path/to/public/folder/i-am-a-title-fish/current/folder/image.png'); + }); + + it('should not throw error when evaluating slug for new entry', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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', + slug: '', + data: {}, + newRecord: true, + }); + + expect( + selectMediaFilePublicPath( + mockConfig, + mockCollection, + 'path/to/collection/media/folder/current/folder/image.png', + mockEntry, + mockImageField, + 'path/to/collection/media/folder/current/folder', + ), + ).toBe('path/to/collection/public/folder/current/folder/image.png'); + }); + }); + }); + + describe('absolute path', () => { + it('should use collection public_folder if available', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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, + 'path/to/collection/media/folder/current/folder/image.png', + mockBaseEntry, + mockImageField, + 'path/to/collection/media/folder/current/folder', + ), + ).toBe('/path/to/collection/public/folder/current/folder/image.png'); + }); + + it('should use collection media_folder if no public_folder is available', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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, + '/path/to/collection/media/folder/current/folder/image.png', + mockBaseEntry, + mockImageField, + '/path/to/collection/media/folder/current/folder', + ), + ).toBe('/path/to/collection/media/folder/current/folder/image.png'); + }); + + describe('template variable', () => { + it('should substitute field value', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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, + '/path/to/some/other/media/i-am-a-title/current/folder/image.png', + mockBaseEntry, + mockImageField, + '/path/to/some/other/media/i-am-a-title/current/folder', + ), + ).toBe('/path/to/some/other/media/i-am-a-title/current/folder/image.png'); + }); + + it('should substitute slug', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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', + slug: 'i-am-a-title-fish', + data: { title: 'i am a title', name: 'fish' }, + }); + + expect( + selectMediaFilePublicPath( + mockConfig, + mockCollection, + '/path/to/some/other/media/i-am-a-title-fish/current/folder/image.png', + mockEntry, + mockImageField, + '/path/to/some/other/media/i-am-a-title-fish/current/folder', + ), + ).toBe('/path/to/some/other/media/i-am-a-title-fish/current/folder/image.png'); + }); + + it('should substitute slug from top level media_folder if no public_folders', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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', + slug: 'i-am-a-title-fish', + data: { title: 'i am a title', name: 'fish' }, + }); + + expect( + selectMediaFilePublicPath( + mockConfig, + mockCollection, + '/path/to/media/folder/i-am-a-title-fish/current/folder/image.png', + mockEntry, + mockImageField, + '/path/to/media/folder/i-am-a-title-fish/current/folder', + ), + ).toBe('/path/to/media/folder/i-am-a-title-fish/current/folder/image.png'); + }); + + it('should substitute slug from top level public_folder', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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', + slug: 'i-am-a-title-fish', + data: { title: 'i am a title', name: 'fish' }, + }); + + expect( + selectMediaFilePublicPath( + mockConfig, + mockCollection, + '/path/to/media/folder/i-am-a-title-fish/current/folder/image.png', + mockEntry, + mockImageField, + '/path/to/media/folder/i-am-a-title-fish/current/folder', + ), + ).toBe('/path/to/public/folder/i-am-a-title-fish/current/folder/image.png'); + }); + + it('should not throw error when evaluating slug for new entry', () => { + const mockConfig = createMockConfig({ + collections: [ + createMockCollection({ + 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/DRAFT_MEDIA_FILES/index.md', + slug: '', + data: {}, + newRecord: true, + }); + + expect( + selectMediaFilePublicPath( + mockConfig, + mockCollection, + '/path/to/collection/media/folder/DRAFT_MEDIA_FILES/current/folder/image.png', + mockEntry, + mockImageField, + '/path/to/collection/media/folder/DRAFT_MEDIA_FILES/current/folder', + ), + ).toBe( + '/path/to/collection/public/folder/DRAFT_MEDIA_FILES/current/folder/image.png', + ); + }); + }); + }); + }); + }); }); }); diff --git a/packages/core/src/lib/util/entry.util.ts b/packages/core/src/lib/util/entry.util.ts index 7720d569..a129af75 100644 --- a/packages/core/src/lib/util/entry.util.ts +++ b/packages/core/src/lib/util/entry.util.ts @@ -1,14 +1,25 @@ import isEqual from 'lodash/isEqual'; +import { dirname } from 'path'; -import { isNotNullish } from './null.util'; +import { DRAFT_MEDIA_FILES } from '@staticcms/core/constants/mediaLibrary'; import { I18N_FIELD_DUPLICATE, I18N_FIELD_TRANSLATE, duplicateDefaultI18nFields, hasI18n, } from '../i18n'; +import { joinUrlPath } from '../urlHelper'; +import { isNotNullish } from './null.util'; +import { isNotEmpty } from './string.util'; -import type { Collection, EntryData, Field, ObjectValue } from '@staticcms/core/interface'; +import type { + BaseField, + Collection, + Entry, + EntryData, + Field, + ObjectValue, +} from '@staticcms/core/interface'; export function applyDefaultsToDraftData( fields: Field[], @@ -73,3 +84,18 @@ export function createEmptyDraftI18nData(collection: Collection, dataFields: Fie const i18nData = createEmptyDraftData(dataFields, skipField); return duplicateDefaultI18nFields(collection, i18nData); } + +export function createEntryMediaPath( + entry: Entry | null | undefined, + collection: Collection | null | undefined, + folder: string, +) { + const entryPath = entry?.path; + return isNotEmpty(entryPath) + ? joinUrlPath(dirname(entryPath), folder) + : joinUrlPath( + collection && 'folder' in collection ? collection.folder : '', + DRAFT_MEDIA_FILES, + folder, + ); +} diff --git a/packages/core/src/lib/util/media.util.ts b/packages/core/src/lib/util/media.util.ts index 07ad239f..08dfe180 100644 --- a/packages/core/src/lib/util/media.util.ts +++ b/packages/core/src/lib/util/media.util.ts @@ -4,6 +4,7 @@ import { dirname } from 'path'; import { basename, isAbsolutePath } from '.'; import { folderFormatter } from '../formatters'; import { joinUrlPath } from '../urlHelper'; +import { createEntryMediaPath } from './entry.util'; import type { BaseField, @@ -19,8 +20,6 @@ import type { ObjectField, } from '@staticcms/core/interface'; -export const DRAFT_MEDIA_FILES = 'DRAFT_MEDIA_FILES'; - function getFileField( collectionFiles: CollectionFile[], slug: string | undefined, @@ -228,13 +227,13 @@ function traverseFields( export function selectMediaFolder( config: Config, collection: Collection | undefined | null, - entryMap: Entry | undefined | null, + entry: Entry | undefined | null, field: MediaField | undefined, currentFolder?: string, ) { let mediaFolder = folderFormatter( config.media_folder ?? '', - entryMap, + entry, collection as Collection, config.media_folder ?? '', 'media_folder', @@ -243,19 +242,12 @@ export function selectMediaFolder( if (currentFolder) { mediaFolder = currentFolder; - } else if (hasCustomFolder('media_folder', collection, entryMap?.slug, field)) { - const folder = evaluateFolder('media_folder', config, collection!, entryMap, field); + } else if (hasCustomFolder('media_folder', collection, entry?.slug, field)) { + const folder = evaluateFolder('media_folder', config, collection!, entry, field); if (folder.startsWith('/')) { mediaFolder = folder.replace(/^[/]*/g, ''); } else { - const entryPath = entryMap?.path; - mediaFolder = entryPath - ? joinUrlPath(dirname(entryPath), folder) - : joinUrlPath( - collection && 'folder' in collection ? collection.folder : '', - DRAFT_MEDIA_FILES, - folder, - ); + mediaFolder = createEntryMediaPath(entry, collection, folder); } } @@ -266,7 +258,7 @@ export function selectMediaFilePublicPath( config: Config, collection: Collection | undefined | null, mediaPath: string, - entryMap: Entry | undefined | null, + entry: Entry | undefined | null, field: MediaField | undefined, currentFolder?: string, ) { @@ -276,7 +268,7 @@ export function selectMediaFilePublicPath( let publicFolder = folderFormatter( config.public_folder ?? '', - entryMap, + entry, collection as Collection, config.public_folder ?? '', 'public_folder', @@ -285,38 +277,35 @@ export function selectMediaFilePublicPath( let mediaFolder = folderFormatter( config.media_folder ?? '', - entryMap, + entry, collection as Collection, config.media_folder ?? '', 'media_folder', config.slug, ); - let selectedPublicFolder = publicFolder; - let selectedMediaFolder = mediaFolder; - - const customPublicFolder = hasCustomFolder('public_folder', collection, entryMap?.slug, field); - const customMediaFolder = hasCustomFolder('media_folder', collection, entryMap?.slug, field); + const customPublicFolder = hasCustomFolder('public_folder', collection, entry?.slug, field); + const customMediaFolder = hasCustomFolder('media_folder', collection, entry?.slug, field); if (customPublicFolder) { - publicFolder = evaluateFolder('public_folder', config, collection!, entryMap, field); - selectedPublicFolder = publicFolder; + publicFolder = evaluateFolder('public_folder', config, collection!, entry, field); } if (customMediaFolder) { - mediaFolder = evaluateFolder('media_folder', config, collection!, entryMap, field); - selectedMediaFolder = mediaFolder; + mediaFolder = evaluateFolder('media_folder', config, collection!, entry, field); + } + + if (publicFolder === '' && mediaFolder === '' && collection && 'folder' in collection) { + mediaFolder = createEntryMediaPath(entry, collection, mediaFolder); } if (currentFolder) { - const mediaFolder = customMediaFolder - ? evaluateFolder('media_folder', config, collection!, entryMap, field) - : config['media_folder']; - selectedPublicFolder = trim(currentFolder, '/').replace(trim(mediaFolder!, '/'), publicFolder); + publicFolder = currentFolder.replace(mediaFolder, publicFolder); + mediaFolder = currentFolder; } - if (mediaPath.startsWith(selectedMediaFolder)) { - return mediaPath.replace(selectedMediaFolder, selectedPublicFolder); + if (mediaPath.startsWith(mediaFolder)) { + return mediaPath.replace(mediaFolder, publicFolder); } return mediaPath;