@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.
- package/CHANGELOG.md +23 -0
- package/dist/cjs/nodeviews/mediaNodeUpdater.js +7 -1
- package/dist/cjs/nodeviews/mediaSingle.js +27 -0
- package/dist/cjs/nodeviews/mediaSingleNext.js +583 -0
- package/dist/es2019/nodeviews/mediaNodeUpdater.js +7 -0
- package/dist/es2019/nodeviews/mediaSingle.js +27 -0
- package/dist/es2019/nodeviews/mediaSingleNext.js +543 -0
- package/dist/esm/nodeviews/mediaNodeUpdater.js +6 -0
- package/dist/esm/nodeviews/mediaSingle.js +27 -0
- package/dist/esm/nodeviews/mediaSingleNext.js +576 -0
- package/dist/types/nodeviews/mediaNodeUpdater.d.ts +1 -0
- package/dist/types/nodeviews/mediaSingle.d.ts +1 -1
- package/dist/types/nodeviews/mediaSingleNext.d.ts +35 -0
- package/dist/types-ts4.5/nodeviews/mediaNodeUpdater.d.ts +1 -0
- package/dist/types-ts4.5/nodeviews/mediaSingle.d.ts +1 -1
- package/dist/types-ts4.5/nodeviews/mediaSingleNext.d.ts +35 -0
- package/package.json +11 -8
|
@@ -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,
|