@atlaskit/editor-plugin-show-diff 15.1.6 → 15.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/pm-plugins/calculateDiff/calculateDiffDecorations.js +5 -4
- package/dist/cjs/pm-plugins/decorations/colorSchemes/factory.js +25 -4
- package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +40 -14
- package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeView.js +14 -0
- package/dist/cjs/ui/ContributorTag/buildContributorTagDom.js +70 -14
- package/dist/cjs/ui/ContributorTag/contributorTagController.js +169 -150
- package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +5 -4
- package/dist/es2019/pm-plugins/decorations/colorSchemes/factory.js +25 -4
- package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +40 -14
- package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeView.js +14 -0
- package/dist/es2019/ui/ContributorTag/buildContributorTagDom.js +69 -13
- package/dist/es2019/ui/ContributorTag/contributorTagController.js +164 -104
- package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +5 -4
- package/dist/esm/pm-plugins/decorations/colorSchemes/factory.js +25 -4
- package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +40 -14
- package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeView.js +14 -0
- package/dist/esm/ui/ContributorTag/buildContributorTagDom.js +69 -13
- package/dist/esm/ui/ContributorTag/contributorTagController.js +171 -152
- package/dist/types/pm-plugins/decorations/colorSchemes/factory.d.ts +8 -2
- package/dist/types/ui/ContributorTag/buildContributorTagDom.d.ts +32 -3
- package/dist/types/ui/ContributorTag/contributorTagController.d.ts +32 -12
- package/package.json +2 -2
|
@@ -3,43 +3,98 @@ export const CONTRIBUTOR_TAG_TESTID = 'diff-contributor-tag';
|
|
|
3
3
|
export const CONTRIBUTOR_TAG_NAME_TESTID = 'diff-contributor-tag-name';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Ceiling for the tag width. A constant rather than the width of the change: a change is routinely a
|
|
7
|
+
* word or two, and clamping to it ellipsised the contributor's name — the one thing the tag exists to
|
|
8
|
+
* show. Longer names are ellipsised into the tooltip.
|
|
8
9
|
*/
|
|
9
|
-
export const
|
|
10
|
+
export const MAX_TAG_WIDTH = '200px';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The tag sits on the line above the change, so it has to paint over that line to be readable —
|
|
14
|
+
* including any diff highlight already on it, which would otherwise slice straight through the tag.
|
|
15
|
+
* Hence one level above `akEditorUnitZIndex`, where the highlights sit; exported because the
|
|
16
|
+
* change's own highlight is stacked one level below it — see `stackBelowContributorTagStyle` in
|
|
17
|
+
* `createInlineChangedDecoration`.
|
|
18
|
+
*
|
|
19
|
+
* Painting above its own highlight is only safe because the tag can never reach it: `bottom: 100%`
|
|
20
|
+
* puts the root's bottom edge exactly on the highlight's top edge, and the root's `overflow: clip`
|
|
21
|
+
* keeps every pixel the tag paints inside it.
|
|
22
|
+
*/
|
|
23
|
+
export const CONTRIBUTOR_TAG_Z_INDEX = 2;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The plugin's whole side of the tag's motion: the fade and the hidden state are
|
|
27
|
+
* `contributorTagStyles` in both EditorContentContainer stylesheets, keyed on this literal and the
|
|
28
|
+
* one below — rename in step. This package cannot import `@atlaskit/editor-core` at runtime, so the
|
|
29
|
+
* plugin emits the hook and editor-core owns the rule, as `VanillaTooltip` does with
|
|
30
|
+
* `VANILLA_TOOLTIP_DEFAULT_CLASS`.
|
|
31
|
+
*/
|
|
32
|
+
export const CONTRIBUTOR_TAG_CLASS = 'ak-editor-diff-contributor-tag';
|
|
33
|
+
|
|
34
|
+
/** Toggled by the controller to move the tag between the fade's two ends. */
|
|
35
|
+
export const CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE = 'data-revealed';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Backstop for removing a fading-out tag: a cancelled transition fires no `transitionend`. Must stay
|
|
39
|
+
* above the fade's 600ms, which lives in another package's stylesheet and cannot be read back as a
|
|
40
|
+
* number — hence the wide margin. Raise in step with that duration.
|
|
41
|
+
*/
|
|
42
|
+
export const TAG_EXIT_FALLBACK_MS = 1200;
|
|
10
43
|
|
|
11
44
|
// Positioned against the host widget its decoration renders on the change's first character:
|
|
12
45
|
// `bottom: 100%` lifts the tag onto the line above, `inset-inline-start` starts it on that
|
|
13
46
|
// character.
|
|
14
47
|
const constraintStyle = convertToInlineCss({
|
|
15
|
-
|
|
48
|
+
// `flex`, not `block`: a block box lays the tag on a line box whose strut is the *paragraph's*
|
|
49
|
+
// line-height (~24px) against the tag's ~20px, so `bottom: 100%` pins that leftover strut to the
|
|
50
|
+
// change and the tag floats above it. A flex container has no line box.
|
|
51
|
+
display: 'flex',
|
|
16
52
|
position: 'absolute',
|
|
17
53
|
bottom: '100%',
|
|
18
54
|
insetInlineStart: 0,
|
|
55
|
+
// On the root, since that is the absolutely positioned box; the tag below fills it.
|
|
56
|
+
maxWidth: MAX_TAG_WIDTH,
|
|
19
57
|
minWidth: 0,
|
|
20
|
-
//
|
|
21
|
-
|
|
58
|
+
// This box's bottom edge is the join with the highlight, and the tag paints *above* that
|
|
59
|
+
// highlight — so anything spilling past this edge would read as the tag laid on top of the change
|
|
60
|
+
// rather than attached to it. The reveal's `translateY` is exactly that spill: it starts the tag
|
|
61
|
+
// 4px low, and clipping the part that hangs over the highlight turns the rise into an unfurl out
|
|
62
|
+
// of the join. `clip`, not `hidden`: `hidden` would make this a scroll container, and focusing the
|
|
63
|
+
// tag mid-reveal would scroll it 4px out of alignment for good.
|
|
64
|
+
//
|
|
65
|
+
// The tooltip is unaffected — it is a `popover`, so it paints in the top layer, which no
|
|
66
|
+
// ancestor's clip reaches.
|
|
67
|
+
overflow: 'clip',
|
|
68
|
+
zIndex: CONTRIBUTOR_TAG_Z_INDEX,
|
|
22
69
|
// It renders inside the document and it is not content: dragging a selection across the change
|
|
23
70
|
// must not select it.
|
|
24
71
|
userSelect: 'none'
|
|
25
72
|
});
|
|
26
73
|
|
|
27
|
-
// `backgroundColor
|
|
28
|
-
//
|
|
74
|
+
// `backgroundColor` comes from the model, and is set by the controller. `opacity`, `pointer-events`
|
|
75
|
+
// and the transition between their two states are in `contributorTagStyles` — see
|
|
76
|
+
// `CONTRIBUTOR_TAG_CLASS`.
|
|
29
77
|
const tagStyle = convertToInlineCss({
|
|
30
78
|
display: 'inline-flex',
|
|
31
79
|
alignItems: 'center',
|
|
32
80
|
gap: "var(--ds-space-050, 4px)",
|
|
33
|
-
// Never wider than the
|
|
81
|
+
// Never wider than `MAX_TAG_WIDTH` on the root; overflow is ellipsised on the name below.
|
|
34
82
|
maxWidth: '100%',
|
|
35
83
|
minWidth: 0,
|
|
36
84
|
overflow: 'hidden',
|
|
37
85
|
paddingInline: "var(--ds-space-050, 4px)",
|
|
38
86
|
paddingBlock: "var(--ds-space-025, 2px)",
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
87
|
+
// Top corners only: the bottom edge butts onto the change, and a rounded corner there would let
|
|
88
|
+
// the page background through at the join.
|
|
89
|
+
borderTopLeftRadius: "var(--ds-radius-small, 4px)",
|
|
90
|
+
borderTopRightRadius: "var(--ds-radius-small, 4px)",
|
|
91
|
+
borderBottomLeftRadius: 0,
|
|
92
|
+
borderBottomRightRadius: 0,
|
|
93
|
+
// Deliberately no bottom border: the change already carries the accent as its own underline, and
|
|
94
|
+
// a second rule directly above it reads as two objects rather than one label on one change.
|
|
95
|
+
//
|
|
96
|
+
// Deliberately no `elevation.shadow.overlay`: its `0 8px 12px` offset casts downwards onto the
|
|
97
|
+
// change, which reads as a gap between the tag and the content it captions.
|
|
43
98
|
font: "var(--ds-font-body-small, normal 400 12px/16px \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
|
|
44
99
|
whiteSpace: 'nowrap',
|
|
45
100
|
boxSizing: 'border-box'
|
|
@@ -86,6 +141,7 @@ const createSpan = (doc, style, attributes = {}) => {
|
|
|
86
141
|
export const buildContributorTagDom = doc => {
|
|
87
142
|
const root = createSpan(doc, constraintStyle);
|
|
88
143
|
const tag = createSpan(doc, tagStyle, {
|
|
144
|
+
class: CONTRIBUTOR_TAG_CLASS,
|
|
89
145
|
'data-testid': CONTRIBUTOR_TAG_TESTID,
|
|
90
146
|
// Deliberately no `aria-label`: it would be announced ahead of the element's contents, but
|
|
91
147
|
// design requires the contributor *after* the change — hence the visually hidden label.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
-
import { bindAll } from 'bind-event-listener';
|
|
2
|
+
import { bind, bindAll } from 'bind-event-listener';
|
|
3
3
|
import { VanillaTooltip } from '@atlaskit/editor-common/vanilla-tooltip';
|
|
4
4
|
import { getAccentTokens } from '../../pm-plugins/decorations/colorSchemes/factory';
|
|
5
5
|
import { colorSchemeRegistry } from '../../pm-plugins/decorations/colorSchemes/schemes';
|
|
6
|
-
import { buildContributorTagDom,
|
|
6
|
+
import { buildContributorTagDom, CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE, TAG_EXIT_FALLBACK_MS } from './buildContributorTagDom';
|
|
7
7
|
import { contributorAvatarRenderer } from './contributorAvatarRenderer';
|
|
8
8
|
import { contributorTagMessages } from './messages';
|
|
9
9
|
const getContributorName = (contributor, formatMessage) => {
|
|
@@ -22,8 +22,8 @@ const getContributorName = (contributor, formatMessage) => {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Resolves the tag's accent from the same scheme value
|
|
26
|
-
* two can never drift.
|
|
25
|
+
* Resolves the tag's accent from the same scheme value the highlight was drawn from, so the colour
|
|
26
|
+
* the two carry can never drift — only the tone does, which is `getAccentTokens`'s to pick.
|
|
27
27
|
*/
|
|
28
28
|
const getTagAccent = ({
|
|
29
29
|
colorScheme,
|
|
@@ -35,17 +35,66 @@ const getTagAccent = ({
|
|
|
35
35
|
return getAccentTokens(accent);
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Keyboard focus, as a member of `revealSources` alongside the elements the pointer can be over. It
|
|
40
|
+
* has no element of its own to key on — the tag is already in the set under its own hover.
|
|
41
|
+
*/
|
|
42
|
+
const FOCUS_SOURCE = Symbol('contributor-tag-focus');
|
|
43
|
+
|
|
44
|
+
/** Moves the tag between the two ends of the fade `contributorTagStyles` declares. */
|
|
45
|
+
const setRevealed = (tag, isVisible) => {
|
|
46
|
+
tag.toggleAttribute(CONTRIBUTOR_TAG_REVEALED_ATTRIBUTE, isVisible);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Whether flipping the tag's state will actually run a transition on it.
|
|
51
|
+
*
|
|
52
|
+
* Asked of the element rather than of `matchMedia`, because reduced motion and a missing stylesheet
|
|
53
|
+
* both compute to a zero duration — and a transition that never starts fires no `transitionend`, so
|
|
54
|
+
* a tag waiting for one would sit on screen until the backstop timeout.
|
|
55
|
+
*
|
|
56
|
+
* Read only when a tag is being taken down: it resolves style.
|
|
57
|
+
*/
|
|
58
|
+
const willTransition = tag => window.getComputedStyle(tag).transitionDuration.split(',').some(duration => parseFloat(duration) > 0);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fades a tag that is still on screen out, and releases it once the fade has finished.
|
|
62
|
+
*
|
|
63
|
+
* No controller owns the element by the time this runs — it is released up front so a republished
|
|
64
|
+
* model's tag can fade in over the top — so everything the removal needs is captured here.
|
|
65
|
+
*/
|
|
66
|
+
const fadeTagOut = (tag, release) => {
|
|
67
|
+
setRevealed(tag, false);
|
|
68
|
+
|
|
69
|
+
// Nothing to wait for: reduced motion, or no stylesheet to fade it with.
|
|
70
|
+
if (!willTransition(tag)) {
|
|
71
|
+
release();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
let unbind;
|
|
75
|
+
let isFinished = false;
|
|
76
|
+
const finish = () => {
|
|
77
|
+
var _unbind;
|
|
78
|
+
// Whichever of the two signals arrives first wins; the other must not release twice.
|
|
79
|
+
if (isFinished) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
isFinished = true;
|
|
83
|
+
(_unbind = unbind) === null || _unbind === void 0 ? void 0 : _unbind();
|
|
84
|
+
unbind = undefined;
|
|
85
|
+
clearTimeout(fallbackId);
|
|
86
|
+
release();
|
|
87
|
+
};
|
|
88
|
+
unbind = bind(tag, {
|
|
89
|
+
type: 'transitionend',
|
|
90
|
+
// `transitionend` bubbles, and opacity is not the only property that could ever carry one.
|
|
91
|
+
listener: event => {
|
|
92
|
+
if (event.target === tag && event.propertyName === 'opacity') {
|
|
93
|
+
finish();
|
|
45
94
|
}
|
|
46
95
|
}
|
|
47
|
-
}
|
|
48
|
-
|
|
96
|
+
});
|
|
97
|
+
const fallbackId = setTimeout(finish, TAG_EXIT_FALLBACK_MS);
|
|
49
98
|
};
|
|
50
99
|
|
|
51
100
|
/** The contributors a tag draws, keyed so a republished model can be recognised as the same pair. */
|
|
@@ -71,8 +120,18 @@ export class ContributorTagController {
|
|
|
71
120
|
_defineProperty(this, "bindToken", 0);
|
|
72
121
|
_defineProperty(this, "fullLabel", '');
|
|
73
122
|
_defineProperty(this, "isDestroyed", false);
|
|
74
|
-
|
|
75
|
-
_defineProperty(this, "
|
|
123
|
+
/** Last visibility written, so a teardown knows whether there is anything on screen to fade. */
|
|
124
|
+
_defineProperty(this, "isVisible", false);
|
|
125
|
+
/**
|
|
126
|
+
* Everything currently asking for the tag to be up, other than the model: each hovered highlight,
|
|
127
|
+
* the tag itself while the pointer is on it, and `FOCUS_SOURCE` while it holds focus.
|
|
128
|
+
*
|
|
129
|
+
* One set rather than independent booleans, which is what let the exit's trailing delay go: a
|
|
130
|
+
* pointer crossing from the highlight onto the tag removes one member and adds another in the same
|
|
131
|
+
* dispatch, so no style is resolved in between and the browser never starts the exit. Booleans
|
|
132
|
+
* could not see each other, so the crossing wrote the tag out and straight back in.
|
|
133
|
+
*/
|
|
134
|
+
_defineProperty(this, "revealSources", new Set());
|
|
76
135
|
_defineProperty(this, "unbindHighlights", []);
|
|
77
136
|
this.options = options;
|
|
78
137
|
}
|
|
@@ -124,56 +183,96 @@ export class ContributorTagController {
|
|
|
124
183
|
this.renderLabels(next);
|
|
125
184
|
this.applyAccent(next);
|
|
126
185
|
this.syncHighlightBindings(next);
|
|
127
|
-
this.
|
|
186
|
+
this.applyVisibility();
|
|
128
187
|
this.syncTooltip();
|
|
129
188
|
}
|
|
130
189
|
|
|
131
|
-
/**
|
|
190
|
+
/**
|
|
191
|
+
* Takes the tag's DOM down, keeping the subscription so a republished model redraws it.
|
|
192
|
+
*
|
|
193
|
+
* A tag that is still on screen is released here and fades out on its own, so a republished
|
|
194
|
+
* model's tag fades in over the top of the outgoing one — see `fadeTagOut`.
|
|
195
|
+
*/
|
|
132
196
|
teardownTag() {
|
|
133
|
-
var _this$unbindTag, _this$tooltip
|
|
197
|
+
var _this$unbindTag, _this$tooltip;
|
|
198
|
+
// Unbound synchronously, ahead of any fade: a tag on its way out must not be revealed again by
|
|
199
|
+
// the pointer, and these listeners are what hold this controller.
|
|
134
200
|
this.unbindHighlightState();
|
|
135
201
|
this.boundSelector = undefined;
|
|
136
202
|
(_this$unbindTag = this.unbindTag) === null || _this$unbindTag === void 0 ? void 0 : _this$unbindTag.call(this);
|
|
137
203
|
this.unbindTag = undefined;
|
|
204
|
+
// Destroyed rather than carried through the fade: it is a popover in the top layer.
|
|
138
205
|
(_this$tooltip = this.tooltip) === null || _this$tooltip === void 0 ? void 0 : _this$tooltip.destroy();
|
|
139
206
|
this.tooltip = undefined;
|
|
140
207
|
this.tooltipContent = undefined;
|
|
141
|
-
|
|
208
|
+
const {
|
|
209
|
+
dom,
|
|
210
|
+
isVisible
|
|
211
|
+
} = this;
|
|
212
|
+
// Destroying an avatar removes its element, so the avatars go with whoever removes the tag —
|
|
213
|
+
// destroyed here and it would fade out empty.
|
|
214
|
+
const {
|
|
215
|
+
avatars
|
|
216
|
+
} = this;
|
|
142
217
|
this.avatars = [];
|
|
143
|
-
(_this$dom = this.dom) === null || _this$dom === void 0 ? void 0 : _this$dom.root.remove();
|
|
144
218
|
this.dom = undefined;
|
|
145
|
-
this.
|
|
146
|
-
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
219
|
+
this.isVisible = false;
|
|
220
|
+
// The set belongs to the listeners just unbound: a stale member would draw the tag as visible
|
|
221
|
+
// with the pointer nowhere near it.
|
|
222
|
+
this.revealSources.clear();
|
|
223
|
+
if (!dom) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const release = () => {
|
|
227
|
+
avatars.forEach(avatar => avatar.destroy());
|
|
228
|
+
dom.root.remove();
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Nothing on screen to fade. `isConnected` covers ProseMirror destroying the widget: a fade on
|
|
232
|
+
// a detached element never starts.
|
|
233
|
+
if (!isVisible || !dom.tag.isConnected) {
|
|
234
|
+
release();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
fadeTagOut(dom.tag, release);
|
|
150
238
|
}
|
|
151
239
|
bindTag({
|
|
152
240
|
tag
|
|
153
241
|
}) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
};
|
|
242
|
+
// `mouseover`/`mouseout` rather than the enter/leave pair, because they are what fire as the
|
|
243
|
+
// pointer moves between the tag's own children. Both are keyed on the tag, so those moves add
|
|
244
|
+
// and remove the same member and the set never empties.
|
|
158
245
|
this.unbindTag = bindAll(tag, [{
|
|
159
246
|
type: 'mouseover',
|
|
160
|
-
listener:
|
|
247
|
+
listener: this.trackRevealSource(tag, true)
|
|
161
248
|
}, {
|
|
162
249
|
type: 'mouseout',
|
|
163
|
-
listener:
|
|
250
|
+
listener: this.trackRevealSource(tag, false)
|
|
164
251
|
}, {
|
|
165
252
|
type: 'focus',
|
|
166
|
-
listener:
|
|
253
|
+
listener: this.trackRevealSource(FOCUS_SOURCE, true)
|
|
167
254
|
}, {
|
|
168
255
|
type: 'blur',
|
|
169
|
-
listener:
|
|
256
|
+
listener: this.trackRevealSource(FOCUS_SOURCE, false)
|
|
170
257
|
}]);
|
|
171
258
|
}
|
|
259
|
+
|
|
260
|
+
/** One listener shape for every reveal source, so all of them land in the same set. */
|
|
261
|
+
trackRevealSource(source, isRevealing) {
|
|
262
|
+
return () => {
|
|
263
|
+
if (isRevealing) {
|
|
264
|
+
this.revealSources.add(source);
|
|
265
|
+
} else {
|
|
266
|
+
this.revealSources.delete(source);
|
|
267
|
+
}
|
|
268
|
+
this.applyVisibility();
|
|
269
|
+
};
|
|
270
|
+
}
|
|
172
271
|
renderAvatars(model) {
|
|
173
|
-
var _this$
|
|
272
|
+
var _this$dom;
|
|
174
273
|
const {
|
|
175
274
|
avatars: container
|
|
176
|
-
} = (_this$
|
|
275
|
+
} = (_this$dom = this.dom) !== null && _this$dom !== void 0 ? _this$dom : {};
|
|
177
276
|
if (!container) {
|
|
178
277
|
return;
|
|
179
278
|
}
|
|
@@ -231,8 +330,8 @@ export class ContributorTagController {
|
|
|
231
330
|
const agentName = isPrimaryAgent ? primaryName : secondaryName;
|
|
232
331
|
const userName = isPrimaryAgent ? secondaryName : primaryName;
|
|
233
332
|
|
|
234
|
-
// A connected pair only ever shows the agent; the pair is spelled out in the label, because
|
|
235
|
-
//
|
|
333
|
+
// A connected pair only ever shows the agent; the pair is spelled out in the label, because the
|
|
334
|
+
// tag is capped at `MAX_TAG_WIDTH` and two names rarely fit inside it.
|
|
236
335
|
this.dom.name.textContent = model.connectedContributor ? agentName || primaryName : primaryName;
|
|
237
336
|
this.fullLabel = model.connectedContributor ? formatMessage(contributorTagMessages.changedByConnected, {
|
|
238
337
|
agentName,
|
|
@@ -256,30 +355,30 @@ export class ContributorTagController {
|
|
|
256
355
|
}
|
|
257
356
|
const accent = this.accentOf(model);
|
|
258
357
|
this.dom.tag.style.setProperty('background-color', accent.background);
|
|
259
|
-
this.dom.tag.style.setProperty('border-bottom-color', accent.border);
|
|
260
358
|
this.dom.name.style.setProperty('color', accent.text);
|
|
261
359
|
this.avatars.forEach(avatar => avatar.setRingColor(accent.background));
|
|
262
360
|
}
|
|
263
361
|
|
|
264
|
-
/**
|
|
265
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Visibility — the one thing the model does not carry on its own, because hover is not in it.
|
|
364
|
+
*
|
|
365
|
+
* One idempotent attribute write, with nothing to guard: whether the flip is a real one, whether
|
|
366
|
+
* the tag is attached yet, and whether motion is wanted at all are all the stylesheet's to answer.
|
|
367
|
+
*/
|
|
368
|
+
applyVisibility() {
|
|
266
369
|
var _this$model;
|
|
267
370
|
if (!this.dom) {
|
|
268
371
|
return;
|
|
269
372
|
}
|
|
270
|
-
this.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
this.maxWidthPx === undefined ? '' : `max(${Math.round(this.maxWidthPx)}px, ${MIN_TAG_WIDTH})`);
|
|
274
|
-
const isVisible = Boolean((_this$model = this.model) === null || _this$model === void 0 ? void 0 : _this$model.isActive) || this.isHighlightHovered || this.isHoveredOrFocused;
|
|
275
|
-
this.dom.tag.style.setProperty('opacity', isVisible ? '1' : '0');
|
|
276
|
-
this.dom.tag.style.setProperty('pointer-events', isVisible ? 'auto' : 'none');
|
|
373
|
+
const isVisible = Boolean((_this$model = this.model) === null || _this$model === void 0 ? void 0 : _this$model.isActive) || this.revealSources.size > 0;
|
|
374
|
+
this.isVisible = isVisible;
|
|
375
|
+
setRevealed(this.dom.tag, isVisible);
|
|
277
376
|
}
|
|
278
377
|
|
|
279
378
|
/**
|
|
280
|
-
* The full label is always the tooltip, whether or not the name it draws is clipped:
|
|
281
|
-
*
|
|
282
|
-
*
|
|
379
|
+
* The full label is always the tooltip, whether or not the name it draws is clipped: a name longer
|
|
380
|
+
* than `MAX_TAG_WIDTH` is ellipsised, and a connected pair spells out a contributor the tag does
|
|
381
|
+
* not show at any width.
|
|
283
382
|
*
|
|
284
383
|
* `VanillaTooltip` has no "set new content" call, so it is rebuilt when the label changes — the
|
|
285
384
|
* shape of `syncVanillaDisabledTooltip` in `mentionNodeView`.
|
|
@@ -308,9 +407,9 @@ export class ContributorTagController {
|
|
|
308
407
|
}
|
|
309
408
|
|
|
310
409
|
/**
|
|
311
|
-
* Observes the highlight this tag describes, for what CSS here cannot see: its hover state
|
|
410
|
+
* Observes the highlight this tag describes, for what CSS here cannot see: its hover state. The
|
|
312
411
|
* highlight is a ProseMirror decoration in a different DOM subtree, so listeners are bound
|
|
313
|
-
* imperatively via `data-diff-id
|
|
412
|
+
* imperatively via `data-diff-id`.
|
|
314
413
|
*
|
|
315
414
|
* Deliberately not routed through plugin state: a transaction per pointer move would re-run the
|
|
316
415
|
* memoised decoration calculation.
|
|
@@ -327,8 +426,8 @@ export class ContributorTagController {
|
|
|
327
426
|
this.boundSelector = selector;
|
|
328
427
|
this.unbindHighlightState();
|
|
329
428
|
|
|
330
|
-
// ProseMirror draws the tag's host in the same pass as the highlights
|
|
331
|
-
//
|
|
429
|
+
// ProseMirror draws the tag's host in the same pass as the highlights it binds to, so those may
|
|
430
|
+
// not be in the document yet. A microtask lands once that pass has finished.
|
|
332
431
|
const pendingBind = ++this.bindToken;
|
|
333
432
|
queueMicrotask(() => {
|
|
334
433
|
if (!this.isDestroyed && pendingBind === this.bindToken) {
|
|
@@ -337,66 +436,27 @@ export class ContributorTagController {
|
|
|
337
436
|
});
|
|
338
437
|
}
|
|
339
438
|
bindHighlightState(selector) {
|
|
340
|
-
|
|
341
|
-
//
|
|
342
|
-
// inside this one, cannot
|
|
343
|
-
|
|
344
|
-
const highlights =
|
|
345
|
-
if (!
|
|
439
|
+
var _this$options$getEdit;
|
|
440
|
+
// Scoped to this editor's content root, so a tag can only ever bind to the decorations its own
|
|
441
|
+
// plugin instance rendered — another editor on the page, or one nested inside this one, cannot
|
|
442
|
+
// reveal this tag.
|
|
443
|
+
const highlights = (_this$options$getEdit = this.options.getEditorRoot()) === null || _this$options$getEdit === void 0 ? void 0 : _this$options$getEdit.querySelectorAll(selector);
|
|
444
|
+
if (!(highlights !== null && highlights !== void 0 && highlights.length)) {
|
|
346
445
|
return;
|
|
347
446
|
}
|
|
348
447
|
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
const measure = () => {
|
|
353
|
-
var _findChangeStartLine;
|
|
354
|
-
// Counted by distinct line box, not by client rect: an inline node inside the change (a
|
|
355
|
-
// mention, emoji, date) splits the highlight into several rects on the same line, and
|
|
356
|
-
// treating that as a wrap would drop the width clamp on changes that never wrapped.
|
|
357
|
-
const lineTops = new Set();
|
|
358
|
-
for (const highlight of ownElements) {
|
|
359
|
-
for (const rect of highlight.getClientRects()) {
|
|
360
|
-
lineTops.add(Math.round(rect.top));
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// A wrapped change's first line says nothing useful about how wide the tag may be.
|
|
365
|
-
this.maxWidthPx = lineTops.size > 1 ? undefined : (_findChangeStartLine = findChangeStartLine(ownElements)) === null || _findChangeStartLine === void 0 ? void 0 : _findChangeStartLine.width;
|
|
366
|
-
this.applyStyles();
|
|
367
|
-
};
|
|
368
|
-
measure();
|
|
369
|
-
|
|
370
|
-
// An inline element cannot be observed itself (ResizeObserver skips boxes with no principal
|
|
371
|
-
// box), so the block it sits in is observed instead.
|
|
372
|
-
this.resizeObserver = new ResizeObserver(measure);
|
|
373
|
-
for (const parent of new Set(ownElements.map(({
|
|
374
|
-
parentElement
|
|
375
|
-
}) => parentElement))) {
|
|
376
|
-
if (parent) {
|
|
377
|
-
this.resizeObserver.observe(parent);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// `bindAll` takes a single target, and a change can render more than one highlight.
|
|
448
|
+
// `bindAll` takes a single target, and a change can render more than one highlight. Each is its
|
|
449
|
+
// own member of `revealSources`, so the pointer can cross between them without the set
|
|
450
|
+
// emptying.
|
|
382
451
|
this.unbindHighlights = Array.from(highlights).map(highlight => bindAll(highlight, [{
|
|
383
452
|
type: 'mouseenter',
|
|
384
|
-
listener: ()
|
|
385
|
-
this.isHighlightHovered = true;
|
|
386
|
-
this.applyStyles();
|
|
387
|
-
}
|
|
453
|
+
listener: this.trackRevealSource(highlight, true)
|
|
388
454
|
}, {
|
|
389
455
|
type: 'mouseleave',
|
|
390
|
-
listener: ()
|
|
391
|
-
this.isHighlightHovered = false;
|
|
392
|
-
this.applyStyles();
|
|
393
|
-
}
|
|
456
|
+
listener: this.trackRevealSource(highlight, false)
|
|
394
457
|
}]));
|
|
395
458
|
}
|
|
396
459
|
unbindHighlightState() {
|
|
397
|
-
var _this$resizeObserver;
|
|
398
|
-
(_this$resizeObserver = this.resizeObserver) === null || _this$resizeObserver === void 0 ? void 0 : _this$resizeObserver.disconnect();
|
|
399
|
-
this.resizeObserver = undefined;
|
|
400
460
|
this.unbindHighlights.forEach(unbind => unbind());
|
|
401
461
|
this.unbindHighlights = [];
|
|
402
462
|
}
|
|
@@ -14,7 +14,7 @@ import { Mapping } from '@atlaskit/editor-prosemirror/transform';
|
|
|
14
14
|
import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
15
15
|
import { UNSAFE_expValNoExposure } from '@atlaskit/platform-feature-experiments/unsafe-exp-val-no-exposure';
|
|
16
16
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
17
|
-
import {
|
|
17
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
18
18
|
import { areDocsEqualByBlockStructureAndText } from '../areDocsEqualByBlockStructureAndText';
|
|
19
19
|
import { createDocMarginAnchorWidget } from '../decorations/createAnchorDecorationWidgets';
|
|
20
20
|
import { createBlockChangedDecoration } from '../decorations/createBlockChangedDecoration';
|
|
@@ -514,8 +514,9 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
514
514
|
// gets highlighted, and structural gaps never do.
|
|
515
515
|
var isSmartNodeLevel = diffType === 'smart' && fg('platform_editor_ai_smart_diff') && smartChangeLevel(change) === 'node';
|
|
516
516
|
// For a large inserted table, skip the per-cell inline decorations that
|
|
517
|
-
// freeze the browser on re-render. Only active
|
|
518
|
-
|
|
517
|
+
// freeze the browser on re-render. Only active when Review moment is
|
|
518
|
+
// eligible (xstate + M1).
|
|
519
|
+
var useCoarseTableDecoration = isSmartNodeLevel && expValEqualsNoExposure('platform_editor_ai_xstate_migration', 'isEnabled', true) && UNSAFE_expValNoExposure('platform_editor_ai_streaming_ux_experience_m1', 'isEnabled', false) === true && isLargeInsertedTableRange(tr.doc, change.fromB, change.toB);
|
|
519
520
|
// Whether the deleted-content widget will actually be rendered for this
|
|
520
521
|
// change. Used to decide if indicator anchor positions should be adjusted
|
|
521
522
|
// inward — when the widget is present the anchor must stay at the block
|
|
@@ -714,7 +715,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
|
|
|
714
715
|
})));
|
|
715
716
|
// If the original node position is known (e.g. a panel type change), also render
|
|
716
717
|
// the old block node as a "deleted" widget so the reviewer sees the before/after.
|
|
717
|
-
var isSmartNodeLevelAttrChange = diffType === 'smart' && fg('platform_editor_ai_smart_diff') &&
|
|
718
|
+
var isSmartNodeLevelAttrChange = diffType === 'smart' && fg('platform_editor_ai_smart_diff') && expValEqualsNoExposure('platform_editor_ai_xstate_migration', 'isEnabled', true) && UNSAFE_expValNoExposure('platform_editor_ai_streaming_ux_experience_m1', 'isEnabled', false) === true;
|
|
718
719
|
if (isSmartNodeLevelAttrChange && change.fromA !== undefined && change.toA !== undefined) {
|
|
719
720
|
var placeBelow = deletedDiffPlacement === 'bottom';
|
|
720
721
|
decorations.push.apply(decorations, _toConsumableArray(createNodeChangedDecorationWidget(_objectSpread(_objectSpread({
|
|
@@ -65,6 +65,18 @@ var bgSubtlerPressedMap = {
|
|
|
65
65
|
gray: "var(--ds-background-accent-gray-subtler-pressed, #8C8F97)",
|
|
66
66
|
lime: "var(--ds-background-accent-lime-subtler-pressed, #B3DF72)"
|
|
67
67
|
};
|
|
68
|
+
var bgBolderMap = {
|
|
69
|
+
green: "var(--ds-background-accent-green-bolder, #1F845A)",
|
|
70
|
+
teal: "var(--ds-background-accent-teal-bolder, #227D9B)",
|
|
71
|
+
blue: "var(--ds-background-accent-blue-bolder, #1868DB)",
|
|
72
|
+
purple: "var(--ds-background-accent-purple-bolder, #964AC0)",
|
|
73
|
+
red: "var(--ds-background-accent-red-bolder, #C9372C)",
|
|
74
|
+
orange: "var(--ds-background-accent-orange-bolder, #BD5B00)",
|
|
75
|
+
yellow: "var(--ds-background-accent-yellow-bolder, #946F00)",
|
|
76
|
+
magenta: "var(--ds-background-accent-magenta-bolder, #AE4787)",
|
|
77
|
+
gray: "var(--ds-background-accent-gray-bolder, #6B6E76)",
|
|
78
|
+
lime: "var(--ds-background-accent-lime-bolder, #5B7F24)"
|
|
79
|
+
};
|
|
68
80
|
var borderAccentMap = {
|
|
69
81
|
green: "var(--ds-border-accent-green, #22A06B)",
|
|
70
82
|
teal: "var(--ds-border-accent-teal, #2898BD)",
|
|
@@ -98,6 +110,9 @@ function bgSubtlestPressed(color) {
|
|
|
98
110
|
function bgSubtler(color) {
|
|
99
111
|
return bgSubtlerMap[color];
|
|
100
112
|
}
|
|
113
|
+
function bgBolder(color) {
|
|
114
|
+
return bgBolderMap[color];
|
|
115
|
+
}
|
|
101
116
|
function bgSubtlerPressed(color) {
|
|
102
117
|
return bgSubtlerPressedMap[color];
|
|
103
118
|
}
|
|
@@ -108,12 +123,18 @@ function textAccent(color) {
|
|
|
108
123
|
return textAccentMap[color];
|
|
109
124
|
}
|
|
110
125
|
|
|
111
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* Presentation tokens for a contributor tag in one diff colour.
|
|
128
|
+
*
|
|
129
|
+
* A `bolder` fill rather than the `subtlest` tint the highlight uses: the tag is a label on the
|
|
130
|
+
* change, and at tag size a tint reads as a second, weaker highlight sitting above the real one.
|
|
131
|
+
* `inverse` is the only text tone that reads on a bolder fill, and it needs no per-colour map —
|
|
132
|
+
* which is also why the tag carries no border: the fill alone bounds it.
|
|
133
|
+
*/
|
|
112
134
|
export function getAccentTokens(color) {
|
|
113
135
|
return {
|
|
114
|
-
background:
|
|
115
|
-
|
|
116
|
-
text: textAccent(color)
|
|
136
|
+
background: bgBolder(color),
|
|
137
|
+
text: "var(--ds-text-inverse, #FFFFFF)"
|
|
117
138
|
};
|
|
118
139
|
}
|
|
119
140
|
|