Fix: show specific field media files in library, cascade folder templates (#3252)

* feat: cascade & compose media folders - initial commit

* refactor: code cleanup

* fix: pass field instead of folder to getAsset

* fix: only show field media files in library

* test: fix medial library selector test

* fix: fallback to original path when asset not found

* fix: only show field media files in media library

* fix: properly handle empty strings in field folders
This commit is contained in:
Erez Rokah
2020-02-14 22:31:33 +02:00
committed by GitHub
parent 8d67de0e68
commit 02ef2010e7
22 changed files with 565 additions and 243 deletions

View File

@ -4,7 +4,7 @@ import collections, {
selectAllowDeletion,
selectEntryPath,
selectEntrySlug,
selectFieldsMediaFolders,
selectFieldsWithMediaFolders,
selectMediaFolders,
getFieldsNames,
selectField,
@ -87,12 +87,12 @@ describe('collections', () => {
describe('selectFieldsMediaFolders', () => {
it('should return empty array for invalid collection', () => {
expect(selectFieldsMediaFolders(fromJS({}))).toEqual([]);
expect(selectFieldsWithMediaFolders(fromJS({}))).toEqual([]);
});
it('should return configs for folder collection', () => {
expect(
selectFieldsMediaFolders(
selectFieldsWithMediaFolders(
fromJS({
folder: 'posts',
fields: [
@ -124,19 +124,26 @@ describe('collections', () => {
}),
),
).toEqual([
'image_media_folder',
'body_media_folder',
'list_1_item_media_folder',
'list_2_item_media_folder',
fromJS({
name: 'image',
media_folder: 'image_media_folder',
}),
fromJS({ name: 'body', media_folder: 'body_media_folder' }),
fromJS({ name: 'list_1_item', media_folder: 'list_1_item_media_folder' }),
fromJS({
name: 'list_2_item',
media_folder: 'list_2_item_media_folder',
}),
]);
});
it('should return configs for files collection', () => {
expect(
selectFieldsMediaFolders(
selectFieldsWithMediaFolders(
fromJS({
files: [
{
name: 'file1',
fields: [
{
name: 'image',
@ -145,6 +152,7 @@ describe('collections', () => {
],
},
{
name: 'file2',
fields: [
{
name: 'body',
@ -153,6 +161,7 @@ describe('collections', () => {
],
},
{
name: 'file3',
fields: [
{
name: 'list_1',
@ -164,6 +173,7 @@ describe('collections', () => {
],
},
{
name: 'file4',
fields: [
{
name: 'list_2',
@ -178,12 +188,13 @@ describe('collections', () => {
},
],
}),
'file4',
),
).toEqual([
'image_media_folder',
'body_media_folder',
'list_1_item_media_folder',
'list_2_item_media_folder',
fromJS({
name: 'list_2_item',
media_folder: 'list_2_item_media_folder',
}),
]);
});
});
@ -195,48 +206,53 @@ describe('collections', () => {
sanitize_replacement: '-',
};
const config = fromJS({ slug });
it('should return fields and collection folder', () => {
const config = fromJS({ slug, media_folder: '/static/img' });
it('should return fields and collection folders', () => {
expect(
selectMediaFolders(
{ config },
fromJS({
folder: 'posts',
media_folder: '/collection_media_folder',
media_folder: '{{media_folder}}/general/',
fields: [
{
name: 'image',
media_folder: '/image_media_folder',
media_folder: '{{media_folder}}/customers/',
},
],
}),
fromJS({ slug: 'name', path: 'src/post/post1.md' }),
fromJS({ slug: 'name', path: 'src/post/post1.md', data: {} }),
),
).toEqual(['collection_media_folder', 'image_media_folder']);
).toEqual(['static/img/general', 'static/img/general/customers']);
});
it('should return fields and collection folder', () => {
it('should return fields, file and collection folders', () => {
expect(
selectMediaFolders(
{ config },
fromJS({
media_folder: '{{media_folder}}/general/',
files: [
{
name: 'name',
file: 'src/post/post1.md',
media_folder: '/file_media_folder',
media_folder: '{{media_folder}}/customers/',
fields: [
{
name: 'image',
media_folder: '/image_media_folder',
media_folder: '{{media_folder}}/logos/',
},
],
},
],
}),
fromJS({ slug: 'name', path: 'src/post/post1.md' }),
fromJS({ slug: 'name', path: 'src/post/post1.md', data: {} }),
),
).toEqual(['file_media_folder', 'image_media_folder']);
).toEqual([
'static/img/general',
'static/img/general/customers',
'static/img/general/customers/logos',
]);
});
});

View File

@ -1,4 +1,4 @@
import { Map, OrderedMap, fromJS } from 'immutable';
import { OrderedMap, fromJS } from 'immutable';
import * as actions from 'Actions/entries';
import reducer, {
selectMediaFolder,
@ -7,13 +7,13 @@ import reducer, {
} from '../entries';
const initialState = OrderedMap({
posts: Map({ name: 'posts' }),
posts: fromJS({ name: 'posts' }),
});
describe('entries', () => {
describe('reducer', () => {
it('should mark entries as fetching', () => {
expect(reducer(initialState, actions.entriesLoading(Map({ name: 'posts' })))).toEqual(
expect(reducer(initialState, actions.entriesLoading(fromJS({ name: 'posts' })))).toEqual(
OrderedMap(
fromJS({
posts: { name: 'posts' },
@ -31,7 +31,7 @@ describe('entries', () => {
{ slug: 'b', title: 'B' },
];
expect(
reducer(initialState, actions.entriesLoaded(Map({ name: 'posts' }), entries, 0)),
reducer(initialState, actions.entriesLoaded(fromJS({ name: 'posts' }), entries, 0)),
).toEqual(
OrderedMap(
fromJS({
@ -53,7 +53,7 @@ describe('entries', () => {
it('should handle loaded entry', () => {
const entry = { slug: 'a', path: '' };
expect(reducer(initialState, actions.entryLoaded(Map({ name: 'posts' }), entry))).toEqual(
expect(reducer(initialState, actions.entryLoaded(fromJS({ name: 'posts' }), entry))).toEqual(
OrderedMap(
fromJS({
posts: { name: 'posts' },
@ -75,8 +75,8 @@ describe('entries', () => {
it("should return global media folder when collection doesn't specify media_folder", () => {
expect(
selectMediaFolder(
Map({ media_folder: 'static/media' }),
Map({ name: 'posts' }),
fromJS({ media_folder: 'static/media' }),
fromJS({ name: 'posts' }),
undefined,
undefined,
),
@ -86,8 +86,8 @@ describe('entries', () => {
it('should return draft media folder when collection specifies media_folder and entry is undefined', () => {
expect(
selectMediaFolder(
Map({ media_folder: 'static/media' }),
Map({ name: 'posts', folder: 'posts', media_folder: '' }),
fromJS({ media_folder: 'static/media' }),
fromJS({ name: 'posts', folder: 'posts', media_folder: '' }),
undefined,
undefined,
),
@ -97,9 +97,9 @@ describe('entries', () => {
it('should return relative media folder when collection specifies media_folder and entry path is not null', () => {
expect(
selectMediaFolder(
Map({ media_folder: 'static/media' }),
Map({ name: 'posts', folder: 'posts', media_folder: '' }),
Map({ path: 'posts/title/index.md' }),
fromJS({ media_folder: 'static/media' }),
fromJS({ name: 'posts', folder: 'posts', media_folder: '' }),
fromJS({ path: 'posts/title/index.md' }),
undefined,
),
).toEqual('posts/title');
@ -108,24 +108,41 @@ describe('entries', () => {
it('should resolve collection relative media folder', () => {
expect(
selectMediaFolder(
Map({ media_folder: 'static/media' }),
Map({ name: 'posts', folder: 'posts', media_folder: '../' }),
Map({ path: 'posts/title/index.md' }),
fromJS({ media_folder: 'static/media' }),
fromJS({ name: 'posts', folder: 'posts', media_folder: '../' }),
fromJS({ path: 'posts/title/index.md' }),
undefined,
),
).toEqual('posts/');
).toEqual('posts');
});
it('should resolve field relative media folder', () => {
const field = fromJS({ media_folder: '' });
expect(
selectMediaFolder(
fromJS({ media_folder: '/static/img' }),
fromJS({
name: 'other',
folder: 'other',
fields: [field],
media_folder: '../',
}),
fromJS({ path: 'src/other/other.md', data: {} }),
field,
),
).toEqual('src/other');
});
it('should return collection absolute media folder without leading slash', () => {
expect(
selectMediaFolder(
Map({ media_folder: '/static/Images' }),
Map({
fromJS({ media_folder: '/static/Images' }),
fromJS({
name: 'getting-started',
folder: 'src/docs/getting-started',
media_folder: '/static/images/docs/getting-started',
}),
Map({ path: 'src/docs/getting-started/with-github.md' }),
fromJS({ path: 'src/docs/getting-started/with-github.md' }),
undefined,
),
).toEqual('static/images/docs/getting-started');
@ -201,7 +218,13 @@ describe('entries', () => {
const collection = fromJS({
name: 'posts',
folder: 'content',
fields: [{ name: 'title', widget: 'string' }],
fields: [
{
name: 'title',
widget: 'string',
media_folder: '../../../{{media_folder}}/{{category}}/{{slug}}',
},
],
});
expect(
@ -209,7 +232,7 @@ describe('entries', () => {
fromJS({ media_folder: 'static/media', slug: slugConfig }),
collection,
entry,
'../../../{{media_folder}}/{{category}}/{{slug}}',
collection.get('fields').get(0),
),
).toEqual('static/media/hosting-and-deployment/deployment-with-nanobox');
});
@ -260,7 +283,47 @@ describe('entries', () => {
fromJS({ path: 'posts/title/index.md', slug: 'index' }),
undefined,
),
).toBe('static/images/');
).toBe('static/images');
});
it('should cascade media_folders', () => {
const mainImageField = fromJS({ name: 'main_image' });
const logoField = fromJS({ name: 'logo', media_folder: '{{media_folder}}/logos/' });
const nestedField2 = fromJS({ name: 'nested', media_folder: '{{media_folder}}/nested2/' });
const nestedField1 = fromJS({
name: 'nested',
media_folder: '{{media_folder}}/nested1/',
fields: [nestedField2],
});
const args = [
fromJS({ media_folder: '/static/img' }),
fromJS({
name: 'general',
media_folder: '{{media_folder}}/general/',
files: [
{
name: 'customers',
media_folder: '{{media_folder}}/customers/',
fields: [
mainImageField,
logoField,
{ media_folder: '{{media_folder}}/nested', field: nestedField1 },
],
},
],
}),
fromJS({ path: 'src/customers/customers.md', slug: 'customers', data: { title: 'title' } }),
];
expect(selectMediaFolder(...args, mainImageField)).toBe('static/img/general/customers');
expect(selectMediaFolder(...args, logoField)).toBe('static/img/general/customers/logos');
expect(selectMediaFolder(...args, nestedField1)).toBe(
'static/img/general/customers/nested/nested1',
);
expect(selectMediaFolder(...args, nestedField2)).toBe(
'static/img/general/customers/nested/nested1/nested2',
);
});
});
@ -274,8 +337,8 @@ describe('entries', () => {
it('should resolve path from global media folder for collection with no media folder', () => {
expect(
selectMediaFilePath(
Map({ media_folder: 'static/media' }),
Map({ name: 'posts', folder: 'posts' }),
fromJS({ media_folder: 'static/media' }),
fromJS({ name: 'posts', folder: 'posts' }),
undefined,
'image.png',
undefined,
@ -286,8 +349,8 @@ describe('entries', () => {
it('should resolve path from collection media folder for collection with media folder', () => {
expect(
selectMediaFilePath(
Map({ media_folder: 'static/media' }),
Map({ name: 'posts', folder: 'posts', media_folder: '' }),
fromJS({ media_folder: 'static/media' }),
fromJS({ name: 'posts', folder: 'posts', media_folder: '' }),
undefined,
'image.png',
undefined,
@ -298,9 +361,9 @@ describe('entries', () => {
it('should handle relative media_folder', () => {
expect(
selectMediaFilePath(
Map({ media_folder: 'static/media' }),
Map({ name: 'posts', folder: 'posts', media_folder: '../../static/media/' }),
Map({ path: 'posts/title/index.md' }),
fromJS({ media_folder: 'static/media' }),
fromJS({ name: 'posts', folder: 'posts', media_folder: '../../static/media/' }),
fromJS({ path: 'posts/title/index.md' }),
'image.png',
undefined,
),
@ -308,13 +371,14 @@ describe('entries', () => {
});
it('should handle field media_folder', () => {
const field = fromJS({ media_folder: '../../static/media/' });
expect(
selectMediaFilePath(
Map({ media_folder: 'static/media' }),
Map({ name: 'posts', folder: 'posts' }),
Map({ path: 'posts/title/index.md' }),
fromJS({ media_folder: 'static/media' }),
fromJS({ name: 'posts', folder: 'posts', fields: [field] }),
fromJS({ path: 'posts/title/index.md' }),
'image.png',
'../../static/media/',
field,
),
).toBe('static/media/image.png');
});
@ -330,7 +394,7 @@ describe('entries', () => {
it('should resolve path from public folder for collection with no media folder', () => {
expect(
selectMediaFilePublicPath(
Map({ public_folder: '/media' }),
fromJS({ public_folder: '/media' }),
null,
'/media/image.png',
undefined,
@ -342,8 +406,8 @@ describe('entries', () => {
it('should resolve path from collection public folder for collection with public folder', () => {
expect(
selectMediaFilePublicPath(
Map({ public_folder: '/media' }),
Map({ name: 'posts', folder: 'posts', public_folder: '' }),
fromJS({ public_folder: '/media' }),
fromJS({ name: 'posts', folder: 'posts', public_folder: '' }),
'image.png',
undefined,
undefined,
@ -354,8 +418,8 @@ describe('entries', () => {
it('should handle relative public_folder', () => {
expect(
selectMediaFilePublicPath(
Map({ public_folder: '/media' }),
Map({ name: 'posts', folder: 'posts', public_folder: '../../static/media/' }),
fromJS({ public_folder: '/media' }),
fromJS({ name: 'posts', folder: 'posts', public_folder: '../../static/media/' }),
'image.png',
undefined,
undefined,
@ -403,10 +467,16 @@ describe('entries', () => {
path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
data: { title: 'Deployment With NanoBox', category: 'Hosting And Deployment' },
});
const field = fromJS({
name: 'title',
widget: 'string',
public_folder: '/{{public_folder}}/{{category}}/{{slug}}',
});
const collection = fromJS({
name: 'posts',
folder: 'content',
fields: [{ name: 'title', widget: 'string' }],
fields: [field],
});
expect(
@ -415,7 +485,7 @@ describe('entries', () => {
collection,
'image.png',
entry,
'/{{public_folder}}/{{category}}/{{slug}}',
field,
),
).toEqual('/static/media/hosting-and-deployment/deployment-with-nanobox/image.png');
});
@ -431,10 +501,16 @@ describe('entries', () => {
path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
data: { title: 'Deployment With NanoBox', category: 'Hosting And Deployment' },
});
const field = fromJS({
name: 'title',
widget: 'string',
public_folder: '/{{public_folder}}/{{category}}/{{slug}}',
});
const collection = fromJS({
name: 'posts',
folder: 'content',
fields: [{ name: 'title', widget: 'string' }],
fields: [field],
});
expect(
@ -443,7 +519,7 @@ describe('entries', () => {
collection,
'image.png',
entry,
'/{{public_folder}}/{{category}}/{{slug}}',
field,
),
).toEqual('/static/media/hosting-and-deployment/deployment-with-nanobox/image.png');
});

View File

@ -43,16 +43,68 @@ describe('mediaLibrary', () => {
);
});
it('should select draft media files when editing a draft', () => {
const { selectEditingDraft } = require('Reducers/entries');
it('should select draft media files from field when editing a draft', () => {
const { selectEditingDraft, selectMediaFolder } = require('Reducers/entries');
selectEditingDraft.mockReturnValue(true);
selectMediaFolder.mockReturnValue('/static/images/posts/logos');
const imageField = fromJS({ name: 'image' });
const collection = fromJS({ fields: [imageField] });
const entry = fromJS({
collection: 'posts',
mediaFiles: [
{ id: 1, path: '/static/images/posts/logos/logo.png' },
{ id: 2, path: '/static/images/posts/general/image.png' },
{ id: 3, path: '/static/images/posts/index.png' },
],
data: {},
});
const state = {
entryDraft: fromJS({ entry: { mediaFiles: [{ id: 1 }] } }),
config: {},
collections: fromJS({ posts: collection }),
entryDraft: fromJS({
entry,
}),
};
expect(selectMediaFiles(state)).toEqual([{ key: 1, id: 1 }]);
expect(selectMediaFiles(state, imageField)).toEqual([
{ id: 1, key: 1, path: '/static/images/posts/logos/logo.png' },
]);
expect(selectMediaFolder).toHaveBeenCalledWith(state.config, collection, entry, imageField);
});
it('should select draft media files from collection when editing a draft', () => {
const { selectEditingDraft, selectMediaFolder } = require('Reducers/entries');
selectEditingDraft.mockReturnValue(true);
selectMediaFolder.mockReturnValue('/static/images/posts');
const imageField = fromJS({ name: 'image' });
const collection = fromJS({ fields: [imageField] });
const entry = fromJS({
collection: 'posts',
mediaFiles: [
{ id: 1, path: '/static/images/posts/logos/logo.png' },
{ id: 2, path: '/static/images/posts/general/image.png' },
{ id: 3, path: '/static/images/posts/index.png' },
],
data: {},
});
const state = {
config: {},
collections: fromJS({ posts: collection }),
entryDraft: fromJS({
entry,
}),
};
expect(selectMediaFiles(state, imageField)).toEqual([
{ id: 3, key: 3, path: '/static/images/posts/index.png' },
]);
expect(selectMediaFolder).toHaveBeenCalledWith(state.config, collection, entry, imageField);
});
it('should select global media files when not editing a draft', () => {

View File

@ -115,56 +115,61 @@ const selectors = {
},
};
const getFieldsMediaFolders = (fields: EntryField[]) => {
const mediaFolders = fields.reduce((acc, f) => {
const getFieldsWithMediaFolders = (fields: EntryField[]) => {
const fieldsWithMediaFolders = fields.reduce((acc, f) => {
if (f.has('media_folder')) {
acc = [...acc, f.get('media_folder') as string];
acc = [...acc, f];
}
if (f.has('fields')) {
const fields = f.get('fields')?.toArray() as EntryField[];
acc = [...acc, ...getFieldsMediaFolders(fields)];
acc = [...acc, ...getFieldsWithMediaFolders(fields)];
}
if (f.has('field')) {
const field = f.get('field') as EntryField;
acc = [...acc, ...getFieldsMediaFolders([field])];
acc = [...acc, ...getFieldsWithMediaFolders([field])];
}
return acc;
}, [] as string[]);
}, [] as EntryField[]);
return mediaFolders;
return fieldsWithMediaFolders;
};
export const selectFieldsMediaFolders = (collection: Collection) => {
const getFileFromSlug = (collection: Collection, slug: string) => {
return collection
.get('files')
?.toArray()
.filter(f => f.get('name') === slug)[0];
};
export const selectFieldsWithMediaFolders = (collection: Collection, slug: string) => {
if (collection.has('folder')) {
const fields = collection.get('fields').toArray();
return getFieldsMediaFolders(fields);
return getFieldsWithMediaFolders(fields);
} else if (collection.has('files')) {
const fields = collection
.get('files')
?.toArray()
.map(f => f.get('fields').toArray()) as EntryField[][];
const flattened = [] as EntryField[];
return getFieldsMediaFolders(flattened.concat(...fields));
const fields =
getFileFromSlug(collection, slug)
?.get('fields')
.toArray() || [];
return getFieldsWithMediaFolders(fields);
}
return [];
};
export const selectMediaFolders = (state: State, collection: Collection, entry: EntryMap) => {
const fieldsFolders = selectFieldsMediaFolders(collection);
const folders = fieldsFolders.map(folder =>
selectMediaFolder(state.config, collection, entry, folder),
);
if (
collection.has('media_folder') ||
collection
.get('files')
?.find(file => file?.get('name') === entry?.get('slug') && file?.has('media_folder'))
) {
const fields = selectFieldsWithMediaFolders(collection, entry.get('slug'));
const folders = fields.map(f => selectMediaFolder(state.config, collection, entry, f));
if (collection.has('files')) {
const file = getFileFromSlug(collection, entry.get('slug'));
if (file) {
folders.unshift(selectMediaFolder(state.config, collection, entry, undefined));
}
}
if (collection.has('media_folder')) {
// stop evaluating media folders at collection level
collection = collection.delete('files');
folders.unshift(selectMediaFolder(state.config, collection, entry, undefined));
}

View File

@ -24,10 +24,12 @@ import {
EntriesRequestPayload,
EntryDraft,
EntryMap,
EntryField,
CollectionFiles,
} from '../types/redux';
import { folderFormatter } from '../lib/formatters';
import { isAbsolutePath, basename } from 'netlify-cms-lib-util';
import { trimStart } from 'lodash';
import { trim } from 'lodash';
let collection: string;
let loadedEntries: EntryObject[];
@ -139,62 +141,209 @@ export const selectEntries = (state: Entries, collection: string) => {
const DRAFT_MEDIA_FILES = 'DRAFT_MEDIA_FILES';
const getCustomFolder = (
name: 'media_folder' | 'public_folder',
const getFileField = (collectionFiles: CollectionFiles, slug: string | undefined) => {
const file = collectionFiles.find(f => f?.get('name') === slug);
return file;
};
const hasCustomFolder = (
folderKey: 'media_folder' | 'public_folder',
collection: Collection | null,
slug: string | undefined,
fieldFolder: string | undefined,
field: EntryField | undefined,
) => {
if (!collection) {
return undefined;
return false;
}
if (fieldFolder !== undefined) {
return fieldFolder;
if (field && field.has(folderKey)) {
return true;
}
if (collection.has('files') && slug) {
const file = collection.get('files')?.find(f => f?.get('name') === slug);
if (file && file.has(name)) {
return file.get(name);
if (collection.has('files')) {
const file = getFileField(collection.get('files')!, slug);
if (file && file.has(folderKey)) {
return true;
}
}
if (collection.has(name)) {
return collection.get(name);
if (collection.has(folderKey)) {
return true;
}
return undefined;
return false;
};
const traverseFields = (
folderKey: 'media_folder' | 'public_folder',
config: Config,
collection: Collection,
entryMap: EntryMap | undefined,
field: EntryField,
fields: EntryField[],
currentFolder: string,
): string | null => {
const matchedField = fields.filter(f => f === field)[0];
if (matchedField) {
return folderFormatter(
matchedField.has(folderKey) ? matchedField.get(folderKey)! : `{{${folderKey}}}`,
entryMap,
collection,
currentFolder,
folderKey,
config.get('slug'),
);
}
for (let f of fields) {
if (!f.has(folderKey)) {
// add identity template if doesn't exist
f = f.set(folderKey, `{{${folderKey}}}`);
}
const folder = folderFormatter(
f.get(folderKey)!,
entryMap,
collection,
currentFolder,
folderKey,
config.get('slug'),
);
if (f.has('fields')) {
return traverseFields(
folderKey,
config,
collection,
entryMap,
field,
f.get('fields')!.toArray(),
folder,
);
} else if (f.has('field')) {
return traverseFields(
folderKey,
config,
collection,
entryMap,
field,
[f.get('field')!],
folder,
);
}
}
return null;
};
const evaluateFolder = (
folderKey: 'media_folder' | 'public_folder',
config: Config,
collection: Collection,
entryMap: EntryMap | undefined,
field: EntryField | undefined,
) => {
let currentFolder = config.get(folderKey);
// add identity template if doesn't exist
if (!collection.has(folderKey)) {
collection = collection.set(folderKey, `{{${folderKey}}}`);
}
if (collection.has('files')) {
// files collection evaluate the collection template
// then move on to the specific file configuration denoted by the slug
currentFolder = folderFormatter(
collection.get(folderKey)!,
entryMap,
collection,
currentFolder,
folderKey,
config.get('slug'),
);
let file = getFileField(collection.get('files')!, entryMap?.get('slug'));
if (file) {
if (!file.has(folderKey)) {
// add identity template if doesn't exist
file = file.set(folderKey, `{{${folderKey}}}`);
}
// evaluate the file template and keep evaluating until we match our field
currentFolder = folderFormatter(
file.get(folderKey)!,
entryMap,
collection,
currentFolder,
folderKey,
config.get('slug'),
);
if (field) {
const fieldFolder = traverseFields(
folderKey,
config,
collection,
entryMap,
field,
file.get('fields')!.toArray(),
currentFolder,
);
if (fieldFolder !== null) {
currentFolder = fieldFolder;
}
}
}
} else {
// folder collection, evaluate the collection template
// and keep evaluating until we match our field
currentFolder = folderFormatter(
collection.get(folderKey)!,
entryMap,
collection,
currentFolder,
folderKey,
config.get('slug'),
);
if (field) {
const fieldFolder = traverseFields(
folderKey,
config,
collection,
entryMap,
field,
collection.get('fields')!.toArray(),
currentFolder,
);
if (fieldFolder !== null) {
currentFolder = fieldFolder;
}
}
}
return currentFolder;
};
export const selectMediaFolder = (
config: Config,
collection: Collection | null,
entryMap: EntryMap | undefined,
fieldMediaFolder: string | undefined,
field: EntryField | undefined,
) => {
let mediaFolder = config.get('media_folder');
const name = 'media_folder';
let mediaFolder = config.get(name);
const customFolder = getCustomFolder(
'media_folder',
collection,
entryMap?.get('slug'),
fieldMediaFolder,
);
const customFolder = hasCustomFolder(name, collection, entryMap?.get('slug'), field);
if (customFolder !== undefined) {
if (customFolder) {
const entryPath = entryMap?.get('path');
if (entryPath) {
const entryDir = dirname(entryPath);
const folder = folderFormatter(
customFolder,
entryMap as EntryMap,
collection!,
mediaFolder,
'media_folder',
config.get('slug'),
);
// return absolute paths as is without the leading '/'
const folder = evaluateFolder(name, config, collection!, entryMap, field);
// return absolute paths as is
if (folder.startsWith('/')) {
mediaFolder = join(trimStart(folder, '/'));
mediaFolder = join(folder);
} else {
mediaFolder = join(entryDir, folder as string);
}
@ -203,7 +352,7 @@ export const selectMediaFolder = (
}
}
return mediaFolder;
return trim(mediaFolder, '/');
};
export const selectMediaFilePath = (
@ -211,13 +360,13 @@ export const selectMediaFilePath = (
collection: Collection | null,
entryMap: EntryMap | undefined,
mediaPath: string,
fieldMediaFolder: string | undefined,
field: EntryField | undefined,
) => {
if (isAbsolutePath(mediaPath)) {
return mediaPath;
}
const mediaFolder = selectMediaFolder(config, collection, entryMap, fieldMediaFolder);
const mediaFolder = selectMediaFolder(config, collection, entryMap, field);
return join(mediaFolder, basename(mediaPath));
};
@ -227,30 +376,19 @@ export const selectMediaFilePublicPath = (
collection: Collection | null,
mediaPath: string,
entryMap: EntryMap | undefined,
fieldPublicFolder: string | undefined,
field: EntryField | undefined,
) => {
if (isAbsolutePath(mediaPath)) {
return mediaPath;
}
let publicFolder = config.get('public_folder');
const name = 'public_folder';
let publicFolder = config.get(name);
const customFolder = getCustomFolder(
'public_folder',
collection,
entryMap?.get('slug'),
fieldPublicFolder,
);
const customFolder = hasCustomFolder(name, collection, entryMap?.get('slug'), field);
if (customFolder !== undefined) {
publicFolder = folderFormatter(
customFolder,
entryMap,
collection!,
publicFolder,
'public_folder',
config.get('slug'),
);
if (customFolder) {
publicFolder = evaluateFolder(name, config, collection!, entryMap, field);
}
return join(publicFolder, basename(mediaPath));

View File

@ -19,7 +19,7 @@ import {
MEDIA_DISPLAY_URL_SUCCESS,
MEDIA_DISPLAY_URL_FAILURE,
} from '../actions/mediaLibrary';
import { selectEditingDraft } from './entries';
import { selectEditingDraft, selectMediaFolder } from './entries';
import { selectIntegration } from './';
import {
State,
@ -28,7 +28,9 @@ import {
MediaFile,
MediaFileMap,
DisplayURLState,
EntryField,
} from '../types/redux';
import { dirname } from 'path';
const defaultState: {
isVisible: boolean;
@ -40,6 +42,7 @@ const defaultState: {
page?: number;
files?: MediaFile[];
config: Map<string, string>;
field?: EntryField;
} = {
isVisible: false,
showMediaButton: true,
@ -56,14 +59,7 @@ const mediaLibrary = (state = Map(defaultState), action: MediaLibraryAction) =>
map.set('showMediaButton', action.payload.enableStandalone());
});
case MEDIA_LIBRARY_OPEN: {
const {
controlID,
forImage,
privateUpload,
config,
mediaFolder,
publicFolder,
} = action.payload;
const { controlID, forImage, privateUpload, config, field } = action.payload;
const libConfig = config || Map();
const privateUploadChanged = state.get('privateUpload') !== privateUpload;
if (privateUploadChanged) {
@ -75,6 +71,7 @@ const mediaLibrary = (state = Map(defaultState), action: MediaLibraryAction) =>
privateUpload,
config: libConfig,
controlMedia: Map(),
field,
});
}
return state.withMutations(map => {
@ -84,8 +81,7 @@ const mediaLibrary = (state = Map(defaultState), action: MediaLibraryAction) =>
map.set('canInsert', !!controlID);
map.set('privateUpload', privateUpload);
map.set('config', libConfig);
map.set('mediaFolder', mediaFolder);
map.set('publicFolder', publicFolder);
map.set('field', field);
});
}
case MEDIA_LIBRARY_CLOSE:
@ -218,7 +214,7 @@ const mediaLibrary = (state = Map(defaultState), action: MediaLibraryAction) =>
}
};
export function selectMediaFiles(state: State) {
export function selectMediaFiles(state: State, field?: EntryField) {
const { mediaLibrary, entryDraft } = state;
const editingDraft = selectEditingDraft(state.entryDraft);
const integration = selectIntegration(state, null, 'assetStore');
@ -228,7 +224,12 @@ export function selectMediaFiles(state: State) {
const entryFiles = entryDraft
.getIn(['entry', 'mediaFiles'], List<MediaFileMap>())
.toJS() as MediaFile[];
files = entryFiles.map(file => ({ key: file.id, ...file }));
const entry = entryDraft.get('entry');
const collection = state.collections.get(entry?.get('collection'));
const mediaFolder = selectMediaFolder(state.config, collection, entry, field);
files = entryFiles
.filter(f => dirname(f.path) === mediaFolder)
.map(file => ({ key: file.id, ...file }));
} else {
files = mediaLibrary.get('files') || [];
}