feat: add cloudinary support (#1932)
This commit is contained in:
parent
9662eb292f
commit
1fc2f50499
@ -63,8 +63,8 @@ export function openMediaLibrary(payload = {}) {
|
||||
const state = getState();
|
||||
const mediaLibrary = state.mediaLibrary.get('externalLibrary');
|
||||
if (mediaLibrary) {
|
||||
const { controlID: id, value, config = Map(), forImage } = payload;
|
||||
mediaLibrary.show({ id, value, config: config.toJS(), imagesOnly: forImage });
|
||||
const { controlID: id, value, config = Map(), allowMultiple, forImage } = payload;
|
||||
mediaLibrary.show({ id, value, config: config.toJS(), allowMultiple, imagesOnly: forImage });
|
||||
}
|
||||
dispatch({ type: MEDIA_LIBRARY_OPEN, payload });
|
||||
};
|
||||
|
@ -56,5 +56,12 @@ export const selectUnpublishedEntriesByStatus = (state, status) =>
|
||||
export const selectIntegration = (state, collection, hook) =>
|
||||
fromIntegrations.selectIntegration(state.integrations, collection, hook);
|
||||
|
||||
export const getAsset = (state, path) =>
|
||||
fromMedias.getAsset(state.config.get('public_folder'), state.medias, path);
|
||||
export const getAsset = (state, path) => {
|
||||
/**
|
||||
* If an external media library is in use, just return the path.
|
||||
*/
|
||||
if (state.mediaLibrary.get('externalLibrary')) {
|
||||
return path;
|
||||
}
|
||||
return fromMedias.getAsset(state.config.get('public_folder'), state.medias, path);
|
||||
};
|
||||
|
@ -17,6 +17,9 @@ const image = {
|
||||
label: 'Image',
|
||||
name: 'image',
|
||||
widget: 'image',
|
||||
media_library: {
|
||||
allow_multiple: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Alt Text',
|
||||
|
11
packages/netlify-cms-media-library-cloudinary/README.md
Normal file
11
packages/netlify-cms-media-library-cloudinary/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Docs coming soon!
|
||||
|
||||
Netlify CMS was recently converted from a single npm package to a "monorepo" of over 20 packages.
|
||||
That's over 20 Readme's! We haven't created one for this package yet, but we will soon.
|
||||
|
||||
In the meantime, you can:
|
||||
|
||||
1. Check out the [main readme](https://github.com/netlify/netlify-cms/#readme) or the [documentation
|
||||
site](https://www.netlifycms.org) for more info.
|
||||
2. Reach out to the [community chat](https://gitter.im/netlify/netlifycms/) if you need help.
|
||||
3. Help out and [write the readme yourself](https://github.com/netlify/netlify-cms/edit/master/packages/netlify-cms-media-library-cloudinary/README.md)!
|
35
packages/netlify-cms-media-library-cloudinary/package.json
Normal file
35
packages/netlify-cms-media-library-cloudinary/package.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "netlify-cms-media-library-cloudinary",
|
||||
"description": "Cloudinary integration for Netlify CMS",
|
||||
"version": "1.0.0",
|
||||
"repository": "https://github.com/netlify/netlify-cms/tree/master/packages/netlify-cms-media-library-cloudinary",
|
||||
"bugs": "https://github.com/netlify/netlify-cms/issues",
|
||||
"main": "dist/netlify-cms-media-library-cloudinary.js",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"netlify",
|
||||
"netlify-cms",
|
||||
"cloudinary",
|
||||
"image",
|
||||
"images",
|
||||
"media",
|
||||
"assets",
|
||||
"files",
|
||||
"uploads"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"watch": "webpack -w",
|
||||
"develop": "npm run watch",
|
||||
"build": "cross-env NODE_ENV=production webpack"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^5.2.0",
|
||||
"webpack": "^4.16.1",
|
||||
"webpack-cli": "^3.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"netlify-cms-lib-util": "^2.0.4"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
82
packages/netlify-cms-media-library-cloudinary/src/index.js
Normal file
82
packages/netlify-cms-media-library-cloudinary/src/index.js
Normal file
@ -0,0 +1,82 @@
|
||||
import { pick } from 'lodash';
|
||||
import { loadScript } from 'netlify-cms-lib-util';
|
||||
|
||||
const defaultOptions = {
|
||||
use_secure_url: true,
|
||||
use_transformations: true,
|
||||
output_filename_only: false,
|
||||
};
|
||||
/**
|
||||
* This configuration hash cannot be overriden, as the values here are required
|
||||
* for the integration to work properly.
|
||||
*/
|
||||
const enforcedConfig = {
|
||||
button_class: undefined,
|
||||
inline_container: undefined,
|
||||
insert_transformation: false,
|
||||
z_index: '99999',
|
||||
};
|
||||
|
||||
const defaultConfig = {
|
||||
multiple: false,
|
||||
};
|
||||
|
||||
function getAssetUrl(asset, { use_secure_url, use_transformations, output_filename_only }) {
|
||||
/**
|
||||
* Allow output of the file name only, in which case the rest of the url (including)
|
||||
* transformations) can be handled by the static site generator.
|
||||
*/
|
||||
if (output_filename_only) {
|
||||
return `${asset.public_id}.${asset.format}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get url from `derived` property if it exists. This property contains the
|
||||
* transformed version of image if transformations have been applied.
|
||||
*/
|
||||
const urlObject = asset.derived && use_transformations ? asset.derived[0] : asset;
|
||||
|
||||
/**
|
||||
* Retrieve the `https` variant of the image url if the `useSecureUrl` option
|
||||
* is set to `true` (this is the default setting).
|
||||
*/
|
||||
const urlKey = use_secure_url ? 'secure_url' : 'url';
|
||||
|
||||
return urlObject[urlKey];
|
||||
}
|
||||
|
||||
async function init({ options, handleInsert }) {
|
||||
const { config: providedConfig = {}, ...integrationOptions } = options;
|
||||
const resolvedOptions = { ...defaultOptions, ...integrationOptions };
|
||||
const cloudinaryConfig = { ...defaultConfig, ...providedConfig, ...enforcedConfig };
|
||||
const cloudinaryBehaviorConfigKeys = ['default_transformations', 'max_files', 'multiple'];
|
||||
const cloudinaryBehaviorConfig = pick(cloudinaryConfig, cloudinaryBehaviorConfigKeys);
|
||||
|
||||
await loadScript('https://media-library.cloudinary.com/global/all.js');
|
||||
|
||||
const insertHandler = data => {
|
||||
const assets = data.assets.map(asset => getAssetUrl(asset, resolvedOptions));
|
||||
handleInsert(cloudinaryConfig.multiple ? assets : assets[0]);
|
||||
};
|
||||
|
||||
const mediaLibrary = window.cloudinary.createMediaLibrary(cloudinaryConfig, { insertHandler });
|
||||
|
||||
return {
|
||||
show: ({ config: instanceConfig = {}, allowMultiple }) => {
|
||||
/**
|
||||
* Ensure multiple selection is not available if the field is configured
|
||||
* to disallow it.
|
||||
*/
|
||||
if (allowMultiple === false) {
|
||||
instanceConfig.multiple = false;
|
||||
}
|
||||
return mediaLibrary.show({ config: { ...cloudinaryBehaviorConfig, instanceConfig } });
|
||||
},
|
||||
hide: () => mediaLibrary.hide(),
|
||||
enableStandalone: () => true,
|
||||
};
|
||||
}
|
||||
|
||||
const cloudinaryMediaLibrary = { name: 'cloudinary', init };
|
||||
|
||||
export default cloudinaryMediaLibrary;
|
@ -0,0 +1,3 @@
|
||||
const { getConfig } = require('../../scripts/webpack.js');
|
||||
|
||||
module.exports = getConfig();
|
@ -113,8 +113,10 @@ async function init({ options = { config: {} }, handleInsert }) {
|
||||
* On show, create a new widget, cache it in the widgets object, and open.
|
||||
* No hide method is provided because the widget doesn't provide it.
|
||||
*/
|
||||
show: ({ value, config: instanceConfig = {}, imagesOnly }) => {
|
||||
show: ({ value, config: instanceConfig = {}, allowMultiple, imagesOnly }) => {
|
||||
const config = { ...baseConfig, imagesOnly, ...instanceConfig };
|
||||
const multiple = allowMultiple === false ? false : !!config.multiple;
|
||||
const resolvedConfig = { ...config, multiple };
|
||||
const files = getFiles(value);
|
||||
|
||||
/**
|
||||
@ -122,9 +124,9 @@ async function init({ options = { config: {} }, handleInsert }) {
|
||||
* from the Uploadcare library will have a `state` method.
|
||||
*/
|
||||
if (files && !files.state) {
|
||||
files.then(result => openDialog(result, config, handleInsert));
|
||||
files.then(result => openDialog(result, resolvedConfig, handleInsert));
|
||||
} else {
|
||||
openDialog(files, config, handleInsert);
|
||||
openDialog(files, resolvedConfig, handleInsert);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
"build": "cross-env NODE_ENV=production webpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"common-tags": "^1.8.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -2,8 +2,10 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import styled from 'react-emotion';
|
||||
import { List } from 'immutable';
|
||||
import { Map, List } from 'immutable';
|
||||
import { once } from 'lodash';
|
||||
import uuid from 'uuid/v4';
|
||||
import { oneLine } from 'common-tags';
|
||||
import { lengths, components, buttons } from 'netlify-cms-ui-default';
|
||||
|
||||
const MAX_DISPLAY_LENGTH = 50;
|
||||
@ -64,6 +66,15 @@ function isMultiple(value) {
|
||||
return Array.isArray(value) || List.isList(value);
|
||||
}
|
||||
|
||||
const warnDeprecatedOptions = once(field =>
|
||||
console.warn(oneLine`
|
||||
Netlify CMS config: ${field.get('name')} field: property "options" has been deprecated for the
|
||||
${field.get('widget')} widget and will be removed in the next major release. Rather than
|
||||
\`field.options.media_library\`, apply media library options for this widget under
|
||||
\`field.media_library\`.
|
||||
`),
|
||||
);
|
||||
|
||||
export default function withFileControl({ forImage } = {}) {
|
||||
return class FileControl extends React.Component {
|
||||
static propTypes = {
|
||||
@ -126,12 +137,28 @@ export default function withFileControl({ forImage } = {}) {
|
||||
handleChange = e => {
|
||||
const { field, onOpenMediaLibrary, value } = this.props;
|
||||
e.preventDefault();
|
||||
let mediaLibraryFieldOptions;
|
||||
|
||||
/**
|
||||
* `options` hash as a general field property is deprecated, only used
|
||||
* when external media libraries were first introduced. Not to be
|
||||
* confused with `options` for the select widget, which serves a different
|
||||
* purpose.
|
||||
*/
|
||||
if (field.hasIn(['options', 'media_library'])) {
|
||||
warnDeprecatedOptions(field);
|
||||
mediaLibraryFieldOptions = field.getIn(['options', 'media_library'], Map());
|
||||
} else {
|
||||
mediaLibraryFieldOptions = field.get('media_library', Map());
|
||||
}
|
||||
|
||||
return onOpenMediaLibrary({
|
||||
controlID: this.controlID,
|
||||
forImage,
|
||||
privateUpload: field.get('private'),
|
||||
value,
|
||||
config: field.getIn(['options', 'media_library', 'config']),
|
||||
allowMultiple: !!mediaLibraryFieldOptions.get('allow_multiple', true),
|
||||
config: mediaLibraryFieldOptions.get('config'),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import cms from 'netlify-cms-core/src';
|
||||
import uploadcare from 'netlify-cms-media-library-uploadcare/src';
|
||||
import cloudinary from 'netlify-cms-media-library-cloudinary/src';
|
||||
|
||||
const { registerMediaLibrary } = cms;
|
||||
|
||||
registerMediaLibrary(uploadcare);
|
||||
registerMediaLibrary(cloudinary);
|
||||
|
84
website/content/docs/cloudinary.md
Normal file
84
website/content/docs/cloudinary.md
Normal file
@ -0,0 +1,84 @@
|
||||
---
|
||||
title: Cloudinary
|
||||
group: media
|
||||
weight: '10'
|
||||
---
|
||||
Cloudinary is a digital asset management platform with a broad feature set, including support for responsive image generation and url based image transformation. They also provide a powerful media library UI for managing assets, and tools for organizing your assets into a hierarchy.
|
||||
|
||||
The Cloudinary media library integration for Netlify CMS uses Cloudinary's own media library interface within Netlify CMS. To get started, you'll need a Cloudinary account and Netlify CMS 2.3.0 or greater.
|
||||
|
||||
## Creating a Cloudinary Account
|
||||
|
||||
You can [sign up for Cloudinary](https://cloudinary.com/users/register/free) for free. Once you're logged in, you'll need to retrieve your Cloud name and API key from the upper left corner of the Cloudinary console.
|
||||
|
||||
![Cloudinary console screenshot](/img/cloudinary-console-details.png)
|
||||
|
||||
## Connecting Cloudinary to Netlify CMS
|
||||
|
||||
To use the Cloudinary media library within Netlify CMS, you'll need to update your Netlify CMS configuration file with the information from your Cloudinary account:
|
||||
|
||||
```yml
|
||||
media_library:
|
||||
name: cloudinary
|
||||
config:
|
||||
cloud_name: your_cloud_name
|
||||
api_key: your_api_key
|
||||
```
|
||||
|
||||
## Netlify CMS configuration options
|
||||
|
||||
The following options are specific to the Netlify CMS integration for Cloudinary:
|
||||
|
||||
* **`output_filename_only`**: _(default: `false`)_\
|
||||
By default, the value provided for a selected image is a complete URL for the asset on Cloudinary's CDN. Setting `output_filename_only` to `true` will instead produce just the filename (e.g. `image.jpg`).
|
||||
* **`use_transformations`**: _(default: `true`)_\
|
||||
If `true`, uses derived url when available (the url will have image transformation segments included). Has no effect if `output_filename_only` is set to `true`.
|
||||
* **`use_secure_url`**: _(default: `true`)_\
|
||||
Controls whether an `http` or `https` URL is provided. Has no effect if `output_filename_only` is set to `true`.
|
||||
|
||||
## Cloudinary configuration options
|
||||
|
||||
The Cloudinary media library integration can be configured in two ways: globally and per field. Global options will be overridden by field options. All options are listed in Cloudinary's [media library documentation](https://cloudinary.com/documentation/media_library_widget#3_set_the_configuration_options), but only the following options are available or recommended for the Netlify CMS integration:
|
||||
|
||||
### Authentication
|
||||
|
||||
* `cloud_name`
|
||||
* `api_key`
|
||||
* `username` _\- pre-fills a username in the Cloudinary login form_
|
||||
|
||||
### Media library behavior
|
||||
|
||||
* `default_transformations` _\- only the first [image transformation](#image-transformations) is used, be sure to use the `Library` column transformation names from the_ [_transformation reference_]("https://cloudinary.com/documentation/image_transformation_reference")
|
||||
* `max_files`
|
||||
* `multiple` _\- has no impact on images inside the [markdown widget](/docs/widgets/#markdown)_
|
||||
|
||||
## Image transformations
|
||||
|
||||
The Cloudinary integration allows images to be transformed in two ways: directly within Netlify CMS, and separately from the CMS via Cloudinary's [dynamic URL's](https://cloudinary.com/documentation/image_transformations#delivering_media_assets_using_dynamic_urls). If you transform images within the Cloudinary media library, the transformed image URL will be output by default. This gives the editor complete freedom to make changes to the image output.
|
||||
|
||||
## Art direction and responsive images
|
||||
|
||||
If you prefer to provide art direction so that images are transformed in a specific way, or dynamically retrieve images based on viewport size, you can do so by providing your own base Cloudinary URL and only storing the asset filenames in your content:
|
||||
|
||||
1. Either globally or for specific fields, configure the Cloudinary extension to only output the asset filename:
|
||||
|
||||
```yml
|
||||
# global
|
||||
media_library:
|
||||
name: cloudinary
|
||||
output_filename_only: true
|
||||
|
||||
# field
|
||||
fields:
|
||||
- { name: image, widget: image, media_library: { output_filename_only: true } }
|
||||
```
|
||||
|
||||
2. Provide a dynamic URL in the site template where the image is used:
|
||||
|
||||
```hbs
|
||||
{{! handlebars example }}
|
||||
|
||||
<img src="https://res.cloudinary.com/<cloud_name>/<resource_type>/<type>/<version>/<transformations>/{{image}}"/>
|
||||
```
|
||||
|
||||
Your dynamic URL can be formed conditionally to provide any desired transformations - please see Cloudinary's [image transformation reference](https://cloudinary.com/documentation/image_transformation_reference) for available transformations.
|
@ -73,9 +73,8 @@ For example:
|
||||
name: cover
|
||||
label: Cover Image
|
||||
widget: image
|
||||
options:
|
||||
media_library:
|
||||
config:
|
||||
multiple: true
|
||||
previewStep: false
|
||||
media_library:
|
||||
config:
|
||||
multiple: true
|
||||
previewStep: false
|
||||
```
|
||||
|
@ -7,13 +7,21 @@ The file widget allows editors to upload a file or select an existing one from t
|
||||
|
||||
- **Name:** `file`
|
||||
- **UI:** file picker button opens media gallery
|
||||
- **Data type:** file path string, based on `media_folder`/`public_folder` configuration
|
||||
- **Data type:** file path string
|
||||
- **Options:**
|
||||
- `default`: accepts a file path string; defaults to null
|
||||
- `media_library`: media library settings to apply when a media library is opened by the
|
||||
current widget
|
||||
- `allow_multiple`: _(default: `true`)_ when set to `false`, prevents multiple selection for any media library extension, but must be supported by the extension in use
|
||||
- `config`: a configuration object that will be passed directly to the media library being
|
||||
used - available options are determined by the library
|
||||
- **Example:**
|
||||
```yaml
|
||||
- label: "Manual PDF"
|
||||
name: "manual_pdf"
|
||||
widget: "file"
|
||||
default: "/uploads/general-manual.pdf"
|
||||
media_library:
|
||||
config:
|
||||
multiple: true
|
||||
```
|
||||
|
@ -7,11 +7,12 @@ The image widget allows editors to upload an image or select an existing one fro
|
||||
|
||||
- **Name:** `image`
|
||||
- **UI:** file picker button opens media gallery allowing image files (jpg, jpeg, webp, gif, png, bmp, tiff, svg) only; displays selected image thumbnail
|
||||
- **Data type:** file path string, based on `media_folder`/`public_folder` configuration
|
||||
- **Data type:** file path string
|
||||
- **Options:**
|
||||
- `default`: accepts a file path string; defaults to null
|
||||
- `media_library`: media library settings to apply when a media library is opened by the
|
||||
current widget
|
||||
- `allow_multiple`: _(default: `true`)_ when set to `false`, prevents multiple selection for any media library extension, but must be supported by the extension in use
|
||||
- `config`: a configuration object that will be passed directly to the media library being
|
||||
used - available options are determined by the library
|
||||
- **Example:**
|
||||
@ -22,6 +23,5 @@ The image widget allows editors to upload an image or select an existing one fro
|
||||
default: "/uploads/chocolate-dogecoin.jpg"
|
||||
media_library:
|
||||
config:
|
||||
publicKey: "demopublickey"
|
||||
multiple: true
|
||||
```
|
||||
|
Loading…
x
Reference in New Issue
Block a user