@atlaskit/renderer 137.1.6 → 137.1.7
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 +35 -0
- package/dist/cjs/react/index.js +8 -9
- package/dist/cjs/ui/Expand.js +60 -33
- package/dist/cjs/ui/Renderer/index.js +1 -1
- package/dist/cjs/ui/expand-body.js +122 -0
- package/dist/cjs/ui/utils/expand-search-text.js +72 -32
- package/dist/es2019/react/index.js +8 -9
- package/dist/es2019/ui/Expand.js +58 -31
- package/dist/es2019/ui/Renderer/index.js +1 -1
- package/dist/es2019/ui/expand-body.js +107 -0
- package/dist/es2019/ui/utils/expand-search-text.js +67 -31
- package/dist/esm/react/index.js +8 -9
- package/dist/esm/ui/Expand.js +60 -33
- package/dist/esm/ui/Renderer/index.js +1 -1
- package/dist/esm/ui/expand-body.js +114 -0
- package/dist/esm/ui/utils/expand-search-text.js +71 -31
- package/dist/types/ui/Expand.d.ts +5 -4
- package/dist/types/ui/expand-body.d.ts +64 -0
- package/dist/types/ui/utils/expand-search-text.d.ts +18 -7
- package/package.json +4 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# @atlaskit/renderer
|
|
2
2
|
|
|
3
|
+
## 137.1.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`2a540b54c3b3d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2a540b54c3b3d) -
|
|
8
|
+
Two fixes to the lazy loading of collapsed expand bodies, now behind the
|
|
9
|
+
`platform_editor_defer_collapsed_expand_body` experiment rather than the
|
|
10
|
+
`hot-121622_lazy_load_expand_content` feature gate.
|
|
11
|
+
|
|
12
|
+
The body is now only held back where `hidden="until-found"` can reveal it again. Without it
|
|
13
|
+
nothing can reveal the text standing in for the body, so browser find cannot reach the content and
|
|
14
|
+
the body was missing for no gain — those browsers render the body as normal.
|
|
15
|
+
|
|
16
|
+
`display: block` is now set on the collapsed wrapper alongside the attribute. Products ship a CSS
|
|
17
|
+
reset with `[hidden] { display: none }`, and as an author rule it beats the UA stylesheet's
|
|
18
|
+
`content-visibility` for `[hidden="until-found"]`, taking the content out of the page where
|
|
19
|
+
browser find cannot reach it at all.
|
|
20
|
+
|
|
21
|
+
`user-select: none` is no longer set on the collapsed wrapper while browser find is enabled.
|
|
22
|
+
WebKit scrolls to a find-in-page match by selecting it, so unselectable text was matched but never
|
|
23
|
+
revealed: Safari reported a hit, the attribute stayed in place, and the expand never opened.
|
|
24
|
+
Chrome does not need a selection, which hid the problem. Nothing is lost by leaving it off,
|
|
25
|
+
because `content-visibility: hidden` already makes a skipped subtree unselectable.
|
|
26
|
+
|
|
27
|
+
A collapsed expand no longer replaces its whole body with text. Instead each block of the body
|
|
28
|
+
shows its own text, and blocks holding something that can be opened on its own — a nested expand,
|
|
29
|
+
or an extension whose stashed ADF a nested renderer mounts expands from — are rendered as they
|
|
30
|
+
are. So a nested expand keeps a real element while its parent is collapsed, which is what lets
|
|
31
|
+
browser find reveal it directly: one match now opens every expand on the path to it and none
|
|
32
|
+
beside it, where before it opened only the outermost one and left the reader searching again at
|
|
33
|
+
each level. Because blocks decide for themselves rather than being swapped out, opening an expand
|
|
34
|
+
no longer rebuilds everything inside it.
|
|
35
|
+
|
|
36
|
+
- Updated dependencies
|
|
37
|
+
|
|
3
38
|
## 137.1.6
|
|
4
39
|
|
|
5
40
|
### Patch Changes
|
package/dist/cjs/react/index.js
CHANGED
|
@@ -15,6 +15,7 @@ var _react = _interopRequireDefault(require("react"));
|
|
|
15
15
|
var _model = require("@atlaskit/editor-prosemirror/model");
|
|
16
16
|
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
|
|
17
17
|
var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
|
|
18
|
+
var _expandBody = require("../ui/expand-body");
|
|
18
19
|
var _nodes = require("./nodes");
|
|
19
20
|
var _textWrapper = _interopRequireDefault(require("./nodes/text-wrapper"));
|
|
20
21
|
var _links = require("./utils/links");
|
|
@@ -99,12 +100,13 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
|
|
|
99
100
|
var shouldSkipLinkMark = function shouldSkipLinkMark(mark) {
|
|
100
101
|
return _this.allowMediaLinking !== true && isMedia && mark.type.name === 'link';
|
|
101
102
|
};
|
|
102
|
-
|
|
103
|
+
var serialized = marks.reduceRight(function (content, mark) {
|
|
103
104
|
if (shouldSkipLinkMark(mark) || shouldSkipBorderMark(mark)) {
|
|
104
105
|
return content;
|
|
105
106
|
}
|
|
106
107
|
return _this.renderMark((0, _marks.toReact)(mark), _this.withMediaMarkProps(node, mark, _this.getMarkProps(mark, [], node)), "".concat(mark.type.name, "-").concat(index), content);
|
|
107
108
|
}, serializedContent);
|
|
109
|
+
return (0, _expandBody.withExpandBodyBlock)(node, currentPath, index, serialized);
|
|
108
110
|
});
|
|
109
111
|
// Ignored via go/ees005
|
|
110
112
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -283,7 +285,7 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
|
|
|
283
285
|
case 'expand':
|
|
284
286
|
return this.getExpandProps(node, path);
|
|
285
287
|
case 'nestedExpand':
|
|
286
|
-
if ((0,
|
|
288
|
+
if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_defer_collapsed_expand_body')) {
|
|
287
289
|
return this.getExpandProps(node, path);
|
|
288
290
|
}
|
|
289
291
|
return this.getProps(node, path);
|
|
@@ -315,7 +317,7 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
|
|
|
315
317
|
if (key === 'root-0') {
|
|
316
318
|
this.resetState();
|
|
317
319
|
}
|
|
318
|
-
return this.renderNode(target, props, key, this.getChildNodes(fragment).map(function (node, index) {
|
|
320
|
+
return this.renderNode(target, props, key, (0, _expandBody.mergeExpandBodyText)(this.getChildNodes(fragment).map(function (node, index) {
|
|
319
321
|
if ((0, _nodes.isTextWrapper)(node)) {
|
|
320
322
|
return _this2.serializeTextWrapper(node.content, {
|
|
321
323
|
index: index,
|
|
@@ -326,7 +328,7 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
|
|
|
326
328
|
index: index,
|
|
327
329
|
parentInfo: parentInfo
|
|
328
330
|
});
|
|
329
|
-
}));
|
|
331
|
+
})));
|
|
330
332
|
}
|
|
331
333
|
}, {
|
|
332
334
|
key: "serializeTextWrapper",
|
|
@@ -736,11 +738,8 @@ var ReactSerializer = exports.default = /*#__PURE__*/function () {
|
|
|
736
738
|
value: function getExpandProps(node) {
|
|
737
739
|
var _this7 = this;
|
|
738
740
|
var _path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
|
|
739
|
-
// Expand
|
|
740
|
-
// text
|
|
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.
|
|
741
|
+
// Expand is given the node so it can tell whether it opted into lazy body loading at all. The
|
|
742
|
+
// text each block shows is attached to the block itself, by `withExpandBodyBlock`.
|
|
744
743
|
if (!(0, _links.isNestedHeaderLinksEnabled)(this.allowHeadingAnchorLinks)) {
|
|
745
744
|
return _objectSpread(_objectSpread({}, this.getProps(node)), {}, {
|
|
746
745
|
node: node
|
package/dist/cjs/ui/Expand.js
CHANGED
|
@@ -17,8 +17,7 @@ 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
19
|
var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
|
|
20
|
-
var
|
|
21
|
-
var _expandSearchText = require("./utils/expand-search-text");
|
|
20
|
+
var _expandBody = require("./expand-body");
|
|
22
21
|
var _uniqueId2 = _interopRequireDefault(require("lodash/uniqueId"));
|
|
23
22
|
var _reactIntl = require("react-intl");
|
|
24
23
|
var _events = require("../analytics/events");
|
|
@@ -150,12 +149,18 @@ var contentContainerStylesNotExpanded = (0, _react.css)({
|
|
|
150
149
|
// hidden="until-found" attribute on the outer container to hide content.
|
|
151
150
|
// We remove height:0/overflow:hidden/clip from the inner wrapper so the
|
|
152
151
|
// browser can actually search through the content.
|
|
152
|
+
//
|
|
153
|
+
// `user-select: none` must NOT be set here, unlike the variant above. WebKit scrolls to a
|
|
154
|
+
// find-in-page match by selecting it, so unselectable text is matched but never revealed: the
|
|
155
|
+
// reader is told there is a hit, the `hidden` attribute stays put, and the expand never opens.
|
|
156
|
+
// Chrome does not need a selection, so it hid the problem. Nothing is lost by leaving it out —
|
|
157
|
+
// content-visibility:hidden already makes a skipped subtree unselectable. It is only needed in the
|
|
158
|
+
// other variant, where the content is merely clipped and would otherwise be selectable.
|
|
153
159
|
var contentContainerStylesNotExpandedBrowserFind = (0, _react.css)({
|
|
154
160
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
|
|
155
161
|
'.expand-content-wrapper, .nestedExpand-content-wrapper': {
|
|
156
162
|
width: '100%',
|
|
157
|
-
display: 'block'
|
|
158
|
-
userSelect: 'none'
|
|
163
|
+
display: 'block'
|
|
159
164
|
}
|
|
160
165
|
});
|
|
161
166
|
var clearNextSiblingMarginTopStyle = (0, _react.css)({
|
|
@@ -223,6 +228,7 @@ function Expand(_ref) {
|
|
|
223
228
|
nestedHeaderIds = _ref.nestedHeaderIds,
|
|
224
229
|
rendererContentMode = _ref.rendererContentMode,
|
|
225
230
|
node = _ref.node;
|
|
231
|
+
var ancestorBody = (0, _expandBody.useExpandBody)();
|
|
226
232
|
var _useState = (0, _react2.useState)(false),
|
|
227
233
|
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
|
|
228
234
|
expanded = _useState2[0],
|
|
@@ -232,12 +238,11 @@ function Expand(_ref) {
|
|
|
232
238
|
focused = _useState4[0],
|
|
233
239
|
setFocused = _useState4[1];
|
|
234
240
|
/**
|
|
235
|
-
* PGXT-9021:
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* client's first render agree and hydration cannot desync.
|
|
241
|
+
* PGXT-9021: once opened, the body stays rendered even if the reader closes the expand again.
|
|
242
|
+
* Throwing it away would re-run every macro and Forge fetch inside it on the next open, which is
|
|
243
|
+
* slower and visibly reloads content the reader has already seen. We only wanted to save work on
|
|
244
|
+
* the first page load, and by now that has happened. Starts false on both the server and the
|
|
245
|
+
* client, so hydration cannot disagree.
|
|
241
246
|
*/
|
|
242
247
|
var _useState5 = (0, _react2.useState)(false),
|
|
243
248
|
_useState6 = (0, _slicedToArray2.default)(_useState5, 2),
|
|
@@ -261,15 +266,21 @@ function Expand(_ref) {
|
|
|
261
266
|
setExpanded(true);
|
|
262
267
|
setHasBeenExpanded(true);
|
|
263
268
|
}, []);
|
|
269
|
+
// The browser found the text inside this expand. Opening this one is not enough: if it sits
|
|
270
|
+
// inside other expands, those have to open too or the reader still cannot see it. Each expand
|
|
271
|
+
// asks the one above it, so a single match opens the whole chain and nothing else.
|
|
272
|
+
var openWithAncestors = (0, _react2.useCallback)(function () {
|
|
273
|
+
openBody();
|
|
274
|
+
ancestorBody === null || ancestorBody === void 0 || ancestorBody.openWithAncestors();
|
|
275
|
+
}, [ancestorBody, openBody]);
|
|
264
276
|
|
|
265
|
-
// Feature-detect hidden="until-found" support via the beforematch event.
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
var _useState7 = (0, _react2.useState)(false),
|
|
277
|
+
// Feature-detect hidden="until-found" support via the beforematch event. Chrome 102+,
|
|
278
|
+
// Firefox 139+ and Safari 26.2+ all have it; in a browser without it, hidden="until-found" is
|
|
279
|
+
// treated as plain boolean hidden (display:none), which would break the expand entirely.
|
|
280
|
+
// Starts as `undefined`, meaning "we do not know yet", and is only answered in an effect. We
|
|
281
|
+
// cannot check during render: the server has no browser to check, so it would say no there and
|
|
282
|
+
// yes on the client, and hydration would disagree.
|
|
283
|
+
var _useState7 = (0, _react2.useState)(undefined),
|
|
273
284
|
_useState8 = (0, _slicedToArray2.default)(_useState7, 2),
|
|
274
285
|
supportsHiddenUntilFound = _useState8[0],
|
|
275
286
|
setSupportsHiddenUntilFound = _useState8[1];
|
|
@@ -286,9 +297,9 @@ function Expand(_ref) {
|
|
|
286
297
|
// because visibility:hidden blocks browser find. On expanded, we restore visibility to visible.
|
|
287
298
|
//
|
|
288
299
|
// Only applied when the browser supports hidden="until-found" (detected via onbeforematch).
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
//
|
|
300
|
+
// Without it we skip this entirely and fall back to the normal CSS hiding
|
|
301
|
+
// (visibility:hidden + height:0), which doesn't support find-in-page but still works
|
|
302
|
+
// correctly for expand/collapse.
|
|
292
303
|
(0, _react2.useEffect)(function () {
|
|
293
304
|
var contentContainer = contentContainerRef.current;
|
|
294
305
|
var contentWrapper = contentWrapperRef.current;
|
|
@@ -297,6 +308,13 @@ function Expand(_ref) {
|
|
|
297
308
|
}
|
|
298
309
|
if (supportsHiddenUntilFound && (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_close_expand_find') && !expanded) {
|
|
299
310
|
contentWrapper.setAttribute('hidden', 'until-found');
|
|
311
|
+
// Products ship a CSS reset with `[hidden] { display: none }` — Confluence does. That is an
|
|
312
|
+
// author rule, so it beats the UA stylesheet's
|
|
313
|
+
// `[hidden="until-found"] { content-visibility: hidden }` and takes the content out of the
|
|
314
|
+
// page altogether, where find cannot reach it. Setting display next to the attribute keeps
|
|
315
|
+
// the two together, rather than relying on a class selector elsewhere out-weighing a reset
|
|
316
|
+
// we do not own.
|
|
317
|
+
contentWrapper.style.display = 'block';
|
|
300
318
|
// Override the CSS visibility:hidden from contentContainerStyles — hidden="until-found"
|
|
301
319
|
// now handles hiding via content-visibility:hidden, which allows browser find to index
|
|
302
320
|
// the content. We use 'visible' (not '') because '' only clears the inline style but
|
|
@@ -305,6 +323,7 @@ function Expand(_ref) {
|
|
|
305
323
|
contentWrapper.style.visibility = 'visible';
|
|
306
324
|
} else {
|
|
307
325
|
contentWrapper.removeAttribute('hidden');
|
|
326
|
+
contentWrapper.style.display = '';
|
|
308
327
|
contentContainer === null || contentContainer === void 0 || contentContainer.style.removeProperty('visibility');
|
|
309
328
|
contentWrapper.style.visibility = '';
|
|
310
329
|
}
|
|
@@ -316,22 +335,28 @@ function Expand(_ref) {
|
|
|
316
335
|
var contentWrapper = contentWrapperRef.current;
|
|
317
336
|
var unbindWrapperBeforeMatch = contentWrapper && supportsHiddenUntilFound ? (0, _bindEventListener.bind)(contentWrapper, {
|
|
318
337
|
type: 'beforematch',
|
|
319
|
-
listener:
|
|
338
|
+
listener: openWithAncestors
|
|
320
339
|
}) : undefined;
|
|
321
340
|
return function () {
|
|
322
341
|
unbindWrapperBeforeMatch === null || unbindWrapperBeforeMatch === void 0 || unbindWrapperBeforeMatch();
|
|
323
342
|
};
|
|
324
|
-
}, [expanded,
|
|
343
|
+
}, [expanded, openWithAncestors, supportsHiddenUntilFound]);
|
|
325
344
|
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
var
|
|
345
|
+
// While this is false, the blocks of the body show their text instead of rendering. It is true
|
|
346
|
+
// when:
|
|
347
|
+
// - the expand is open, or has been opened before, so the reader wants to see the content;
|
|
348
|
+
// - no node was given, so this expand never opted into lazy loading;
|
|
349
|
+
// - the experiment is off;
|
|
350
|
+
// - the browser has no hidden="until-found", so the text would not be findable and standing it in
|
|
351
|
+
// would gain nothing. While we still do not know, we assume the browser has it — assuming the
|
|
352
|
+
// opposite would render the whole body on the first paint and lose the saving entirely.
|
|
353
|
+
var revealed = expanded || hasBeenExpanded || !node || supportsHiddenUntilFound === false || !(0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_defer_collapsed_expand_body');
|
|
354
|
+
var expandBody = (0, _react2.useMemo)(function () {
|
|
355
|
+
return {
|
|
356
|
+
revealed: revealed,
|
|
357
|
+
openWithAncestors: openWithAncestors
|
|
358
|
+
};
|
|
359
|
+
}, [revealed, openWithAncestors]);
|
|
335
360
|
return (0, _react.jsx)(Container, {
|
|
336
361
|
"data-testid": "expand-container-".concat(nodeType, "-").concat(id),
|
|
337
362
|
"data-node-type": nodeType,
|
|
@@ -396,7 +421,9 @@ function Expand(_ref) {
|
|
|
396
421
|
ref: contentWrapperRef
|
|
397
422
|
}, (0, _react.jsx)(_ui.WidthProvider, null, (0, _react.jsx)("div", {
|
|
398
423
|
css: clearNextSiblingMarginTopStyle
|
|
399
|
-
}),
|
|
424
|
+
}), (0, _react.jsx)(_expandBody.ExpandBodyProvider, {
|
|
425
|
+
value: expandBody
|
|
426
|
+
}, children)))));
|
|
400
427
|
}
|
|
401
428
|
|
|
402
429
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
@@ -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.
|
|
77
|
+
var packageVersion = "137.1.6";
|
|
78
78
|
var setAsQueryContainerStyles = (0, _react2.css)({
|
|
79
79
|
containerName: 'ak-renderer-wrapper',
|
|
80
80
|
containerType: 'inline-size'
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _typeof = require("@babel/runtime/helpers/typeof");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.withExpandBodyBlock = exports.useExpandBody = exports.mergeExpandBodyText = exports.ExpandBodyProvider = exports.ExpandBodyBlock = void 0;
|
|
8
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
9
|
+
var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
|
|
10
|
+
var _expandSearchText = require("./utils/expand-search-text");
|
|
11
|
+
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); }
|
|
12
|
+
var ExpandBodyContext = /*#__PURE__*/(0, _react.createContext)(null);
|
|
13
|
+
var ExpandBodyProvider = exports.ExpandBodyProvider = ExpandBodyContext.Provider;
|
|
14
|
+
var useExpandBody = exports.useExpandBody = function useExpandBody() {
|
|
15
|
+
return (0, _react.useContext)(ExpandBodyContext);
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* One or more neighbouring blocks of an expand's body. Shows their text until the expand is
|
|
19
|
+
* opened, then renders them. Falls back to rendering if there is no text, or if there is no
|
|
20
|
+
* expand above it.
|
|
21
|
+
*
|
|
22
|
+
* The text is rendered as-is, with no element around it. Nothing reads it but browser find,
|
|
23
|
+
* which only needs the characters to be in the DOM.
|
|
24
|
+
*/
|
|
25
|
+
var ExpandBodyBlock = exports.ExpandBodyBlock = function ExpandBodyBlock(_ref) {
|
|
26
|
+
var children = _ref.children,
|
|
27
|
+
searchText = _ref.searchText;
|
|
28
|
+
var body = useExpandBody();
|
|
29
|
+
if (searchText === undefined || body === null || body.revealed) {
|
|
30
|
+
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, children);
|
|
31
|
+
}
|
|
32
|
+
return searchText;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Called for every node the serializer renders. If the node is a block of an expand's body,
|
|
37
|
+
* wraps it so it can show its text instead of rendering while that expand is collapsed.
|
|
38
|
+
* Everything else is returned untouched.
|
|
39
|
+
*
|
|
40
|
+
* A block holding an expand of its own is also returned untouched, so a table with a nested
|
|
41
|
+
* expand in it, or an extension holding stashed ADF, renders even while collapsed. That
|
|
42
|
+
* expand needs an element of its own, because the element browser find reveals is the only
|
|
43
|
+
* way we learn the match was inside it rather than higher up. We give up the saving on those
|
|
44
|
+
* blocks so the reader searches once instead of once per level.
|
|
45
|
+
*
|
|
46
|
+
* `ancestors` is the node's ancestor chain, nearest last.
|
|
47
|
+
*/
|
|
48
|
+
var withExpandBodyBlock = exports.withExpandBodyBlock = function withExpandBodyBlock(node, ancestors, index, serialized) {
|
|
49
|
+
var parent = ancestors[ancestors.length - 1];
|
|
50
|
+
if (!parent || !(0, _expandSearchText.isExpandNode)(parent)) {
|
|
51
|
+
return serialized;
|
|
52
|
+
}
|
|
53
|
+
if (!(0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_defer_collapsed_expand_body') || (0, _expandSearchText.holdsRevealableContent)(node)) {
|
|
54
|
+
return serialized;
|
|
55
|
+
}
|
|
56
|
+
var searchText = (0, _expandSearchText.getBlockSearchText)(node);
|
|
57
|
+
if (searchText === undefined) {
|
|
58
|
+
return serialized;
|
|
59
|
+
}
|
|
60
|
+
return /*#__PURE__*/_react.default.createElement(ExpandBodyBlock, {
|
|
61
|
+
key: "expand-body-block-".concat(index),
|
|
62
|
+
searchText: searchText
|
|
63
|
+
}, serialized);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Whatever the serializer produced for one child: an element, a string, an array of either. */
|
|
67
|
+
|
|
68
|
+
var searchTextOf = function searchTextOf(child) {
|
|
69
|
+
return /*#__PURE__*/(0, _react.isValidElement)(child) && child.type === ExpandBodyBlock ? child.props.searchText : undefined;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Joins neighbouring blocks that are showing text into one, so a run of them is a single string in
|
|
74
|
+
* the DOM rather than one per block. A body of twenty paragraphs becomes one text node instead of
|
|
75
|
+
* twenty.
|
|
76
|
+
*
|
|
77
|
+
* A block that is being rendered — a nested expand, say — ends the run, because the text either
|
|
78
|
+
* side of it has to stay either side of it.
|
|
79
|
+
*/
|
|
80
|
+
var mergeExpandBodyText = exports.mergeExpandBodyText = function mergeExpandBodyText(children) {
|
|
81
|
+
// Called for every fragment in the document, and only an expand's body has anything to merge, so
|
|
82
|
+
// leave the array alone unless two neighbours are actually showing text.
|
|
83
|
+
var hasRun = children.some(function (child, index) {
|
|
84
|
+
return index > 0 && searchTextOf(child) !== undefined && searchTextOf(children[index - 1]) !== undefined;
|
|
85
|
+
});
|
|
86
|
+
if (!hasRun) {
|
|
87
|
+
return children;
|
|
88
|
+
}
|
|
89
|
+
var merged = [];
|
|
90
|
+
var run = [];
|
|
91
|
+
var endRun = function endRun() {
|
|
92
|
+
if (run.length === 0) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (run.length === 1) {
|
|
96
|
+
merged.push(run[0]);
|
|
97
|
+
} else {
|
|
98
|
+
var _key;
|
|
99
|
+
var text = run.map(searchTextOf).filter(Boolean).join(_expandSearchText.BLOCK_SEPARATOR);
|
|
100
|
+
// The blocks themselves, not the wrappers around them: nesting the wrappers would show
|
|
101
|
+
// each block's text again inside the joined run.
|
|
102
|
+
var blocks = run.map(function (child) {
|
|
103
|
+
return child.props.children;
|
|
104
|
+
});
|
|
105
|
+
merged.push( /*#__PURE__*/_react.default.createElement(ExpandBodyBlock, {
|
|
106
|
+
key: (_key = run[0].key) !== null && _key !== void 0 ? _key : undefined,
|
|
107
|
+
searchText: text
|
|
108
|
+
}, blocks));
|
|
109
|
+
}
|
|
110
|
+
run = [];
|
|
111
|
+
};
|
|
112
|
+
children.forEach(function (child) {
|
|
113
|
+
if (searchTextOf(child) === undefined) {
|
|
114
|
+
endRun();
|
|
115
|
+
merged.push(child);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
run.push(child);
|
|
119
|
+
});
|
|
120
|
+
endRun();
|
|
121
|
+
return merged;
|
|
122
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
6
|
+
exports.isExpandNode = exports.holdsRevealableContent = exports.getBlockSearchText = exports.BLOCK_SEPARATOR = void 0;
|
|
7
7
|
var _utils = require("@atlaskit/editor-prosemirror/utils");
|
|
8
8
|
/**
|
|
9
9
|
* Minimal structural view of an ADF entity. `parameters.nestedContent` holds raw ADF JSON rather
|
|
@@ -11,22 +11,29 @@ var _utils = require("@atlaskit/editor-prosemirror/utils");
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
/** Separates text from adjacent blocks so phrases cannot fuse across block boundaries. */
|
|
14
|
-
var BLOCK_SEPARATOR = ' ';
|
|
14
|
+
var BLOCK_SEPARATOR = exports.BLOCK_SEPARATOR = ' ';
|
|
15
15
|
var asText = function asText(value) {
|
|
16
16
|
return typeof value === 'string' ? value : '';
|
|
17
17
|
};
|
|
18
|
+
var nestedContentOf = function nestedContentOf(attrs) {
|
|
19
|
+
var _attrs$parameters;
|
|
20
|
+
return attrs === null || attrs === void 0 || (_attrs$parameters = attrs.parameters) === null || _attrs$parameters === void 0 ? void 0 : _attrs$parameters.nestedContent;
|
|
21
|
+
};
|
|
22
|
+
var isExpandNode = exports.isExpandNode = function isExpandNode(node) {
|
|
23
|
+
return node.type.name === 'expand' || node.type.name === 'nestedExpand';
|
|
24
|
+
};
|
|
18
25
|
|
|
19
26
|
/**
|
|
20
|
-
*
|
|
27
|
+
* Reads the text out of raw ADF JSON.
|
|
21
28
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
29
|
+
* Some extensions keep a whole ADF subtree in `attrs.parameters.nestedContent` rather than in their
|
|
30
|
+
* node content. Confluence does this when the authored nesting is deeper than the schema allows —
|
|
31
|
+
* an expand more than two collapsible layers deep, for example. To ProseMirror the extension is a
|
|
32
|
+
* leaf, so walking the node tree never sees that subtree, even though a nested renderer puts it on
|
|
33
|
+
* the page. Recurses, because stashed content often stashes more content of its own.
|
|
27
34
|
*/
|
|
28
35
|
var _adfEntityText = function adfEntityText(entity) {
|
|
29
|
-
var _entity$
|
|
36
|
+
var _entity$content;
|
|
30
37
|
if (!entity) {
|
|
31
38
|
return '';
|
|
32
39
|
}
|
|
@@ -34,13 +41,13 @@ var _adfEntityText = function adfEntityText(entity) {
|
|
|
34
41
|
if (entity.type === 'text') {
|
|
35
42
|
parts.push(asText(entity.text));
|
|
36
43
|
}
|
|
37
|
-
var nestedContent = (
|
|
44
|
+
var nestedContent = nestedContentOf(entity.attrs);
|
|
38
45
|
if (nestedContent) {
|
|
39
46
|
// Prefer the real subtree over `attrs.text`, which is only a placeholder label for it.
|
|
40
47
|
parts.push(_adfEntityText(nestedContent));
|
|
41
48
|
} else {
|
|
42
|
-
var _entity$
|
|
43
|
-
parts.push(asText((_entity$
|
|
49
|
+
var _entity$attrs;
|
|
50
|
+
parts.push(asText((_entity$attrs = entity.attrs) === null || _entity$attrs === void 0 ? void 0 : _entity$attrs.text));
|
|
44
51
|
}
|
|
45
52
|
(_entity$content = entity.content) === null || _entity$content === void 0 || _entity$content.forEach(function (child) {
|
|
46
53
|
return parts.push(_adfEntityText(child));
|
|
@@ -49,16 +56,48 @@ var _adfEntityText = function adfEntityText(entity) {
|
|
|
49
56
|
};
|
|
50
57
|
|
|
51
58
|
/**
|
|
52
|
-
* `textBetween`
|
|
53
|
-
*
|
|
59
|
+
* `textBetween` skips every leaf node unless its spec declares `leafText`, and no ADF node
|
|
60
|
+
* does — so mentions, emoji, dates and statuses would all be lost. They keep their visible
|
|
61
|
+
* text in attrs, so read it from there.
|
|
54
62
|
*/
|
|
55
63
|
var leafText = function leafText(leaf) {
|
|
56
|
-
var _leaf$attrs
|
|
57
|
-
var nestedContent = (
|
|
64
|
+
var _leaf$attrs;
|
|
65
|
+
var nestedContent = nestedContentOf(leaf.attrs);
|
|
58
66
|
if (nestedContent) {
|
|
59
67
|
return _adfEntityText(nestedContent);
|
|
60
68
|
}
|
|
61
|
-
return asText((_leaf$
|
|
69
|
+
return asText((_leaf$attrs = leaf.attrs) === null || _leaf$attrs === void 0 ? void 0 : _leaf$attrs.text);
|
|
70
|
+
};
|
|
71
|
+
var isRevealable = function isRevealable(node) {
|
|
72
|
+
return isExpandNode(node) || Boolean(nestedContentOf(node.attrs));
|
|
73
|
+
};
|
|
74
|
+
var revealableCache = new WeakMap();
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether this block holds something that can be opened on its own: an expand, or an extension
|
|
78
|
+
* holding stashed ADF that a nested renderer will mount expands from.
|
|
79
|
+
*
|
|
80
|
+
* Those need a real element even while the expand around them is collapsed. The element is how we
|
|
81
|
+
* know find matched inside them rather than higher up: the beforematch event says nothing about
|
|
82
|
+
* where the match was, so the element the browser chose to reveal is the only clue.
|
|
83
|
+
*/
|
|
84
|
+
var holdsRevealableContent = exports.holdsRevealableContent = function holdsRevealableContent(node) {
|
|
85
|
+
var cached = revealableCache.get(node);
|
|
86
|
+
if (cached !== undefined) {
|
|
87
|
+
return cached;
|
|
88
|
+
}
|
|
89
|
+
var found = isRevealable(node);
|
|
90
|
+
if (!found) {
|
|
91
|
+
node.descendants(function (descendant) {
|
|
92
|
+
if (found) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
found = isRevealable(descendant);
|
|
96
|
+
return !found;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
revealableCache.set(node, found);
|
|
100
|
+
return found;
|
|
62
101
|
};
|
|
63
102
|
var hasInlineComment = function hasInlineComment(node) {
|
|
64
103
|
return (0, _utils.findChildrenByMark)(node, node.type.schema.marks.annotation, true).some(function (annotation) {
|
|
@@ -68,30 +107,31 @@ var hasInlineComment = function hasInlineComment(node) {
|
|
|
68
107
|
});
|
|
69
108
|
};
|
|
70
109
|
|
|
71
|
-
/** `null` is a cached "no
|
|
110
|
+
/** `null` is a cached "no search text for this node", as distinct from `undefined` for a miss. */
|
|
72
111
|
var searchTextCache = new WeakMap();
|
|
73
112
|
|
|
74
113
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
114
|
+
* The text shown in place of one block of a collapsed expand's body, so browser find can still
|
|
115
|
+
* reach the content without the block being rendered.
|
|
77
116
|
*
|
|
78
|
-
* Cached on the node. ProseMirror nodes
|
|
79
|
-
*
|
|
80
|
-
* serializer rebuilds that invalidate the module-level `memoizeOne` caches.
|
|
117
|
+
* Cached on the node. ProseMirror nodes never change, and the serializer reuses the same instances
|
|
118
|
+
* across renders, so the text only has to be worked out once per block.
|
|
81
119
|
*
|
|
82
|
-
* @returns the
|
|
120
|
+
* @returns the text, or `undefined` if this block has to be rendered instead.
|
|
83
121
|
*/
|
|
84
|
-
var
|
|
122
|
+
var getBlockSearchText = exports.getBlockSearchText = function getBlockSearchText(node) {
|
|
85
123
|
var cached = searchTextCache.get(node);
|
|
86
124
|
if (cached === undefined) {
|
|
87
125
|
cached = hasInlineComment(node) ?
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
126
|
+
// No text, which makes the block render instead. Comment navigation looks through the DOM
|
|
127
|
+
// for the commented text and scrolls to it, so that text has to be a real element —
|
|
128
|
+
// plain text carries no comment marks and cannot stand in for it. Without this, the
|
|
129
|
+
// arrow keys silently skip every comment inside a collapsed expand. We give up the
|
|
130
|
+
// saving for this block on purpose.
|
|
131
|
+
null :
|
|
132
|
+
// A leaf block is not inside its own range, so `textBetween` would return nothing and its
|
|
133
|
+
// text would be lost — a macro sitting straight in an expand body, for instance.
|
|
134
|
+
node.isLeaf ? leafText(node) : node.textBetween(0, node.content.size, BLOCK_SEPARATOR, leafText);
|
|
95
135
|
searchTextCache.set(node, cached);
|
|
96
136
|
}
|
|
97
137
|
return cached !== null && cached !== void 0 ? cached : undefined;
|
|
@@ -4,6 +4,7 @@ import React from 'react';
|
|
|
4
4
|
import { MarkType } from '@atlaskit/editor-prosemirror/model';
|
|
5
5
|
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
6
6
|
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
7
|
+
import { mergeExpandBodyText, withExpandBodyBlock } from '../ui/expand-body';
|
|
7
8
|
import { Doc, DocWithSelectAllTrap, isTextNode, isTextWrapper, mergeTextNodes, toReact } from './nodes';
|
|
8
9
|
import TextWrapperComponent from './nodes/text-wrapper';
|
|
9
10
|
import { isNestedHeaderLinksEnabled } from './utils/links';
|
|
@@ -80,12 +81,13 @@ export default class ReactSerializer {
|
|
|
80
81
|
return ((_n$type = n.type) === null || _n$type === void 0 ? void 0 : _n$type.name) !== 'mediaSingle';
|
|
81
82
|
}) && isMedia && mark.type.name === 'border';
|
|
82
83
|
const shouldSkipLinkMark = mark => this.allowMediaLinking !== true && isMedia && mark.type.name === 'link';
|
|
83
|
-
|
|
84
|
+
const serialized = marks.reduceRight((content, mark) => {
|
|
84
85
|
if (shouldSkipLinkMark(mark) || shouldSkipBorderMark(mark)) {
|
|
85
86
|
return content;
|
|
86
87
|
}
|
|
87
88
|
return this.renderMark(markToReact(mark), this.withMediaMarkProps(node, mark, this.getMarkProps(mark, [], node)), `${mark.type.name}-${index}`, content);
|
|
88
89
|
}, serializedContent);
|
|
90
|
+
return withExpandBodyBlock(node, currentPath, index, serialized);
|
|
89
91
|
});
|
|
90
92
|
// Ignored via go/ees005
|
|
91
93
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -266,7 +268,7 @@ export default class ReactSerializer {
|
|
|
266
268
|
case 'expand':
|
|
267
269
|
return this.getExpandProps(node, path);
|
|
268
270
|
case 'nestedExpand':
|
|
269
|
-
if (
|
|
271
|
+
if (isExperimentEnabled('platform_editor_defer_collapsed_expand_body')) {
|
|
270
272
|
return this.getExpandProps(node, path);
|
|
271
273
|
}
|
|
272
274
|
return this.getProps(node, path);
|
|
@@ -297,7 +299,7 @@ export default class ReactSerializer {
|
|
|
297
299
|
if (key === 'root-0') {
|
|
298
300
|
this.resetState();
|
|
299
301
|
}
|
|
300
|
-
return this.renderNode(target, props, key, this.getChildNodes(fragment).map((node, index) => {
|
|
302
|
+
return this.renderNode(target, props, key, mergeExpandBodyText(this.getChildNodes(fragment).map((node, index) => {
|
|
301
303
|
if (isTextWrapper(node)) {
|
|
302
304
|
return this.serializeTextWrapper(node.content, {
|
|
303
305
|
index,
|
|
@@ -308,7 +310,7 @@ export default class ReactSerializer {
|
|
|
308
310
|
index,
|
|
309
311
|
parentInfo
|
|
310
312
|
});
|
|
311
|
-
}));
|
|
313
|
+
})));
|
|
312
314
|
}
|
|
313
315
|
serializeTextWrapper(content, {
|
|
314
316
|
parentInfo
|
|
@@ -655,11 +657,8 @@ export default class ReactSerializer {
|
|
|
655
657
|
};
|
|
656
658
|
}
|
|
657
659
|
getExpandProps(node, _path = []) {
|
|
658
|
-
// Expand
|
|
659
|
-
// text
|
|
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.
|
|
660
|
+
// Expand is given the node so it can tell whether it opted into lazy body loading at all. The
|
|
661
|
+
// text each block shows is attached to the block itself, by `withExpandBodyBlock`.
|
|
663
662
|
if (!isNestedHeaderLinksEnabled(this.allowHeadingAnchorLinks)) {
|
|
664
663
|
return {
|
|
665
664
|
...this.getProps(node),
|