@atlaskit/editor-plugin-show-diff 0.1.2 → 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,12 @@
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
+
3
10
  ## 0.1.2
4
11
 
5
12
  ### 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,16 +74,23 @@ 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;
80
88
  var steps = ((_config$steps2 = config === null || config === void 0 ? void 0 : config.steps) !== null && _config$steps2 !== void 0 ? _config$steps2 : []).map(function (step) {
81
89
  return _transform.Step.fromJSON(schema, step);
82
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) || {};
83
94
  return {
84
95
  steps: steps,
85
96
  originalDoc: config !== null && config !== void 0 && config.originalDoc ? (0, _processRawValue.processRawValue)(state.schema, config.originalDoc) : undefined,
@@ -89,24 +100,33 @@ var createPlugin = exports.createPlugin = function createPlugin(config) {
89
100
  steps: steps,
90
101
  originalDoc: config !== null && config !== void 0 && config.originalDoc ? (0, _processRawValue.processRawValue)(state.schema, config.originalDoc) : undefined,
91
102
  isDisplayingChanges: isDisplayingChanges
92
- }
103
+ },
104
+ editorView: editorView,
105
+ nodeViews: nodeViews
93
106
  }),
94
107
  isDisplayingChanges: isDisplayingChanges
95
108
  };
96
109
  },
97
110
  apply: function apply(tr, currentPluginState, oldState, newState) {
111
+ var _editorView2;
98
112
  var meta = tr.getMeta(showDiffPluginKey);
99
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) || {};
100
117
  if (meta) {
101
118
  if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SHOW_DIFF') {
119
+ var _meta$steps, _meta$originalDoc;
102
120
  // Calculate and store decorations in state
103
121
  var decorations = calculateDecorations({
104
122
  state: newState,
105
123
  pluginState: {
106
- steps: meta.steps,
107
- originalDoc: meta.originalDoc,
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,
108
126
  isDisplayingChanges: true
109
- }
127
+ },
128
+ editorView: editorView,
129
+ nodeViews: nodeViews
110
130
  });
111
131
  newPluginState = _objectSpread(_objectSpread(_objectSpread({}, currentPluginState), meta), {}, {
112
132
  decorations: decorations,
@@ -126,6 +146,10 @@ var createPlugin = exports.createPlugin = function createPlugin(config) {
126
146
  });
127
147
  }
128
148
  },
149
+ view: function view(editorView) {
150
+ setEditorView(editorView);
151
+ return {};
152
+ },
129
153
  props: {
130
154
  decorations: function decorations(state) {
131
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,14 +58,21 @@ 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;
64
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) || {};
65
76
  return {
66
77
  steps,
67
78
  originalDoc: config !== null && config !== void 0 && config.originalDoc ? processRawValue(state.schema, config.originalDoc) : undefined,
@@ -71,24 +82,33 @@ export const createPlugin = config => {
71
82
  steps,
72
83
  originalDoc: config !== null && config !== void 0 && config.originalDoc ? processRawValue(state.schema, config.originalDoc) : undefined,
73
84
  isDisplayingChanges
74
- }
85
+ },
86
+ editorView,
87
+ nodeViews
75
88
  }),
76
89
  isDisplayingChanges
77
90
  };
78
91
  },
79
92
  apply: (tr, currentPluginState, oldState, newState) => {
93
+ var _editorView2;
80
94
  const meta = tr.getMeta(showDiffPluginKey);
81
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) || {};
82
99
  if (meta) {
83
100
  if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SHOW_DIFF') {
101
+ var _meta$steps, _meta$originalDoc;
84
102
  // Calculate and store decorations in state
85
103
  const decorations = calculateDecorations({
86
104
  state: newState,
87
105
  pluginState: {
88
- steps: meta.steps,
89
- originalDoc: meta.originalDoc,
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,
90
108
  isDisplayingChanges: true
91
- }
109
+ },
110
+ editorView,
111
+ nodeViews: nodeViews
92
112
  });
93
113
  newPluginState = {
94
114
  ...currentPluginState,
@@ -116,6 +136,10 @@ export const createPlugin = config => {
116
136
  };
117
137
  }
118
138
  },
139
+ view(editorView) {
140
+ setEditorView(editorView);
141
+ return {};
142
+ },
119
143
  props: {
120
144
  decorations: state => {
121
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,16 +68,23 @@ 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;
74
82
  var steps = ((_config$steps2 = config === null || config === void 0 ? void 0 : config.steps) !== null && _config$steps2 !== void 0 ? _config$steps2 : []).map(function (step) {
75
83
  return ProseMirrorStep.fromJSON(schema, step);
76
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) || {};
77
88
  return {
78
89
  steps: steps,
79
90
  originalDoc: config !== null && config !== void 0 && config.originalDoc ? processRawValue(state.schema, config.originalDoc) : undefined,
@@ -83,24 +94,33 @@ export var createPlugin = function createPlugin(config) {
83
94
  steps: steps,
84
95
  originalDoc: config !== null && config !== void 0 && config.originalDoc ? processRawValue(state.schema, config.originalDoc) : undefined,
85
96
  isDisplayingChanges: isDisplayingChanges
86
- }
97
+ },
98
+ editorView: editorView,
99
+ nodeViews: nodeViews
87
100
  }),
88
101
  isDisplayingChanges: isDisplayingChanges
89
102
  };
90
103
  },
91
104
  apply: function apply(tr, currentPluginState, oldState, newState) {
105
+ var _editorView2;
92
106
  var meta = tr.getMeta(showDiffPluginKey);
93
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) || {};
94
111
  if (meta) {
95
112
  if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SHOW_DIFF') {
113
+ var _meta$steps, _meta$originalDoc;
96
114
  // Calculate and store decorations in state
97
115
  var decorations = calculateDecorations({
98
116
  state: newState,
99
117
  pluginState: {
100
- steps: meta.steps,
101
- originalDoc: meta.originalDoc,
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,
102
120
  isDisplayingChanges: true
103
- }
121
+ },
122
+ editorView: editorView,
123
+ nodeViews: nodeViews
104
124
  });
105
125
  newPluginState = _objectSpread(_objectSpread(_objectSpread({}, currentPluginState), meta), {}, {
106
126
  decorations: decorations,
@@ -120,6 +140,10 @@ export var createPlugin = function createPlugin(config) {
120
140
  });
121
141
  }
122
142
  },
143
+ view: function view(editorView) {
144
+ setEditorView(editorView);
145
+ return {};
146
+ },
123
147
  props: {
124
148
  decorations: function decorations(state) {
125
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.2",
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": {