Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Media Utils: Add TypeScript support and export more utils #64784

Merged
merged 16 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/editor/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
{ "path": "../i18n" },
{ "path": "../icons" },
{ "path": "../keycodes" },
{ "path": "../media-utils" },
{ "path": "../notices" },
{ "path": "../plugins" },
{ "path": "../private-apis" },
Expand Down
4 changes: 4 additions & 0 deletions packages/media-utils/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New Features

- Rewrite in TypeScript, exporting all the individual utility functions.

## 5.7.0 (2024-09-05)

## 5.6.0 (2024-08-21)
Expand Down
71 changes: 70 additions & 1 deletion packages/media-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,75 @@ npm install @wordpress/media-utils --save

_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for such language features and APIs, you should include [the polyfill shipped in `@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default#polyfill) in your code._

## API

<!-- START TOKEN(Autogenerated API docs) -->

### Attachment

Undocumented declaration.

### MediaUpload

Undocumented declaration.

### RestAttachment

Undocumented declaration.

### transformAttachment

Transforms an attachment object from the REST API shape into the shape expected by the block editor and other consumers.

_Parameters_

- _attachment_ `RestAttachment`: REST API attachment object.

### uploadMedia

Upload a media file when the file upload button is activated or when adding a file to the editor via drag & drop.

_Parameters_

- _$0_ `UploadMediaArgs`: Parameters object passed to the function.
- _$0.allowedTypes_ `UploadMediaArgs[ 'allowedTypes' ]`: Array with the types of media that can be uploaded, if unset all types are allowed.
- _$0.additionalData_ `UploadMediaArgs[ 'additionalData' ]`: Additional data to include in the request.
- _$0.filesList_ `UploadMediaArgs[ 'filesList' ]`: List of files.
- _$0.maxUploadFileSize_ `UploadMediaArgs[ 'maxUploadFileSize' ]`: Maximum upload size in bytes allowed for the site.
- _$0.onError_ `UploadMediaArgs[ 'onError' ]`: Function called when an error happens.
- _$0.onFileChange_ `UploadMediaArgs[ 'onFileChange' ]`: Function called each time a file or a temporary representation of the file is available.
- _$0.wpAllowedMimeTypes_ `UploadMediaArgs[ 'wpAllowedMimeTypes' ]`: List of allowed mime types and file extensions.
- _$0.signal_ `UploadMediaArgs[ 'signal' ]`: Abort signal.

### validateFileSize

Verifies whether the file is within the file upload size limits for the site.

_Parameters_

- _file_ `File`: File object.
- _maxUploadFileSize_ `number`: Maximum upload size in bytes allowed for the site.

### validateMimeType

Verifies if the caller (e.g. a block) supports this mime type.

_Parameters_

- _file_ `File`: File object.
- _allowedTypes_ `string[]`: List of allowed mime types.

### validateMimeTypeForUser

Verifies if the user is allowed to upload this mime type.

_Parameters_

- _file_ `File`: File object.
- _wpAllowedMimeTypes_ `Record< string, string > | null`: List of allowed mime types and file extensions.

<!-- END TOKEN(Autogenerated API docs) -->

## Usage

### uploadMedia
Expand Down Expand Up @@ -43,7 +112,7 @@ Beware that first onFileChange is called with temporary blob URLs and then with
### MediaUpload

Media upload component provides a UI button that allows users to open the WordPress media library. It is normally used in conjunction with the filter `editor.MediaUpload`.
The component follows the interface specified in [https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/media-upload/README.md](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/media-upload/README.md), and more details regarding its usage can be checked there.
The component follows the interface specified in <https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/media-upload/README.md>, and more details regarding its usage can be checked there.

## Contributing to this package

Expand Down
3 changes: 2 additions & 1 deletion packages/media-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"repository": {
"type": "git",
"url": "https://github.com/WordPress/gutenberg.git",
"directory": "packages/url"
"directory": "packages/media-utils"
},
"bugs": {
"url": "https://github.com/WordPress/gutenberg/issues"
Expand All @@ -25,6 +25,7 @@
},
"main": "build/index.js",
"module": "build-module/index.js",
"types": "build-types",
"dependencies": {
"@babel/runtime": "^7.16.0",
"@wordpress/api-fetch": "file:../api-fetch",
Expand Down
2 changes: 0 additions & 2 deletions packages/media-utils/src/index.js

This file was deleted.

9 changes: 9 additions & 0 deletions packages/media-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export * from './components';

export { uploadMedia } from './utils/upload-media';
ntsekouras marked this conversation as resolved.
Show resolved Hide resolved
export { transformAttachment } from './utils/transform-attachment';
export { validateFileSize } from './utils/validate-file-size';
export { validateMimeType } from './utils/validate-mime-type';
export { validateMimeTypeForUser } from './utils/validate-mime-type-for-user';

export type { Attachment, RestAttachment } from './utils/types';
33 changes: 33 additions & 0 deletions packages/media-utils/src/utils/flatten-form-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Determines whether the passed argument appears to be a plain object.
*
* @param data The object to inspect.
*/
function isPlainObject( data: unknown ): data is Record< string, unknown > {
return (
data !== null &&
typeof data === 'object' &&
Object.getPrototypeOf( data ) === Object.prototype
);
}

/**
* Recursively flatten data passed to form data, to allow using multi-level objects.
*
* @param {FormData} formData Form data object.
* @param {string} key Key to amend to form data object
* @param {string|Object} data Data to be amended to form data.
*/
export function flattenFormData(
formData: FormData,
key: string,
data: string | undefined | Record< string, string >
) {
if ( isPlainObject( data ) ) {
for ( const [ name, value ] of Object.entries( data ) ) {
flattenFormData( formData, `${ key }[${ name }]`, value );
}
} else if ( data !== undefined ) {
formData.append( key, String( data ) );
}
}
29 changes: 29 additions & 0 deletions packages/media-utils/src/utils/get-mime-types-array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Browsers may use unexpected mime types, and they differ from browser to browser.
* This function computes a flexible array of mime types from the mime type structured provided by the server.
* Converts { jpg|jpeg|jpe: "image/jpeg" } into [ "image/jpeg", "image/jpg", "image/jpeg", "image/jpe" ]
*
* @param {?Object} wpMimeTypesObject Mime type object received from the server.
* Extensions are keys separated by '|' and values are mime types associated with an extension.
*
* @return An array of mime types or null
*/
export function getMimeTypesArray(
wpMimeTypesObject?: Record< string, string > | null
) {
if ( ! wpMimeTypesObject ) {
return null;
}
return Object.entries( wpMimeTypesObject ).flatMap(
( [ extensionsString, mime ] ) => {
const [ type ] = mime.split( '/' );
const extensions = extensionsString.split( '|' );
return [
mime,
...extensions.map(
( extension ) => `${ type }/${ extension }`
),
];
}
);
}
1 change: 0 additions & 1 deletion packages/media-utils/src/utils/index.js

This file was deleted.

49 changes: 49 additions & 0 deletions packages/media-utils/src/utils/test/flatten-form-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Internal dependencies
*/
import { flattenFormData } from '../flatten-form-data';

describe( 'flattenFormData', () => {
it( 'should flatten nested data structure', () => {
const data = new FormData();

class RichTextData {
toString() {
return 'i am rich text';
}
}

const additionalData = {
foo: null,
bar: 1234,
meta: {
nested: 'foo',
dothis: true,
dothat: false,
supermeta: {
nested: 'baz',
},
},
customClass: new RichTextData(),
};

for ( const [ key, value ] of Object.entries( additionalData ) ) {
flattenFormData(
data,
key,
value as Parameters< typeof flattenFormData >[ 2 ]
);
}

const actual = Object.fromEntries( data.entries() );
expect( actual ).toStrictEqual( {
bar: '1234',
foo: 'null',
'meta[dothat]': 'false',
'meta[dothis]': 'true',
'meta[nested]': 'foo',
'meta[supermeta][nested]': 'baz',
customClass: 'i am rich text',
} );
} );
} );
47 changes: 47 additions & 0 deletions packages/media-utils/src/utils/test/get-mime-types-array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Internal dependencies
*/
import { getMimeTypesArray } from '../get-mime-types-array';

describe( 'getMimeTypesArray', () => {
it( 'should return null if it is "falsy" e.g: undefined or null', () => {
expect( getMimeTypesArray( null ) ).toEqual( null );
expect( getMimeTypesArray( undefined ) ).toEqual( null );
} );

it( 'should return an empty array if an empty object is passed', () => {
expect( getMimeTypesArray( {} ) ).toEqual( [] );
} );

it( 'should return the type plus a new mime type with type and subtype with the extension if a type is passed', () => {
expect( getMimeTypesArray( { ext: 'chicken' } ) ).toEqual( [
'chicken',
'chicken/ext',
] );
} );

it( 'should return the mime type passed and a new mime type with type and the extension as subtype', () => {
expect( getMimeTypesArray( { ext: 'chicken/ribs' } ) ).toEqual( [
'chicken/ribs',
'chicken/ext',
] );
} );

it( 'should return the mime type passed and an additional mime type per extension supported', () => {
expect( getMimeTypesArray( { 'jpg|jpeg|jpe': 'image/jpeg' } ) ).toEqual(
[ 'image/jpeg', 'image/jpg', 'image/jpeg', 'image/jpe' ]
);
} );

it( 'should handle multiple mime types', () => {
expect(
getMimeTypesArray( { 'ext|aaa': 'chicken/ribs', aaa: 'bbb' } )
).toEqual( [
'chicken/ribs',
'chicken/ext',
'chicken/aaa',
'bbb',
'bbb/aaa',
] );
} );
} );
24 changes: 24 additions & 0 deletions packages/media-utils/src/utils/test/upload-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Internal dependencies
*/
import { UploadError } from '../upload-error';

describe( 'UploadError', () => {
it( 'holds error code and file name', () => {
const file = new File( [], 'example.jpg', {
lastModified: 1234567891,
type: 'image/jpeg',
} );

const error = new UploadError( {
code: 'some_error',
message: 'An error occurred',
file,
} );

expect( error ).toStrictEqual( expect.any( Error ) );
expect( error.code ).toBe( 'some_error' );
expect( error.message ).toBe( 'An error occurred' );
expect( error.file ).toBe( file );
} );
} );
Loading
Loading