@atlaskit/renderer 137.1.2 → 137.1.4

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 CHANGED
@@ -1,5 +1,65 @@
1
1
  # @atlaskit/renderer
2
2
 
3
+ ## 137.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [`53ad40b08a887`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/53ad40b08a887) -
8
+ Clean up platform_editor_video_caption_commit
9
+ - [`ad40c91e7c746`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ad40c91e7c746) -
10
+ Rework lazy loading of Expand body content behind the `hot-121622_lazy_load_expand_content`
11
+ feature gate, fixing an SSR hydration failure. Bodies were previously wrapped in `Suspense` +
12
+ `React.lazy`, which `renderToString` cannot resolve — it emitted an errored Suspense boundary and
13
+ hydration failed with "The server did not finish this Suspense boundary". That wrapper provided no
14
+ code splitting and has been removed.
15
+
16
+ A collapsed expand now renders a plain-text mirror of its body, and mounts the real children on
17
+ expand or on the browser's `beforematch` event. `Expand` takes the expand's ProseMirror `node` and
18
+ derives that mirror itself, replacing the `loadBodyContent` and `searchText` props. Deriving it in
19
+ the component means a nested expand — which is not mounted until its ancestor opens — never builds
20
+ a mirror on initial load, and the result is cached per node.
21
+
22
+ The mirror also now covers text the previous `node.textContent` call could not see: leaf nodes
23
+ that carry their text in attrs (mentions, emoji, status), text separated across block boundaries,
24
+ and ADF stashed in a coerced extension's `parameters.nestedContent` — which is how Confluence
25
+ stores nesting the schema cannot represent, and which renders on the page via a nested renderer.
26
+
27
+ Once an expand has been opened its children stay mounted, including after it is collapsed again
28
+ (PGXT-9021). Remounting them would re-run every macro and Forge data fetch inside the body on each
29
+ reopen; the saving this gate exists for is at initial load, where the body has never been opened.
30
+
31
+ - Updated dependencies
32
+
33
+ ## 137.1.3
34
+
35
+ ### Patch Changes
36
+
37
+ - [`28054f27e1549`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/28054f27e1549) -
38
+ Support inline-bodied Forge macros in view mode, behind `platform_forge_inline_bodied_macro`.
39
+
40
+ A macro with a rich body must declare `layout: bodied`, and `layout` is a single enum, so it
41
+ cannot also express "inline". Such a macro now declares display intent separately with
42
+ `outputType: inline`. The stored node remains a block `bodiedExtension` — only its presentation
43
+ changes, and only in view mode; the editor still shows a block.
44
+
45
+ `@atlassian/forge-ui-types`: adds `properties.outputType?: 'inline' | 'block'`.
46
+
47
+ `@atlassian/xen-editor-provider`: when a macro declares `layout: bodied` + `outputType: inline`,
48
+ the output type is seeded into `guestParams` at insert so the renderer's inline treatment applies
49
+ from the first render with no config edit, and it is re-applied as a default when a config panel
50
+ saves (config saves replace `guestParams` wholesale). The Forge app container is laid out inline
51
+ for these macros in view mode, Custom UI excluded.
52
+
53
+ `@atlaskit/renderer`: native Forge inline extensions are marked with `data-forge-inline` and their
54
+ nested body renderer's top-level paragraphs are collapsed inline, so the app's own block spacing
55
+ does not push the surrounding sentence away. Nested inline-bodied Forge macros are left as blocks,
56
+ because the pass that marks the sibling textblocks around an inline extension only resolves
57
+ positions correctly at the top level.
58
+
59
+ With the gate disabled every code path above evaluates to today's behaviour.
60
+
61
+ - Updated dependencies
62
+
3
63
  ## 137.1.2
4
64
 
5
65
  ### Patch Changes
@@ -736,25 +736,14 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
736
736
  value: function getExpandProps(node) {
737
737
  var _this7 = this;
738
738
  var _path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
739
- var loadBodyContent = false;
740
- if ((0, _fg.fg)('hot-121622_lazy_load_expand_content')) {
741
- var annotations = (0, _utils2.findChildrenByMark)(node, node.type.schema.marks.annotation, true);
742
- // Force rendering children if there are inline comments to support comments navigation
743
- // which relies on the HTML node to be present.
744
- loadBodyContent = annotations.some(function (annotation) {
745
- return annotation.node.marks.some(function (mark) {
746
- return mark.attrs.annotationType === 'inlineComment';
747
- });
748
- });
749
- }
750
-
751
- // Only compute searchText when the browser find experiment is enabled,
752
- // to avoid unnecessary node.textContent string allocations per expand node.
753
- var searchText = (0, _expValEquals.expValEquals)('platform_editor_close_expand_find', 'isEnabled', true) ? node.textContent : undefined;
739
+ // Expand receives the node rather than a precomputed string so it can derive the collapsed
740
+ // text mirror only when it actually renders one — a nested expand is not mounted until its
741
+ // ancestor opens and so that derivation can reach content the model walk cannot, such as
742
+ // the ADF stashed in a coerced extension's `parameters.nestedContent`. Expand owns the
743
+ // feature gate check.
754
744
  if (!(0, _links.isNestedHeaderLinksEnabled)(this.allowHeadingAnchorLinks)) {
755
745
  return _objectSpread(_objectSpread({}, this.getProps(node)), {}, {
756
- loadBodyContent: loadBodyContent,
757
- searchText: searchText
746
+ node: node
758
747
  });
759
748
  }
760
749
  var nestedHeaderIds = (0, _utils2.findChildrenByType)(node, node.type.schema.nodes.heading).map(function (_ref4) {
@@ -763,8 +752,7 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
763
752
  });
764
753
  return _objectSpread(_objectSpread({}, this.getProps(node)), {}, {
765
754
  nestedHeaderIds: nestedHeaderIds,
766
- loadBodyContent: loadBodyContent,
767
- searchText: searchText
755
+ node: node
768
756
  });
769
757
  }
770
758
 
@@ -26,6 +26,13 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
26
26
 
27
27
  // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
28
28
 
29
+ var FORGE_EXTENSION_TYPE = 'com.atlassian.ecosystem';
30
+ /**
31
+ * Mirrors `FORGE_INLINE_BODIED_PARAM` in `@atlassian/xen-editor-provider`. Duplicated rather than
32
+ * imported: the renderer must not depend on a Forge package, and this is a stored parameter name,
33
+ * so it is part of the document contract rather than that package's API.
34
+ */
35
+ var FORGE_INLINE_BODIED_PARAM = 'atlassianForgeInlineBodied';
29
36
  var viewportSizes = ['small', 'medium', 'default', 'large', 'xlarge'];
30
37
  // Mirrors sizes from https://bitbucket.org/atlassian/atlassian-frontend-monorepo/src/master/platform/packages/forge/xen-editor-provider/src/render/renderers/ForgeUIExtension.tsx
31
38
  var macroHeights = {
@@ -73,6 +80,7 @@ var FireExtensionAsInlineAnalytics = function FireExtensionAsInlineAnalytics(_re
73
80
  return null;
74
81
  };
75
82
  var renderExtension = exports.renderExtension = function renderExtension(content, layout) {
83
+ var _node$parameters;
76
84
  var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
77
85
  var removeOverflow = arguments.length > 3 ? arguments[3] : undefined;
78
86
  var extensionId = arguments.length > 4 ? arguments[4] : undefined;
@@ -105,8 +113,36 @@ var renderExtension = exports.renderExtension = function renderExtension(content
105
113
  */
106
114
  var viewportSize = getViewportSize(extensionId, extensionViewportSizes);
107
115
  var extensionHeight = nodeHeight || viewportSize;
108
- var isInline = (shouldDisplayExtensionAsInline === null || shouldDisplayExtensionAsInline === void 0 ? void 0 : shouldDisplayExtensionAsInline(node)) && (0, _expValEquals.expValEquals)('platform_editor_render_bodied_extension_as_inline', 'isEnabled', true);
116
+ /**
117
+ * Scoped to nodes inserted by an app declaring `outputType: inline`, which is what writes
118
+ * `atlassianForgeInlineBodied`. The output-type marker alone would also match migrated Connect
119
+ * content — it carries the same marker, and after an upgrade plus a storage round trip in the
120
+ * same shape — so keying on it would change how existing content renders. The renderer only
121
+ * ever sees the stored node, never the manifest, so a parameter this code writes is the only
122
+ * available signal.
123
+ *
124
+ * Evaluated once and shared with `isNativeForgeInline` below, so the gate is read a single time
125
+ * per render, and the cheap checks stay in front of it so anything ineligible short-circuits
126
+ * without firing an exposure it can never act on.
127
+ */
128
+ var isForgeInlineBodiedEnabled = Boolean((node === null || node === void 0 ? void 0 : node.extensionType) === FORGE_EXTENSION_TYPE && (node === null || node === void 0 ? void 0 : node.content) && (node === null || node === void 0 || (_node$parameters = node.parameters) === null || _node$parameters === void 0 || (_node$parameters = _node$parameters.guestParams) === null || _node$parameters === void 0 ? void 0 : _node$parameters[FORGE_INLINE_BODIED_PARAM]) === 'true' && (0, _fg.fg)('platform_forge_inline_bodied_macro'));
129
+ /**
130
+ * The pass that marks the sibling textblocks around an inline extension resolves their
131
+ * positions without a depth term, so it only ever matches at the top level. Inlining a nested
132
+ * container without joining its neighbours leaves a shrink-wrapped box alone on its own line,
133
+ * which is worse than leaving it a block — so keep nested inline-bodied Forge macros as
134
+ * blocks until the sibling marking works at depth.
135
+ */
136
+ var isNestedForgeInlineBodied = !isTopLevel && isForgeInlineBodiedEnabled;
137
+ var isInline = (shouldDisplayExtensionAsInline === null || shouldDisplayExtensionAsInline === void 0 ? void 0 : shouldDisplayExtensionAsInline(node)) && (0, _expValEquals.expValEquals)('platform_editor_render_bodied_extension_as_inline', 'isEnabled', true) && !isNestedForgeInlineBodied;
109
138
  var inlineClassName = isInline ? _consts.RendererCssClassName.EXTENSION_AS_INLINE : '';
139
+ /**
140
+ * A native Forge macro does not have its body rendered by the product — the body ADF is
141
+ * sent to the app over the bridge and the app renders it with its own nested renderer.
142
+ * The inline styling above stops at the outer wrapper, so mark these nodes to let the
143
+ * nested document's block spacing be collapsed too.
144
+ */
145
+ var isNativeForgeInline = Boolean(isInline && isForgeInlineBodiedEnabled);
110
146
  var asInlineAnalytics = isInline && fireAnalyticsEvent && node ? (0, _react.jsx)(FireExtensionAsInlineAnalytics, {
111
147
  fireAnalyticsEvent: fireAnalyticsEvent,
112
148
  node: node
@@ -128,7 +164,8 @@ var renderExtension = exports.renderExtension = function renderExtension(content
128
164
  "data-local-id": localId,
129
165
  "data-testid": "extension--wrapper",
130
166
  "data-node-type": "extension",
131
- "data-top-level": isTopLevel || undefined
167
+ "data-top-level": isTopLevel || undefined,
168
+ "data-forge-inline": isNativeForgeInline || undefined
132
169
  }, (0, _react.jsx)("div", {
133
170
  tabIndex: options.tabIndex
134
171
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
@@ -158,7 +195,8 @@ var renderExtension = exports.renderExtension = function renderExtension(content
158
195
  },
159
196
  "data-layout": layout,
160
197
  "data-local-id": localId,
161
- "data-top-level": isTopLevel || undefined
198
+ "data-top-level": isTopLevel || undefined,
199
+ "data-forge-inline": isNativeForgeInline || undefined
162
200
  }, (0, _react.jsx)("div", {
163
201
  tabIndex: options.tabIndex
164
202
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
@@ -16,7 +16,7 @@ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
16
16
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
17
17
  var getMediaClientConfigForRenderer = function getMediaClientConfigForRenderer(provider) {
18
18
  // eslint-disable-next-line @atlaskit/platform/no-preconditioning
19
- return provider.viewAndUploadMediaClientConfig && (0, _fg.fg)('platform_media_video_captions') && (0, _fg.fg)('platform_editor_video_caption_commit') ? provider.viewAndUploadMediaClientConfig : provider.viewMediaClientConfig;
19
+ return provider.viewAndUploadMediaClientConfig && (0, _fg.fg)('platform_media_video_captions') ? provider.viewAndUploadMediaClientConfig : provider.viewMediaClientConfig;
20
20
  };
21
21
  var EditorMediaClientProvider = exports.EditorMediaClientProvider = function EditorMediaClientProvider(_ref) {
22
22
  var children = _ref.children,
@@ -16,8 +16,9 @@ var _ui = require("@atlaskit/editor-common/ui");
16
16
  var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
17
17
  var _chevronRight = _interopRequireDefault(require("@atlaskit/icon/core/chevron-right"));
18
18
  var _Tooltip = _interopRequireDefault(require("@atlaskit/tooltip/Tooltip"));
19
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
19
20
  var _fg = require("@atlaskit/platform-feature-flags/fg");
20
- var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
21
+ var _expandSearchText = require("./utils/expand-search-text");
21
22
  var _uniqueId2 = _interopRequireDefault(require("lodash/uniqueId"));
22
23
  var _reactIntl = require("react-intl");
23
24
  var _events = require("../analytics/events");
@@ -164,16 +165,6 @@ var clearNextSiblingMarginTopStyle = (0, _react.css)({
164
165
  marginTop: '0 !important'
165
166
  }
166
167
  });
167
-
168
- // Lazy-loaded children component
169
- var LazyChildren = /*#__PURE__*/(0, _react2.lazy)(function () {
170
- return Promise.resolve({
171
- default: function _default(_ref) {
172
- var children = _ref.children;
173
- return /*#__PURE__*/_react2.default.createElement(_react2.default.Fragment, null, children);
174
- }
175
- });
176
- });
177
168
  var Container = function Container(props) {
178
169
  return (0, _react.jsx)("div", {
179
170
  css: [containerStyles, props['data-node-type'] === 'expand' && containerStylesDataNodeTypeExpand, props.expanded && containerStylesExpanded, props.focused && containerStylesFocused],
@@ -222,29 +213,36 @@ function fireExpandToggleAnalytics(nodeType, expanded, fireAnalyticsEvent) {
222
213
  eventType: _analytics.EVENT_TYPE.TRACK
223
214
  });
224
215
  }
225
- function Expand(_ref2) {
226
- var title = _ref2.title,
227
- children = _ref2.children,
228
- nodeType = _ref2.nodeType,
229
- intl = _ref2.intl,
230
- fireAnalyticsEvent = _ref2.fireAnalyticsEvent,
231
- localId = _ref2.localId,
232
- nestedHeaderIds = _ref2.nestedHeaderIds,
233
- rendererContentMode = _ref2.rendererContentMode,
234
- loadBodyContent = _ref2.loadBodyContent,
235
- searchText = _ref2.searchText;
236
- var _React$useState = _react2.default.useState(false),
237
- _React$useState2 = (0, _slicedToArray2.default)(_React$useState, 2),
238
- expanded = _React$useState2[0],
239
- setExpanded = _React$useState2[1];
240
- var _React$useState3 = _react2.default.useState(false),
241
- _React$useState4 = (0, _slicedToArray2.default)(_React$useState3, 2),
242
- focused = _React$useState4[0],
243
- setFocused = _React$useState4[1];
244
- var _React$useState5 = _react2.default.useState(false),
245
- _React$useState6 = (0, _slicedToArray2.default)(_React$useState5, 2),
246
- hasLoadedChildren = _React$useState6[0],
247
- setHasLoadedChildren = _React$useState6[1];
216
+ function Expand(_ref) {
217
+ var title = _ref.title,
218
+ children = _ref.children,
219
+ nodeType = _ref.nodeType,
220
+ intl = _ref.intl,
221
+ fireAnalyticsEvent = _ref.fireAnalyticsEvent,
222
+ localId = _ref.localId,
223
+ nestedHeaderIds = _ref.nestedHeaderIds,
224
+ rendererContentMode = _ref.rendererContentMode,
225
+ node = _ref.node;
226
+ var _useState = (0, _react2.useState)(false),
227
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
228
+ expanded = _useState2[0],
229
+ setExpanded = _useState2[1];
230
+ var _useState3 = (0, _react2.useState)(false),
231
+ _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
232
+ focused = _useState4[0],
233
+ setFocused = _useState4[1];
234
+ /**
235
+ * PGXT-9021: latches on the first open so the body stays mounted once rendered, including after
236
+ * the expand is collapsed again. Remounting it would re-run every macro and Forge data fetch
237
+ * inside on each reopen, which is both slower and visibly reloads content the reader has already
238
+ * seen. The saving this gate exists for is at initial load, where the body has never been opened,
239
+ * so nothing is given up by keeping it after that. Never set on the server, so the server and the
240
+ * client's first render agree and hydration cannot desync.
241
+ */
242
+ var _useState5 = (0, _react2.useState)(false),
243
+ _useState6 = (0, _slicedToArray2.default)(_useState5, 2),
244
+ hasBeenExpanded = _useState6[0],
245
+ setHasBeenExpanded = _useState6[1];
248
246
  var isMobile = false;
249
247
  var label = intl.formatMessage(expanded ? _ui.expandMessages.collapseNode : _ui.expandMessages.expandNode);
250
248
  var _useRef = (0, _react2.useRef)((0, _uniqueId2.default)('expand-title-')),
@@ -257,16 +255,12 @@ function Expand(_ref2) {
257
255
  var handleBlur = (0, _react2.useCallback)(function () {
258
256
  return setFocused(false);
259
257
  }, []);
260
- var expandForBrowserFind = (0, _react2.useCallback)(function () {
261
- setHasLoadedChildren(true);
258
+ // Both state updates happen in the same handler so they land in one render, rather than the
259
+ // latch reacting to `expanded` from an effect.
260
+ var openBody = (0, _react2.useCallback)(function () {
262
261
  setExpanded(true);
262
+ setHasBeenExpanded(true);
263
263
  }, []);
264
- var shouldRenderLazyChildren = hasLoadedChildren || loadBodyContent;
265
- // Only render the lightweight text placeholder when lazy load is ON and
266
- // children haven't been loaded yet. When lazy load is OFF, children are
267
- // always in the DOM — hidden="until-found" on the wrapper already makes
268
- // them searchable by browser find, so no placeholder is needed.
269
- var shouldRenderBrowserFindText = !shouldRenderLazyChildren && searchText && (0, _fg.fg)('hot-121622_lazy_load_expand_content') && (0, _expValEquals.expValEquals)('platform_editor_close_expand_find', 'isEnabled', true);
270
264
 
271
265
  // Feature-detect hidden="until-found" support via the beforematch event.
272
266
  // Chrome 102+ and Firefox 130+ support it; Safari does not yet.
@@ -275,11 +269,11 @@ function Expand(_ref2) {
275
269
  // Initialised as false and set in useEffect to avoid SSR/client hydration mismatch —
276
270
  // useMemo would return true on the client's first render in supported browsers,
277
271
  // differing from the server snapshot which always produces false.
278
- var _React$useState7 = _react2.default.useState(false),
279
- _React$useState8 = (0, _slicedToArray2.default)(_React$useState7, 2),
280
- supportsHiddenUntilFound = _React$useState8[0],
281
- setSupportsHiddenUntilFound = _React$useState8[1];
282
- _react2.default.useEffect(function () {
272
+ var _useState7 = (0, _react2.useState)(false),
273
+ _useState8 = (0, _slicedToArray2.default)(_useState7, 2),
274
+ supportsHiddenUntilFound = _useState8[0],
275
+ setSupportsHiddenUntilFound = _useState8[1];
276
+ (0, _react2.useEffect)(function () {
283
277
  var doc = (0, _browserApis.getDocument)();
284
278
  setSupportsHiddenUntilFound(doc !== null && doc !== void 0 && doc.body ? 'onbeforematch' in doc.body : false);
285
279
  }, []);
@@ -295,13 +289,13 @@ function Expand(_ref2) {
295
289
  // In unsupported browsers (Safari), we skip this entirely and fall back to the normal
296
290
  // CSS hiding (visibility:hidden + height:0), which doesn't support find-in-page but
297
291
  // still works correctly for expand/collapse.
298
- _react2.default.useEffect(function () {
292
+ (0, _react2.useEffect)(function () {
299
293
  var contentContainer = contentContainerRef.current;
300
294
  var contentWrapper = contentWrapperRef.current;
301
295
  if (!contentWrapper) {
302
296
  return;
303
297
  }
304
- if (supportsHiddenUntilFound && (0, _expValEquals.expValEquals)('platform_editor_close_expand_find', 'isEnabled', true) && !expanded) {
298
+ if (supportsHiddenUntilFound && (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_close_expand_find') && !expanded) {
305
299
  contentWrapper.setAttribute('hidden', 'until-found');
306
300
  // Override the CSS visibility:hidden from contentContainerStyles — hidden="until-found"
307
301
  // now handles hiding via content-visibility:hidden, which allows browser find to index
@@ -315,31 +309,29 @@ function Expand(_ref2) {
315
309
  contentWrapper.style.visibility = '';
316
310
  }
317
311
  }, [expanded, supportsHiddenUntilFound]);
318
- _react2.default.useEffect(function () {
319
- if (!(0, _expValEquals.expValEquals)('platform_editor_close_expand_find', 'isEnabled', true) || expanded) {
312
+ (0, _react2.useEffect)(function () {
313
+ if (!(0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_close_expand_find') || expanded) {
320
314
  return;
321
315
  }
322
316
  var contentWrapper = contentWrapperRef.current;
323
317
  var unbindWrapperBeforeMatch = contentWrapper && supportsHiddenUntilFound ? (0, _bindEventListener.bind)(contentWrapper, {
324
318
  type: 'beforematch',
325
- listener: expandForBrowserFind
319
+ listener: openBody
326
320
  }) : undefined;
327
321
  return function () {
328
322
  unbindWrapperBeforeMatch === null || unbindWrapperBeforeMatch === void 0 || unbindWrapperBeforeMatch();
329
323
  };
330
- }, [expandForBrowserFind, expanded, supportsHiddenUntilFound]);
331
- var expandContent = children;
332
- if (shouldRenderBrowserFindText) {
333
- // Browser find path: keep a lightweight text mirror in the closed expand
334
- // so Ctrl+F can index it without mounting the rich children tree.
335
- expandContent = (0, _react.jsx)("span", null, searchText);
336
- } else if (!shouldRenderLazyChildren && (0, _fg.fg)('hot-121622_lazy_load_expand_content')) {
337
- expandContent = null;
338
- } else if ((0, _fg.fg)('hot-121622_lazy_load_expand_content')) {
339
- expandContent = (0, _react.jsx)(_react2.Suspense, {
340
- fallback: (0, _react.jsx)("div", null, intl.formatMessage(_ui.expandMessages.loading))
341
- }, (0, _react.jsx)(LazyChildren, null, children));
342
- }
324
+ }, [expanded, openBody, supportsHiddenUntilFound]);
325
+
326
+ // Until the expand is first opened, stand in a plain-text mirror of the body so
327
+ // hidden="until-found" keeps the content findable by browser find without mounting the real
328
+ // subtree. The beforematch listener above expands before the browser scrolls to its match.
329
+ // Computed lazily: a nested expand is not mounted until its ancestor opens, so its mirror is
330
+ // never built on initial load.
331
+ var searchText = (0, _react2.useMemo)(function () {
332
+ return node && (0, _fg.fg)('hot-121622_lazy_load_expand_content') ? (0, _expandSearchText.getExpandSearchText)(node) : undefined;
333
+ }, [node]);
334
+ var expandContent = searchText && !expanded && !hasBeenExpanded ? (0, _react.jsx)("span", null, searchText) : children;
343
335
  return (0, _react.jsx)(Container, {
344
336
  "data-testid": "expand-container-".concat(nodeType, "-").concat(id),
345
337
  "data-node-type": nodeType,
@@ -349,15 +341,8 @@ function Expand(_ref2) {
349
341
  expanded: expanded,
350
342
  focused: focused
351
343
  }, nestedHeaderIds && nestedHeaderIds.length > 0 ? (0, _react.jsx)(_activeHeaderIdProvider.ActiveHeaderIdConsumer, {
352
- nestedHeaderIds: nestedHeaderIds
353
- // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
354
- ,
355
- onNestedHeaderIdMatch: function onNestedHeaderIdMatch() {
356
- if (!hasLoadedChildren) {
357
- setHasLoadedChildren(true);
358
- }
359
- setExpanded(true);
360
- }
344
+ nestedHeaderIds: nestedHeaderIds,
345
+ onNestedHeaderIdMatch: openBody
361
346
  }) : null, (0, _react.jsx)(TitleContainer
362
347
  // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
363
348
  , {
@@ -365,12 +350,11 @@ function Expand(_ref2) {
365
350
  e.preventDefault();
366
351
  e.stopPropagation();
367
352
  fireExpandToggleAnalytics(nodeType, expanded, fireAnalyticsEvent);
368
-
369
- // Mark children as loaded when expanding for the first time
370
- if (!expanded && !hasLoadedChildren) {
371
- setHasLoadedChildren(true);
353
+ if (expanded) {
354
+ setExpanded(false);
355
+ } else {
356
+ openBody();
372
357
  }
373
- setExpanded(!expanded);
374
358
  e.persist();
375
359
  // @ts-ignore detail doesn't exist on type
376
360
  e.detail ? handleBlur() : handleFocus();
@@ -405,7 +389,7 @@ function Expand(_ref2) {
405
389
  id: id
406
390
  }, title || intl.formatMessage(_ui.expandMessages.expandDefaultTitle))), (0, _react.jsx)(ContentContainer, {
407
391
  expanded: expanded,
408
- enableBrowserFind: supportsHiddenUntilFound && (0, _expValEquals.expValEquals)('platform_editor_close_expand_find', 'isEnabled', true),
392
+ enableBrowserFind: supportsHiddenUntilFound && (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_close_expand_find'),
409
393
  contentRef: contentContainerRef
410
394
  }, (0, _react.jsx)("div", {
411
395
  className: "".concat(nodeType, "-content-wrapper"),
@@ -417,4 +401,4 @@ function Expand(_ref2) {
417
401
 
418
402
  // eslint-disable-next-line @typescript-eslint/ban-types
419
403
  var _default_1 = (0, _reactIntl.injectIntl)(Expand);
420
- var _default2 = exports.default = _default_1;
404
+ var _default = exports.default = _default_1;
@@ -867,6 +867,20 @@ var extensionAsInlineStyle = (0, _react.css)((0, _defineProperty2.default)((0, _
867
867
  }), ".".concat(_consts.RendererCssClassName.EXTENSION_AS_INLINE, " .").concat(_consts.RendererCssClassName.EXTENSION_INNER_WRAPPER), {
868
868
  display: 'inline-block'
869
869
  }));
870
+ var forgeInlineBodiedSpacingStyle = (0, _react.css)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, ".".concat(_consts.RendererCssClassName.DOCUMENT, " .").concat(_consts.RendererCssClassName.EXTENSION_AS_INLINE, "[data-forge-inline]"), {
871
+ marginBottom: 0,
872
+ verticalAlign: 'baseline',
873
+ maxWidth: '100%'
874
+ }), [".".concat(_consts.RendererCssClassName.DOCUMENT, " [data-forge-inline] + [data-as-inline=\"on\"]"), ".".concat(_consts.RendererCssClassName.DOCUMENT, " [data-as-inline=\"on\"]:has(+ [data-forge-inline])")].join(', '), {
875
+ display: 'inline'
876
+ }), ".".concat(_consts.RendererCssClassName.EXTENSION_AS_INLINE, "[data-forge-inline] *"), {
877
+ maxWidth: '100%'
878
+ }), ".".concat(_consts.RendererCssClassName.EXTENSION_AS_INLINE, "[data-forge-inline] .").concat(_consts.RendererCssClassName.EXTENSION_OVERFLOW_CONTAINER), {
879
+ overflowX: 'auto'
880
+ }), ".".concat(_consts.RendererCssClassName.EXTENSION_AS_INLINE, "[data-forge-inline] .").concat(_consts.RendererCssClassName.DOCUMENT, " > p"), {
881
+ display: 'inline',
882
+ margin: 0
883
+ }));
870
884
 
871
885
  // Removes the blockNodesVerticalMargin styling for inline extensions, i.e. borderless excerpt-include
872
886
  var inlineExtensionRendererMarginFix = (0, _react.css)({
@@ -2293,7 +2307,7 @@ var RendererStyleContainer = exports.RendererStyleContainer = function RendererS
2293
2307
  // eslint-disable-next-line @atlaskit/platform/no-preconditioning
2294
2308
  (0, _fg.fg)('editor_inline_comments_on_inline_nodes') && rendererAnnotationStylesCommentHeightFix, (0, _expValEquals.expValEquals)('platform_editor_copy_link_a11y_inconsistency_fix', 'isEnabled', true) ? baseOtherStyles : baseOtherStylesDuplicateAnchor,
2295
2309
  // this should be placed after baseOtherStyles
2296
- (0, _expValEquals.expValEquals)('platform_editor_render_bodied_extension_as_inline', 'isEnabled', true) && ((0, _expValEquals.expValEquals)('platform_editor_remove_important_in_render_ext', 'isEnabled', true) ? extensionAsInlineStyle : oldExtensionAsInlineStyle), inlineExtensionRendererMarginFix, allowNestedHeaderLinks && ((0, _expValEquals.expValEquals)('platform_editor_copy_link_a11y_inconsistency_fix', 'isEnabled', true) ? alignedHeadingAnchorStyle : alignedHeadingAnchorStyleDuplicateAnchor), mediaSingleSharedStyle,
2310
+ (0, _expValEquals.expValEquals)('platform_editor_render_bodied_extension_as_inline', 'isEnabled', true) && ((0, _expValEquals.expValEquals)('platform_editor_remove_important_in_render_ext', 'isEnabled', true) ? extensionAsInlineStyle : oldExtensionAsInlineStyle), forgeInlineBodiedSpacingStyle, inlineExtensionRendererMarginFix, allowNestedHeaderLinks && ((0, _expValEquals.expValEquals)('platform_editor_copy_link_a11y_inconsistency_fix', 'isEnabled', true) ? alignedHeadingAnchorStyle : alignedHeadingAnchorStyleDuplicateAnchor), mediaSingleSharedStyle,
2297
2311
  // merge firstWrappedMediaStyles with mediaSingleSharedStyle when clean up platform_editor_fix_media_in_renderer
2298
2312
  (0, _fg.fg)('platform_editor_fix_media_in_renderer') && firstWrappedMediaStyles, tableSharedStyle, (0, _expValEquals.expValEquals)('platform_editor_table_q4_loveability', 'isEnabled', true) && roundedTableOuterBorderOverlayStyles, (0, _expValEquals.expValEquals)('platform_editor_table_q4_loveability', 'isEnabled', true) && !(0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_table_q4_patch_7') && roundedTableLegacyClipPathStyles, (0, _expValEquals.expValEquals)('platform_editor_table_q4_loveability', 'isEnabled', true) && roundedTableRemixBlockHighlightStyles, (0, _expValEquals.expValEquals)('platform_editor_table_fit_to_content_auto_convert', 'isEnabled', true) && tableContentModeScopedStyles, (0, _expValEquals.expValEquals)('platform_editor_table_fit_to_content_auto_convert', 'isEnabled', true) && tableContentModeNestedTableStyles, tableRendererHeaderStylesForTableCellOnly, (0, _fg.fg)('platform_editor_bordered_panel_nested_in_table') && tableRendererNestedPanelStyles, isBackgroundClipBrowserFixNeeded() && tableStylesBackGroundClipForGeckoForTableCellOnly, firstNodeWithNotMarginTopWithNestedDnD, rendererTableStyles, isStickyScrollbarOn && stickyScrollbarStyles, isStickyScrollbarOn && (0, _expValEquals.expValEquals)('platform_editor_table_css_overflow_shadow', 'isEnabled', true) && stickyScrollbarOverflowShadowFixStyles, rendererTableHeaderEqualHeightStylesForTableCellOnly, allowColumnSorting && rendererTableSortableColumnStyles, allowColumnSorting && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_table_menu_updates', 'isEnabled', true) && rendererTableSortableColumnValignStyles, allowColumnSorting && allowNestedHeaderLinks && ((0, _expValEquals.expValEquals)('platform_editor_copy_link_a11y_inconsistency_fix', 'isEnabled', true) ? rendererTableHeaderEqualHeightStylesAllowNestedHeaderLinks : rendererTableHeaderEqualHeightStylesAllowNestedHeaderLinksDuplicateAnchor), rendererTableColumnStyles, stickyHeaderStyles, codeBlockAndLayoutStyles, columnLayoutSharedStyle, isAdvancedLayoutsOn && columnLayoutResponsiveSharedStyle, isAdvancedLayoutsOn && columnLayoutResponsiveRendererStyles, isAdvancedLayoutsOn && layoutSectionForAdvancedLayoutsStyles, !useBlockRenderForCodeBlock && gridRenderForCodeBlockStyles, browser.safari && codeBlockInListSafariFixStyles, appearance === 'full-page' && !isPreviewPanelResponsivenessOn && responsiveBreakoutWidth, appearance === 'full-page' && isPreviewPanelResponsivenessOn && responsiveBreakoutWidthWithReducedPadding, (appearance === 'full-width' || appearance === 'max' && ((0, _expValEquals.expValEquals)('editor_tinymce_full_width_mode', 'isEnabled', true) || (0, _expValEquals.expValEquals)('confluence_max_width_content_appearance', 'isEnabled', true))) && responsiveBreakoutWidthFullWidth, (0, _expValEquals.expValEquals)('platform_editor_lovability_emoji_scaling', 'isEnabled', true) ? contentMode === 'compact' ? scaledDenseEmojiStyles : scaledEmojiStyles : contentMode === 'compact' ? denseStyles : undefined, contentMode === 'compact' ? scaledDenseUnicodeEmojiStylesNew : scaledUnicodeEmojiStylesNew, syncBlockStyles, centerWrapperStyles, isInsideSyncBlock ? syncBlockRendererStyles : null, isInsideSyncBlock && tableFakeBorderStyles, isInsideSyncBlock && (0, _expValEquals.expValEquals)('platform_editor_table_q4_loveability', 'isEnabled', true) ? roundedTableFakeBorderOverlayStyles : null, (0, _expValEquals.expValEquals)('platform_editor_hide_extension_renderer_support', 'isEnabled', true) && hideExtensionStyles],
2299
2313
  "data-testid": testId
@@ -74,7 +74,7 @@ var DEGRADED_SEVERITY_THRESHOLD = exports.DEGRADED_SEVERITY_THRESHOLD = 3000;
74
74
  var TABLE_INFO_TIMEOUT = 10000;
75
75
  var RENDER_EVENT_SAMPLE_RATE = 0.1;
76
76
  var packageName = "@atlaskit/renderer";
77
- var packageVersion = "137.1.1";
77
+ var packageVersion = "137.1.3";
78
78
  var setAsQueryContainerStyles = (0, _react2.css)({
79
79
  containerName: 'ak-renderer-wrapper',
80
80
  containerType: 'inline-size'
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getExpandSearchText = void 0;
7
+ var _utils = require("@atlaskit/editor-prosemirror/utils");
8
+ /**
9
+ * Minimal structural view of an ADF entity. `parameters.nestedContent` holds raw ADF JSON rather
10
+ * than ProseMirror nodes, so it cannot be walked with the model API.
11
+ */
12
+
13
+ /** Separates text from adjacent blocks so phrases cannot fuse across block boundaries. */
14
+ var BLOCK_SEPARATOR = ' ';
15
+ var asText = function asText(value) {
16
+ return typeof value === 'string' ? value : '';
17
+ };
18
+
19
+ /**
20
+ * Extracts text from raw ADF JSON.
21
+ *
22
+ * Needed because some extensions stash a whole ADF subtree in `attrs.parameters.nestedContent`
23
+ * instead of in their node content — Confluence does this when it coerces nesting the schema
24
+ * cannot represent (an expand more than two collapsible layers deep, say). Such a node is a
25
+ * ProseMirror leaf, so the model walk cannot see the subtree even though it renders on the page
26
+ * via a nested renderer. Recurses, because nestedContent routinely holds further nested content.
27
+ */
28
+ var _adfEntityText = function adfEntityText(entity) {
29
+ var _entity$attrs, _entity$content;
30
+ if (!entity) {
31
+ return '';
32
+ }
33
+ var parts = [];
34
+ if (entity.type === 'text') {
35
+ parts.push(asText(entity.text));
36
+ }
37
+ var nestedContent = (_entity$attrs = entity.attrs) === null || _entity$attrs === void 0 || (_entity$attrs = _entity$attrs.parameters) === null || _entity$attrs === void 0 ? void 0 : _entity$attrs.nestedContent;
38
+ if (nestedContent) {
39
+ // Prefer the real subtree over `attrs.text`, which is only a placeholder label for it.
40
+ parts.push(_adfEntityText(nestedContent));
41
+ } else {
42
+ var _entity$attrs2;
43
+ parts.push(asText((_entity$attrs2 = entity.attrs) === null || _entity$attrs2 === void 0 ? void 0 : _entity$attrs2.text));
44
+ }
45
+ (_entity$content = entity.content) === null || _entity$content === void 0 || _entity$content.forEach(function (child) {
46
+ return parts.push(_adfEntityText(child));
47
+ });
48
+ return parts.filter(Boolean).join(BLOCK_SEPARATOR);
49
+ };
50
+
51
+ /**
52
+ * `textBetween` drops every non-text leaf unless the node spec declares `leafText`, and no ADF
53
+ * node does. Leaves carry their visible text in attrs, so recover it here.
54
+ */
55
+ var leafText = function leafText(leaf) {
56
+ var _leaf$attrs, _leaf$attrs2;
57
+ var nestedContent = (_leaf$attrs = leaf.attrs) === null || _leaf$attrs === void 0 || (_leaf$attrs = _leaf$attrs.parameters) === null || _leaf$attrs === void 0 ? void 0 : _leaf$attrs.nestedContent;
58
+ if (nestedContent) {
59
+ return _adfEntityText(nestedContent);
60
+ }
61
+ return asText((_leaf$attrs2 = leaf.attrs) === null || _leaf$attrs2 === void 0 ? void 0 : _leaf$attrs2.text);
62
+ };
63
+ var hasInlineComment = function hasInlineComment(node) {
64
+ return (0, _utils.findChildrenByMark)(node, node.type.schema.marks.annotation, true).some(function (annotation) {
65
+ return annotation.node.marks.some(function (mark) {
66
+ return mark.attrs.annotationType === 'inlineComment';
67
+ });
68
+ });
69
+ };
70
+
71
+ /** `null` is a cached "no mirror for this node", as distinct from `undefined` for a cache miss. */
72
+ var searchTextCache = new WeakMap();
73
+
74
+ /**
75
+ * Plain-text mirror of an expand's body, rendered in its place while collapsed so browser
76
+ * find-in-page can still reach the content.
77
+ *
78
+ * Cached on the node. ProseMirror nodes are immutable and `render-document` memoises
79
+ * `nodeFromJSON`, so the same node instance is reused across renders — including across the
80
+ * serializer rebuilds that invalidate the module-level `memoizeOne` caches.
81
+ *
82
+ * @returns the mirror text, or `undefined` if this expand must keep its body rendered.
83
+ */
84
+ var getExpandSearchText = exports.getExpandSearchText = function getExpandSearchText(node) {
85
+ var cached = searchTextCache.get(node);
86
+ if (cached === undefined) {
87
+ cached = hasInlineComment(node) ?
88
+ // Withhold the mirror, which makes Expand keep rendering `children`. Comment
89
+ // navigation walks the DOM to find the annotated node and scrolls it into view, so
90
+ // the annotation has to exist as a real element — a flat text mirror carries no
91
+ // annotation marks and cannot stand in for it. Without this, stepping through
92
+ // comments with the arrow keys silently fails to reach any comment inside a
93
+ // collapsed expand. Costs the lazy-load win for these expands, deliberately.
94
+ null : node.textBetween(0, node.content.size, BLOCK_SEPARATOR, leafText);
95
+ searchTextCache.set(node, cached);
96
+ }
97
+ return cached !== null && cached !== void 0 ? cached : undefined;
98
+ };
@@ -9,7 +9,7 @@ import TextWrapperComponent from './nodes/text-wrapper';
9
9
  import { isNestedHeaderLinksEnabled } from './utils/links';
10
10
  import { getColumnWidths } from '@atlaskit/editor-common/utils';
11
11
  import { getMarksByOrder, isSameMark } from '@atlaskit/editor-common/validator';
12
- import { findChildrenByMark, findChildrenByType } from '@atlaskit/editor-prosemirror/utils';
12
+ import { findChildrenByType } from '@atlaskit/editor-prosemirror/utils';
13
13
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
14
14
  import { fg } from '@atlaskit/platform-feature-flags/fg';
15
15
  import { getText } from '../utils';
@@ -655,24 +655,15 @@ export default class ReactSerializer {
655
655
  };
656
656
  }
657
657
  getExpandProps(node, _path = []) {
658
- let loadBodyContent = false;
659
- if (fg('hot-121622_lazy_load_expand_content')) {
660
- const annotations = findChildrenByMark(node, node.type.schema.marks.annotation, true);
661
- // Force rendering children if there are inline comments to support comments navigation
662
- // which relies on the HTML node to be present.
663
- loadBodyContent = annotations.some(annotation => {
664
- return annotation.node.marks.some(mark => mark.attrs.annotationType === 'inlineComment');
665
- });
666
- }
667
-
668
- // Only compute searchText when the browser find experiment is enabled,
669
- // to avoid unnecessary node.textContent string allocations per expand node.
670
- const searchText = expValEquals('platform_editor_close_expand_find', 'isEnabled', true) ? node.textContent : undefined;
658
+ // Expand receives the node rather than a precomputed string so it can derive the collapsed
659
+ // text mirror only when it actually renders one — a nested expand is not mounted until its
660
+ // ancestor opens and so that derivation can reach content the model walk cannot, such as
661
+ // the ADF stashed in a coerced extension's `parameters.nestedContent`. Expand owns the
662
+ // feature gate check.
671
663
  if (!isNestedHeaderLinksEnabled(this.allowHeadingAnchorLinks)) {
672
664
  return {
673
665
  ...this.getProps(node),
674
- loadBodyContent,
675
- searchText
666
+ node
676
667
  };
677
668
  }
678
669
  const nestedHeaderIds = findChildrenByType(node, node.type.schema.nodes.heading).map(({
@@ -681,8 +672,7 @@ export default class ReactSerializer {
681
672
  return {
682
673
  ...this.getProps(node),
683
674
  nestedHeaderIds,
684
- loadBodyContent,
685
- searchText
675
+ node
686
676
  };
687
677
  }
688
678