@atlaskit/editor-plugin-show-diff 15.1.4 → 15.1.5
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 +14 -0
- package/dist/cjs/pm-plugins/calculateDiff/diffBySteps.js +2 -31
- package/dist/cjs/pm-plugins/calculateDiff/smart/classifySmartChanges.js +3 -2
- package/dist/cjs/pm-plugins/calculateDiff/smart/segmentText.js +3 -34
- package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +49 -23
- package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +9 -2
- package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +7 -2
- package/dist/cjs/pm-plugins/decorations/extractContributorTags.js +38 -10
- package/dist/cjs/pm-plugins/utils/charsByOffset.js +45 -0
- package/dist/cjs/pm-plugins/utils/hasVisibleContent.js +37 -0
- package/dist/cjs/ui/ContributorTag/contributorTagController.js +1 -1
- package/dist/cjs/ui/IndicatorBar/IndicatorBar.js +1 -1
- package/dist/cjs/ui/IndicatorBar/IndicatorBarContentComponent.js +1 -1
- package/dist/es2019/pm-plugins/calculateDiff/diffBySteps.js +1 -30
- package/dist/es2019/pm-plugins/calculateDiff/smart/classifySmartChanges.js +2 -1
- package/dist/es2019/pm-plugins/calculateDiff/smart/segmentText.js +2 -28
- package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +40 -15
- package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +10 -3
- package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +7 -2
- package/dist/es2019/pm-plugins/decorations/extractContributorTags.js +27 -3
- package/dist/es2019/pm-plugins/utils/charsByOffset.js +37 -0
- package/dist/es2019/pm-plugins/utils/hasVisibleContent.js +28 -0
- package/dist/es2019/ui/ContributorTag/contributorTagController.js +1 -1
- package/dist/es2019/ui/IndicatorBar/IndicatorBar.js +1 -1
- package/dist/es2019/ui/IndicatorBar/IndicatorBarContentComponent.js +1 -1
- package/dist/esm/pm-plugins/calculateDiff/diffBySteps.js +1 -30
- package/dist/esm/pm-plugins/calculateDiff/smart/classifySmartChanges.js +2 -1
- package/dist/esm/pm-plugins/calculateDiff/smart/segmentText.js +2 -32
- package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +48 -22
- package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +10 -3
- package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +7 -2
- package/dist/esm/pm-plugins/decorations/extractContributorTags.js +38 -10
- package/dist/esm/pm-plugins/utils/charsByOffset.js +39 -0
- package/dist/esm/pm-plugins/utils/hasVisibleContent.js +32 -0
- package/dist/esm/ui/ContributorTag/contributorTagController.js +1 -1
- package/dist/esm/ui/IndicatorBar/IndicatorBar.js +1 -1
- package/dist/esm/ui/IndicatorBar/IndicatorBarContentComponent.js +1 -1
- package/dist/types/pm-plugins/calculateDiff/smart/segmentText.d.ts +0 -9
- package/dist/types/pm-plugins/decorations/createContributorTagWidget.d.ts +4 -3
- package/dist/types/pm-plugins/decorations/extractContributorTags.d.ts +1 -1
- package/dist/types/pm-plugins/utils/charsByOffset.d.ts +16 -0
- package/dist/types/pm-plugins/utils/hasVisibleContent.d.ts +13 -0
- package/dist/types/ui/ContributorTag/contributorTagController.d.ts +1 -1
- package/package.json +2 -2
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isWhitespaceChar } from '../../utils/charsByOffset';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* A half-open offset span `[from, to)` measured in *content offsets* relative to the
|
|
3
5
|
* start of a textblock (i.e. the same coordinate space as `buildCharsByOffset`).
|
|
@@ -19,37 +21,9 @@
|
|
|
19
21
|
const NON_WORD_CHARS = ' \t\n\r\f\v.,;:!?…。!?、"\'`“”‘’()[]{}<>/\\|@#$%^&*-+=~';
|
|
20
22
|
const NON_WORD_CHAR_SET = new Set(NON_WORD_CHARS.split(''));
|
|
21
23
|
const isWordChar = ch => !NON_WORD_CHAR_SET.has(ch);
|
|
22
|
-
// Whitespace is checked via a Set rather than a regex to avoid both the `require-unicode-regexp`
|
|
23
|
-
// lint rule and the TS1501 error the `u` flag triggers under this package's build lib target.
|
|
24
|
-
const WHITESPACE_CHAR_SET = new Set([' ', '\t', '\n', '\r', '\f', '\v', '\u00a0']);
|
|
25
|
-
const isWhitespaceChar = ch => WHITESPACE_CHAR_SET.has(ch);
|
|
26
24
|
// Sentence terminators used by the regex fallback. We intentionally keep this small and
|
|
27
25
|
// conservative — the Intl.Segmenter path is the source of truth when available.
|
|
28
26
|
const SENTENCE_TERMINATORS = new Set(['.', '!', '?', '…', '。', '!', '?']);
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Build a per-content-offset view of a textblock's characters.
|
|
32
|
-
*
|
|
33
|
-
* `chars[i]` is the character at content offset `i`, or `null` when that offset lies
|
|
34
|
-
* inside a non-text inline node (mention, date, emoji, hardBreak, …). A `null` acts as an
|
|
35
|
-
* *opaque single token*: it counts as one word and never terminates a sentence.
|
|
36
|
-
*/
|
|
37
|
-
export const buildCharsByOffset = parent => {
|
|
38
|
-
const chars = Array.from({
|
|
39
|
-
length: parent.content.size
|
|
40
|
-
}, () => null);
|
|
41
|
-
parent.content.forEach((child, offset) => {
|
|
42
|
-
var _child$text;
|
|
43
|
-
if (!child.isText) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const text = (_child$text = child.text) !== null && _child$text !== void 0 ? _child$text : '';
|
|
47
|
-
for (let i = 0; i < text.length; i++) {
|
|
48
|
-
chars[offset + i] = text[i];
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
return chars;
|
|
52
|
-
};
|
|
53
27
|
const getSegmenterCtor = () => {
|
|
54
28
|
if (typeof Intl === 'undefined') {
|
|
55
29
|
return undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
2
2
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
3
3
|
import { ContributorTagController } from '../../ui/ContributorTag/contributorTagController';
|
|
4
|
+
import { buildCharsByOffset, isWhitespaceChar } from '../utils/charsByOffset';
|
|
4
5
|
import { clampAnchorPosIntoCell } from './createAnchorDecorationWidgets';
|
|
5
6
|
import { buildContributorTagDecorationSpec } from './decorationKeys';
|
|
6
7
|
|
|
@@ -8,7 +9,7 @@ import { buildContributorTagDecorationSpec } from './decorationKeys';
|
|
|
8
9
|
export const CONTRIBUTOR_TAG_HOST_ATTRIBUTE = 'data-contributor-tag-host';
|
|
9
10
|
|
|
10
11
|
// Contributor tags only render behind this gate — see `areAttributionColorGatesEnabled`.
|
|
11
|
-
const isContributorTagWidgetEnabled = () => fg('confluence_ncs_step_diffing_version_history');
|
|
12
|
+
export const isContributorTagWidgetEnabled = () => fg('confluence_ncs_step_diffing_version_history');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Everything a tag needs to draw itself into the document, plumbed down from the plugin. Absent in
|
|
@@ -75,31 +76,55 @@ export const createContributorTagHost = (diffId, mountContext) => {
|
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
/**
|
|
78
|
-
* The
|
|
79
|
+
* The first *visible* character of the content that `[from, to)` highlights.
|
|
79
80
|
*
|
|
80
|
-
* `from` is not sufficient
|
|
81
|
-
* later block, and ProseMirror clips `Decoration.inline` to the content it can paint
|
|
82
|
-
* left at `from` would sit lines away from the highlight.
|
|
81
|
+
* `from` is not sufficient, for two reasons. A range can begin at the tail of one paragraph and only
|
|
82
|
+
* cover text in a later block, and ProseMirror clips `Decoration.inline` to the content it can paint
|
|
83
|
+
* — so a host left at `from` would sit lines away from the highlight. And a range routinely starts on
|
|
84
|
+
* whitespace, because the changeset joins neighbouring edits across the space between them: a tag
|
|
85
|
+
* hosted there captions the gap in front of the change rather than the change (EDITOR-8855).
|
|
86
|
+
*
|
|
87
|
+
* Whitespace is skipped across blocks, so a range whose first block contributes nothing but spaces
|
|
88
|
+
* anchors on the next block that has something to show. The fallback to the first paintable position
|
|
89
|
+
* covers a range holding no text at all — an atomic block such as media or a rule; a range of
|
|
90
|
+
* nothing but whitespace never reaches here, because a change with no visible content carries no tag
|
|
91
|
+
* (see `hasVisibleContent`).
|
|
83
92
|
*/
|
|
84
|
-
const
|
|
85
|
-
var
|
|
93
|
+
const resolveTagAnchorPos = (doc, from, to) => {
|
|
94
|
+
var _ref, _visibleEdge;
|
|
86
95
|
let edge;
|
|
96
|
+
let visibleEdge;
|
|
87
97
|
doc.nodesBetween(from, to, (node, nodePos) => {
|
|
98
|
+
if (visibleEdge !== undefined) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
88
101
|
if (!node.isTextblock) {
|
|
89
|
-
return
|
|
102
|
+
return true;
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
// The slice of this block the range actually covers; empty means nothing paintable.
|
|
93
|
-
const
|
|
94
|
-
const
|
|
106
|
+
const blockStart = nodePos + 1;
|
|
107
|
+
const contentFrom = Math.max(blockStart, from);
|
|
108
|
+
const contentTo = Math.min(blockStart + node.content.size, to);
|
|
95
109
|
if (contentTo > contentFrom && edge === undefined) {
|
|
96
110
|
edge = contentFrom;
|
|
97
111
|
}
|
|
98
112
|
|
|
113
|
+
// A `null` is a non-text inline node (mention, emoji, date, …). Those are visible, so they
|
|
114
|
+
// anchor the tag exactly as a letter does.
|
|
115
|
+
const chars = buildCharsByOffset(node);
|
|
116
|
+
for (let offset = contentFrom - blockStart; offset < contentTo - blockStart; offset++) {
|
|
117
|
+
const char = chars[offset];
|
|
118
|
+
if (char === null || !isWhitespaceChar(char)) {
|
|
119
|
+
visibleEdge = blockStart + offset;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
99
124
|
// Textblocks have no block children to descend into.
|
|
100
125
|
return false;
|
|
101
126
|
});
|
|
102
|
-
return (
|
|
127
|
+
return (_ref = (_visibleEdge = visibleEdge) !== null && _visibleEdge !== void 0 ? _visibleEdge : edge) !== null && _ref !== void 0 ? _ref : from;
|
|
103
128
|
};
|
|
104
129
|
|
|
105
130
|
/**
|
|
@@ -120,9 +145,9 @@ const resolveCodeBlockStart = (doc, pos) => {
|
|
|
120
145
|
};
|
|
121
146
|
|
|
122
147
|
/**
|
|
123
|
-
* A widget hosting the contributor tag of one diff range, placed on the first character the
|
|
124
|
-
* highlights — or, with `anchorAtRangeStart`, at `from` itself. Either way a host that would
|
|
125
|
-
* inside a code block is hoisted out in front of it; see `resolveCodeBlockStart`.
|
|
148
|
+
* A widget hosting the contributor tag of one diff range, placed on the first visible character the
|
|
149
|
+
* range highlights — or, with `anchorAtRangeStart`, at `from` itself. Either way a host that would
|
|
150
|
+
* land inside a code block is hoisted out in front of it; see `resolveCodeBlockStart`.
|
|
126
151
|
*
|
|
127
152
|
* The tag is mounted in `toDOM` and taken down in `destroy`, so it lives exactly as long as the host
|
|
128
153
|
* element ProseMirror drew it into — see `mountContributorTag`.
|
|
@@ -142,7 +167,7 @@ export const createContributorTagWidget = ({
|
|
|
142
167
|
// Reassigned when ProseMirror redraws this decoration, which it may do more than once for the
|
|
143
168
|
// same `Decoration` instance.
|
|
144
169
|
let mount;
|
|
145
|
-
const anchorPos = anchorAtRangeStart ? from :
|
|
170
|
+
const anchorPos = anchorAtRangeStart ? from : resolveTagAnchorPos(doc, from, to);
|
|
146
171
|
// A change inside a code block is hoisted out of it; anything else keeps its own anchor.
|
|
147
172
|
const codeBlockPos = resolveCodeBlockStart(doc, anchorPos);
|
|
148
173
|
return Decoration.widget(
|
|
@@ -2,10 +2,11 @@ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
|
|
|
2
2
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
3
3
|
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
|
|
4
4
|
import { isExtendedEnabled } from '../isExtendedEnabled';
|
|
5
|
+
import { hasVisibleContent } from '../utils/hasVisibleContent';
|
|
5
6
|
import { buildAtomicInlineChangedCSSVariables, buildDeletedInlineContentStyleExtended, buildDeletedInlineStyle, buildDeletedInlineStyleStandard, buildInsertStyle, buildInsertStyleActive, buildInsertStyleExtended, buildInsertStyleExtendedActive, buildInsertStyleExtendedNoUnderline, buildInsertStyleExtendedNoUnderlineActive } from './colorSchemes/factory';
|
|
6
7
|
import { colorSchemeRegistry, getLegacyColorScheme } from './colorSchemes/schemes';
|
|
7
8
|
import { createInlineIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
|
|
8
|
-
import { createContributorTagWidget } from './createContributorTagWidget';
|
|
9
|
+
import { createContributorTagWidget, isContributorTagWidgetEnabled } from './createContributorTagWidget';
|
|
9
10
|
import { getAtomicInlineNodeClassNameLegacy, resolveInlineChangedStyleLegacy } from './createInlineChangedDecoration.styles.legacy';
|
|
10
11
|
import { buildDiffDecorationSpec } from './decorationKeys';
|
|
11
12
|
import { REVEAL_ATTR, resolveRevealStyle } from './revealStyles';
|
|
@@ -116,6 +117,12 @@ export const createInlineChangedDecoration = ({
|
|
|
116
117
|
// rebuilds decorations without touching the doc, and a fresh id would change the React key and the
|
|
117
118
|
// tag host's key, remounting every tag mid-step. Random ids are kept when tags are off.
|
|
118
119
|
const diffId = showContributorTags ? `inline-${change.fromB}-${change.toB}` : crypto.randomUUID();
|
|
120
|
+
|
|
121
|
+
// Whether this highlight hosts a tag. A range of nothing but whitespace has nothing to caption,
|
|
122
|
+
// so it stays unattributed: the spec is the only route attribution takes to
|
|
123
|
+
// `extractContributorTags`, so dropping the key there is what keeps a tag from being resolved for
|
|
124
|
+
// a host that is never emitted (EDITOR-8855).
|
|
125
|
+
const canTagChange = showContributorTags && !!doc && isExtendedEnabled(diffType) && isContributorTagWidgetEnabled() && hasVisibleContent(doc.slice(change.fromB, change.toB).content);
|
|
119
126
|
if (shouldHideDeleted) {
|
|
120
127
|
return [Decoration.inline(change.fromB, change.toB, {
|
|
121
128
|
style: displayNoneStyle
|
|
@@ -165,7 +172,7 @@ export const createInlineChangedDecoration = ({
|
|
|
165
172
|
'data-diff-id': diffId
|
|
166
173
|
})
|
|
167
174
|
}, buildDiffDecorationSpec({
|
|
168
|
-
attributionKey,
|
|
175
|
+
attributionKey: canTagChange ? attributionKey : undefined,
|
|
169
176
|
colorScheme,
|
|
170
177
|
decorationType: 'inline',
|
|
171
178
|
diffId,
|
|
@@ -210,7 +217,7 @@ export const createInlineChangedDecoration = ({
|
|
|
210
217
|
|
|
211
218
|
// Built from the raw change range, not the anchors above: a tag lines up with the changed text,
|
|
212
219
|
// while the bar's anchors are held on a block boundary when a deleted widget renders beside them.
|
|
213
|
-
if (
|
|
220
|
+
if (canTagChange && doc) {
|
|
214
221
|
const tagWidget = createContributorTagWidget({
|
|
215
222
|
doc,
|
|
216
223
|
from: change.fromB,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
2
2
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
3
3
|
import { isExtendedEnabled } from '../isExtendedEnabled';
|
|
4
|
+
import { hasVisibleContent } from '../utils/hasVisibleContent';
|
|
4
5
|
import { createLeftAnchorWidget } from './createAnchorDecorationWidgets';
|
|
5
6
|
import { createChangedRowDecorationWidgets } from './createChangedRowDecorationWidgets';
|
|
6
7
|
import { createContributorTagHost, unmountContributorTag } from './createContributorTagWidget';
|
|
@@ -204,6 +205,10 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
204
205
|
// Derived from the deleted range so it survives a recalculation — see the same reasoning in
|
|
205
206
|
// `createInlineChangedDecoration`.
|
|
206
207
|
const diffId = showContributorTags ? `widget-${change.fromA}-${change.toA}` : crypto.randomUUID();
|
|
208
|
+
// Whether this widget hosts a tag. Removing nothing but whitespace leaves no visible deleted
|
|
209
|
+
// content to caption, so the change stays unattributed — matching the inline half's rule in
|
|
210
|
+
// `createInlineChangedDecoration` (EDITOR-8855).
|
|
211
|
+
const canTagWidget = showContributorTags && hasVisibleContent(slice.content);
|
|
207
212
|
const decorations = [];
|
|
208
213
|
const replacementNode = newDoc.nodeAt(change.fromB);
|
|
209
214
|
const firstReplacedNode = slice.content.firstChild;
|
|
@@ -333,7 +338,7 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
333
338
|
// Taken down in the widget's `destroy` below: `dom` is rebuilt on every recalculation, so the tag
|
|
334
339
|
// mounted into it must not outlive the copy ProseMirror is drawing.
|
|
335
340
|
let tagMount;
|
|
336
|
-
if (
|
|
341
|
+
if (canTagWidget) {
|
|
337
342
|
// Lets the contributor tag find the deleted content on hover.
|
|
338
343
|
dom.setAttribute('data-diff-id', diffId);
|
|
339
344
|
|
|
@@ -357,7 +362,7 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
357
362
|
}
|
|
358
363
|
decorations.push(Decoration.widget(safeInsertPos, dom, {
|
|
359
364
|
...buildDiffDecorationSpec({
|
|
360
|
-
attributionKey,
|
|
365
|
+
attributionKey: canTagWidget ? attributionKey : undefined,
|
|
361
366
|
colorScheme,
|
|
362
367
|
decorationType: 'widget',
|
|
363
368
|
diffId,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
1
2
|
import { isDiffDecoration, isDiffDecorationSpec } from './decorationKeys';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -18,6 +19,14 @@ const toTagContributor = ({
|
|
|
18
19
|
*/
|
|
19
20
|
const isSameChange = (widget, inline) => widget.spec.attributionKey === inline.spec.attributionKey && widget.from >= inline.from && widget.from <= inline.to;
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Whether a deleted-content widget is the left-most half of the replacement it belongs to, in which
|
|
24
|
+
* case it keeps the tag so the tag marks where the change begins (EDITOR-8855). `side: -1` paints
|
|
25
|
+
* the deleted content in front of the inserted text; `1` is `deletedDiffPlacement: 'bottom'` /
|
|
26
|
+
* `inlineDeletedDiffPlacement: 'after'`, where the inline half leads instead.
|
|
27
|
+
*/
|
|
28
|
+
const leadsReplacement = widget => fg('confluence_ncs_step_diffing_version_history') && widget.spec.side === -1;
|
|
29
|
+
|
|
21
30
|
/**
|
|
22
31
|
* Whether `inner` captions the same change as the block decoration `block`, in which case only the
|
|
23
32
|
* block keeps the tag — one tag per block. Containment counts in either direction: a leaf block's
|
|
@@ -28,7 +37,7 @@ const isSameBlockChange = (inner, block) => inner.spec.attributionKey === block.
|
|
|
28
37
|
/**
|
|
29
38
|
* One model per diff decoration whose attribution resolves to a supplied contributor; anything
|
|
30
39
|
* unattributed, untaggable or unresolved is skipped. A replacement's two decorations are collapsed
|
|
31
|
-
* into
|
|
40
|
+
* into a single tag, on whichever half renders first — see `isSameChange` and `leadsReplacement`.
|
|
32
41
|
*/
|
|
33
42
|
export const extractContributorTags = (decorations, contributors) => {
|
|
34
43
|
if (!contributors) {
|
|
@@ -55,13 +64,16 @@ export const extractContributorTags = (decorations, contributors) => {
|
|
|
55
64
|
});
|
|
56
65
|
}
|
|
57
66
|
|
|
58
|
-
//
|
|
67
|
+
// Candidate hosts, in the order they outrank each other: block, then leading widget, then inline.
|
|
59
68
|
const inlineTargets = targets.filter(({
|
|
60
69
|
decoration
|
|
61
70
|
}) => decoration.spec.decorationType === 'inline');
|
|
62
71
|
const blockTargets = targets.filter(({
|
|
63
72
|
decoration
|
|
64
73
|
}) => decoration.spec.decorationType === 'block');
|
|
74
|
+
const widgetTargets = targets.filter(({
|
|
75
|
+
decoration
|
|
76
|
+
}) => decoration.spec.decorationType === 'widget');
|
|
65
77
|
const linkedDiffIds = new Map();
|
|
66
78
|
const folded = new Set();
|
|
67
79
|
const fold = (host, target) => {
|
|
@@ -86,7 +98,19 @@ export const extractContributorTags = (decorations, contributors) => {
|
|
|
86
98
|
fold(block, target);
|
|
87
99
|
continue;
|
|
88
100
|
}
|
|
89
|
-
if (decorationType
|
|
101
|
+
if (decorationType === 'inline') {
|
|
102
|
+
// The deleted half renders in front of this one, so that half captions the change.
|
|
103
|
+
const leadingWidget = widgetTargets.find(({
|
|
104
|
+
decoration
|
|
105
|
+
}) => leadsReplacement(decoration) && isSameChange(decoration, target.decoration));
|
|
106
|
+
if (leadingWidget) {
|
|
107
|
+
fold(leadingWidget, target);
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// A leading widget keeps its own tag; it was folded into above.
|
|
113
|
+
if (leadsReplacement(target.decoration)) {
|
|
90
114
|
continue;
|
|
91
115
|
}
|
|
92
116
|
const host = inlineTargets.find(({
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whitespace as the diff treats it, U+00A0 included: a non-breaking space separates words for every
|
|
3
|
+
* consumer here, so it is not a character a change can be said to start on.
|
|
4
|
+
*
|
|
5
|
+
* A Set rather than a regex, to avoid both the `require-unicode-regexp` lint rule and the TS1501
|
|
6
|
+
* error the `u` flag triggers under this package's declaration build target.
|
|
7
|
+
*/
|
|
8
|
+
const WHITESPACE_CHAR_SET = new Set([' ', '\t', '\n', '\r', '\f', '\v', '\u00a0']);
|
|
9
|
+
export const isWhitespaceChar = char => WHITESPACE_CHAR_SET.has(char);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build a per-content-offset view of a textblock's characters.
|
|
13
|
+
*
|
|
14
|
+
* Returns an array whose length is `parent.content.size`. For every offset that lies inside a text
|
|
15
|
+
* node, `chars[offset]` is the character at that offset; for every offset that lies inside (or on
|
|
16
|
+
* the edge of) a non-text inline node — hardBreak, mention, emoji, date, … — the entry is `null`,
|
|
17
|
+
* which acts as an *opaque single token*: it counts as one word and is never whitespace.
|
|
18
|
+
*
|
|
19
|
+
* Using doc positions to index `parent.textContent` is wrong because `textContent` strips non-text
|
|
20
|
+
* inline nodes, so every such node shifts the lookup off by its size. This per-offset view restores
|
|
21
|
+
* a 1:1 mapping between doc positions inside the textblock and the character (or "no character",
|
|
22
|
+
* i.e. a hard word boundary) at that position.
|
|
23
|
+
*/
|
|
24
|
+
export const buildCharsByOffset = parent => {
|
|
25
|
+
const chars = new Array(parent.content.size).fill(null);
|
|
26
|
+
parent.content.forEach((child, offset) => {
|
|
27
|
+
var _child$text;
|
|
28
|
+
if (!child.isText) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const text = (_child$text = child.text) !== null && _child$text !== void 0 ? _child$text : '';
|
|
32
|
+
for (let i = 0; i < text.length; i++) {
|
|
33
|
+
chars[offset + i] = text[i];
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return chars;
|
|
37
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { isWhitespaceChar } from './charsByOffset';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether `fragment` holds anything a reader can see.
|
|
5
|
+
*
|
|
6
|
+
* Text counts once it carries a single non-whitespace character; a leaf node (mention, emoji, date,
|
|
7
|
+
* media, rule, hardBreak, …) counts in its own right, exactly as `resolveTagAnchorPos` treats one.
|
|
8
|
+
* Everything else is descended into, so a paragraph is visible when its content is.
|
|
9
|
+
*
|
|
10
|
+
* A change whose content is nothing but whitespace — a space typed between two words, a run of
|
|
11
|
+
* trailing spaces, an added blank paragraph — has no visible content for a contributor tag to
|
|
12
|
+
* caption, so it carries no tag (EDITOR-8855).
|
|
13
|
+
*/
|
|
14
|
+
export const hasVisibleContent = fragment => {
|
|
15
|
+
let visible = false;
|
|
16
|
+
fragment.forEach(node => {
|
|
17
|
+
if (visible) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (node.isText) {
|
|
21
|
+
var _node$text;
|
|
22
|
+
visible = [...((_node$text = node.text) !== null && _node$text !== void 0 ? _node$text : '')].some(char => !isWhitespaceChar(char));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
visible = node.isLeaf || hasVisibleContent(node.content);
|
|
26
|
+
});
|
|
27
|
+
return visible;
|
|
28
|
+
};
|
|
@@ -62,7 +62,7 @@ const identityOf = model => [model === null || model === void 0 ? void 0 : model
|
|
|
62
62
|
* `toDOM` for a widget it reuses: stepping to the next change republishes the tags without redrawing
|
|
63
63
|
* their hosts, so anything the tag reacts to — `isActive` above all — has to arrive through a
|
|
64
64
|
* subscription of its own (EDITOR-8702). Draws nothing when this diff has no tag, which is the case
|
|
65
|
-
* for the
|
|
65
|
+
* for the folded half of a replacement: its two decorations share the one tag.
|
|
66
66
|
*/
|
|
67
67
|
export class ContributorTagController {
|
|
68
68
|
constructor(options) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* IndicatorBarContentComponent.tsx generated by @compiled/babel-plugin
|
|
1
|
+
/* IndicatorBarContentComponent.tsx generated by @compiled/babel-plugin v3.0.1 */
|
|
2
2
|
import { ax, ix } from "@compiled/react/runtime";
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
|
|
@@ -10,6 +10,7 @@ import { simplifyChanges, ChangeSet } from 'prosemirror-changeset';
|
|
|
10
10
|
import { Mark } from '@atlaskit/editor-prosemirror/model';
|
|
11
11
|
import { Mapping, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
12
12
|
import { getAttributionKey } from '../decorations/colorSchemes/attributions';
|
|
13
|
+
import { buildCharsByOffset } from '../utils/charsByOffset';
|
|
13
14
|
import { attrAwareTokenEncoder } from './attrAwareTokenEncoder';
|
|
14
15
|
import { optimizeChanges } from './optimizeChanges';
|
|
15
16
|
var WORD_CHAR_REGEX = /(?:[0-9A-Z_a-z\xAA\xB2\xB3\xB5\xB9\xBA\xBC-\xBE\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088F\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09F9\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BF2\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5C\u0C5D\u0C60\u0C61\u0C66-\u0C6F\u0C78-\u0C7E\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDC-\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D58-\u0D61\u0D66-\u0D78\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F33\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1369-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C8A\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2070\u2071\u2074-\u2079\u207F-\u2089\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2150-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2CFD\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3192-\u3195\u31A0-\u31BF\u31F0-\u31FF\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7DC\uA7F1-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA830-\uA835\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDE80-\uDE9C\uDEA0-\uDED0\uDEE1-\uDEFB\uDF00-\uDF23\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDDC0-\uDDF3\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC58-\uDC76\uDC79-\uDC9E\uDCA7-\uDCAF\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDD1B\uDD20-\uDD39\uDD40-\uDD59\uDD80-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE40-\uDE48\uDE60-\uDE7E\uDE80-\uDE9F\uDEC0-\uDEC7\uDEC9-\uDEE4\uDEEB-\uDEEF\uDF00-\uDF35\uDF40-\uDF55\uDF58-\uDF72\uDF78-\uDF91\uDFA9-\uDFAF]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDD23\uDD30-\uDD39\uDD40-\uDD65\uDD6F-\uDD85\uDE60-\uDE7E\uDE80-\uDEA9\uDEB0\uDEB1\uDEC2-\uDEC7\uDF00-\uDF27\uDF30-\uDF45\uDF51-\uDF54\uDF70-\uDF81\uDFB0-\uDFCB\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC52-\uDC6F\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDDE1-\uDDF4\uDE00-\uDE11\uDE13-\uDE2B\uDE3F\uDE40\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61\uDF80-\uDF89\uDF8B\uDF8E\uDF90-\uDFB5\uDFB7\uDFD1\uDFD3]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDED0-\uDEE3\uDF00-\uDF1A\uDF30-\uDF3B\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCF2\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8\uDFC0-\uDFE0\uDFF0-\uDFF9]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC6C\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDDB0-\uDDDB\uDDE0-\uDDE9\uDEE0-\uDEF2\uDF02\uDF04-\uDF10\uDF12-\uDF33\uDF50-\uDF59\uDFB0\uDFC0-\uDFD4]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD80E\uD80F\uD81C-\uD822\uD840-\uD868\uD86A-\uD86D\uD86F-\uD872\uD874-\uD879\uD880-\uD883\uD885-\uD88C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2F\uDC41-\uDC46\uDC60-\uDFFF]|\uD810[\uDC00-\uDFFA]|\uD811[\uDC00-\uDE46]|\uD818[\uDD00-\uDD1D\uDD30-\uDD39]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDE70-\uDEBE\uDEC0-\uDEC9\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDD40-\uDD6C\uDD70-\uDD79\uDE40-\uDE96\uDEA0-\uDEB8\uDEBB-\uDED3\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3\uDFF2-\uDFF6]|\uD823[\uDC00-\uDCD5\uDCFF-\uDD1E\uDD80-\uDDF2]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD32\uDD50-\uDD52\uDD55\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD833[\uDCF0-\uDCF9]|\uD834[\uDEC0-\uDED3\uDEE0-\uDEF3\uDF60-\uDF78]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD837[\uDF00-\uDF1E\uDF25-\uDF2A]|\uD838[\uDC30-\uDC6D\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD839[\uDCD0-\uDCEB\uDCF0-\uDCF9\uDDD0-\uDDED\uDDF0-\uDDFA\uDEC0-\uDEDE\uDEE0-\uDEE2\uDEE4\uDEE5\uDEE7-\uDEED\uDEF0-\uDEF4\uDEFE\uDEFF\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDCC7-\uDCCF\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83C[\uDD00-\uDD0C]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEAD\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0\uDFF0-\uDFFF]|\uD87B[\uDC00-\uDE5D]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A\uDF50-\uDFFF]|\uD88D[\uDC00-\uDC79])/;
|
|
@@ -19,36 +20,6 @@ var mapPosition = function mapPosition(mapping, pos) {
|
|
|
19
20
|
return mapping.map(pos);
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
/**
|
|
23
|
-
* Build a per-content-offset view of the textblock's characters.
|
|
24
|
-
*
|
|
25
|
-
* Returns an array `chars` whose length is `parent.content.size`. For every
|
|
26
|
-
* offset that lies inside a text node, `chars[offset]` is the character at
|
|
27
|
-
* that offset; for every offset that lies inside (or on the edge of) a
|
|
28
|
-
* non-text inline node — hardBreak, mention, emoji, date, etc. — the entry
|
|
29
|
-
* is `null`.
|
|
30
|
-
*
|
|
31
|
-
* Using doc positions to index `parent.textContent` is wrong because
|
|
32
|
-
* `textContent` strips non-text inline nodes, so every such node shifts the
|
|
33
|
-
* lookup off by its size. This per-offset view restores a 1:1 mapping between
|
|
34
|
-
* doc positions inside the textblock and the character (or "no character",
|
|
35
|
-
* i.e. a hard word boundary) at that position.
|
|
36
|
-
*/
|
|
37
|
-
var buildCharsByOffset = function buildCharsByOffset(parent) {
|
|
38
|
-
var chars = new Array(parent.content.size).fill(null);
|
|
39
|
-
parent.content.forEach(function (child, offset) {
|
|
40
|
-
var _child$text;
|
|
41
|
-
if (!child.isText) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
var text = (_child$text = child.text) !== null && _child$text !== void 0 ? _child$text : '';
|
|
45
|
-
for (var i = 0; i < text.length; i++) {
|
|
46
|
-
chars[offset + i] = text[i];
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
return chars;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
23
|
/**
|
|
53
24
|
* Given a ProseMirror doc and a position range [from, to], expand
|
|
54
25
|
* both endpoints outward to the nearest word boundaries.
|
|
@@ -6,8 +6,9 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
|
|
|
6
6
|
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
7
7
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
8
8
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
9
|
+
import { buildCharsByOffset } from '../../utils/charsByOffset';
|
|
9
10
|
import { makePromotedChange, mergeOverlappingByNewDocRange, rangesOverlap } from './helpers';
|
|
10
|
-
import {
|
|
11
|
+
import { countWords, segmentSentences, segmentWordSpans } from './segmentText';
|
|
11
12
|
import { resolveThresholds } from './thresholds';
|
|
12
13
|
/**
|
|
13
14
|
* Block-first `smart` classifier.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
2
2
|
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
3
3
|
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
4
|
+
import { isWhitespaceChar } from '../../utils/charsByOffset';
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* A half-open offset span `[from, to)` measured in *content offsets* relative to the
|
|
6
8
|
* start of a textblock (i.e. the same coordinate space as `buildCharsByOffset`).
|
|
@@ -24,41 +26,9 @@ var NON_WORD_CHAR_SET = new Set(NON_WORD_CHARS.split(''));
|
|
|
24
26
|
var isWordChar = function isWordChar(ch) {
|
|
25
27
|
return !NON_WORD_CHAR_SET.has(ch);
|
|
26
28
|
};
|
|
27
|
-
// Whitespace is checked via a Set rather than a regex to avoid both the `require-unicode-regexp`
|
|
28
|
-
// lint rule and the TS1501 error the `u` flag triggers under this package's build lib target.
|
|
29
|
-
var WHITESPACE_CHAR_SET = new Set([' ', '\t', '\n', '\r', '\f', '\v', "\xA0"]);
|
|
30
|
-
var isWhitespaceChar = function isWhitespaceChar(ch) {
|
|
31
|
-
return WHITESPACE_CHAR_SET.has(ch);
|
|
32
|
-
};
|
|
33
29
|
// Sentence terminators used by the regex fallback. We intentionally keep this small and
|
|
34
30
|
// conservative — the Intl.Segmenter path is the source of truth when available.
|
|
35
31
|
var SENTENCE_TERMINATORS = new Set(['.', '!', '?', '…', '。', '!', '?']);
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Build a per-content-offset view of a textblock's characters.
|
|
39
|
-
*
|
|
40
|
-
* `chars[i]` is the character at content offset `i`, or `null` when that offset lies
|
|
41
|
-
* inside a non-text inline node (mention, date, emoji, hardBreak, …). A `null` acts as an
|
|
42
|
-
* *opaque single token*: it counts as one word and never terminates a sentence.
|
|
43
|
-
*/
|
|
44
|
-
export var buildCharsByOffset = function buildCharsByOffset(parent) {
|
|
45
|
-
var chars = Array.from({
|
|
46
|
-
length: parent.content.size
|
|
47
|
-
}, function () {
|
|
48
|
-
return null;
|
|
49
|
-
});
|
|
50
|
-
parent.content.forEach(function (child, offset) {
|
|
51
|
-
var _child$text;
|
|
52
|
-
if (!child.isText) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
var text = (_child$text = child.text) !== null && _child$text !== void 0 ? _child$text : '';
|
|
56
|
-
for (var i = 0; i < text.length; i++) {
|
|
57
|
-
chars[offset + i] = text[i];
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
return chars;
|
|
61
|
-
};
|
|
62
32
|
var getSegmenterCtor = function getSegmenterCtor() {
|
|
63
33
|
if (typeof Intl === 'undefined') {
|
|
64
34
|
return undefined;
|
|
@@ -4,6 +4,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
4
4
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
5
5
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
6
6
|
import { ContributorTagController } from '../../ui/ContributorTag/contributorTagController';
|
|
7
|
+
import { buildCharsByOffset, isWhitespaceChar } from '../utils/charsByOffset';
|
|
7
8
|
import { clampAnchorPosIntoCell } from './createAnchorDecorationWidgets';
|
|
8
9
|
import { buildContributorTagDecorationSpec } from './decorationKeys';
|
|
9
10
|
|
|
@@ -11,7 +12,7 @@ import { buildContributorTagDecorationSpec } from './decorationKeys';
|
|
|
11
12
|
export var CONTRIBUTOR_TAG_HOST_ATTRIBUTE = 'data-contributor-tag-host';
|
|
12
13
|
|
|
13
14
|
// Contributor tags only render behind this gate — see `areAttributionColorGatesEnabled`.
|
|
14
|
-
var isContributorTagWidgetEnabled = function isContributorTagWidgetEnabled() {
|
|
15
|
+
export var isContributorTagWidgetEnabled = function isContributorTagWidgetEnabled() {
|
|
15
16
|
return fg('confluence_ncs_step_diffing_version_history');
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -78,30 +79,55 @@ export var createContributorTagHost = function createContributorTagHost(diffId,
|
|
|
78
79
|
};
|
|
79
80
|
|
|
80
81
|
/**
|
|
81
|
-
* The
|
|
82
|
+
* The first *visible* character of the content that `[from, to)` highlights.
|
|
82
83
|
*
|
|
83
|
-
* `from` is not sufficient
|
|
84
|
-
* later block, and ProseMirror clips `Decoration.inline` to the content it can paint
|
|
85
|
-
* left at `from` would sit lines away from the highlight.
|
|
84
|
+
* `from` is not sufficient, for two reasons. A range can begin at the tail of one paragraph and only
|
|
85
|
+
* cover text in a later block, and ProseMirror clips `Decoration.inline` to the content it can paint
|
|
86
|
+
* — so a host left at `from` would sit lines away from the highlight. And a range routinely starts on
|
|
87
|
+
* whitespace, because the changeset joins neighbouring edits across the space between them: a tag
|
|
88
|
+
* hosted there captions the gap in front of the change rather than the change (EDITOR-8855).
|
|
89
|
+
*
|
|
90
|
+
* Whitespace is skipped across blocks, so a range whose first block contributes nothing but spaces
|
|
91
|
+
* anchors on the next block that has something to show. The fallback to the first paintable position
|
|
92
|
+
* covers a range holding no text at all — an atomic block such as media or a rule; a range of
|
|
93
|
+
* nothing but whitespace never reaches here, because a change with no visible content carries no tag
|
|
94
|
+
* (see `hasVisibleContent`).
|
|
86
95
|
*/
|
|
87
|
-
var
|
|
96
|
+
var resolveTagAnchorPos = function resolveTagAnchorPos(doc, from, to) {
|
|
97
|
+
var _ref2;
|
|
88
98
|
var edge;
|
|
99
|
+
var visibleEdge;
|
|
89
100
|
doc.nodesBetween(from, to, function (node, nodePos) {
|
|
101
|
+
if (visibleEdge !== undefined) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
90
104
|
if (!node.isTextblock) {
|
|
91
|
-
return
|
|
105
|
+
return true;
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
// The slice of this block the range actually covers; empty means nothing paintable.
|
|
95
|
-
var
|
|
96
|
-
var
|
|
109
|
+
var blockStart = nodePos + 1;
|
|
110
|
+
var contentFrom = Math.max(blockStart, from);
|
|
111
|
+
var contentTo = Math.min(blockStart + node.content.size, to);
|
|
97
112
|
if (contentTo > contentFrom && edge === undefined) {
|
|
98
113
|
edge = contentFrom;
|
|
99
114
|
}
|
|
100
115
|
|
|
116
|
+
// A `null` is a non-text inline node (mention, emoji, date, …). Those are visible, so they
|
|
117
|
+
// anchor the tag exactly as a letter does.
|
|
118
|
+
var chars = buildCharsByOffset(node);
|
|
119
|
+
for (var offset = contentFrom - blockStart; offset < contentTo - blockStart; offset++) {
|
|
120
|
+
var char = chars[offset];
|
|
121
|
+
if (char === null || !isWhitespaceChar(char)) {
|
|
122
|
+
visibleEdge = blockStart + offset;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
101
127
|
// Textblocks have no block children to descend into.
|
|
102
128
|
return false;
|
|
103
129
|
});
|
|
104
|
-
return
|
|
130
|
+
return (_ref2 = visibleEdge !== null && visibleEdge !== void 0 ? visibleEdge : edge) !== null && _ref2 !== void 0 ? _ref2 : from;
|
|
105
131
|
};
|
|
106
132
|
|
|
107
133
|
/**
|
|
@@ -122,21 +148,21 @@ var resolveCodeBlockStart = function resolveCodeBlockStart(doc, pos) {
|
|
|
122
148
|
};
|
|
123
149
|
|
|
124
150
|
/**
|
|
125
|
-
* A widget hosting the contributor tag of one diff range, placed on the first character the
|
|
126
|
-
* highlights — or, with `anchorAtRangeStart`, at `from` itself. Either way a host that would
|
|
127
|
-
* inside a code block is hoisted out in front of it; see `resolveCodeBlockStart`.
|
|
151
|
+
* A widget hosting the contributor tag of one diff range, placed on the first visible character the
|
|
152
|
+
* range highlights — or, with `anchorAtRangeStart`, at `from` itself. Either way a host that would
|
|
153
|
+
* land inside a code block is hoisted out in front of it; see `resolveCodeBlockStart`.
|
|
128
154
|
*
|
|
129
155
|
* The tag is mounted in `toDOM` and taken down in `destroy`, so it lives exactly as long as the host
|
|
130
156
|
* element ProseMirror drew it into — see `mountContributorTag`.
|
|
131
157
|
*/
|
|
132
|
-
export var createContributorTagWidget = function createContributorTagWidget(
|
|
133
|
-
var
|
|
134
|
-
anchorAtRangeStart =
|
|
135
|
-
doc =
|
|
136
|
-
from =
|
|
137
|
-
to =
|
|
138
|
-
diffId =
|
|
139
|
-
mountContext =
|
|
158
|
+
export var createContributorTagWidget = function createContributorTagWidget(_ref3) {
|
|
159
|
+
var _ref3$anchorAtRangeSt = _ref3.anchorAtRangeStart,
|
|
160
|
+
anchorAtRangeStart = _ref3$anchorAtRangeSt === void 0 ? false : _ref3$anchorAtRangeSt,
|
|
161
|
+
doc = _ref3.doc,
|
|
162
|
+
from = _ref3.from,
|
|
163
|
+
to = _ref3.to,
|
|
164
|
+
diffId = _ref3.diffId,
|
|
165
|
+
mountContext = _ref3.mountContext;
|
|
140
166
|
if (!isContributorTagWidgetEnabled()) {
|
|
141
167
|
return undefined;
|
|
142
168
|
}
|
|
@@ -144,7 +170,7 @@ export var createContributorTagWidget = function createContributorTagWidget(_ref
|
|
|
144
170
|
// Reassigned when ProseMirror redraws this decoration, which it may do more than once for the
|
|
145
171
|
// same `Decoration` instance.
|
|
146
172
|
var mount;
|
|
147
|
-
var anchorPos = anchorAtRangeStart ? from :
|
|
173
|
+
var anchorPos = anchorAtRangeStart ? from : resolveTagAnchorPos(doc, from, to);
|
|
148
174
|
// A change inside a code block is hoisted out of it; anything else keeps its own anchor.
|
|
149
175
|
var codeBlockPos = resolveCodeBlockStart(doc, anchorPos);
|
|
150
176
|
return Decoration.widget(
|