@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/dist/es2019/ui/Expand.js
CHANGED
|
@@ -13,8 +13,7 @@ import { akEditorLineHeight, akEditorSwoopCubicBezier, akLayoutGutterOffset } fr
|
|
|
13
13
|
import ChevronRightIcon from '@atlaskit/icon/core/chevron-right';
|
|
14
14
|
import Tooltip from '@atlaskit/tooltip/Tooltip';
|
|
15
15
|
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
|
|
16
|
-
import {
|
|
17
|
-
import { getExpandSearchText } from './utils/expand-search-text';
|
|
16
|
+
import { ExpandBodyProvider, useExpandBody } from './expand-body';
|
|
18
17
|
import _uniqueId from 'lodash/uniqueId';
|
|
19
18
|
import { injectIntl } from 'react-intl';
|
|
20
19
|
import { MODE, PLATFORM } from '../analytics/events';
|
|
@@ -139,12 +138,18 @@ const contentContainerStylesNotExpanded = css({
|
|
|
139
138
|
// hidden="until-found" attribute on the outer container to hide content.
|
|
140
139
|
// We remove height:0/overflow:hidden/clip from the inner wrapper so the
|
|
141
140
|
// browser can actually search through the content.
|
|
141
|
+
//
|
|
142
|
+
// `user-select: none` must NOT be set here, unlike the variant above. WebKit scrolls to a
|
|
143
|
+
// find-in-page match by selecting it, so unselectable text is matched but never revealed: the
|
|
144
|
+
// reader is told there is a hit, the `hidden` attribute stays put, and the expand never opens.
|
|
145
|
+
// Chrome does not need a selection, so it hid the problem. Nothing is lost by leaving it out —
|
|
146
|
+
// content-visibility:hidden already makes a skipped subtree unselectable. It is only needed in the
|
|
147
|
+
// other variant, where the content is merely clipped and would otherwise be selectable.
|
|
142
148
|
const contentContainerStylesNotExpandedBrowserFind = css({
|
|
143
149
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
|
|
144
150
|
'.expand-content-wrapper, .nestedExpand-content-wrapper': {
|
|
145
151
|
width: '100%',
|
|
146
|
-
display: 'block'
|
|
147
|
-
userSelect: 'none'
|
|
152
|
+
display: 'block'
|
|
148
153
|
}
|
|
149
154
|
});
|
|
150
155
|
const clearNextSiblingMarginTopStyle = css({
|
|
@@ -215,15 +220,15 @@ function Expand({
|
|
|
215
220
|
rendererContentMode,
|
|
216
221
|
node
|
|
217
222
|
}) {
|
|
223
|
+
const ancestorBody = useExpandBody();
|
|
218
224
|
const [expanded, setExpanded] = useState(false);
|
|
219
225
|
const [focused, setFocused] = useState(false);
|
|
220
226
|
/**
|
|
221
|
-
* PGXT-9021:
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* client's first render agree and hydration cannot desync.
|
|
227
|
+
* PGXT-9021: once opened, the body stays rendered even if the reader closes the expand again.
|
|
228
|
+
* Throwing it away would re-run every macro and Forge fetch inside it on the next open, which is
|
|
229
|
+
* slower and visibly reloads content the reader has already seen. We only wanted to save work on
|
|
230
|
+
* the first page load, and by now that has happened. Starts false on both the server and the
|
|
231
|
+
* client, so hydration cannot disagree.
|
|
227
232
|
*/
|
|
228
233
|
const [hasBeenExpanded, setHasBeenExpanded] = useState(false);
|
|
229
234
|
const isMobile = false;
|
|
@@ -241,15 +246,21 @@ function Expand({
|
|
|
241
246
|
setExpanded(true);
|
|
242
247
|
setHasBeenExpanded(true);
|
|
243
248
|
}, []);
|
|
249
|
+
// The browser found the text inside this expand. Opening this one is not enough: if it sits
|
|
250
|
+
// inside other expands, those have to open too or the reader still cannot see it. Each expand
|
|
251
|
+
// asks the one above it, so a single match opens the whole chain and nothing else.
|
|
252
|
+
const openWithAncestors = useCallback(() => {
|
|
253
|
+
openBody();
|
|
254
|
+
ancestorBody === null || ancestorBody === void 0 ? void 0 : ancestorBody.openWithAncestors();
|
|
255
|
+
}, [ancestorBody, openBody]);
|
|
244
256
|
|
|
245
|
-
// Feature-detect hidden="until-found" support via the beforematch event.
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
const [supportsHiddenUntilFound, setSupportsHiddenUntilFound] = useState(false);
|
|
257
|
+
// Feature-detect hidden="until-found" support via the beforematch event. Chrome 102+,
|
|
258
|
+
// Firefox 139+ and Safari 26.2+ all have it; in a browser without it, hidden="until-found" is
|
|
259
|
+
// treated as plain boolean hidden (display:none), which would break the expand entirely.
|
|
260
|
+
// Starts as `undefined`, meaning "we do not know yet", and is only answered in an effect. We
|
|
261
|
+
// cannot check during render: the server has no browser to check, so it would say no there and
|
|
262
|
+
// yes on the client, and hydration would disagree.
|
|
263
|
+
const [supportsHiddenUntilFound, setSupportsHiddenUntilFound] = useState(undefined);
|
|
253
264
|
useEffect(() => {
|
|
254
265
|
const doc = getDocument();
|
|
255
266
|
setSupportsHiddenUntilFound(doc !== null && doc !== void 0 && doc.body ? 'onbeforematch' in doc.body : false);
|
|
@@ -263,9 +274,9 @@ function Expand({
|
|
|
263
274
|
// because visibility:hidden blocks browser find. On expanded, we restore visibility to visible.
|
|
264
275
|
//
|
|
265
276
|
// Only applied when the browser supports hidden="until-found" (detected via onbeforematch).
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
277
|
+
// Without it we skip this entirely and fall back to the normal CSS hiding
|
|
278
|
+
// (visibility:hidden + height:0), which doesn't support find-in-page but still works
|
|
279
|
+
// correctly for expand/collapse.
|
|
269
280
|
useEffect(() => {
|
|
270
281
|
const contentContainer = contentContainerRef.current;
|
|
271
282
|
const contentWrapper = contentWrapperRef.current;
|
|
@@ -274,6 +285,13 @@ function Expand({
|
|
|
274
285
|
}
|
|
275
286
|
if (supportsHiddenUntilFound && isExperimentEnabled('platform_editor_close_expand_find') && !expanded) {
|
|
276
287
|
contentWrapper.setAttribute('hidden', 'until-found');
|
|
288
|
+
// Products ship a CSS reset with `[hidden] { display: none }` — Confluence does. That is an
|
|
289
|
+
// author rule, so it beats the UA stylesheet's
|
|
290
|
+
// `[hidden="until-found"] { content-visibility: hidden }` and takes the content out of the
|
|
291
|
+
// page altogether, where find cannot reach it. Setting display next to the attribute keeps
|
|
292
|
+
// the two together, rather than relying on a class selector elsewhere out-weighing a reset
|
|
293
|
+
// we do not own.
|
|
294
|
+
contentWrapper.style.display = 'block';
|
|
277
295
|
// Override the CSS visibility:hidden from contentContainerStyles — hidden="until-found"
|
|
278
296
|
// now handles hiding via content-visibility:hidden, which allows browser find to index
|
|
279
297
|
// the content. We use 'visible' (not '') because '' only clears the inline style but
|
|
@@ -282,6 +300,7 @@ function Expand({
|
|
|
282
300
|
contentWrapper.style.visibility = 'visible';
|
|
283
301
|
} else {
|
|
284
302
|
contentWrapper.removeAttribute('hidden');
|
|
303
|
+
contentWrapper.style.display = '';
|
|
285
304
|
contentContainer === null || contentContainer === void 0 ? void 0 : contentContainer.style.removeProperty('visibility');
|
|
286
305
|
contentWrapper.style.visibility = '';
|
|
287
306
|
}
|
|
@@ -293,20 +312,26 @@ function Expand({
|
|
|
293
312
|
const contentWrapper = contentWrapperRef.current;
|
|
294
313
|
const unbindWrapperBeforeMatch = contentWrapper && supportsHiddenUntilFound ? bind(contentWrapper, {
|
|
295
314
|
type: 'beforematch',
|
|
296
|
-
listener:
|
|
315
|
+
listener: openWithAncestors
|
|
297
316
|
}) : undefined;
|
|
298
317
|
return () => {
|
|
299
318
|
unbindWrapperBeforeMatch === null || unbindWrapperBeforeMatch === void 0 ? void 0 : unbindWrapperBeforeMatch();
|
|
300
319
|
};
|
|
301
|
-
}, [expanded,
|
|
320
|
+
}, [expanded, openWithAncestors, supportsHiddenUntilFound]);
|
|
302
321
|
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
322
|
+
// While this is false, the blocks of the body show their text instead of rendering. It is true
|
|
323
|
+
// when:
|
|
324
|
+
// - the expand is open, or has been opened before, so the reader wants to see the content;
|
|
325
|
+
// - no node was given, so this expand never opted into lazy loading;
|
|
326
|
+
// - the experiment is off;
|
|
327
|
+
// - the browser has no hidden="until-found", so the text would not be findable and standing it in
|
|
328
|
+
// would gain nothing. While we still do not know, we assume the browser has it — assuming the
|
|
329
|
+
// opposite would render the whole body on the first paint and lose the saving entirely.
|
|
330
|
+
const revealed = expanded || hasBeenExpanded || !node || supportsHiddenUntilFound === false || !isExperimentEnabled('platform_editor_defer_collapsed_expand_body');
|
|
331
|
+
const expandBody = useMemo(() => ({
|
|
332
|
+
revealed,
|
|
333
|
+
openWithAncestors
|
|
334
|
+
}), [revealed, openWithAncestors]);
|
|
310
335
|
return jsx(Container, {
|
|
311
336
|
"data-testid": `expand-container-${nodeType}-${id}`,
|
|
312
337
|
"data-node-type": nodeType,
|
|
@@ -371,7 +396,9 @@ function Expand({
|
|
|
371
396
|
ref: contentWrapperRef
|
|
372
397
|
}, jsx(WidthProvider, null, jsx("div", {
|
|
373
398
|
css: clearNextSiblingMarginTopStyle
|
|
374
|
-
}),
|
|
399
|
+
}), jsx(ExpandBodyProvider, {
|
|
400
|
+
value: expandBody
|
|
401
|
+
}, children)))));
|
|
375
402
|
}
|
|
376
403
|
|
|
377
404
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
@@ -60,7 +60,7 @@ export const DEGRADED_SEVERITY_THRESHOLD = 3000;
|
|
|
60
60
|
const TABLE_INFO_TIMEOUT = 10000;
|
|
61
61
|
const RENDER_EVENT_SAMPLE_RATE = 0.1;
|
|
62
62
|
const packageName = "@atlaskit/renderer";
|
|
63
|
-
const packageVersion = "137.1.
|
|
63
|
+
const packageVersion = "137.1.6";
|
|
64
64
|
const setAsQueryContainerStyles = css({
|
|
65
65
|
containerName: 'ak-renderer-wrapper',
|
|
66
66
|
containerType: 'inline-size'
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React, { createContext, isValidElement, useContext } from 'react';
|
|
2
|
+
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
|
|
3
|
+
import { BLOCK_SEPARATOR, getBlockSearchText, holdsRevealableContent, isExpandNode } from './utils/expand-search-text';
|
|
4
|
+
const ExpandBodyContext = /*#__PURE__*/createContext(null);
|
|
5
|
+
export const ExpandBodyProvider = ExpandBodyContext.Provider;
|
|
6
|
+
export const useExpandBody = () => useContext(ExpandBodyContext);
|
|
7
|
+
/**
|
|
8
|
+
* One or more neighbouring blocks of an expand's body. Shows their text until the expand is
|
|
9
|
+
* opened, then renders them. Falls back to rendering if there is no text, or if there is no
|
|
10
|
+
* expand above it.
|
|
11
|
+
*
|
|
12
|
+
* The text is rendered as-is, with no element around it. Nothing reads it but browser find,
|
|
13
|
+
* which only needs the characters to be in the DOM.
|
|
14
|
+
*/
|
|
15
|
+
export const ExpandBodyBlock = ({
|
|
16
|
+
children,
|
|
17
|
+
searchText
|
|
18
|
+
}) => {
|
|
19
|
+
const body = useExpandBody();
|
|
20
|
+
if (searchText === undefined || body === null || body.revealed) {
|
|
21
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, children);
|
|
22
|
+
}
|
|
23
|
+
return searchText;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Called for every node the serializer renders. If the node is a block of an expand's body,
|
|
28
|
+
* wraps it so it can show its text instead of rendering while that expand is collapsed.
|
|
29
|
+
* Everything else is returned untouched.
|
|
30
|
+
*
|
|
31
|
+
* A block holding an expand of its own is also returned untouched, so a table with a nested
|
|
32
|
+
* expand in it, or an extension holding stashed ADF, renders even while collapsed. That
|
|
33
|
+
* expand needs an element of its own, because the element browser find reveals is the only
|
|
34
|
+
* way we learn the match was inside it rather than higher up. We give up the saving on those
|
|
35
|
+
* blocks so the reader searches once instead of once per level.
|
|
36
|
+
*
|
|
37
|
+
* `ancestors` is the node's ancestor chain, nearest last.
|
|
38
|
+
*/
|
|
39
|
+
export const withExpandBodyBlock = (node, ancestors, index, serialized) => {
|
|
40
|
+
const parent = ancestors[ancestors.length - 1];
|
|
41
|
+
if (!parent || !isExpandNode(parent)) {
|
|
42
|
+
return serialized;
|
|
43
|
+
}
|
|
44
|
+
if (!isExperimentEnabled('platform_editor_defer_collapsed_expand_body') || holdsRevealableContent(node)) {
|
|
45
|
+
return serialized;
|
|
46
|
+
}
|
|
47
|
+
const searchText = getBlockSearchText(node);
|
|
48
|
+
if (searchText === undefined) {
|
|
49
|
+
return serialized;
|
|
50
|
+
}
|
|
51
|
+
return /*#__PURE__*/React.createElement(ExpandBodyBlock, {
|
|
52
|
+
key: `expand-body-block-${index}`,
|
|
53
|
+
searchText: searchText
|
|
54
|
+
}, serialized);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Whatever the serializer produced for one child: an element, a string, an array of either. */
|
|
58
|
+
|
|
59
|
+
const searchTextOf = child => /*#__PURE__*/isValidElement(child) && child.type === ExpandBodyBlock ? child.props.searchText : undefined;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Joins neighbouring blocks that are showing text into one, so a run of them is a single string in
|
|
63
|
+
* the DOM rather than one per block. A body of twenty paragraphs becomes one text node instead of
|
|
64
|
+
* twenty.
|
|
65
|
+
*
|
|
66
|
+
* A block that is being rendered — a nested expand, say — ends the run, because the text either
|
|
67
|
+
* side of it has to stay either side of it.
|
|
68
|
+
*/
|
|
69
|
+
export const mergeExpandBodyText = children => {
|
|
70
|
+
// Called for every fragment in the document, and only an expand's body has anything to merge, so
|
|
71
|
+
// leave the array alone unless two neighbours are actually showing text.
|
|
72
|
+
const hasRun = children.some((child, index) => index > 0 && searchTextOf(child) !== undefined && searchTextOf(children[index - 1]) !== undefined);
|
|
73
|
+
if (!hasRun) {
|
|
74
|
+
return children;
|
|
75
|
+
}
|
|
76
|
+
const merged = [];
|
|
77
|
+
let run = [];
|
|
78
|
+
const endRun = () => {
|
|
79
|
+
if (run.length === 0) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (run.length === 1) {
|
|
83
|
+
merged.push(run[0]);
|
|
84
|
+
} else {
|
|
85
|
+
var _key;
|
|
86
|
+
const text = run.map(searchTextOf).filter(Boolean).join(BLOCK_SEPARATOR);
|
|
87
|
+
// The blocks themselves, not the wrappers around them: nesting the wrappers would show
|
|
88
|
+
// each block's text again inside the joined run.
|
|
89
|
+
const blocks = run.map(child => child.props.children);
|
|
90
|
+
merged.push( /*#__PURE__*/React.createElement(ExpandBodyBlock, {
|
|
91
|
+
key: (_key = run[0].key) !== null && _key !== void 0 ? _key : undefined,
|
|
92
|
+
searchText: text
|
|
93
|
+
}, blocks));
|
|
94
|
+
}
|
|
95
|
+
run = [];
|
|
96
|
+
};
|
|
97
|
+
children.forEach(child => {
|
|
98
|
+
if (searchTextOf(child) === undefined) {
|
|
99
|
+
endRun();
|
|
100
|
+
merged.push(child);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
run.push(child);
|
|
104
|
+
});
|
|
105
|
+
endRun();
|
|
106
|
+
return merged;
|
|
107
|
+
};
|
|
@@ -6,20 +6,25 @@ import { findChildrenByMark } from '@atlaskit/editor-prosemirror/utils';
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/** Separates text from adjacent blocks so phrases cannot fuse across block boundaries. */
|
|
9
|
-
const BLOCK_SEPARATOR = ' ';
|
|
9
|
+
export const BLOCK_SEPARATOR = ' ';
|
|
10
10
|
const asText = value => typeof value === 'string' ? value : '';
|
|
11
|
+
const nestedContentOf = attrs => {
|
|
12
|
+
var _attrs$parameters;
|
|
13
|
+
return attrs === null || attrs === void 0 ? void 0 : (_attrs$parameters = attrs.parameters) === null || _attrs$parameters === void 0 ? void 0 : _attrs$parameters.nestedContent;
|
|
14
|
+
};
|
|
15
|
+
export const isExpandNode = node => node.type.name === 'expand' || node.type.name === 'nestedExpand';
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
|
-
*
|
|
18
|
+
* Reads the text out of raw ADF JSON.
|
|
14
19
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
+
* Some extensions keep a whole ADF subtree in `attrs.parameters.nestedContent` rather than in their
|
|
21
|
+
* node content. Confluence does this when the authored nesting is deeper than the schema allows —
|
|
22
|
+
* an expand more than two collapsible layers deep, for example. To ProseMirror the extension is a
|
|
23
|
+
* leaf, so walking the node tree never sees that subtree, even though a nested renderer puts it on
|
|
24
|
+
* the page. Recurses, because stashed content often stashes more content of its own.
|
|
20
25
|
*/
|
|
21
26
|
const adfEntityText = entity => {
|
|
22
|
-
var _entity$
|
|
27
|
+
var _entity$content;
|
|
23
28
|
if (!entity) {
|
|
24
29
|
return '';
|
|
25
30
|
}
|
|
@@ -27,57 +32,88 @@ const adfEntityText = entity => {
|
|
|
27
32
|
if (entity.type === 'text') {
|
|
28
33
|
parts.push(asText(entity.text));
|
|
29
34
|
}
|
|
30
|
-
const nestedContent = (
|
|
35
|
+
const nestedContent = nestedContentOf(entity.attrs);
|
|
31
36
|
if (nestedContent) {
|
|
32
37
|
// Prefer the real subtree over `attrs.text`, which is only a placeholder label for it.
|
|
33
38
|
parts.push(adfEntityText(nestedContent));
|
|
34
39
|
} else {
|
|
35
|
-
var _entity$
|
|
36
|
-
parts.push(asText((_entity$
|
|
40
|
+
var _entity$attrs;
|
|
41
|
+
parts.push(asText((_entity$attrs = entity.attrs) === null || _entity$attrs === void 0 ? void 0 : _entity$attrs.text));
|
|
37
42
|
}
|
|
38
43
|
(_entity$content = entity.content) === null || _entity$content === void 0 ? void 0 : _entity$content.forEach(child => parts.push(adfEntityText(child)));
|
|
39
44
|
return parts.filter(Boolean).join(BLOCK_SEPARATOR);
|
|
40
45
|
};
|
|
41
46
|
|
|
42
47
|
/**
|
|
43
|
-
* `textBetween`
|
|
44
|
-
*
|
|
48
|
+
* `textBetween` skips every leaf node unless its spec declares `leafText`, and no ADF node
|
|
49
|
+
* does — so mentions, emoji, dates and statuses would all be lost. They keep their visible
|
|
50
|
+
* text in attrs, so read it from there.
|
|
45
51
|
*/
|
|
46
52
|
const leafText = leaf => {
|
|
47
|
-
var _leaf$attrs
|
|
48
|
-
const nestedContent = (
|
|
53
|
+
var _leaf$attrs;
|
|
54
|
+
const nestedContent = nestedContentOf(leaf.attrs);
|
|
49
55
|
if (nestedContent) {
|
|
50
56
|
return adfEntityText(nestedContent);
|
|
51
57
|
}
|
|
52
|
-
return asText((_leaf$
|
|
58
|
+
return asText((_leaf$attrs = leaf.attrs) === null || _leaf$attrs === void 0 ? void 0 : _leaf$attrs.text);
|
|
59
|
+
};
|
|
60
|
+
const isRevealable = node => isExpandNode(node) || Boolean(nestedContentOf(node.attrs));
|
|
61
|
+
const revealableCache = new WeakMap();
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether this block holds something that can be opened on its own: an expand, or an extension
|
|
65
|
+
* holding stashed ADF that a nested renderer will mount expands from.
|
|
66
|
+
*
|
|
67
|
+
* Those need a real element even while the expand around them is collapsed. The element is how we
|
|
68
|
+
* know find matched inside them rather than higher up: the beforematch event says nothing about
|
|
69
|
+
* where the match was, so the element the browser chose to reveal is the only clue.
|
|
70
|
+
*/
|
|
71
|
+
export const holdsRevealableContent = node => {
|
|
72
|
+
const cached = revealableCache.get(node);
|
|
73
|
+
if (cached !== undefined) {
|
|
74
|
+
return cached;
|
|
75
|
+
}
|
|
76
|
+
let found = isRevealable(node);
|
|
77
|
+
if (!found) {
|
|
78
|
+
node.descendants(descendant => {
|
|
79
|
+
if (found) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
found = isRevealable(descendant);
|
|
83
|
+
return !found;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
revealableCache.set(node, found);
|
|
87
|
+
return found;
|
|
53
88
|
};
|
|
54
89
|
const hasInlineComment = node => findChildrenByMark(node, node.type.schema.marks.annotation, true).some(annotation => annotation.node.marks.some(mark => mark.attrs.annotationType === 'inlineComment'));
|
|
55
90
|
|
|
56
|
-
/** `null` is a cached "no
|
|
91
|
+
/** `null` is a cached "no search text for this node", as distinct from `undefined` for a miss. */
|
|
57
92
|
const searchTextCache = new WeakMap();
|
|
58
93
|
|
|
59
94
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
95
|
+
* The text shown in place of one block of a collapsed expand's body, so browser find can still
|
|
96
|
+
* reach the content without the block being rendered.
|
|
62
97
|
*
|
|
63
|
-
* Cached on the node. ProseMirror nodes
|
|
64
|
-
*
|
|
65
|
-
* serializer rebuilds that invalidate the module-level `memoizeOne` caches.
|
|
98
|
+
* Cached on the node. ProseMirror nodes never change, and the serializer reuses the same instances
|
|
99
|
+
* across renders, so the text only has to be worked out once per block.
|
|
66
100
|
*
|
|
67
|
-
* @returns the
|
|
101
|
+
* @returns the text, or `undefined` if this block has to be rendered instead.
|
|
68
102
|
*/
|
|
69
|
-
export const
|
|
103
|
+
export const getBlockSearchText = node => {
|
|
70
104
|
var _cached;
|
|
71
105
|
let cached = searchTextCache.get(node);
|
|
72
106
|
if (cached === undefined) {
|
|
73
107
|
cached = hasInlineComment(node) ?
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
108
|
+
// No text, which makes the block render instead. Comment navigation looks through the DOM
|
|
109
|
+
// for the commented text and scrolls to it, so that text has to be a real element —
|
|
110
|
+
// plain text carries no comment marks and cannot stand in for it. Without this, the
|
|
111
|
+
// arrow keys silently skip every comment inside a collapsed expand. We give up the
|
|
112
|
+
// saving for this block on purpose.
|
|
113
|
+
null :
|
|
114
|
+
// A leaf block is not inside its own range, so `textBetween` would return nothing and its
|
|
115
|
+
// text would be lost — a macro sitting straight in an expand body, for instance.
|
|
116
|
+
node.isLeaf ? leafText(node) : node.textBetween(0, node.content.size, BLOCK_SEPARATOR, leafText);
|
|
81
117
|
searchTextCache.set(node, cached);
|
|
82
118
|
}
|
|
83
119
|
return (_cached = cached) !== null && _cached !== void 0 ? _cached : undefined;
|
package/dist/esm/react/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import React from 'react';
|
|
|
11
11
|
import { MarkType } from '@atlaskit/editor-prosemirror/model';
|
|
12
12
|
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
13
13
|
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
14
|
+
import { mergeExpandBodyText, withExpandBodyBlock } from '../ui/expand-body';
|
|
14
15
|
import { Doc, DocWithSelectAllTrap, isTextNode, isTextWrapper, mergeTextNodes, toReact } from './nodes';
|
|
15
16
|
import TextWrapperComponent from './nodes/text-wrapper';
|
|
16
17
|
import { isNestedHeaderLinksEnabled } from './utils/links';
|
|
@@ -92,12 +93,13 @@ var ReactSerializer = /*#__PURE__*/function () {
|
|
|
92
93
|
var shouldSkipLinkMark = function shouldSkipLinkMark(mark) {
|
|
93
94
|
return _this.allowMediaLinking !== true && isMedia && mark.type.name === 'link';
|
|
94
95
|
};
|
|
95
|
-
|
|
96
|
+
var serialized = marks.reduceRight(function (content, mark) {
|
|
96
97
|
if (shouldSkipLinkMark(mark) || shouldSkipBorderMark(mark)) {
|
|
97
98
|
return content;
|
|
98
99
|
}
|
|
99
100
|
return _this.renderMark(markToReact(mark), _this.withMediaMarkProps(node, mark, _this.getMarkProps(mark, [], node)), "".concat(mark.type.name, "-").concat(index), content);
|
|
100
101
|
}, serializedContent);
|
|
102
|
+
return withExpandBodyBlock(node, currentPath, index, serialized);
|
|
101
103
|
});
|
|
102
104
|
// Ignored via go/ees005
|
|
103
105
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -276,7 +278,7 @@ var ReactSerializer = /*#__PURE__*/function () {
|
|
|
276
278
|
case 'expand':
|
|
277
279
|
return this.getExpandProps(node, path);
|
|
278
280
|
case 'nestedExpand':
|
|
279
|
-
if (
|
|
281
|
+
if (isExperimentEnabled('platform_editor_defer_collapsed_expand_body')) {
|
|
280
282
|
return this.getExpandProps(node, path);
|
|
281
283
|
}
|
|
282
284
|
return this.getProps(node, path);
|
|
@@ -308,7 +310,7 @@ var ReactSerializer = /*#__PURE__*/function () {
|
|
|
308
310
|
if (key === 'root-0') {
|
|
309
311
|
this.resetState();
|
|
310
312
|
}
|
|
311
|
-
return this.renderNode(target, props, key, this.getChildNodes(fragment).map(function (node, index) {
|
|
313
|
+
return this.renderNode(target, props, key, mergeExpandBodyText(this.getChildNodes(fragment).map(function (node, index) {
|
|
312
314
|
if (isTextWrapper(node)) {
|
|
313
315
|
return _this2.serializeTextWrapper(node.content, {
|
|
314
316
|
index: index,
|
|
@@ -319,7 +321,7 @@ var ReactSerializer = /*#__PURE__*/function () {
|
|
|
319
321
|
index: index,
|
|
320
322
|
parentInfo: parentInfo
|
|
321
323
|
});
|
|
322
|
-
}));
|
|
324
|
+
})));
|
|
323
325
|
}
|
|
324
326
|
}, {
|
|
325
327
|
key: "serializeTextWrapper",
|
|
@@ -729,11 +731,8 @@ var ReactSerializer = /*#__PURE__*/function () {
|
|
|
729
731
|
value: function getExpandProps(node) {
|
|
730
732
|
var _this7 = this;
|
|
731
733
|
var _path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
|
|
732
|
-
// Expand
|
|
733
|
-
// text
|
|
734
|
-
// ancestor opens — and so that derivation can reach content the model walk cannot, such as
|
|
735
|
-
// the ADF stashed in a coerced extension's `parameters.nestedContent`. Expand owns the
|
|
736
|
-
// feature gate check.
|
|
734
|
+
// Expand is given the node so it can tell whether it opted into lazy body loading at all. The
|
|
735
|
+
// text each block shows is attached to the block itself, by `withExpandBodyBlock`.
|
|
737
736
|
if (!isNestedHeaderLinksEnabled(this.allowHeadingAnchorLinks)) {
|
|
738
737
|
return _objectSpread(_objectSpread({}, this.getProps(node)), {}, {
|
|
739
738
|
node: node
|