Skip to content

Commit

Permalink
feat: field based media/public folders (decaporg#3208)
Browse files Browse the repository at this point in the history
  • Loading branch information
erezrokah authored and vladdu committed Jan 26, 2021
1 parent 1b299c9 commit eef043d
Show file tree
Hide file tree
Showing 30 changed files with 738 additions and 127 deletions.
5 changes: 3 additions & 2 deletions packages/netlify-cms-backend-bitbucket/src/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
getPointerFileForMediaFileObj,
getLargeMediaFilteredMediaFiles,
FetchError,
blobToFileObj,
} from 'netlify-cms-lib-util';
import NetlifyAuthenticator from 'netlify-cms-lib-auth';
import AuthenticationPage from './AuthenticationPage';
Expand Down Expand Up @@ -325,7 +326,7 @@ export default class BitbucketBackend implements Implementation {
async getMediaFile(path: string) {
const name = basename(path);
const blob = await getMediaAsBlob(path, null, this.api!.readFile.bind(this.api!));
const fileObj = new File([blob], name);
const fileObj = blobToFileObj(name, blob);
const url = URL.createObjectURL(fileObj);
const id = await getBlobSHA(fileObj);

Expand Down Expand Up @@ -423,7 +424,7 @@ export default class BitbucketBackend implements Implementation {

return getMediaAsBlob(file.path, null, readFile).then(blob => {
const name = basename(file.path);
const fileObj = new File([blob], name);
const fileObj = blobToFileObj(name, blob);
return {
id: file.path,
displayURL: URL.createObjectURL(fileObj),
Expand Down
5 changes: 3 additions & 2 deletions packages/netlify-cms-backend-github/src/implementation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
getPreviewStatus,
UnpublishedEntryMediaFile,
runWithLock,
blobToFileObj,
} from 'netlify-cms-lib-util';
import AuthenticationPage from './AuthenticationPage';
import { UsersGetAuthenticatedResponse as GitHubUser } from '@octokit/rest';
Expand Down Expand Up @@ -324,7 +325,7 @@ export default class GitHub implements Implementation {
const blob = await getMediaAsBlob(path, null, this.api!.readFile.bind(this.api!));

const name = basename(path);
const fileObj = new File([blob], name);
const fileObj = blobToFileObj(name, blob);
const url = URL.createObjectURL(fileObj);
const id = await getBlobSHA(blob);

Expand Down Expand Up @@ -388,7 +389,7 @@ export default class GitHub implements Implementation {

return getMediaAsBlob(file.path, file.id, readFile).then(blob => {
const name = basename(file.path);
const fileObj = new File([blob], name);
const fileObj = blobToFileObj(name, blob);
return {
id: file.id,
displayURL: URL.createObjectURL(fileObj),
Expand Down
7 changes: 4 additions & 3 deletions packages/netlify-cms-backend-gitlab/src/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import {
asyncLock,
AsyncLock,
runWithLock,
getBlobSHA,
blobToFileObj,
} from 'netlify-cms-lib-util';
import AuthenticationPage from './AuthenticationPage';
import API, { API_NAME } from './API';
import { getBlobSHA } from 'netlify-cms-lib-util/src';

const MAX_CONCURRENT_DOWNLOADS = 10;

Expand Down Expand Up @@ -194,7 +195,7 @@ export default class GitLab implements Implementation {
async getMediaFile(path: string) {
const name = basename(path);
const blob = await getMediaAsBlob(path, null, this.api!.readFile.bind(this.api!));
const fileObj = new File([blob], name);
const fileObj = blobToFileObj(name, blob);
const url = URL.createObjectURL(fileObj);
const id = await getBlobSHA(blob);

Expand Down Expand Up @@ -275,7 +276,7 @@ export default class GitLab implements Implementation {

return getMediaAsBlob(file.path, null, readFile).then(blob => {
const name = basename(file.path);
const fileObj = new File([blob], name);
const fileObj = blobToFileObj(name, blob);
return {
id: file.path,
displayURL: URL.createObjectURL(fileObj),
Expand Down
1 change: 1 addition & 0 deletions packages/netlify-cms-core/src/actions/__tests__/media.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ describe('media', () => {
payload.collection,
payload.entry,
path,
undefined,
);
});
});
Expand Down
21 changes: 15 additions & 6 deletions packages/netlify-cms-core/src/actions/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,12 +293,19 @@ export function retrieveLocalBackup(collection: Collection, slug: string) {
const assetProxies: AssetProxy[] = await Promise.all(
mediaFiles.map(file => {
if (file.file || file.url) {
return createAssetProxy({ path: file.path, file: file.file, url: file.url });
return createAssetProxy({
path: file.path,
file: file.file,
url: file.url,
folder: file.folder,
});
} else {
return getAsset({ collection, entry: fromJS(entry), path: file.path })(
dispatch,
getState,
);
return getAsset({
collection,
entry: fromJS(entry),
path: file.path,
folder: file.folder,
})(dispatch, getState);
}
}),
);
Expand Down Expand Up @@ -554,7 +561,9 @@ export async function getMediaAssets({
const assets = await Promise.all(
filesArray
.filter(file => file.draft)
.map(file => getAsset({ collection, entry, path: file.path })(dispatch, getState)),
.map(file =>
getAsset({ collection, entry, path: file.path, folder: file.folder })(dispatch, getState),
),
);

return assets;
Expand Down
6 changes: 3 additions & 3 deletions packages/netlify-cms-core/src/actions/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ interface GetAssetArgs {
collection: Collection;
entry: EntryMap;
path: string;
folder?: string;
}

export function getAsset({ collection, entry, path }: GetAssetArgs) {
export function getAsset({ collection, entry, path, folder }: GetAssetArgs) {
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
if (!path) return createAssetProxy({ path: '', file: new File([], 'empty') });

const state = getState();
const resolvedPath = selectMediaFilePath(state.config, collection, entry, path);
const resolvedPath = selectMediaFilePath(state.config, collection, entry, path, folder);

let asset = state.medias.get(resolvedPath);
if (asset) {
Expand Down
23 changes: 18 additions & 5 deletions packages/netlify-cms-core/src/actions/mediaLibrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export function openMediaLibrary(
config?: Map<string, unknown>;
allowMultiple?: boolean;
forImage?: boolean;
mediaFolder?: string;
publicFolder?: string;
} = {},
) {
return (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
Expand All @@ -101,17 +103,25 @@ export function closeMediaLibrary() {
};
}

export function insertMedia(mediaPath: string | string[]) {
export function insertMedia(mediaPath: string | string[], publicFolder: string | undefined) {
return (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
const state = getState();
const config = state.config;
const entry = state.entryDraft.get('entry');
const collectionName = state.entryDraft.getIn(['entry', 'collection']);
const collection = state.collections.get(collectionName);
if (Array.isArray(mediaPath)) {
mediaPath = mediaPath.map(path => selectMediaFilePublicPath(config, collection, path, entry));
mediaPath = mediaPath.map(path =>
selectMediaFilePublicPath(config, collection, path, entry, publicFolder),
);
} else {
mediaPath = selectMediaFilePublicPath(config, collection, mediaPath as string, entry);
mediaPath = selectMediaFilePublicPath(
config,
collection,
mediaPath as string,
entry,
publicFolder,
);
}
dispatch({ type: MEDIA_INSERT, payload: { mediaPath } });
};
Expand Down Expand Up @@ -191,12 +201,13 @@ function createMediaFileFromAsset({
size: file.size,
url: assetProxy.url,
path: assetProxy.path,
folder: assetProxy.folder,
};
return mediaFile;
}

export function persistMedia(file: File, opts: MediaOptions = {}) {
const { privateUpload } = opts;
const { privateUpload, mediaFolder } = opts;
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
const state = getState();
const backend = currentBackend(state.config);
Expand Down Expand Up @@ -250,10 +261,11 @@ export function persistMedia(file: File, opts: MediaOptions = {}) {
} else {
const entry = state.entryDraft.get('entry');
const collection = state.collections.get(entry?.get('collection'));
const path = selectMediaFilePath(state.config, collection, entry, file.name);
const path = selectMediaFilePath(state.config, collection, entry, file.name, mediaFolder);
assetProxy = createAssetProxy({
file,
path,
folder: mediaFolder,
});
}

Expand Down Expand Up @@ -397,6 +409,7 @@ export function mediaLoading(page: number) {

interface MediaOptions {
privateUpload?: boolean;
mediaFolder?: string;
}

export function mediaLoaded(files: ImplementationMediaFile[], opts: MediaOptions = {}) {
Expand Down
18 changes: 11 additions & 7 deletions packages/netlify-cms-core/src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { List, Map, fromJS } from 'immutable';
import * as fuzzy from 'fuzzy';
import { resolveFormat } from './formats/formats';
import { selectUseWorkflow } from './reducers/config';
import { selectMediaFilePath, selectMediaFolder, selectEntry } from './reducers/entries';
import { selectMediaFilePath, selectEntry } from './reducers/entries';
import { selectIntegration } from './reducers/integrations';
import {
selectEntrySlug,
Expand All @@ -13,6 +13,7 @@ import {
selectAllowDeletion,
selectFolderEntryExtension,
selectInferedField,
selectMediaFolders,
} from './reducers/collections';
import { createEntry, EntryValue } from './valueObjects/Entry';
import { sanitizeChar } from './lib/urlHelper';
Expand All @@ -31,6 +32,7 @@ import {
User,
getPathDepth,
Config as ImplementationConfig,
blobToFileObj,
} from 'netlify-cms-lib-util';
import { status } from './constants/publishModes';
import { extractTemplateVars, dateParsers } from './lib/stringTemplate';
Expand Down Expand Up @@ -450,8 +452,7 @@ export class Backend {
// make sure to serialize the file
if (file.url?.startsWith('blob:')) {
const blob = await fetch(file.url as string).then(res => res.blob());
const options = file.name.match(/.svg$/) ? { type: 'image/svg+xml' } : {};
return { ...file, file: new File([blob], file.name, options) };
return { ...file, file: blobToFileObj(file.name, blob) };
}
return file;
}),
Expand Down Expand Up @@ -492,10 +493,12 @@ export class Backend {
});

const entryWithFormat = this.entryWithFormat(collection)(entry);
if (collection.has('media_folder') && !integration) {
entry.mediaFiles = await this.implementation.getMedia(
selectMediaFolder(state.config, collection, fromJS(entryWithFormat)),
);
const mediaFolders = selectMediaFolders(state, collection, fromJS(entryWithFormat));
if (mediaFolders.length > 0 && !integration) {
entry.mediaFiles = [];
for (const folder of mediaFolders) {
entry.mediaFiles = [...entry.mediaFiles, ...(await this.implementation.getMedia(folder))];
}
} else {
entry.mediaFiles = state.mediaLibrary.get('files') || [];
}
Expand Down Expand Up @@ -697,6 +700,7 @@ export class Backend {
collection,
entryDraft.get('entry').set('path', path),
oldPath,
asset.folder,
);
asset.path = newPath;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,15 @@ const CardImage = styled.div`
height: 150px;
`;

const CardImageAsset = ({ getAsset, image }) => {
return <Asset path={image} getAsset={getAsset} component={CardImage} />;
const CardImageAsset = ({ getAsset, image, folder }) => {
return <Asset folder={folder} path={image} getAsset={getAsset} component={CardImage} />;
};

const EntryCard = ({
path,
summary,
image,
imageFolder,
collectionLabel,
viewStyle = VIEW_STYLE_LIST,
boundGetAsset,
Expand All @@ -114,7 +115,9 @@ const EntryCard = ({
{collectionLabel ? <CollectionLabel>{collectionLabel}</CollectionLabel> : null}
<CardHeading>{summary}</CardHeading>
</CardBody>
{image ? <CardImageAsset getAsset={boundGetAsset} image={image} /> : null}
{image ? (
<CardImageAsset getAsset={boundGetAsset} image={image} folder={imageFolder} />
) : null}
</GridCardLink>
</GridCard>
);
Expand All @@ -140,12 +143,16 @@ const mapStateToProps = (state, ownProps) => {
summary,
path: `/collections/${collection.get('name')}/entries/${entry.get('slug')}`,
image,
imageFolder: collection
.get('fields')
?.find(f => f.get('name') === inferedFields.imageField && f.get('widget') === 'image')
?.get('media_folder'),
};
};

const mapDispatchToProps = {
boundGetAsset: (collection, entry) => (dispatch, getState) => path => {
return getAsset({ collection, entry, path })(dispatch, getState);
boundGetAsset: (collection, entry) => (dispatch, getState) => (path, folder) => {
return getAsset({ collection, entry, path, folder })(dispatch, getState);
},
};

Expand Down
4 changes: 2 additions & 2 deletions packages/netlify-cms-core/src/components/Editor/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -515,8 +515,8 @@ const mapDispatchToProps = {
unpublishPublishedEntry,
deleteUnpublishedEntry,
logoutUser,
boundGetAsset: (collection, entry) => (dispatch, getState) => path => {
return getAsset({ collection, entry, path })(dispatch, getState);
boundGetAsset: (collection, entry) => (dispatch, getState) => (path, folder) => {
return getAsset({ collection, entry, path, folder })(dispatch, getState);
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,8 @@ const mapDispatchToProps = {
},
clearSearch,
clearFieldErrors,
boundGetAsset: (collection, entry) => (dispatch, getState) => path => {
return getAsset({ collection, entry, path })(dispatch, getState);
boundGetAsset: (collection, entry) => (dispatch, getState) => (path, folder) => {
return getAsset({ collection, entry, path, folder })(dispatch, getState);
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ class MediaLibrary extends React.Component {
event.persist();
event.stopPropagation();
event.preventDefault();
const { persistMedia, privateUpload, config, t } = this.props;
const { persistMedia, privateUpload, config, t, mediaFolder } = this.props;
const { files: fileList } = event.dataTransfer || event.target;
const files = [...fileList];
const file = files[0];
Expand All @@ -173,7 +173,7 @@ class MediaLibrary extends React.Component {
}),
);
} else {
await persistMedia(file, { privateUpload });
await persistMedia(file, { privateUpload, mediaFolder });

this.setState({ selectedFile: this.props.files[0] });

Expand All @@ -190,8 +190,8 @@ class MediaLibrary extends React.Component {
handleInsert = () => {
const { selectedFile } = this.state;
const { path } = selectedFile;
const { insertMedia } = this.props;
insertMedia(path);
const { insertMedia, publicFolder } = this.props;
insertMedia(path, publicFolder);
this.handleClose();
};

Expand Down Expand Up @@ -332,6 +332,8 @@ const mapStateToProps = state => {
page: mediaLibrary.get('page'),
hasNextPage: mediaLibrary.get('hasNextPage'),
isPaginating: mediaLibrary.get('isPaginating'),
mediaFolder: mediaLibrary.get('mediaFolder'),
publicFolder: mediaLibrary.get('publicFolder'),
};
return { ...mediaLibraryProps };
};
Expand Down
2 changes: 1 addition & 1 deletion packages/netlify-cms-core/src/mediaLibrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface MediaLibrary {

const initializeMediaLibrary = once(async function initializeMediaLibrary(name, options) {
const lib = (getMediaLibrary(name) as unknown) as MediaLibrary;
const handleInsert = (url: string) => store.dispatch(insertMedia(url));
const handleInsert = (url: string) => store.dispatch(insertMedia(url, undefined));
const instance = await lib.init({ options, handleInsert });
store.dispatch(createMediaLibrary(instance));
});
Expand Down
Loading

0 comments on commit eef043d

Please sign in to comment.