@wordpress/media-utils 5.43.0 → 5.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/build/components/media-upload-modal/index.cjs +44 -35
- package/build/components/media-upload-modal/index.cjs.map +2 -2
- package/build/utils/validate-mime-type.cjs +1 -1
- package/build/utils/validate-mime-type.cjs.map +2 -2
- package/build-module/components/media-upload-modal/index.mjs +44 -35
- package/build-module/components/media-upload-modal/index.mjs.map +2 -2
- package/build-module/utils/validate-mime-type.mjs +1 -1
- package/build-module/utils/validate-mime-type.mjs.map +2 -2
- package/build-types/components/media-upload-modal/index.d.ts.map +1 -1
- package/package.json +17 -16
- package/src/components/media-upload-modal/index.tsx +53 -38
- package/src/utils/test/validate-mime-type.ts +12 -0
- package/src/utils/validate-mime-type.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -42,6 +42,7 @@ var import_data = require("@wordpress/data");
|
|
|
42
42
|
var import_components = require("@wordpress/components");
|
|
43
43
|
var import_icons = require("@wordpress/icons");
|
|
44
44
|
var import_dataviews = require("@wordpress/dataviews");
|
|
45
|
+
var import_views = require("@wordpress/views");
|
|
45
46
|
var import_ui = require("@wordpress/ui");
|
|
46
47
|
var import_media_fields = require("@wordpress/media-fields");
|
|
47
48
|
var import_notices = require("@wordpress/notices");
|
|
@@ -57,6 +58,41 @@ var LAYOUT_PICKER_GRID = "pickerGrid";
|
|
|
57
58
|
var LAYOUT_PICKER_TABLE = "pickerTable";
|
|
58
59
|
var NOTICES_CONTEXT = "media-modal";
|
|
59
60
|
var NOTICE_ID_UPLOAD_PROGRESS = "media-modal-upload-progress";
|
|
61
|
+
var defaultView = {
|
|
62
|
+
type: LAYOUT_PICKER_GRID,
|
|
63
|
+
fields: [],
|
|
64
|
+
showTitle: false,
|
|
65
|
+
titleField: "title",
|
|
66
|
+
mediaField: "media_thumbnail",
|
|
67
|
+
search: "",
|
|
68
|
+
page: 1,
|
|
69
|
+
perPage: 50,
|
|
70
|
+
filters: [],
|
|
71
|
+
layout: {
|
|
72
|
+
previewSize: 170,
|
|
73
|
+
density: "compact"
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var defaultLayouts = {
|
|
77
|
+
[LAYOUT_PICKER_GRID]: {
|
|
78
|
+
fields: [],
|
|
79
|
+
showTitle: false,
|
|
80
|
+
layout: {
|
|
81
|
+
previewSize: 170,
|
|
82
|
+
density: "compact"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
[LAYOUT_PICKER_TABLE]: {
|
|
86
|
+
fields: [
|
|
87
|
+
"filename",
|
|
88
|
+
"filesize",
|
|
89
|
+
"media_dimensions",
|
|
90
|
+
"author",
|
|
91
|
+
"date"
|
|
92
|
+
],
|
|
93
|
+
showTitle: true
|
|
94
|
+
}
|
|
95
|
+
};
|
|
60
96
|
function MediaUploadModal({
|
|
61
97
|
allowedTypes,
|
|
62
98
|
multiple = false,
|
|
@@ -79,21 +115,12 @@ function MediaUploadModal({
|
|
|
79
115
|
});
|
|
80
116
|
const { createSuccessNotice, removeAllNotices } = (0, import_data.useDispatch)(import_notices.store);
|
|
81
117
|
const invalidateAttachmentResolutions = (0, import_use_invalidate_attachment_resolutions.useInvalidateAttachmentResolutions)();
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
search: "",
|
|
89
|
-
page: 1,
|
|
90
|
-
perPage: 50,
|
|
91
|
-
filters: [],
|
|
92
|
-
layout: {
|
|
93
|
-
previewSize: 170,
|
|
94
|
-
density: "compact"
|
|
95
|
-
}
|
|
96
|
-
}));
|
|
118
|
+
const { view, updateView, isModified, resetToDefault } = (0, import_views.useView)({
|
|
119
|
+
kind: "postType",
|
|
120
|
+
name: "attachment",
|
|
121
|
+
slug: "media-modal",
|
|
122
|
+
defaultView
|
|
123
|
+
});
|
|
97
124
|
const queryArgs = (0, import_element.useMemo)(() => {
|
|
98
125
|
const filters = {};
|
|
99
126
|
view.filters?.forEach((filter) => {
|
|
@@ -294,25 +321,6 @@ function MediaUploadModal({
|
|
|
294
321
|
}),
|
|
295
322
|
[totalItems, totalPages]
|
|
296
323
|
);
|
|
297
|
-
const defaultLayouts = (0, import_element.useMemo)(
|
|
298
|
-
() => ({
|
|
299
|
-
[LAYOUT_PICKER_GRID]: {
|
|
300
|
-
fields: [],
|
|
301
|
-
showTitle: false
|
|
302
|
-
},
|
|
303
|
-
[LAYOUT_PICKER_TABLE]: {
|
|
304
|
-
fields: [
|
|
305
|
-
"filename",
|
|
306
|
-
"filesize",
|
|
307
|
-
"media_dimensions",
|
|
308
|
-
"author",
|
|
309
|
-
"date"
|
|
310
|
-
],
|
|
311
|
-
showTitle: true
|
|
312
|
-
}
|
|
313
|
-
}),
|
|
314
|
-
[]
|
|
315
|
-
);
|
|
316
324
|
const acceptTypes = (0, import_element.useMemo)(() => {
|
|
317
325
|
if (allowedTypes?.includes("*")) {
|
|
318
326
|
return void 0;
|
|
@@ -383,7 +391,7 @@ function MediaUploadModal({
|
|
|
383
391
|
data: mediaRecords || [],
|
|
384
392
|
fields,
|
|
385
393
|
view,
|
|
386
|
-
onChangeView:
|
|
394
|
+
onChangeView: updateView,
|
|
387
395
|
actions,
|
|
388
396
|
selection,
|
|
389
397
|
onChangeSelection: setSelection,
|
|
@@ -392,6 +400,7 @@ function MediaUploadModal({
|
|
|
392
400
|
defaultLayouts,
|
|
393
401
|
getItemId: (item) => String(item.id),
|
|
394
402
|
itemListLabel: (0, import_i18n.__)("Media items"),
|
|
403
|
+
onReset: isModified ? resetToDefault : false,
|
|
395
404
|
children: [
|
|
396
405
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
397
406
|
import_ui.Stack,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/components/media-upload-modal/index.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * External dependencies\n */\nimport clsx from 'clsx';\n\n/**\n * WordPress dependencies\n */\nimport {\n\tcreatePortal,\n\tuseState,\n\tuseCallback,\n\tuseMemo,\n\tuseRef,\n\tuseEffect,\n} from '@wordpress/element';\nimport { __, sprintf, _n } from '@wordpress/i18n';\nimport {\n\tprivateApis as coreDataPrivateApis,\n\tstore as coreStore,\n} from '@wordpress/core-data';\nimport { resolveSelect, useDispatch } from '@wordpress/data';\nimport { Modal, DropZone, FormFileUpload, Button } from '@wordpress/components';\nimport { upload as uploadIcon } from '@wordpress/icons';\nimport { DataViewsPicker } from '@wordpress/dataviews';\nimport type { View, Field, ActionButton } from '@wordpress/dataviews';\nimport { Stack } from '@wordpress/ui';\nimport {\n\taltTextField,\n\tattachedToField,\n\tauthorField,\n\tcaptionField,\n\tdateAddedField,\n\tdateModifiedField,\n\tdescriptionField,\n\tfilenameField,\n\tfilesizeField,\n\tmediaDimensionsField,\n\tmediaThumbnailField,\n\tmimeTypeField,\n} from '@wordpress/media-fields';\nimport { store as noticesStore, SnackbarNotices } from '@wordpress/notices';\n\n/**\n * Internal dependencies\n */\nimport type { Attachment, RestAttachment } from '../../utils/types';\nimport { transformAttachment } from '../../utils/transform-attachment';\nimport { uploadMedia } from '../../utils/upload-media';\nimport { unlock } from '../../lock-unlock';\nimport { UploadStatusPopover } from './upload-status-popover';\nimport { useInvalidateAttachmentResolutions } from './use-invalidate-attachment-resolutions';\nimport { useUploadStatus } from './use-upload-status';\n\nconst { useEntityRecordsWithPermissions } = unlock( coreDataPrivateApis );\n\n// Layout constants - matching the picker layout types\nconst LAYOUT_PICKER_GRID = 'pickerGrid';\nconst LAYOUT_PICKER_TABLE = 'pickerTable';\n\n// Custom notices context for the media modal\nconst NOTICES_CONTEXT = 'media-modal';\n\n// Notice ID - reused for all upload-related notices to prevent flooding\nconst NOTICE_ID_UPLOAD_PROGRESS = 'media-modal-upload-progress';\n\ninterface MediaUploadModalProps {\n\t/**\n\t * Array of allowed media types.\n\t */\n\tallowedTypes?: string[];\n\n\t/**\n\t * Whether multiple files can be selected.\n\t * @default false\n\t */\n\tmultiple?: boolean;\n\n\t/**\n\t * The currently selected media item(s).\n\t * Can be a single ID number or array of IDs for multiple selection.\n\t */\n\tvalue?: number | number[];\n\n\t/**\n\t * Function called when media is selected.\n\t * Receives single attachment object or array of attachments.\n\t */\n\tonSelect: ( media: Attachment | Attachment[] ) => void;\n\n\t/**\n\t * Function called when the modal is closed without selection.\n\t */\n\tonClose?: () => void;\n\n\t/**\n\t * Function to handle media uploads.\n\t * If not provided, drag and drop will be disabled.\n\t */\n\tonUpload?: ( args: {\n\t\tallowedTypes?: string[];\n\t\tfilesList: File[];\n\t\tonFileChange?: ( attachments: Partial< Attachment >[] ) => void;\n\t\tonError?: ( error: Error ) => void;\n\t\tmultiple?: boolean;\n\t} ) => void;\n\n\t/**\n\t * Title for the modal.\n\t * @default 'Select Media'\n\t */\n\ttitle?: string;\n\n\t/**\n\t * Whether the modal is open.\n\t */\n\tisOpen: boolean;\n\n\t/**\n\t * Whether the modal can be closed by clicking outside or pressing escape.\n\t * @default true\n\t */\n\tisDismissible?: boolean;\n\n\t/**\n\t * Additional CSS class for the modal.\n\t */\n\tmodalClass?: string;\n\n\t/**\n\t * Whether to show a search input.\n\t * @default true\n\t */\n\tsearch?: boolean;\n\n\t/**\n\t * Label for the search input.\n\t */\n\tsearchLabel?: string;\n}\n\n/**\n * MediaUploadModal component that uses Modal and DataViewsPicker for media selection.\n *\n * This is a modern functional component alternative to the legacy MediaUpload class component.\n * It provides a cleaner API and better integration with the WordPress block editor.\n *\n * @param props Component props\n * @param props.allowedTypes Array of allowed media types\n * @param props.multiple Whether multiple files can be selected\n * @param props.value Currently selected media item(s)\n * @param props.onSelect Function called when media is selected\n * @param props.onClose Function called when modal is closed\n * @param props.onUpload Function to handle media uploads\n * @param props.title Title for the modal\n * @param props.isOpen Whether the modal is open\n * @param props.isDismissible Whether modal can be dismissed\n * @param props.modalClass Additional CSS class for modal\n * @param props.search Whether to show search input\n * @param props.searchLabel Label for search input\n * @return JSX element or null\n */\nexport function MediaUploadModal( {\n\tallowedTypes,\n\tmultiple = false,\n\tvalue,\n\tonSelect,\n\tonClose,\n\tonUpload,\n\ttitle = __( 'Select Media' ),\n\tisOpen,\n\tisDismissible = true,\n\tmodalClass,\n\tsearch = true,\n\tsearchLabel = __( 'Search media' ),\n}: MediaUploadModalProps ) {\n\tconst [ selection, setSelection ] = useState< string[] >( () => {\n\t\tif ( ! value ) {\n\t\t\treturn [];\n\t\t}\n\t\treturn Array.isArray( value )\n\t\t\t? value.map( String )\n\t\t\t: [ String( value ) ];\n\t} );\n\n\tconst { createSuccessNotice, removeAllNotices } =\n\t\tuseDispatch( noticesStore );\n\tconst invalidateAttachmentResolutions =\n\t\tuseInvalidateAttachmentResolutions();\n\n\t// DataViews configuration - allow view updates\n\tconst [ view, setView ] = useState< View >( () => ( {\n\t\ttype: LAYOUT_PICKER_GRID,\n\t\tfields: [],\n\t\tshowTitle: false,\n\t\ttitleField: 'title',\n\t\tmediaField: 'media_thumbnail',\n\t\tsearch: '',\n\t\tpage: 1,\n\t\tperPage: 50,\n\t\tfilters: [],\n\t\tlayout: {\n\t\t\tpreviewSize: 170,\n\t\t\tdensity: 'compact',\n\t\t},\n\t} ) );\n\n\t// Build query args based on view properties, similar to PostList\n\tconst queryArgs = useMemo( () => {\n\t\tconst filters: Record< string, any > = {};\n\n\t\tview.filters?.forEach( ( filter ) => {\n\t\t\t// Handle media type filters\n\t\t\tif ( filter.field === 'media_type' ) {\n\t\t\t\tfilters.media_type = filter.value;\n\t\t\t}\n\t\t\t// Handle author filters\n\t\t\tif ( filter.field === 'author' ) {\n\t\t\t\tif ( filter.operator === 'isAny' ) {\n\t\t\t\t\tfilters.author = filter.value;\n\t\t\t\t} else if ( filter.operator === 'isNone' ) {\n\t\t\t\t\tfilters.author_exclude = filter.value;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle date filters\n\t\t\tif ( filter.field === 'date' || filter.field === 'modified' ) {\n\t\t\t\tif ( filter.operator === 'before' ) {\n\t\t\t\t\tfilters.before = filter.value;\n\t\t\t\t} else if ( filter.operator === 'after' ) {\n\t\t\t\t\tfilters.after = filter.value;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle mime type filters\n\t\t\tif ( filter.field === 'mime_type' ) {\n\t\t\t\tfilters.mime_type = filter.value;\n\t\t\t}\n\t\t} );\n\n\t\t// Base media type on allowedTypes if no filter is set\n\t\tif ( ! filters.media_type ) {\n\t\t\tfilters.media_type = allowedTypes?.includes( '*' )\n\t\t\t\t? undefined\n\t\t\t\t: allowedTypes;\n\t\t}\n\n\t\treturn {\n\t\t\tper_page: view.perPage || 20,\n\t\t\tpage: view.page || 1,\n\t\t\tstatus: 'inherit',\n\t\t\torder: view.sort?.direction,\n\t\t\torderby: view.sort?.field,\n\t\t\tsearch: view.search,\n\t\t\t_embed: 'author,wp:attached-to',\n\t\t\t...filters,\n\t\t};\n\t}, [ view, allowedTypes ] );\n\n\t// Per-batch completion handler: auto-select uploaded items and refresh the grid.\n\tconst handleBatchComplete = useCallback(\n\t\t( attachments: Partial< Attachment >[] ) => {\n\t\t\tconst uploadedIds = attachments\n\t\t\t\t.map( ( attachment ) => String( attachment.id ) )\n\t\t\t\t.filter( Boolean );\n\n\t\t\tif ( multiple ) {\n\t\t\t\tsetSelection( ( prev ) => {\n\t\t\t\t\tconst existing = new Set( prev );\n\t\t\t\t\tconst newIds = uploadedIds.filter(\n\t\t\t\t\t\t( id ) => ! existing.has( id )\n\t\t\t\t\t);\n\t\t\t\t\treturn [ ...prev, ...newIds ];\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\tsetSelection( uploadedIds.slice( 0, 1 ) );\n\t\t\t}\n\n\t\t\t// Invalidate all cached attachment queries so every page of\n\t\t\t// results refreshes \u2014 not just the page the user is viewing.\n\t\t\tinvalidateAttachmentResolutions();\n\t\t},\n\t\t[ multiple, invalidateAttachmentResolutions ]\n\t);\n\n\tconst {\n\t\tuploadingFiles,\n\t\tregisterBatch,\n\t\tdismissError,\n\t\tclearCompleted,\n\t\tallComplete,\n\t} = useUploadStatus( { onBatchComplete: handleBatchComplete } );\n\n\tconst isPopoverOpenRef = useRef( false );\n\tconst handlePopoverOpenChange = useCallback(\n\t\t( open: boolean ) => {\n\t\t\tisPopoverOpenRef.current = open;\n\t\t\tif ( ! open ) {\n\t\t\t\tclearCompleted();\n\t\t\t}\n\t\t},\n\t\t[ clearCompleted ]\n\t);\n\n\t// Fetch all media attachments using WordPress core data with permissions\n\tconst {\n\t\trecords: mediaRecords,\n\t\tisResolving: isLoading,\n\t\ttotalItems,\n\t\ttotalPages,\n\t} = useEntityRecordsWithPermissions( 'postType', 'attachment', queryArgs );\n\n\tconst fields: Field< RestAttachment >[] = useMemo(\n\t\t() => [\n\t\t\t// Media field definitions from @wordpress/media-fields\n\t\t\t// Cast is safe because RestAttachment has the same properties as Attachment\n\t\t\t{\n\t\t\t\t...( mediaThumbnailField as Field< RestAttachment > ),\n\t\t\t\tenableHiding: false, // Within the modal, the thumbnail should always be shown.\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'title',\n\t\t\t\ttype: 'text' as const,\n\t\t\t\tlabel: __( 'Title' ),\n\t\t\t\tgetValue: ( { item }: { item: RestAttachment } ) => {\n\t\t\t\t\tconst titleValue = item.title.raw || item.title.rendered;\n\t\t\t\t\treturn titleValue || __( '(no title)' );\n\t\t\t\t},\n\t\t\t},\n\t\t\taltTextField as Field< RestAttachment >,\n\t\t\tcaptionField as Field< RestAttachment >,\n\t\t\tdescriptionField as Field< RestAttachment >,\n\t\t\tdateAddedField as Field< RestAttachment >,\n\t\t\tdateModifiedField as Field< RestAttachment >,\n\t\t\tauthorField as Field< RestAttachment >,\n\t\t\tfilenameField as Field< RestAttachment >,\n\t\t\tfilesizeField as Field< RestAttachment >,\n\t\t\tmediaDimensionsField as Field< RestAttachment >,\n\t\t\tmimeTypeField as Field< RestAttachment >,\n\t\t\tattachedToField as Field< RestAttachment >,\n\t\t],\n\t\t[]\n\t);\n\n\tconst actions: ActionButton< RestAttachment >[] = useMemo(\n\t\t() => [\n\t\t\t{\n\t\t\t\tid: 'select',\n\t\t\t\tlabel: __( 'Select' ),\n\t\t\t\tisPrimary: true,\n\t\t\t\tsupportsBulk: multiple,\n\t\t\t\tasync callback() {\n\t\t\t\t\tif ( selection.length === 0 ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst selectedPostsQuery = {\n\t\t\t\t\t\tinclude: selection,\n\t\t\t\t\t\tper_page: -1,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst selectedPosts = await resolveSelect(\n\t\t\t\t\t\tcoreStore\n\t\t\t\t\t).getEntityRecords< RestAttachment >(\n\t\t\t\t\t\t'postType',\n\t\t\t\t\t\t'attachment',\n\t\t\t\t\t\tselectedPostsQuery\n\t\t\t\t\t);\n\n\t\t\t\t\t// Transform the selected posts to the expected Attachment format\n\t\t\t\t\tconst transformedPosts = ( selectedPosts ?? [] )\n\t\t\t\t\t\t.map( transformAttachment )\n\t\t\t\t\t\t.filter( Boolean );\n\n\t\t\t\t\tconst selectedItems = multiple\n\t\t\t\t\t\t? transformedPosts\n\t\t\t\t\t\t: transformedPosts?.[ 0 ];\n\n\t\t\t\t\tremoveAllNotices( 'snackbar', NOTICES_CONTEXT );\n\t\t\t\t\tonSelect( selectedItems );\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\t[ multiple, onSelect, selection, removeAllNotices ]\n\t);\n\n\tconst handleModalClose = useCallback( () => {\n\t\tremoveAllNotices( 'snackbar', NOTICES_CONTEXT );\n\t\tonClose?.();\n\t}, [ removeAllNotices, onClose ] );\n\n\t// Use onUpload if provided, otherwise fall back to uploadMedia\n\tconst handleUpload = onUpload || uploadMedia;\n\n\t// Show success notice and auto-clear completed entries when all batches finish.\n\tconst prevAllCompleteRef = useRef( false );\n\tuseEffect( () => {\n\t\tif ( allComplete && ! prevAllCompleteRef.current ) {\n\t\t\tconst completeCount = uploadingFiles.filter(\n\t\t\t\t( file ) => file.status === 'uploaded'\n\t\t\t).length;\n\t\t\tif ( completeCount > 0 ) {\n\t\t\t\tcreateSuccessNotice(\n\t\t\t\t\tsprintf(\n\t\t\t\t\t\t// translators: %s: number of files\n\t\t\t\t\t\t_n(\n\t\t\t\t\t\t\t'Uploaded %s file',\n\t\t\t\t\t\t\t'Uploaded %s files',\n\t\t\t\t\t\t\tcompleteCount\n\t\t\t\t\t\t),\n\t\t\t\t\t\tcompleteCount.toLocaleString()\n\t\t\t\t\t),\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'snackbar',\n\t\t\t\t\t\tcontext: NOTICES_CONTEXT,\n\t\t\t\t\t\tid: NOTICE_ID_UPLOAD_PROGRESS,\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Auto-clear completed entries, unless the popover is\n\t\t\t// open \u2014 in that case, they'll be cleared on close.\n\t\t\tif ( ! isPopoverOpenRef.current ) {\n\t\t\t\tclearCompleted();\n\t\t\t}\n\t\t}\n\t\tprevAllCompleteRef.current = allComplete;\n\t}, [ allComplete, uploadingFiles, createSuccessNotice, clearCompleted ] );\n\n\tconst handleFileSelect = useCallback(\n\t\t( event: React.ChangeEvent< HTMLInputElement > ) => {\n\t\t\tconst files = event.target.files;\n\t\t\tif ( files && files.length > 0 ) {\n\t\t\t\tconst filesArray = Array.from( files );\n\t\t\t\tconst { onFileChange, onError } = registerBatch( filesArray );\n\n\t\t\t\thandleUpload( {\n\t\t\t\t\tallowedTypes,\n\t\t\t\t\tfilesList: filesArray,\n\t\t\t\t\tonFileChange,\n\t\t\t\t\tonError,\n\t\t\t\t} );\n\t\t\t}\n\t\t},\n\t\t[ allowedTypes, handleUpload, registerBatch ]\n\t);\n\n\tconst paginationInfo = useMemo(\n\t\t() => ( {\n\t\t\ttotalItems,\n\t\t\ttotalPages,\n\t\t} ),\n\t\t[ totalItems, totalPages ]\n\t);\n\n\tconst defaultLayouts = useMemo(\n\t\t() => ( {\n\t\t\t[ LAYOUT_PICKER_GRID ]: {\n\t\t\t\tfields: [],\n\t\t\t\tshowTitle: false,\n\t\t\t},\n\t\t\t[ LAYOUT_PICKER_TABLE ]: {\n\t\t\t\tfields: [\n\t\t\t\t\t'filename',\n\t\t\t\t\t'filesize',\n\t\t\t\t\t'media_dimensions',\n\t\t\t\t\t'author',\n\t\t\t\t\t'date',\n\t\t\t\t],\n\t\t\t\tshowTitle: true,\n\t\t\t},\n\t\t} ),\n\t\t[]\n\t);\n\n\t// Build accept attribute from allowedTypes\n\tconst acceptTypes = useMemo( () => {\n\t\tif ( allowedTypes?.includes( '*' ) ) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn allowedTypes?.join( ',' );\n\t}, [ allowedTypes ] );\n\n\tif ( ! isOpen ) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<Modal\n\t\t\ttitle={ title }\n\t\t\tonRequestClose={ handleModalClose }\n\t\t\tisDismissible={ isDismissible }\n\t\t\tclassName={ modalClass }\n\t\t\toverlayClassName=\"media-upload-modal\"\n\t\t\tsize=\"fill\"\n\t\t\theaderActions={\n\t\t\t\t<FormFileUpload\n\t\t\t\t\taccept={ acceptTypes }\n\t\t\t\t\tmultiple\n\t\t\t\t\tonChange={ handleFileSelect }\n\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\trender={ ( { openFileDialog } ) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tonClick={ openFileDialog }\n\t\t\t\t\t\t\ticon={ uploadIcon }\n\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ __( 'Upload media' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t) }\n\t\t\t\t/>\n\t\t\t}\n\t\t>\n\t\t\t<DropZone\n\t\t\t\tonFilesDrop={ ( files ) => {\n\t\t\t\t\tlet filteredFiles = files;\n\t\t\t\t\t// Filter files by allowed types if specified\n\t\t\t\t\tif ( allowedTypes && ! allowedTypes.includes( '*' ) ) {\n\t\t\t\t\t\tfilteredFiles = files.filter( ( file ) =>\n\t\t\t\t\t\t\tallowedTypes.some( ( allowedType ) => {\n\t\t\t\t\t\t\t\t// Check if the file type matches the allowed MIME type\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\tfile.type === allowedType ||\n\t\t\t\t\t\t\t\t\tfile.type.startsWith(\n\t\t\t\t\t\t\t\t\t\tallowedType.replace( '*', '' )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} )\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif ( filteredFiles.length > 0 ) {\n\t\t\t\t\t\tconst { onFileChange, onError } =\n\t\t\t\t\t\t\tregisterBatch( filteredFiles );\n\n\t\t\t\t\t\thandleUpload( {\n\t\t\t\t\t\t\tallowedTypes,\n\t\t\t\t\t\t\tfilesList: filteredFiles,\n\t\t\t\t\t\t\tonFileChange,\n\t\t\t\t\t\t\tonError,\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\t\t\t\t} }\n\t\t\t\tlabel={ __( 'Drop files to upload' ) }\n\t\t\t/>\n\t\t\t<DataViewsPicker\n\t\t\t\tdata={ mediaRecords || [] }\n\t\t\t\tfields={ fields }\n\t\t\t\tview={ view }\n\t\t\t\tonChangeView={ setView }\n\t\t\t\tactions={ actions }\n\t\t\t\tselection={ selection }\n\t\t\t\tonChangeSelection={ setSelection }\n\t\t\t\tisLoading={ isLoading }\n\t\t\t\tpaginationInfo={ paginationInfo }\n\t\t\t\tdefaultLayouts={ defaultLayouts }\n\t\t\t\tgetItemId={ ( item: RestAttachment ) => String( item.id ) }\n\t\t\t\titemListLabel={ __( 'Media items' ) }\n\t\t\t>\n\t\t\t\t<Stack\n\t\t\t\t\tdirection=\"row\"\n\t\t\t\t\talign=\"top\"\n\t\t\t\t\tjustify=\"space-between\"\n\t\t\t\t\tclassName=\"dataviews__view-actions\"\n\t\t\t\t\tgap=\"xs\"\n\t\t\t\t>\n\t\t\t\t\t<Stack\n\t\t\t\t\t\tdirection=\"row\"\n\t\t\t\t\t\tgap=\"sm\"\n\t\t\t\t\t\tjustify=\"start\"\n\t\t\t\t\t\tclassName=\"dataviews__search\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{ search && (\n\t\t\t\t\t\t\t<DataViewsPicker.Search label={ searchLabel } />\n\t\t\t\t\t\t) }\n\t\t\t\t\t\t<DataViewsPicker.FiltersToggle />\n\t\t\t\t\t</Stack>\n\t\t\t\t\t<Stack direction=\"row\" gap=\"xs\" style={ { flexShrink: 0 } }>\n\t\t\t\t\t\t<DataViewsPicker.LayoutSwitcher />\n\t\t\t\t\t\t<DataViewsPicker.ViewConfig />\n\t\t\t\t\t</Stack>\n\t\t\t\t</Stack>\n\t\t\t\t<DataViewsPicker.FiltersToggled className=\"dataviews-filters__container\" />\n\t\t\t\t<DataViewsPicker.Layout />\n\t\t\t\t<div\n\t\t\t\t\tclassName={ clsx( 'media-upload-modal__footer', {\n\t\t\t\t\t\t'is-uploading': uploadingFiles.length > 0,\n\t\t\t\t\t} ) }\n\t\t\t\t>\n\t\t\t\t\t<UploadStatusPopover\n\t\t\t\t\t\tuploadingFiles={ uploadingFiles }\n\t\t\t\t\t\tonDismissError={ dismissError }\n\t\t\t\t\t\tonOpenChange={ handlePopoverOpenChange }\n\t\t\t\t\t/>\n\t\t\t\t\t<DataViewsPicker.BulkActionToolbar />\n\t\t\t\t</div>\n\t\t\t</DataViewsPicker>\n\t\t\t{ createPortal(\n\t\t\t\t<SnackbarNotices\n\t\t\t\t\tclassName=\"media-upload-modal__snackbar\"\n\t\t\t\t\tcontext={ NOTICES_CONTEXT }\n\t\t\t\t/>,\n\t\t\t\tdocument.body\n\t\t\t) }\n\t\t</Modal>\n\t);\n}\n\nexport default MediaUploadModal;\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAAiB;AAKjB,qBAOO;AACP,kBAAgC;AAChC,uBAGO;AACP,kBAA2C;AAC3C,wBAAwD;AACxD,mBAAqC;AACrC,uBAAgC;
|
|
4
|
+
"sourcesContent": ["/**\n * External dependencies\n */\nimport clsx from 'clsx';\n\n/**\n * WordPress dependencies\n */\nimport {\n\tcreatePortal,\n\tuseState,\n\tuseCallback,\n\tuseMemo,\n\tuseRef,\n\tuseEffect,\n} from '@wordpress/element';\nimport { __, sprintf, _n } from '@wordpress/i18n';\nimport {\n\tprivateApis as coreDataPrivateApis,\n\tstore as coreStore,\n} from '@wordpress/core-data';\nimport { resolveSelect, useDispatch } from '@wordpress/data';\nimport { Modal, DropZone, FormFileUpload, Button } from '@wordpress/components';\nimport { upload as uploadIcon } from '@wordpress/icons';\nimport { DataViewsPicker } from '@wordpress/dataviews';\nimport type {\n\tField,\n\tActionButton,\n\tSupportedLayouts,\n\tView,\n} from '@wordpress/dataviews';\nimport { useView } from '@wordpress/views';\nimport { Stack } from '@wordpress/ui';\nimport {\n\taltTextField,\n\tattachedToField,\n\tauthorField,\n\tcaptionField,\n\tdateAddedField,\n\tdateModifiedField,\n\tdescriptionField,\n\tfilenameField,\n\tfilesizeField,\n\tmediaDimensionsField,\n\tmediaThumbnailField,\n\tmimeTypeField,\n} from '@wordpress/media-fields';\nimport { store as noticesStore, SnackbarNotices } from '@wordpress/notices';\n\n/**\n * Internal dependencies\n */\nimport type { Attachment, RestAttachment } from '../../utils/types';\nimport { transformAttachment } from '../../utils/transform-attachment';\nimport { uploadMedia } from '../../utils/upload-media';\nimport { unlock } from '../../lock-unlock';\nimport { UploadStatusPopover } from './upload-status-popover';\nimport { useInvalidateAttachmentResolutions } from './use-invalidate-attachment-resolutions';\nimport { useUploadStatus } from './use-upload-status';\n\nconst { useEntityRecordsWithPermissions } = unlock( coreDataPrivateApis );\n\n// Layout constants - matching the picker layout types\nconst LAYOUT_PICKER_GRID = 'pickerGrid';\nconst LAYOUT_PICKER_TABLE = 'pickerTable';\n\n// Custom notices context for the media modal\nconst NOTICES_CONTEXT = 'media-modal';\n\n// Notice ID - reused for all upload-related notices to prevent flooding\nconst NOTICE_ID_UPLOAD_PROGRESS = 'media-modal-upload-progress';\n\nconst defaultView: View = {\n\ttype: LAYOUT_PICKER_GRID,\n\tfields: [],\n\tshowTitle: false,\n\ttitleField: 'title',\n\tmediaField: 'media_thumbnail',\n\tsearch: '',\n\tpage: 1,\n\tperPage: 50,\n\tfilters: [],\n\tlayout: {\n\t\tpreviewSize: 170,\n\t\tdensity: 'compact',\n\t},\n};\n\nconst defaultLayouts: SupportedLayouts = {\n\t[ LAYOUT_PICKER_GRID ]: {\n\t\tfields: [],\n\t\tshowTitle: false,\n\t\tlayout: {\n\t\t\tpreviewSize: 170,\n\t\t\tdensity: 'compact',\n\t\t},\n\t},\n\t[ LAYOUT_PICKER_TABLE ]: {\n\t\tfields: [\n\t\t\t'filename',\n\t\t\t'filesize',\n\t\t\t'media_dimensions',\n\t\t\t'author',\n\t\t\t'date',\n\t\t],\n\t\tshowTitle: true,\n\t},\n};\n\ninterface MediaUploadModalProps {\n\t/**\n\t * Array of allowed media types.\n\t */\n\tallowedTypes?: string[];\n\n\t/**\n\t * Whether multiple files can be selected.\n\t * @default false\n\t */\n\tmultiple?: boolean;\n\n\t/**\n\t * The currently selected media item(s).\n\t * Can be a single ID number or array of IDs for multiple selection.\n\t */\n\tvalue?: number | number[];\n\n\t/**\n\t * Function called when media is selected.\n\t * Receives single attachment object or array of attachments.\n\t */\n\tonSelect: ( media: Attachment | Attachment[] ) => void;\n\n\t/**\n\t * Function called when the modal is closed without selection.\n\t */\n\tonClose?: () => void;\n\n\t/**\n\t * Function to handle media uploads.\n\t * If not provided, drag and drop will be disabled.\n\t */\n\tonUpload?: ( args: {\n\t\tallowedTypes?: string[];\n\t\tfilesList: File[];\n\t\tonFileChange?: ( attachments: Partial< Attachment >[] ) => void;\n\t\tonError?: ( error: Error ) => void;\n\t\tmultiple?: boolean;\n\t} ) => void;\n\n\t/**\n\t * Title for the modal.\n\t * @default 'Select Media'\n\t */\n\ttitle?: string;\n\n\t/**\n\t * Whether the modal is open.\n\t */\n\tisOpen: boolean;\n\n\t/**\n\t * Whether the modal can be closed by clicking outside or pressing escape.\n\t * @default true\n\t */\n\tisDismissible?: boolean;\n\n\t/**\n\t * Additional CSS class for the modal.\n\t */\n\tmodalClass?: string;\n\n\t/**\n\t * Whether to show a search input.\n\t * @default true\n\t */\n\tsearch?: boolean;\n\n\t/**\n\t * Label for the search input.\n\t */\n\tsearchLabel?: string;\n}\n\n/**\n * MediaUploadModal component that uses Modal and DataViewsPicker for media selection.\n *\n * This is a modern functional component alternative to the legacy MediaUpload class component.\n * It provides a cleaner API and better integration with the WordPress block editor.\n *\n * @param props Component props\n * @param props.allowedTypes Array of allowed media types\n * @param props.multiple Whether multiple files can be selected\n * @param props.value Currently selected media item(s)\n * @param props.onSelect Function called when media is selected\n * @param props.onClose Function called when modal is closed\n * @param props.onUpload Function to handle media uploads\n * @param props.title Title for the modal\n * @param props.isOpen Whether the modal is open\n * @param props.isDismissible Whether modal can be dismissed\n * @param props.modalClass Additional CSS class for modal\n * @param props.search Whether to show search input\n * @param props.searchLabel Label for search input\n * @return JSX element or null\n */\nexport function MediaUploadModal( {\n\tallowedTypes,\n\tmultiple = false,\n\tvalue,\n\tonSelect,\n\tonClose,\n\tonUpload,\n\ttitle = __( 'Select Media' ),\n\tisOpen,\n\tisDismissible = true,\n\tmodalClass,\n\tsearch = true,\n\tsearchLabel = __( 'Search media' ),\n}: MediaUploadModalProps ) {\n\tconst [ selection, setSelection ] = useState< string[] >( () => {\n\t\tif ( ! value ) {\n\t\t\treturn [];\n\t\t}\n\t\treturn Array.isArray( value )\n\t\t\t? value.map( String )\n\t\t\t: [ String( value ) ];\n\t} );\n\n\tconst { createSuccessNotice, removeAllNotices } =\n\t\tuseDispatch( noticesStore );\n\tconst invalidateAttachmentResolutions =\n\t\tuseInvalidateAttachmentResolutions();\n\n\t// Persist view configuration across sessions via the preferences store.\n\tconst { view, updateView, isModified, resetToDefault } = useView( {\n\t\tkind: 'postType',\n\t\tname: 'attachment',\n\t\tslug: 'media-modal',\n\t\tdefaultView,\n\t} );\n\n\t// Build query args based on view properties, similar to PostList\n\tconst queryArgs = useMemo( () => {\n\t\tconst filters: Record< string, any > = {};\n\n\t\tview.filters?.forEach( ( filter ) => {\n\t\t\t// Handle media type filters\n\t\t\tif ( filter.field === 'media_type' ) {\n\t\t\t\tfilters.media_type = filter.value;\n\t\t\t}\n\t\t\t// Handle author filters\n\t\t\tif ( filter.field === 'author' ) {\n\t\t\t\tif ( filter.operator === 'isAny' ) {\n\t\t\t\t\tfilters.author = filter.value;\n\t\t\t\t} else if ( filter.operator === 'isNone' ) {\n\t\t\t\t\tfilters.author_exclude = filter.value;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle date filters\n\t\t\tif ( filter.field === 'date' || filter.field === 'modified' ) {\n\t\t\t\tif ( filter.operator === 'before' ) {\n\t\t\t\t\tfilters.before = filter.value;\n\t\t\t\t} else if ( filter.operator === 'after' ) {\n\t\t\t\t\tfilters.after = filter.value;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle mime type filters\n\t\t\tif ( filter.field === 'mime_type' ) {\n\t\t\t\tfilters.mime_type = filter.value;\n\t\t\t}\n\t\t} );\n\n\t\t// Base media type on allowedTypes if no filter is set\n\t\tif ( ! filters.media_type ) {\n\t\t\tfilters.media_type = allowedTypes?.includes( '*' )\n\t\t\t\t? undefined\n\t\t\t\t: allowedTypes;\n\t\t}\n\n\t\treturn {\n\t\t\tper_page: view.perPage || 20,\n\t\t\tpage: view.page || 1,\n\t\t\tstatus: 'inherit',\n\t\t\torder: view.sort?.direction,\n\t\t\torderby: view.sort?.field,\n\t\t\tsearch: view.search,\n\t\t\t_embed: 'author,wp:attached-to',\n\t\t\t...filters,\n\t\t};\n\t}, [ view, allowedTypes ] );\n\n\t// Per-batch completion handler: auto-select uploaded items and refresh the grid.\n\tconst handleBatchComplete = useCallback(\n\t\t( attachments: Partial< Attachment >[] ) => {\n\t\t\tconst uploadedIds = attachments\n\t\t\t\t.map( ( attachment ) => String( attachment.id ) )\n\t\t\t\t.filter( Boolean );\n\n\t\t\tif ( multiple ) {\n\t\t\t\tsetSelection( ( prev ) => {\n\t\t\t\t\tconst existing = new Set( prev );\n\t\t\t\t\tconst newIds = uploadedIds.filter(\n\t\t\t\t\t\t( id ) => ! existing.has( id )\n\t\t\t\t\t);\n\t\t\t\t\treturn [ ...prev, ...newIds ];\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\tsetSelection( uploadedIds.slice( 0, 1 ) );\n\t\t\t}\n\n\t\t\t// Invalidate all cached attachment queries so every page of\n\t\t\t// results refreshes \u2014 not just the page the user is viewing.\n\t\t\tinvalidateAttachmentResolutions();\n\t\t},\n\t\t[ multiple, invalidateAttachmentResolutions ]\n\t);\n\n\tconst {\n\t\tuploadingFiles,\n\t\tregisterBatch,\n\t\tdismissError,\n\t\tclearCompleted,\n\t\tallComplete,\n\t} = useUploadStatus( { onBatchComplete: handleBatchComplete } );\n\n\tconst isPopoverOpenRef = useRef( false );\n\tconst handlePopoverOpenChange = useCallback(\n\t\t( open: boolean ) => {\n\t\t\tisPopoverOpenRef.current = open;\n\t\t\tif ( ! open ) {\n\t\t\t\tclearCompleted();\n\t\t\t}\n\t\t},\n\t\t[ clearCompleted ]\n\t);\n\n\t// Fetch all media attachments using WordPress core data with permissions\n\tconst {\n\t\trecords: mediaRecords,\n\t\tisResolving: isLoading,\n\t\ttotalItems,\n\t\ttotalPages,\n\t} = useEntityRecordsWithPermissions( 'postType', 'attachment', queryArgs );\n\n\tconst fields: Field< RestAttachment >[] = useMemo(\n\t\t() => [\n\t\t\t// Media field definitions from @wordpress/media-fields\n\t\t\t// Cast is safe because RestAttachment has the same properties as Attachment\n\t\t\t{\n\t\t\t\t...( mediaThumbnailField as Field< RestAttachment > ),\n\t\t\t\tenableHiding: false, // Within the modal, the thumbnail should always be shown.\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'title',\n\t\t\t\ttype: 'text' as const,\n\t\t\t\tlabel: __( 'Title' ),\n\t\t\t\tgetValue: ( { item }: { item: RestAttachment } ) => {\n\t\t\t\t\tconst titleValue = item.title.raw || item.title.rendered;\n\t\t\t\t\treturn titleValue || __( '(no title)' );\n\t\t\t\t},\n\t\t\t},\n\t\t\taltTextField as Field< RestAttachment >,\n\t\t\tcaptionField as Field< RestAttachment >,\n\t\t\tdescriptionField as Field< RestAttachment >,\n\t\t\tdateAddedField as Field< RestAttachment >,\n\t\t\tdateModifiedField as Field< RestAttachment >,\n\t\t\tauthorField as Field< RestAttachment >,\n\t\t\tfilenameField as Field< RestAttachment >,\n\t\t\tfilesizeField as Field< RestAttachment >,\n\t\t\tmediaDimensionsField as Field< RestAttachment >,\n\t\t\tmimeTypeField as Field< RestAttachment >,\n\t\t\tattachedToField as Field< RestAttachment >,\n\t\t],\n\t\t[]\n\t);\n\n\tconst actions: ActionButton< RestAttachment >[] = useMemo(\n\t\t() => [\n\t\t\t{\n\t\t\t\tid: 'select',\n\t\t\t\tlabel: __( 'Select' ),\n\t\t\t\tisPrimary: true,\n\t\t\t\tsupportsBulk: multiple,\n\t\t\t\tasync callback() {\n\t\t\t\t\tif ( selection.length === 0 ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst selectedPostsQuery = {\n\t\t\t\t\t\tinclude: selection,\n\t\t\t\t\t\tper_page: -1,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst selectedPosts = await resolveSelect(\n\t\t\t\t\t\tcoreStore\n\t\t\t\t\t).getEntityRecords< RestAttachment >(\n\t\t\t\t\t\t'postType',\n\t\t\t\t\t\t'attachment',\n\t\t\t\t\t\tselectedPostsQuery\n\t\t\t\t\t);\n\n\t\t\t\t\t// Transform the selected posts to the expected Attachment format\n\t\t\t\t\tconst transformedPosts = ( selectedPosts ?? [] )\n\t\t\t\t\t\t.map( transformAttachment )\n\t\t\t\t\t\t.filter( Boolean );\n\n\t\t\t\t\tconst selectedItems = multiple\n\t\t\t\t\t\t? transformedPosts\n\t\t\t\t\t\t: transformedPosts?.[ 0 ];\n\n\t\t\t\t\tremoveAllNotices( 'snackbar', NOTICES_CONTEXT );\n\t\t\t\t\tonSelect( selectedItems );\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\t[ multiple, onSelect, selection, removeAllNotices ]\n\t);\n\n\tconst handleModalClose = useCallback( () => {\n\t\tremoveAllNotices( 'snackbar', NOTICES_CONTEXT );\n\t\tonClose?.();\n\t}, [ removeAllNotices, onClose ] );\n\n\t// Use onUpload if provided, otherwise fall back to uploadMedia\n\tconst handleUpload = onUpload || uploadMedia;\n\n\t// Show success notice and auto-clear completed entries when all batches finish.\n\tconst prevAllCompleteRef = useRef( false );\n\tuseEffect( () => {\n\t\tif ( allComplete && ! prevAllCompleteRef.current ) {\n\t\t\tconst completeCount = uploadingFiles.filter(\n\t\t\t\t( file ) => file.status === 'uploaded'\n\t\t\t).length;\n\t\t\tif ( completeCount > 0 ) {\n\t\t\t\tcreateSuccessNotice(\n\t\t\t\t\tsprintf(\n\t\t\t\t\t\t// translators: %s: number of files\n\t\t\t\t\t\t_n(\n\t\t\t\t\t\t\t'Uploaded %s file',\n\t\t\t\t\t\t\t'Uploaded %s files',\n\t\t\t\t\t\t\tcompleteCount\n\t\t\t\t\t\t),\n\t\t\t\t\t\tcompleteCount.toLocaleString()\n\t\t\t\t\t),\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'snackbar',\n\t\t\t\t\t\tcontext: NOTICES_CONTEXT,\n\t\t\t\t\t\tid: NOTICE_ID_UPLOAD_PROGRESS,\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Auto-clear completed entries, unless the popover is\n\t\t\t// open \u2014 in that case, they'll be cleared on close.\n\t\t\tif ( ! isPopoverOpenRef.current ) {\n\t\t\t\tclearCompleted();\n\t\t\t}\n\t\t}\n\t\tprevAllCompleteRef.current = allComplete;\n\t}, [ allComplete, uploadingFiles, createSuccessNotice, clearCompleted ] );\n\n\tconst handleFileSelect = useCallback(\n\t\t( event: React.ChangeEvent< HTMLInputElement > ) => {\n\t\t\tconst files = event.target.files;\n\t\t\tif ( files && files.length > 0 ) {\n\t\t\t\tconst filesArray = Array.from( files );\n\t\t\t\tconst { onFileChange, onError } = registerBatch( filesArray );\n\n\t\t\t\thandleUpload( {\n\t\t\t\t\tallowedTypes,\n\t\t\t\t\tfilesList: filesArray,\n\t\t\t\t\tonFileChange,\n\t\t\t\t\tonError,\n\t\t\t\t} );\n\t\t\t}\n\t\t},\n\t\t[ allowedTypes, handleUpload, registerBatch ]\n\t);\n\n\tconst paginationInfo = useMemo(\n\t\t() => ( {\n\t\t\ttotalItems,\n\t\t\ttotalPages,\n\t\t} ),\n\t\t[ totalItems, totalPages ]\n\t);\n\n\t// Build accept attribute from allowedTypes\n\tconst acceptTypes = useMemo( () => {\n\t\tif ( allowedTypes?.includes( '*' ) ) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn allowedTypes?.join( ',' );\n\t}, [ allowedTypes ] );\n\n\tif ( ! isOpen ) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<Modal\n\t\t\ttitle={ title }\n\t\t\tonRequestClose={ handleModalClose }\n\t\t\tisDismissible={ isDismissible }\n\t\t\tclassName={ modalClass }\n\t\t\toverlayClassName=\"media-upload-modal\"\n\t\t\tsize=\"fill\"\n\t\t\theaderActions={\n\t\t\t\t<FormFileUpload\n\t\t\t\t\taccept={ acceptTypes }\n\t\t\t\t\tmultiple\n\t\t\t\t\tonChange={ handleFileSelect }\n\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\trender={ ( { openFileDialog } ) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tonClick={ openFileDialog }\n\t\t\t\t\t\t\ticon={ uploadIcon }\n\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ __( 'Upload media' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t) }\n\t\t\t\t/>\n\t\t\t}\n\t\t>\n\t\t\t<DropZone\n\t\t\t\tonFilesDrop={ ( files ) => {\n\t\t\t\t\tlet filteredFiles = files;\n\t\t\t\t\t// Filter files by allowed types if specified\n\t\t\t\t\tif ( allowedTypes && ! allowedTypes.includes( '*' ) ) {\n\t\t\t\t\t\tfilteredFiles = files.filter( ( file ) =>\n\t\t\t\t\t\t\tallowedTypes.some( ( allowedType ) => {\n\t\t\t\t\t\t\t\t// Check if the file type matches the allowed MIME type\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\tfile.type === allowedType ||\n\t\t\t\t\t\t\t\t\tfile.type.startsWith(\n\t\t\t\t\t\t\t\t\t\tallowedType.replace( '*', '' )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} )\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif ( filteredFiles.length > 0 ) {\n\t\t\t\t\t\tconst { onFileChange, onError } =\n\t\t\t\t\t\t\tregisterBatch( filteredFiles );\n\n\t\t\t\t\t\thandleUpload( {\n\t\t\t\t\t\t\tallowedTypes,\n\t\t\t\t\t\t\tfilesList: filteredFiles,\n\t\t\t\t\t\t\tonFileChange,\n\t\t\t\t\t\t\tonError,\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\t\t\t\t} }\n\t\t\t\tlabel={ __( 'Drop files to upload' ) }\n\t\t\t/>\n\t\t\t<DataViewsPicker\n\t\t\t\tdata={ mediaRecords || [] }\n\t\t\t\tfields={ fields }\n\t\t\t\tview={ view }\n\t\t\t\tonChangeView={ updateView }\n\t\t\t\tactions={ actions }\n\t\t\t\tselection={ selection }\n\t\t\t\tonChangeSelection={ setSelection }\n\t\t\t\tisLoading={ isLoading }\n\t\t\t\tpaginationInfo={ paginationInfo }\n\t\t\t\tdefaultLayouts={ defaultLayouts }\n\t\t\t\tgetItemId={ ( item: RestAttachment ) => String( item.id ) }\n\t\t\t\titemListLabel={ __( 'Media items' ) }\n\t\t\t\tonReset={ isModified ? resetToDefault : false }\n\t\t\t>\n\t\t\t\t<Stack\n\t\t\t\t\tdirection=\"row\"\n\t\t\t\t\talign=\"top\"\n\t\t\t\t\tjustify=\"space-between\"\n\t\t\t\t\tclassName=\"dataviews__view-actions\"\n\t\t\t\t\tgap=\"xs\"\n\t\t\t\t>\n\t\t\t\t\t<Stack\n\t\t\t\t\t\tdirection=\"row\"\n\t\t\t\t\t\tgap=\"sm\"\n\t\t\t\t\t\tjustify=\"start\"\n\t\t\t\t\t\tclassName=\"dataviews__search\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{ search && (\n\t\t\t\t\t\t\t<DataViewsPicker.Search label={ searchLabel } />\n\t\t\t\t\t\t) }\n\t\t\t\t\t\t<DataViewsPicker.FiltersToggle />\n\t\t\t\t\t</Stack>\n\t\t\t\t\t<Stack direction=\"row\" gap=\"xs\" style={ { flexShrink: 0 } }>\n\t\t\t\t\t\t<DataViewsPicker.LayoutSwitcher />\n\t\t\t\t\t\t<DataViewsPicker.ViewConfig />\n\t\t\t\t\t</Stack>\n\t\t\t\t</Stack>\n\t\t\t\t<DataViewsPicker.FiltersToggled className=\"dataviews-filters__container\" />\n\t\t\t\t<DataViewsPicker.Layout />\n\t\t\t\t<div\n\t\t\t\t\tclassName={ clsx( 'media-upload-modal__footer', {\n\t\t\t\t\t\t'is-uploading': uploadingFiles.length > 0,\n\t\t\t\t\t} ) }\n\t\t\t\t>\n\t\t\t\t\t<UploadStatusPopover\n\t\t\t\t\t\tuploadingFiles={ uploadingFiles }\n\t\t\t\t\t\tonDismissError={ dismissError }\n\t\t\t\t\t\tonOpenChange={ handlePopoverOpenChange }\n\t\t\t\t\t/>\n\t\t\t\t\t<DataViewsPicker.BulkActionToolbar />\n\t\t\t\t</div>\n\t\t\t</DataViewsPicker>\n\t\t\t{ createPortal(\n\t\t\t\t<SnackbarNotices\n\t\t\t\t\tclassName=\"media-upload-modal__snackbar\"\n\t\t\t\t\tcontext={ NOTICES_CONTEXT }\n\t\t\t\t/>,\n\t\t\t\tdocument.body\n\t\t\t) }\n\t\t</Modal>\n\t);\n}\n\nexport default MediaUploadModal;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["coreDataPrivateApis", "noticesStore", "coreStore", "uploadIcon", "clsx"]
|
|
7
7
|
}
|
|
@@ -26,7 +26,7 @@ module.exports = __toCommonJS(validate_mime_type_exports);
|
|
|
26
26
|
var import_i18n = require("@wordpress/i18n");
|
|
27
27
|
var import_upload_error = require("./upload-error.cjs");
|
|
28
28
|
function validateMimeType(file, allowedTypes) {
|
|
29
|
-
if (!allowedTypes) {
|
|
29
|
+
if (!allowedTypes || allowedTypes.includes("*")) {
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
const isAllowedType = allowedTypes.some((allowedType) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/validate-mime-type.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { __, sprintf } from '@wordpress/i18n';\n\n/**\n * Internal dependencies\n */\nimport { UploadError } from './upload-error';\n\n/**\n * Verifies if the caller (e.g. a block) supports this mime type.\n *\n * @param file File object.\n * @param allowedTypes List of allowed mime types.\n */\nexport function validateMimeType( file: File, allowedTypes?: string[] ) {\n\tif ( ! allowedTypes ) {\n\t\treturn;\n\t}\n\n\t// Allowed type specified by consumer.\n\tconst isAllowedType = allowedTypes.some( ( allowedType ) => {\n\t\t// If a complete mimetype is specified verify if it matches exactly the mime type of the file.\n\t\tif ( allowedType.includes( '/' ) ) {\n\t\t\treturn allowedType === file.type;\n\t\t}\n\t\t// Otherwise a general mime type is used, and we should verify if the file mimetype starts with it.\n\t\treturn file.type.startsWith( `${ allowedType }/` );\n\t} );\n\n\tif ( file.type && ! isAllowedType ) {\n\t\tthrow new UploadError( {\n\t\t\tcode: 'MIME_TYPE_NOT_SUPPORTED',\n\t\t\tmessage: sprintf(\n\t\t\t\t// translators: %s: file name.\n\t\t\t\t__( '%s: Sorry, this file type is not supported here.' ),\n\t\t\t\tfile.name\n\t\t\t),\n\t\t\tfile,\n\t\t} );\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAA4B;AAK5B,0BAA4B;AAQrB,SAAS,iBAAkB,MAAY,cAA0B;AACvE,MAAK,CAAE,
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { __, sprintf } from '@wordpress/i18n';\n\n/**\n * Internal dependencies\n */\nimport { UploadError } from './upload-error';\n\n/**\n * Verifies if the caller (e.g. a block) supports this mime type.\n *\n * @param file File object.\n * @param allowedTypes List of allowed mime types.\n */\nexport function validateMimeType( file: File, allowedTypes?: string[] ) {\n\tif ( ! allowedTypes || allowedTypes.includes( '*' ) ) {\n\t\treturn;\n\t}\n\n\t// Allowed type specified by consumer.\n\tconst isAllowedType = allowedTypes.some( ( allowedType ) => {\n\t\t// If a complete mimetype is specified verify if it matches exactly the mime type of the file.\n\t\tif ( allowedType.includes( '/' ) ) {\n\t\t\treturn allowedType === file.type;\n\t\t}\n\t\t// Otherwise a general mime type is used, and we should verify if the file mimetype starts with it.\n\t\treturn file.type.startsWith( `${ allowedType }/` );\n\t} );\n\n\tif ( file.type && ! isAllowedType ) {\n\t\tthrow new UploadError( {\n\t\t\tcode: 'MIME_TYPE_NOT_SUPPORTED',\n\t\t\tmessage: sprintf(\n\t\t\t\t// translators: %s: file name.\n\t\t\t\t__( '%s: Sorry, this file type is not supported here.' ),\n\t\t\t\tfile.name\n\t\t\t),\n\t\t\tfile,\n\t\t} );\n\t}\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAA4B;AAK5B,0BAA4B;AAQrB,SAAS,iBAAkB,MAAY,cAA0B;AACvE,MAAK,CAAE,gBAAgB,aAAa,SAAU,GAAI,GAAI;AACrD;AAAA,EACD;AAGA,QAAM,gBAAgB,aAAa,KAAM,CAAE,gBAAiB;AAE3D,QAAK,YAAY,SAAU,GAAI,GAAI;AAClC,aAAO,gBAAgB,KAAK;AAAA,IAC7B;AAEA,WAAO,KAAK,KAAK,WAAY,GAAI,WAAY,GAAI;AAAA,EAClD,CAAE;AAEF,MAAK,KAAK,QAAQ,CAAE,eAAgB;AACnC,UAAM,IAAI,gCAAa;AAAA,MACtB,MAAM;AAAA,MACN,aAAS;AAAA;AAAA,YAER,gBAAI,kDAAmD;AAAA,QACvD,KAAK;AAAA,MACN;AAAA,MACA;AAAA,IACD,CAAE;AAAA,EACH;AACD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -17,6 +17,7 @@ import { resolveSelect, useDispatch } from "@wordpress/data";
|
|
|
17
17
|
import { Modal, DropZone, FormFileUpload, Button } from "@wordpress/components";
|
|
18
18
|
import { upload as uploadIcon } from "@wordpress/icons";
|
|
19
19
|
import { DataViewsPicker } from "@wordpress/dataviews";
|
|
20
|
+
import { useView } from "@wordpress/views";
|
|
20
21
|
import { Stack } from "@wordpress/ui";
|
|
21
22
|
import {
|
|
22
23
|
altTextField,
|
|
@@ -45,6 +46,41 @@ var LAYOUT_PICKER_GRID = "pickerGrid";
|
|
|
45
46
|
var LAYOUT_PICKER_TABLE = "pickerTable";
|
|
46
47
|
var NOTICES_CONTEXT = "media-modal";
|
|
47
48
|
var NOTICE_ID_UPLOAD_PROGRESS = "media-modal-upload-progress";
|
|
49
|
+
var defaultView = {
|
|
50
|
+
type: LAYOUT_PICKER_GRID,
|
|
51
|
+
fields: [],
|
|
52
|
+
showTitle: false,
|
|
53
|
+
titleField: "title",
|
|
54
|
+
mediaField: "media_thumbnail",
|
|
55
|
+
search: "",
|
|
56
|
+
page: 1,
|
|
57
|
+
perPage: 50,
|
|
58
|
+
filters: [],
|
|
59
|
+
layout: {
|
|
60
|
+
previewSize: 170,
|
|
61
|
+
density: "compact"
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var defaultLayouts = {
|
|
65
|
+
[LAYOUT_PICKER_GRID]: {
|
|
66
|
+
fields: [],
|
|
67
|
+
showTitle: false,
|
|
68
|
+
layout: {
|
|
69
|
+
previewSize: 170,
|
|
70
|
+
density: "compact"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
[LAYOUT_PICKER_TABLE]: {
|
|
74
|
+
fields: [
|
|
75
|
+
"filename",
|
|
76
|
+
"filesize",
|
|
77
|
+
"media_dimensions",
|
|
78
|
+
"author",
|
|
79
|
+
"date"
|
|
80
|
+
],
|
|
81
|
+
showTitle: true
|
|
82
|
+
}
|
|
83
|
+
};
|
|
48
84
|
function MediaUploadModal({
|
|
49
85
|
allowedTypes,
|
|
50
86
|
multiple = false,
|
|
@@ -67,21 +103,12 @@ function MediaUploadModal({
|
|
|
67
103
|
});
|
|
68
104
|
const { createSuccessNotice, removeAllNotices } = useDispatch(noticesStore);
|
|
69
105
|
const invalidateAttachmentResolutions = useInvalidateAttachmentResolutions();
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
search: "",
|
|
77
|
-
page: 1,
|
|
78
|
-
perPage: 50,
|
|
79
|
-
filters: [],
|
|
80
|
-
layout: {
|
|
81
|
-
previewSize: 170,
|
|
82
|
-
density: "compact"
|
|
83
|
-
}
|
|
84
|
-
}));
|
|
106
|
+
const { view, updateView, isModified, resetToDefault } = useView({
|
|
107
|
+
kind: "postType",
|
|
108
|
+
name: "attachment",
|
|
109
|
+
slug: "media-modal",
|
|
110
|
+
defaultView
|
|
111
|
+
});
|
|
85
112
|
const queryArgs = useMemo(() => {
|
|
86
113
|
const filters = {};
|
|
87
114
|
view.filters?.forEach((filter) => {
|
|
@@ -282,25 +309,6 @@ function MediaUploadModal({
|
|
|
282
309
|
}),
|
|
283
310
|
[totalItems, totalPages]
|
|
284
311
|
);
|
|
285
|
-
const defaultLayouts = useMemo(
|
|
286
|
-
() => ({
|
|
287
|
-
[LAYOUT_PICKER_GRID]: {
|
|
288
|
-
fields: [],
|
|
289
|
-
showTitle: false
|
|
290
|
-
},
|
|
291
|
-
[LAYOUT_PICKER_TABLE]: {
|
|
292
|
-
fields: [
|
|
293
|
-
"filename",
|
|
294
|
-
"filesize",
|
|
295
|
-
"media_dimensions",
|
|
296
|
-
"author",
|
|
297
|
-
"date"
|
|
298
|
-
],
|
|
299
|
-
showTitle: true
|
|
300
|
-
}
|
|
301
|
-
}),
|
|
302
|
-
[]
|
|
303
|
-
);
|
|
304
312
|
const acceptTypes = useMemo(() => {
|
|
305
313
|
if (allowedTypes?.includes("*")) {
|
|
306
314
|
return void 0;
|
|
@@ -371,7 +379,7 @@ function MediaUploadModal({
|
|
|
371
379
|
data: mediaRecords || [],
|
|
372
380
|
fields,
|
|
373
381
|
view,
|
|
374
|
-
onChangeView:
|
|
382
|
+
onChangeView: updateView,
|
|
375
383
|
actions,
|
|
376
384
|
selection,
|
|
377
385
|
onChangeSelection: setSelection,
|
|
@@ -380,6 +388,7 @@ function MediaUploadModal({
|
|
|
380
388
|
defaultLayouts,
|
|
381
389
|
getItemId: (item) => String(item.id),
|
|
382
390
|
itemListLabel: __("Media items"),
|
|
391
|
+
onReset: isModified ? resetToDefault : false,
|
|
383
392
|
children: [
|
|
384
393
|
/* @__PURE__ */ jsxs(
|
|
385
394
|
Stack,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/components/media-upload-modal/index.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * External dependencies\n */\nimport clsx from 'clsx';\n\n/**\n * WordPress dependencies\n */\nimport {\n\tcreatePortal,\n\tuseState,\n\tuseCallback,\n\tuseMemo,\n\tuseRef,\n\tuseEffect,\n} from '@wordpress/element';\nimport { __, sprintf, _n } from '@wordpress/i18n';\nimport {\n\tprivateApis as coreDataPrivateApis,\n\tstore as coreStore,\n} from '@wordpress/core-data';\nimport { resolveSelect, useDispatch } from '@wordpress/data';\nimport { Modal, DropZone, FormFileUpload, Button } from '@wordpress/components';\nimport { upload as uploadIcon } from '@wordpress/icons';\nimport { DataViewsPicker } from '@wordpress/dataviews';\nimport type { View, Field, ActionButton } from '@wordpress/dataviews';\nimport { Stack } from '@wordpress/ui';\nimport {\n\taltTextField,\n\tattachedToField,\n\tauthorField,\n\tcaptionField,\n\tdateAddedField,\n\tdateModifiedField,\n\tdescriptionField,\n\tfilenameField,\n\tfilesizeField,\n\tmediaDimensionsField,\n\tmediaThumbnailField,\n\tmimeTypeField,\n} from '@wordpress/media-fields';\nimport { store as noticesStore, SnackbarNotices } from '@wordpress/notices';\n\n/**\n * Internal dependencies\n */\nimport type { Attachment, RestAttachment } from '../../utils/types';\nimport { transformAttachment } from '../../utils/transform-attachment';\nimport { uploadMedia } from '../../utils/upload-media';\nimport { unlock } from '../../lock-unlock';\nimport { UploadStatusPopover } from './upload-status-popover';\nimport { useInvalidateAttachmentResolutions } from './use-invalidate-attachment-resolutions';\nimport { useUploadStatus } from './use-upload-status';\n\nconst { useEntityRecordsWithPermissions } = unlock( coreDataPrivateApis );\n\n// Layout constants - matching the picker layout types\nconst LAYOUT_PICKER_GRID = 'pickerGrid';\nconst LAYOUT_PICKER_TABLE = 'pickerTable';\n\n// Custom notices context for the media modal\nconst NOTICES_CONTEXT = 'media-modal';\n\n// Notice ID - reused for all upload-related notices to prevent flooding\nconst NOTICE_ID_UPLOAD_PROGRESS = 'media-modal-upload-progress';\n\ninterface MediaUploadModalProps {\n\t/**\n\t * Array of allowed media types.\n\t */\n\tallowedTypes?: string[];\n\n\t/**\n\t * Whether multiple files can be selected.\n\t * @default false\n\t */\n\tmultiple?: boolean;\n\n\t/**\n\t * The currently selected media item(s).\n\t * Can be a single ID number or array of IDs for multiple selection.\n\t */\n\tvalue?: number | number[];\n\n\t/**\n\t * Function called when media is selected.\n\t * Receives single attachment object or array of attachments.\n\t */\n\tonSelect: ( media: Attachment | Attachment[] ) => void;\n\n\t/**\n\t * Function called when the modal is closed without selection.\n\t */\n\tonClose?: () => void;\n\n\t/**\n\t * Function to handle media uploads.\n\t * If not provided, drag and drop will be disabled.\n\t */\n\tonUpload?: ( args: {\n\t\tallowedTypes?: string[];\n\t\tfilesList: File[];\n\t\tonFileChange?: ( attachments: Partial< Attachment >[] ) => void;\n\t\tonError?: ( error: Error ) => void;\n\t\tmultiple?: boolean;\n\t} ) => void;\n\n\t/**\n\t * Title for the modal.\n\t * @default 'Select Media'\n\t */\n\ttitle?: string;\n\n\t/**\n\t * Whether the modal is open.\n\t */\n\tisOpen: boolean;\n\n\t/**\n\t * Whether the modal can be closed by clicking outside or pressing escape.\n\t * @default true\n\t */\n\tisDismissible?: boolean;\n\n\t/**\n\t * Additional CSS class for the modal.\n\t */\n\tmodalClass?: string;\n\n\t/**\n\t * Whether to show a search input.\n\t * @default true\n\t */\n\tsearch?: boolean;\n\n\t/**\n\t * Label for the search input.\n\t */\n\tsearchLabel?: string;\n}\n\n/**\n * MediaUploadModal component that uses Modal and DataViewsPicker for media selection.\n *\n * This is a modern functional component alternative to the legacy MediaUpload class component.\n * It provides a cleaner API and better integration with the WordPress block editor.\n *\n * @param props Component props\n * @param props.allowedTypes Array of allowed media types\n * @param props.multiple Whether multiple files can be selected\n * @param props.value Currently selected media item(s)\n * @param props.onSelect Function called when media is selected\n * @param props.onClose Function called when modal is closed\n * @param props.onUpload Function to handle media uploads\n * @param props.title Title for the modal\n * @param props.isOpen Whether the modal is open\n * @param props.isDismissible Whether modal can be dismissed\n * @param props.modalClass Additional CSS class for modal\n * @param props.search Whether to show search input\n * @param props.searchLabel Label for search input\n * @return JSX element or null\n */\nexport function MediaUploadModal( {\n\tallowedTypes,\n\tmultiple = false,\n\tvalue,\n\tonSelect,\n\tonClose,\n\tonUpload,\n\ttitle = __( 'Select Media' ),\n\tisOpen,\n\tisDismissible = true,\n\tmodalClass,\n\tsearch = true,\n\tsearchLabel = __( 'Search media' ),\n}: MediaUploadModalProps ) {\n\tconst [ selection, setSelection ] = useState< string[] >( () => {\n\t\tif ( ! value ) {\n\t\t\treturn [];\n\t\t}\n\t\treturn Array.isArray( value )\n\t\t\t? value.map( String )\n\t\t\t: [ String( value ) ];\n\t} );\n\n\tconst { createSuccessNotice, removeAllNotices } =\n\t\tuseDispatch( noticesStore );\n\tconst invalidateAttachmentResolutions =\n\t\tuseInvalidateAttachmentResolutions();\n\n\t// DataViews configuration - allow view updates\n\tconst [ view, setView ] = useState< View >( () => ( {\n\t\ttype: LAYOUT_PICKER_GRID,\n\t\tfields: [],\n\t\tshowTitle: false,\n\t\ttitleField: 'title',\n\t\tmediaField: 'media_thumbnail',\n\t\tsearch: '',\n\t\tpage: 1,\n\t\tperPage: 50,\n\t\tfilters: [],\n\t\tlayout: {\n\t\t\tpreviewSize: 170,\n\t\t\tdensity: 'compact',\n\t\t},\n\t} ) );\n\n\t// Build query args based on view properties, similar to PostList\n\tconst queryArgs = useMemo( () => {\n\t\tconst filters: Record< string, any > = {};\n\n\t\tview.filters?.forEach( ( filter ) => {\n\t\t\t// Handle media type filters\n\t\t\tif ( filter.field === 'media_type' ) {\n\t\t\t\tfilters.media_type = filter.value;\n\t\t\t}\n\t\t\t// Handle author filters\n\t\t\tif ( filter.field === 'author' ) {\n\t\t\t\tif ( filter.operator === 'isAny' ) {\n\t\t\t\t\tfilters.author = filter.value;\n\t\t\t\t} else if ( filter.operator === 'isNone' ) {\n\t\t\t\t\tfilters.author_exclude = filter.value;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle date filters\n\t\t\tif ( filter.field === 'date' || filter.field === 'modified' ) {\n\t\t\t\tif ( filter.operator === 'before' ) {\n\t\t\t\t\tfilters.before = filter.value;\n\t\t\t\t} else if ( filter.operator === 'after' ) {\n\t\t\t\t\tfilters.after = filter.value;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle mime type filters\n\t\t\tif ( filter.field === 'mime_type' ) {\n\t\t\t\tfilters.mime_type = filter.value;\n\t\t\t}\n\t\t} );\n\n\t\t// Base media type on allowedTypes if no filter is set\n\t\tif ( ! filters.media_type ) {\n\t\t\tfilters.media_type = allowedTypes?.includes( '*' )\n\t\t\t\t? undefined\n\t\t\t\t: allowedTypes;\n\t\t}\n\n\t\treturn {\n\t\t\tper_page: view.perPage || 20,\n\t\t\tpage: view.page || 1,\n\t\t\tstatus: 'inherit',\n\t\t\torder: view.sort?.direction,\n\t\t\torderby: view.sort?.field,\n\t\t\tsearch: view.search,\n\t\t\t_embed: 'author,wp:attached-to',\n\t\t\t...filters,\n\t\t};\n\t}, [ view, allowedTypes ] );\n\n\t// Per-batch completion handler: auto-select uploaded items and refresh the grid.\n\tconst handleBatchComplete = useCallback(\n\t\t( attachments: Partial< Attachment >[] ) => {\n\t\t\tconst uploadedIds = attachments\n\t\t\t\t.map( ( attachment ) => String( attachment.id ) )\n\t\t\t\t.filter( Boolean );\n\n\t\t\tif ( multiple ) {\n\t\t\t\tsetSelection( ( prev ) => {\n\t\t\t\t\tconst existing = new Set( prev );\n\t\t\t\t\tconst newIds = uploadedIds.filter(\n\t\t\t\t\t\t( id ) => ! existing.has( id )\n\t\t\t\t\t);\n\t\t\t\t\treturn [ ...prev, ...newIds ];\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\tsetSelection( uploadedIds.slice( 0, 1 ) );\n\t\t\t}\n\n\t\t\t// Invalidate all cached attachment queries so every page of\n\t\t\t// results refreshes \u2014 not just the page the user is viewing.\n\t\t\tinvalidateAttachmentResolutions();\n\t\t},\n\t\t[ multiple, invalidateAttachmentResolutions ]\n\t);\n\n\tconst {\n\t\tuploadingFiles,\n\t\tregisterBatch,\n\t\tdismissError,\n\t\tclearCompleted,\n\t\tallComplete,\n\t} = useUploadStatus( { onBatchComplete: handleBatchComplete } );\n\n\tconst isPopoverOpenRef = useRef( false );\n\tconst handlePopoverOpenChange = useCallback(\n\t\t( open: boolean ) => {\n\t\t\tisPopoverOpenRef.current = open;\n\t\t\tif ( ! open ) {\n\t\t\t\tclearCompleted();\n\t\t\t}\n\t\t},\n\t\t[ clearCompleted ]\n\t);\n\n\t// Fetch all media attachments using WordPress core data with permissions\n\tconst {\n\t\trecords: mediaRecords,\n\t\tisResolving: isLoading,\n\t\ttotalItems,\n\t\ttotalPages,\n\t} = useEntityRecordsWithPermissions( 'postType', 'attachment', queryArgs );\n\n\tconst fields: Field< RestAttachment >[] = useMemo(\n\t\t() => [\n\t\t\t// Media field definitions from @wordpress/media-fields\n\t\t\t// Cast is safe because RestAttachment has the same properties as Attachment\n\t\t\t{\n\t\t\t\t...( mediaThumbnailField as Field< RestAttachment > ),\n\t\t\t\tenableHiding: false, // Within the modal, the thumbnail should always be shown.\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'title',\n\t\t\t\ttype: 'text' as const,\n\t\t\t\tlabel: __( 'Title' ),\n\t\t\t\tgetValue: ( { item }: { item: RestAttachment } ) => {\n\t\t\t\t\tconst titleValue = item.title.raw || item.title.rendered;\n\t\t\t\t\treturn titleValue || __( '(no title)' );\n\t\t\t\t},\n\t\t\t},\n\t\t\taltTextField as Field< RestAttachment >,\n\t\t\tcaptionField as Field< RestAttachment >,\n\t\t\tdescriptionField as Field< RestAttachment >,\n\t\t\tdateAddedField as Field< RestAttachment >,\n\t\t\tdateModifiedField as Field< RestAttachment >,\n\t\t\tauthorField as Field< RestAttachment >,\n\t\t\tfilenameField as Field< RestAttachment >,\n\t\t\tfilesizeField as Field< RestAttachment >,\n\t\t\tmediaDimensionsField as Field< RestAttachment >,\n\t\t\tmimeTypeField as Field< RestAttachment >,\n\t\t\tattachedToField as Field< RestAttachment >,\n\t\t],\n\t\t[]\n\t);\n\n\tconst actions: ActionButton< RestAttachment >[] = useMemo(\n\t\t() => [\n\t\t\t{\n\t\t\t\tid: 'select',\n\t\t\t\tlabel: __( 'Select' ),\n\t\t\t\tisPrimary: true,\n\t\t\t\tsupportsBulk: multiple,\n\t\t\t\tasync callback() {\n\t\t\t\t\tif ( selection.length === 0 ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst selectedPostsQuery = {\n\t\t\t\t\t\tinclude: selection,\n\t\t\t\t\t\tper_page: -1,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst selectedPosts = await resolveSelect(\n\t\t\t\t\t\tcoreStore\n\t\t\t\t\t).getEntityRecords< RestAttachment >(\n\t\t\t\t\t\t'postType',\n\t\t\t\t\t\t'attachment',\n\t\t\t\t\t\tselectedPostsQuery\n\t\t\t\t\t);\n\n\t\t\t\t\t// Transform the selected posts to the expected Attachment format\n\t\t\t\t\tconst transformedPosts = ( selectedPosts ?? [] )\n\t\t\t\t\t\t.map( transformAttachment )\n\t\t\t\t\t\t.filter( Boolean );\n\n\t\t\t\t\tconst selectedItems = multiple\n\t\t\t\t\t\t? transformedPosts\n\t\t\t\t\t\t: transformedPosts?.[ 0 ];\n\n\t\t\t\t\tremoveAllNotices( 'snackbar', NOTICES_CONTEXT );\n\t\t\t\t\tonSelect( selectedItems );\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\t[ multiple, onSelect, selection, removeAllNotices ]\n\t);\n\n\tconst handleModalClose = useCallback( () => {\n\t\tremoveAllNotices( 'snackbar', NOTICES_CONTEXT );\n\t\tonClose?.();\n\t}, [ removeAllNotices, onClose ] );\n\n\t// Use onUpload if provided, otherwise fall back to uploadMedia\n\tconst handleUpload = onUpload || uploadMedia;\n\n\t// Show success notice and auto-clear completed entries when all batches finish.\n\tconst prevAllCompleteRef = useRef( false );\n\tuseEffect( () => {\n\t\tif ( allComplete && ! prevAllCompleteRef.current ) {\n\t\t\tconst completeCount = uploadingFiles.filter(\n\t\t\t\t( file ) => file.status === 'uploaded'\n\t\t\t).length;\n\t\t\tif ( completeCount > 0 ) {\n\t\t\t\tcreateSuccessNotice(\n\t\t\t\t\tsprintf(\n\t\t\t\t\t\t// translators: %s: number of files\n\t\t\t\t\t\t_n(\n\t\t\t\t\t\t\t'Uploaded %s file',\n\t\t\t\t\t\t\t'Uploaded %s files',\n\t\t\t\t\t\t\tcompleteCount\n\t\t\t\t\t\t),\n\t\t\t\t\t\tcompleteCount.toLocaleString()\n\t\t\t\t\t),\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'snackbar',\n\t\t\t\t\t\tcontext: NOTICES_CONTEXT,\n\t\t\t\t\t\tid: NOTICE_ID_UPLOAD_PROGRESS,\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Auto-clear completed entries, unless the popover is\n\t\t\t// open \u2014 in that case, they'll be cleared on close.\n\t\t\tif ( ! isPopoverOpenRef.current ) {\n\t\t\t\tclearCompleted();\n\t\t\t}\n\t\t}\n\t\tprevAllCompleteRef.current = allComplete;\n\t}, [ allComplete, uploadingFiles, createSuccessNotice, clearCompleted ] );\n\n\tconst handleFileSelect = useCallback(\n\t\t( event: React.ChangeEvent< HTMLInputElement > ) => {\n\t\t\tconst files = event.target.files;\n\t\t\tif ( files && files.length > 0 ) {\n\t\t\t\tconst filesArray = Array.from( files );\n\t\t\t\tconst { onFileChange, onError } = registerBatch( filesArray );\n\n\t\t\t\thandleUpload( {\n\t\t\t\t\tallowedTypes,\n\t\t\t\t\tfilesList: filesArray,\n\t\t\t\t\tonFileChange,\n\t\t\t\t\tonError,\n\t\t\t\t} );\n\t\t\t}\n\t\t},\n\t\t[ allowedTypes, handleUpload, registerBatch ]\n\t);\n\n\tconst paginationInfo = useMemo(\n\t\t() => ( {\n\t\t\ttotalItems,\n\t\t\ttotalPages,\n\t\t} ),\n\t\t[ totalItems, totalPages ]\n\t);\n\n\tconst defaultLayouts = useMemo(\n\t\t() => ( {\n\t\t\t[ LAYOUT_PICKER_GRID ]: {\n\t\t\t\tfields: [],\n\t\t\t\tshowTitle: false,\n\t\t\t},\n\t\t\t[ LAYOUT_PICKER_TABLE ]: {\n\t\t\t\tfields: [\n\t\t\t\t\t'filename',\n\t\t\t\t\t'filesize',\n\t\t\t\t\t'media_dimensions',\n\t\t\t\t\t'author',\n\t\t\t\t\t'date',\n\t\t\t\t],\n\t\t\t\tshowTitle: true,\n\t\t\t},\n\t\t} ),\n\t\t[]\n\t);\n\n\t// Build accept attribute from allowedTypes\n\tconst acceptTypes = useMemo( () => {\n\t\tif ( allowedTypes?.includes( '*' ) ) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn allowedTypes?.join( ',' );\n\t}, [ allowedTypes ] );\n\n\tif ( ! isOpen ) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<Modal\n\t\t\ttitle={ title }\n\t\t\tonRequestClose={ handleModalClose }\n\t\t\tisDismissible={ isDismissible }\n\t\t\tclassName={ modalClass }\n\t\t\toverlayClassName=\"media-upload-modal\"\n\t\t\tsize=\"fill\"\n\t\t\theaderActions={\n\t\t\t\t<FormFileUpload\n\t\t\t\t\taccept={ acceptTypes }\n\t\t\t\t\tmultiple\n\t\t\t\t\tonChange={ handleFileSelect }\n\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\trender={ ( { openFileDialog } ) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tonClick={ openFileDialog }\n\t\t\t\t\t\t\ticon={ uploadIcon }\n\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ __( 'Upload media' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t) }\n\t\t\t\t/>\n\t\t\t}\n\t\t>\n\t\t\t<DropZone\n\t\t\t\tonFilesDrop={ ( files ) => {\n\t\t\t\t\tlet filteredFiles = files;\n\t\t\t\t\t// Filter files by allowed types if specified\n\t\t\t\t\tif ( allowedTypes && ! allowedTypes.includes( '*' ) ) {\n\t\t\t\t\t\tfilteredFiles = files.filter( ( file ) =>\n\t\t\t\t\t\t\tallowedTypes.some( ( allowedType ) => {\n\t\t\t\t\t\t\t\t// Check if the file type matches the allowed MIME type\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\tfile.type === allowedType ||\n\t\t\t\t\t\t\t\t\tfile.type.startsWith(\n\t\t\t\t\t\t\t\t\t\tallowedType.replace( '*', '' )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} )\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif ( filteredFiles.length > 0 ) {\n\t\t\t\t\t\tconst { onFileChange, onError } =\n\t\t\t\t\t\t\tregisterBatch( filteredFiles );\n\n\t\t\t\t\t\thandleUpload( {\n\t\t\t\t\t\t\tallowedTypes,\n\t\t\t\t\t\t\tfilesList: filteredFiles,\n\t\t\t\t\t\t\tonFileChange,\n\t\t\t\t\t\t\tonError,\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\t\t\t\t} }\n\t\t\t\tlabel={ __( 'Drop files to upload' ) }\n\t\t\t/>\n\t\t\t<DataViewsPicker\n\t\t\t\tdata={ mediaRecords || [] }\n\t\t\t\tfields={ fields }\n\t\t\t\tview={ view }\n\t\t\t\tonChangeView={ setView }\n\t\t\t\tactions={ actions }\n\t\t\t\tselection={ selection }\n\t\t\t\tonChangeSelection={ setSelection }\n\t\t\t\tisLoading={ isLoading }\n\t\t\t\tpaginationInfo={ paginationInfo }\n\t\t\t\tdefaultLayouts={ defaultLayouts }\n\t\t\t\tgetItemId={ ( item: RestAttachment ) => String( item.id ) }\n\t\t\t\titemListLabel={ __( 'Media items' ) }\n\t\t\t>\n\t\t\t\t<Stack\n\t\t\t\t\tdirection=\"row\"\n\t\t\t\t\talign=\"top\"\n\t\t\t\t\tjustify=\"space-between\"\n\t\t\t\t\tclassName=\"dataviews__view-actions\"\n\t\t\t\t\tgap=\"xs\"\n\t\t\t\t>\n\t\t\t\t\t<Stack\n\t\t\t\t\t\tdirection=\"row\"\n\t\t\t\t\t\tgap=\"sm\"\n\t\t\t\t\t\tjustify=\"start\"\n\t\t\t\t\t\tclassName=\"dataviews__search\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{ search && (\n\t\t\t\t\t\t\t<DataViewsPicker.Search label={ searchLabel } />\n\t\t\t\t\t\t) }\n\t\t\t\t\t\t<DataViewsPicker.FiltersToggle />\n\t\t\t\t\t</Stack>\n\t\t\t\t\t<Stack direction=\"row\" gap=\"xs\" style={ { flexShrink: 0 } }>\n\t\t\t\t\t\t<DataViewsPicker.LayoutSwitcher />\n\t\t\t\t\t\t<DataViewsPicker.ViewConfig />\n\t\t\t\t\t</Stack>\n\t\t\t\t</Stack>\n\t\t\t\t<DataViewsPicker.FiltersToggled className=\"dataviews-filters__container\" />\n\t\t\t\t<DataViewsPicker.Layout />\n\t\t\t\t<div\n\t\t\t\t\tclassName={ clsx( 'media-upload-modal__footer', {\n\t\t\t\t\t\t'is-uploading': uploadingFiles.length > 0,\n\t\t\t\t\t} ) }\n\t\t\t\t>\n\t\t\t\t\t<UploadStatusPopover\n\t\t\t\t\t\tuploadingFiles={ uploadingFiles }\n\t\t\t\t\t\tonDismissError={ dismissError }\n\t\t\t\t\t\tonOpenChange={ handlePopoverOpenChange }\n\t\t\t\t\t/>\n\t\t\t\t\t<DataViewsPicker.BulkActionToolbar />\n\t\t\t\t</div>\n\t\t\t</DataViewsPicker>\n\t\t\t{ createPortal(\n\t\t\t\t<SnackbarNotices\n\t\t\t\t\tclassName=\"media-upload-modal__snackbar\"\n\t\t\t\t\tcontext={ NOTICES_CONTEXT }\n\t\t\t\t/>,\n\t\t\t\tdocument.body\n\t\t\t) }\n\t\t</Modal>\n\t);\n}\n\nexport default MediaUploadModal;\n"],
|
|
5
|
-
"mappings": ";AAGA,OAAO,UAAU;AAKjB;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,IAAI,SAAS,UAAU;AAChC;AAAA,EACC,eAAe;AAAA,EACf,SAAS;AAAA,OACH;AACP,SAAS,eAAe,mBAAmB;AAC3C,SAAS,OAAO,UAAU,gBAAgB,cAAc;AACxD,SAAS,UAAU,kBAAkB;AACrC,SAAS,uBAAuB;
|
|
4
|
+
"sourcesContent": ["/**\n * External dependencies\n */\nimport clsx from 'clsx';\n\n/**\n * WordPress dependencies\n */\nimport {\n\tcreatePortal,\n\tuseState,\n\tuseCallback,\n\tuseMemo,\n\tuseRef,\n\tuseEffect,\n} from '@wordpress/element';\nimport { __, sprintf, _n } from '@wordpress/i18n';\nimport {\n\tprivateApis as coreDataPrivateApis,\n\tstore as coreStore,\n} from '@wordpress/core-data';\nimport { resolveSelect, useDispatch } from '@wordpress/data';\nimport { Modal, DropZone, FormFileUpload, Button } from '@wordpress/components';\nimport { upload as uploadIcon } from '@wordpress/icons';\nimport { DataViewsPicker } from '@wordpress/dataviews';\nimport type {\n\tField,\n\tActionButton,\n\tSupportedLayouts,\n\tView,\n} from '@wordpress/dataviews';\nimport { useView } from '@wordpress/views';\nimport { Stack } from '@wordpress/ui';\nimport {\n\taltTextField,\n\tattachedToField,\n\tauthorField,\n\tcaptionField,\n\tdateAddedField,\n\tdateModifiedField,\n\tdescriptionField,\n\tfilenameField,\n\tfilesizeField,\n\tmediaDimensionsField,\n\tmediaThumbnailField,\n\tmimeTypeField,\n} from '@wordpress/media-fields';\nimport { store as noticesStore, SnackbarNotices } from '@wordpress/notices';\n\n/**\n * Internal dependencies\n */\nimport type { Attachment, RestAttachment } from '../../utils/types';\nimport { transformAttachment } from '../../utils/transform-attachment';\nimport { uploadMedia } from '../../utils/upload-media';\nimport { unlock } from '../../lock-unlock';\nimport { UploadStatusPopover } from './upload-status-popover';\nimport { useInvalidateAttachmentResolutions } from './use-invalidate-attachment-resolutions';\nimport { useUploadStatus } from './use-upload-status';\n\nconst { useEntityRecordsWithPermissions } = unlock( coreDataPrivateApis );\n\n// Layout constants - matching the picker layout types\nconst LAYOUT_PICKER_GRID = 'pickerGrid';\nconst LAYOUT_PICKER_TABLE = 'pickerTable';\n\n// Custom notices context for the media modal\nconst NOTICES_CONTEXT = 'media-modal';\n\n// Notice ID - reused for all upload-related notices to prevent flooding\nconst NOTICE_ID_UPLOAD_PROGRESS = 'media-modal-upload-progress';\n\nconst defaultView: View = {\n\ttype: LAYOUT_PICKER_GRID,\n\tfields: [],\n\tshowTitle: false,\n\ttitleField: 'title',\n\tmediaField: 'media_thumbnail',\n\tsearch: '',\n\tpage: 1,\n\tperPage: 50,\n\tfilters: [],\n\tlayout: {\n\t\tpreviewSize: 170,\n\t\tdensity: 'compact',\n\t},\n};\n\nconst defaultLayouts: SupportedLayouts = {\n\t[ LAYOUT_PICKER_GRID ]: {\n\t\tfields: [],\n\t\tshowTitle: false,\n\t\tlayout: {\n\t\t\tpreviewSize: 170,\n\t\t\tdensity: 'compact',\n\t\t},\n\t},\n\t[ LAYOUT_PICKER_TABLE ]: {\n\t\tfields: [\n\t\t\t'filename',\n\t\t\t'filesize',\n\t\t\t'media_dimensions',\n\t\t\t'author',\n\t\t\t'date',\n\t\t],\n\t\tshowTitle: true,\n\t},\n};\n\ninterface MediaUploadModalProps {\n\t/**\n\t * Array of allowed media types.\n\t */\n\tallowedTypes?: string[];\n\n\t/**\n\t * Whether multiple files can be selected.\n\t * @default false\n\t */\n\tmultiple?: boolean;\n\n\t/**\n\t * The currently selected media item(s).\n\t * Can be a single ID number or array of IDs for multiple selection.\n\t */\n\tvalue?: number | number[];\n\n\t/**\n\t * Function called when media is selected.\n\t * Receives single attachment object or array of attachments.\n\t */\n\tonSelect: ( media: Attachment | Attachment[] ) => void;\n\n\t/**\n\t * Function called when the modal is closed without selection.\n\t */\n\tonClose?: () => void;\n\n\t/**\n\t * Function to handle media uploads.\n\t * If not provided, drag and drop will be disabled.\n\t */\n\tonUpload?: ( args: {\n\t\tallowedTypes?: string[];\n\t\tfilesList: File[];\n\t\tonFileChange?: ( attachments: Partial< Attachment >[] ) => void;\n\t\tonError?: ( error: Error ) => void;\n\t\tmultiple?: boolean;\n\t} ) => void;\n\n\t/**\n\t * Title for the modal.\n\t * @default 'Select Media'\n\t */\n\ttitle?: string;\n\n\t/**\n\t * Whether the modal is open.\n\t */\n\tisOpen: boolean;\n\n\t/**\n\t * Whether the modal can be closed by clicking outside or pressing escape.\n\t * @default true\n\t */\n\tisDismissible?: boolean;\n\n\t/**\n\t * Additional CSS class for the modal.\n\t */\n\tmodalClass?: string;\n\n\t/**\n\t * Whether to show a search input.\n\t * @default true\n\t */\n\tsearch?: boolean;\n\n\t/**\n\t * Label for the search input.\n\t */\n\tsearchLabel?: string;\n}\n\n/**\n * MediaUploadModal component that uses Modal and DataViewsPicker for media selection.\n *\n * This is a modern functional component alternative to the legacy MediaUpload class component.\n * It provides a cleaner API and better integration with the WordPress block editor.\n *\n * @param props Component props\n * @param props.allowedTypes Array of allowed media types\n * @param props.multiple Whether multiple files can be selected\n * @param props.value Currently selected media item(s)\n * @param props.onSelect Function called when media is selected\n * @param props.onClose Function called when modal is closed\n * @param props.onUpload Function to handle media uploads\n * @param props.title Title for the modal\n * @param props.isOpen Whether the modal is open\n * @param props.isDismissible Whether modal can be dismissed\n * @param props.modalClass Additional CSS class for modal\n * @param props.search Whether to show search input\n * @param props.searchLabel Label for search input\n * @return JSX element or null\n */\nexport function MediaUploadModal( {\n\tallowedTypes,\n\tmultiple = false,\n\tvalue,\n\tonSelect,\n\tonClose,\n\tonUpload,\n\ttitle = __( 'Select Media' ),\n\tisOpen,\n\tisDismissible = true,\n\tmodalClass,\n\tsearch = true,\n\tsearchLabel = __( 'Search media' ),\n}: MediaUploadModalProps ) {\n\tconst [ selection, setSelection ] = useState< string[] >( () => {\n\t\tif ( ! value ) {\n\t\t\treturn [];\n\t\t}\n\t\treturn Array.isArray( value )\n\t\t\t? value.map( String )\n\t\t\t: [ String( value ) ];\n\t} );\n\n\tconst { createSuccessNotice, removeAllNotices } =\n\t\tuseDispatch( noticesStore );\n\tconst invalidateAttachmentResolutions =\n\t\tuseInvalidateAttachmentResolutions();\n\n\t// Persist view configuration across sessions via the preferences store.\n\tconst { view, updateView, isModified, resetToDefault } = useView( {\n\t\tkind: 'postType',\n\t\tname: 'attachment',\n\t\tslug: 'media-modal',\n\t\tdefaultView,\n\t} );\n\n\t// Build query args based on view properties, similar to PostList\n\tconst queryArgs = useMemo( () => {\n\t\tconst filters: Record< string, any > = {};\n\n\t\tview.filters?.forEach( ( filter ) => {\n\t\t\t// Handle media type filters\n\t\t\tif ( filter.field === 'media_type' ) {\n\t\t\t\tfilters.media_type = filter.value;\n\t\t\t}\n\t\t\t// Handle author filters\n\t\t\tif ( filter.field === 'author' ) {\n\t\t\t\tif ( filter.operator === 'isAny' ) {\n\t\t\t\t\tfilters.author = filter.value;\n\t\t\t\t} else if ( filter.operator === 'isNone' ) {\n\t\t\t\t\tfilters.author_exclude = filter.value;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle date filters\n\t\t\tif ( filter.field === 'date' || filter.field === 'modified' ) {\n\t\t\t\tif ( filter.operator === 'before' ) {\n\t\t\t\t\tfilters.before = filter.value;\n\t\t\t\t} else if ( filter.operator === 'after' ) {\n\t\t\t\t\tfilters.after = filter.value;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle mime type filters\n\t\t\tif ( filter.field === 'mime_type' ) {\n\t\t\t\tfilters.mime_type = filter.value;\n\t\t\t}\n\t\t} );\n\n\t\t// Base media type on allowedTypes if no filter is set\n\t\tif ( ! filters.media_type ) {\n\t\t\tfilters.media_type = allowedTypes?.includes( '*' )\n\t\t\t\t? undefined\n\t\t\t\t: allowedTypes;\n\t\t}\n\n\t\treturn {\n\t\t\tper_page: view.perPage || 20,\n\t\t\tpage: view.page || 1,\n\t\t\tstatus: 'inherit',\n\t\t\torder: view.sort?.direction,\n\t\t\torderby: view.sort?.field,\n\t\t\tsearch: view.search,\n\t\t\t_embed: 'author,wp:attached-to',\n\t\t\t...filters,\n\t\t};\n\t}, [ view, allowedTypes ] );\n\n\t// Per-batch completion handler: auto-select uploaded items and refresh the grid.\n\tconst handleBatchComplete = useCallback(\n\t\t( attachments: Partial< Attachment >[] ) => {\n\t\t\tconst uploadedIds = attachments\n\t\t\t\t.map( ( attachment ) => String( attachment.id ) )\n\t\t\t\t.filter( Boolean );\n\n\t\t\tif ( multiple ) {\n\t\t\t\tsetSelection( ( prev ) => {\n\t\t\t\t\tconst existing = new Set( prev );\n\t\t\t\t\tconst newIds = uploadedIds.filter(\n\t\t\t\t\t\t( id ) => ! existing.has( id )\n\t\t\t\t\t);\n\t\t\t\t\treturn [ ...prev, ...newIds ];\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\tsetSelection( uploadedIds.slice( 0, 1 ) );\n\t\t\t}\n\n\t\t\t// Invalidate all cached attachment queries so every page of\n\t\t\t// results refreshes \u2014 not just the page the user is viewing.\n\t\t\tinvalidateAttachmentResolutions();\n\t\t},\n\t\t[ multiple, invalidateAttachmentResolutions ]\n\t);\n\n\tconst {\n\t\tuploadingFiles,\n\t\tregisterBatch,\n\t\tdismissError,\n\t\tclearCompleted,\n\t\tallComplete,\n\t} = useUploadStatus( { onBatchComplete: handleBatchComplete } );\n\n\tconst isPopoverOpenRef = useRef( false );\n\tconst handlePopoverOpenChange = useCallback(\n\t\t( open: boolean ) => {\n\t\t\tisPopoverOpenRef.current = open;\n\t\t\tif ( ! open ) {\n\t\t\t\tclearCompleted();\n\t\t\t}\n\t\t},\n\t\t[ clearCompleted ]\n\t);\n\n\t// Fetch all media attachments using WordPress core data with permissions\n\tconst {\n\t\trecords: mediaRecords,\n\t\tisResolving: isLoading,\n\t\ttotalItems,\n\t\ttotalPages,\n\t} = useEntityRecordsWithPermissions( 'postType', 'attachment', queryArgs );\n\n\tconst fields: Field< RestAttachment >[] = useMemo(\n\t\t() => [\n\t\t\t// Media field definitions from @wordpress/media-fields\n\t\t\t// Cast is safe because RestAttachment has the same properties as Attachment\n\t\t\t{\n\t\t\t\t...( mediaThumbnailField as Field< RestAttachment > ),\n\t\t\t\tenableHiding: false, // Within the modal, the thumbnail should always be shown.\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'title',\n\t\t\t\ttype: 'text' as const,\n\t\t\t\tlabel: __( 'Title' ),\n\t\t\t\tgetValue: ( { item }: { item: RestAttachment } ) => {\n\t\t\t\t\tconst titleValue = item.title.raw || item.title.rendered;\n\t\t\t\t\treturn titleValue || __( '(no title)' );\n\t\t\t\t},\n\t\t\t},\n\t\t\taltTextField as Field< RestAttachment >,\n\t\t\tcaptionField as Field< RestAttachment >,\n\t\t\tdescriptionField as Field< RestAttachment >,\n\t\t\tdateAddedField as Field< RestAttachment >,\n\t\t\tdateModifiedField as Field< RestAttachment >,\n\t\t\tauthorField as Field< RestAttachment >,\n\t\t\tfilenameField as Field< RestAttachment >,\n\t\t\tfilesizeField as Field< RestAttachment >,\n\t\t\tmediaDimensionsField as Field< RestAttachment >,\n\t\t\tmimeTypeField as Field< RestAttachment >,\n\t\t\tattachedToField as Field< RestAttachment >,\n\t\t],\n\t\t[]\n\t);\n\n\tconst actions: ActionButton< RestAttachment >[] = useMemo(\n\t\t() => [\n\t\t\t{\n\t\t\t\tid: 'select',\n\t\t\t\tlabel: __( 'Select' ),\n\t\t\t\tisPrimary: true,\n\t\t\t\tsupportsBulk: multiple,\n\t\t\t\tasync callback() {\n\t\t\t\t\tif ( selection.length === 0 ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst selectedPostsQuery = {\n\t\t\t\t\t\tinclude: selection,\n\t\t\t\t\t\tper_page: -1,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst selectedPosts = await resolveSelect(\n\t\t\t\t\t\tcoreStore\n\t\t\t\t\t).getEntityRecords< RestAttachment >(\n\t\t\t\t\t\t'postType',\n\t\t\t\t\t\t'attachment',\n\t\t\t\t\t\tselectedPostsQuery\n\t\t\t\t\t);\n\n\t\t\t\t\t// Transform the selected posts to the expected Attachment format\n\t\t\t\t\tconst transformedPosts = ( selectedPosts ?? [] )\n\t\t\t\t\t\t.map( transformAttachment )\n\t\t\t\t\t\t.filter( Boolean );\n\n\t\t\t\t\tconst selectedItems = multiple\n\t\t\t\t\t\t? transformedPosts\n\t\t\t\t\t\t: transformedPosts?.[ 0 ];\n\n\t\t\t\t\tremoveAllNotices( 'snackbar', NOTICES_CONTEXT );\n\t\t\t\t\tonSelect( selectedItems );\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\t[ multiple, onSelect, selection, removeAllNotices ]\n\t);\n\n\tconst handleModalClose = useCallback( () => {\n\t\tremoveAllNotices( 'snackbar', NOTICES_CONTEXT );\n\t\tonClose?.();\n\t}, [ removeAllNotices, onClose ] );\n\n\t// Use onUpload if provided, otherwise fall back to uploadMedia\n\tconst handleUpload = onUpload || uploadMedia;\n\n\t// Show success notice and auto-clear completed entries when all batches finish.\n\tconst prevAllCompleteRef = useRef( false );\n\tuseEffect( () => {\n\t\tif ( allComplete && ! prevAllCompleteRef.current ) {\n\t\t\tconst completeCount = uploadingFiles.filter(\n\t\t\t\t( file ) => file.status === 'uploaded'\n\t\t\t).length;\n\t\t\tif ( completeCount > 0 ) {\n\t\t\t\tcreateSuccessNotice(\n\t\t\t\t\tsprintf(\n\t\t\t\t\t\t// translators: %s: number of files\n\t\t\t\t\t\t_n(\n\t\t\t\t\t\t\t'Uploaded %s file',\n\t\t\t\t\t\t\t'Uploaded %s files',\n\t\t\t\t\t\t\tcompleteCount\n\t\t\t\t\t\t),\n\t\t\t\t\t\tcompleteCount.toLocaleString()\n\t\t\t\t\t),\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'snackbar',\n\t\t\t\t\t\tcontext: NOTICES_CONTEXT,\n\t\t\t\t\t\tid: NOTICE_ID_UPLOAD_PROGRESS,\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Auto-clear completed entries, unless the popover is\n\t\t\t// open \u2014 in that case, they'll be cleared on close.\n\t\t\tif ( ! isPopoverOpenRef.current ) {\n\t\t\t\tclearCompleted();\n\t\t\t}\n\t\t}\n\t\tprevAllCompleteRef.current = allComplete;\n\t}, [ allComplete, uploadingFiles, createSuccessNotice, clearCompleted ] );\n\n\tconst handleFileSelect = useCallback(\n\t\t( event: React.ChangeEvent< HTMLInputElement > ) => {\n\t\t\tconst files = event.target.files;\n\t\t\tif ( files && files.length > 0 ) {\n\t\t\t\tconst filesArray = Array.from( files );\n\t\t\t\tconst { onFileChange, onError } = registerBatch( filesArray );\n\n\t\t\t\thandleUpload( {\n\t\t\t\t\tallowedTypes,\n\t\t\t\t\tfilesList: filesArray,\n\t\t\t\t\tonFileChange,\n\t\t\t\t\tonError,\n\t\t\t\t} );\n\t\t\t}\n\t\t},\n\t\t[ allowedTypes, handleUpload, registerBatch ]\n\t);\n\n\tconst paginationInfo = useMemo(\n\t\t() => ( {\n\t\t\ttotalItems,\n\t\t\ttotalPages,\n\t\t} ),\n\t\t[ totalItems, totalPages ]\n\t);\n\n\t// Build accept attribute from allowedTypes\n\tconst acceptTypes = useMemo( () => {\n\t\tif ( allowedTypes?.includes( '*' ) ) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn allowedTypes?.join( ',' );\n\t}, [ allowedTypes ] );\n\n\tif ( ! isOpen ) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<Modal\n\t\t\ttitle={ title }\n\t\t\tonRequestClose={ handleModalClose }\n\t\t\tisDismissible={ isDismissible }\n\t\t\tclassName={ modalClass }\n\t\t\toverlayClassName=\"media-upload-modal\"\n\t\t\tsize=\"fill\"\n\t\t\theaderActions={\n\t\t\t\t<FormFileUpload\n\t\t\t\t\taccept={ acceptTypes }\n\t\t\t\t\tmultiple\n\t\t\t\t\tonChange={ handleFileSelect }\n\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\trender={ ( { openFileDialog } ) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tonClick={ openFileDialog }\n\t\t\t\t\t\t\ticon={ uploadIcon }\n\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ __( 'Upload media' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t) }\n\t\t\t\t/>\n\t\t\t}\n\t\t>\n\t\t\t<DropZone\n\t\t\t\tonFilesDrop={ ( files ) => {\n\t\t\t\t\tlet filteredFiles = files;\n\t\t\t\t\t// Filter files by allowed types if specified\n\t\t\t\t\tif ( allowedTypes && ! allowedTypes.includes( '*' ) ) {\n\t\t\t\t\t\tfilteredFiles = files.filter( ( file ) =>\n\t\t\t\t\t\t\tallowedTypes.some( ( allowedType ) => {\n\t\t\t\t\t\t\t\t// Check if the file type matches the allowed MIME type\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\tfile.type === allowedType ||\n\t\t\t\t\t\t\t\t\tfile.type.startsWith(\n\t\t\t\t\t\t\t\t\t\tallowedType.replace( '*', '' )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} )\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif ( filteredFiles.length > 0 ) {\n\t\t\t\t\t\tconst { onFileChange, onError } =\n\t\t\t\t\t\t\tregisterBatch( filteredFiles );\n\n\t\t\t\t\t\thandleUpload( {\n\t\t\t\t\t\t\tallowedTypes,\n\t\t\t\t\t\t\tfilesList: filteredFiles,\n\t\t\t\t\t\t\tonFileChange,\n\t\t\t\t\t\t\tonError,\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\t\t\t\t} }\n\t\t\t\tlabel={ __( 'Drop files to upload' ) }\n\t\t\t/>\n\t\t\t<DataViewsPicker\n\t\t\t\tdata={ mediaRecords || [] }\n\t\t\t\tfields={ fields }\n\t\t\t\tview={ view }\n\t\t\t\tonChangeView={ updateView }\n\t\t\t\tactions={ actions }\n\t\t\t\tselection={ selection }\n\t\t\t\tonChangeSelection={ setSelection }\n\t\t\t\tisLoading={ isLoading }\n\t\t\t\tpaginationInfo={ paginationInfo }\n\t\t\t\tdefaultLayouts={ defaultLayouts }\n\t\t\t\tgetItemId={ ( item: RestAttachment ) => String( item.id ) }\n\t\t\t\titemListLabel={ __( 'Media items' ) }\n\t\t\t\tonReset={ isModified ? resetToDefault : false }\n\t\t\t>\n\t\t\t\t<Stack\n\t\t\t\t\tdirection=\"row\"\n\t\t\t\t\talign=\"top\"\n\t\t\t\t\tjustify=\"space-between\"\n\t\t\t\t\tclassName=\"dataviews__view-actions\"\n\t\t\t\t\tgap=\"xs\"\n\t\t\t\t>\n\t\t\t\t\t<Stack\n\t\t\t\t\t\tdirection=\"row\"\n\t\t\t\t\t\tgap=\"sm\"\n\t\t\t\t\t\tjustify=\"start\"\n\t\t\t\t\t\tclassName=\"dataviews__search\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{ search && (\n\t\t\t\t\t\t\t<DataViewsPicker.Search label={ searchLabel } />\n\t\t\t\t\t\t) }\n\t\t\t\t\t\t<DataViewsPicker.FiltersToggle />\n\t\t\t\t\t</Stack>\n\t\t\t\t\t<Stack direction=\"row\" gap=\"xs\" style={ { flexShrink: 0 } }>\n\t\t\t\t\t\t<DataViewsPicker.LayoutSwitcher />\n\t\t\t\t\t\t<DataViewsPicker.ViewConfig />\n\t\t\t\t\t</Stack>\n\t\t\t\t</Stack>\n\t\t\t\t<DataViewsPicker.FiltersToggled className=\"dataviews-filters__container\" />\n\t\t\t\t<DataViewsPicker.Layout />\n\t\t\t\t<div\n\t\t\t\t\tclassName={ clsx( 'media-upload-modal__footer', {\n\t\t\t\t\t\t'is-uploading': uploadingFiles.length > 0,\n\t\t\t\t\t} ) }\n\t\t\t\t>\n\t\t\t\t\t<UploadStatusPopover\n\t\t\t\t\t\tuploadingFiles={ uploadingFiles }\n\t\t\t\t\t\tonDismissError={ dismissError }\n\t\t\t\t\t\tonOpenChange={ handlePopoverOpenChange }\n\t\t\t\t\t/>\n\t\t\t\t\t<DataViewsPicker.BulkActionToolbar />\n\t\t\t\t</div>\n\t\t\t</DataViewsPicker>\n\t\t\t{ createPortal(\n\t\t\t\t<SnackbarNotices\n\t\t\t\t\tclassName=\"media-upload-modal__snackbar\"\n\t\t\t\t\tcontext={ NOTICES_CONTEXT }\n\t\t\t\t/>,\n\t\t\t\tdocument.body\n\t\t\t) }\n\t\t</Modal>\n\t);\n}\n\nexport default MediaUploadModal;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { __, sprintf } from "@wordpress/i18n";
|
|
3
3
|
import { UploadError } from "./upload-error.mjs";
|
|
4
4
|
function validateMimeType(file, allowedTypes) {
|
|
5
|
-
if (!allowedTypes) {
|
|
5
|
+
if (!allowedTypes || allowedTypes.includes("*")) {
|
|
6
6
|
return;
|
|
7
7
|
}
|
|
8
8
|
const isAllowedType = allowedTypes.some((allowedType) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/validate-mime-type.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { __, sprintf } from '@wordpress/i18n';\n\n/**\n * Internal dependencies\n */\nimport { UploadError } from './upload-error';\n\n/**\n * Verifies if the caller (e.g. a block) supports this mime type.\n *\n * @param file File object.\n * @param allowedTypes List of allowed mime types.\n */\nexport function validateMimeType( file: File, allowedTypes?: string[] ) {\n\tif ( ! allowedTypes ) {\n\t\treturn;\n\t}\n\n\t// Allowed type specified by consumer.\n\tconst isAllowedType = allowedTypes.some( ( allowedType ) => {\n\t\t// If a complete mimetype is specified verify if it matches exactly the mime type of the file.\n\t\tif ( allowedType.includes( '/' ) ) {\n\t\t\treturn allowedType === file.type;\n\t\t}\n\t\t// Otherwise a general mime type is used, and we should verify if the file mimetype starts with it.\n\t\treturn file.type.startsWith( `${ allowedType }/` );\n\t} );\n\n\tif ( file.type && ! isAllowedType ) {\n\t\tthrow new UploadError( {\n\t\t\tcode: 'MIME_TYPE_NOT_SUPPORTED',\n\t\t\tmessage: sprintf(\n\t\t\t\t// translators: %s: file name.\n\t\t\t\t__( '%s: Sorry, this file type is not supported here.' ),\n\t\t\t\tfile.name\n\t\t\t),\n\t\t\tfile,\n\t\t} );\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";AAGA,SAAS,IAAI,eAAe;AAK5B,SAAS,mBAAmB;AAQrB,SAAS,iBAAkB,MAAY,cAA0B;AACvE,MAAK,CAAE,
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { __, sprintf } from '@wordpress/i18n';\n\n/**\n * Internal dependencies\n */\nimport { UploadError } from './upload-error';\n\n/**\n * Verifies if the caller (e.g. a block) supports this mime type.\n *\n * @param file File object.\n * @param allowedTypes List of allowed mime types.\n */\nexport function validateMimeType( file: File, allowedTypes?: string[] ) {\n\tif ( ! allowedTypes || allowedTypes.includes( '*' ) ) {\n\t\treturn;\n\t}\n\n\t// Allowed type specified by consumer.\n\tconst isAllowedType = allowedTypes.some( ( allowedType ) => {\n\t\t// If a complete mimetype is specified verify if it matches exactly the mime type of the file.\n\t\tif ( allowedType.includes( '/' ) ) {\n\t\t\treturn allowedType === file.type;\n\t\t}\n\t\t// Otherwise a general mime type is used, and we should verify if the file mimetype starts with it.\n\t\treturn file.type.startsWith( `${ allowedType }/` );\n\t} );\n\n\tif ( file.type && ! isAllowedType ) {\n\t\tthrow new UploadError( {\n\t\t\tcode: 'MIME_TYPE_NOT_SUPPORTED',\n\t\t\tmessage: sprintf(\n\t\t\t\t// translators: %s: file name.\n\t\t\t\t__( '%s: Sorry, this file type is not supported here.' ),\n\t\t\t\tfile.name\n\t\t\t),\n\t\t\tfile,\n\t\t} );\n\t}\n}\n"],
|
|
5
|
+
"mappings": ";AAGA,SAAS,IAAI,eAAe;AAK5B,SAAS,mBAAmB;AAQrB,SAAS,iBAAkB,MAAY,cAA0B;AACvE,MAAK,CAAE,gBAAgB,aAAa,SAAU,GAAI,GAAI;AACrD;AAAA,EACD;AAGA,QAAM,gBAAgB,aAAa,KAAM,CAAE,gBAAiB;AAE3D,QAAK,YAAY,SAAU,GAAI,GAAI;AAClC,aAAO,gBAAgB,KAAK;AAAA,IAC7B;AAEA,WAAO,KAAK,KAAK,WAAY,GAAI,WAAY,GAAI;AAAA,EAClD,CAAE;AAEF,MAAK,KAAK,QAAQ,CAAE,eAAgB;AACnC,UAAM,IAAI,YAAa;AAAA,MACtB,MAAM;AAAA,MACN,SAAS;AAAA;AAAA,QAER,GAAI,kDAAmD;AAAA,QACvD,KAAK;AAAA,MACN;AAAA,MACA;AAAA,IACD,CAAE;AAAA,EACH;AACD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/media-upload-modal/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/media-upload-modal/index.tsx"],"names":[],"mappings":"AAiDA;;GAEG;AACH,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,mBAAmB,CAAC;AAyDpE,UAAU,qBAAqB;IAC9B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAE1B;;;OAGG;IACH,QAAQ,EAAE,CAAE,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,KAAM,IAAI,CAAC;IAEvD;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAE,IAAI,EAAE;QAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QACxB,SAAS,EAAE,IAAI,EAAE,CAAC;QAClB,YAAY,CAAC,EAAE,CAAE,WAAW,EAAE,OAAO,CAAE,UAAU,CAAE,EAAE,KAAM,IAAI,CAAC;QAChE,OAAO,CAAC,EAAE,CAAE,KAAK,EAAE,KAAK,KAAM,IAAI,CAAC;QACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;KACnB,KAAM,IAAI,CAAC;IAEZ;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAE,EACjC,YAAY,EACZ,QAAgB,EAChB,KAAK,EACL,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,KAA4B,EAC5B,MAAM,EACN,aAAoB,EACpB,UAAU,EACV,MAAa,EACb,WAAkC,GAClC,EAAE,qBAAqB,sCAgZvB;AAED,eAAe,gBAAgB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/media-utils",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.44.0",
|
|
4
4
|
"description": "WordPress Media Upload Utils.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -47,20 +47,21 @@
|
|
|
47
47
|
"build-style/**"
|
|
48
48
|
],
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@wordpress/api-fetch": "^7.
|
|
51
|
-
"@wordpress/base-styles": "^6.
|
|
52
|
-
"@wordpress/blob": "^4.
|
|
53
|
-
"@wordpress/components": "^32.
|
|
54
|
-
"@wordpress/core-data": "^7.
|
|
55
|
-
"@wordpress/data": "^10.
|
|
56
|
-
"@wordpress/dataviews": "^14.
|
|
57
|
-
"@wordpress/element": "^6.
|
|
58
|
-
"@wordpress/i18n": "^6.
|
|
59
|
-
"@wordpress/icons": "^12.
|
|
60
|
-
"@wordpress/media-fields": "^0.
|
|
61
|
-
"@wordpress/notices": "^5.
|
|
62
|
-
"@wordpress/private-apis": "^1.
|
|
63
|
-
"@wordpress/ui": "^0.
|
|
50
|
+
"@wordpress/api-fetch": "^7.44.0",
|
|
51
|
+
"@wordpress/base-styles": "^6.20.0",
|
|
52
|
+
"@wordpress/blob": "^4.44.0",
|
|
53
|
+
"@wordpress/components": "^32.6.0",
|
|
54
|
+
"@wordpress/core-data": "^7.44.0",
|
|
55
|
+
"@wordpress/data": "^10.44.0",
|
|
56
|
+
"@wordpress/dataviews": "^14.1.0",
|
|
57
|
+
"@wordpress/element": "^6.44.0",
|
|
58
|
+
"@wordpress/i18n": "^6.17.0",
|
|
59
|
+
"@wordpress/icons": "^12.2.0",
|
|
60
|
+
"@wordpress/media-fields": "^0.9.0",
|
|
61
|
+
"@wordpress/notices": "^5.44.0",
|
|
62
|
+
"@wordpress/private-apis": "^1.44.0",
|
|
63
|
+
"@wordpress/ui": "^0.11.0",
|
|
64
|
+
"@wordpress/views": "^1.11.0",
|
|
64
65
|
"clsx": "^2.1.1"
|
|
65
66
|
},
|
|
66
67
|
"peerDependencies": {
|
|
@@ -69,5 +70,5 @@
|
|
|
69
70
|
"publishConfig": {
|
|
70
71
|
"access": "public"
|
|
71
72
|
},
|
|
72
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "b862d8c84121a47bbeff882f6c87e61681ce2e0d"
|
|
73
74
|
}
|
|
@@ -23,7 +23,13 @@ import { resolveSelect, useDispatch } from '@wordpress/data';
|
|
|
23
23
|
import { Modal, DropZone, FormFileUpload, Button } from '@wordpress/components';
|
|
24
24
|
import { upload as uploadIcon } from '@wordpress/icons';
|
|
25
25
|
import { DataViewsPicker } from '@wordpress/dataviews';
|
|
26
|
-
import type {
|
|
26
|
+
import type {
|
|
27
|
+
Field,
|
|
28
|
+
ActionButton,
|
|
29
|
+
SupportedLayouts,
|
|
30
|
+
View,
|
|
31
|
+
} from '@wordpress/dataviews';
|
|
32
|
+
import { useView } from '@wordpress/views';
|
|
27
33
|
import { Stack } from '@wordpress/ui';
|
|
28
34
|
import {
|
|
29
35
|
altTextField,
|
|
@@ -64,6 +70,43 @@ const NOTICES_CONTEXT = 'media-modal';
|
|
|
64
70
|
// Notice ID - reused for all upload-related notices to prevent flooding
|
|
65
71
|
const NOTICE_ID_UPLOAD_PROGRESS = 'media-modal-upload-progress';
|
|
66
72
|
|
|
73
|
+
const defaultView: View = {
|
|
74
|
+
type: LAYOUT_PICKER_GRID,
|
|
75
|
+
fields: [],
|
|
76
|
+
showTitle: false,
|
|
77
|
+
titleField: 'title',
|
|
78
|
+
mediaField: 'media_thumbnail',
|
|
79
|
+
search: '',
|
|
80
|
+
page: 1,
|
|
81
|
+
perPage: 50,
|
|
82
|
+
filters: [],
|
|
83
|
+
layout: {
|
|
84
|
+
previewSize: 170,
|
|
85
|
+
density: 'compact',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const defaultLayouts: SupportedLayouts = {
|
|
90
|
+
[ LAYOUT_PICKER_GRID ]: {
|
|
91
|
+
fields: [],
|
|
92
|
+
showTitle: false,
|
|
93
|
+
layout: {
|
|
94
|
+
previewSize: 170,
|
|
95
|
+
density: 'compact',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
[ LAYOUT_PICKER_TABLE ]: {
|
|
99
|
+
fields: [
|
|
100
|
+
'filename',
|
|
101
|
+
'filesize',
|
|
102
|
+
'media_dimensions',
|
|
103
|
+
'author',
|
|
104
|
+
'date',
|
|
105
|
+
],
|
|
106
|
+
showTitle: true,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
67
110
|
interface MediaUploadModalProps {
|
|
68
111
|
/**
|
|
69
112
|
* Array of allowed media types.
|
|
@@ -188,22 +231,13 @@ export function MediaUploadModal( {
|
|
|
188
231
|
const invalidateAttachmentResolutions =
|
|
189
232
|
useInvalidateAttachmentResolutions();
|
|
190
233
|
|
|
191
|
-
//
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
search: '',
|
|
199
|
-
page: 1,
|
|
200
|
-
perPage: 50,
|
|
201
|
-
filters: [],
|
|
202
|
-
layout: {
|
|
203
|
-
previewSize: 170,
|
|
204
|
-
density: 'compact',
|
|
205
|
-
},
|
|
206
|
-
} ) );
|
|
234
|
+
// Persist view configuration across sessions via the preferences store.
|
|
235
|
+
const { view, updateView, isModified, resetToDefault } = useView( {
|
|
236
|
+
kind: 'postType',
|
|
237
|
+
name: 'attachment',
|
|
238
|
+
slug: 'media-modal',
|
|
239
|
+
defaultView,
|
|
240
|
+
} );
|
|
207
241
|
|
|
208
242
|
// Build query args based on view properties, similar to PostList
|
|
209
243
|
const queryArgs = useMemo( () => {
|
|
@@ -451,26 +485,6 @@ export function MediaUploadModal( {
|
|
|
451
485
|
[ totalItems, totalPages ]
|
|
452
486
|
);
|
|
453
487
|
|
|
454
|
-
const defaultLayouts = useMemo(
|
|
455
|
-
() => ( {
|
|
456
|
-
[ LAYOUT_PICKER_GRID ]: {
|
|
457
|
-
fields: [],
|
|
458
|
-
showTitle: false,
|
|
459
|
-
},
|
|
460
|
-
[ LAYOUT_PICKER_TABLE ]: {
|
|
461
|
-
fields: [
|
|
462
|
-
'filename',
|
|
463
|
-
'filesize',
|
|
464
|
-
'media_dimensions',
|
|
465
|
-
'author',
|
|
466
|
-
'date',
|
|
467
|
-
],
|
|
468
|
-
showTitle: true,
|
|
469
|
-
},
|
|
470
|
-
} ),
|
|
471
|
-
[]
|
|
472
|
-
);
|
|
473
|
-
|
|
474
488
|
// Build accept attribute from allowedTypes
|
|
475
489
|
const acceptTypes = useMemo( () => {
|
|
476
490
|
if ( allowedTypes?.includes( '*' ) ) {
|
|
@@ -544,7 +558,7 @@ export function MediaUploadModal( {
|
|
|
544
558
|
data={ mediaRecords || [] }
|
|
545
559
|
fields={ fields }
|
|
546
560
|
view={ view }
|
|
547
|
-
onChangeView={
|
|
561
|
+
onChangeView={ updateView }
|
|
548
562
|
actions={ actions }
|
|
549
563
|
selection={ selection }
|
|
550
564
|
onChangeSelection={ setSelection }
|
|
@@ -553,6 +567,7 @@ export function MediaUploadModal( {
|
|
|
553
567
|
defaultLayouts={ defaultLayouts }
|
|
554
568
|
getItemId={ ( item: RestAttachment ) => String( item.id ) }
|
|
555
569
|
itemListLabel={ __( 'Media items' ) }
|
|
570
|
+
onReset={ isModified ? resetToDefault : false }
|
|
556
571
|
>
|
|
557
572
|
<Stack
|
|
558
573
|
direction="row"
|
|
@@ -54,4 +54,16 @@ describe( 'validateMimeType', () => {
|
|
|
54
54
|
} )
|
|
55
55
|
);
|
|
56
56
|
} );
|
|
57
|
+
|
|
58
|
+
it( 'should not error if allowedTypes contains the wildcard "*"', () => {
|
|
59
|
+
expect( () => {
|
|
60
|
+
validateMimeType( xmlFile, [ '*' ] );
|
|
61
|
+
} ).not.toThrow();
|
|
62
|
+
} );
|
|
63
|
+
|
|
64
|
+
it( 'should not error if allowedTypes contains "*" alongside other types', () => {
|
|
65
|
+
expect( () => {
|
|
66
|
+
validateMimeType( xmlFile, [ 'image', '*' ] );
|
|
67
|
+
} ).not.toThrow();
|
|
68
|
+
} );
|
|
57
69
|
} );
|
|
@@ -15,7 +15,7 @@ import { UploadError } from './upload-error';
|
|
|
15
15
|
* @param allowedTypes List of allowed mime types.
|
|
16
16
|
*/
|
|
17
17
|
export function validateMimeType( file: File, allowedTypes?: string[] ) {
|
|
18
|
-
if ( ! allowedTypes ) {
|
|
18
|
+
if ( ! allowedTypes || allowedTypes.includes( '*' ) ) {
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
|