chore: remove external media library integrations
This commit is contained in:
@ -376,9 +376,7 @@ const testConfig: Config<RelationKitchenSinkPostField> = {
|
||||
label: 'Choose URL',
|
||||
widget: 'file',
|
||||
required: false,
|
||||
media_library: {
|
||||
choose_url: true,
|
||||
},
|
||||
choose_url: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -411,9 +409,7 @@ const testConfig: Config<RelationKitchenSinkPostField> = {
|
||||
label: 'Choose URL',
|
||||
widget: 'image',
|
||||
required: false,
|
||||
media_library: {
|
||||
choose_url: true,
|
||||
},
|
||||
choose_url: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -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<RootState, {}, AnyAction>, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const mediaLibrary = state.mediaLibrary.externalLibrary;
|
||||
if (mediaLibrary) {
|
||||
mediaLibrary.onClearControl?.({ id });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function removeMediaControl(id: string) {
|
||||
return (_dispatch: ThunkDispatch<RootState, {}, AnyAction>, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const mediaLibrary = state.mediaLibrary.externalLibrary;
|
||||
if (mediaLibrary) {
|
||||
mediaLibrary.onRemoveControl?.({ id });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function openMediaLibrary<EF extends BaseField = UnknownField>(
|
||||
payload: {
|
||||
controlID?: string;
|
||||
@ -85,15 +53,13 @@ export function openMediaLibrary<EF extends BaseField = UnknownField>(
|
||||
alt?: string;
|
||||
allowMultiple?: boolean;
|
||||
replaceIndex?: number;
|
||||
config?: Record<string, unknown>;
|
||||
config?: MediaLibraryConfig;
|
||||
collection?: Collection<EF>;
|
||||
field?: EF;
|
||||
insertOptions?: MediaLibrarInsertOptions;
|
||||
} = {},
|
||||
) {
|
||||
return (dispatch: ThunkDispatch<RootState, {}, AnyAction>, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const mediaLibrary = state.mediaLibrary.externalLibrary;
|
||||
return (dispatch: ThunkDispatch<RootState, {}, AnyAction>) => {
|
||||
const {
|
||||
controlID,
|
||||
value,
|
||||
@ -107,10 +73,6 @@ export function openMediaLibrary<EF extends BaseField = UnknownField>(
|
||||
insertOptions,
|
||||
} = payload;
|
||||
|
||||
if (mediaLibrary) {
|
||||
mediaLibrary.show({ id: controlID, value, config, allowMultiple, imagesOnly: forImage });
|
||||
}
|
||||
|
||||
dispatch(
|
||||
mediaLibraryOpened({
|
||||
controlID,
|
||||
@ -129,12 +91,7 @@ export function openMediaLibrary<EF extends BaseField = UnknownField>(
|
||||
}
|
||||
|
||||
export function closeMediaLibrary() {
|
||||
return (dispatch: ThunkDispatch<RootState, {}, AnyAction>, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const mediaLibrary = state.mediaLibrary.externalLibrary;
|
||||
if (mediaLibrary) {
|
||||
mediaLibrary.hide?.();
|
||||
}
|
||||
return (dispatch: ThunkDispatch<RootState, {}, AnyAction>) => {
|
||||
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<RootState, {}, AnyAction>, 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<string, unknown>;
|
||||
config?: MediaLibraryConfig;
|
||||
collection?: Collection;
|
||||
field?: Field;
|
||||
insertOptions?: MediaLibrarInsertOptions;
|
||||
@ -540,7 +502,7 @@ export async function waitForMediaLibraryToLoad(
|
||||
dispatch: ThunkDispatch<RootState, {}, AnyAction>,
|
||||
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
|
||||
|
@ -807,7 +807,7 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
|
||||
);
|
||||
return this.implementation.getMedia(
|
||||
folder,
|
||||
configState.config?.media_library_folder_support ?? false,
|
||||
configState.config?.media_library?.folder_support ?? false,
|
||||
mediaPath,
|
||||
);
|
||||
}),
|
||||
|
@ -117,7 +117,6 @@ export default class GitGateway implements BackendClass {
|
||||
api?: GitHubAPI | GitLabAPI | BitBucketAPI;
|
||||
branch: string;
|
||||
mediaFolder?: string;
|
||||
transformImages: boolean;
|
||||
gatewayUrl: string;
|
||||
netlifyLargeMediaURL: string;
|
||||
backendType: string | null;
|
||||
@ -141,8 +140,6 @@ export default class GitGateway implements BackendClass {
|
||||
this.config = config;
|
||||
this.branch = config.backend.branch?.trim() || 'main';
|
||||
this.mediaFolder = config.media_folder;
|
||||
const { use_large_media_transforms_in_media_library: transformImages = true } = config.backend;
|
||||
this.transformImages = transformImages;
|
||||
|
||||
const netlifySiteURL = localStorage.getItem('netlifySiteURL');
|
||||
this.apiUrl = getEndpoint(config.backend.identity_url || defaults.identity, netlifySiteURL);
|
||||
@ -440,7 +437,7 @@ export default class GitGateway implements BackendClass {
|
||||
rootURL: this.netlifyLargeMediaURL,
|
||||
makeAuthorizedRequest: this.requestFunction,
|
||||
patterns,
|
||||
transformImages: this.transformImages ? { nf_resize: 'fit', w: 560, h: 320 } : false,
|
||||
transformImages: false,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
@ -14,7 +14,6 @@ import './components/entry-editor/widgets';
|
||||
import ErrorBoundary from './components/ErrorBoundary';
|
||||
import addExtensions from './extensions';
|
||||
import { getPhrases } from './lib/phrases';
|
||||
import './mediaLibrary';
|
||||
import { selectLocale } from './reducers/selectors/config';
|
||||
import { store } from './store';
|
||||
|
||||
|
@ -9,10 +9,8 @@ import {
|
||||
changeDraftFieldValidation,
|
||||
} from '@staticcms/core/actions/entries';
|
||||
import {
|
||||
clearMediaControl as clearMediaControlAction,
|
||||
openMediaLibrary as openMediaLibraryAction,
|
||||
removeInsertedMedia as removeInsertedMediaAction,
|
||||
removeMediaControl as removeMediaControlAction,
|
||||
} from '@staticcms/core/actions/mediaLibrary';
|
||||
import { query as queryAction } from '@staticcms/core/actions/search';
|
||||
import useMemoCompare from '@staticcms/core/lib/hooks/useMemoCompare';
|
||||
@ -41,7 +39,6 @@ import type { ComponentType } from 'react';
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
|
||||
const EditorControl = ({
|
||||
clearMediaControl,
|
||||
collection,
|
||||
config: configState,
|
||||
entry,
|
||||
@ -57,7 +54,6 @@ const EditorControl = ({
|
||||
parentPath,
|
||||
query,
|
||||
removeInsertedMedia,
|
||||
removeMediaControl,
|
||||
t,
|
||||
value,
|
||||
forList = false,
|
||||
@ -169,10 +165,8 @@ const EditorControl = ({
|
||||
locale,
|
||||
mediaPaths,
|
||||
onChange: handleChangeDraftField,
|
||||
clearMediaControl,
|
||||
openMediaLibrary,
|
||||
removeInsertedMedia,
|
||||
removeMediaControl,
|
||||
path,
|
||||
query,
|
||||
t,
|
||||
@ -203,10 +197,8 @@ const EditorControl = ({
|
||||
locale,
|
||||
mediaPaths,
|
||||
handleChangeDraftField,
|
||||
clearMediaControl,
|
||||
openMediaLibrary,
|
||||
removeInsertedMedia,
|
||||
removeMediaControl,
|
||||
path,
|
||||
query,
|
||||
finalValue,
|
||||
@ -254,8 +246,6 @@ function mapStateToProps(state: RootState, ownProps: EditorControlOwnProps) {
|
||||
const mapDispatchToProps = {
|
||||
changeDraftField: changeDraftFieldAction,
|
||||
openMediaLibrary: openMediaLibraryAction,
|
||||
clearMediaControl: clearMediaControlAction,
|
||||
removeMediaControl: removeMediaControlAction,
|
||||
removeInsertedMedia: removeInsertedMediaAction,
|
||||
query: queryAction,
|
||||
};
|
||||
|
@ -484,7 +484,7 @@ const MediaLibrary: FC<TranslatedProps<MediaLibraryProps>> = ({
|
||||
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<TranslatedProps<MediaLibraryProps>> = ({
|
||||
placeholder={t('mediaLibrary.mediaLibraryModal.search')}
|
||||
disabled={!dynamicSearchActive && !hasFilteredFiles}
|
||||
/>
|
||||
{config?.media_library_folder_support ? (
|
||||
{config?.media_library?.folder_support ? (
|
||||
<div className="flex gap-1.5 items-center">
|
||||
<IconButton
|
||||
onClick={handleHome}
|
||||
@ -565,7 +565,7 @@ const MediaLibrary: FC<TranslatedProps<MediaLibraryProps>> = ({
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{config?.media_library_folder_support ? (
|
||||
{config?.media_library?.folder_support ? (
|
||||
<div
|
||||
className="
|
||||
flex
|
||||
|
@ -164,7 +164,7 @@ const MediaLibraryCardGrid: FC<MediaLibraryCardGridProps> = props => {
|
||||
overflow-hidden
|
||||
`,
|
||||
isDialog && 'rounded-b-lg',
|
||||
!config?.media_library_folder_support && 'pt-[20px]',
|
||||
!config?.media_library?.folder_support && 'pt-[20px]',
|
||||
)}
|
||||
style={{
|
||||
width,
|
||||
@ -180,7 +180,7 @@ const MediaLibraryCardGrid: FC<MediaLibraryCardGridProps> = props => {
|
||||
rowHeight={() => rowHeightWithGutter}
|
||||
width={width}
|
||||
height={
|
||||
height - (!config?.media_library_folder_support ? MEDIA_LIBRARY_PADDING : 0)
|
||||
height - (!config?.media_library?.folder_support ? MEDIA_LIBRARY_PADDING : 0)
|
||||
}
|
||||
itemData={
|
||||
{
|
||||
|
@ -79,7 +79,6 @@ export const LOAD_ASSET_FAILURE = 'LOAD_ASSET_FAILURE';
|
||||
// Media Library
|
||||
export const MEDIA_LIBRARY_OPEN = 'MEDIA_LIBRARY_OPEN';
|
||||
export const MEDIA_LIBRARY_CLOSE = 'MEDIA_LIBRARY_CLOSE';
|
||||
export const MEDIA_LIBRARY_CREATE = 'MEDIA_LIBRARY_CREATE';
|
||||
export const MEDIA_INSERT = 'MEDIA_INSERT';
|
||||
export const MEDIA_REMOVE_INSERTED = 'MEDIA_REMOVE_INSERTED';
|
||||
export const MEDIA_LOAD_REQUEST = 'MEDIA_LOAD_REQUEST';
|
||||
|
@ -11,7 +11,6 @@ export * from './backends';
|
||||
export * from './interface';
|
||||
export * from './lib';
|
||||
export { default as locales } from './locales';
|
||||
export * from './media-libraries';
|
||||
export * from './widgets';
|
||||
|
||||
const CMS = {
|
||||
|
@ -234,20 +234,6 @@ export type Collection<EF extends BaseField = UnknownField> =
|
||||
|
||||
export type Collections<EF extends BaseField = UnknownField> = Record<string, Collection<EF>>;
|
||||
|
||||
export interface MediaLibraryInstance {
|
||||
show: (args: {
|
||||
id?: string;
|
||||
value?: string | string[];
|
||||
config: Record<string, unknown>;
|
||||
allowMultiple?: boolean;
|
||||
imagesOnly?: boolean;
|
||||
}) => void;
|
||||
hide?: () => void;
|
||||
onClearControl?: (args: { id: string }) => void;
|
||||
onRemoveControl?: (args: { id: string }) => void;
|
||||
enableStandalone: () => boolean;
|
||||
}
|
||||
|
||||
export type MediaFile = BackendMediaFile & { key?: string };
|
||||
|
||||
export interface DisplayURLState {
|
||||
@ -281,13 +267,9 @@ export interface WidgetControlProps<T, F extends BaseField = UnknownField, EV =
|
||||
mediaPaths: Record<string, MediaPath>;
|
||||
onChange: (value: T | null | undefined) => void;
|
||||
// @deprecated Use useMediaInsert instead
|
||||
clearMediaControl: EditorControlProps['clearMediaControl'];
|
||||
// @deprecated Use useMediaInsert instead
|
||||
openMediaLibrary: EditorControlProps['openMediaLibrary'];
|
||||
// @deprecated Use useMediaInsert instead
|
||||
removeInsertedMedia: EditorControlProps['removeInsertedMedia'];
|
||||
// @deprecated Use useMediaInsert instead
|
||||
removeMediaControl: EditorControlProps['removeMediaControl'];
|
||||
i18n: I18nSettings | undefined;
|
||||
hasErrors: boolean;
|
||||
errors: FieldError[];
|
||||
@ -530,26 +512,16 @@ export type WidgetValueSerializer = {
|
||||
deserialize: (value: ValueOrNestedValue) => ValueOrNestedValue;
|
||||
};
|
||||
|
||||
export type MediaLibraryOptions = Record<string, unknown>;
|
||||
|
||||
export interface MediaLibraryInitOptions {
|
||||
options: Record<string, unknown> | undefined;
|
||||
handleInsert: (url: string | string[]) => void;
|
||||
}
|
||||
|
||||
export interface MediaLibraryExternalLibrary {
|
||||
name: string;
|
||||
config?: MediaLibraryOptions;
|
||||
init: ({ options, handleInsert }: MediaLibraryInitOptions) => Promise<MediaLibraryInstance>;
|
||||
export interface MediaLibraryConfig {
|
||||
max_file_size?: number;
|
||||
folder_support?: boolean;
|
||||
}
|
||||
|
||||
export interface MediaLibraryInternalOptions {
|
||||
allow_multiple?: boolean;
|
||||
choose_url?: boolean;
|
||||
}
|
||||
|
||||
export type MediaLibrary = MediaLibraryExternalLibrary | MediaLibraryInternalOptions;
|
||||
|
||||
export type BackendType = 'git-gateway' | 'github' | 'gitlab' | 'bitbucket' | 'test-repo' | 'proxy';
|
||||
|
||||
export type MapWidgetType = 'Point' | 'LineString' | 'Polygon';
|
||||
@ -579,9 +551,10 @@ export interface BaseField {
|
||||
}
|
||||
|
||||
export interface MediaField extends BaseField {
|
||||
media_library?: MediaLibrary;
|
||||
media_folder?: string;
|
||||
public_folder?: string;
|
||||
choose_url?: boolean;
|
||||
multiple?: boolean;
|
||||
}
|
||||
|
||||
export interface BooleanField extends BaseField {
|
||||
@ -774,7 +747,6 @@ export interface Backend {
|
||||
proxy_url?: string;
|
||||
large_media_url?: string;
|
||||
login?: boolean;
|
||||
use_large_media_transforms_in_media_library?: boolean;
|
||||
identity_url?: string;
|
||||
gateway_url?: string;
|
||||
auth_scope?: AuthScope;
|
||||
@ -810,14 +782,13 @@ export interface Config<EF extends BaseField = UnknownField> {
|
||||
media_folder?: string;
|
||||
public_folder?: string;
|
||||
media_folder_relative?: boolean;
|
||||
media_library?: MediaLibrary;
|
||||
media_library?: MediaLibraryConfig;
|
||||
load_config_file?: boolean;
|
||||
slug?: Slug;
|
||||
i18n?: I18nInfo;
|
||||
local_backend?: boolean | LocalBackend;
|
||||
editor?: EditorConfig;
|
||||
search?: boolean;
|
||||
media_library_folder_support?: boolean;
|
||||
}
|
||||
|
||||
export interface InitOptions<EF extends BaseField = UnknownField> {
|
||||
|
@ -36,7 +36,7 @@ export default function useMediaFiles(field?: MediaField, currentFolder?: string
|
||||
const backend = currentBackend(config);
|
||||
const files = await backend.getMedia(
|
||||
currentFolder,
|
||||
config.media_library_folder_support ?? false,
|
||||
config.media_library?.folder_support ?? false,
|
||||
config.public_folder
|
||||
? trim(currentFolder, '/').replace(trim(config.media_folder!), config.public_folder)
|
||||
: currentFolder,
|
||||
|
@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { openMediaLibrary, removeInsertedMedia } from '@staticcms/core/actions/mediaLibrary';
|
||||
import { selectConfig } from '@staticcms/core/reducers/selectors/config';
|
||||
import { selectMediaPath } from '@staticcms/core/reducers/selectors/mediaLibrary';
|
||||
import { useAppDispatch, useAppSelector } from '@staticcms/core/store/hooks';
|
||||
|
||||
@ -28,20 +29,13 @@ export default function useMediaInsert<T extends string | string[], F extends Me
|
||||
|
||||
const { controlID, collection, field, forImage = false, insertOptions } = options;
|
||||
|
||||
const config = useAppSelector(selectConfig);
|
||||
|
||||
const finalControlID = useMemo(() => controlID ?? uuid(), [controlID]);
|
||||
const mediaPathSelector = useMemo(() => selectMediaPath(finalControlID), [finalControlID]);
|
||||
const mediaPath = useAppSelector(mediaPathSelector);
|
||||
const [selected, setSelected] = useState(false);
|
||||
|
||||
const mediaLibraryFieldOptions = useMemo(() => {
|
||||
return field?.media_library ?? {};
|
||||
}, [field?.media_library]);
|
||||
|
||||
const config = useMemo(
|
||||
() => ('config' in mediaLibraryFieldOptions ? mediaLibraryFieldOptions.config : undefined),
|
||||
[mediaLibraryFieldOptions],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selected && mediaPath && (mediaPath.path !== value.path || mediaPath.alt !== value.alt)) {
|
||||
setSelected(true);
|
||||
@ -63,7 +57,7 @@ export default function useMediaInsert<T extends string | string[], F extends Me
|
||||
alt: value.alt,
|
||||
replaceIndex,
|
||||
allowMultiple: false,
|
||||
config,
|
||||
config: config?.media_library,
|
||||
collection,
|
||||
field,
|
||||
insertOptions,
|
||||
@ -77,7 +71,7 @@ export default function useMediaInsert<T extends string | string[], F extends Me
|
||||
forImage,
|
||||
value.path,
|
||||
value.alt,
|
||||
config,
|
||||
config?.media_library,
|
||||
collection,
|
||||
field,
|
||||
insertOptions,
|
||||
|
@ -13,8 +13,6 @@ import type {
|
||||
EventListener,
|
||||
FieldPreviewComponent,
|
||||
LocalePhrasesRoot,
|
||||
MediaLibraryExternalLibrary,
|
||||
MediaLibraryOptions,
|
||||
ObjectValue,
|
||||
PreviewStyle,
|
||||
PreviewStyleOptions,
|
||||
@ -25,7 +23,7 @@ import type {
|
||||
Widget,
|
||||
WidgetOptions,
|
||||
WidgetParam,
|
||||
WidgetValueSerializer,
|
||||
WidgetValueSerializer
|
||||
} from '../interface';
|
||||
|
||||
export const allowedEvents = ['prePublish', 'postPublish', 'preSave', 'postSave'] as const;
|
||||
@ -45,7 +43,6 @@ interface Registry {
|
||||
icons: Record<string, CustomIcon>;
|
||||
additionalLinks: Record<string, AdditionalLink>;
|
||||
widgetValueSerializers: Record<string, WidgetValueSerializer>;
|
||||
mediaLibraries: (MediaLibraryExternalLibrary & { options: MediaLibraryOptions })[];
|
||||
locales: Record<string, LocalePhrasesRoot>;
|
||||
eventHandlers: typeof eventHandlers;
|
||||
previewStyles: PreviewStyle[];
|
||||
@ -66,7 +63,6 @@ const registry: Registry = {
|
||||
icons: {},
|
||||
additionalLinks: {},
|
||||
widgetValueSerializers: {},
|
||||
mediaLibraries: [],
|
||||
locales: {},
|
||||
eventHandlers,
|
||||
previewStyles: [],
|
||||
@ -88,8 +84,6 @@ export default {
|
||||
getWidgetValueSerializer,
|
||||
registerBackend,
|
||||
getBackend,
|
||||
registerMediaLibrary,
|
||||
getMediaLibrary,
|
||||
registerLocale,
|
||||
getLocale,
|
||||
registerEventListener,
|
||||
@ -306,25 +300,6 @@ export function getBackend<EF extends BaseField = UnknownField>(
|
||||
return registry.backends[name] as unknown as BackendInitializer<EF>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Media Libraries
|
||||
*/
|
||||
export function registerMediaLibrary(
|
||||
mediaLibrary: MediaLibraryExternalLibrary,
|
||||
options: MediaLibraryOptions = {},
|
||||
) {
|
||||
if (registry.mediaLibraries.find(ml => mediaLibrary.name === ml.name)) {
|
||||
throw new Error(`A media library named ${mediaLibrary.name} has already been registered.`);
|
||||
}
|
||||
registry.mediaLibraries.push({ ...mediaLibrary, options });
|
||||
}
|
||||
|
||||
export function getMediaLibrary(
|
||||
name: string,
|
||||
): (MediaLibraryExternalLibrary & { options: MediaLibraryOptions }) | undefined {
|
||||
return registry.mediaLibraries.find(ml => ml.name === name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event Handlers
|
||||
*/
|
||||
|
@ -1,130 +0,0 @@
|
||||
import pick from 'lodash/pick';
|
||||
|
||||
import { loadScript } from '@staticcms/core/lib/util';
|
||||
|
||||
import type { MediaLibraryInitOptions, MediaLibraryInstance } from '@staticcms/core/interface';
|
||||
|
||||
interface GetAssetOptions {
|
||||
use_secure_url: boolean;
|
||||
use_transformations: boolean;
|
||||
output_filename_only: boolean;
|
||||
}
|
||||
|
||||
const defaultOptions = {
|
||||
use_secure_url: true,
|
||||
use_transformations: true,
|
||||
output_filename_only: false,
|
||||
};
|
||||
/**
|
||||
* This configuration hash cannot be overridden, 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: '1003',
|
||||
};
|
||||
|
||||
const defaultConfig = {
|
||||
multiple: false,
|
||||
};
|
||||
|
||||
interface CloudinaryAsset {
|
||||
public_id: string;
|
||||
format: string;
|
||||
secure_url: string;
|
||||
url: string;
|
||||
derived?: [
|
||||
{
|
||||
secure_url: string;
|
||||
url: string;
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
cloudinary: {
|
||||
createMediaLibrary: (
|
||||
config: Record<string, unknown>,
|
||||
handlers: { insertHandler: (data: { assets: CloudinaryAsset[] }) => void },
|
||||
) => {
|
||||
show: (config: Record<string, unknown>) => void;
|
||||
hide: () => void;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getAssetUrl(
|
||||
asset: CloudinaryAsset,
|
||||
{ use_secure_url, use_transformations, output_filename_only }: GetAssetOptions,
|
||||
): string {
|
||||
/**
|
||||
* 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,
|
||||
}: MediaLibraryInitOptions): Promise<MediaLibraryInstance> {
|
||||
/**
|
||||
* Configuration is specific to Cloudinary, while options are specific to this
|
||||
* integration.
|
||||
*/
|
||||
const { config = {}, ...integrationOptions } = options ?? {};
|
||||
const providedConfig = config as { multiple?: boolean };
|
||||
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');
|
||||
|
||||
function insertHandler(data: { assets: CloudinaryAsset[] }) {
|
||||
const assets = data.assets.map(asset => getAssetUrl(asset, resolvedOptions));
|
||||
handleInsert(providedConfig.multiple || assets.length > 1 ? 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({ ...cloudinaryBehaviorConfig, ...instanceConfig });
|
||||
},
|
||||
hide: () => mediaLibrary.hide(),
|
||||
enableStandalone: () => true,
|
||||
};
|
||||
}
|
||||
|
||||
const cloudinaryMediaLibrary = { name: 'cloudinary', init };
|
||||
|
||||
export const StaticMediaLibraryCloudinary = cloudinaryMediaLibrary;
|
||||
export default cloudinaryMediaLibrary;
|
@ -1,2 +0,0 @@
|
||||
export { default as MediaLibraryCloudinary } from './cloudinary';
|
||||
export { default as MediaLibraryUploadcare } from './uploadcare';
|
@ -1,232 +0,0 @@
|
||||
import uploadcare from 'uploadcare-widget';
|
||||
import uploadcareTabEffects from 'uploadcare-widget-tab-effects';
|
||||
|
||||
import type { MediaLibraryInitOptions, MediaLibraryInstance } from '@staticcms/core/interface';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
UPLOADCARE_PUBLIC_KEY: string;
|
||||
UPLOADCARE_LIVE: boolean;
|
||||
UPLOADCARE_MANUAL_START: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
window.UPLOADCARE_LIVE = false;
|
||||
window.UPLOADCARE_MANUAL_START = true;
|
||||
|
||||
const USER_AGENT = 'StaticCms-Uploadcare-MediaLibrary';
|
||||
const CDN_BASE_URL = 'https://ucarecdn.com';
|
||||
|
||||
/**
|
||||
* Default Uploadcare widget configuration, can be overridden via config.yml.
|
||||
*/
|
||||
const defaultConfig = {
|
||||
previewStep: true,
|
||||
integration: USER_AGENT,
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine whether an array of urls represents an unaltered set of Uploadcare
|
||||
* group urls. If they've been changed or any are missing, a new group will need
|
||||
* to be created to represent the current values.
|
||||
*/
|
||||
function isFileGroup(files: string[]) {
|
||||
const basePatternString = `~${files.length}/nth/`;
|
||||
|
||||
function mapExpression(_val: string, idx: number) {
|
||||
return new RegExp(`${basePatternString}${idx}/$`);
|
||||
}
|
||||
|
||||
const expressions = Array.from({ length: files.length }, mapExpression);
|
||||
return expressions.every(exp => files.some(url => exp.test(url)));
|
||||
}
|
||||
|
||||
export interface UploadcareFileGroupInfo {
|
||||
cdnUrl: string;
|
||||
name: string;
|
||||
isImage: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fileGroupInfo object wrapped in a promise-like object.
|
||||
*/
|
||||
function getFileGroup(files: string[]): Promise<UploadcareFileGroupInfo> {
|
||||
/**
|
||||
* Capture the group id from the first file in the files array.
|
||||
*/
|
||||
const groupId = new RegExp(`^.+/([^/]+~${files.length})/nth/`).exec(files[0])?.[1];
|
||||
|
||||
/**
|
||||
* The `openDialog` method handles the jQuery promise object returned by
|
||||
* `fileFrom`, but requires the promise returned by `loadFileGroup` to provide
|
||||
* the result of it's `done` method.
|
||||
*/
|
||||
return new Promise(resolve => uploadcare.loadFileGroup(groupId).done(group => resolve(group)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a url or array/List of urls to Uploadcare file objects wrapped in
|
||||
* promises, or Uploadcare groups when possible. Output is wrapped in a promise
|
||||
* because the value we're returning may be a promise that we created.
|
||||
*/
|
||||
function getFiles(
|
||||
value: string[] | string | undefined,
|
||||
): Promise<UploadcareFileGroupInfo | UploadcareFileGroupInfo[]> | null {
|
||||
if (Array.isArray(value)) {
|
||||
return isFileGroup(value) ? getFileGroup(value) : Promise.all(value.map(val => getFile(val)));
|
||||
}
|
||||
return value && typeof value === 'string' ? getFile(value) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a single url to an Uploadcare file object wrapped in a promise-like
|
||||
* object. Group urls that get passed here were not a part of a complete and
|
||||
* untouched group, so they'll be uploaded as new images (only way to do it).
|
||||
*/
|
||||
function getFile(url: string): Promise<UploadcareFileGroupInfo> {
|
||||
const groupPattern = /~\d+\/nth\/\d+\//;
|
||||
const uploaded = url.startsWith(CDN_BASE_URL) && !groupPattern.test(url);
|
||||
return uploadcare.fileFrom(uploaded ? 'uploaded' : 'url', url);
|
||||
}
|
||||
|
||||
interface OpenDialogOptions {
|
||||
files:
|
||||
| UploadcareFileGroupInfo
|
||||
| UploadcareFileGroupInfo[]
|
||||
| Promise<UploadcareFileGroupInfo | UploadcareFileGroupInfo[]>
|
||||
| null;
|
||||
config: {
|
||||
multiple: boolean;
|
||||
imagesOnly: boolean;
|
||||
previewStep: boolean;
|
||||
integration: string;
|
||||
};
|
||||
handleInsert: (url: string | string[]) => void;
|
||||
settings: {
|
||||
defaultOperations?: string;
|
||||
autoFilename?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the standalone dialog. A single instance is created and destroyed for
|
||||
* each use.
|
||||
*/
|
||||
function openDialog({ files, config, handleInsert, settings = {} }: OpenDialogOptions) {
|
||||
if (!files) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.defaultOperations && !settings.defaultOperations.startsWith('/')) {
|
||||
console.warn(
|
||||
'Uploadcare default operations should start with `/`. Example: `/preview/-/resize/100x100/image.png`',
|
||||
);
|
||||
}
|
||||
|
||||
function buildUrl(fileInfo: UploadcareFileGroupInfo) {
|
||||
const { cdnUrl, name, isImage } = fileInfo;
|
||||
|
||||
let url =
|
||||
isImage && settings.defaultOperations ? `${cdnUrl}-${settings.defaultOperations}` : cdnUrl;
|
||||
const filenameDefined = !url.endsWith('/');
|
||||
|
||||
if (!filenameDefined && settings.autoFilename) {
|
||||
url = url + name;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
uploadcare.openDialog(files, config).done(({ promise, files }) => {
|
||||
const isGroup = Boolean(files);
|
||||
|
||||
return promise().then(info => {
|
||||
if (isGroup) {
|
||||
return Promise.all(
|
||||
files().map(promise => promise.then(fileInfo => buildUrl(fileInfo))),
|
||||
).then(urls => handleInsert(urls));
|
||||
} else {
|
||||
handleInsert(buildUrl(info));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization function will only run once, returns an API object for Simple
|
||||
* CMS to call methods on.
|
||||
*/
|
||||
async function init({
|
||||
options = { config: {}, settings: {} },
|
||||
handleInsert,
|
||||
}: MediaLibraryInitOptions): Promise<MediaLibraryInstance> {
|
||||
const { publicKey, ...globalConfig } = options.config as {
|
||||
publicKey: string;
|
||||
} & Record<string, unknown>;
|
||||
const baseConfig = { ...defaultConfig, ...globalConfig };
|
||||
|
||||
window.UPLOADCARE_PUBLIC_KEY = publicKey;
|
||||
|
||||
/**
|
||||
* Register the effects tab by default because the effects tab is awesome. Can
|
||||
* be disabled via config.
|
||||
*/
|
||||
uploadcare.registerTab('preview', uploadcareTabEffects);
|
||||
|
||||
return {
|
||||
/**
|
||||
* 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 = {}, allowMultiple, imagesOnly = false }) => {
|
||||
const config = { ...baseConfig, imagesOnly, ...instanceConfig } as {
|
||||
imagesOnly: boolean;
|
||||
previewStep: boolean;
|
||||
integration: string;
|
||||
multiple?: boolean;
|
||||
};
|
||||
const multiple = allowMultiple === false ? false : Boolean(config.multiple);
|
||||
const resolvedConfig = { ...config, multiple };
|
||||
const files = getFiles(value);
|
||||
|
||||
/**
|
||||
* Resolve the promise only if it's ours. Only the jQuery promise objects
|
||||
* from the Uploadcare library will have a `state` method.
|
||||
*/
|
||||
if (files && !('state' in files)) {
|
||||
return files.then(result =>
|
||||
openDialog({
|
||||
files: result,
|
||||
config: resolvedConfig,
|
||||
settings: options.settings as Record<string, unknown>,
|
||||
handleInsert,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return openDialog({
|
||||
files,
|
||||
config: resolvedConfig,
|
||||
settings: options.settings as Record<string, unknown>,
|
||||
handleInsert,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Uploadcare doesn't provide a "media library" widget for viewing and
|
||||
* selecting existing files, so we return `false` here so Static CMS only
|
||||
* opens the Uploadcare widget when called from an editor control. This
|
||||
* results in the "Media" button in the global nav being hidden.
|
||||
*/
|
||||
enableStandalone: () => 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;
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
@ -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<string, MediaPath>;
|
||||
displayURLs: Record<string, MediaLibraryDisplayURL>;
|
||||
externalLibrary?: MediaLibraryInstance;
|
||||
controlID?: string;
|
||||
page?: number;
|
||||
files?: MediaFile[];
|
||||
config: Record<string, unknown>;
|
||||
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,
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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<BaseField>,
|
||||
field,
|
||||
});
|
||||
|
@ -45,14 +45,7 @@ const MediaPopover = <T extends FileOrImageField | MarkdownField>({
|
||||
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?.();
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user