@atlaskit/editor-plugin-show-diff 20.0.11 → 21.0.1

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cjs/pm-plugins/NodeViewSerializer.js +71 -7
  3. package/dist/cjs/pm-plugins/createEditorProxy.js +84 -0
  4. package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +25 -6
  5. package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +35 -11
  6. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeView.js +38 -2
  7. package/dist/cjs/pm-plugins/markViews.js +65 -0
  8. package/dist/cjs/pm-plugins/resolveDiffContributors.js +3 -3
  9. package/dist/cjs/ui/ContributorTag/contributorAvatarRenderer.js +8 -9
  10. package/dist/cjs/ui/ContributorTag/contributorTagIcons.js +1 -8
  11. package/dist/es2019/pm-plugins/NodeViewSerializer.js +64 -8
  12. package/dist/es2019/pm-plugins/createEditorProxy.js +76 -0
  13. package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +26 -8
  14. package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +35 -12
  15. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeView.js +36 -2
  16. package/dist/es2019/pm-plugins/markViews.js +61 -0
  17. package/dist/es2019/pm-plugins/resolveDiffContributors.js +3 -3
  18. package/dist/es2019/ui/ContributorTag/contributorAvatarRenderer.js +8 -9
  19. package/dist/es2019/ui/ContributorTag/contributorTagIcons.js +1 -8
  20. package/dist/esm/pm-plugins/NodeViewSerializer.js +71 -7
  21. package/dist/esm/pm-plugins/createEditorProxy.js +78 -0
  22. package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +27 -8
  23. package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +37 -12
  24. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeView.js +38 -2
  25. package/dist/esm/pm-plugins/markViews.js +60 -0
  26. package/dist/esm/pm-plugins/resolveDiffContributors.js +3 -3
  27. package/dist/esm/ui/ContributorTag/contributorAvatarRenderer.js +8 -9
  28. package/dist/esm/ui/ContributorTag/contributorTagIcons.js +1 -8
  29. package/dist/types/pm-plugins/NodeViewSerializer.d.ts +7 -2
  30. package/dist/types/pm-plugins/createEditorProxy.d.ts +18 -0
  31. package/dist/types/pm-plugins/decorations/createContributorTagWidget.d.ts +2 -1
  32. package/dist/types/pm-plugins/markViews.d.ts +14 -0
  33. package/dist/types/ui/ContributorTag/contributorTagIcons.d.ts +1 -4
  34. package/package.json +11 -11
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 21.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`df1aa098e75d3`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/df1aa098e75d3) -
8
+ Contributor tags now always render Rovo's real avatar URL when available, instead of the hardcoded
9
+ Rovo glyph. The glyph was removed; the generic agent fallback is used when no URL is supplied or
10
+ it fails to load, matching Figma/Lovable/Replit.
11
+ - Updated dependencies
12
+
13
+ ## 21.0.0
14
+
15
+ ### Patch Changes
16
+
17
+ - [`a302d2ba96967`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a302d2ba96967) -
18
+ Fix contributor tag placement so it no longer overflows onto the next line when a change wraps.
19
+ - [`6563a0ae0eb6f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/6563a0ae0eb6f) -
20
+ [EDITOR-9212] Fix AI Suggested Edits diff rendering for a table nested inside an expand. Behind
21
+ `platform_editor_ai_show_diff_patch_2`, the diff preview now keeps the node's `breakout` mark, and
22
+ renders against its own document so node positions are resolved within the preview instead of the
23
+ live document — nested tables are no longer sized as top-level and pushed out of their cell, and a
24
+ preview can no longer write to the live document. The changed-node outline and the "Removed"
25
+ lozenge are both placed at the breakout width rather than the content column. Selecting a
26
+ suggestion that targets a collapsed expand now opens it in both the live document and the diff
27
+ preview, so the change is visible instead of hidden behind a title bar.
28
+ - Updated dependencies
29
+
3
30
  ## 20.0.11
4
31
 
5
32
  ### Patch Changes
@@ -8,8 +8,13 @@ exports.NodeViewSerializer = void 0;
8
8
  exports.isEditorViewWithNodeViews = isEditorViewWithNodeViews;
9
9
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
10
10
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
11
+ var _expand = require("@atlaskit/editor-common/expand");
11
12
  var _model = require("@atlaskit/editor-prosemirror/model");
13
+ var _utils = require("@atlaskit/editor-prosemirror/utils");
12
14
  var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
15
+ var _fg = require("@atlaskit/platform-feature-flags/fg");
16
+ var _createEditorProxy = require("./createEditorProxy");
17
+ var _markViews = require("./markViews");
13
18
  /**
14
19
  * Utilities for working with ProseMirror node views and DOM serialization within the
15
20
  * Show Diff editor plugin.
@@ -37,6 +42,16 @@ function isEditorViewWithNodeViews(view) {
37
42
  return view.nodeViews !== undefined;
38
43
  }
39
44
 
45
+ /** Expand-family nodes whose expanded state lives in a node-keyed WeakMap. */
46
+ var EXPAND_TYPES = new Set(['expand', 'nestedExpand']);
47
+
48
+ /** Tables are the only node views that read and write positions, so only they need a preview. */
49
+ var containsTable = function containsTable(node) {
50
+ var table = node.type.schema.nodes.table;
51
+ // `contains` only checks descendants, so the node itself is checked separately.
52
+ return !!table && (node.type === table || (0, _utils.contains)(node, table));
53
+ };
54
+
40
55
  /**
41
56
  * Encapsulates DOM serialization and node view access/creation.
42
57
  *
@@ -82,11 +97,18 @@ var NodeViewSerializer = exports.NodeViewSerializer = /*#__PURE__*/function () {
82
97
  key: "appendChildNodes",
83
98
  value: function appendChildNodes(children, contentDOM) {
84
99
  var _this = this;
100
+ var basePos = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
101
+ var editorProxy = arguments.length > 3 ? arguments[3] : undefined;
102
+ // A node's first child sits one position inside it, and each subsequent
103
+ // child is offset by the previous child's `nodeSize`. Tracking this lets nested node
104
+ // views resolve to their real depth instead of every one of them seeing depth 0.
105
+ var childPos = basePos + 1;
85
106
  children.forEach(function (child) {
86
- var childNode = _this.tryCreateNodeView(child) || _this.serializeNode(child);
107
+ var childNode = _this.tryCreateNodeViewInner(child, childPos, editorProxy) || _this.serializeNode(child);
87
108
  if (childNode) {
88
109
  contentDOM === null || contentDOM === void 0 || contentDOM.append(childNode);
89
110
  }
111
+ childPos += child.nodeSize;
90
112
  });
91
113
  }
92
114
 
@@ -95,11 +117,29 @@ var NodeViewSerializer = exports.NodeViewSerializer = /*#__PURE__*/function () {
95
117
  *
96
118
  * Returns `null` when there is no `EditorView`, no constructor for the node type,
97
119
  * or the node type is blocklisted. Otherwise returns the constructed node view instance.
120
+ *
121
+ * `$sourcePos` is where the node sits in the doc it came from, so nested tables keep their depth.
98
122
  */
99
123
  }, {
100
124
  key: "tryCreateNodeView",
101
125
  value: function tryCreateNodeView(targetNode) {
126
+ var _preview$rootPos;
127
+ var basePos = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
128
+ var $sourcePos = arguments.length > 2 ? arguments[2] : undefined;
129
+ if (!this.editorView) {
130
+ return null;
131
+ }
132
+ // The preview becomes the document every nested node view resolves its position against,
133
+ // so positions stay internally consistent.
134
+ var preview = (0, _fg.fg)('platform_editor_ai_show_diff_patch_2') && containsTable(targetNode) ? (0, _createEditorProxy.createEditorProxy)(this.editorView, targetNode, $sourcePos) : null;
135
+ return this.tryCreateNodeViewInner(targetNode, (_preview$rootPos = preview === null || preview === void 0 ? void 0 : preview.rootPos) !== null && _preview$rootPos !== void 0 ? _preview$rootPos : basePos, preview === null || preview === void 0 ? void 0 : preview.editorProxy);
136
+ }
137
+ }, {
138
+ key: "tryCreateNodeViewInner",
139
+ value: function tryCreateNodeViewInner(targetNode) {
102
140
  var _this$nodeViews;
141
+ var basePos = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
142
+ var editorProxy = arguments.length > 2 ? arguments[2] : undefined;
103
143
  if (!this.editorView) {
104
144
  return null;
105
145
  }
@@ -145,23 +185,47 @@ var NodeViewSerializer = exports.NodeViewSerializer = /*#__PURE__*/function () {
145
185
  if (!(0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_show_diff_deleted_nodeview_content') && targetNode.type.name === 'paragraph' && targetNode.children.length === 1) {
146
186
  return this.serializeFragment(targetNode.content);
147
187
  }
148
- this.appendChildNodes(targetNode.children, _contentDOM);
188
+ this.appendChildNodes(targetNode.children, _contentDOM, basePos, editorProxy);
149
189
  }
150
- return _dom;
190
+ return this.withMarkViews(targetNode, _dom, editorProxy);
151
191
  }
152
- var _constructor = constructor(targetNode, this.editorView, function () {
153
- return 0;
192
+
193
+ // The expand node view reads `expandedState.get(node) ?? false`, never `attrs.__expanded`.
194
+ // Open the preview so its tables get real layout; an existing entry is the live expand's
195
+ // own state, so leave it alone.
196
+ if (EXPAND_TYPES.has(targetNode.type.name) && !_expand.expandedState.has(targetNode) && (0, _fg.fg)('platform_editor_ai_show_diff_patch_2')) {
197
+ _expand.expandedState.set(targetNode, true);
198
+ }
199
+
200
+ // `isTableNested` is `doc.resolve(pos).depth > 0`, so the old hardcoded `0` made nested
201
+ // tables size as top-level. Positions are resolved against the preview document, so
202
+ // they are only meaningful with an editor proxy.
203
+ var view = editorProxy !== null && editorProxy !== void 0 ? editorProxy : this.editorView;
204
+ var docSize = view.state.doc.content.size;
205
+ var resolvedPos = editorProxy ? Math.max(0, Math.min(basePos, docSize)) : 0;
206
+ var _constructor = constructor(targetNode, view, function () {
207
+ return resolvedPos;
154
208
  }, [], {}),
155
209
  dom = _constructor.dom,
156
210
  contentDOM = _constructor.contentDOM;
157
211
  // Iteratively populate children
158
- this.appendChildNodes(targetNode.children, contentDOM);
159
- return dom;
212
+ this.appendChildNodes(targetNode.children, contentDOM, basePos, editorProxy);
213
+ return this.withMarkViews(targetNode, dom, editorProxy);
160
214
  } catch (_unused) {
161
215
  return null;
162
216
  }
163
217
  }
164
218
 
219
+ /** Wraps rendered DOM in its marks, rendered against the editor proxy when there is one. */
220
+ }, {
221
+ key: "withMarkViews",
222
+ value: function withMarkViews(targetNode, nodeDom, editorProxy) {
223
+ return (0, _markViews.wrapInMarkViews)(targetNode, nodeDom, {
224
+ nodeViews: this.nodeViews,
225
+ view: editorProxy !== null && editorProxy !== void 0 ? editorProxy : this.editorView
226
+ });
227
+ }
228
+
165
229
  /**
166
230
  * Serializes a node to a DOM `Node` using the schema's `DOMSerializer`.
167
231
  */
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createEditorProxy = createEditorProxy;
7
+ var _selection = require("@atlaskit/editor-common/selection/gap-cursor/selection");
8
+ /**
9
+ * A preview is rendered by the live view's own node views, which read `getPos()` against
10
+ * `view.state.doc` for both layout (`isTableNested`) and writes (`scaleTable` builds attribute
11
+ * steps at `getPos() + 1`). Handing them live positions makes the layout correct but points the
12
+ * writes at unrelated live nodes, so give them a view whose document *is* the preview instead.
13
+ *
14
+ * With `$sourcePos` (the node's position in the doc it came from) the node is swapped into a copy
15
+ * of its real top-level ancestor, so a table inside an expand still resolves as nested.
16
+ *
17
+ * Returns `null` when the node cannot be a top-level child (e.g. a bare `tableCell`), in which
18
+ * case the caller falls back to the live view and the previous stubbed positions.
19
+ */
20
+ function createEditorProxy(liveView, rootNode, $sourcePos) {
21
+ var liveState = liveView.state;
22
+ var createDoc = function createDoc(topLevel) {
23
+ return liveState.schema.topNodeType.createChecked(null, topLevel);
24
+ };
25
+ var previewDoc;
26
+ var rootPos = 0;
27
+ try {
28
+ var _$sourcePos$nodeAfter;
29
+ if ($sourcePos && $sourcePos.depth > 0 && ((_$sourcePos$nodeAfter = $sourcePos.nodeAfter) === null || _$sourcePos$nodeAfter === void 0 ? void 0 : _$sourcePos$nodeAfter.type) === rootNode.type) {
30
+ var topLevel = rootNode;
31
+ for (var depth = $sourcePos.depth; depth > 0; depth--) {
32
+ var ancestor = $sourcePos.node(depth);
33
+ topLevel = ancestor.copy(ancestor.content.replaceChild($sourcePos.index(depth), topLevel));
34
+ }
35
+ previewDoc = createDoc(topLevel);
36
+ rootPos = $sourcePos.pos - $sourcePos.before(1);
37
+ } else {
38
+ previewDoc = createDoc(rootNode);
39
+ }
40
+ } catch (_unused) {
41
+ return null;
42
+ }
43
+
44
+ // A gap cursor just before the node selects nothing, so the table's node view doesn't render
45
+ // as selected or show its controls.
46
+ var previewSelection = new _selection.GapCursorSelection(previewDoc.resolve(rootPos), _selection.Side.LEFT);
47
+ // Proxy rather than `EditorState.create` so plugin state is shared as-is: re-initialising
48
+ // every plugin per preview would be costly and re-runs their side effects.
49
+ var stateProxy = new Proxy(liveState, {
50
+ get: function get(target, prop, receiver) {
51
+ if (prop === 'doc') {
52
+ return previewDoc;
53
+ }
54
+ if (prop === 'selection') {
55
+ return previewSelection;
56
+ }
57
+ return Reflect.get(target, prop, receiver);
58
+ }
59
+ });
60
+ var editorProxy = new Proxy(liveView, {
61
+ get: function get(target, prop, receiver) {
62
+ if (prop === 'state') {
63
+ return stateProxy;
64
+ }
65
+ if (prop === 'dispatch') {
66
+ // Keep the transaction inside the preview. It was built against `previewDoc`, so
67
+ // applying it to the live state would corrupt the real document.
68
+ return function (tr) {
69
+ try {
70
+ stateProxy = stateProxy.apply(tr);
71
+ } catch (_unused2) {
72
+ // A preview is throwaway; a failed self-update is not worth surfacing.
73
+ }
74
+ };
75
+ }
76
+ var value = Reflect.get(target, prop, receiver);
77
+ return typeof value === 'function' ? value.bind(target) : value;
78
+ }
79
+ });
80
+ return {
81
+ editorProxy: editorProxy,
82
+ rootPos: rootPos
83
+ };
84
+ }
@@ -224,13 +224,14 @@ var resolveHoistedCodeBlockAnchor = exports.resolveHoistedCodeBlockAnchor = func
224
224
  * highlights — or, with `anchorAtRangeStart`, at `from` itself. A host inside a code block is
225
225
  * hoisted in front of it instead, anchored via `resolveHoistedCodeBlockAnchor` (EDITOR-9045).
226
226
  *
227
- * Returns one decoration normally, two (marker + tag) when hoisted out of a code block.
227
+ * Returns one decoration when a caller-supplied `anchorName` is used, two (marker + tag)
228
+ * otherwise — hoisted out of a code block, or anchored to its own point marker.
228
229
  *
229
230
  * The tag is mounted in `toDOM` and taken down in `destroy`, so it lives exactly as long as the host
230
231
  * element ProseMirror drew it into — see `mountContributorTag`.
231
232
  */
232
233
  var createContributorTagWidget = exports.createContributorTagWidget = function createContributorTagWidget(_ref4) {
233
- var _hoisted$anchorName, _hoisted$codeBlockSta;
234
+ var _hoisted$codeBlockSta, _ref5, _hoisted$anchorName;
234
235
  var _ref4$anchorAtRangeSt = _ref4.anchorAtRangeStart,
235
236
  anchorAtRangeStart = _ref4$anchorAtRangeSt === void 0 ? false : _ref4$anchorAtRangeSt,
236
237
  anchorName = _ref4.anchorName,
@@ -250,15 +251,33 @@ var createContributorTagWidget = exports.createContributorTagWidget = function c
250
251
  // A change inside a code block is hoisted out of it, anchored to its own marker; anything else
251
252
  // keeps its own anchor.
252
253
  var hoisted = resolveHoistedCodeBlockAnchor(doc, anchorPos, diffId);
253
- var effectiveAnchorName = (_hoisted$anchorName = hoisted === null || hoisted === void 0 ? void 0 : hoisted.anchorName) !== null && _hoisted$anchorName !== void 0 ? _hoisted$anchorName : anchorName;
254
+ // Keep the host out of the table row's grid (EDITOR-8442).
255
+ var hostPos = (0, _createAnchorDecorationWidgets.clampAnchorPosIntoCell)(doc, (_hoisted$codeBlockSta = hoisted === null || hoisted === void 0 ? void 0 : hoisted.codeBlockStart) !== null && _hoisted$codeBlockSta !== void 0 ? _hoisted$codeBlockSta : anchorPos, 1);
256
+
257
+ // Inline diffs pass no anchorName. Give them their own point anchor instead of the
258
+ // `position: relative` fallback, which drifts left once the range wraps a line (EDITOR-9286).
259
+ var ownAnchorName = (0, _decorationKeys.buildAnchorDecorationKey)({
260
+ diffId: diffId,
261
+ anchorType: _decorationKeys.AnchorTypeKey.tag
262
+ });
263
+ var effectiveAnchorName = (_ref5 = (_hoisted$anchorName = hoisted === null || hoisted === void 0 ? void 0 : hoisted.anchorName) !== null && _hoisted$anchorName !== void 0 ? _hoisted$anchorName : anchorName) !== null && _ref5 !== void 0 ? _ref5 : ownAnchorName;
254
264
  var tagAnchorName = effectiveAnchorName && supportsAnchorPositioning() ? effectiveAnchorName : undefined;
255
265
  var decorations = [];
256
266
  if (hoisted) {
257
267
  decorations.push(hoisted.marker);
268
+ } else if (!anchorName && tagAnchorName) {
269
+ decorations.push(_view.Decoration.widget(hostPos, function () {
270
+ return (0, _createAnchorDecorationWidgets.createAnchorNameSpan)(ownAnchorName);
271
+ }, _objectSpread(_objectSpread({}, (0, _decorationKeys.buildAnchorDecorationSpec)({
272
+ diffId: diffId,
273
+ anchorType: _decorationKeys.AnchorTypeKey.tag,
274
+ side: -1
275
+ })), {}, {
276
+ marks: [],
277
+ ignoreSelection: true
278
+ })));
258
279
  }
259
- decorations.push(_view.Decoration.widget(
260
- // Keep the host out of the table row's grid (EDITOR-8442).
261
- (0, _createAnchorDecorationWidgets.clampAnchorPosIntoCell)(doc, (_hoisted$codeBlockSta = hoisted === null || hoisted === void 0 ? void 0 : hoisted.codeBlockStart) !== null && _hoisted$codeBlockSta !== void 0 ? _hoisted$codeBlockSta : anchorPos, 1), function () {
280
+ decorations.push(_view.Decoration.widget(hostPos, function () {
262
281
  var host = buildContributorTagHost(diffId, tagAnchorName);
263
282
  mount = mountContributorTag({
264
283
  anchorName: tagAnchorName,
@@ -24,6 +24,21 @@ var _tableDiffMode = require("./utils/tableDiffMode");
24
24
  var _wrapBlockNodeView = require("./utils/wrapBlockNodeView");
25
25
  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; }
26
26
  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) { (0, _defineProperty2.default)(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; }
27
+ /**
28
+ * Where a slice's `index`th child sits in `doc`: the children belong to the ancestor `openStart`
29
+ * levels above the change. `null` if out of range — `posAtIndex` throws past the last child.
30
+ */
31
+ var getSourcePos = function getSourcePos(doc, $changeFrom, openStart, index) {
32
+ var depth = $changeFrom ? $changeFrom.depth - openStart : -1;
33
+ if (!$changeFrom || depth < 0) {
34
+ return null;
35
+ }
36
+ var childIndex = $changeFrom.index(depth) + index;
37
+ if (childIndex >= $changeFrom.node(depth).childCount) {
38
+ return null;
39
+ }
40
+ return (0, _safeResolve.safeResolve)(doc, $changeFrom.posAtIndex(childIndex, depth));
41
+ };
27
42
  var isHeadingLevel = function isHeadingLevel(level) {
28
43
  return typeof level === 'number' && level >= 1 && level <= 6;
29
44
  };
@@ -291,7 +306,7 @@ var createNodeChangedDecorationWidget = exports.createNodeChangedDecorationWidge
291
306
  * and if it's the first or last content, we go in however many the sliced Open
292
307
  * or sliced End depth is and match only the entire node.
293
308
  */
294
- slice.content.forEach(function (node) {
309
+ slice.content.forEach(function (node, _offset, index) {
295
310
  var isFirst = firstReplacedNode === node;
296
311
  var isLast = lastReplacedNode === node;
297
312
  var isOpenAtSliceBoundary = isFirst && slice.openStart > 0 || isLast && slice.openEnd > 0;
@@ -362,7 +377,8 @@ var createNodeChangedDecorationWidget = exports.createNodeChangedDecorationWidge
362
377
  }
363
378
 
364
379
  // Try to create node view, fallback to serialization
365
- var nodeView = serializer.tryCreateNodeView(node);
380
+ var $sourcePos = (0, _fg.fg)('platform_editor_ai_show_diff_patch_2') ? getSourcePos(doc, $changeFromA, slice.openStart, index) : null;
381
+ var nodeView = serializer.tryCreateNodeView(node, 0, $sourcePos !== null && $sourcePos !== void 0 ? $sourcePos : undefined);
366
382
  if (nodeView) {
367
383
  if (node.isInline) {
368
384
  var wrapper = (0, _wrapBlockNodeView.createContentWrapper)(colorScheme, isActive, isInserted, reveal);
@@ -465,18 +481,27 @@ var createNodeChangedDecorationWidget = exports.createNodeChangedDecorationWidge
465
481
  });
466
482
  }
467
483
 
468
- // Needed even when the indicator bar is off, because a contributor tag also anchors against the
469
- // widget.
484
+ // Needed even with the indicator bar off, since it anchors its top/bottom to the widget.
470
485
  if (showIndicators || showContributorTags) {
471
486
  dom.style.setProperty('anchor-name', "--".concat((0, _decorationKeys.buildAnchorDecorationKey)({
472
487
  diffId: diffId
473
488
  })));
474
- // The tag built below only anchors via `anchor()` if `contributorTagAnchorName` is set; without
475
- // it the tag falls back to a non-anchored position (EDITOR-9045).
476
- if ((0, _fg.fg)('confluence_ncs_step_diffing_version_history')) {
477
- contributorTagAnchorName !== null && contributorTagAnchorName !== void 0 ? contributorTagAnchorName : contributorTagAnchorName = (0, _decorationKeys.buildAnchorDecorationKey)({
478
- diffId: diffId
489
+ // `dom` is inline, so its box fragments across a line wrap and `anchor()` resolves to the
490
+ // wrong edge. The tag anchors to its own point marker instead (EDITOR-9286).
491
+ if ((0, _fg.fg)('confluence_ncs_step_diffing_version_history') && !contributorTagAnchorName) {
492
+ var tagPointAnchorName = (0, _decorationKeys.buildAnchorDecorationKey)({
493
+ diffId: diffId,
494
+ anchorType: _decorationKeys.AnchorTypeKey.tag
479
495
  });
496
+ contributorTagAnchorName = tagPointAnchorName;
497
+ decorations.push(_view.Decoration.widget(safeInsertPos, function () {
498
+ return (0, _createAnchorDecorationWidgets.createAnchorNameSpan)(tagPointAnchorName);
499
+ }, {
500
+ key: "contributor-tag-anchor-".concat(diffId),
501
+ side: -1,
502
+ marks: [],
503
+ ignoreSelection: true
504
+ }));
480
505
  }
481
506
  }
482
507
 
@@ -511,8 +536,7 @@ var createNodeChangedDecorationWidget = exports.createNodeChangedDecorationWidge
511
536
  }
512
537
  })));
513
538
  } else {
514
- // Hosted at the start of the deleted run rather than on `dom` itself: `dom` is inline, so once
515
- // the deleted content wraps its box is the union of its line fragments.
539
+ // Anchored to the point marker pushed above, not to `dom` (EDITOR-9286).
516
540
  var tagHost = (0, _createContributorTagWidget.createContributorTagHost)({
517
541
  anchorName: contributorTagAnchorName,
518
542
  diffId: diffId,
@@ -321,6 +321,21 @@ var applyTextLikeBlockNodeStyles = function applyTextLikeBlockNodeStyles(_ref8)
321
321
  /**
322
322
  * Creates a content wrapper with deleted styles for a block node
323
323
  */
324
+ /**
325
+ * The element that spans breakout width, for nodes carrying a `breakout` mark. The wrapper divs
326
+ * stay at editor-column width, so anything that should align to the node's visible edges —
327
+ * outlines, absolutely positioned lozenges — has to attach here instead.
328
+ */
329
+ var findBreakoutElement = function findBreakoutElement(targetNode, nodeView) {
330
+ var _targetNode$marks;
331
+ var hasBreakoutMark = (_targetNode$marks = targetNode.marks) === null || _targetNode$marks === void 0 ? void 0 : _targetNode$marks.some(function (mark) {
332
+ return mark.type.name === 'breakout';
333
+ });
334
+ if (!hasBreakoutMark || !(nodeView instanceof HTMLElement)) {
335
+ return null;
336
+ }
337
+ return nodeView.matches('[data-node-type]') ? nodeView : nodeView.querySelector('[data-node-type]');
338
+ };
324
339
  var createBlockNodeContentWrapper = function createBlockNodeContentWrapper(_ref9) {
325
340
  var nodeView = _ref9.nodeView,
326
341
  targetNode = _ref9.targetNode,
@@ -338,7 +353,16 @@ var createBlockNodeContentWrapper = function createBlockNodeContentWrapper(_ref9
338
353
  // should not also get the inline content style (borderBottom underline) on their container.
339
354
  var shouldSkipContentStyle = isInserted && nodeStyle !== undefined;
340
355
  var contentStyle = shouldSkipContentStyle ? '' : (0, _wrapBlockNodeViewStyles.getChangedContentStyle)(colorScheme, isActive, isInserted, hideAddedDiffsUnderline);
341
- contentWrapper.setAttribute('style', "".concat(contentStyle).concat(nodeStyle || ''));
356
+
357
+ // A `breakout` node renders wider than the editor column while the wrapper divs
358
+ // stay at column width, so paint the outline on the node element that spans that width.
359
+ var breakoutTarget = nodeStyle ? findBreakoutElement(targetNode, nodeView) : null;
360
+ if ((0, _fg.fg)('platform_editor_ai_show_diff_patch_2') && breakoutTarget) {
361
+ appendStyleToElement(breakoutTarget, nodeStyle !== null && nodeStyle !== void 0 ? nodeStyle : '');
362
+ contentWrapper.setAttribute('style', contentStyle || '');
363
+ } else {
364
+ contentWrapper.setAttribute('style', "".concat(contentStyle).concat(nodeStyle || ''));
365
+ }
342
366
  contentWrapper.append(nodeView);
343
367
  return contentWrapper;
344
368
  };
@@ -468,7 +492,19 @@ var wrapBlockNode = function wrapBlockNode(_ref10) {
468
492
  })) {
469
493
  return;
470
494
  }
471
- blockWrapper.append(lozenge);
495
+
496
+ // `lozenge` is absolutely positioned, so it lands on the nearest positioned ancestor. For a
497
+ // breakout node that is the column-width wrapper, leaving the pill short of the node's
498
+ // right edge — host it on the breakout element, which must establish a containing block.
499
+ var breakoutHost = (0, _fg.fg)('platform_editor_ai_show_diff_patch_2') ? findBreakoutElement(targetNode, nodeView) : null;
500
+ if (breakoutHost) {
501
+ appendStyleToElement(breakoutHost, (0, _lazyNodeView.convertToInlineCss)({
502
+ position: 'relative'
503
+ }));
504
+ breakoutHost.append(lozenge);
505
+ } else {
506
+ blockWrapper.append(lozenge);
507
+ }
472
508
  }
473
509
  var contentWrapper = createBlockNodeContentWrapper({
474
510
  nodeView: nodeView,
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.wrapInMarkViews = void 0;
7
+ var _model = require("@atlaskit/editor-prosemirror/model");
8
+ var _fg = require("@atlaskit/platform-feature-flags/fg");
9
+ /**
10
+ * `@atlaskit/editor-prosemirror/view` doesn't re-export prosemirror-view's `MarkViewConstructor`,
11
+ * so it's typed locally, as `EditorSSRRenderer` does.
12
+ */
13
+
14
+ /** Where to look up a mark's renderer, and the view to render it against. */
15
+
16
+ /**
17
+ * Renders a mark as ProseMirror does: `view.nodeViews` also holds mark views, keyed by mark name,
18
+ * so `breakout` resolves whether it's registered under `markViews` or `nodeViews`. Falls back to
19
+ * `toDOM` — they can differ: `breakout` renders `.fabric-editor-breakout-mark-dom`, which the width
20
+ * CSS targets.
21
+ */
22
+ var renderMarkView = function renderMarkView(mark, inline, _ref) {
23
+ var nodeViews = _ref.nodeViews,
24
+ view = _ref.view;
25
+ try {
26
+ var markView = nodeViews === null || nodeViews === void 0 ? void 0 : nodeViews[mark.type.name];
27
+ if (markView && view) {
28
+ var rendered = markView(mark, view, inline);
29
+ return {
30
+ dom: rendered.dom,
31
+ contentDOM: rendered.contentDOM
32
+ };
33
+ }
34
+ var toDOM = mark.type.spec.toDOM;
35
+ if (!toDOM) {
36
+ return null;
37
+ }
38
+ return _model.DOMSerializer.renderSpec(document, toDOM(mark, inline));
39
+ } catch (_unused) {
40
+ return null;
41
+ }
42
+ };
43
+
44
+ /**
45
+ * Wraps rendered DOM in its marks, as ProseMirror does for the live document. Without this a
46
+ * block node loses `breakout`, whose wrapper supplies the width. Marks nest
47
+ * outside-in, so `marks[0]` is outermost.
48
+ */
49
+ var wrapInMarkViews = exports.wrapInMarkViews = function wrapInMarkViews(targetNode, nodeDom, sources) {
50
+ if (targetNode.marks.length === 0 || !(0, _fg.fg)('platform_editor_ai_show_diff_patch_2')) {
51
+ return nodeDom;
52
+ }
53
+ var wrapped = nodeDom;
54
+ for (var index = targetNode.marks.length - 1; index >= 0; index--) {
55
+ var _mark = targetNode.marks[index];
56
+ var rendered = renderMarkView(_mark, targetNode.isInline, sources);
57
+ // No content hole means nowhere to nest the node, so skip the mark, not the node.
58
+ if (!(rendered !== null && rendered !== void 0 && rendered.contentDOM)) {
59
+ continue;
60
+ }
61
+ rendered.contentDOM.appendChild(wrapped);
62
+ wrapped = rendered.dom;
63
+ }
64
+ return wrapped;
65
+ };
@@ -30,10 +30,10 @@ var SHOW_DIFF_AGENT_BRANDS = {
30
30
 
31
31
  /**
32
32
  * Brands that prefer the account's real avatar over their fixed glyph when the invoking profile
33
- * has one. Claude/ChatGPT/Rovo intentionally keep their fixed glyph always (see `AGENT_KIND_ICONS`
34
- * in `contributorAvatarRenderer.ts`).
33
+ * has one. Claude/ChatGPT intentionally keep their fixed glyph always (see `AGENT_KIND_ICONS` in
34
+ * `contributorAvatarRenderer.ts`).
35
35
  */
36
- var AVATAR_ELIGIBLE_BRANDS = new Set(['figma', 'lovable', 'replit']);
36
+ var AVATAR_ELIGIBLE_BRANDS = new Set(['figma', 'lovable', 'replit', 'rovo']);
37
37
 
38
38
  /** Known external agent types emitted by Confluence's agent identification service that carry
39
39
  * no `@atlaskit/agent-color` brand — a generic labelled fallback. */
@@ -10,25 +10,24 @@ var _default = require("@atlaskit/avatar/constants/default");
10
10
  var _lazyNodeView = require("@atlaskit/editor-common/lazy-node-view");
11
11
  var _contributorTagIcons = require("./contributorTagIcons");
12
12
  /**
13
- * Agent kinds with a custom contributor-tag icon. Figma/Lovable/Replit have no entry here — they
14
- * always render the real avatar `resolveDiffContributors` supplies, falling back to the generic
15
- * `aiAgent` glyph (see `showContributorIcon`) on load failure. The declared type stays the wider
13
+ * Agent kinds with a custom contributor-tag icon, used as a fallback glyph when no `avatarUrl` is
14
+ * available or its image fails to load (see `showContributorIcon`). Rovo/Figma/Lovable/Replit have
15
+ * no entry here — they fall back to the generic `aiAgent` glyph. The declared type stays the wider
16
16
  * `TagContributor['agentKind']` so indexing below (with `'identified' | 'external'` in play too)
17
17
  * stays safely partial.
18
18
  */
19
19
  var AGENT_KIND_ICONS = {
20
20
  claude: 'claude',
21
- chatgpt: 'chatgpt',
22
- rovo: 'rovoHex'
21
+ chatgpt: 'chatgpt'
23
22
  };
24
23
 
25
24
  /**
26
25
  * Brands that always render their fixed glyph, even when the contributor also carries an
27
- * `avatarUrl`. Figma/Lovable/Replit are excluded here and use the real avatar when
26
+ * `avatarUrl`. Rovo/Figma/Lovable/Replit are excluded here and use the real avatar when
28
27
  * `resolveDiffContributors` supplies one, falling back to the generic `aiAgent` glyph on load
29
28
  * failure.
30
29
  */
31
- var FIXED_GLYPH_AGENT_KINDS = new Set(['claude', 'chatgpt', 'rovo']);
30
+ var FIXED_GLYPH_AGENT_KINDS = new Set(['claude', 'chatgpt']);
32
31
 
33
32
  /** The size `@atlaskit/avatar`'s `xxsmall` rendered at. */
34
33
  var AVATAR_SIZE = _avatarSizes.AVATAR_SIZES.xxsmall;
@@ -125,8 +124,8 @@ var contributorAvatarRenderer = exports.contributorAvatarRenderer = function con
125
124
  };
126
125
  if (contributor.avatarUrl) {
127
126
  var _contributor$agentKin2;
128
- // Claude/ChatGPT/Rovo always use their fixed glyph, even when the profile also has an
129
- // avatar URL. Other agents (e.g. Figma/Lovable/Replit) and users prefer the real avatar.
127
+ // Claude/ChatGPT always use their fixed glyph, even when the profile also has an avatar
128
+ // URL. Other agents (e.g. Rovo/Figma/Lovable/Replit) and users prefer the real avatar.
130
129
  var usesFixedGlyph = isAgent && FIXED_GLYPH_AGENT_KINDS.has((_contributor$agentKin2 = contributor.agentKind) !== null && _contributor$agentKin2 !== void 0 ? _contributor$agentKin2 : 'external');
131
130
  if (usesFixedGlyph) {
132
131
  showContributorIcon();
@@ -10,9 +10,6 @@ exports.getContributorTagIcon = void 0;
10
10
  * Neither `@atlaskit/icon`, `@atlaskit/icon-lab` nor `@atlaskit/logo` exposes a raw-SVG entry
11
11
  * point, so the markup is copied here rather than kept as a React island for these glyphs. Keep in
12
12
  * sync with the sources named on each constant.
13
- *
14
- * The Rovo hex is the `brand` appearance, whose `--rovo-*-color` variables resolve to `initial` in
15
- * both themes — the fallbacks below are the rendered colours, so no theme observer is needed.
16
13
  */
17
14
 
18
15
  /** `@atlaskit/icon/core/person`, at the 16px the avatar renders it. */
@@ -28,9 +25,6 @@ var AI_BOT_ICON_SVG = "<svg width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0
28
25
  */
29
26
  var AI_AGENT_ICON_SVG = "<svg width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 16 16\" role=\"presentation\" style=\"display:block\"><path fill=\"currentcolor\" fill-rule=\"evenodd\" d=\"M6.971.493a2 2 0 0 1 2.058 0l5 3A2 2 0 0 1 15 5.208v5.584a2 2 0 0 1-.971 1.715l-5 3a2 2 0 0 1-2.058 0l-5-3A2 2 0 0 1 1 10.792V5.208a2 2 0 0 1 .971-1.715zm1.286 1.286a.5.5 0 0 0-.514 0l-5 3a.5.5 0 0 0-.243.429v5.584a.5.5 0 0 0 .243.429l1.098.66a4.25 4.25 0 1 1 8.318 0l1.098-.66a.5.5 0 0 0 .243-.429V5.208a.5.5 0 0 0-.243-.429zM8 8.25a2.75 2.75 0 1 0 0 5.5 2.75 2.75 0 0 0 0-5.5\" clip-rule=\"evenodd\"/></svg>";
30
27
 
31
- /** `RovoHexIcon` from `@atlaskit/logo/rovo-hex/icon`, at the 16px `xxsmall` renders. */
32
- var ROVO_HEX_ICON_SVG = "<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" role=\"presentation\" style=\"display:block\"><path fill=\"#1868db\" fill-rule=\"evenodd\" d=\"m13.227 16.789-7.154 4.169-3.403-1.96a2.72 2.72 0 0 1-1.362-2.355V7.366c0-.973.518-1.87 1.361-2.354l6.044-3.487-.038.114a3.6 3.6 0 0 0-.172 1.088v9.278c0 1.274.68 2.45 1.786 3.085z\" clip-rule=\"evenodd\"/><path fill=\"#6a9a23\" fill-rule=\"evenodd\" d=\"m11.296 15.671-7.193 4.153 6.607 3.812a2.73 2.73 0 0 0 2.676.026l.048-.033a2.73 2.73 0 0 0 1.232-1.525 2.7 2.7 0 0 0 .127-.822v-3.594z\" clip-rule=\"evenodd\"/><path fill=\"#af59e1\" fill-rule=\"evenodd\" d=\"m21.477 5.003-4.404-2.539-5.904 4.89 2.69 1.556a3.56 3.56 0 0 1 1.785 3.086v9.277a3.6 3.6 0 0 1-.21 1.202l6.044-3.486a2.71 2.71 0 0 0 1.362-2.355V7.357c0-.97-.521-1.87-1.363-2.354\" clip-rule=\"evenodd\"/><path fill=\"#fca700\" fill-rule=\"evenodd\" d=\"M12.74 8.266 9.353 6.312V2.718c0-.283.044-.56.127-.821A2.73 2.73 0 0 1 10.71.372V.37a.4.4 0 0 0 .048-.033 2.73 2.73 0 0 1 2.676.026l6.499 3.75z\" clip-rule=\"evenodd\"/></svg>";
33
-
34
28
  /** Claude hexagon lockup from the brand asset: the hexagon and knot glyph both baked in. */
35
29
  var CLAUDE_ICON_SVG = "<svg width=\"16\" height=\"18\" viewBox=\"0 0 16 18\" fill=\"none\" role=\"presentation\" style=\"display:block\"><path fill=\"#D97757\" d=\"M14.5007 3.28903L9.50384 0.402344C8.5736 -0.13506 7.42701 -0.134016 6.49774 0.405082L1.49458 3.30759C0.569434 3.8443 0 4.83297 0 5.90253V11.1804C0 12.2519 0.571497 13.2421 1.49931 13.7781L6.49931 16.6666C7.42787 17.203 8.57213 17.203 9.50069 16.6666L14.5007 13.7781C15.4285 13.2421 16 12.2519 16 11.1804V5.88671C16 4.8152 15.4285 3.82504 14.5007 3.28903Z\"/><path fill=\"#FAF9F6\" d=\"M5.19791 10.055L7.0123 9.03681L7.04267 8.94806L7.0123 8.89907H6.92357L6.62 8.88034L5.58321 8.85232L4.68418 8.815L3.81317 8.76826L3.59367 8.72158L3.38818 8.4507L3.4092 8.31526L3.59367 8.1915L3.85754 8.21485L4.44133 8.25455L5.317 8.31526L5.95216 8.35262L6.89321 8.4507H7.04267L7.06368 8.38999L7.0123 8.35262L6.97261 8.31526L6.06658 7.70112L5.08582 7.05196L4.5721 6.67834L4.29422 6.48919L4.15411 6.31172L4.09339 5.92409L4.34559 5.64621L4.68418 5.66956L4.77058 5.69291L5.11384 5.95678L5.84708 6.52422L6.80448 7.22943L6.94459 7.34618L7.00063 7.30649L7.00763 7.27846L6.94459 7.17338L6.42385 6.23233L5.86809 5.27492L5.62057 4.87795L5.55519 4.63977C5.53183 4.54169 5.51549 4.45996 5.51549 4.35956L5.80271 3.96959L5.9615 3.91821L6.34446 3.96959L6.50558 4.10969L6.74377 4.65378L7.12907 5.51077L7.72686 6.676L7.90199 7.0216L7.99539 7.34152L8.0304 7.43959H8.09113V7.38354L8.14018 6.72737L8.23124 5.92175L8.31999 4.88496L8.35032 4.59307L8.49511 4.24279L8.78232 4.05365L9.00649 4.16107L9.19098 4.42494L9.16533 4.5954L9.05555 5.30762L8.84073 6.42381L8.70062 7.17105H8.78232L8.87574 7.07764L9.25401 6.57559L9.88922 5.78165L10.1694 5.4664L10.4963 5.11847L10.7065 4.95267H11.1035L11.3953 5.38701L11.2646 5.83535L10.8559 6.35375L10.5173 6.79276L10.0316 7.4466L9.72807 7.96966L9.7561 8.0117L9.82849 8.00469L10.926 7.77118L11.5191 7.66376L12.2266 7.54233L12.5466 7.69178L12.5816 7.84356L12.4555 8.15414L11.6989 8.34095L10.8116 8.51842L9.48985 8.83136L9.4735 8.84303L9.49223 8.86637L10.0877 8.92241L10.3422 8.93639H10.9657L12.1262 9.02283L12.4298 9.2236L12.612 9.46881L12.5816 9.6556L12.1146 9.89382L11.4841 9.74434L10.013 9.3941L9.50858 9.26797H9.43849V9.31004L9.85882 9.721L10.6294 10.4169L11.5938 11.3136L11.6429 11.5354L11.5191 11.7106L11.3884 11.6918L10.5407 11.0544L10.2137 10.7672L9.4735 10.1436H9.42451V10.209L9.59495 10.4589L10.4963 11.8133L10.543 12.2289L10.4776 12.3644L10.2441 12.4461L9.98726 12.3994L9.45952 11.6592L8.91544 10.8255L8.47645 10.0783L8.42271 10.1086L8.16352 12.8991L8.04207 13.0416L7.76188 13.149L7.52837 12.9715L7.40461 12.6843L7.52837 12.1168L7.67781 11.3766L7.79925 10.7881L7.90899 10.0573L7.97438 9.81443L7.96971 9.79808L7.916 9.80507L7.36491 10.5617L6.5266 11.6942L5.86342 12.4041L5.70463 12.4671L5.42909 12.3247L5.45477 12.0702L5.60889 11.8436L6.5266 10.6761L7.08002 9.95217L7.4373 9.53421L7.43497 9.47349H7.41395L4.97607 11.0567L4.54174 11.1127L4.35493 10.9376L4.37828 10.6504L4.46702 10.557L5.20024 10.0526L5.19791 10.055Z\"/></svg>";
36
30
 
@@ -41,8 +35,7 @@ var markup = {
41
35
  aiBot: AI_BOT_ICON_SVG,
42
36
  chatgpt: CHATGPT_ICON_SVG,
43
37
  claude: CLAUDE_ICON_SVG,
44
- person: PERSON_ICON_SVG,
45
- rovoHex: ROVO_HEX_ICON_SVG
38
+ person: PERSON_ICON_SVG
46
39
  };
47
40
 
48
41
  /**