@atlaskit/editor-plugin-media 1.34.10 → 1.35.1

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.
@@ -421,4 +421,11 @@ export class MediaNodeUpdater {
421
421
  }
422
422
  const hasPrivateAttrsChanged = (currentAttrs, newAttrs) => {
423
423
  return currentAttrs.__fileName !== newAttrs.__fileName || currentAttrs.__fileMimeType !== newAttrs.__fileMimeType || currentAttrs.__fileSize !== newAttrs.__fileSize || currentAttrs.__contextId !== newAttrs.__contextId;
424
+ };
425
+ export const createMediaNodeUpdater = props => {
426
+ const updaterProps = {
427
+ ...props,
428
+ isMediaSingle: true
429
+ };
430
+ return new MediaNodeUpdater(updaterProps);
424
431
  };
@@ -31,10 +31,14 @@ import ResizableMediaSingleNext from '../ui/ResizableMediaSingle/ResizableMediaS
31
31
  import { isMediaBlobUrlFromAttrs } from '../utils/media-common';
32
32
  import { hasPrivateAttrsChanged } from './helpers';
33
33
  import { MediaNodeUpdater } from './mediaNodeUpdater';
34
+ import { MediaSingleNodeNext } from './mediaSingleNext';
34
35
  import { MediaSingleNodeSelector } from './styles';
35
36
  const figureWrapperStyles = css({
36
37
  margin: 0
37
38
  });
39
+ /*
40
+ * @deprecated Please use the MediaSingleNodeNext
41
+ */
38
42
  // eslint-disable-next-line @repo/internal/react/no-class-components
39
43
  export default class MediaSingleNode extends Component {
40
44
  constructor(...args) {
@@ -474,6 +478,29 @@ const MediaSingleNodeWrapper = ({
474
478
  editorViewModeState
475
479
  } = useSharedPluginState(pluginInjectionApi, ['width', 'media', 'annotation', 'editorDisabled', 'editorViewMode']);
476
480
  const mediaProvider = useMemo(() => mediaState !== null && mediaState !== void 0 && mediaState.mediaProvider ? Promise.resolve(mediaState === null || mediaState === void 0 ? void 0 : mediaState.mediaProvider) : undefined, [mediaState === null || mediaState === void 0 ? void 0 : mediaState.mediaProvider]);
481
+ if (fg('platform_editor_react18_phase2__media_single')) {
482
+ return jsx(MediaSingleNodeNext, {
483
+ width: (widthState === null || widthState === void 0 ? void 0 : widthState.width) || 0,
484
+ lineLength: (widthState === null || widthState === void 0 ? void 0 : widthState.lineLength) || 0,
485
+ node: node,
486
+ getPos: getPos,
487
+ mediaProvider: mediaProvider,
488
+ contextIdentifierProvider: contextIdentifierProvider,
489
+ mediaOptions: mediaOptions,
490
+ view: view,
491
+ fullWidthMode: fullWidthMode,
492
+ selected: selected,
493
+ eventDispatcher: eventDispatcher,
494
+ mediaPluginState: mediaState !== null && mediaState !== void 0 ? mediaState : undefined,
495
+ annotationPluginState: annotationState !== null && annotationState !== void 0 ? annotationState : undefined,
496
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
497
+ forwardRef: forwardRef,
498
+ pluginInjectionApi: pluginInjectionApi,
499
+ editorDisabled: editorDisabledState === null || editorDisabledState === void 0 ? void 0 : editorDisabledState.editorDisabled,
500
+ editorViewMode: (editorViewModeState === null || editorViewModeState === void 0 ? void 0 : editorViewModeState.mode) === 'view',
501
+ editorAppearance: editorAppearance
502
+ });
503
+ }
477
504
  return jsx(MediaSingleNode, {
478
505
  width: widthState.width,
479
506
  lineLength: widthState.lineLength,
@@ -0,0 +1,543 @@
1
+ /**
2
+ * @jsxRuntime classic
3
+ * @jsx jsx
4
+ * @jsxFrag
5
+ */
6
+ import React, { Fragment } from 'react';
7
+
8
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
9
+ import { css, jsx } from '@emotion/react';
10
+ import { usePreviousState } from '@atlaskit/editor-common/hooks';
11
+ import { calcMediaSinglePixelWidth, DEFAULT_IMAGE_HEIGHT, DEFAULT_IMAGE_WIDTH, ExternalImageBadge, getMaxWidthForNestedNode, MEDIA_SINGLE_GUTTER_SIZE, MediaBadges } from '@atlaskit/editor-common/media-single';
12
+ import { MediaSingle } from '@atlaskit/editor-common/ui';
13
+ import { browser } from '@atlaskit/editor-common/utils';
14
+ import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
15
+ import { findParentNodeOfTypeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
16
+ import { getAttrsFromUrl } from '@atlaskit/media-client';
17
+ import { fg } from '@atlaskit/platform-feature-flags';
18
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
19
+ import { insertAndSelectCaptionFromMediaSinglePos } from '../commands/captions';
20
+ import { CaptionPlaceholder, CaptionPlaceholderButton } from '../ui/CaptionPlaceholder';
21
+ import { CommentBadge, CommentBadgeNextWrapper } from '../ui/CommentBadge';
22
+ import ResizableMediaSingle from '../ui/ResizableMediaSingle';
23
+ import ResizableMediaSingleNext from '../ui/ResizableMediaSingle/ResizableMediaSingleNext';
24
+ import { isMediaBlobUrlFromAttrs } from '../utils/media-common';
25
+ import { hasPrivateAttrsChanged } from './helpers';
26
+ import { createMediaNodeUpdater } from './mediaNodeUpdater';
27
+ import { MediaSingleNodeSelector } from './styles';
28
+ const figureWrapperStyles = css({
29
+ margin: 0
30
+ });
31
+ const useMediaNodeUpdater = ({
32
+ mediaProvider,
33
+ mediaNode,
34
+ dispatchAnalyticsEvent,
35
+ mediaSingleNodeProps
36
+ }) => {
37
+ const previousMediaProvider = usePreviousState(mediaProvider);
38
+ const previousMediaNode = usePreviousState(mediaNode);
39
+ const mediaNodeUpdaterRef = React.useRef(null);
40
+ const createOrUpdateMediaNodeUpdater = React.useCallback(props => {
41
+ const mediaChildNode = mediaNode.firstChild;
42
+ const updaterProps = {
43
+ ...props,
44
+ isMediaSingle: true,
45
+ node: mediaChildNode ? mediaChildNode : mediaNode,
46
+ dispatchAnalyticsEvent
47
+ };
48
+ if (!mediaNodeUpdaterRef.current) {
49
+ mediaNodeUpdaterRef.current = createMediaNodeUpdater(updaterProps);
50
+ } else {
51
+ mediaNodeUpdaterRef.current.setProps(updaterProps);
52
+ }
53
+ }, [mediaNode, dispatchAnalyticsEvent]);
54
+ React.useEffect(() => {
55
+ // Forced updates not required on mobile
56
+ if (mediaSingleNodeProps.isCopyPasteEnabled === false) {
57
+ return;
58
+ }
59
+ if (!mediaNodeUpdaterRef.current || previousMediaProvider !== mediaProvider) {
60
+ var _mediaNodeUpdaterRef$;
61
+ createOrUpdateMediaNodeUpdater(mediaSingleNodeProps);
62
+ (_mediaNodeUpdaterRef$ = mediaNodeUpdaterRef.current) === null || _mediaNodeUpdaterRef$ === void 0 ? void 0 : _mediaNodeUpdaterRef$.updateMediaSingleFileAttrs();
63
+ } else if (mediaNode.firstChild && previousMediaNode !== null && previousMediaNode !== void 0 && previousMediaNode.firstChild && mediaNode.firstChild !== (previousMediaNode === null || previousMediaNode === void 0 ? void 0 : previousMediaNode.firstChild)) {
64
+ const attrsChanged = hasPrivateAttrsChanged(previousMediaNode.firstChild.attrs, mediaNode.firstChild.attrs);
65
+ if (attrsChanged) {
66
+ var _mediaNodeUpdaterRef$2;
67
+ createOrUpdateMediaNodeUpdater(mediaSingleNodeProps);
68
+ // We need to call this method on any prop change since attrs can get removed with collab editing
69
+ (_mediaNodeUpdaterRef$2 = mediaNodeUpdaterRef.current) === null || _mediaNodeUpdaterRef$2 === void 0 ? void 0 : _mediaNodeUpdaterRef$2.updateMediaSingleFileAttrs();
70
+ }
71
+ }
72
+ }, [createOrUpdateMediaNodeUpdater, mediaNode, mediaProvider, mediaSingleNodeProps, previousMediaNode, previousMediaProvider]);
73
+ return mediaNodeUpdaterRef.current;
74
+ };
75
+ const mediaAsyncOperations = async props => {
76
+ const updatedDimensions = await props.updater.getRemoteDimensions();
77
+ const currentAttrs = props.mediaChildNode.attrs;
78
+ if (updatedDimensions && ((currentAttrs === null || currentAttrs === void 0 ? void 0 : currentAttrs.width) !== updatedDimensions.width || (currentAttrs === null || currentAttrs === void 0 ? void 0 : currentAttrs.height) !== updatedDimensions.height)) {
79
+ props.updater.updateDimensions(updatedDimensions);
80
+ }
81
+ if (props.mediaChildNode.attrs.type === 'external' && props.mediaChildNode.attrs.__external) {
82
+ const updatingNode = props.updater.handleExternalMedia(props.getPos);
83
+ props.addPendingTask(updatingNode);
84
+ await updatingNode;
85
+ return;
86
+ }
87
+ const contextId = props.updater.getNodeContextId();
88
+ if (!contextId) {
89
+ await props.updater.updateContextId();
90
+ }
91
+ const hasDifferentContextId = await props.updater.hasDifferentContextId();
92
+ if (hasDifferentContextId) {
93
+ try {
94
+ const copyNode = props.updater.copyNode({
95
+ traceId: props.mediaNode.attrs.__mediaTraceId
96
+ });
97
+ props.addPendingTask(copyNode);
98
+ await copyNode;
99
+ } catch (e) {}
100
+ }
101
+ };
102
+ const useMediaAsyncOperations = ({
103
+ mediaNode,
104
+ mediaNodeUpdater,
105
+ addPendingTask,
106
+ getPos
107
+ }) => {
108
+ React.useEffect(() => {
109
+ if (!mediaNodeUpdater) {
110
+ return;
111
+ }
112
+ // we want the first child of MediaSingle (type "media")
113
+ const childNode = mediaNode.firstChild;
114
+ if (!childNode) {
115
+ return;
116
+ }
117
+ mediaAsyncOperations({
118
+ mediaChildNode: childNode,
119
+ updater: mediaNodeUpdater,
120
+ getPos,
121
+ mediaNode,
122
+ addPendingTask
123
+ });
124
+ }, [mediaNode, addPendingTask, mediaNodeUpdater, getPos]);
125
+ };
126
+ const noop = () => {};
127
+
128
+ /**
129
+ * Keep returning the same ProseMirror Node, unless the node content changed.
130
+ *
131
+ * React uses shallow comparation with `Object.is`,
132
+ * but that can cause multiple re-renders when the same node is given in a different instance.
133
+ *
134
+ * To avoid unnecessary re-renders, this hook uses the `Node.eq` from ProseMirror API to compare
135
+ * previous and new values.
136
+ */
137
+ const useLatestMediaNode = nextMediaNode => {
138
+ const previousMediaNode = usePreviousState(nextMediaNode);
139
+ const [mediaNode, setMediaNode] = React.useState(nextMediaNode);
140
+ React.useEffect(() => {
141
+ if (!previousMediaNode) {
142
+ return;
143
+ }
144
+ if (!previousMediaNode.eq(nextMediaNode)) {
145
+ setMediaNode(nextMediaNode);
146
+ }
147
+ }, [previousMediaNode, nextMediaNode]);
148
+ return mediaNode;
149
+ };
150
+ const useMediaDimensionsLogic = ({
151
+ childMediaNodeAttrs
152
+ }) => {
153
+ const {
154
+ width: originalWidth,
155
+ height: originalHeight
156
+ } = childMediaNodeAttrs;
157
+ const isExternalMedia = childMediaNodeAttrs.type === 'external';
158
+ const hasMediaUrlBlob = isExternalMedia && typeof childMediaNodeAttrs.url === 'string' && isMediaBlobUrlFromAttrs(childMediaNodeAttrs);
159
+ const urlBlobAttrs = React.useMemo(() => {
160
+ if (!hasMediaUrlBlob) {
161
+ return null;
162
+ }
163
+ return getAttrsFromUrl(childMediaNodeAttrs.url);
164
+ }, [hasMediaUrlBlob, childMediaNodeAttrs]);
165
+ const {
166
+ width,
167
+ height
168
+ } = React.useMemo(() => {
169
+ // original width and height of child media node (scaled)
170
+ let width = originalWidth;
171
+ let height = originalHeight;
172
+ if (isExternalMedia) {
173
+ if (urlBlobAttrs) {
174
+ if (urlBlobAttrs) {
175
+ const {
176
+ width: urlWidth,
177
+ height: urlHeight
178
+ } = urlBlobAttrs;
179
+ width = width || urlWidth;
180
+ height = height || urlHeight;
181
+ }
182
+ }
183
+ if (width === null) {
184
+ width = DEFAULT_IMAGE_WIDTH;
185
+ }
186
+ if (height === null) {
187
+ height = DEFAULT_IMAGE_HEIGHT;
188
+ }
189
+ }
190
+ if (!width || !height) {
191
+ width = DEFAULT_IMAGE_WIDTH;
192
+ height = DEFAULT_IMAGE_HEIGHT;
193
+ }
194
+ return {
195
+ width,
196
+ height
197
+ };
198
+ }, [originalWidth, originalHeight, isExternalMedia, urlBlobAttrs]);
199
+ return {
200
+ width,
201
+ height
202
+ };
203
+ };
204
+ const useUpdateSizeCallback = ({
205
+ mediaNode,
206
+ view,
207
+ getPos
208
+ }) => {
209
+ const updateSize = React.useCallback((width, layout) => {
210
+ const {
211
+ state,
212
+ dispatch
213
+ } = view;
214
+ const pos = getPos();
215
+ if (typeof pos === 'undefined') {
216
+ return;
217
+ }
218
+ const tr = state.tr.setNodeMarkup(pos, undefined, {
219
+ ...mediaNode.attrs,
220
+ layout,
221
+ width,
222
+ widthType: 'pixel'
223
+ });
224
+ tr.setMeta('scrollIntoView', false);
225
+ /**
226
+ * Any changes to attributes of a node count the node as "recreated" in Prosemirror[1]
227
+ * This makes it so Prosemirror resets the selection to the child i.e. "media" instead of "media-single"
228
+ * The recommended fix is to reset the selection.[2]
229
+ *
230
+ * [1] https://discuss.prosemirror.net/t/setnodemarkup-loses-current-nodeselection/976
231
+ * [2] https://discuss.prosemirror.net/t/setnodemarkup-and-deselect/3673
232
+ */
233
+ tr.setSelection(NodeSelection.create(tr.doc, pos));
234
+ return dispatch(tr);
235
+ }, [view, getPos, mediaNode]);
236
+ return updateSize;
237
+ };
238
+
239
+ /**
240
+ * This value is used to fallback when widthState is undefined.
241
+ *
242
+ * Previously, the old MediaSingle was ignoring the undefined situation:
243
+ *
244
+ * <MediaSingleNode
245
+ * width={widthState!.width}
246
+ * lineLength={widthState!.lineLength}
247
+ */
248
+ const FALLBACK_MOST_COMMON_WIDTH = 760;
249
+ export const MediaSingleNodeNext = mediaSingleNodeNextProps => {
250
+ var _mediaOptions$getEdit, _pluginInjectionApi$m, _pluginInjectionApi$m2, _mediaNode$firstChild2;
251
+ const {
252
+ selected,
253
+ getPos,
254
+ node: nextMediaNode,
255
+ mediaOptions,
256
+ fullWidthMode,
257
+ view,
258
+ pluginInjectionApi,
259
+ width: containerWidth,
260
+ lineLength,
261
+ dispatchAnalyticsEvent,
262
+ editorViewMode,
263
+ editorDisabled,
264
+ annotationPluginState,
265
+ editorAppearance,
266
+ mediaProvider: mediaProviderPromise,
267
+ forwardRef,
268
+ contextIdentifierProvider: contextIdentifierProviderPromise,
269
+ mediaPluginState
270
+ } = mediaSingleNodeNextProps;
271
+ const {
272
+ commentsOnMedia = false
273
+ } = (mediaOptions === null || mediaOptions === void 0 ? void 0 : (_mediaOptions$getEdit = mediaOptions.getEditorFeatureFlags) === null || _mediaOptions$getEdit === void 0 ? void 0 : _mediaOptions$getEdit.call(mediaOptions)) || {};
274
+ const [mediaProvider, setMediaProvider] = React.useState(null);
275
+ const [_contextIdentifierProvider, setContextIdentifierProvider] = React.useState(null);
276
+ const [viewMediaClientConfig, setViewMediaClientConfig] = React.useState();
277
+ const mountedRef = React.useRef(true);
278
+ const pos = getPos();
279
+ const isSelected = selected();
280
+ const contentWidthForLegacyExperience = getMaxWidthForNestedNode(view, getPos()) || lineLength;
281
+ const mediaNode = useLatestMediaNode(nextMediaNode);
282
+ const mediaNodeUpdater = useMediaNodeUpdater({
283
+ mediaNode,
284
+ mediaSingleNodeProps: mediaSingleNodeNextProps,
285
+ mediaProvider,
286
+ dispatchAnalyticsEvent
287
+ });
288
+ useMediaAsyncOperations({
289
+ mediaNodeUpdater,
290
+ getPos,
291
+ mediaNode,
292
+ addPendingTask: (mediaPluginState === null || mediaPluginState === void 0 ? void 0 : mediaPluginState.addPendingTask) || noop
293
+ });
294
+ React.useLayoutEffect(() => {
295
+ mountedRef.current = true;
296
+ return () => {
297
+ mountedRef.current = false;
298
+ };
299
+ }, []);
300
+ React.useLayoutEffect(() => {
301
+ if (!mediaProviderPromise) {
302
+ return;
303
+ }
304
+ mediaProviderPromise.then(resolvedProvider => {
305
+ const {
306
+ viewMediaClientConfig
307
+ } = resolvedProvider;
308
+ if (mountedRef.current) {
309
+ setViewMediaClientConfig(viewMediaClientConfig);
310
+ setMediaProvider(resolvedProvider);
311
+ }
312
+ });
313
+ }, [mediaProviderPromise]);
314
+ React.useEffect(() => {
315
+ if (!contextIdentifierProviderPromise) {
316
+ return;
317
+ }
318
+ contextIdentifierProviderPromise.then(provider => {
319
+ if (mountedRef.current) {
320
+ setContextIdentifierProvider(provider);
321
+ }
322
+ });
323
+ }, [contextIdentifierProviderPromise]);
324
+ React.useEffect(() => {
325
+ var _mediaNode$firstChild;
326
+ // No-op but logging an exposure when an external image is rendered
327
+ // Remove this block when cleaning up platform_editor_add_media_from_url
328
+ if (((_mediaNode$firstChild = mediaNode.firstChild) === null || _mediaNode$firstChild === void 0 ? void 0 : _mediaNode$firstChild.attrs.type) === 'external') {
329
+ if (editorExperiment('add-media-from-url', true)) {
330
+ editorExperiment('add-media-from-url', true, {
331
+ exposure: true
332
+ });
333
+ } else {
334
+ editorExperiment('add-media-from-url', false, {
335
+ exposure: true
336
+ });
337
+ }
338
+ }
339
+ }, [mediaNode]);
340
+ const {
341
+ layout,
342
+ widthType,
343
+ width: mediaSingleWidthAttribute
344
+ } = mediaNode.attrs;
345
+ const childNode = mediaNode.firstChild;
346
+ const childMediaNodeAttrs = React.useMemo(() => {
347
+ return (childNode === null || childNode === void 0 ? void 0 : childNode.attrs) || {};
348
+ }, [childNode]);
349
+ const {
350
+ width,
351
+ height
352
+ } = useMediaDimensionsLogic({
353
+ childMediaNodeAttrs
354
+ });
355
+ const updateSize = useUpdateSizeCallback({
356
+ view,
357
+ getPos,
358
+ mediaNode
359
+ });
360
+ const canResize = React.useMemo(() => {
361
+ if (typeof pos !== 'number') {
362
+ return false;
363
+ }
364
+ const result = Boolean(!!mediaOptions.allowResizing && !editorDisabled && !editorViewMode);
365
+ if (mediaOptions.allowResizingInTables) {
366
+ return result;
367
+ }
368
+
369
+ // If resizing not allowed in tables, check parents for tables
370
+ const $pos = view.state.doc.resolve(pos);
371
+ const {
372
+ table
373
+ } = view.state.schema.nodes;
374
+ const disabledNode = !!findParentNodeOfTypeClosestToPos($pos, [table]);
375
+ return Boolean(result && !disabledNode);
376
+ }, [mediaOptions, pos, view, editorDisabled, editorViewMode]);
377
+ const badgeOffsetRight = React.useMemo(() => {
378
+ if (typeof pos !== 'number') {
379
+ return undefined;
380
+ }
381
+ const $pos = view.state.doc.resolve(pos);
382
+ const {
383
+ table
384
+ } = view.state.schema.nodes;
385
+ const foundTableNode = findParentNodeOfTypeClosestToPos($pos, [table]);
386
+ return foundTableNode ? '2px' : '14px';
387
+ }, [pos, view]);
388
+ const shouldShowPlaceholder = React.useMemo(() => {
389
+ const result = mediaOptions.allowCaptions && mediaNode.childCount !== 2 && isSelected && view.state.selection instanceof NodeSelection;
390
+ return !editorDisabled && result;
391
+ }, [editorDisabled, mediaOptions.allowCaptions, mediaNode, view, isSelected]);
392
+ const isInsideTable = React.useMemo(() => {
393
+ if (typeof pos !== 'number') {
394
+ return false;
395
+ }
396
+ return findParentNodeOfTypeClosestToPos(view.state.doc.resolve(pos), [view.state.schema.nodes.table]);
397
+ }, [pos, view]);
398
+ const currentMediaElement = React.useCallback(() => {
399
+ if (typeof pos !== 'number') {
400
+ return null;
401
+ }
402
+ const mediaNode = view.domAtPos(pos + 1).node;
403
+ return mediaNode instanceof HTMLElement ? mediaNode : null;
404
+ }, [view, pos]);
405
+ const mediaSingleWidth = React.useMemo(() => {
406
+ return calcMediaSinglePixelWidth({
407
+ width: mediaSingleWidthAttribute,
408
+ widthType,
409
+ origWidth: width,
410
+ layout,
411
+ // This will only be used when calculating legacy media single width
412
+ // thus we use the legacy value (exclude table as container node)
413
+ contentWidth: contentWidthForLegacyExperience,
414
+ containerWidth,
415
+ gutterOffset: MEDIA_SINGLE_GUTTER_SIZE
416
+ });
417
+ }, [mediaSingleWidthAttribute, widthType, width, layout, contentWidthForLegacyExperience, containerWidth]);
418
+ const currentMaxWidth = isSelected ? pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$m = pluginInjectionApi.media) === null || _pluginInjectionApi$m === void 0 ? void 0 : (_pluginInjectionApi$m2 = _pluginInjectionApi$m.sharedState.currentState()) === null || _pluginInjectionApi$m2 === void 0 ? void 0 : _pluginInjectionApi$m2.currentMaxWidth : undefined;
419
+ const contentWidth = currentMaxWidth || lineLength;
420
+ const isCurrentNodeDrafting = Boolean((annotationPluginState === null || annotationPluginState === void 0 ? void 0 : annotationPluginState.isDrafting) && (annotationPluginState === null || annotationPluginState === void 0 ? void 0 : annotationPluginState.targetNodeId) === (mediaNode === null || mediaNode === void 0 ? void 0 : (_mediaNode$firstChild2 = mediaNode.firstChild) === null || _mediaNode$firstChild2 === void 0 ? void 0 : _mediaNode$firstChild2.attrs.id));
421
+ const shouldShowExternalMediaBadge = childMediaNodeAttrs.type === 'external';
422
+ const mediaSingleWrapperRef = /*#__PURE__*/React.createRef();
423
+ const captionPlaceHolderRef = /*#__PURE__*/React.createRef();
424
+ const onMediaSingleClicked = React.useCallback(event => {
425
+ var _captionPlaceHolderRe;
426
+ // Workaround for iOS 16 Caption selection issue
427
+ // @see https://product-fabric.atlassian.net/browse/MEX-2012
428
+ if (!browser.ios) {
429
+ return;
430
+ }
431
+ if (mediaSingleWrapperRef.current !== event.target) {
432
+ return;
433
+ }
434
+ (_captionPlaceHolderRe = captionPlaceHolderRef.current) === null || _captionPlaceHolderRe === void 0 ? void 0 : _captionPlaceHolderRe.click();
435
+ }, [mediaSingleWrapperRef, captionPlaceHolderRef]);
436
+ const clickPlaceholder = React.useCallback(() => {
437
+ var _pluginInjectionApi$a;
438
+ if (typeof getPos === 'boolean') {
439
+ return;
440
+ }
441
+ insertAndSelectCaptionFromMediaSinglePos(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions)(getPos(), mediaNode)(view.state, view.dispatch);
442
+ }, [view, getPos, mediaNode, pluginInjectionApi]);
443
+ const legacySize = React.useMemo(() => {
444
+ return {
445
+ width: mediaSingleWidthAttribute,
446
+ widthType: widthType
447
+ };
448
+ }, [widthType, mediaSingleWidthAttribute]);
449
+ const MediaChildren = jsx("figure", {
450
+ ref: mediaSingleWrapperRef,
451
+ css: figureWrapperStyles
452
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
453
+ ,
454
+ className: MediaSingleNodeSelector,
455
+ onClick: onMediaSingleClicked
456
+ }, editorExperiment('add-media-from-url', true) && jsx(MediaBadges, {
457
+ mediaElement: currentMediaElement(),
458
+ mediaHeight: height,
459
+ mediaWidth: width,
460
+ extendedResizeOffset: fg('platform.editor.media.extended-resize-experience') && !isInsideTable
461
+ }, ({
462
+ badgeSize
463
+ }) => jsx(React.Fragment, null, shouldShowExternalMediaBadge && jsx(ExternalImageBadge, {
464
+ badgeSize: badgeSize
465
+ }), commentsOnMedia && jsx(CommentBadgeNextWrapper, {
466
+ view: view,
467
+ api: pluginInjectionApi,
468
+ mediaNode: mediaNode === null || mediaNode === void 0 ? void 0 : mediaNode.firstChild,
469
+ getPos: getPos,
470
+ isDrafting: isCurrentNodeDrafting,
471
+ badgeSize: badgeSize
472
+ }))), !editorExperiment('add-media-from-url', true) && commentsOnMedia && jsx(CommentBadge, {
473
+ view: view,
474
+ api: pluginInjectionApi,
475
+ mediaNode: mediaNode === null || mediaNode === void 0 ? void 0 : mediaNode.firstChild,
476
+ badgeOffsetRight: badgeOffsetRight,
477
+ getPos: getPos,
478
+ isDrafting: isCurrentNodeDrafting
479
+ }), jsx("div", {
480
+ ref: forwardRef
481
+ }), shouldShowPlaceholder && (editorExperiment('typography_migration_ugc', true) ? jsx(CaptionPlaceholderButton
482
+ // platform_editor_typography_migration_ugc clean up
483
+ // remove typecasting
484
+ , {
485
+ ref: captionPlaceHolderRef,
486
+ onClick: clickPlaceholder
487
+ }) : jsx(CaptionPlaceholder, {
488
+ ref: captionPlaceHolderRef,
489
+ onClick: clickPlaceholder
490
+ })));
491
+ return jsx(Fragment, null, canResize ? fg('platform.editor.media.extended-resize-experience') ? jsx(ResizableMediaSingleNext, {
492
+ view: view,
493
+ getPos: getPos,
494
+ updateSize: updateSize,
495
+ gridSize: 12,
496
+ viewMediaClientConfig: viewMediaClientConfig,
497
+ allowBreakoutSnapPoints: mediaOptions && mediaOptions.allowBreakoutSnapPoints,
498
+ selected: isSelected,
499
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
500
+ pluginInjectionApi: pluginInjectionApi,
501
+ layout: layout,
502
+ width: width,
503
+ height: height,
504
+ containerWidth: containerWidth,
505
+ lineLength: contentWidth || FALLBACK_MOST_COMMON_WIDTH,
506
+ fullWidthMode: fullWidthMode,
507
+ hasFallbackContainer: false,
508
+ mediaSingleWidth: mediaSingleWidth,
509
+ editorAppearance: editorAppearance,
510
+ showLegacyNotification: widthType !== 'pixel'
511
+ }, MediaChildren) : jsx(ResizableMediaSingle, {
512
+ view: view,
513
+ getPos: getPos,
514
+ updateSize: updateSize,
515
+ gridSize: 12,
516
+ viewMediaClientConfig: viewMediaClientConfig,
517
+ allowBreakoutSnapPoints: mediaOptions && mediaOptions.allowBreakoutSnapPoints,
518
+ selected: isSelected,
519
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
520
+ pluginInjectionApi: pluginInjectionApi,
521
+ layout: layout,
522
+ width: width,
523
+ height: height,
524
+ containerWidth: containerWidth,
525
+ fullWidthMode: fullWidthMode,
526
+ hasFallbackContainer: false,
527
+ mediaSingleWidth: mediaSingleWidth,
528
+ editorAppearance: editorAppearance,
529
+ lineLength: contentWidthForLegacyExperience || FALLBACK_MOST_COMMON_WIDTH,
530
+ pctWidth: mediaSingleWidthAttribute
531
+ }, MediaChildren) : jsx(MediaSingle, {
532
+ layout: layout,
533
+ width: width,
534
+ height: height,
535
+ containerWidth: containerWidth,
536
+ fullWidthMode: fullWidthMode,
537
+ hasFallbackContainer: false,
538
+ editorAppearance: editorAppearance,
539
+ pctWidth: mediaSingleWidthAttribute,
540
+ lineLength: lineLength || FALLBACK_MOST_COMMON_WIDTH,
541
+ size: legacySize
542
+ }, MediaChildren));
543
+ };
@@ -770,4 +770,10 @@ export var MediaNodeUpdater = /*#__PURE__*/function () {
770
770
  }();
771
771
  var hasPrivateAttrsChanged = function hasPrivateAttrsChanged(currentAttrs, newAttrs) {
772
772
  return currentAttrs.__fileName !== newAttrs.__fileName || currentAttrs.__fileMimeType !== newAttrs.__fileMimeType || currentAttrs.__fileSize !== newAttrs.__fileSize || currentAttrs.__contextId !== newAttrs.__contextId;
773
+ };
774
+ export var createMediaNodeUpdater = function createMediaNodeUpdater(props) {
775
+ var updaterProps = _objectSpread(_objectSpread({}, props), {}, {
776
+ isMediaSingle: true
777
+ });
778
+ return new MediaNodeUpdater(updaterProps);
773
779
  };
@@ -44,10 +44,14 @@ import ResizableMediaSingleNext from '../ui/ResizableMediaSingle/ResizableMediaS
44
44
  import { isMediaBlobUrlFromAttrs } from '../utils/media-common';
45
45
  import { hasPrivateAttrsChanged } from './helpers';
46
46
  import { MediaNodeUpdater } from './mediaNodeUpdater';
47
+ import { MediaSingleNodeNext } from './mediaSingleNext';
47
48
  import { MediaSingleNodeSelector } from './styles';
48
49
  var figureWrapperStyles = css({
49
50
  margin: 0
50
51
  });
52
+ /*
53
+ * @deprecated Please use the MediaSingleNodeNext
54
+ */
51
55
  // eslint-disable-next-line @repo/internal/react/no-class-components
52
56
  var MediaSingleNode = /*#__PURE__*/function (_Component) {
53
57
  _inherits(MediaSingleNode, _Component);
@@ -554,6 +558,29 @@ var MediaSingleNodeWrapper = function MediaSingleNodeWrapper(_ref7) {
554
558
  var mediaProvider = useMemo(function () {
555
559
  return mediaState !== null && mediaState !== void 0 && mediaState.mediaProvider ? Promise.resolve(mediaState === null || mediaState === void 0 ? void 0 : mediaState.mediaProvider) : undefined;
556
560
  }, [mediaState === null || mediaState === void 0 ? void 0 : mediaState.mediaProvider]);
561
+ if (fg('platform_editor_react18_phase2__media_single')) {
562
+ return jsx(MediaSingleNodeNext, {
563
+ width: (widthState === null || widthState === void 0 ? void 0 : widthState.width) || 0,
564
+ lineLength: (widthState === null || widthState === void 0 ? void 0 : widthState.lineLength) || 0,
565
+ node: node,
566
+ getPos: getPos,
567
+ mediaProvider: mediaProvider,
568
+ contextIdentifierProvider: contextIdentifierProvider,
569
+ mediaOptions: mediaOptions,
570
+ view: view,
571
+ fullWidthMode: fullWidthMode,
572
+ selected: selected,
573
+ eventDispatcher: eventDispatcher,
574
+ mediaPluginState: mediaState !== null && mediaState !== void 0 ? mediaState : undefined,
575
+ annotationPluginState: annotationState !== null && annotationState !== void 0 ? annotationState : undefined,
576
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
577
+ forwardRef: forwardRef,
578
+ pluginInjectionApi: pluginInjectionApi,
579
+ editorDisabled: editorDisabledState === null || editorDisabledState === void 0 ? void 0 : editorDisabledState.editorDisabled,
580
+ editorViewMode: (editorViewModeState === null || editorViewModeState === void 0 ? void 0 : editorViewModeState.mode) === 'view',
581
+ editorAppearance: editorAppearance
582
+ });
583
+ }
557
584
  return jsx(MediaSingleNode, {
558
585
  width: widthState.width,
559
586
  lineLength: widthState.lineLength,