From 56dc4a1d64e91412b9579bb138219832558d4ec3 Mon Sep 17 00:00:00 2001 From: Daniel Lautzenheiser Date: Wed, 12 Apr 2023 22:27:15 -0400 Subject: [PATCH] chore: remove external media library integrations --- packages/core/src/__tests__/testConfig.ts | 8 +- packages/core/src/actions/mediaLibrary.ts | 65 +---- packages/core/src/backend.ts | 2 +- .../backends/git-gateway/implementation.tsx | 5 +- packages/core/src/bootstrap.tsx | 1 - .../editor-control-pane/EditorControl.tsx | 10 - .../media-library/common/MediaLibrary.tsx | 6 +- .../common/MediaLibraryCardGrid.tsx | 4 +- packages/core/src/constants.ts | 1 - packages/core/src/index.ts | 1 - packages/core/src/interface.ts | 41 +-- packages/core/src/lib/hooks/useMediaFiles.ts | 2 +- packages/core/src/lib/hooks/useMediaInsert.ts | 16 +- packages/core/src/lib/registry.ts | 27 +- .../src/media-libraries/cloudinary/index.ts | 130 ---------- packages/core/src/media-libraries/index.tsx | 2 - .../src/media-libraries/uploadcare/index.ts | 232 ----------------- packages/core/src/mediaLibrary.ts | 52 ---- packages/core/src/reducers/mediaLibrary.ts | 13 +- .../widgets/file/__test__/FileControl.spec.ts | 8 +- .../core/src/widgets/file/withFileControl.tsx | 37 +-- .../plate/components/common/MediaPopover.tsx | 9 +- packages/core/test/data/widgets.mock.ts | 4 - packages/docs/content/docs/beta-features.mdx | 15 +- packages/docs/content/docs/cloudinary.mdx | 235 ------------------ .../content/docs/configuration-options.mdx | 54 ++-- .../docs/content/docs/migration-guide-v2.mdx | 60 ++++- .../docs/content/docs/netlify-large-media.mdx | 31 --- packages/docs/content/docs/uploadcare.mdx | 121 --------- packages/docs/content/menu.json | 4 - 30 files changed, 139 insertions(+), 1057 deletions(-) delete mode 100644 packages/core/src/media-libraries/cloudinary/index.ts delete mode 100644 packages/core/src/media-libraries/index.tsx delete mode 100644 packages/core/src/media-libraries/uploadcare/index.ts delete mode 100644 packages/core/src/mediaLibrary.ts delete mode 100644 packages/docs/content/docs/cloudinary.mdx delete mode 100644 packages/docs/content/docs/netlify-large-media.mdx delete mode 100644 packages/docs/content/docs/uploadcare.mdx diff --git a/packages/core/src/__tests__/testConfig.ts b/packages/core/src/__tests__/testConfig.ts index 645f26f4..070d8bd5 100644 --- a/packages/core/src/__tests__/testConfig.ts +++ b/packages/core/src/__tests__/testConfig.ts @@ -376,9 +376,7 @@ const testConfig: Config = { label: 'Choose URL', widget: 'file', required: false, - media_library: { - choose_url: true, - }, + choose_url: true, }, ], }, @@ -411,9 +409,7 @@ const testConfig: Config = { label: 'Choose URL', widget: 'image', required: false, - media_library: { - choose_url: true, - }, + choose_url: true, }, ], }, diff --git a/packages/core/src/actions/mediaLibrary.ts b/packages/core/src/actions/mediaLibrary.ts index 03e27ef1..d86c2d73 100644 --- a/packages/core/src/actions/mediaLibrary.ts +++ b/packages/core/src/actions/mediaLibrary.ts @@ -9,7 +9,6 @@ import { MEDIA_DISPLAY_URL_SUCCESS, MEDIA_INSERT, MEDIA_LIBRARY_CLOSE, - MEDIA_LIBRARY_CREATE, MEDIA_LIBRARY_OPEN, MEDIA_LOAD_FAILURE, MEDIA_LOAD_REQUEST, @@ -40,43 +39,12 @@ import type { ImplementationMediaFile, MediaFile, MediaLibrarInsertOptions, - MediaLibraryInstance, + MediaLibraryConfig, UnknownField, } from '../interface'; import type { RootState } from '../store'; import type AssetProxy from '../valueObjects/AssetProxy'; -export function createMediaLibrary(instance: MediaLibraryInstance) { - const api = { - show: instance.show || (() => undefined), - hide: instance.hide || (() => undefined), - onClearControl: instance.onClearControl || (() => undefined), - onRemoveControl: instance.onRemoveControl || (() => undefined), - enableStandalone: instance.enableStandalone || (() => undefined), - }; - return { type: MEDIA_LIBRARY_CREATE, payload: api } as const; -} - -export function clearMediaControl(id: string) { - return (_dispatch: ThunkDispatch, getState: () => RootState) => { - const state = getState(); - const mediaLibrary = state.mediaLibrary.externalLibrary; - if (mediaLibrary) { - mediaLibrary.onClearControl?.({ id }); - } - }; -} - -export function removeMediaControl(id: string) { - return (_dispatch: ThunkDispatch, getState: () => RootState) => { - const state = getState(); - const mediaLibrary = state.mediaLibrary.externalLibrary; - if (mediaLibrary) { - mediaLibrary.onRemoveControl?.({ id }); - } - }; -} - export function openMediaLibrary( payload: { controlID?: string; @@ -85,15 +53,13 @@ export function openMediaLibrary( alt?: string; allowMultiple?: boolean; replaceIndex?: number; - config?: Record; + config?: MediaLibraryConfig; collection?: Collection; field?: EF; insertOptions?: MediaLibrarInsertOptions; } = {}, ) { - return (dispatch: ThunkDispatch, getState: () => RootState) => { - const state = getState(); - const mediaLibrary = state.mediaLibrary.externalLibrary; + return (dispatch: ThunkDispatch) => { const { controlID, value, @@ -107,10 +73,6 @@ export function openMediaLibrary( insertOptions, } = payload; - if (mediaLibrary) { - mediaLibrary.show({ id: controlID, value, config, allowMultiple, imagesOnly: forImage }); - } - dispatch( mediaLibraryOpened({ controlID, @@ -129,12 +91,7 @@ export function openMediaLibrary( } export function closeMediaLibrary() { - return (dispatch: ThunkDispatch, getState: () => RootState) => { - const state = getState(); - const mediaLibrary = state.mediaLibrary.externalLibrary; - if (mediaLibrary) { - mediaLibrary.hide?.(); - } + return (dispatch: ThunkDispatch) => { dispatch(mediaLibraryClosed()); }; } @@ -178,7 +135,12 @@ export function removeInsertedMedia(controlID: string) { } export function loadMedia( - opts: { delay?: number; query?: string; page?: number; currentFolder?: string } = {}, + opts: { + delay?: number; + query?: string; + page?: number; + currentFolder?: string; + } = {}, ) { const { delay = 0, page = 1, currentFolder } = opts; return async (dispatch: ThunkDispatch, getState: () => RootState) => { @@ -193,7 +155,7 @@ export function loadMedia( function loadFunction() { return backend - .getMedia(currentFolder, config?.media_library_folder_support ?? false) + .getMedia(currentFolder, config?.media_library?.folder_support ?? false) .then(files => dispatch(mediaLoaded(files))) .catch((error: { status?: number }) => { console.error(error); @@ -446,7 +408,7 @@ function mediaLibraryOpened(payload: { alt?: string; replaceIndex?: number; allowMultiple?: boolean; - config?: Record; + config?: MediaLibraryConfig; collection?: Collection; field?: Field; insertOptions?: MediaLibrarInsertOptions; @@ -540,7 +502,7 @@ export async function waitForMediaLibraryToLoad( dispatch: ThunkDispatch, state: RootState, ) { - if (state.mediaLibrary.isLoading !== false && !state.mediaLibrary.externalLibrary) { + if (state.mediaLibrary.isLoading !== false) { await waitUntilWithTimeout(dispatch, resolve => ({ predicate: ({ type }) => type === MEDIA_LOAD_SUCCESS || type === MEDIA_LOAD_FAILURE, run: () => resolve(), @@ -583,7 +545,6 @@ export async function getMediaDisplayURL( } export type MediaLibraryAction = ReturnType< - | typeof createMediaLibrary | typeof mediaLibraryOpened | typeof mediaLibraryClosed | typeof mediaInserted diff --git a/packages/core/src/backend.ts b/packages/core/src/backend.ts index 821f6383..d8bc1955 100644 --- a/packages/core/src/backend.ts +++ b/packages/core/src/backend.ts @@ -807,7 +807,7 @@ export class Backend> = ({ px-5 pt-4 `, - config?.media_library_folder_support && + config?.media_library?.folder_support && ` pb-4 border-b @@ -517,7 +517,7 @@ const MediaLibrary: FC> = ({ placeholder={t('mediaLibrary.mediaLibraryModal.search')} disabled={!dynamicSearchActive && !hasFilteredFiles} /> - {config?.media_library_folder_support ? ( + {config?.media_library?.folder_support ? (
> = ({ ) : null}
- {config?.media_library_folder_support ? ( + {config?.media_library?.folder_support ? (
false, - }; -} - -/** - * The object that will be registered only needs a (default) name and `init` - * method. The `init` method returns the API object. - */ -const uploadcareMediaLibrary = { name: 'uploadcare', init }; - -export const StaticMediaLibraryUploadcare = uploadcareMediaLibrary; -export default uploadcareMediaLibrary; diff --git a/packages/core/src/mediaLibrary.ts b/packages/core/src/mediaLibrary.ts deleted file mode 100644 index 7148c842..00000000 --- a/packages/core/src/mediaLibrary.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * This module is currently concerned only with external media libraries - * registered via `registerMediaLibrary`. - */ -import once from 'lodash/once'; - -import { configFailed } from './actions/config'; -import { createMediaLibrary, insertMedia } from './actions/mediaLibrary'; -import { getMediaLibrary } from './lib/registry'; -import { store } from './store'; - -import type { MediaLibrary, MediaLibraryExternalLibrary } from './interface'; -import type { RootState } from './store'; - -function handleInsert(url: string | string[]) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return store.dispatch(insertMedia(url, undefined)); -} - -const initializeMediaLibrary = once(async function initializeMediaLibrary( - name: string, - { config }: MediaLibraryExternalLibrary, -) { - const lib = getMediaLibrary(name); - if (!lib) { - const err = new Error( - `Missing external media library '${name}'. Please use 'registerMediaLibrary' to register it.`, - ); - store.dispatch(configFailed(err)); - } else { - const instance = await lib.init({ options: config, handleInsert }); - store.dispatch(createMediaLibrary(instance)); - } -}); - -function isExternalMediaLibraryConfig( - config: MediaLibrary | undefined, -): config is MediaLibraryExternalLibrary { - return Boolean(config && 'name' in config); -} - -store.subscribe(() => { - const state = store.getState() as unknown as RootState; - if (state.config.config && isExternalMediaLibraryConfig(state.config.config.media_library)) { - const mediaLibraryName = state.config.config.media_library?.name; - if (mediaLibraryName && !state.mediaLibrary.externalLibrary) { - const mediaLibraryConfig = state.config.config.media_library; - initializeMediaLibrary(mediaLibraryName, mediaLibraryConfig); - } - } -}); diff --git a/packages/core/src/reducers/mediaLibrary.ts b/packages/core/src/reducers/mediaLibrary.ts index 0f017d85..3d563ea8 100644 --- a/packages/core/src/reducers/mediaLibrary.ts +++ b/packages/core/src/reducers/mediaLibrary.ts @@ -10,7 +10,6 @@ import { MEDIA_DISPLAY_URL_SUCCESS, MEDIA_INSERT, MEDIA_LIBRARY_CLOSE, - MEDIA_LIBRARY_CREATE, MEDIA_LIBRARY_OPEN, MEDIA_LOAD_FAILURE, MEDIA_LOAD_REQUEST, @@ -27,8 +26,8 @@ import type { Field, MediaFile, MediaLibrarInsertOptions, + MediaLibraryConfig, MediaLibraryDisplayURL, - MediaLibraryInstance, MediaPath, } from '../interface'; @@ -37,11 +36,10 @@ export type MediaLibraryState = { showMediaButton: boolean; controlMedia: Record; displayURLs: Record; - externalLibrary?: MediaLibraryInstance; controlID?: string; page?: number; files?: MediaFile[]; - config: Record; + config: MediaLibraryConfig; collection?: Collection; field?: Field; value?: string | string[]; @@ -72,13 +70,6 @@ function mediaLibrary( action: MediaLibraryAction, ): MediaLibraryState { switch (action.type) { - case MEDIA_LIBRARY_CREATE: - return { - ...state, - externalLibrary: action.payload, - showMediaButton: action.payload.enableStandalone(), - }; - case MEDIA_LIBRARY_OPEN: { const { controlID, diff --git a/packages/core/src/widgets/file/__test__/FileControl.spec.ts b/packages/core/src/widgets/file/__test__/FileControl.spec.ts index c7b02028..afe6efad 100644 --- a/packages/core/src/widgets/file/__test__/FileControl.spec.ts +++ b/packages/core/src/widgets/file/__test__/FileControl.spec.ts @@ -121,7 +121,7 @@ describe('File Control', () => { it('should show only the choose upload and choose url buttons by default when choose url is true', () => { const { getByTestId, queryByTestId } = renderControl({ label: 'I am a label', - field: { ...mockFileField, media_library: { choose_url: true } }, + field: { ...mockFileField, choose_url: true }, }); expect(getByTestId('choose-upload')).toBeInTheDocument(); @@ -147,7 +147,7 @@ describe('File Control', () => { it('should show the add/replace upload, replace url and remove buttons by there is a value and choose url is true', () => { const { getByTestId, queryByTestId } = renderControl({ label: 'I am a label', - field: { ...mockFileField, media_library: { choose_url: true } }, + field: { ...mockFileField, choose_url: true }, value: 'https://example.com/file.pdf', }); @@ -241,7 +241,7 @@ describe('File Control', () => { it('should show only the choose upload and choose url buttons by default when choose url is true', () => { const { getByTestId } = renderControl({ label: 'I am a label', - field: { ...mockFileField, media_library: { choose_url: true } }, + field: { ...mockFileField, choose_url: true }, disabled: true, }); @@ -263,7 +263,7 @@ describe('File Control', () => { it('should show the add/replace upload, replace url and remove buttons by there is a value and choose url is true', () => { const { getByTestId } = renderControl({ label: 'I am a label', - field: { ...mockFileField, media_library: { choose_url: true } }, + field: { ...mockFileField, choose_url: true }, value: 'https://example.com/file.pdf', disabled: true, }); diff --git a/packages/core/src/widgets/file/withFileControl.tsx b/packages/core/src/widgets/file/withFileControl.tsx index 7cba361f..d5f5e417 100644 --- a/packages/core/src/widgets/file/withFileControl.tsx +++ b/packages/core/src/widgets/file/withFileControl.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { memo, useCallback, useMemo, useRef, useState } from 'react'; import Button from '@staticcms/core/components/common/button/Button'; import Field from '@staticcms/core/components/common/field/Field'; @@ -8,6 +8,8 @@ import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert'; import useUUID from '@staticcms/core/lib/hooks/useUUID'; import { basename } from '@staticcms/core/lib/util'; import { isEmpty } from '@staticcms/core/lib/util/string.util'; +import { selectConfig } from '@staticcms/core/reducers/selectors/config'; +import { useAppSelector } from '@staticcms/core/store/hooks'; import SortableImage from './components/SortableImage'; import type { @@ -49,8 +51,6 @@ const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => { duplicate, onChange, openMediaLibrary, - clearMediaControl, - removeMediaControl, hasErrors, disabled, t, @@ -82,31 +82,13 @@ const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => { handleOnChange, ); - useEffect(() => { - return () => { - removeMediaControl(controlID); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const mediaLibraryFieldOptions = useMemo(() => { - return field.media_library ?? {}; - }, [field.media_library]); - - const config = useMemo( - () => ('config' in mediaLibraryFieldOptions ? mediaLibraryFieldOptions.config : undefined), - [mediaLibraryFieldOptions], - ); + const config = useAppSelector(selectConfig); const allowsMultiple = useMemo(() => { - return config?.multiple ?? false; - }, [config?.multiple]); + return field.multiple ?? false; + }, [field.multiple]); - const chooseUrl = useMemo( - () => - 'choose_url' in mediaLibraryFieldOptions && (mediaLibraryFieldOptions.choose_url ?? true), - [mediaLibraryFieldOptions], - ); + const chooseUrl = useMemo(() => field.choose_url ?? false, [field.choose_url]); const handleUrl = useCallback( (subject: 'image' | 'file') => (e: MouseEvent) => { @@ -123,10 +105,9 @@ const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => { (e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); - clearMediaControl(controlID); handleOnChange({ path: '' }); }, - [clearMediaControl, controlID, handleOnChange], + [handleOnChange], ); const onRemoveOne = useCallback( @@ -148,7 +129,7 @@ const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => { value: internalValue, replaceIndex: index, allowMultiple: false, - config, + config: config?.media_library, collection: collection as Collection, field, }); diff --git a/packages/core/src/widgets/markdown/plate/components/common/MediaPopover.tsx b/packages/core/src/widgets/markdown/plate/components/common/MediaPopover.tsx index e15977f7..1790aab8 100644 --- a/packages/core/src/widgets/markdown/plate/components/common/MediaPopover.tsx +++ b/packages/core/src/widgets/markdown/plate/components/common/MediaPopover.tsx @@ -45,14 +45,7 @@ const MediaPopover = ({ onMediaToggle?.(false); }); - const mediaLibraryFieldOptions = useMemo(() => { - return field.media_library ?? {}; - }, [field.media_library]); - - const chooseUrl = useMemo( - () => 'choose_url' in mediaLibraryFieldOptions && (mediaLibraryFieldOptions.choose_url ?? true), - [mediaLibraryFieldOptions], - ); + const chooseUrl = useMemo(() => field.choose_url ?? false, [field.choose_url]); const handleFocus = useCallback(() => { onFocus?.(); diff --git a/packages/core/test/data/widgets.mock.ts b/packages/core/test/data/widgets.mock.ts index 2e39c130..d04aea7f 100644 --- a/packages/core/test/data/widgets.mock.ts +++ b/packages/core/test/data/widgets.mock.ts @@ -20,10 +20,8 @@ export const createMockWidgetControlProps = < | 'data' | 'hasErrors' | 'onChange' - | 'clearMediaControl' | 'openMediaLibrary' | 'removeInsertedMedia' - | 'removeMediaControl' | 'query' | 't' > & @@ -73,10 +71,8 @@ export const createMockWidgetControlProps = < hidden: false, theme: 'light', onChange: jest.fn(), - clearMediaControl: jest.fn(), openMediaLibrary: jest.fn(), removeInsertedMedia: jest.fn(), - removeMediaControl: jest.fn(), query: jest.fn(), t: jest.fn(), ...extra, diff --git a/packages/docs/content/docs/beta-features.mdx b/packages/docs/content/docs/beta-features.mdx index fd2c3440..77163365 100644 --- a/packages/docs/content/docs/beta-features.mdx +++ b/packages/docs/content/docs/beta-features.mdx @@ -92,8 +92,7 @@ Example config: widget: 'image' default: '/uploads/chocolate-dogecoin.jpg' media_library: - config: - max_file_size: 512000 # in bytes, only for default media library + max_file_size: 512000 # in bytes, only for default media library ``` ```js @@ -103,9 +102,7 @@ Example config: widget: 'image', default: '/uploads/chocolate-dogecoin.jpg', media_library: { - config: { - max_file_size: 512000 // in bytes, only for default media library - }, + max_file_size: 512000 // in bytes, only for default media library }, }, ``` @@ -197,11 +194,3 @@ See [Gitea Backend](/docs/gitea-backend) for more information. Adds support for viewing subfolders and creating new subfolders in the media library, under your configured `media_folder`. See [Media Library Folders](/docs/configuration-options#media-library-folders) for more information. - -## External Media Library - -Using an external media library allows you to store your media files outside of your git backend. This is helpful if you are trying to store large media files. There are three external media libraries available in beta: - -- [Cloudinary](/docs/cloudinary) -- [Netlify Large Media](/docs/netlify-large-media) -- [Uploadcare](/docs/uploadcare) diff --git a/packages/docs/content/docs/cloudinary.mdx b/packages/docs/content/docs/cloudinary.mdx deleted file mode 100644 index 5a43438d..00000000 --- a/packages/docs/content/docs/cloudinary.mdx +++ /dev/null @@ -1,235 +0,0 @@ ---- -group: Media -title: Cloudinary -weight: 10 -beta: true ---- - -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 Static CMS uses Cloudinary's own media library interface within Static CMS. To get started, you'll need a Cloudinary account and Static 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.webp) - -## Connecting Cloudinary - -To use the Cloudinary media library within Static CMS, you'll need to update your Static CMS configuration file with the information from your Cloudinary account: - - -```yaml -media_library: - name: cloudinary - config: - cloud_name: your_cloud_name - api_key: your_api_key -``` - -```js -media_library: { - name: 'cloudinary', - config: { - cloud_name: 'your_cloud_name', - api_key: 'your_api_key' - }, -}, -``` - - - -**Note:** The user must be logged in to the Cloudinary account connected to the `api_key` used in your Static CMS configuration. - -### Security Considerations - -Although this setup exposes the `cloud_name` and `api_key` publicly via the `config` endpoint, this information is not sensitive. Any integration of the Cloudinary media library requires this information to be exposed publicly. To use this library or use the restricted Cloudinary API endpoints, the user must have access to the Cloudinary account login details or the `api_secret` associated with the `cloud_name` and `api_key`. - -## Static CMS configuration options - -The following options are specific to the Static 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`). This should be `true` if you will be directly embedding cloudinary transformation urls in page templates. Refer to [Inserting Cloudinary URL in page templates](#inserting-cloudinary-url-in-page-templates). -- **`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 following options are used to configure the media library. All options are listed in Cloudinary's [media library documentation](https://cloudinary.com/documentation/media_library_widget#3_set_the_configuration_options), but only options listed below are available or recommended for the Static CMS integration: - -### Authentication - -- `cloud_name` -- `api_key` - -### Media library behavior - -- `default_transformations` _\- only the first [image transformation](#image-transformations) is used, be sure to use the `SDK Parameter` column transformation names from the_ [_transformation reference_](https://cloudinary.com/documentation/image_transformation_reference) -- `max_files` _\- has no impact on images inside the [markdown widget](/docs/widgets/#markdown)_. Refer to [media library documentation](https://cloudinary.com/documentation/media_library_widget#3_set_the_configuration_options) for details on this property -- `multiple` _\- has no impact on images inside the [markdown widget](/docs/widgets/#markdown)_. Refer to [media library documentation](https://cloudinary.com/documentation/media_library_widget#3_set_the_configuration_options) for details on this property - -## Image transformations - -The Cloudinary integration allows images to be transformed in two ways: directly within Static CMS via [Cloudinary's Media Library](#transforming-images-via-media-library), and separately from Static CMS via Cloudinary's [dynamic URL's](https://cloudinary.com/documentation/image_transformations#delivering_media_assets_using_dynamic_urls) by [inserting cloudinary urls](#inserting-cloudinary-url-in-page-templates). - -### Transforming Images - -If you transform and insert images from 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. -There are two ways to configure image transformation via media library - [globally](#global-configuration) and per [field](#field-configuration). Global options will be overridden by field options. - -#### Global configuration - -Global configuration, which is meant to affect the Cloudinary widget at all times, can be provided -as seen below, under the primary `media_library` property. Settings applied here will affect every -instance of the Cloudinary widget. - - - -```yaml -# global -media_library: - name: cloudinary - output_filename_only: false - config: - default_transformations: - - - fetch_format: auto - width: 160 - quality: auto - crop: scale -``` - -```js -// global -media_library: { - name: 'cloudinary', - output_filename_only: false, - config: { - default_transformations: [ - [ - { - fetch_format: 'auto', - width: 160, - quality: 'auto', - crop: 'scale' - } - ], - ], - }, -}, -``` - - - -#### Field configuration - -Configuration can also be provided for individual fields that use the media library. The structure -is very similar to the global configuration, except the settings are added to an individual `field`. -For example: - - -```yaml -# field -fields: # The fields each document in this collection have - - label: 'Cover Image' - name: 'image' - widget: 'image' - required: false - tagtitle: '' - media_library: - config: - default_transformations: - - fetch_format: auto - width: 300 - quality: auto - crop: fill - effect: grayscale -``` - -```js -// field -fields: [ - { - label: 'Cover Image', - name: 'image', - widget: 'image', - required: false, - tagtitle: '', - media_library: { - config: { - default_transformations: [ - { - fetch_format: 'auto', - width: 300, - quality: 'auto', - crop: 'fill', - effect: 'grayscale', - }, - ], - }, - }, - }, -], -``` - - - -## Inserting Cloudinary URL in page templates - -If you prefer to provide 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: - -- Either globally or for specific fields, configure the Cloudinary extension to only output the asset filename - -**Global** - - -```yaml -# global -media_library: - name: cloudinary - output_filename_only: true -``` - -```js -// global -media_library: { - name: 'cloudinary', - output_filename_only: true, -}, -``` - - - -**Field** - - -```yaml -# field -media_library: - name: cloudinary - output_filename_only: true -``` - -```js -// field -media_library: { - name: 'cloudinary', - output_filename_only: true, -}, -``` - - - -- Provide a dynamic URL in the site template - -```handlebars -{{! handlebars example }} - -``` - -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. diff --git a/packages/docs/content/docs/configuration-options.mdx b/packages/docs/content/docs/configuration-options.mdx index 69ec8c74..ca5438fc 100644 --- a/packages/docs/content/docs/configuration-options.mdx +++ b/packages/docs/content/docs/configuration-options.mdx @@ -67,22 +67,7 @@ Based on the settings above, if a user used an image widget field called `avatar This setting can be set to an absolute URL e.g. `https://netlify.com/media` should you wish, however in general this is not advisable as content should have relative paths to other content. -## Media Library Folders - -The `media_library_folder_support` flag enables support for viewing subfolders and creating new subfolders in the media library, under your configured `media_folder`. - - -```yaml -media_library_folder_support: true -``` - -```js -media_library_folder_support: true, -``` - - - -## Media Library Integrations +## Media Library Media library integrations are configured via the `media_library` property, and its value should be an object with at least a `name` property. A `config` property can also be used for options that should be passed to the library in use. @@ -91,18 +76,39 @@ Media library integrations are configured via the `media_library` property, and ```yaml media_library: - name: uploadcare - config: - publicKey: demopublickey + choose_url: true, + max_file_size: 512000 + folder_support: true ``` ```js -media_library: { - name: 'uploadcare', - config: { - publicKey: 'demopublickey' +{ + media_library: { + choose_url: "true,", + max_file_size: 512000, + folder_support: true } -}, +} +``` + + + +### Media Library Folders + +The `folder_support` flag enables support for viewing subfolders and creating new subfolders in the media library, under your configured `media_folder`. + + +```yaml +media_library: + folder_support: true +``` + +```js +{ + media_library: { + folder_support: true; + } +} ``` diff --git a/packages/docs/content/docs/migration-guide-v2.mdx b/packages/docs/content/docs/migration-guide-v2.mdx index c5026eac..abd63298 100644 --- a/packages/docs/content/docs/migration-guide-v2.mdx +++ b/packages/docs/content/docs/migration-guide-v2.mdx @@ -190,6 +190,60 @@ collections: +## External Media Libraries + +External media integrations (Cloudinary, Netlify Large Media and Uploadcare) have been removed as part of an ongoing to effect to narrow the focus of Static CMS. With [Decap](https://decapcms.org/) (previously Netlify CMS) being supported again its not as critical for Static CMS to support every possible option. + +The external media integrations have been broken for sometime, and would require a lot of effort to get them back to the level already available in Decap. So, in order to focus our efforts on other features, it has been decided to remove all external media integrations. + +This brings with it some breaking changes for the `media_library` config property. + +**Old Config** + + +```yaml +media_library: + choose_url: true + config: + max_file_size: 512000 +``` + +```js +{ + media_library: { + choose_url: true, + config: { + max_file_size: 512000 + } + } +} +``` + + + +**New Config** + + +```yaml +media_library: + choose_url: true + max_file_size: 512000 +``` + +```js +{ + media_library: { + choose_url: true, + max_file_size: 512000 + } +} +``` + + + +Also the `clearMediaControl` and `removeMediaControl` widget control props have been removed as they were only used for the external media library integrations. + + ## Other Breaking Changes - [Card previews](/docs/custom-previews#collection-card-preview) now are only used for the card view. The `viewStyle` property has been removed. [Field previews](/docs/custom-previews#field-preview) can be used to change the table view. @@ -209,10 +263,8 @@ collections: In the Widget Control component property, the following properties have been deprecated. They will be removed in `v3.0.0`. -| Param | Type | Description | -| ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Param | Type | Description | +| ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | mediaPaths | object | Use [useMediaInsert](/docs/custom-widgets#interacting-with-the-media-library) instead. Key/value object of control IDs (passed to the media library) mapping to media paths | -| clearMediaControl | function | Use [useMediaInsert](/docs/custom-widgets#interacting-with-the-media-library) instead. Clears a control ID's value from the internal store | | openMediaLibrary | function | Use [useMediaInsert](/docs/custom-widgets#interacting-with-the-media-library) instead. Opens the media library popup. See [Open Media Library](#open-media-library) | | removeInsertedMedia | function | Use [useMediaInsert](/docs/custom-widgets#interacting-with-the-media-library) instead. Removes draft media for a give control ID | -| removeMediaControl | function | Use [useMediaInsert](/docs/custom-widgets#interacting-with-the-media-library) instead. Clears a control ID completely from the internal store | diff --git a/packages/docs/content/docs/netlify-large-media.mdx b/packages/docs/content/docs/netlify-large-media.mdx deleted file mode 100644 index b6b87c7a..00000000 --- a/packages/docs/content/docs/netlify-large-media.mdx +++ /dev/null @@ -1,31 +0,0 @@ ---- -group: Media -title: Netlify Large Media -weight: 20 -beta: true ---- - -[Netlify Large Media](https://www.netlify.com/features/large-media/) is a [Git LFS](https://git-lfs.github.com/) implementation for repositories connected to Netlify sites. This means that you can use Git to work with large asset files like images, audio, and video, without bloating your repository. It does this by replacing the asset files in your repository with text pointer files, then uploading the assets to the Netlify Large Media storage service. - -If you have a Netlify site with Large Media enabled, Static CMS will handle Large Media asset files seamlessly, in the same way as files stored directly in the repository. - -## Requirements - -To use Netlify Large Media with Static CMS, you will need to do the following: - -- Configure Static CMS to use the [Git Gateway backend with Netlify Identity](/docs/git-gateway-backend). -- Configure the Netlify site and connected repository to use Large Media, following the [Large Media docs on Netlify](https://www.netlify.com/docs/large-media/). - -When these are complete, you can use Static CMS as normal, and the configured asset files will automatically be handled by Netlify Large Media. - -## Image transformations - -All JPEG, PNG, and GIF files that are handled with Netlify Large Media also have access to Netlify's on-demand image transformation service. This service allows you to request an image to match the dimensions you specify in a query parameter added to the image URL. - -You can learn more about this feature in [Netlify's image transformation docs](https://www.netlify.com/docs/image-transformation/). - -### Media Gallery Thumbnails - -In repositories enabled with Netlify Large Media, Static CMS will use the image transformation query parameters to load thumbnail-sized images for the media gallery view. This makes images in the media gallery load significantly faster. - -**Note:** When using this option all tracked file types have to be imported into Large Media. For example if you track `*.jpg` but still have jpg-files that are not imported into Large Media the backend will throw an error. Check the [netlify docs](https://docs.netlify.com/large-media/setup/#migrate-files-from-git-history) on how to add previously committed files to Large Media. diff --git a/packages/docs/content/docs/uploadcare.mdx b/packages/docs/content/docs/uploadcare.mdx deleted file mode 100644 index 09a60b0a..00000000 --- a/packages/docs/content/docs/uploadcare.mdx +++ /dev/null @@ -1,121 +0,0 @@ ---- -group: Media -title: Uploadcare -weight: 30 -beta: true ---- - -Uploadcare is a sleek service that allows you to upload files without worrying about maintaining a growing collection — more of an asset store than a library. Just upload when you need to, and the files are hosted on their CDN. They provide image processing controls from simple cropping and rotation to filters and face detection, and a lot more. You can check out Uploadcare's full feature set on their [website](https://uploadcare.com/). - -The Uploadcare media library integration for Static CMS allows you to use Uploadcare as your media handler within Static CMS itself. It's available by default as of our 2.1.0 release, and works in tandem with the existing file and image widgets, so using it only requires creating an Uploadcare account and updating your Static CMS configuration. - -**Please make sure that Static CMS is updated to 2.1.0 or greater before proceeding.** - -## Creating an Uploadcare Account - -You can [sign up](https://uploadcare.com/accounts/signup/) for a free Uploadcare account to get started. Once you've signed up, go to your dashboard, select a project, and then select "API keys" from the menu on the left. The public key on the API keys page will be needed in your Static CMS configuration. For more info on getting your API key, visit their [walkthrough](https://uploadcare.com/docs/keys/). - -## Updating Static CMS Configuration - -The next and final step is updating your Static CMS configuration file: - -1. Add a `media_library` property at the same level as `media_folder`, with an object as it's value. -2. In the `media_library` object, add the name of the media player under `name`. -3. Add a `config` object under name with a `publicKey` property with your Uploadcare public key as it's value. - -Your `config` should now include something like this (except with a real API key): - - -```yaml -media_library: - name: uploadcare - config: - publicKey: YOUR_UPLOADCARE_PUBLIC_KEY -``` - -```js -media_library: { - name: 'uploadcare', - config: { - publicKey: 'YOUR_UPLOADCARE_PUBLIC_KEY', - }, -}, -``` - - -Once you've finished updating your Static CMS configuration, the Uploadcare widget will appear when using the image or file widgets. - -**Note:** You'll need to [register the media libraries yourself](/blog/2019/07/netlify-cms-gatsby-plugin-4-0-0#using-media-libraries-with-netlify-cms-app). - -## Configuring the Uploadcare Widget - -The Uploadcare widget can be configured with settings that are outlined [in their docs](https://uploadcare.com/docs/file_uploads/widget/options/). The widget itself accepts configuration through global variables and data properties on HTML elements, but with Static CMS you can pass configuration options directly through your `config`. - -**Note:** all default values described in Uploadcare's documentation also apply in the Static CMS integration, except for `previewStep`, which is set to `true`. This was done because the preview step provides helpful information like upload status, and provides access to image editing controls. This option can be disabled through the configuration options below. - -### Global configuration - -Global configuration, which is meant to affect the Uploadcare widget at all times, can be provided as seen above, under the primary `media_library` property. Settings applied here will affect every instance of the Uploadcare widget. - -## Field configuration - -Configuration can also be provided for individual fields that use the media library. The structure is very similar to the global configuration, except the settings are added to an individual `field`. For example: - - -```yaml -fields: - name: cover - label: Cover Image - widget: image - media_library: - config: - multiple: true - previewStep: false -``` - -```js -fields: { - name: 'cover', - label: 'Cover Image', - widget: 'image', - media_library: { - config: { - multiple: true, - previewStep: false, - }, - }, -}, -``` - - -## Integration settings - -There are several settings that control the behavior of integration with the widget. - -* `autoFilename` (`boolean`) - specify whether to add a filename to the end of the url. Example: `http://ucarecdn.com/:uuid/filename.png` -* `defaultOperations` (`string`) - specify a string added at the end of the url. This could be useful to apply a set of CDN operations to each image, for example resizing or compression. All the possible operations are listed [here](https://uploadcare.com/docs/api_reference/cdn/). - - -```yaml -media_library: - name: uploadcare - config: - publicKey: YOUR_UPLOADCARE_PUBLIC_KEY - settings: - autoFiletitle: true - defaultOperations: '/resize/800x600/' -``` - -```js -media_library: { - name: 'uploadcare', - config: { - publicKey: 'YOUR_UPLOADCARE_PUBLIC_KEY', - }, - settings: { - autoFiletitle: true, - defaultOperations: '/resize/800x600/', - }, -}, -``` - diff --git a/packages/docs/content/menu.json b/packages/docs/content/menu.json index f90e89b8..be377227 100644 --- a/packages/docs/content/menu.json +++ b/packages/docs/content/menu.json @@ -21,10 +21,6 @@ "name": "Widgets", "title": "Widgets" }, - { - "name": "Media", - "title": "Media" - }, { "name": "Customization", "title": "Customizing Static CMS"