@atlaskit/editor-plugin-show-diff 0.1.1 → 0.1.3

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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 0.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`8700ce859da07`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/8700ce859da07) -
8
+ [EDITOR-1249] Added inline node support for show diff
9
+
10
+ ## 0.1.2
11
+
12
+ ### Patch Changes
13
+
14
+ - [`7fe4c9e51271d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/7fe4c9e51271d) -
15
+ Fix initial show diff after performance fix.
16
+ - [`b2d53a70dbaa5`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b2d53a70dbaa5) -
17
+ Improve show diff performance by storing decorations in state.
18
+ - Updated dependencies
19
+
3
20
  ## 0.1.1
4
21
 
5
22
  ### Patch Changes
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.NodeViewSerializer = void 0;
8
+ exports.isEditorViewWithNodeViews = isEditorViewWithNodeViews;
9
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
10
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
11
+ var _model = require("@atlaskit/editor-prosemirror/model");
12
+ /**
13
+ * Utilities for working with ProseMirror node views and DOM serialization within the
14
+ * Show Diff editor plugin.
15
+ *
16
+ * This module centralizes:
17
+ * - Access to the editor's `nodeViews` registry (when available on `EditorView`)
18
+ * - Safe attempts to instantiate a node view for a given node, with a blocklist to
19
+ * avoid node types that are known to be problematic in this context (e.g. tables)
20
+ * - Schema-driven serialization of nodes and fragments to DOM via `DOMSerializer`
21
+ *
22
+ * The Show Diff decorations leverage this to either render nodes using their
23
+ * corresponding node view implementation, or fall back to DOM serialization.
24
+ */
25
+
26
+ /**
27
+ * Narrowed `EditorView` that exposes the internal `nodeViews` registry.
28
+ * Many editor instances provide this, but it's not part of the base type.
29
+ */
30
+
31
+ /**
32
+ * Type guard to detect whether an `EditorView` exposes a `nodeViews` map.
33
+ */
34
+ function isEditorViewWithNodeViews(view) {
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ return view.nodeViews !== undefined;
37
+ }
38
+
39
+ /**
40
+ * Encapsulates DOM serialization and node view access/creation.
41
+ *
42
+ * Responsible for:
43
+ * - Creating a `DOMSerializer` from the provided schema
44
+ * - Reading `nodeViews` from an `EditorView` (if present) or using an explicit mapping
45
+ * - Preventing node view creation for blocklisted node types
46
+ */
47
+ var NodeViewSerializer = exports.NodeViewSerializer = /*#__PURE__*/function () {
48
+ function NodeViewSerializer(params) {
49
+ var _ref, _params$nodeViews, _this$editorView, _params$blocklist;
50
+ (0, _classCallCheck2.default)(this, NodeViewSerializer);
51
+ this.serializer = _model.DOMSerializer.fromSchema(params.schema);
52
+ if (params.editorView && isEditorViewWithNodeViews(params.editorView)) {
53
+ this.editorView = params.editorView;
54
+ }
55
+ this.nodeViews = (_ref = (_params$nodeViews = params.nodeViews) !== null && _params$nodeViews !== void 0 ? _params$nodeViews : (_this$editorView = this.editorView) === null || _this$editorView === void 0 ? void 0 : _this$editorView.nodeViews) !== null && _ref !== void 0 ? _ref : {};
56
+ this.nodeViewBlocklist = new Set((_params$blocklist = params.blocklist) !== null && _params$blocklist !== void 0 ? _params$blocklist : ['tableRow', 'table']);
57
+ }
58
+
59
+ /**
60
+ * Attempts to create a node view for the given node.
61
+ *
62
+ * Returns `null` when there is no `EditorView`, no constructor for the node type,
63
+ * or the node type is blocklisted. Otherwise returns the constructed node view instance.
64
+ */
65
+ return (0, _createClass2.default)(NodeViewSerializer, [{
66
+ key: "tryCreateNodeView",
67
+ value: function tryCreateNodeView(targetNode) {
68
+ if (!this.editorView) {
69
+ return null;
70
+ }
71
+ var constructor = this.nodeViews[targetNode.type.name];
72
+ if (!constructor) {
73
+ return null;
74
+ }
75
+ if (this.nodeViewBlocklist.has(targetNode.type.name)) {
76
+ return null;
77
+ }
78
+ return constructor(targetNode, this.editorView, function () {
79
+ return 0;
80
+ }, [], {});
81
+ }
82
+
83
+ /**
84
+ * Serializes a node to a DOM `Node` using the schema's `DOMSerializer`.
85
+ */
86
+ }, {
87
+ key: "serializeNode",
88
+ value: function serializeNode(node) {
89
+ return this.serializer.serializeNode(node);
90
+ }
91
+
92
+ /**
93
+ * Serializes a fragment to a `DocumentFragment` using the schema's `DOMSerializer`.
94
+ */
95
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
96
+ }, {
97
+ key: "serializeFragment",
98
+ value: function serializeFragment(fragment) {
99
+ return this.serializer.serializeFragment(fragment);
100
+ }
101
+ }]);
102
+ }();
@@ -5,8 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.createInlineChangedDecoration = exports.createDeletedContentDecoration = void 0;
7
7
  var _lazyNodeView = require("@atlaskit/editor-common/lazy-node-view");
8
- var _model = require("@atlaskit/editor-prosemirror/model");
9
8
  var _view = require("@atlaskit/editor-prosemirror/view");
9
+ var _NodeViewSerializer = require("./NodeViewSerializer");
10
10
  var style = (0, _lazyNodeView.convertToInlineCss)({
11
11
  background: "var(--ds-background-accent-purple-subtlest, #F3F0FF)",
12
12
  textDecoration: 'underline',
@@ -14,7 +14,6 @@ var style = (0, _lazyNodeView.convertToInlineCss)({
14
14
  textDecorationThickness: "var(--ds-space-025, 2px)",
15
15
  textDecorationColor: "var(--ds-border-accent-purple, #8270DB)"
16
16
  });
17
-
18
17
  /**
19
18
  * Inline decoration used for insertions as the content already exists in the document
20
19
  *
@@ -29,7 +28,18 @@ var createInlineChangedDecoration = exports.createInlineChangedDecoration = func
29
28
  };
30
29
  var deletedContentStyle = (0, _lazyNodeView.convertToInlineCss)({
31
30
  color: "var(--ds-text-accent-gray, #44546F)",
32
- textDecoration: 'line-through'
31
+ textDecoration: 'line-through',
32
+ position: 'relative',
33
+ opacity: 0.6
34
+ });
35
+ var deletedContentStyleUnbounded = (0, _lazyNodeView.convertToInlineCss)({
36
+ position: 'absolute',
37
+ top: '50%',
38
+ width: '100%',
39
+ display: 'inline-block',
40
+ borderTop: "1px solid ".concat("var(--ds-text-accent-gray, #44546F)"),
41
+ pointerEvents: 'none',
42
+ zIndex: 1
33
43
  });
34
44
 
35
45
  /**
@@ -43,7 +53,9 @@ var deletedContentStyle = (0, _lazyNodeView.convertToInlineCss)({
43
53
  var createDeletedContentDecoration = exports.createDeletedContentDecoration = function createDeletedContentDecoration(_ref) {
44
54
  var change = _ref.change,
45
55
  doc = _ref.doc,
46
- tr = _ref.tr;
56
+ tr = _ref.tr,
57
+ editorView = _ref.editorView,
58
+ nodeViews = _ref.nodeViews;
47
59
  var dom = document.createElement('span');
48
60
  dom.setAttribute('style', deletedContentStyle);
49
61
 
@@ -53,22 +65,94 @@ var createDeletedContentDecoration = exports.createDeletedContentDecoration = fu
53
65
  * or sliced End depth is and match only the content and not with the entire node.
54
66
  */
55
67
  var slice = doc.slice(change.fromA, change.toA);
68
+ var nodeViewSerializer = new _NodeViewSerializer.NodeViewSerializer({
69
+ schema: tr.doc.type.schema,
70
+ editorView: editorView,
71
+ nodeViews: nodeViews
72
+ });
56
73
  slice.content.forEach(function (node) {
57
- var serializer = _model.DOMSerializer.fromSchema(tr.doc.type.schema);
74
+ // Create a wrapper for each node with strikethrough
75
+ var createWrapperWithStrikethrough = function createWrapperWithStrikethrough() {
76
+ var wrapper = document.createElement('span');
77
+ wrapper.style.position = 'relative';
78
+ wrapper.style.width = 'fit-content';
79
+ var strikethrough = document.createElement('span');
80
+ strikethrough.setAttribute('style', deletedContentStyleUnbounded);
81
+ wrapper.append(strikethrough);
82
+ return wrapper;
83
+ };
84
+
85
+ // Helper function to handle multiple child nodes
86
+ var handleMultipleChildNodes = function handleMultipleChildNodes(node) {
87
+ if (node.content.childCount > 1 && node.type.inlineContent) {
88
+ node.content.forEach(function (childNode) {
89
+ var childNodeView = nodeViewSerializer.tryCreateNodeView(childNode);
90
+ if (childNodeView) {
91
+ var lineBreak = document.createElement('br');
92
+ targetNode = node;
93
+ dom.append(lineBreak);
94
+ var wrapper = createWrapperWithStrikethrough();
95
+ wrapper.append(childNodeView.dom);
96
+ dom.append(wrapper);
97
+ } else {
98
+ // Fallback to serializing the individual child node
99
+ var serializedChild = nodeViewSerializer.serializeNode(childNode);
100
+ dom.append(serializedChild);
101
+ }
102
+ });
103
+ return true; // Indicates we handled multiple children
104
+ }
105
+ return false; // Indicates single child, continue with normal logic
106
+ };
107
+
108
+ // Determine which node to use and how to serialize
58
109
  var isFirst = slice.content.firstChild === node;
59
110
  var isLast = slice.content.lastChild === node;
60
- if (isFirst || isLast && slice.content.childCount > 2) {
61
- if (node.content.childCount > 0 && node.type.inlineContent === true) {
62
- dom.append(serializer.serializeFragment(node.content));
63
- } else {
64
- dom.append(serializer.serializeNode(node));
111
+ var hasInlineContent = node.content.childCount > 0 && node.type.inlineContent === true;
112
+ var targetNode;
113
+ var fallbackSerialization;
114
+ if ((isFirst || isLast && slice.content.childCount > 2) && hasInlineContent) {
115
+ if (handleMultipleChildNodes(node)) {
116
+ return;
65
117
  }
118
+ targetNode = node.content.content[0];
119
+ fallbackSerialization = function fallbackSerialization() {
120
+ return nodeViewSerializer.serializeFragment(node.content);
121
+ };
66
122
  } else if (isLast && slice.content.childCount === 2) {
67
- var lineBreak = document.createElement('br');
68
- dom.append(lineBreak);
69
- dom.append(serializer.serializeFragment(node.content));
123
+ if (handleMultipleChildNodes(node)) {
124
+ return;
125
+ }
126
+ targetNode = node;
127
+ fallbackSerialization = function fallbackSerialization() {
128
+ if (node.type.name === 'text') {
129
+ return document.createTextNode(node.text || '');
130
+ }
131
+ if (node.type.name === 'paragraph') {
132
+ var lineBreak = document.createElement('br');
133
+ dom.append(lineBreak);
134
+ return nodeViewSerializer.serializeFragment(node.content);
135
+ }
136
+ return nodeViewSerializer.serializeFragment(node.content);
137
+ };
138
+ } else {
139
+ if (handleMultipleChildNodes(node)) {
140
+ return;
141
+ }
142
+ targetNode = node.content.content[0] || node;
143
+ fallbackSerialization = function fallbackSerialization() {
144
+ return nodeViewSerializer.serializeNode(node);
145
+ };
146
+ }
147
+
148
+ // Try to create node view, fallback to serialization
149
+ var nodeView = nodeViewSerializer.tryCreateNodeView(targetNode);
150
+ if (nodeView) {
151
+ var wrapper = createWrapperWithStrikethrough();
152
+ wrapper.append(nodeView.dom);
153
+ dom.append(wrapper);
70
154
  } else {
71
- dom.append(serializer.serializeNode(node));
155
+ dom.append(fallbackSerialization());
72
156
  }
73
157
  });
74
158
  dom.setAttribute('data-testid', 'show-diff-deleted-decoration');
@@ -21,7 +21,9 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
21
21
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } // eslint-disable-next-line @atlassian/tangerine/import/entry-points
22
22
  var calculateDecorations = function calculateDecorations(_ref) {
23
23
  var state = _ref.state,
24
- pluginState = _ref.pluginState;
24
+ pluginState = _ref.pluginState,
25
+ editorView = _ref.editorView,
26
+ nodeViews = _ref.nodeViews;
25
27
  var originalDoc = pluginState.originalDoc,
26
28
  steps = pluginState.steps;
27
29
  if (!originalDoc || !pluginState.isDisplayingChanges) {
@@ -59,7 +61,9 @@ var calculateDecorations = function calculateDecorations(_ref) {
59
61
  decorations.push((0, _decorations.createDeletedContentDecoration)({
60
62
  change: change,
61
63
  doc: originalDoc,
62
- tr: tr
64
+ tr: tr,
65
+ editorView: editorView,
66
+ nodeViews: nodeViews
63
67
  }));
64
68
  }
65
69
  });
@@ -70,60 +74,82 @@ var calculateDecorations = function calculateDecorations(_ref) {
70
74
  };
71
75
  var showDiffPluginKey = exports.showDiffPluginKey = new _state.PluginKey('showDiffPlugin');
72
76
  var createPlugin = exports.createPlugin = function createPlugin(config) {
77
+ var editorView;
78
+ var setEditorView = function setEditorView(newEditorView) {
79
+ editorView = newEditorView;
80
+ };
73
81
  return new _safePlugin.SafePlugin({
74
82
  key: showDiffPluginKey,
75
83
  state: {
76
84
  init: function init(_, state) {
77
- var _config$steps, _config$steps2;
85
+ var _config$steps, _config$steps2, _editorView;
78
86
  var schema = state.schema;
79
87
  var isDisplayingChanges = ((_config$steps = config === null || config === void 0 ? void 0 : config.steps) !== null && _config$steps !== void 0 ? _config$steps : []).length > 0;
88
+ var steps = ((_config$steps2 = config === null || config === void 0 ? void 0 : config.steps) !== null && _config$steps2 !== void 0 ? _config$steps2 : []).map(function (step) {
89
+ return _transform.Step.fromJSON(schema, step);
90
+ });
91
+
92
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
+ var nodeViews = ((_editorView = editorView) === null || _editorView === void 0 ? void 0 : _editorView.nodeViews) || {};
80
94
  return {
81
- steps: ((_config$steps2 = config === null || config === void 0 ? void 0 : config.steps) !== null && _config$steps2 !== void 0 ? _config$steps2 : []).map(function (step) {
82
- return _transform.Step.fromJSON(schema, step);
83
- }),
95
+ steps: steps,
84
96
  originalDoc: config !== null && config !== void 0 && config.originalDoc ? (0, _processRawValue.processRawValue)(state.schema, config.originalDoc) : undefined,
85
97
  decorations: calculateDecorations({
86
98
  state: state,
87
99
  pluginState: {
88
- steps: [],
100
+ steps: steps,
89
101
  originalDoc: config !== null && config !== void 0 && config.originalDoc ? (0, _processRawValue.processRawValue)(state.schema, config.originalDoc) : undefined,
90
102
  isDisplayingChanges: isDisplayingChanges
91
- }
103
+ },
104
+ editorView: editorView,
105
+ nodeViews: nodeViews
92
106
  }),
93
107
  isDisplayingChanges: isDisplayingChanges
94
108
  };
95
109
  },
96
110
  apply: function apply(tr, currentPluginState, oldState, newState) {
111
+ var _editorView2;
97
112
  var meta = tr.getMeta(showDiffPluginKey);
98
113
  var newPluginState = currentPluginState;
114
+
115
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
+ var nodeViews = ((_editorView2 = editorView) === null || _editorView2 === void 0 ? void 0 : _editorView2.nodeViews) || {};
99
117
  if (meta) {
100
118
  if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SHOW_DIFF') {
119
+ var _meta$steps, _meta$originalDoc;
120
+ // Calculate and store decorations in state
121
+ var decorations = calculateDecorations({
122
+ state: newState,
123
+ pluginState: {
124
+ steps: (_meta$steps = meta.steps) !== null && _meta$steps !== void 0 ? _meta$steps : currentPluginState.steps,
125
+ originalDoc: (_meta$originalDoc = meta.originalDoc) !== null && _meta$originalDoc !== void 0 ? _meta$originalDoc : currentPluginState.originalDoc,
126
+ isDisplayingChanges: true
127
+ },
128
+ editorView: editorView,
129
+ nodeViews: nodeViews
130
+ });
101
131
  newPluginState = _objectSpread(_objectSpread(_objectSpread({}, currentPluginState), meta), {}, {
132
+ decorations: decorations,
102
133
  isDisplayingChanges: true
103
134
  });
104
135
  } else if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'HIDE_DIFF') {
105
136
  newPluginState = _objectSpread(_objectSpread(_objectSpread({}, currentPluginState), meta), {}, {
137
+ decorations: _view.DecorationSet.empty,
106
138
  isDisplayingChanges: false
107
139
  });
108
140
  } else {
109
141
  newPluginState = _objectSpread(_objectSpread({}, currentPluginState), meta);
110
142
  }
111
143
  }
112
-
113
- // Calculate and store decorations in state
114
- var decorations = calculateDecorations({
115
- state: newState,
116
- pluginState: {
117
- steps: newPluginState.steps,
118
- originalDoc: newPluginState.originalDoc,
119
- isDisplayingChanges: newPluginState.isDisplayingChanges
120
- }
121
- });
122
144
  return _objectSpread(_objectSpread({}, newPluginState), {}, {
123
- decorations: decorations
145
+ decorations: newPluginState.decorations.map(tr.mapping, tr.doc)
124
146
  });
125
147
  }
126
148
  },
149
+ view: function view(editorView) {
150
+ setEditorView(editorView);
151
+ return {};
152
+ },
127
153
  props: {
128
154
  decorations: function decorations(state) {
129
155
  var pluginState = showDiffPluginKey.getState(state);
@@ -0,0 +1,83 @@
1
+ import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
2
+
3
+ /**
4
+ * Utilities for working with ProseMirror node views and DOM serialization within the
5
+ * Show Diff editor plugin.
6
+ *
7
+ * This module centralizes:
8
+ * - Access to the editor's `nodeViews` registry (when available on `EditorView`)
9
+ * - Safe attempts to instantiate a node view for a given node, with a blocklist to
10
+ * avoid node types that are known to be problematic in this context (e.g. tables)
11
+ * - Schema-driven serialization of nodes and fragments to DOM via `DOMSerializer`
12
+ *
13
+ * The Show Diff decorations leverage this to either render nodes using their
14
+ * corresponding node view implementation, or fall back to DOM serialization.
15
+ */
16
+
17
+ /**
18
+ * Narrowed `EditorView` that exposes the internal `nodeViews` registry.
19
+ * Many editor instances provide this, but it's not part of the base type.
20
+ */
21
+
22
+ /**
23
+ * Type guard to detect whether an `EditorView` exposes a `nodeViews` map.
24
+ */
25
+ export function isEditorViewWithNodeViews(view) {
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ return view.nodeViews !== undefined;
28
+ }
29
+
30
+ /**
31
+ * Encapsulates DOM serialization and node view access/creation.
32
+ *
33
+ * Responsible for:
34
+ * - Creating a `DOMSerializer` from the provided schema
35
+ * - Reading `nodeViews` from an `EditorView` (if present) or using an explicit mapping
36
+ * - Preventing node view creation for blocklisted node types
37
+ */
38
+ export class NodeViewSerializer {
39
+ constructor(params) {
40
+ var _ref, _params$nodeViews, _this$editorView, _params$blocklist;
41
+ this.serializer = DOMSerializer.fromSchema(params.schema);
42
+ if (params.editorView && isEditorViewWithNodeViews(params.editorView)) {
43
+ this.editorView = params.editorView;
44
+ }
45
+ this.nodeViews = (_ref = (_params$nodeViews = params.nodeViews) !== null && _params$nodeViews !== void 0 ? _params$nodeViews : (_this$editorView = this.editorView) === null || _this$editorView === void 0 ? void 0 : _this$editorView.nodeViews) !== null && _ref !== void 0 ? _ref : {};
46
+ this.nodeViewBlocklist = new Set((_params$blocklist = params.blocklist) !== null && _params$blocklist !== void 0 ? _params$blocklist : ['tableRow', 'table']);
47
+ }
48
+
49
+ /**
50
+ * Attempts to create a node view for the given node.
51
+ *
52
+ * Returns `null` when there is no `EditorView`, no constructor for the node type,
53
+ * or the node type is blocklisted. Otherwise returns the constructed node view instance.
54
+ */
55
+ tryCreateNodeView(targetNode) {
56
+ if (!this.editorView) {
57
+ return null;
58
+ }
59
+ const constructor = this.nodeViews[targetNode.type.name];
60
+ if (!constructor) {
61
+ return null;
62
+ }
63
+ if (this.nodeViewBlocklist.has(targetNode.type.name)) {
64
+ return null;
65
+ }
66
+ return constructor(targetNode, this.editorView, () => 0, [], {});
67
+ }
68
+
69
+ /**
70
+ * Serializes a node to a DOM `Node` using the schema's `DOMSerializer`.
71
+ */
72
+ serializeNode(node) {
73
+ return this.serializer.serializeNode(node);
74
+ }
75
+
76
+ /**
77
+ * Serializes a fragment to a `DocumentFragment` using the schema's `DOMSerializer`.
78
+ */
79
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
+ serializeFragment(fragment) {
81
+ return this.serializer.serializeFragment(fragment);
82
+ }
83
+ }
@@ -1,6 +1,6 @@
1
1
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
- import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
3
2
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
3
+ import { NodeViewSerializer } from './NodeViewSerializer';
4
4
  const style = convertToInlineCss({
5
5
  background: "var(--ds-background-accent-purple-subtlest, #F3F0FF)",
6
6
  textDecoration: 'underline',
@@ -8,7 +8,6 @@ const style = convertToInlineCss({
8
8
  textDecorationThickness: "var(--ds-space-025, 2px)",
9
9
  textDecorationColor: "var(--ds-border-accent-purple, #8270DB)"
10
10
  });
11
-
12
11
  /**
13
12
  * Inline decoration used for insertions as the content already exists in the document
14
13
  *
@@ -21,7 +20,18 @@ export const createInlineChangedDecoration = change => Decoration.inline(change.
21
20
  }, {});
22
21
  const deletedContentStyle = convertToInlineCss({
23
22
  color: "var(--ds-text-accent-gray, #44546F)",
24
- textDecoration: 'line-through'
23
+ textDecoration: 'line-through',
24
+ position: 'relative',
25
+ opacity: 0.6
26
+ });
27
+ const deletedContentStyleUnbounded = convertToInlineCss({
28
+ position: 'absolute',
29
+ top: '50%',
30
+ width: '100%',
31
+ display: 'inline-block',
32
+ borderTop: `1px solid ${"var(--ds-text-accent-gray, #44546F)"}`,
33
+ pointerEvents: 'none',
34
+ zIndex: 1
25
35
  });
26
36
 
27
37
  /**
@@ -35,7 +45,9 @@ const deletedContentStyle = convertToInlineCss({
35
45
  export const createDeletedContentDecoration = ({
36
46
  change,
37
47
  doc,
38
- tr
48
+ tr,
49
+ editorView,
50
+ nodeViews
39
51
  }) => {
40
52
  const dom = document.createElement('span');
41
53
  dom.setAttribute('style', deletedContentStyle);
@@ -46,22 +58,90 @@ export const createDeletedContentDecoration = ({
46
58
  * or sliced End depth is and match only the content and not with the entire node.
47
59
  */
48
60
  const slice = doc.slice(change.fromA, change.toA);
61
+ const nodeViewSerializer = new NodeViewSerializer({
62
+ schema: tr.doc.type.schema,
63
+ editorView,
64
+ nodeViews
65
+ });
49
66
  slice.content.forEach(node => {
50
- const serializer = DOMSerializer.fromSchema(tr.doc.type.schema);
67
+ // Create a wrapper for each node with strikethrough
68
+ const createWrapperWithStrikethrough = () => {
69
+ const wrapper = document.createElement('span');
70
+ wrapper.style.position = 'relative';
71
+ wrapper.style.width = 'fit-content';
72
+ const strikethrough = document.createElement('span');
73
+ strikethrough.setAttribute('style', deletedContentStyleUnbounded);
74
+ wrapper.append(strikethrough);
75
+ return wrapper;
76
+ };
77
+
78
+ // Helper function to handle multiple child nodes
79
+ const handleMultipleChildNodes = node => {
80
+ if (node.content.childCount > 1 && node.type.inlineContent) {
81
+ node.content.forEach(childNode => {
82
+ const childNodeView = nodeViewSerializer.tryCreateNodeView(childNode);
83
+ if (childNodeView) {
84
+ const lineBreak = document.createElement('br');
85
+ targetNode = node;
86
+ dom.append(lineBreak);
87
+ const wrapper = createWrapperWithStrikethrough();
88
+ wrapper.append(childNodeView.dom);
89
+ dom.append(wrapper);
90
+ } else {
91
+ // Fallback to serializing the individual child node
92
+ const serializedChild = nodeViewSerializer.serializeNode(childNode);
93
+ dom.append(serializedChild);
94
+ }
95
+ });
96
+ return true; // Indicates we handled multiple children
97
+ }
98
+ return false; // Indicates single child, continue with normal logic
99
+ };
100
+
101
+ // Determine which node to use and how to serialize
51
102
  const isFirst = slice.content.firstChild === node;
52
103
  const isLast = slice.content.lastChild === node;
53
- if (isFirst || isLast && slice.content.childCount > 2) {
54
- if (node.content.childCount > 0 && node.type.inlineContent === true) {
55
- dom.append(serializer.serializeFragment(node.content));
56
- } else {
57
- dom.append(serializer.serializeNode(node));
104
+ const hasInlineContent = node.content.childCount > 0 && node.type.inlineContent === true;
105
+ let targetNode;
106
+ let fallbackSerialization;
107
+ if ((isFirst || isLast && slice.content.childCount > 2) && hasInlineContent) {
108
+ if (handleMultipleChildNodes(node)) {
109
+ return;
58
110
  }
111
+ targetNode = node.content.content[0];
112
+ fallbackSerialization = () => nodeViewSerializer.serializeFragment(node.content);
59
113
  } else if (isLast && slice.content.childCount === 2) {
60
- const lineBreak = document.createElement('br');
61
- dom.append(lineBreak);
62
- dom.append(serializer.serializeFragment(node.content));
114
+ if (handleMultipleChildNodes(node)) {
115
+ return;
116
+ }
117
+ targetNode = node;
118
+ fallbackSerialization = () => {
119
+ if (node.type.name === 'text') {
120
+ return document.createTextNode(node.text || '');
121
+ }
122
+ if (node.type.name === 'paragraph') {
123
+ const lineBreak = document.createElement('br');
124
+ dom.append(lineBreak);
125
+ return nodeViewSerializer.serializeFragment(node.content);
126
+ }
127
+ return nodeViewSerializer.serializeFragment(node.content);
128
+ };
129
+ } else {
130
+ if (handleMultipleChildNodes(node)) {
131
+ return;
132
+ }
133
+ targetNode = node.content.content[0] || node;
134
+ fallbackSerialization = () => nodeViewSerializer.serializeNode(node);
135
+ }
136
+
137
+ // Try to create node view, fallback to serialization
138
+ const nodeView = nodeViewSerializer.tryCreateNodeView(targetNode);
139
+ if (nodeView) {
140
+ const wrapper = createWrapperWithStrikethrough();
141
+ wrapper.append(nodeView.dom);
142
+ dom.append(wrapper);
63
143
  } else {
64
- dom.append(serializer.serializeNode(node));
144
+ dom.append(fallbackSerialization());
65
145
  }
66
146
  });
67
147
  dom.setAttribute('data-testid', 'show-diff-deleted-decoration');
@@ -9,7 +9,9 @@ import { createInlineChangedDecoration, createDeletedContentDecoration } from '.
9
9
  import { getMarkChangeRanges } from './markDecorations';
10
10
  const calculateDecorations = ({
11
11
  state,
12
- pluginState
12
+ pluginState,
13
+ editorView,
14
+ nodeViews
13
15
  }) => {
14
16
  const {
15
17
  originalDoc,
@@ -43,7 +45,9 @@ const calculateDecorations = ({
43
45
  decorations.push(createDeletedContentDecoration({
44
46
  change,
45
47
  doc: originalDoc,
46
- tr
48
+ tr,
49
+ editorView,
50
+ nodeViews
47
51
  }));
48
52
  }
49
53
  });
@@ -54,41 +58,69 @@ const calculateDecorations = ({
54
58
  };
55
59
  export const showDiffPluginKey = new PluginKey('showDiffPlugin');
56
60
  export const createPlugin = config => {
61
+ let editorView;
62
+ const setEditorView = newEditorView => {
63
+ editorView = newEditorView;
64
+ };
57
65
  return new SafePlugin({
58
66
  key: showDiffPluginKey,
59
67
  state: {
60
68
  init(_, state) {
61
- var _config$steps, _config$steps2;
69
+ var _config$steps, _config$steps2, _editorView;
62
70
  const schema = state.schema;
63
71
  const isDisplayingChanges = ((_config$steps = config === null || config === void 0 ? void 0 : config.steps) !== null && _config$steps !== void 0 ? _config$steps : []).length > 0;
72
+ const steps = ((_config$steps2 = config === null || config === void 0 ? void 0 : config.steps) !== null && _config$steps2 !== void 0 ? _config$steps2 : []).map(step => ProseMirrorStep.fromJSON(schema, step));
73
+
74
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
+ const nodeViews = ((_editorView = editorView) === null || _editorView === void 0 ? void 0 : _editorView.nodeViews) || {};
64
76
  return {
65
- steps: ((_config$steps2 = config === null || config === void 0 ? void 0 : config.steps) !== null && _config$steps2 !== void 0 ? _config$steps2 : []).map(step => ProseMirrorStep.fromJSON(schema, step)),
77
+ steps,
66
78
  originalDoc: config !== null && config !== void 0 && config.originalDoc ? processRawValue(state.schema, config.originalDoc) : undefined,
67
79
  decorations: calculateDecorations({
68
80
  state,
69
81
  pluginState: {
70
- steps: [],
82
+ steps,
71
83
  originalDoc: config !== null && config !== void 0 && config.originalDoc ? processRawValue(state.schema, config.originalDoc) : undefined,
72
84
  isDisplayingChanges
73
- }
85
+ },
86
+ editorView,
87
+ nodeViews
74
88
  }),
75
89
  isDisplayingChanges
76
90
  };
77
91
  },
78
92
  apply: (tr, currentPluginState, oldState, newState) => {
93
+ var _editorView2;
79
94
  const meta = tr.getMeta(showDiffPluginKey);
80
95
  let newPluginState = currentPluginState;
96
+
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ const nodeViews = ((_editorView2 = editorView) === null || _editorView2 === void 0 ? void 0 : _editorView2.nodeViews) || {};
81
99
  if (meta) {
82
100
  if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SHOW_DIFF') {
101
+ var _meta$steps, _meta$originalDoc;
102
+ // Calculate and store decorations in state
103
+ const decorations = calculateDecorations({
104
+ state: newState,
105
+ pluginState: {
106
+ steps: (_meta$steps = meta.steps) !== null && _meta$steps !== void 0 ? _meta$steps : currentPluginState.steps,
107
+ originalDoc: (_meta$originalDoc = meta.originalDoc) !== null && _meta$originalDoc !== void 0 ? _meta$originalDoc : currentPluginState.originalDoc,
108
+ isDisplayingChanges: true
109
+ },
110
+ editorView,
111
+ nodeViews: nodeViews
112
+ });
83
113
  newPluginState = {
84
114
  ...currentPluginState,
85
115
  ...meta,
116
+ decorations,
86
117
  isDisplayingChanges: true
87
118
  };
88
119
  } else if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'HIDE_DIFF') {
89
120
  newPluginState = {
90
121
  ...currentPluginState,
91
122
  ...meta,
123
+ decorations: DecorationSet.empty,
92
124
  isDisplayingChanges: false
93
125
  };
94
126
  } else {
@@ -98,22 +130,16 @@ export const createPlugin = config => {
98
130
  };
99
131
  }
100
132
  }
101
-
102
- // Calculate and store decorations in state
103
- const decorations = calculateDecorations({
104
- state: newState,
105
- pluginState: {
106
- steps: newPluginState.steps,
107
- originalDoc: newPluginState.originalDoc,
108
- isDisplayingChanges: newPluginState.isDisplayingChanges
109
- }
110
- });
111
133
  return {
112
134
  ...newPluginState,
113
- decorations
135
+ decorations: newPluginState.decorations.map(tr.mapping, tr.doc)
114
136
  };
115
137
  }
116
138
  },
139
+ view(editorView) {
140
+ setEditorView(editorView);
141
+ return {};
142
+ },
117
143
  props: {
118
144
  decorations: state => {
119
145
  const pluginState = showDiffPluginKey.getState(state);
@@ -0,0 +1,95 @@
1
+ import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
2
+ import _createClass from "@babel/runtime/helpers/createClass";
3
+ import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
4
+
5
+ /**
6
+ * Utilities for working with ProseMirror node views and DOM serialization within the
7
+ * Show Diff editor plugin.
8
+ *
9
+ * This module centralizes:
10
+ * - Access to the editor's `nodeViews` registry (when available on `EditorView`)
11
+ * - Safe attempts to instantiate a node view for a given node, with a blocklist to
12
+ * avoid node types that are known to be problematic in this context (e.g. tables)
13
+ * - Schema-driven serialization of nodes and fragments to DOM via `DOMSerializer`
14
+ *
15
+ * The Show Diff decorations leverage this to either render nodes using their
16
+ * corresponding node view implementation, or fall back to DOM serialization.
17
+ */
18
+
19
+ /**
20
+ * Narrowed `EditorView` that exposes the internal `nodeViews` registry.
21
+ * Many editor instances provide this, but it's not part of the base type.
22
+ */
23
+
24
+ /**
25
+ * Type guard to detect whether an `EditorView` exposes a `nodeViews` map.
26
+ */
27
+ export function isEditorViewWithNodeViews(view) {
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ return view.nodeViews !== undefined;
30
+ }
31
+
32
+ /**
33
+ * Encapsulates DOM serialization and node view access/creation.
34
+ *
35
+ * Responsible for:
36
+ * - Creating a `DOMSerializer` from the provided schema
37
+ * - Reading `nodeViews` from an `EditorView` (if present) or using an explicit mapping
38
+ * - Preventing node view creation for blocklisted node types
39
+ */
40
+ export var NodeViewSerializer = /*#__PURE__*/function () {
41
+ function NodeViewSerializer(params) {
42
+ var _ref, _params$nodeViews, _this$editorView, _params$blocklist;
43
+ _classCallCheck(this, NodeViewSerializer);
44
+ this.serializer = DOMSerializer.fromSchema(params.schema);
45
+ if (params.editorView && isEditorViewWithNodeViews(params.editorView)) {
46
+ this.editorView = params.editorView;
47
+ }
48
+ this.nodeViews = (_ref = (_params$nodeViews = params.nodeViews) !== null && _params$nodeViews !== void 0 ? _params$nodeViews : (_this$editorView = this.editorView) === null || _this$editorView === void 0 ? void 0 : _this$editorView.nodeViews) !== null && _ref !== void 0 ? _ref : {};
49
+ this.nodeViewBlocklist = new Set((_params$blocklist = params.blocklist) !== null && _params$blocklist !== void 0 ? _params$blocklist : ['tableRow', 'table']);
50
+ }
51
+
52
+ /**
53
+ * Attempts to create a node view for the given node.
54
+ *
55
+ * Returns `null` when there is no `EditorView`, no constructor for the node type,
56
+ * or the node type is blocklisted. Otherwise returns the constructed node view instance.
57
+ */
58
+ return _createClass(NodeViewSerializer, [{
59
+ key: "tryCreateNodeView",
60
+ value: function tryCreateNodeView(targetNode) {
61
+ if (!this.editorView) {
62
+ return null;
63
+ }
64
+ var constructor = this.nodeViews[targetNode.type.name];
65
+ if (!constructor) {
66
+ return null;
67
+ }
68
+ if (this.nodeViewBlocklist.has(targetNode.type.name)) {
69
+ return null;
70
+ }
71
+ return constructor(targetNode, this.editorView, function () {
72
+ return 0;
73
+ }, [], {});
74
+ }
75
+
76
+ /**
77
+ * Serializes a node to a DOM `Node` using the schema's `DOMSerializer`.
78
+ */
79
+ }, {
80
+ key: "serializeNode",
81
+ value: function serializeNode(node) {
82
+ return this.serializer.serializeNode(node);
83
+ }
84
+
85
+ /**
86
+ * Serializes a fragment to a `DocumentFragment` using the schema's `DOMSerializer`.
87
+ */
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ }, {
90
+ key: "serializeFragment",
91
+ value: function serializeFragment(fragment) {
92
+ return this.serializer.serializeFragment(fragment);
93
+ }
94
+ }]);
95
+ }();
@@ -1,6 +1,6 @@
1
1
  import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
- import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
3
2
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
3
+ import { NodeViewSerializer } from './NodeViewSerializer';
4
4
  var style = convertToInlineCss({
5
5
  background: "var(--ds-background-accent-purple-subtlest, #F3F0FF)",
6
6
  textDecoration: 'underline',
@@ -8,7 +8,6 @@ var style = convertToInlineCss({
8
8
  textDecorationThickness: "var(--ds-space-025, 2px)",
9
9
  textDecorationColor: "var(--ds-border-accent-purple, #8270DB)"
10
10
  });
11
-
12
11
  /**
13
12
  * Inline decoration used for insertions as the content already exists in the document
14
13
  *
@@ -23,7 +22,18 @@ export var createInlineChangedDecoration = function createInlineChangedDecoratio
23
22
  };
24
23
  var deletedContentStyle = convertToInlineCss({
25
24
  color: "var(--ds-text-accent-gray, #44546F)",
26
- textDecoration: 'line-through'
25
+ textDecoration: 'line-through',
26
+ position: 'relative',
27
+ opacity: 0.6
28
+ });
29
+ var deletedContentStyleUnbounded = convertToInlineCss({
30
+ position: 'absolute',
31
+ top: '50%',
32
+ width: '100%',
33
+ display: 'inline-block',
34
+ borderTop: "1px solid ".concat("var(--ds-text-accent-gray, #44546F)"),
35
+ pointerEvents: 'none',
36
+ zIndex: 1
27
37
  });
28
38
 
29
39
  /**
@@ -37,7 +47,9 @@ var deletedContentStyle = convertToInlineCss({
37
47
  export var createDeletedContentDecoration = function createDeletedContentDecoration(_ref) {
38
48
  var change = _ref.change,
39
49
  doc = _ref.doc,
40
- tr = _ref.tr;
50
+ tr = _ref.tr,
51
+ editorView = _ref.editorView,
52
+ nodeViews = _ref.nodeViews;
41
53
  var dom = document.createElement('span');
42
54
  dom.setAttribute('style', deletedContentStyle);
43
55
 
@@ -47,22 +59,94 @@ export var createDeletedContentDecoration = function createDeletedContentDecorat
47
59
  * or sliced End depth is and match only the content and not with the entire node.
48
60
  */
49
61
  var slice = doc.slice(change.fromA, change.toA);
62
+ var nodeViewSerializer = new NodeViewSerializer({
63
+ schema: tr.doc.type.schema,
64
+ editorView: editorView,
65
+ nodeViews: nodeViews
66
+ });
50
67
  slice.content.forEach(function (node) {
51
- var serializer = DOMSerializer.fromSchema(tr.doc.type.schema);
68
+ // Create a wrapper for each node with strikethrough
69
+ var createWrapperWithStrikethrough = function createWrapperWithStrikethrough() {
70
+ var wrapper = document.createElement('span');
71
+ wrapper.style.position = 'relative';
72
+ wrapper.style.width = 'fit-content';
73
+ var strikethrough = document.createElement('span');
74
+ strikethrough.setAttribute('style', deletedContentStyleUnbounded);
75
+ wrapper.append(strikethrough);
76
+ return wrapper;
77
+ };
78
+
79
+ // Helper function to handle multiple child nodes
80
+ var handleMultipleChildNodes = function handleMultipleChildNodes(node) {
81
+ if (node.content.childCount > 1 && node.type.inlineContent) {
82
+ node.content.forEach(function (childNode) {
83
+ var childNodeView = nodeViewSerializer.tryCreateNodeView(childNode);
84
+ if (childNodeView) {
85
+ var lineBreak = document.createElement('br');
86
+ targetNode = node;
87
+ dom.append(lineBreak);
88
+ var wrapper = createWrapperWithStrikethrough();
89
+ wrapper.append(childNodeView.dom);
90
+ dom.append(wrapper);
91
+ } else {
92
+ // Fallback to serializing the individual child node
93
+ var serializedChild = nodeViewSerializer.serializeNode(childNode);
94
+ dom.append(serializedChild);
95
+ }
96
+ });
97
+ return true; // Indicates we handled multiple children
98
+ }
99
+ return false; // Indicates single child, continue with normal logic
100
+ };
101
+
102
+ // Determine which node to use and how to serialize
52
103
  var isFirst = slice.content.firstChild === node;
53
104
  var isLast = slice.content.lastChild === node;
54
- if (isFirst || isLast && slice.content.childCount > 2) {
55
- if (node.content.childCount > 0 && node.type.inlineContent === true) {
56
- dom.append(serializer.serializeFragment(node.content));
57
- } else {
58
- dom.append(serializer.serializeNode(node));
105
+ var hasInlineContent = node.content.childCount > 0 && node.type.inlineContent === true;
106
+ var targetNode;
107
+ var fallbackSerialization;
108
+ if ((isFirst || isLast && slice.content.childCount > 2) && hasInlineContent) {
109
+ if (handleMultipleChildNodes(node)) {
110
+ return;
59
111
  }
112
+ targetNode = node.content.content[0];
113
+ fallbackSerialization = function fallbackSerialization() {
114
+ return nodeViewSerializer.serializeFragment(node.content);
115
+ };
60
116
  } else if (isLast && slice.content.childCount === 2) {
61
- var lineBreak = document.createElement('br');
62
- dom.append(lineBreak);
63
- dom.append(serializer.serializeFragment(node.content));
117
+ if (handleMultipleChildNodes(node)) {
118
+ return;
119
+ }
120
+ targetNode = node;
121
+ fallbackSerialization = function fallbackSerialization() {
122
+ if (node.type.name === 'text') {
123
+ return document.createTextNode(node.text || '');
124
+ }
125
+ if (node.type.name === 'paragraph') {
126
+ var lineBreak = document.createElement('br');
127
+ dom.append(lineBreak);
128
+ return nodeViewSerializer.serializeFragment(node.content);
129
+ }
130
+ return nodeViewSerializer.serializeFragment(node.content);
131
+ };
132
+ } else {
133
+ if (handleMultipleChildNodes(node)) {
134
+ return;
135
+ }
136
+ targetNode = node.content.content[0] || node;
137
+ fallbackSerialization = function fallbackSerialization() {
138
+ return nodeViewSerializer.serializeNode(node);
139
+ };
140
+ }
141
+
142
+ // Try to create node view, fallback to serialization
143
+ var nodeView = nodeViewSerializer.tryCreateNodeView(targetNode);
144
+ if (nodeView) {
145
+ var wrapper = createWrapperWithStrikethrough();
146
+ wrapper.append(nodeView.dom);
147
+ dom.append(wrapper);
64
148
  } else {
65
- dom.append(serializer.serializeNode(node));
149
+ dom.append(fallbackSerialization());
66
150
  }
67
151
  });
68
152
  dom.setAttribute('data-testid', 'show-diff-deleted-decoration');
@@ -15,7 +15,9 @@ import { createInlineChangedDecoration, createDeletedContentDecoration } from '.
15
15
  import { getMarkChangeRanges } from './markDecorations';
16
16
  var calculateDecorations = function calculateDecorations(_ref) {
17
17
  var state = _ref.state,
18
- pluginState = _ref.pluginState;
18
+ pluginState = _ref.pluginState,
19
+ editorView = _ref.editorView,
20
+ nodeViews = _ref.nodeViews;
19
21
  var originalDoc = pluginState.originalDoc,
20
22
  steps = pluginState.steps;
21
23
  if (!originalDoc || !pluginState.isDisplayingChanges) {
@@ -53,7 +55,9 @@ var calculateDecorations = function calculateDecorations(_ref) {
53
55
  decorations.push(createDeletedContentDecoration({
54
56
  change: change,
55
57
  doc: originalDoc,
56
- tr: tr
58
+ tr: tr,
59
+ editorView: editorView,
60
+ nodeViews: nodeViews
57
61
  }));
58
62
  }
59
63
  });
@@ -64,60 +68,82 @@ var calculateDecorations = function calculateDecorations(_ref) {
64
68
  };
65
69
  export var showDiffPluginKey = new PluginKey('showDiffPlugin');
66
70
  export var createPlugin = function createPlugin(config) {
71
+ var editorView;
72
+ var setEditorView = function setEditorView(newEditorView) {
73
+ editorView = newEditorView;
74
+ };
67
75
  return new SafePlugin({
68
76
  key: showDiffPluginKey,
69
77
  state: {
70
78
  init: function init(_, state) {
71
- var _config$steps, _config$steps2;
79
+ var _config$steps, _config$steps2, _editorView;
72
80
  var schema = state.schema;
73
81
  var isDisplayingChanges = ((_config$steps = config === null || config === void 0 ? void 0 : config.steps) !== null && _config$steps !== void 0 ? _config$steps : []).length > 0;
82
+ var steps = ((_config$steps2 = config === null || config === void 0 ? void 0 : config.steps) !== null && _config$steps2 !== void 0 ? _config$steps2 : []).map(function (step) {
83
+ return ProseMirrorStep.fromJSON(schema, step);
84
+ });
85
+
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ var nodeViews = ((_editorView = editorView) === null || _editorView === void 0 ? void 0 : _editorView.nodeViews) || {};
74
88
  return {
75
- steps: ((_config$steps2 = config === null || config === void 0 ? void 0 : config.steps) !== null && _config$steps2 !== void 0 ? _config$steps2 : []).map(function (step) {
76
- return ProseMirrorStep.fromJSON(schema, step);
77
- }),
89
+ steps: steps,
78
90
  originalDoc: config !== null && config !== void 0 && config.originalDoc ? processRawValue(state.schema, config.originalDoc) : undefined,
79
91
  decorations: calculateDecorations({
80
92
  state: state,
81
93
  pluginState: {
82
- steps: [],
94
+ steps: steps,
83
95
  originalDoc: config !== null && config !== void 0 && config.originalDoc ? processRawValue(state.schema, config.originalDoc) : undefined,
84
96
  isDisplayingChanges: isDisplayingChanges
85
- }
97
+ },
98
+ editorView: editorView,
99
+ nodeViews: nodeViews
86
100
  }),
87
101
  isDisplayingChanges: isDisplayingChanges
88
102
  };
89
103
  },
90
104
  apply: function apply(tr, currentPluginState, oldState, newState) {
105
+ var _editorView2;
91
106
  var meta = tr.getMeta(showDiffPluginKey);
92
107
  var newPluginState = currentPluginState;
108
+
109
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
+ var nodeViews = ((_editorView2 = editorView) === null || _editorView2 === void 0 ? void 0 : _editorView2.nodeViews) || {};
93
111
  if (meta) {
94
112
  if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SHOW_DIFF') {
113
+ var _meta$steps, _meta$originalDoc;
114
+ // Calculate and store decorations in state
115
+ var decorations = calculateDecorations({
116
+ state: newState,
117
+ pluginState: {
118
+ steps: (_meta$steps = meta.steps) !== null && _meta$steps !== void 0 ? _meta$steps : currentPluginState.steps,
119
+ originalDoc: (_meta$originalDoc = meta.originalDoc) !== null && _meta$originalDoc !== void 0 ? _meta$originalDoc : currentPluginState.originalDoc,
120
+ isDisplayingChanges: true
121
+ },
122
+ editorView: editorView,
123
+ nodeViews: nodeViews
124
+ });
95
125
  newPluginState = _objectSpread(_objectSpread(_objectSpread({}, currentPluginState), meta), {}, {
126
+ decorations: decorations,
96
127
  isDisplayingChanges: true
97
128
  });
98
129
  } else if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'HIDE_DIFF') {
99
130
  newPluginState = _objectSpread(_objectSpread(_objectSpread({}, currentPluginState), meta), {}, {
131
+ decorations: DecorationSet.empty,
100
132
  isDisplayingChanges: false
101
133
  });
102
134
  } else {
103
135
  newPluginState = _objectSpread(_objectSpread({}, currentPluginState), meta);
104
136
  }
105
137
  }
106
-
107
- // Calculate and store decorations in state
108
- var decorations = calculateDecorations({
109
- state: newState,
110
- pluginState: {
111
- steps: newPluginState.steps,
112
- originalDoc: newPluginState.originalDoc,
113
- isDisplayingChanges: newPluginState.isDisplayingChanges
114
- }
115
- });
116
138
  return _objectSpread(_objectSpread({}, newPluginState), {}, {
117
- decorations: decorations
139
+ decorations: newPluginState.decorations.map(tr.mapping, tr.doc)
118
140
  });
119
141
  }
120
142
  },
143
+ view: function view(editorView) {
144
+ setEditorView(editorView);
145
+ return {};
146
+ },
121
147
  props: {
122
148
  decorations: function decorations(state) {
123
149
  var pluginState = showDiffPluginKey.getState(state);
@@ -0,0 +1,62 @@
1
+ import { type NodeViewConstructor } from '@atlaskit/editor-common/lazy-node-view';
2
+ import type { Schema, Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
4
+ /**
5
+ * Utilities for working with ProseMirror node views and DOM serialization within the
6
+ * Show Diff editor plugin.
7
+ *
8
+ * This module centralizes:
9
+ * - Access to the editor's `nodeViews` registry (when available on `EditorView`)
10
+ * - Safe attempts to instantiate a node view for a given node, with a blocklist to
11
+ * avoid node types that are known to be problematic in this context (e.g. tables)
12
+ * - Schema-driven serialization of nodes and fragments to DOM via `DOMSerializer`
13
+ *
14
+ * The Show Diff decorations leverage this to either render nodes using their
15
+ * corresponding node view implementation, or fall back to DOM serialization.
16
+ */
17
+ /**
18
+ * Narrowed `EditorView` that exposes the internal `nodeViews` registry.
19
+ * Many editor instances provide this, but it's not part of the base type.
20
+ */
21
+ export interface EditorViewWithNodeViews extends EditorView {
22
+ nodeViews: Record<string, NodeViewConstructor>;
23
+ }
24
+ /**
25
+ * Type guard to detect whether an `EditorView` exposes a `nodeViews` map.
26
+ */
27
+ export declare function isEditorViewWithNodeViews(view: EditorView): view is EditorViewWithNodeViews;
28
+ /**
29
+ * Encapsulates DOM serialization and node view access/creation.
30
+ *
31
+ * Responsible for:
32
+ * - Creating a `DOMSerializer` from the provided schema
33
+ * - Reading `nodeViews` from an `EditorView` (if present) or using an explicit mapping
34
+ * - Preventing node view creation for blocklisted node types
35
+ */
36
+ export declare class NodeViewSerializer {
37
+ private serializer;
38
+ private editorView?;
39
+ private nodeViews;
40
+ private nodeViewBlocklist;
41
+ constructor(params: {
42
+ schema: Schema;
43
+ editorView?: EditorView;
44
+ nodeViews?: Record<string, NodeViewConstructor>;
45
+ blocklist?: string[];
46
+ });
47
+ /**
48
+ * Attempts to create a node view for the given node.
49
+ *
50
+ * Returns `null` when there is no `EditorView`, no constructor for the node type,
51
+ * or the node type is blocklisted. Otherwise returns the constructed node view instance.
52
+ */
53
+ tryCreateNodeView(targetNode: PMNode): import("prosemirror-view").NodeView | null;
54
+ /**
55
+ * Serializes a node to a DOM `Node` using the schema's `DOMSerializer`.
56
+ */
57
+ serializeNode(node: PMNode): Node;
58
+ /**
59
+ * Serializes a fragment to a `DocumentFragment` using the schema's `DOMSerializer`.
60
+ */
61
+ serializeFragment(fragment: any): DocumentFragment | HTMLElement;
62
+ }
@@ -1,6 +1,8 @@
1
1
  import type { Change } from 'prosemirror-changeset';
2
+ import { type NodeViewConstructor } from '@atlaskit/editor-common/lazy-node-view';
2
3
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
4
  import type { Transaction } from '@atlaskit/editor-prosemirror/state';
5
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
4
6
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
5
7
  /**
6
8
  * Inline decoration used for insertions as the content already exists in the document
@@ -16,6 +18,8 @@ interface DeletedContentDecorationProps {
16
18
  change: Change;
17
19
  doc: PMNode;
18
20
  tr: Transaction;
21
+ nodeViews: Record<string, NodeViewConstructor>;
22
+ editorView?: EditorView;
19
23
  }
20
24
  /**
21
25
  * Creates a widget to represent the deleted content in the editor
@@ -25,5 +29,5 @@ interface DeletedContentDecorationProps {
25
29
  * @param props.tr The relevant transaction this decoration is being created against
26
30
  * @returns Prosemirror widget decoration
27
31
  */
28
- export declare const createDeletedContentDecoration: ({ change, doc, tr, }: DeletedContentDecorationProps) => Decoration;
32
+ export declare const createDeletedContentDecoration: ({ change, doc, tr, editorView, nodeViews, }: DeletedContentDecorationProps) => Decoration;
29
33
  export {};
@@ -0,0 +1,62 @@
1
+ import { type NodeViewConstructor } from '@atlaskit/editor-common/lazy-node-view';
2
+ import type { Schema, Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
4
+ /**
5
+ * Utilities for working with ProseMirror node views and DOM serialization within the
6
+ * Show Diff editor plugin.
7
+ *
8
+ * This module centralizes:
9
+ * - Access to the editor's `nodeViews` registry (when available on `EditorView`)
10
+ * - Safe attempts to instantiate a node view for a given node, with a blocklist to
11
+ * avoid node types that are known to be problematic in this context (e.g. tables)
12
+ * - Schema-driven serialization of nodes and fragments to DOM via `DOMSerializer`
13
+ *
14
+ * The Show Diff decorations leverage this to either render nodes using their
15
+ * corresponding node view implementation, or fall back to DOM serialization.
16
+ */
17
+ /**
18
+ * Narrowed `EditorView` that exposes the internal `nodeViews` registry.
19
+ * Many editor instances provide this, but it's not part of the base type.
20
+ */
21
+ export interface EditorViewWithNodeViews extends EditorView {
22
+ nodeViews: Record<string, NodeViewConstructor>;
23
+ }
24
+ /**
25
+ * Type guard to detect whether an `EditorView` exposes a `nodeViews` map.
26
+ */
27
+ export declare function isEditorViewWithNodeViews(view: EditorView): view is EditorViewWithNodeViews;
28
+ /**
29
+ * Encapsulates DOM serialization and node view access/creation.
30
+ *
31
+ * Responsible for:
32
+ * - Creating a `DOMSerializer` from the provided schema
33
+ * - Reading `nodeViews` from an `EditorView` (if present) or using an explicit mapping
34
+ * - Preventing node view creation for blocklisted node types
35
+ */
36
+ export declare class NodeViewSerializer {
37
+ private serializer;
38
+ private editorView?;
39
+ private nodeViews;
40
+ private nodeViewBlocklist;
41
+ constructor(params: {
42
+ schema: Schema;
43
+ editorView?: EditorView;
44
+ nodeViews?: Record<string, NodeViewConstructor>;
45
+ blocklist?: string[];
46
+ });
47
+ /**
48
+ * Attempts to create a node view for the given node.
49
+ *
50
+ * Returns `null` when there is no `EditorView`, no constructor for the node type,
51
+ * or the node type is blocklisted. Otherwise returns the constructed node view instance.
52
+ */
53
+ tryCreateNodeView(targetNode: PMNode): import("prosemirror-view").NodeView | null;
54
+ /**
55
+ * Serializes a node to a DOM `Node` using the schema's `DOMSerializer`.
56
+ */
57
+ serializeNode(node: PMNode): Node;
58
+ /**
59
+ * Serializes a fragment to a `DocumentFragment` using the schema's `DOMSerializer`.
60
+ */
61
+ serializeFragment(fragment: any): DocumentFragment | HTMLElement;
62
+ }
@@ -1,6 +1,8 @@
1
1
  import type { Change } from 'prosemirror-changeset';
2
+ import { type NodeViewConstructor } from '@atlaskit/editor-common/lazy-node-view';
2
3
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
4
  import type { Transaction } from '@atlaskit/editor-prosemirror/state';
5
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
4
6
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
5
7
  /**
6
8
  * Inline decoration used for insertions as the content already exists in the document
@@ -16,6 +18,8 @@ interface DeletedContentDecorationProps {
16
18
  change: Change;
17
19
  doc: PMNode;
18
20
  tr: Transaction;
21
+ nodeViews: Record<string, NodeViewConstructor>;
22
+ editorView?: EditorView;
19
23
  }
20
24
  /**
21
25
  * Creates a widget to represent the deleted content in the editor
@@ -25,5 +29,5 @@ interface DeletedContentDecorationProps {
25
29
  * @param props.tr The relevant transaction this decoration is being created against
26
30
  * @returns Prosemirror widget decoration
27
31
  */
28
- export declare const createDeletedContentDecoration: ({ change, doc, tr, }: DeletedContentDecorationProps) => Decoration;
32
+ export declare const createDeletedContentDecoration: ({ change, doc, tr, editorView, nodeViews, }: DeletedContentDecorationProps) => Decoration;
29
33
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-show-diff",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "ShowDiff plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -37,7 +37,7 @@
37
37
  "prosemirror-changeset": "^2.2.1"
38
38
  },
39
39
  "peerDependencies": {
40
- "@atlaskit/editor-common": "^107.23.0",
40
+ "@atlaskit/editor-common": "^107.25.0",
41
41
  "react": "^18.2.0"
42
42
  },
43
43
  "techstack": {