@atlaskit/editor-plugin-show-diff 20.0.10 → 21.0.0
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 +26 -0
- package/dist/cjs/pm-plugins/NodeViewSerializer.js +71 -7
- package/dist/cjs/pm-plugins/calculateDiff/attrAwareTokenEncoder.js +9 -7
- package/dist/cjs/pm-plugins/createEditorProxy.js +84 -0
- package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +25 -6
- package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +35 -11
- package/dist/cjs/pm-plugins/decorations/utils/excerptIncludeAttrs.js +27 -0
- package/dist/cjs/pm-plugins/decorations/utils/getAttrChangeRanges.js +7 -0
- package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeView.js +38 -2
- package/dist/cjs/pm-plugins/markViews.js +65 -0
- package/dist/es2019/pm-plugins/NodeViewSerializer.js +64 -8
- package/dist/es2019/pm-plugins/calculateDiff/attrAwareTokenEncoder.js +9 -7
- package/dist/es2019/pm-plugins/createEditorProxy.js +76 -0
- package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +26 -8
- package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +35 -12
- package/dist/es2019/pm-plugins/decorations/utils/excerptIncludeAttrs.js +13 -0
- package/dist/es2019/pm-plugins/decorations/utils/getAttrChangeRanges.js +10 -1
- package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeView.js +36 -2
- package/dist/es2019/pm-plugins/markViews.js +61 -0
- package/dist/esm/pm-plugins/NodeViewSerializer.js +71 -7
- package/dist/esm/pm-plugins/calculateDiff/attrAwareTokenEncoder.js +9 -7
- package/dist/esm/pm-plugins/createEditorProxy.js +78 -0
- package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +27 -8
- package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +37 -12
- package/dist/esm/pm-plugins/decorations/utils/excerptIncludeAttrs.js +20 -0
- package/dist/esm/pm-plugins/decorations/utils/getAttrChangeRanges.js +7 -0
- package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeView.js +38 -2
- package/dist/esm/pm-plugins/markViews.js +60 -0
- package/dist/types/pm-plugins/NodeViewSerializer.d.ts +7 -2
- package/dist/types/pm-plugins/createEditorProxy.d.ts +18 -0
- package/dist/types/pm-plugins/decorations/createContributorTagWidget.d.ts +2 -1
- package/dist/types/pm-plugins/decorations/utils/excerptIncludeAttrs.d.ts +6 -0
- package/dist/types/pm-plugins/markViews.d.ts +14 -0
- package/package.json +13 -10
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { expandedState } from '@atlaskit/editor-common/expand';
|
|
1
2
|
import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
|
|
3
|
+
import { contains } from '@atlaskit/editor-prosemirror/utils';
|
|
2
4
|
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
|
|
5
|
+
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
6
|
+
import { createEditorProxy } from './createEditorProxy';
|
|
7
|
+
import { wrapInMarkViews } from './markViews';
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* Utilities for working with ProseMirror node views and DOM serialization within the
|
|
@@ -28,6 +33,18 @@ export function isEditorViewWithNodeViews(view) {
|
|
|
28
33
|
return view.nodeViews !== undefined;
|
|
29
34
|
}
|
|
30
35
|
|
|
36
|
+
/** Expand-family nodes whose expanded state lives in a node-keyed WeakMap. */
|
|
37
|
+
const EXPAND_TYPES = new Set(['expand', 'nestedExpand']);
|
|
38
|
+
|
|
39
|
+
/** Tables are the only node views that read and write positions, so only they need a preview. */
|
|
40
|
+
const containsTable = node => {
|
|
41
|
+
const {
|
|
42
|
+
table
|
|
43
|
+
} = node.type.schema.nodes;
|
|
44
|
+
// `contains` only checks descendants, so the node itself is checked separately.
|
|
45
|
+
return !!table && (node.type === table || contains(node, table));
|
|
46
|
+
};
|
|
47
|
+
|
|
31
48
|
/**
|
|
32
49
|
* Encapsulates DOM serialization and node view access/creation.
|
|
33
50
|
*
|
|
@@ -66,12 +83,17 @@ export class NodeViewSerializer {
|
|
|
66
83
|
/**
|
|
67
84
|
* Appends serialized child nodes to the given contentDOM element.
|
|
68
85
|
*/
|
|
69
|
-
appendChildNodes(children, contentDOM) {
|
|
86
|
+
appendChildNodes(children, contentDOM, basePos = 0, editorProxy) {
|
|
87
|
+
// A node's first child sits one position inside it, and each subsequent
|
|
88
|
+
// child is offset by the previous child's `nodeSize`. Tracking this lets nested node
|
|
89
|
+
// views resolve to their real depth instead of every one of them seeing depth 0.
|
|
90
|
+
let childPos = basePos + 1;
|
|
70
91
|
children.forEach(child => {
|
|
71
|
-
const childNode = this.
|
|
92
|
+
const childNode = this.tryCreateNodeViewInner(child, childPos, editorProxy) || this.serializeNode(child);
|
|
72
93
|
if (childNode) {
|
|
73
94
|
contentDOM === null || contentDOM === void 0 ? void 0 : contentDOM.append(childNode);
|
|
74
95
|
}
|
|
96
|
+
childPos += child.nodeSize;
|
|
75
97
|
});
|
|
76
98
|
}
|
|
77
99
|
|
|
@@ -80,8 +102,20 @@ export class NodeViewSerializer {
|
|
|
80
102
|
*
|
|
81
103
|
* Returns `null` when there is no `EditorView`, no constructor for the node type,
|
|
82
104
|
* or the node type is blocklisted. Otherwise returns the constructed node view instance.
|
|
105
|
+
*
|
|
106
|
+
* `$sourcePos` is where the node sits in the doc it came from, so nested tables keep their depth.
|
|
83
107
|
*/
|
|
84
|
-
tryCreateNodeView(targetNode) {
|
|
108
|
+
tryCreateNodeView(targetNode, basePos = 0, $sourcePos) {
|
|
109
|
+
var _preview$rootPos;
|
|
110
|
+
if (!this.editorView) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
// The preview becomes the document every nested node view resolves its position against,
|
|
114
|
+
// so positions stay internally consistent.
|
|
115
|
+
const preview = fg('platform_editor_ai_show_diff_patch_2') && containsTable(targetNode) ? createEditorProxy(this.editorView, targetNode, $sourcePos) : null;
|
|
116
|
+
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);
|
|
117
|
+
}
|
|
118
|
+
tryCreateNodeViewInner(targetNode, basePos = 0, editorProxy) {
|
|
85
119
|
var _this$nodeViews;
|
|
86
120
|
if (!this.editorView) {
|
|
87
121
|
return null;
|
|
@@ -129,22 +163,44 @@ export class NodeViewSerializer {
|
|
|
129
163
|
if (!isExperimentEnabled('platform_editor_show_diff_deleted_nodeview_content') && targetNode.type.name === 'paragraph' && targetNode.children.length === 1) {
|
|
130
164
|
return this.serializeFragment(targetNode.content);
|
|
131
165
|
}
|
|
132
|
-
this.appendChildNodes(targetNode.children, contentDOM);
|
|
166
|
+
this.appendChildNodes(targetNode.children, contentDOM, basePos, editorProxy);
|
|
133
167
|
}
|
|
134
|
-
return dom;
|
|
168
|
+
return this.withMarkViews(targetNode, dom, editorProxy);
|
|
135
169
|
}
|
|
170
|
+
|
|
171
|
+
// The expand node view reads `expandedState.get(node) ?? false`, never `attrs.__expanded`.
|
|
172
|
+
// Open the preview so its tables get real layout; an existing entry is the live expand's
|
|
173
|
+
// own state, so leave it alone.
|
|
174
|
+
if (EXPAND_TYPES.has(targetNode.type.name) && !expandedState.has(targetNode) && fg('platform_editor_ai_show_diff_patch_2')) {
|
|
175
|
+
expandedState.set(targetNode, true);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// `isTableNested` is `doc.resolve(pos).depth > 0`, so the old hardcoded `0` made nested
|
|
179
|
+
// tables size as top-level. Positions are resolved against the preview document, so
|
|
180
|
+
// they are only meaningful with an editor proxy.
|
|
181
|
+
const view = editorProxy !== null && editorProxy !== void 0 ? editorProxy : this.editorView;
|
|
182
|
+
const docSize = view.state.doc.content.size;
|
|
183
|
+
const resolvedPos = editorProxy ? Math.max(0, Math.min(basePos, docSize)) : 0;
|
|
136
184
|
const {
|
|
137
185
|
dom,
|
|
138
186
|
contentDOM
|
|
139
|
-
} = constructor(targetNode,
|
|
187
|
+
} = constructor(targetNode, view, () => resolvedPos, [], {});
|
|
140
188
|
// Iteratively populate children
|
|
141
|
-
this.appendChildNodes(targetNode.children, contentDOM);
|
|
142
|
-
return dom;
|
|
189
|
+
this.appendChildNodes(targetNode.children, contentDOM, basePos, editorProxy);
|
|
190
|
+
return this.withMarkViews(targetNode, dom, editorProxy);
|
|
143
191
|
} catch {
|
|
144
192
|
return null;
|
|
145
193
|
}
|
|
146
194
|
}
|
|
147
195
|
|
|
196
|
+
/** Wraps rendered DOM in its marks, rendered against the editor proxy when there is one. */
|
|
197
|
+
withMarkViews(targetNode, nodeDom, editorProxy) {
|
|
198
|
+
return wrapInMarkViews(targetNode, nodeDom, {
|
|
199
|
+
nodeViews: this.nodeViews,
|
|
200
|
+
view: editorProxy !== null && editorProxy !== void 0 ? editorProxy : this.editorView
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
148
204
|
/**
|
|
149
205
|
* Serializes a node to a DOM `Node` using the schema's `DOMSerializer`.
|
|
150
206
|
*/
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getBaseNodeTypeName } from '@atlaskit/editor-common/utils/node-type-utils';
|
|
2
|
+
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
2
3
|
import { getDiffableAttrNames } from '../decorations/utils/diffableAttrs';
|
|
4
|
+
import { getComparableExcerptIncludeAttrs, isExcerptInclude } from '../decorations/utils/excerptIncludeAttrs';
|
|
3
5
|
import { encodeCharacterWithMarks } from './encodeCharacterWithMarks';
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -11,14 +13,12 @@ import { encodeCharacterWithMarks } from './encodeCharacterWithMarks';
|
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
// Stable composite token; attr names are pre-ordered by the caller.
|
|
14
|
-
const encodeNodeWithAttrs = (
|
|
15
|
-
var _node$attrs;
|
|
16
|
-
const attrs = (_node$attrs = node.attrs) !== null && _node$attrs !== void 0 ? _node$attrs : {};
|
|
16
|
+
const encodeNodeWithAttrs = (nodeTypeName, attrs, attrNames) => {
|
|
17
17
|
const parts = attrNames.map(name => {
|
|
18
18
|
var _attrs$name;
|
|
19
19
|
return `${name}=${JSON.stringify((_attrs$name = attrs[name]) !== null && _attrs$name !== void 0 ? _attrs$name : null)}`;
|
|
20
20
|
});
|
|
21
|
-
return `${
|
|
21
|
+
return `${nodeTypeName}|${parts.join('|')}`;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
// Like the library default, but node types with a `diffableAttrs` rule fold their
|
|
@@ -27,11 +27,13 @@ const encodeNodeWithAttrs = (node, attrNames) => {
|
|
|
27
27
|
export const attrAwareTokenEncoder = {
|
|
28
28
|
encodeCharacter: (char, marks) => encodeCharacterWithMarks(char, marks),
|
|
29
29
|
encodeNodeStart: node => {
|
|
30
|
-
|
|
30
|
+
// A one-sided reference is not comparable from an isolated token. Attribute-step comparison
|
|
31
|
+
// checks the reference when both versions have one.
|
|
32
|
+
const attrs = fg('platform_editor_normalize_excerpt_diff') && node.type.name === 'extension' && isExcerptInclude(node.attrs) ? getComparableExcerptIncludeAttrs(node.attrs) : node.attrs;
|
|
31
33
|
// Normalise variants (e.g. `panel_c1` → `panel`) so `diffableAttrs` needs one entry.
|
|
32
|
-
const attrNames = getDiffableAttrNames(getBaseNodeTypeName(node.type),
|
|
34
|
+
const attrNames = getDiffableAttrNames(getBaseNodeTypeName(node.type), attrs !== null && attrs !== void 0 ? attrs : {});
|
|
33
35
|
if (attrNames && attrNames.length > 0) {
|
|
34
|
-
return encodeNodeWithAttrs(node, attrNames);
|
|
36
|
+
return encodeNodeWithAttrs(node.type.name, attrs, attrNames);
|
|
35
37
|
}
|
|
36
38
|
return node.type.name;
|
|
37
39
|
},
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { GapCursorSelection, Side } from '@atlaskit/editor-common/selection/gap-cursor/selection';
|
|
2
|
+
/**
|
|
3
|
+
* A preview is rendered by the live view's own node views, which read `getPos()` against
|
|
4
|
+
* `view.state.doc` for both layout (`isTableNested`) and writes (`scaleTable` builds attribute
|
|
5
|
+
* steps at `getPos() + 1`). Handing them live positions makes the layout correct but points the
|
|
6
|
+
* writes at unrelated live nodes, so give them a view whose document *is* the preview instead.
|
|
7
|
+
*
|
|
8
|
+
* With `$sourcePos` (the node's position in the doc it came from) the node is swapped into a copy
|
|
9
|
+
* of its real top-level ancestor, so a table inside an expand still resolves as nested.
|
|
10
|
+
*
|
|
11
|
+
* Returns `null` when the node cannot be a top-level child (e.g. a bare `tableCell`), in which
|
|
12
|
+
* case the caller falls back to the live view and the previous stubbed positions.
|
|
13
|
+
*/
|
|
14
|
+
export function createEditorProxy(liveView, rootNode, $sourcePos) {
|
|
15
|
+
const liveState = liveView.state;
|
|
16
|
+
const createDoc = topLevel => liveState.schema.topNodeType.createChecked(null, topLevel);
|
|
17
|
+
let previewDoc;
|
|
18
|
+
let rootPos = 0;
|
|
19
|
+
try {
|
|
20
|
+
var _$sourcePos$nodeAfter;
|
|
21
|
+
if ($sourcePos && $sourcePos.depth > 0 && ((_$sourcePos$nodeAfter = $sourcePos.nodeAfter) === null || _$sourcePos$nodeAfter === void 0 ? void 0 : _$sourcePos$nodeAfter.type) === rootNode.type) {
|
|
22
|
+
let topLevel = rootNode;
|
|
23
|
+
for (let depth = $sourcePos.depth; depth > 0; depth--) {
|
|
24
|
+
const ancestor = $sourcePos.node(depth);
|
|
25
|
+
topLevel = ancestor.copy(ancestor.content.replaceChild($sourcePos.index(depth), topLevel));
|
|
26
|
+
}
|
|
27
|
+
previewDoc = createDoc(topLevel);
|
|
28
|
+
rootPos = $sourcePos.pos - $sourcePos.before(1);
|
|
29
|
+
} else {
|
|
30
|
+
previewDoc = createDoc(rootNode);
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// A gap cursor just before the node selects nothing, so the table's node view doesn't render
|
|
37
|
+
// as selected or show its controls.
|
|
38
|
+
const previewSelection = new GapCursorSelection(previewDoc.resolve(rootPos), Side.LEFT);
|
|
39
|
+
// Proxy rather than `EditorState.create` so plugin state is shared as-is: re-initialising
|
|
40
|
+
// every plugin per preview would be costly and re-runs their side effects.
|
|
41
|
+
let stateProxy = new Proxy(liveState, {
|
|
42
|
+
get(target, prop, receiver) {
|
|
43
|
+
if (prop === 'doc') {
|
|
44
|
+
return previewDoc;
|
|
45
|
+
}
|
|
46
|
+
if (prop === 'selection') {
|
|
47
|
+
return previewSelection;
|
|
48
|
+
}
|
|
49
|
+
return Reflect.get(target, prop, receiver);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const editorProxy = new Proxy(liveView, {
|
|
53
|
+
get(target, prop, receiver) {
|
|
54
|
+
if (prop === 'state') {
|
|
55
|
+
return stateProxy;
|
|
56
|
+
}
|
|
57
|
+
if (prop === 'dispatch') {
|
|
58
|
+
// Keep the transaction inside the preview. It was built against `previewDoc`, so
|
|
59
|
+
// applying it to the live state would corrupt the real document.
|
|
60
|
+
return tr => {
|
|
61
|
+
try {
|
|
62
|
+
stateProxy = stateProxy.apply(tr);
|
|
63
|
+
} catch {
|
|
64
|
+
// A preview is throwaway; a failed self-update is not worth surfacing.
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const value = Reflect.get(target, prop, receiver);
|
|
69
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
editorProxy,
|
|
74
|
+
rootPos
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -2,8 +2,8 @@ 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
4
|
import { buildCharsByOffset, isWhitespaceChar } from '../utils/charsByOffset';
|
|
5
|
-
import { clampAnchorPosIntoCell } from './createAnchorDecorationWidgets';
|
|
6
|
-
import { AnchorTypeKey, buildAnchorDecorationKey, buildContributorTagDecorationSpec } from './decorationKeys';
|
|
5
|
+
import { clampAnchorPosIntoCell, createAnchorNameSpan } from './createAnchorDecorationWidgets';
|
|
6
|
+
import { AnchorTypeKey, buildAnchorDecorationKey, buildAnchorDecorationSpec, buildContributorTagDecorationSpec } from './decorationKeys';
|
|
7
7
|
|
|
8
8
|
/** Marks the host element of one tag, so a tag can be found in the document by its own diff. */
|
|
9
9
|
export const CONTRIBUTOR_TAG_HOST_ATTRIBUTE = 'data-contributor-tag-host';
|
|
@@ -212,7 +212,8 @@ export const resolveHoistedCodeBlockAnchor = (doc, pos, diffId) => {
|
|
|
212
212
|
* highlights — or, with `anchorAtRangeStart`, at `from` itself. A host inside a code block is
|
|
213
213
|
* hoisted in front of it instead, anchored via `resolveHoistedCodeBlockAnchor` (EDITOR-9045).
|
|
214
214
|
*
|
|
215
|
-
* Returns one decoration
|
|
215
|
+
* Returns one decoration when a caller-supplied `anchorName` is used, two (marker + tag)
|
|
216
|
+
* otherwise — hoisted out of a code block, or anchored to its own point marker.
|
|
216
217
|
*
|
|
217
218
|
* The tag is mounted in `toDOM` and taken down in `destroy`, so it lives exactly as long as the host
|
|
218
219
|
* element ProseMirror drew it into — see `mountContributorTag`.
|
|
@@ -226,7 +227,7 @@ export const createContributorTagWidget = ({
|
|
|
226
227
|
diffId,
|
|
227
228
|
mountContext
|
|
228
229
|
}) => {
|
|
229
|
-
var _hoisted$
|
|
230
|
+
var _hoisted$codeBlockSta, _ref2, _hoisted$anchorName;
|
|
230
231
|
if (!isContributorTagWidgetEnabled()) {
|
|
231
232
|
return undefined;
|
|
232
233
|
}
|
|
@@ -238,15 +239,32 @@ export const createContributorTagWidget = ({
|
|
|
238
239
|
// A change inside a code block is hoisted out of it, anchored to its own marker; anything else
|
|
239
240
|
// keeps its own anchor.
|
|
240
241
|
const hoisted = resolveHoistedCodeBlockAnchor(doc, anchorPos, diffId);
|
|
241
|
-
|
|
242
|
+
// Keep the host out of the table row's grid (EDITOR-8442).
|
|
243
|
+
const hostPos = clampAnchorPosIntoCell(doc, (_hoisted$codeBlockSta = hoisted === null || hoisted === void 0 ? void 0 : hoisted.codeBlockStart) !== null && _hoisted$codeBlockSta !== void 0 ? _hoisted$codeBlockSta : anchorPos, 1);
|
|
244
|
+
|
|
245
|
+
// Inline diffs pass no anchorName. Give them their own point anchor instead of the
|
|
246
|
+
// `position: relative` fallback, which drifts left once the range wraps a line (EDITOR-9286).
|
|
247
|
+
const ownAnchorName = buildAnchorDecorationKey({
|
|
248
|
+
diffId,
|
|
249
|
+
anchorType: AnchorTypeKey.tag
|
|
250
|
+
});
|
|
251
|
+
const effectiveAnchorName = (_ref2 = (_hoisted$anchorName = hoisted === null || hoisted === void 0 ? void 0 : hoisted.anchorName) !== null && _hoisted$anchorName !== void 0 ? _hoisted$anchorName : anchorName) !== null && _ref2 !== void 0 ? _ref2 : ownAnchorName;
|
|
242
252
|
const tagAnchorName = effectiveAnchorName && supportsAnchorPositioning() ? effectiveAnchorName : undefined;
|
|
243
253
|
const decorations = [];
|
|
244
254
|
if (hoisted) {
|
|
245
255
|
decorations.push(hoisted.marker);
|
|
256
|
+
} else if (!anchorName && tagAnchorName) {
|
|
257
|
+
decorations.push(Decoration.widget(hostPos, () => createAnchorNameSpan(ownAnchorName), {
|
|
258
|
+
...buildAnchorDecorationSpec({
|
|
259
|
+
diffId,
|
|
260
|
+
anchorType: AnchorTypeKey.tag,
|
|
261
|
+
side: -1
|
|
262
|
+
}),
|
|
263
|
+
marks: [],
|
|
264
|
+
ignoreSelection: true
|
|
265
|
+
}));
|
|
246
266
|
}
|
|
247
|
-
decorations.push(Decoration.widget(
|
|
248
|
-
// Keep the host out of the table row's grid (EDITOR-8442).
|
|
249
|
-
clampAnchorPosIntoCell(doc, (_hoisted$codeBlockSta = hoisted === null || hoisted === void 0 ? void 0 : hoisted.codeBlockStart) !== null && _hoisted$codeBlockSta !== void 0 ? _hoisted$codeBlockSta : anchorPos, 1), () => {
|
|
267
|
+
decorations.push(Decoration.widget(hostPos, () => {
|
|
250
268
|
const host = buildContributorTagHost(diffId, tagAnchorName);
|
|
251
269
|
mount = mountContributorTag({
|
|
252
270
|
anchorName: tagAnchorName,
|
|
@@ -2,7 +2,7 @@ import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
|
2
2
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
3
3
|
import { countEmptyTextBlockOnlySlice } from '../utils/emptyTextBlocks';
|
|
4
4
|
import { isEmptyParagraphSlice } from '../utils/isEmptyParagraphSlice';
|
|
5
|
-
import { clampAnchorPosIntoCell, createLeftAnchorWidget } from './createAnchorDecorationWidgets';
|
|
5
|
+
import { clampAnchorPosIntoCell, createAnchorNameSpan, createLeftAnchorWidget } from './createAnchorDecorationWidgets';
|
|
6
6
|
import { createChangedRowDecorationWidgets } from './createChangedRowDecorationWidgets';
|
|
7
7
|
import { createContributorTagHost, resolveHoistedCodeBlockAnchor, unmountContributorTag } from './createContributorTagWidget';
|
|
8
8
|
import { AnchorTypeKey, buildContributorTagDecorationSpec, buildDiffDecorationSpec, buildAnchorDecorationKey, scrollMarginTopValue } from './decorationKeys';
|
|
@@ -14,6 +14,22 @@ import { findSafeInsertPos } from './utils/findSafeInsertPos';
|
|
|
14
14
|
import { safeResolve } from './utils/safeResolve';
|
|
15
15
|
import { getTableDiffMode } from './utils/tableDiffMode';
|
|
16
16
|
import { wrapBlockNodeView, createRemovedLozenge, injectInnerWrapper, createContentWrapper } from './utils/wrapBlockNodeView';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Where a slice's `index`th child sits in `doc`: the children belong to the ancestor `openStart`
|
|
20
|
+
* levels above the change. `null` if out of range — `posAtIndex` throws past the last child.
|
|
21
|
+
*/
|
|
22
|
+
const getSourcePos = (doc, $changeFrom, openStart, index) => {
|
|
23
|
+
const depth = $changeFrom ? $changeFrom.depth - openStart : -1;
|
|
24
|
+
if (!$changeFrom || depth < 0) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const childIndex = $changeFrom.index(depth) + index;
|
|
28
|
+
if (childIndex >= $changeFrom.node(depth).childCount) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return safeResolve(doc, $changeFrom.posAtIndex(childIndex, depth));
|
|
32
|
+
};
|
|
17
33
|
const isHeadingLevel = level => typeof level === 'number' && level >= 1 && level <= 6;
|
|
18
34
|
|
|
19
35
|
/**
|
|
@@ -282,7 +298,7 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
282
298
|
* and if it's the first or last content, we go in however many the sliced Open
|
|
283
299
|
* or sliced End depth is and match only the entire node.
|
|
284
300
|
*/
|
|
285
|
-
slice.content.forEach(node => {
|
|
301
|
+
slice.content.forEach((node, _offset, index) => {
|
|
286
302
|
const isFirst = firstReplacedNode === node;
|
|
287
303
|
const isLast = lastReplacedNode === node;
|
|
288
304
|
const isOpenAtSliceBoundary = isFirst && slice.openStart > 0 || isLast && slice.openEnd > 0;
|
|
@@ -347,7 +363,8 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
347
363
|
}
|
|
348
364
|
|
|
349
365
|
// Try to create node view, fallback to serialization
|
|
350
|
-
const
|
|
366
|
+
const $sourcePos = fg('platform_editor_ai_show_diff_patch_2') ? getSourcePos(doc, $changeFromA, slice.openStart, index) : null;
|
|
367
|
+
const nodeView = serializer.tryCreateNodeView(node, 0, $sourcePos !== null && $sourcePos !== void 0 ? $sourcePos : undefined);
|
|
351
368
|
if (nodeView) {
|
|
352
369
|
if (node.isInline) {
|
|
353
370
|
const wrapper = createContentWrapper(colorScheme, isActive, isInserted, reveal);
|
|
@@ -450,18 +467,25 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
450
467
|
});
|
|
451
468
|
}
|
|
452
469
|
|
|
453
|
-
// Needed even
|
|
454
|
-
// widget.
|
|
470
|
+
// Needed even with the indicator bar off, since it anchors its top/bottom to the widget.
|
|
455
471
|
if (showIndicators || showContributorTags) {
|
|
456
472
|
dom.style.setProperty('anchor-name', `--${buildAnchorDecorationKey({
|
|
457
473
|
diffId
|
|
458
474
|
})}`);
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
if (fg('confluence_ncs_step_diffing_version_history')) {
|
|
462
|
-
|
|
463
|
-
diffId
|
|
475
|
+
// `dom` is inline, so its box fragments across a line wrap and `anchor()` resolves to the
|
|
476
|
+
// wrong edge. The tag anchors to its own point marker instead (EDITOR-9286).
|
|
477
|
+
if (fg('confluence_ncs_step_diffing_version_history') && !contributorTagAnchorName) {
|
|
478
|
+
const tagPointAnchorName = buildAnchorDecorationKey({
|
|
479
|
+
diffId,
|
|
480
|
+
anchorType: AnchorTypeKey.tag
|
|
464
481
|
});
|
|
482
|
+
contributorTagAnchorName = tagPointAnchorName;
|
|
483
|
+
decorations.push(Decoration.widget(safeInsertPos, () => createAnchorNameSpan(tagPointAnchorName), {
|
|
484
|
+
key: `contributor-tag-anchor-${diffId}`,
|
|
485
|
+
side: -1,
|
|
486
|
+
marks: [],
|
|
487
|
+
ignoreSelection: true
|
|
488
|
+
}));
|
|
465
489
|
}
|
|
466
490
|
}
|
|
467
491
|
|
|
@@ -495,8 +519,7 @@ export const createNodeChangedDecorationWidget = ({
|
|
|
495
519
|
}
|
|
496
520
|
}));
|
|
497
521
|
} else {
|
|
498
|
-
//
|
|
499
|
-
// the deleted content wraps its box is the union of its line fragments.
|
|
522
|
+
// Anchored to the point marker pushed above, not to `dom` (EDITOR-9286).
|
|
500
523
|
const tagHost = createContributorTagHost({
|
|
501
524
|
anchorName: contributorTagAnchorName,
|
|
502
525
|
diffId,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import pick from 'lodash/pick';
|
|
2
|
+
const asRecord = value => value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
3
|
+
export const isExcerptInclude = attrs => attrs.extensionKey === 'excerpt-include';
|
|
4
|
+
export const getReferencedContentId = attrs => {
|
|
5
|
+
var _asRecord;
|
|
6
|
+
const parameters = asRecord(attrs.parameters);
|
|
7
|
+
const macroParams = asRecord(parameters === null || parameters === void 0 ? void 0 : parameters.macroParams);
|
|
8
|
+
const value = (_asRecord = asRecord(macroParams === null || macroParams === void 0 ? void 0 : macroParams._referencedContentId)) === null || _asRecord === void 0 ? void 0 : _asRecord.value;
|
|
9
|
+
return typeof value === 'string' ? value : undefined;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** Compare macro identity and authored options, excluding publish-time rendering metadata. */
|
|
13
|
+
export const getComparableExcerptIncludeAttrs = attrs => pick(attrs, ['extensionType', 'layout', 'parameters.macroMetadata.macroId.value', 'parameters.macroParams[""].value', 'parameters.macroParams.name.value', 'parameters.macroParams.nopanel.value', 'parameters.macroParams.inline.value']);
|
|
@@ -3,7 +3,9 @@ import omit from 'lodash/omit';
|
|
|
3
3
|
import { SetAttrsStep } from '@atlaskit/adf-schema/steps/set-attrs';
|
|
4
4
|
import { getBaseNodeTypeName } from '@atlaskit/editor-common/utils/node-type-utils';
|
|
5
5
|
import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
|
|
6
|
+
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
6
7
|
import { getRequiredIncludedDiffableAttrs, isDiffableAttr } from './diffableAttrs';
|
|
8
|
+
import { getComparableExcerptIncludeAttrs, getReferencedContentId, isExcerptInclude } from './excerptIncludeAttrs';
|
|
7
9
|
const filterUndefined = x => !!x;
|
|
8
10
|
|
|
9
11
|
// Attr lists sourced from the shared `diffableAttrs` map (shared with the encoder).
|
|
@@ -34,7 +36,14 @@ const transientExtensionAttrPaths = ['localId', 'parameters.macroParams._parentI
|
|
|
34
36
|
* extension's user-visible configuration.
|
|
35
37
|
*/
|
|
36
38
|
const getComparableExtensionAttrs = node => omit(node.attrs, transientExtensionAttrPaths);
|
|
37
|
-
const haveSameRelevantExtensionAttrs = (beforeNode, afterNode) =>
|
|
39
|
+
const haveSameRelevantExtensionAttrs = (beforeNode, afterNode) => {
|
|
40
|
+
if (fg('platform_editor_normalize_excerpt_diff') && isExcerptInclude(beforeNode.attrs) && isExcerptInclude(afterNode.attrs)) {
|
|
41
|
+
const beforeReference = getReferencedContentId(beforeNode.attrs);
|
|
42
|
+
const afterReference = getReferencedContentId(afterNode.attrs);
|
|
43
|
+
return isEqual(getComparableExcerptIncludeAttrs(beforeNode.attrs), getComparableExcerptIncludeAttrs(afterNode.attrs)) && (beforeReference === undefined || afterReference === undefined || beforeReference === afterReference);
|
|
44
|
+
}
|
|
45
|
+
return isEqual(getComparableExtensionAttrs(beforeNode), getComparableExtensionAttrs(afterNode));
|
|
46
|
+
};
|
|
38
47
|
const getStepAttrs = step => {
|
|
39
48
|
if (step instanceof AttrStep) {
|
|
40
49
|
return [step.attr];
|
|
@@ -311,6 +311,19 @@ const applyTextLikeBlockNodeStyles = ({
|
|
|
311
311
|
/**
|
|
312
312
|
* Creates a content wrapper with deleted styles for a block node
|
|
313
313
|
*/
|
|
314
|
+
/**
|
|
315
|
+
* The element that spans breakout width, for nodes carrying a `breakout` mark. The wrapper divs
|
|
316
|
+
* stay at editor-column width, so anything that should align to the node's visible edges —
|
|
317
|
+
* outlines, absolutely positioned lozenges — has to attach here instead.
|
|
318
|
+
*/
|
|
319
|
+
const findBreakoutElement = (targetNode, nodeView) => {
|
|
320
|
+
var _targetNode$marks;
|
|
321
|
+
const hasBreakoutMark = (_targetNode$marks = targetNode.marks) === null || _targetNode$marks === void 0 ? void 0 : _targetNode$marks.some(mark => mark.type.name === 'breakout');
|
|
322
|
+
if (!hasBreakoutMark || !(nodeView instanceof HTMLElement)) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
return nodeView.matches('[data-node-type]') ? nodeView : nodeView.querySelector('[data-node-type]');
|
|
326
|
+
};
|
|
314
327
|
const createBlockNodeContentWrapper = ({
|
|
315
328
|
nodeView,
|
|
316
329
|
targetNode,
|
|
@@ -328,7 +341,16 @@ const createBlockNodeContentWrapper = ({
|
|
|
328
341
|
// should not also get the inline content style (borderBottom underline) on their container.
|
|
329
342
|
const shouldSkipContentStyle = isInserted && nodeStyle !== undefined;
|
|
330
343
|
const contentStyle = shouldSkipContentStyle ? '' : getChangedContentStyle(colorScheme, isActive, isInserted, hideAddedDiffsUnderline);
|
|
331
|
-
|
|
344
|
+
|
|
345
|
+
// A `breakout` node renders wider than the editor column while the wrapper divs
|
|
346
|
+
// stay at column width, so paint the outline on the node element that spans that width.
|
|
347
|
+
const breakoutTarget = nodeStyle ? findBreakoutElement(targetNode, nodeView) : null;
|
|
348
|
+
if (fg('platform_editor_ai_show_diff_patch_2') && breakoutTarget) {
|
|
349
|
+
appendStyleToElement(breakoutTarget, nodeStyle !== null && nodeStyle !== void 0 ? nodeStyle : '');
|
|
350
|
+
contentWrapper.setAttribute('style', contentStyle || '');
|
|
351
|
+
} else {
|
|
352
|
+
contentWrapper.setAttribute('style', `${contentStyle}${nodeStyle || ''}`);
|
|
353
|
+
}
|
|
332
354
|
contentWrapper.append(nodeView);
|
|
333
355
|
return contentWrapper;
|
|
334
356
|
};
|
|
@@ -456,7 +478,19 @@ const wrapBlockNode = ({
|
|
|
456
478
|
})) {
|
|
457
479
|
return;
|
|
458
480
|
}
|
|
459
|
-
|
|
481
|
+
|
|
482
|
+
// `lozenge` is absolutely positioned, so it lands on the nearest positioned ancestor. For a
|
|
483
|
+
// breakout node that is the column-width wrapper, leaving the pill short of the node's
|
|
484
|
+
// right edge — host it on the breakout element, which must establish a containing block.
|
|
485
|
+
const breakoutHost = fg('platform_editor_ai_show_diff_patch_2') ? findBreakoutElement(targetNode, nodeView) : null;
|
|
486
|
+
if (breakoutHost) {
|
|
487
|
+
appendStyleToElement(breakoutHost, convertToInlineCss({
|
|
488
|
+
position: 'relative'
|
|
489
|
+
}));
|
|
490
|
+
breakoutHost.append(lozenge);
|
|
491
|
+
} else {
|
|
492
|
+
blockWrapper.append(lozenge);
|
|
493
|
+
}
|
|
460
494
|
}
|
|
461
495
|
const contentWrapper = createBlockNodeContentWrapper({
|
|
462
496
|
nodeView,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
|
|
2
|
+
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `@atlaskit/editor-prosemirror/view` doesn't re-export prosemirror-view's `MarkViewConstructor`,
|
|
6
|
+
* so it's typed locally, as `EditorSSRRenderer` does.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Where to look up a mark's renderer, and the view to render it against. */
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Renders a mark as ProseMirror does: `view.nodeViews` also holds mark views, keyed by mark name,
|
|
13
|
+
* so `breakout` resolves whether it's registered under `markViews` or `nodeViews`. Falls back to
|
|
14
|
+
* `toDOM` — they can differ: `breakout` renders `.fabric-editor-breakout-mark-dom`, which the width
|
|
15
|
+
* CSS targets.
|
|
16
|
+
*/
|
|
17
|
+
const renderMarkView = (mark, inline, {
|
|
18
|
+
nodeViews,
|
|
19
|
+
view
|
|
20
|
+
}) => {
|
|
21
|
+
try {
|
|
22
|
+
const markView = nodeViews === null || nodeViews === void 0 ? void 0 : nodeViews[mark.type.name];
|
|
23
|
+
if (markView && view) {
|
|
24
|
+
const rendered = markView(mark, view, inline);
|
|
25
|
+
return {
|
|
26
|
+
dom: rendered.dom,
|
|
27
|
+
contentDOM: rendered.contentDOM
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const toDOM = mark.type.spec.toDOM;
|
|
31
|
+
if (!toDOM) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return DOMSerializer.renderSpec(document, toDOM(mark, inline));
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Wraps rendered DOM in its marks, as ProseMirror does for the live document. Without this a
|
|
42
|
+
* block node loses `breakout`, whose wrapper supplies the width. Marks nest
|
|
43
|
+
* outside-in, so `marks[0]` is outermost.
|
|
44
|
+
*/
|
|
45
|
+
export const wrapInMarkViews = (targetNode, nodeDom, sources) => {
|
|
46
|
+
if (targetNode.marks.length === 0 || !fg('platform_editor_ai_show_diff_patch_2')) {
|
|
47
|
+
return nodeDom;
|
|
48
|
+
}
|
|
49
|
+
let wrapped = nodeDom;
|
|
50
|
+
for (let index = targetNode.marks.length - 1; index >= 0; index--) {
|
|
51
|
+
const mark = targetNode.marks[index];
|
|
52
|
+
const rendered = renderMarkView(mark, targetNode.isInline, sources);
|
|
53
|
+
// No content hole means nowhere to nest the node, so skip the mark, not the node.
|
|
54
|
+
if (!(rendered !== null && rendered !== void 0 && rendered.contentDOM)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
rendered.contentDOM.appendChild(wrapped);
|
|
58
|
+
wrapped = rendered.dom;
|
|
59
|
+
}
|
|
60
|
+
return wrapped;
|
|
61
|
+
};
|