@wordpress/media-fields 0.2.0 → 0.2.1-next.06ee73755.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/LICENSE.md +1 -1
- package/build/alt_text/{index.js → index.cjs} +2 -3
- package/build/alt_text/{index.js.map → index.cjs.map} +2 -2
- package/build/attached_to/edit.cjs +153 -0
- package/build/attached_to/edit.cjs.map +7 -0
- package/build/attached_to/index.cjs +49 -0
- package/build/attached_to/index.cjs.map +7 -0
- package/build/attached_to/view.cjs +49 -0
- package/build/attached_to/view.cjs.map +7 -0
- package/build/caption/{index.js → index.cjs} +3 -4
- package/build/caption/{index.js.map → index.cjs.map} +2 -2
- package/build/date_added/index.cjs +41 -0
- package/build/date_added/index.cjs.map +7 -0
- package/build/date_modified/index.cjs +41 -0
- package/build/date_modified/index.cjs.map +7 -0
- package/build/description/{index.js → index.cjs} +3 -4
- package/build/description/{index.js.map → index.cjs.map} +2 -2
- package/build/filename/{index.js → index.cjs} +2 -2
- package/build/filename/{view.js → view.cjs} +1 -1
- package/build/filesize/{index.js → index.cjs} +1 -1
- package/build/{index.js → index.cjs} +18 -9
- package/build/index.cjs.map +7 -0
- package/build/media_dimensions/{index.js → index.cjs} +1 -1
- package/build/media_thumbnail/{index.js → index.cjs} +2 -2
- package/build/media_thumbnail/{view.js → view.cjs} +43 -37
- package/build/media_thumbnail/view.cjs.map +7 -0
- package/build/mime_type/{index.js → index.cjs} +4 -2
- package/build/mime_type/{index.js.map → index.cjs.map} +2 -2
- package/build/{types.js → types.cjs} +1 -1
- package/build/utils/{get-media-type-from-mime-type.js → get-media-type-from-mime-type.cjs} +1 -1
- package/build/utils/{get-raw-content.js → get-raw-content.cjs} +1 -1
- package/build/utils/{get-rendered-content.js → get-rendered-content.cjs} +1 -1
- package/build-module/alt_text/{index.js → index.mjs} +2 -3
- package/build-module/alt_text/{index.js.map → index.mjs.map} +2 -2
- package/build-module/attached_to/edit.mjs +135 -0
- package/build-module/attached_to/edit.mjs.map +7 -0
- package/build-module/attached_to/index.mjs +18 -0
- package/build-module/attached_to/index.mjs.map +7 -0
- package/build-module/attached_to/view.mjs +28 -0
- package/build-module/attached_to/view.mjs.map +7 -0
- package/build-module/caption/{index.js → index.mjs} +3 -4
- package/build-module/caption/{index.js.map → index.mjs.map} +2 -2
- package/build-module/date_added/index.mjs +20 -0
- package/build-module/date_added/index.mjs.map +7 -0
- package/build-module/date_modified/index.mjs +20 -0
- package/build-module/date_modified/index.mjs.map +7 -0
- package/build-module/description/{index.js → index.mjs} +3 -4
- package/build-module/description/{index.js.map → index.mjs.map} +2 -2
- package/build-module/filename/{index.js → index.mjs} +2 -2
- package/build-module/filename/{view.js → view.mjs} +1 -1
- package/build-module/filesize/{index.js → index.mjs} +1 -1
- package/build-module/index.mjs +26 -0
- package/build-module/index.mjs.map +7 -0
- package/build-module/media_dimensions/{index.js → index.mjs} +1 -1
- package/build-module/media_thumbnail/{index.js → index.mjs} +2 -2
- package/build-module/media_thumbnail/{view.js → view.mjs} +43 -37
- package/build-module/media_thumbnail/view.mjs.map +7 -0
- package/build-module/mime_type/{index.js → index.mjs} +4 -2
- package/build-module/mime_type/{index.js.map → index.mjs.map} +2 -2
- package/build-module/types.mjs +1 -0
- package/build-module/utils/{get-media-type-from-mime-type.js → get-media-type-from-mime-type.mjs} +1 -1
- package/build-module/utils/{get-raw-content.js → get-raw-content.mjs} +1 -1
- package/build-module/utils/{get-rendered-content.js → get-rendered-content.mjs} +1 -1
- package/build-style/style-rtl.css +4 -0
- package/build-style/style.css +4 -0
- package/build-types/alt_text/index.d.ts.map +1 -1
- package/build-types/attached_to/edit.d.ts +29 -0
- package/build-types/attached_to/edit.d.ts.map +1 -0
- package/build-types/attached_to/index.d.ts +8 -0
- package/build-types/attached_to/index.d.ts.map +1 -0
- package/build-types/attached_to/view.d.ts +7 -0
- package/build-types/attached_to/view.d.ts.map +1 -0
- package/build-types/caption/index.d.ts.map +1 -1
- package/build-types/date_added/index.d.ts +8 -0
- package/build-types/date_added/index.d.ts.map +1 -0
- package/build-types/date_modified/index.d.ts +8 -0
- package/build-types/date_modified/index.d.ts.map +1 -0
- package/build-types/description/index.d.ts.map +1 -1
- package/build-types/filename/test/index.test.d.ts +2 -0
- package/build-types/filename/test/index.test.d.ts.map +1 -0
- package/build-types/filename/test/view.test.d.ts +2 -0
- package/build-types/filename/test/view.test.d.ts.map +1 -0
- package/build-types/filesize/test/index.test.d.ts +2 -0
- package/build-types/filesize/test/index.test.d.ts.map +1 -0
- package/build-types/index.d.ts +3 -0
- package/build-types/index.d.ts.map +1 -1
- package/build-types/media_dimensions/test/index.test.d.ts +2 -0
- package/build-types/media_dimensions/test/index.test.d.ts.map +1 -0
- package/build-types/media_thumbnail/view.d.ts.map +1 -1
- package/build-types/mime_type/index.d.ts.map +1 -1
- package/build-types/stories/index.story.d.ts.map +1 -1
- package/package.json +30 -15
- package/src/alt_text/index.tsx +0 -1
- package/src/attached_to/edit.tsx +185 -0
- package/src/attached_to/index.tsx +24 -0
- package/src/attached_to/view.tsx +43 -0
- package/src/caption/index.tsx +0 -1
- package/src/date_added/index.tsx +26 -0
- package/src/date_modified/index.tsx +26 -0
- package/src/description/index.tsx +0 -1
- package/src/filename/test/index.test.ts +59 -0
- package/src/filename/test/view.test.tsx +87 -0
- package/src/filesize/test/index.test.tsx +107 -0
- package/src/index.ts +3 -0
- package/src/media_dimensions/test/index.test.ts +132 -0
- package/src/media_thumbnail/view.tsx +63 -46
- package/src/mime_type/index.ts +3 -1
- package/src/stories/index.story.tsx +85 -2
- package/build/index.js.map +0 -7
- package/build/media_thumbnail/view.js.map +0 -7
- package/build-module/index.js +0 -20
- package/build-module/index.js.map +0 -7
- package/build-module/media_thumbnail/view.js.map +0 -7
- package/build-module/types.js +0 -1
- package/tsconfig.json +0 -31
- package/tsconfig.tsbuildinfo +0 -1
- /package/build/filename/{index.js.map → index.cjs.map} +0 -0
- /package/build/filename/{view.js.map → view.cjs.map} +0 -0
- /package/build/filesize/{index.js.map → index.cjs.map} +0 -0
- /package/build/media_dimensions/{index.js.map → index.cjs.map} +0 -0
- /package/build/media_thumbnail/{index.js.map → index.cjs.map} +0 -0
- /package/build/{types.js.map → types.cjs.map} +0 -0
- /package/build/utils/{get-media-type-from-mime-type.js.map → get-media-type-from-mime-type.cjs.map} +0 -0
- /package/build/utils/{get-raw-content.js.map → get-raw-content.cjs.map} +0 -0
- /package/build/utils/{get-rendered-content.js.map → get-rendered-content.cjs.map} +0 -0
- /package/build-module/filename/{index.js.map → index.mjs.map} +0 -0
- /package/build-module/filename/{view.js.map → view.mjs.map} +0 -0
- /package/build-module/filesize/{index.js.map → index.mjs.map} +0 -0
- /package/build-module/media_dimensions/{index.js.map → index.mjs.map} +0 -0
- /package/build-module/media_thumbnail/{index.js.map → index.mjs.map} +0 -0
- /package/build-module/{types.js.map → types.mjs.map} +0 -0
- /package/build-module/utils/{get-media-type-from-mime-type.js.map → get-media-type-from-mime-type.mjs.map} +0 -0
- /package/build-module/utils/{get-raw-content.js.map → get-raw-content.mjs.map} +0 -0
- /package/build-module/utils/{get-rendered-content.js.map → get-rendered-content.mjs.map} +0 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
__experimentalFetchLinkSuggestions as fetchLinkSuggestions,
|
|
6
|
+
store as coreStore,
|
|
7
|
+
} from '@wordpress/core-data';
|
|
8
|
+
import { Button, ComboboxControl } from '@wordpress/components';
|
|
9
|
+
import { __ } from '@wordpress/i18n';
|
|
10
|
+
import { useState, createInterpolateElement } from '@wordpress/element';
|
|
11
|
+
import { debounce } from '@wordpress/compose';
|
|
12
|
+
import { useSelect } from '@wordpress/data';
|
|
13
|
+
import type { DataFormControlProps } from '@wordpress/dataviews';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Internal dependencies
|
|
17
|
+
*/
|
|
18
|
+
import type { MediaItem } from '../types';
|
|
19
|
+
import { getRenderedContent } from '../utils/get-rendered-content';
|
|
20
|
+
|
|
21
|
+
export type SearchResult = {
|
|
22
|
+
/**
|
|
23
|
+
* Post or term id.
|
|
24
|
+
*/
|
|
25
|
+
id: number;
|
|
26
|
+
/**
|
|
27
|
+
* Link url.
|
|
28
|
+
*/
|
|
29
|
+
url: string;
|
|
30
|
+
/**
|
|
31
|
+
* Title of the link.
|
|
32
|
+
*/
|
|
33
|
+
title: string;
|
|
34
|
+
/**
|
|
35
|
+
* The taxonomy or post type slug or type URL.
|
|
36
|
+
*/
|
|
37
|
+
type: string;
|
|
38
|
+
/**
|
|
39
|
+
* Link kind of post-type or taxonomy
|
|
40
|
+
*/
|
|
41
|
+
kind?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default function MediaAttachedToEdit( {
|
|
45
|
+
data,
|
|
46
|
+
onChange,
|
|
47
|
+
}: DataFormControlProps< MediaItem > ) {
|
|
48
|
+
const defaultPost =
|
|
49
|
+
!! data.post && !! data?._embedded?.[ 'wp:attached-to' ]?.[ 0 ]
|
|
50
|
+
? [
|
|
51
|
+
{
|
|
52
|
+
label: getRenderedContent(
|
|
53
|
+
data._embedded?.[ 'wp:attached-to' ]?.[ 0 ]?.title
|
|
54
|
+
),
|
|
55
|
+
value: data.post.toString(),
|
|
56
|
+
},
|
|
57
|
+
]
|
|
58
|
+
: [];
|
|
59
|
+
const [ options, setOptions ] =
|
|
60
|
+
useState< { label: string; value: string }[] >( defaultPost );
|
|
61
|
+
const [ searchResults, setSearchResults ] = useState< SearchResult[] >(
|
|
62
|
+
[]
|
|
63
|
+
);
|
|
64
|
+
const [ isLoading, setIsLoading ] = useState( false );
|
|
65
|
+
const [ value, setValue ] = useState< string | null >(
|
|
66
|
+
data?.post?.toString() ?? null
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const postTypes = useSelect(
|
|
70
|
+
( select ) => select( coreStore ).getPostTypes(),
|
|
71
|
+
[]
|
|
72
|
+
);
|
|
73
|
+
const handleDetach = () => {
|
|
74
|
+
onChange( {
|
|
75
|
+
post: 0,
|
|
76
|
+
_embedded: { ...data?._embedded, 'wp:attached-to': undefined },
|
|
77
|
+
} );
|
|
78
|
+
setOptions( [] );
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const onValueChange = async ( filterValue: string ) => {
|
|
82
|
+
setIsLoading( true );
|
|
83
|
+
const results = await fetchLinkSuggestions(
|
|
84
|
+
filterValue,
|
|
85
|
+
/*
|
|
86
|
+
* @TODO `fetchLinkSuggestions()` should accept `perPage` as an option argument.
|
|
87
|
+
* `isInitialSuggestions` limits the result to 3, otherwise it's hardcoded to 20.
|
|
88
|
+
*/
|
|
89
|
+
{ type: 'post', isInitialSuggestions: true },
|
|
90
|
+
{}
|
|
91
|
+
);
|
|
92
|
+
setSearchResults( results );
|
|
93
|
+
const mappedSuggestions = results.map( ( result ) => {
|
|
94
|
+
return {
|
|
95
|
+
label: result.title,
|
|
96
|
+
value: result.id.toString(),
|
|
97
|
+
};
|
|
98
|
+
} );
|
|
99
|
+
setOptions( mappedSuggestions );
|
|
100
|
+
setIsLoading( false );
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Handle selection.
|
|
105
|
+
*
|
|
106
|
+
* @param {Object} selectedPostId The selected post id.
|
|
107
|
+
*/
|
|
108
|
+
const handleSelectOption = (
|
|
109
|
+
selectedPostId: string | null | undefined
|
|
110
|
+
) => {
|
|
111
|
+
if ( ! selectedPostId ) {
|
|
112
|
+
handleDetach();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
setValue( selectedPostId );
|
|
116
|
+
if ( selectedPostId ) {
|
|
117
|
+
const selectedPost = searchResults.find(
|
|
118
|
+
( result ) => result.id === Number( selectedPostId )
|
|
119
|
+
);
|
|
120
|
+
// Although unlikely, it's technically possible for selectedPost to not be found.
|
|
121
|
+
// E.g. if the user selects an option just as new search results are loaded.
|
|
122
|
+
// TODO: Add error handling for when selectedPost is not found.
|
|
123
|
+
if ( selectedPost && postTypes ) {
|
|
124
|
+
const postType = postTypes.find(
|
|
125
|
+
( _postType: { slug: string } ) =>
|
|
126
|
+
_postType.slug === selectedPost?.type
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const attachedTo = {
|
|
130
|
+
...( postType && { type: postType.slug } ),
|
|
131
|
+
id: Number( selectedPostId ),
|
|
132
|
+
title: {
|
|
133
|
+
raw: selectedPost.title,
|
|
134
|
+
rendered: selectedPost.title,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
onChange( {
|
|
139
|
+
post: Number( selectedPostId ),
|
|
140
|
+
_embedded: {
|
|
141
|
+
...data?._embedded,
|
|
142
|
+
'wp:attached-to': [ attachedTo ],
|
|
143
|
+
},
|
|
144
|
+
} );
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const help = !! data.post
|
|
150
|
+
? createInterpolateElement(
|
|
151
|
+
__(
|
|
152
|
+
'Search for a post or page to attach this media to or <button>detach current</button>.'
|
|
153
|
+
),
|
|
154
|
+
{
|
|
155
|
+
button: (
|
|
156
|
+
<Button
|
|
157
|
+
__next40pxDefaultSize
|
|
158
|
+
onClick={ handleDetach }
|
|
159
|
+
variant="link"
|
|
160
|
+
accessibleWhenDisabled
|
|
161
|
+
/>
|
|
162
|
+
),
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
: __( 'Search for a post or page to attach this media to.' );
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<ComboboxControl
|
|
169
|
+
className="dataviews-media-field__attached-to"
|
|
170
|
+
__next40pxDefaultSize
|
|
171
|
+
isLoading={ isLoading }
|
|
172
|
+
label={ __( 'Attached to' ) }
|
|
173
|
+
help={ help }
|
|
174
|
+
value={ value }
|
|
175
|
+
options={ options }
|
|
176
|
+
onFilterValueChange={ debounce(
|
|
177
|
+
( filterValue: unknown ) =>
|
|
178
|
+
onValueChange( filterValue as string ),
|
|
179
|
+
300
|
|
180
|
+
) }
|
|
181
|
+
onChange={ handleSelectOption }
|
|
182
|
+
hideLabelFromVision
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
import type { Field } from '@wordpress/dataviews';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import type { MediaItem } from '../types';
|
|
11
|
+
import MediaAttachedToView from './view';
|
|
12
|
+
import MediaAttachedToEdit from './edit';
|
|
13
|
+
|
|
14
|
+
const attachedToField: Partial< Field< MediaItem > > = {
|
|
15
|
+
id: 'attached_to',
|
|
16
|
+
type: 'text',
|
|
17
|
+
label: __( 'Attached to' ),
|
|
18
|
+
Edit: MediaAttachedToEdit,
|
|
19
|
+
render: MediaAttachedToView,
|
|
20
|
+
enableSorting: false,
|
|
21
|
+
filterBy: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default attachedToField;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useState, useEffect } from '@wordpress/element';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
import type { DataViewRenderFieldProps } from '@wordpress/dataviews';
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import type { MediaItem } from '../types';
|
|
11
|
+
import { getRenderedContent } from '../utils/get-rendered-content';
|
|
12
|
+
|
|
13
|
+
export default function MediaAttachedToView( {
|
|
14
|
+
item,
|
|
15
|
+
}: DataViewRenderFieldProps< MediaItem > ) {
|
|
16
|
+
// Store the displayed title in state, as the embedded post may be loaded
|
|
17
|
+
// asynchronously. This ensures that the title remains stable after it
|
|
18
|
+
// is updated by the user, and while it is re-fetched from the server.
|
|
19
|
+
const [ attachedPostTitle, setAttachedPostTitle ] = useState<
|
|
20
|
+
string | null
|
|
21
|
+
>( null );
|
|
22
|
+
|
|
23
|
+
const parentId = item.post;
|
|
24
|
+
const embeddedPostId = item._embedded?.[ 'wp:attached-to' ]?.[ 0 ]?.id;
|
|
25
|
+
const embeddedPostTitle =
|
|
26
|
+
item._embedded?.[ 'wp:attached-to' ]?.[ 0 ]?.title;
|
|
27
|
+
|
|
28
|
+
useEffect( () => {
|
|
29
|
+
if ( !! parentId && parentId === embeddedPostId ) {
|
|
30
|
+
setAttachedPostTitle(
|
|
31
|
+
getRenderedContent( embeddedPostTitle ) ||
|
|
32
|
+
embeddedPostId?.toString() ||
|
|
33
|
+
''
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if ( ! parentId ) {
|
|
38
|
+
setAttachedPostTitle( __( '(Unattached)' ) );
|
|
39
|
+
}
|
|
40
|
+
}, [ parentId, embeddedPostId, embeddedPostTitle ] );
|
|
41
|
+
|
|
42
|
+
return <>{ attachedPostTitle }</>;
|
|
43
|
+
}
|
package/src/caption/index.tsx
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
import { getSettings } from '@wordpress/date';
|
|
6
|
+
import type { Field } from '@wordpress/dataviews';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Internal dependencies
|
|
10
|
+
*/
|
|
11
|
+
import type { MediaItem } from '../types';
|
|
12
|
+
|
|
13
|
+
const dateAddedField: Partial< Field< MediaItem > > = {
|
|
14
|
+
id: 'date',
|
|
15
|
+
type: 'datetime',
|
|
16
|
+
label: __( 'Date added' ),
|
|
17
|
+
filterBy: {
|
|
18
|
+
operators: [ 'before', 'after' ],
|
|
19
|
+
},
|
|
20
|
+
format: {
|
|
21
|
+
datetime: getSettings().formats.datetimeAbbreviated,
|
|
22
|
+
},
|
|
23
|
+
readOnly: true,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default dateAddedField;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
import { getSettings } from '@wordpress/date';
|
|
6
|
+
import type { Field } from '@wordpress/dataviews';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Internal dependencies
|
|
10
|
+
*/
|
|
11
|
+
import type { MediaItem } from '../types';
|
|
12
|
+
|
|
13
|
+
const dateModifiedField: Partial< Field< MediaItem > > = {
|
|
14
|
+
id: 'modified',
|
|
15
|
+
type: 'datetime',
|
|
16
|
+
label: __( 'Date modified' ),
|
|
17
|
+
filterBy: {
|
|
18
|
+
operators: [ 'before', 'after' ],
|
|
19
|
+
},
|
|
20
|
+
format: {
|
|
21
|
+
datetime: getSettings().formats.datetimeAbbreviated,
|
|
22
|
+
},
|
|
23
|
+
readOnly: true,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default dateModifiedField;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import filenameField from '../index';
|
|
5
|
+
import type { MediaItem } from '../../types';
|
|
6
|
+
|
|
7
|
+
describe( 'filenameField', () => {
|
|
8
|
+
it( 'has correct field configuration', () => {
|
|
9
|
+
expect( filenameField ).toMatchObject( {
|
|
10
|
+
id: 'filename',
|
|
11
|
+
type: 'text',
|
|
12
|
+
label: 'File name',
|
|
13
|
+
enableSorting: false,
|
|
14
|
+
filterBy: false,
|
|
15
|
+
readOnly: true,
|
|
16
|
+
} );
|
|
17
|
+
} );
|
|
18
|
+
|
|
19
|
+
describe( 'getValue', () => {
|
|
20
|
+
it( 'extracts filename from source_url', () => {
|
|
21
|
+
const item: Partial< MediaItem > = {
|
|
22
|
+
source_url:
|
|
23
|
+
'https://example.com/wp-content/uploads/2024/image.jpg',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const result = filenameField.getValue?.( {
|
|
27
|
+
item: item as MediaItem,
|
|
28
|
+
} );
|
|
29
|
+
|
|
30
|
+
expect( result ).toBe( 'image.jpg' );
|
|
31
|
+
} );
|
|
32
|
+
|
|
33
|
+
it( 'returns undefined when source_url is undefined', () => {
|
|
34
|
+
const item: Partial< MediaItem > = {};
|
|
35
|
+
|
|
36
|
+
const result = filenameField.getValue?.( {
|
|
37
|
+
item: item as MediaItem,
|
|
38
|
+
} );
|
|
39
|
+
|
|
40
|
+
expect( result ).toBeUndefined();
|
|
41
|
+
} );
|
|
42
|
+
|
|
43
|
+
it( 'returns undefined when source_url is empty string', () => {
|
|
44
|
+
const item: Partial< MediaItem > = {
|
|
45
|
+
source_url: '',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const result = filenameField.getValue?.( {
|
|
49
|
+
item: item as MediaItem,
|
|
50
|
+
} );
|
|
51
|
+
|
|
52
|
+
expect( result ).toBeUndefined();
|
|
53
|
+
} );
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
it( 'has a render function', () => {
|
|
57
|
+
expect( filenameField.render ).toBeDefined();
|
|
58
|
+
} );
|
|
59
|
+
} );
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import type { NormalizedField } from '@wordpress/dataviews';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Internal dependencies
|
|
13
|
+
*/
|
|
14
|
+
import FileNameView from '../view';
|
|
15
|
+
import filenameField from '../index';
|
|
16
|
+
import type { MediaItem } from '../../types';
|
|
17
|
+
|
|
18
|
+
describe( 'FileNameView', () => {
|
|
19
|
+
describe( 'filename rendering', () => {
|
|
20
|
+
it( 'renders short filename (15 characters or less)', () => {
|
|
21
|
+
const item: Partial< MediaItem > = {
|
|
22
|
+
source_url: 'https://example.com/uploads/12345678901.jpg', // exactly 15 chars
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
render(
|
|
26
|
+
<FileNameView
|
|
27
|
+
item={ item as MediaItem }
|
|
28
|
+
field={ filenameField as NormalizedField< MediaItem > }
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Verify the filename is visible to users
|
|
33
|
+
expect( screen.getByText( '12345678901.jpg' ) ).toBeInTheDocument();
|
|
34
|
+
} );
|
|
35
|
+
|
|
36
|
+
it( 'renders long filename (more than 15 characters)', () => {
|
|
37
|
+
const longFilename =
|
|
38
|
+
'very-long-filename-that-exceeds-fifteen-characters.jpg';
|
|
39
|
+
const item: Partial< MediaItem > = {
|
|
40
|
+
source_url: `https://example.com/uploads/${ longFilename }`,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
render(
|
|
44
|
+
<FileNameView
|
|
45
|
+
item={ item as MediaItem }
|
|
46
|
+
field={ filenameField as NormalizedField< MediaItem > }
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Verify the full filename text is accessible to users
|
|
51
|
+
// (the component handles truncation via Truncate/Tooltip, but the text is still present)
|
|
52
|
+
expect( screen.getByText( longFilename ) ).toBeInTheDocument();
|
|
53
|
+
} );
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
describe( 'edge cases', () => {
|
|
57
|
+
it( 'renders nothing when source_url is missing', () => {
|
|
58
|
+
const item: Partial< MediaItem > = {};
|
|
59
|
+
|
|
60
|
+
const { container } = render(
|
|
61
|
+
<FileNameView
|
|
62
|
+
item={ item as MediaItem }
|
|
63
|
+
field={ filenameField as NormalizedField< MediaItem > }
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// When there's no source_url, component should render nothing
|
|
68
|
+
expect( container ).toBeEmptyDOMElement();
|
|
69
|
+
} );
|
|
70
|
+
|
|
71
|
+
it( 'renders nothing when source_url is empty', () => {
|
|
72
|
+
const item: Partial< MediaItem > = {
|
|
73
|
+
source_url: '',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const { container } = render(
|
|
77
|
+
<FileNameView
|
|
78
|
+
item={ item as MediaItem }
|
|
79
|
+
field={ filenameField as NormalizedField< MediaItem > }
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// When source_url is empty, component should render nothing
|
|
84
|
+
expect( container ).toBeEmptyDOMElement();
|
|
85
|
+
} );
|
|
86
|
+
} );
|
|
87
|
+
} );
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import filesizeField from '../index';
|
|
5
|
+
import type { MediaItem } from '../../types';
|
|
6
|
+
|
|
7
|
+
describe( 'filesizeField', () => {
|
|
8
|
+
it( 'has correct field configuration', () => {
|
|
9
|
+
expect( filesizeField ).toMatchObject( {
|
|
10
|
+
id: 'filesize',
|
|
11
|
+
type: 'text',
|
|
12
|
+
label: 'File size',
|
|
13
|
+
enableSorting: false,
|
|
14
|
+
filterBy: false,
|
|
15
|
+
readOnly: true,
|
|
16
|
+
} );
|
|
17
|
+
} );
|
|
18
|
+
|
|
19
|
+
describe( 'getValue - byte formatting logic', () => {
|
|
20
|
+
it( 'returns empty string for 0 bytes due to truthy check', () => {
|
|
21
|
+
const item = {
|
|
22
|
+
media_details: {
|
|
23
|
+
filesize: 0,
|
|
24
|
+
sizes: {},
|
|
25
|
+
},
|
|
26
|
+
} as MediaItem;
|
|
27
|
+
|
|
28
|
+
const result = filesizeField.getValue?.( {
|
|
29
|
+
item,
|
|
30
|
+
} );
|
|
31
|
+
|
|
32
|
+
expect( result ).toBe( '' );
|
|
33
|
+
} );
|
|
34
|
+
|
|
35
|
+
it.each( [
|
|
36
|
+
[ 512, /^512\s+B$/, 'bytes (less than 1 KB)' ],
|
|
37
|
+
[ 1024 * 50, /^50\s+KB$/, '50 kilobytes' ],
|
|
38
|
+
[ 1024 * 1024 * 5, /^5\s+MB$/, '5 megabytes' ],
|
|
39
|
+
[ 1024 * 1024 * 1024 * 2.5, /^2\.5\s+GB$/, '2.5 gigabytes' ],
|
|
40
|
+
[ 1024 * 1024 * 1024 * 1024 * 1.5, /^1\.5\s+TB$/, '1.5 terabytes' ],
|
|
41
|
+
[
|
|
42
|
+
1024 * 1024 * 3.14159,
|
|
43
|
+
/^3\.14\s+MB$/,
|
|
44
|
+
'fractional sizes with proper decimals',
|
|
45
|
+
],
|
|
46
|
+
[ 1024, /^1\s+KB$/, 'boundary value (exactly 1 KB)' ],
|
|
47
|
+
] )(
|
|
48
|
+
'formats %s bytes correctly: %s',
|
|
49
|
+
( filesize, expected, description ) => {
|
|
50
|
+
const item = {
|
|
51
|
+
media_details: {
|
|
52
|
+
filesize,
|
|
53
|
+
sizes: {},
|
|
54
|
+
},
|
|
55
|
+
} as MediaItem;
|
|
56
|
+
|
|
57
|
+
const result = filesizeField.getValue?.( {
|
|
58
|
+
item,
|
|
59
|
+
} );
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
expect( result ).toMatch( expected );
|
|
63
|
+
} catch ( error ) {
|
|
64
|
+
const message =
|
|
65
|
+
error instanceof Error
|
|
66
|
+
? error.message
|
|
67
|
+
: String( error );
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Failed to format filesize (${ description }): ${ message }`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
it.each( [
|
|
76
|
+
[ { media_details: { sizes: {} } }, 'when filesize is missing' ],
|
|
77
|
+
[ {}, 'when media_details is missing' ],
|
|
78
|
+
] )( 'returns empty string %s', ( item, description ) => {
|
|
79
|
+
const result = filesizeField.getValue?.( {
|
|
80
|
+
item: item as MediaItem,
|
|
81
|
+
} );
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
expect( result ).toBe( '' );
|
|
85
|
+
} catch ( error ) {
|
|
86
|
+
const message =
|
|
87
|
+
error instanceof Error ? error.message : String( error );
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Failed getValue test (${ description }): ${ message }`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
} );
|
|
93
|
+
} );
|
|
94
|
+
|
|
95
|
+
describe( 'isVisible', () => {
|
|
96
|
+
it.each( [
|
|
97
|
+
[ { media_details: { filesize: 1024, sizes: {} } }, true ],
|
|
98
|
+
[ { media_details: { filesize: 0, sizes: {} } }, false ],
|
|
99
|
+
[ { media_details: { sizes: {} } }, false ],
|
|
100
|
+
[ {}, false ],
|
|
101
|
+
] )( 'returns %s for item %#', ( item, expected ) => {
|
|
102
|
+
const result = filesizeField.isVisible?.( item as MediaItem );
|
|
103
|
+
|
|
104
|
+
expect( result ).toBe( expected );
|
|
105
|
+
} );
|
|
106
|
+
} );
|
|
107
|
+
} );
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { default as altTextField } from './alt_text';
|
|
2
|
+
export { default as attachedToField } from './attached_to';
|
|
2
3
|
export { default as captionField } from './caption';
|
|
4
|
+
export { default as dateAddedField } from './date_added';
|
|
5
|
+
export { default as dateModifiedField } from './date_modified';
|
|
3
6
|
export { default as descriptionField } from './description';
|
|
4
7
|
export { default as filenameField } from './filename';
|
|
5
8
|
export { default as filesizeField } from './filesize';
|