Allow for relative paths of media files (#2394)
* Allow for relative paths of media files fixes #325 * Switch to calculating the relative path based on collection The required relative path is now calculated depending on the location of the collection of the current entry having the media inserted into. And the configuration option has now been changed to a boolean flag. This allows collections to not neccesarily all be in the same location relative to the media folder, and simplifies config. * Clean up code and fix linting * Add unit tests to resolveMediaFilename() * Rework insertMedia action to fetch own config This moves more of the media path resolution logic into the action which makes it easier to unit test * Add unit tests for the mediaLibrary.insertMedia action * yarn run format * add dependabot config (#2580)
This commit is contained in:
committed by
Shawn Erquhart
parent
861c36f658
commit
a47a29fb8b
@ -78,5 +78,8 @@
|
||||
"react": "^16.8.4",
|
||||
"react-dom": "^16.8.4",
|
||||
"react-immutable-proptypes": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"redux-mock-store": "^1.5.3"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,94 @@
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { fromJS } from 'immutable';
|
||||
import { insertMedia } from '../mediaLibrary';
|
||||
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureMockStore(middlewares);
|
||||
|
||||
describe('mediaLibrary', () => {
|
||||
describe('insertMedia', () => {
|
||||
it('test public URL is returned directly', () => {
|
||||
const store = mockStore({});
|
||||
store.dispatch(insertMedia({ url: '//localhost/foo.png' }));
|
||||
expect(store.getActions()[0]).toEqual({
|
||||
type: 'MEDIA_INSERT',
|
||||
payload: { mediaPath: '//localhost/foo.png' },
|
||||
});
|
||||
});
|
||||
|
||||
it('Test relative path resolution', () => {
|
||||
const store = mockStore({
|
||||
config: fromJS({
|
||||
media_folder_relative: true,
|
||||
media_folder: 'content/media',
|
||||
}),
|
||||
entryDraft: fromJS({
|
||||
entry: {
|
||||
collection: 'blog-posts',
|
||||
},
|
||||
}),
|
||||
collections: fromJS({
|
||||
'blog-posts': {
|
||||
folder: 'content/blog/posts',
|
||||
},
|
||||
}),
|
||||
});
|
||||
store.dispatch(insertMedia({ name: 'foo.png' }));
|
||||
expect(store.getActions()[0]).toEqual({
|
||||
type: 'MEDIA_INSERT',
|
||||
payload: { mediaPath: '../../media/foo.png' },
|
||||
});
|
||||
});
|
||||
|
||||
// media_folder_relative will be used even if public_folder is specified
|
||||
it('Test relative path resolution, with public folder specified', () => {
|
||||
const store = mockStore({
|
||||
config: fromJS({
|
||||
media_folder_relative: true,
|
||||
media_folder: 'content/media',
|
||||
public_folder: '/static/assets/media',
|
||||
}),
|
||||
entryDraft: fromJS({
|
||||
entry: {
|
||||
collection: 'blog-posts',
|
||||
},
|
||||
}),
|
||||
collections: fromJS({
|
||||
'blog-posts': {
|
||||
folder: 'content/blog/posts',
|
||||
},
|
||||
}),
|
||||
});
|
||||
store.dispatch(insertMedia({ name: 'foo.png' }));
|
||||
expect(store.getActions()[0]).toEqual({
|
||||
type: 'MEDIA_INSERT',
|
||||
payload: { mediaPath: '../../media/foo.png' },
|
||||
});
|
||||
});
|
||||
|
||||
it('Test public_folder resolution', () => {
|
||||
const store = mockStore({
|
||||
config: fromJS({
|
||||
public_folder: '/static/assets/media',
|
||||
}),
|
||||
});
|
||||
store.dispatch(insertMedia({ name: 'foo.png' }));
|
||||
expect(store.getActions()[0]).toEqual({
|
||||
type: 'MEDIA_INSERT',
|
||||
payload: { mediaPath: '/static/assets/media/foo.png' },
|
||||
});
|
||||
});
|
||||
|
||||
it('Test incorrect usage', () => {
|
||||
const store = mockStore();
|
||||
|
||||
try {
|
||||
store.dispatch(insertMedia({ foo: 'foo.png' }));
|
||||
throw new Error('Expected Exception');
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual('Incorrect usage, expected {url} or {file}');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import { Map } from 'immutable';
|
||||
import { actions as notifActions } from 'redux-notifications';
|
||||
import { getBlobSHA } from 'netlify-cms-lib-util';
|
||||
import { resolveMediaFilename, getBlobSHA } from 'netlify-cms-lib-util';
|
||||
import { currentBackend } from 'coreSrc/backend';
|
||||
import { createAssetProxy } from 'ValueObjects/AssetProxy';
|
||||
import { selectIntegration } from 'Reducers';
|
||||
@ -82,8 +82,33 @@ export function closeMediaLibrary() {
|
||||
};
|
||||
}
|
||||
|
||||
export function insertMedia(mediaPath) {
|
||||
return { type: MEDIA_INSERT, payload: { mediaPath } };
|
||||
export function insertMedia(media) {
|
||||
return (dispatch, getState) => {
|
||||
let mediaPath;
|
||||
if (media.url) {
|
||||
// media.url is public, and already resolved
|
||||
mediaPath = media.url;
|
||||
} else if (media.name) {
|
||||
// media.name still needs to be resolved to the appropriate URL
|
||||
const state = getState();
|
||||
const config = state.config;
|
||||
if (config.get('media_folder_relative')) {
|
||||
// the path is being resolved relatively
|
||||
// and we need to know the path of the entry to resolve it
|
||||
const mediaFolder = config.get('media_folder');
|
||||
const collection = state.entryDraft.getIn(['entry', 'collection']);
|
||||
const collectionFolder = state.collections.getIn([collection, 'folder']);
|
||||
mediaPath = resolveMediaFilename(media.name, { mediaFolder, collectionFolder });
|
||||
} else {
|
||||
// the path is being resolved to a public URL
|
||||
const publicFolder = config.get('public_folder');
|
||||
mediaPath = resolveMediaFilename(media.name, { publicFolder });
|
||||
}
|
||||
} else {
|
||||
throw new Error('Incorrect usage, expected {url} or {file}');
|
||||
}
|
||||
dispatch({ type: MEDIA_INSERT, payload: { mediaPath } });
|
||||
};
|
||||
}
|
||||
|
||||
export function removeInsertedMedia(controlID) {
|
||||
|
@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
||||
import { orderBy, map } from 'lodash';
|
||||
import { translate } from 'react-polyglot';
|
||||
import fuzzy from 'fuzzy';
|
||||
import { resolvePath, fileExtension } from 'netlify-cms-lib-util';
|
||||
import { fileExtension } from 'netlify-cms-lib-util';
|
||||
import {
|
||||
loadMedia as loadMediaAction,
|
||||
persistMedia as persistMediaAction,
|
||||
@ -56,7 +56,6 @@ class MediaLibrary extends React.Component {
|
||||
persistMedia: PropTypes.func.isRequired,
|
||||
deleteMedia: PropTypes.func.isRequired,
|
||||
insertMedia: PropTypes.func.isRequired,
|
||||
publicFolder: PropTypes.string,
|
||||
closeMediaLibrary: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
@ -189,9 +188,8 @@ class MediaLibrary extends React.Component {
|
||||
handleInsert = () => {
|
||||
const { selectedFile } = this.state;
|
||||
const { name, url, urlIsPublicPath } = selectedFile;
|
||||
const { insertMedia, publicFolder } = this.props;
|
||||
const publicPath = urlIsPublicPath ? url : resolvePath(name, publicFolder);
|
||||
insertMedia(publicPath);
|
||||
const { insertMedia } = this.props;
|
||||
insertMedia(urlIsPublicPath ? { url } : { name });
|
||||
this.handleClose();
|
||||
};
|
||||
|
||||
|
@ -40,6 +40,7 @@ const getConfigSchema = () => ({
|
||||
show_preview_links: { type: 'boolean' },
|
||||
media_folder: { type: 'string', examples: ['assets/uploads'] },
|
||||
public_folder: { type: 'string', examples: ['/uploads'] },
|
||||
media_folder_relative: { type: 'boolean' },
|
||||
media_library: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
Reference in New Issue
Block a user