@atlaskit/editor-ssr-renderer 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @atlaskit/editor-ssr-renderer
2
2
 
3
+ ## 1.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`595b07a99bd65`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/595b07a99bd65) -
8
+ [https://hello.jira.atlassian.cloud/browse/EDITOR-3995](EDITOR-3995) - add toolbar supporting to
9
+ `EditorSSRRenderer`
10
+
11
+ ## 1.5.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [`cbf58f8500db4`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/cbf58f8500db4) -
16
+ [https://hello.jira.atlassian.cloud/browse/EDITOR-3893](EDITOR-3893) - fix mussing <br /> in empty
17
+ textblocks in the `EditorSSRRenderer`
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies
22
+
3
23
  ## 1.4.0
4
24
 
5
25
  ### Minor Changes
@@ -9,18 +9,18 @@ exports.EditorSSRRenderer = EditorSSRRenderer;
9
9
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
10
10
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
11
11
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
12
- var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
13
- var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
14
12
  var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
15
13
  var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
16
14
  var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
15
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
16
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
17
17
  var _react = _interopRequireWildcard(require("react"));
18
18
  var _view = require("@atlaskit/editor-prosemirror/view");
19
19
  var _state = require("@atlaskit/editor-prosemirror/state");
20
20
  var _model = require("@atlaskit/editor-prosemirror/model");
21
21
  var _eventDispatcher = require("@atlaskit/editor-common/event-dispatcher");
22
22
  var _providerFactory = require("@atlaskit/editor-common/provider-factory");
23
- var _excluded = ["plugins", "schema", "doc", "portalProviderAPI", "intl"];
23
+ var _excluded = ["plugins", "schema", "doc", "portalProviderAPI", "intl", "onEditorStateChanged"];
24
24
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
25
25
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
26
26
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -28,34 +28,127 @@ function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0,
28
28
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
29
29
  // The copy of type from prosemirror-view.
30
30
  // Probably, we need to fix this package exports and add `NodeViewConstructor` and `MarkViewConstructor` types here.
31
- var SSREditorView = /*#__PURE__*/function (_EditorView) {
32
- function SSREditorView() {
31
+ /**
32
+ * A lightweight EditorView implementation for SSR environments.
33
+ *
34
+ * It's kind of aggressive right now, we need to test and see how it works.
35
+ * Probably, we will need to implement more methods/properties in the future.
36
+ *
37
+ * If it doesn't work, we can extend `EditorView` directly instead of implementing `Partial<EditorView>`,
38
+ * but it will perform some DOM operation during the construction.
39
+ */
40
+ var SSREditorView = /*#__PURE__*/function () {
41
+ function SSREditorView(place, props) {
33
42
  (0, _classCallCheck2.default)(this, SSREditorView);
34
- return _callSuper(this, SSREditorView, arguments);
43
+ this.state = props.state;
44
+ this.dom = document.createElement('div');
45
+ this.editable = true;
46
+ this.dragging = null;
47
+ this.composing = false;
48
+ this.props = props;
49
+ this.root = document;
50
+ this.isDestroyed = false;
35
51
  }
36
- (0, _inherits2.default)(SSREditorView, _EditorView);
37
52
  return (0, _createClass2.default)(SSREditorView, [{
38
53
  key: "update",
39
54
  value: function update() {
40
- // Skip any updates in SSR
55
+ // No-op in SSR
41
56
  }
42
57
  }, {
43
58
  key: "setProps",
44
59
  value: function setProps() {
45
- // Skip any updates in SSR
60
+ // No-op in SSR
46
61
  }
47
62
  }, {
48
63
  key: "dispatchEvent",
49
64
  value: function dispatchEvent() {
50
- // Don't notify about events in SSR
65
+ // No-op in SSR
51
66
  }
52
67
  }, {
53
68
  key: "dispatch",
54
69
  value: function dispatch() {
55
- // Don't notify about events in SSR
70
+ // No-op in SSR
71
+ }
72
+ }, {
73
+ key: "hasFocus",
74
+ value: function hasFocus() {
75
+ return false;
76
+ }
77
+ }, {
78
+ key: "focus",
79
+ value: function focus() {
80
+ // No-op in SSR
81
+ }
82
+ }, {
83
+ key: "updateRoot",
84
+ value: function updateRoot() {
85
+ // No-op in SSR
86
+ }
87
+ }, {
88
+ key: "posAtCoords",
89
+ value: function posAtCoords() {
90
+ return null;
91
+ }
92
+ }, {
93
+ key: "coordsAtPos",
94
+ value: function coordsAtPos() {
95
+ return {
96
+ left: 0,
97
+ right: 0,
98
+ top: 0,
99
+ bottom: 0
100
+ };
101
+ }
102
+ }, {
103
+ key: "domAtPos",
104
+ value: function domAtPos() {
105
+ return {
106
+ node: this.root,
107
+ offset: 0
108
+ };
109
+ }
110
+ }, {
111
+ key: "nodeDOM",
112
+ value: function nodeDOM() {
113
+ return null;
114
+ }
115
+ }, {
116
+ key: "posAtDOM",
117
+ value: function posAtDOM() {
118
+ return 0;
119
+ }
120
+ }, {
121
+ key: "endOfTextblock",
122
+ value: function endOfTextblock() {
123
+ return false;
124
+ }
125
+ }, {
126
+ key: "pasteHTML",
127
+ value: function pasteHTML() {
128
+ return false;
129
+ }
130
+ }, {
131
+ key: "pasteText",
132
+ value: function pasteText() {
133
+ return false;
134
+ }
135
+ }, {
136
+ key: "destroy",
137
+ value: function destroy() {
138
+ // No-op in SSR
139
+ }
140
+ }, {
141
+ key: "updateState",
142
+ value: function updateState() {
143
+ // No-op in SSR
144
+ }
145
+ }, {
146
+ key: "someProp",
147
+ value: function someProp() {
148
+ return undefined;
56
149
  }
57
150
  }]);
58
- }(_view.EditorView);
151
+ }();
59
152
  var SSREventDispatcher = /*#__PURE__*/function (_EventDispatcher) {
60
153
  function SSREventDispatcher() {
61
154
  (0, _classCallCheck2.default)(this, SSREventDispatcher);
@@ -75,6 +168,7 @@ function EditorSSRRenderer(_ref) {
75
168
  doc = _ref.doc,
76
169
  portalProviderAPI = _ref.portalProviderAPI,
77
170
  intl = _ref.intl,
171
+ onEditorStateChanged = _ref.onEditorStateChanged,
78
172
  divProps = (0, _objectWithoutProperties2.default)(_ref, _excluded);
79
173
  // PMPluginFactoryParams use `getIntl` function to get current intl instance,
80
174
  // so we don't need to add `intl` as a dependency to `useMemo`.
@@ -121,15 +215,26 @@ function EditorSSRRenderer(_ref) {
121
215
  return Object.assign(acc, plugin.props.markViews);
122
216
  }, {});
123
217
  }, [pmPlugins]);
218
+ var editorState = (0, _react.useMemo)(function () {
219
+ return _state.EditorState.create({
220
+ doc: doc,
221
+ schema: schema,
222
+ plugins: pmPlugins
223
+ });
224
+ }, [doc, pmPlugins, schema]);
225
+
226
+ // In React 19 could be replaced by `useEffectEvent` hook.
227
+ var onEditorStateChangedRef = (0, _react.useRef)(onEditorStateChanged);
228
+ onEditorStateChangedRef.current = onEditorStateChanged;
229
+ (0, _react.useLayoutEffect)(function () {
230
+ var _onEditorStateChanged;
231
+ (_onEditorStateChanged = onEditorStateChangedRef.current) === null || _onEditorStateChanged === void 0 || _onEditorStateChanged.call(onEditorStateChangedRef, editorState);
232
+ }, [editorState]);
124
233
  var editorView = (0, _react.useMemo)(function () {
125
234
  return new SSREditorView(null, {
126
- state: _state.EditorState.create({
127
- doc: doc,
128
- schema: schema,
129
- plugins: pmPlugins
130
- })
235
+ state: editorState
131
236
  });
132
- }, [doc, pmPlugins, schema]);
237
+ }, [editorState]);
133
238
  var _useMemo = (0, _react.useMemo)(function () {
134
239
  var nodePositions = new WeakMap();
135
240
  var toDomNodeRenderers = Object.fromEntries(Object.entries(schema.nodes).map(function (_ref3) {
@@ -161,6 +266,31 @@ function EditorSSRRenderer(_ref) {
161
266
  var _nodePositions$get;
162
267
  return (_nodePositions$get = nodePositions.get(node)) !== null && _nodePositions$get !== void 0 ? _nodePositions$get : 0;
163
268
  }, [], _view.DecorationSet.create(node, []));
269
+
270
+ // ProseMirror View adds <br class="ProseMirror-trailingBreak" /> to empty nodes. Because we are using
271
+ // DOMSerializer, we should simulate the same behaviour to get the same HTML document.
272
+ //
273
+ // There are a lot of conditions that check for adding `<br />` but we could implement only the case when we
274
+ // are adding `<br />` to empty texblock, because if we add `<br />` in other cases it will change order of DOM nodes inside
275
+ // this node (`<br />`) will be the first, after will be other nodes. It's because we are adding `<br />` to root node before
276
+ // we are rendering child node.
277
+ //
278
+ // See: https://discuss.prosemirror.net/t/where-can-i-read-about-prosemirror-trailingbreak/6665
279
+ // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L803
280
+ // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L1365
281
+ if (nodeViewInstance.contentDOM &&
282
+ // if (this.node.isTextblock) updater.addTextblockHacks()
283
+ node.isTextblock &&
284
+ // !lastChild || // Empty textblock
285
+ !node.lastChild
286
+ // NOT IMPLEMENTED CASE !(lastChild instanceof TextViewDesc) ||
287
+ // NOT IMPLEMENTED CASE /\n$/.test(lastChild.node.text!) ||
288
+ // NOT IMPLEMENTED CASE (this.view.requiresGeckoHackNode && /\s$/.test(lastChild.node.text!))
289
+ ) {
290
+ var br = document.createElement('br');
291
+ br.classList.add('ProseMirror-trailingBreak');
292
+ nodeViewInstance.contentDOM.appendChild(br);
293
+ }
164
294
  return {
165
295
  dom: nodeViewInstance.dom,
166
296
  contentDOM: nodeViewInstance.contentDOM
@@ -1,5 +1,5 @@
1
1
  import React, { useMemo, useRef, useLayoutEffect } from 'react';
2
- import { EditorView, DecorationSet } from '@atlaskit/editor-prosemirror/view';
2
+ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
3
3
  import { EditorState } from '@atlaskit/editor-prosemirror/state';
4
4
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
5
5
  import { EventDispatcher, createDispatch } from '@atlaskit/editor-common/event-dispatcher';
@@ -8,18 +8,87 @@ import { ProviderFactory } from '@atlaskit/editor-common/provider-factory';
8
8
  // The copy of type from prosemirror-view.
9
9
  // Probably, we need to fix this package exports and add `NodeViewConstructor` and `MarkViewConstructor` types here.
10
10
 
11
- class SSREditorView extends EditorView {
11
+ /**
12
+ * A lightweight EditorView implementation for SSR environments.
13
+ *
14
+ * It's kind of aggressive right now, we need to test and see how it works.
15
+ * Probably, we will need to implement more methods/properties in the future.
16
+ *
17
+ * If it doesn't work, we can extend `EditorView` directly instead of implementing `Partial<EditorView>`,
18
+ * but it will perform some DOM operation during the construction.
19
+ */
20
+ class SSREditorView {
12
21
  update() {
13
- // Skip any updates in SSR
22
+ // No-op in SSR
14
23
  }
15
24
  setProps() {
16
- // Skip any updates in SSR
25
+ // No-op in SSR
17
26
  }
18
27
  dispatchEvent() {
19
- // Don't notify about events in SSR
28
+ // No-op in SSR
20
29
  }
21
30
  dispatch() {
22
- // Don't notify about events in SSR
31
+ // No-op in SSR
32
+ }
33
+ hasFocus() {
34
+ return false;
35
+ }
36
+ focus() {
37
+ // No-op in SSR
38
+ }
39
+ updateRoot() {
40
+ // No-op in SSR
41
+ }
42
+ posAtCoords() {
43
+ return null;
44
+ }
45
+ coordsAtPos() {
46
+ return {
47
+ left: 0,
48
+ right: 0,
49
+ top: 0,
50
+ bottom: 0
51
+ };
52
+ }
53
+ domAtPos() {
54
+ return {
55
+ node: this.root,
56
+ offset: 0
57
+ };
58
+ }
59
+ nodeDOM() {
60
+ return null;
61
+ }
62
+ posAtDOM() {
63
+ return 0;
64
+ }
65
+ endOfTextblock() {
66
+ return false;
67
+ }
68
+ pasteHTML() {
69
+ return false;
70
+ }
71
+ pasteText() {
72
+ return false;
73
+ }
74
+ destroy() {
75
+ // No-op in SSR
76
+ }
77
+ updateState() {
78
+ // No-op in SSR
79
+ }
80
+ someProp() {
81
+ return undefined;
82
+ }
83
+ constructor(place, props) {
84
+ this.state = props.state;
85
+ this.dom = document.createElement('div');
86
+ this.editable = true;
87
+ this.dragging = null;
88
+ this.composing = false;
89
+ this.props = props;
90
+ this.root = document;
91
+ this.isDestroyed = false;
23
92
  }
24
93
  }
25
94
  class SSREventDispatcher extends EventDispatcher {
@@ -33,6 +102,7 @@ export function EditorSSRRenderer({
33
102
  doc,
34
103
  portalProviderAPI,
35
104
  intl,
105
+ onEditorStateChanged,
36
106
  ...divProps
37
107
  }) {
38
108
  // PMPluginFactoryParams use `getIntl` function to get current intl instance,
@@ -79,15 +149,26 @@ export function EditorSSRRenderer({
79
149
  return Object.assign(acc, plugin.props.markViews);
80
150
  }, {});
81
151
  }, [pmPlugins]);
152
+ const editorState = useMemo(() => {
153
+ return EditorState.create({
154
+ doc,
155
+ schema,
156
+ plugins: pmPlugins
157
+ });
158
+ }, [doc, pmPlugins, schema]);
159
+
160
+ // In React 19 could be replaced by `useEffectEvent` hook.
161
+ const onEditorStateChangedRef = useRef(onEditorStateChanged);
162
+ onEditorStateChangedRef.current = onEditorStateChanged;
163
+ useLayoutEffect(() => {
164
+ var _onEditorStateChanged;
165
+ (_onEditorStateChanged = onEditorStateChangedRef.current) === null || _onEditorStateChanged === void 0 ? void 0 : _onEditorStateChanged.call(onEditorStateChangedRef, editorState);
166
+ }, [editorState]);
82
167
  const editorView = useMemo(() => {
83
168
  return new SSREditorView(null, {
84
- state: EditorState.create({
85
- doc,
86
- schema,
87
- plugins: pmPlugins
88
- })
169
+ state: editorState
89
170
  });
90
- }, [doc, pmPlugins, schema]);
171
+ }, [editorState]);
91
172
  const {
92
173
  serializer,
93
174
  nodePositions
@@ -105,6 +186,31 @@ export function EditorSSRRenderer({
105
186
  var _nodePositions$get;
106
187
  return (_nodePositions$get = nodePositions.get(node)) !== null && _nodePositions$get !== void 0 ? _nodePositions$get : 0;
107
188
  }, [], DecorationSet.create(node, []));
189
+
190
+ // ProseMirror View adds <br class="ProseMirror-trailingBreak" /> to empty nodes. Because we are using
191
+ // DOMSerializer, we should simulate the same behaviour to get the same HTML document.
192
+ //
193
+ // There are a lot of conditions that check for adding `<br />` but we could implement only the case when we
194
+ // are adding `<br />` to empty texblock, because if we add `<br />` in other cases it will change order of DOM nodes inside
195
+ // this node (`<br />`) will be the first, after will be other nodes. It's because we are adding `<br />` to root node before
196
+ // we are rendering child node.
197
+ //
198
+ // See: https://discuss.prosemirror.net/t/where-can-i-read-about-prosemirror-trailingbreak/6665
199
+ // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L803
200
+ // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L1365
201
+ if (nodeViewInstance.contentDOM &&
202
+ // if (this.node.isTextblock) updater.addTextblockHacks()
203
+ node.isTextblock &&
204
+ // !lastChild || // Empty textblock
205
+ !node.lastChild
206
+ // NOT IMPLEMENTED CASE !(lastChild instanceof TextViewDesc) ||
207
+ // NOT IMPLEMENTED CASE /\n$/.test(lastChild.node.text!) ||
208
+ // NOT IMPLEMENTED CASE (this.view.requiresGeckoHackNode && /\s$/.test(lastChild.node.text!))
209
+ ) {
210
+ const br = document.createElement('br');
211
+ br.classList.add('ProseMirror-trailingBreak');
212
+ nodeViewInstance.contentDOM.appendChild(br);
213
+ }
108
214
  return {
109
215
  dom: nodeViewInstance.dom,
110
216
  contentDOM: nodeViewInstance.contentDOM
@@ -1,18 +1,18 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
3
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
4
- import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
5
- import _createClass from "@babel/runtime/helpers/createClass";
6
4
  import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
7
5
  import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
8
6
  import _inherits from "@babel/runtime/helpers/inherits";
9
- var _excluded = ["plugins", "schema", "doc", "portalProviderAPI", "intl"];
7
+ import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
8
+ import _createClass from "@babel/runtime/helpers/createClass";
9
+ var _excluded = ["plugins", "schema", "doc", "portalProviderAPI", "intl", "onEditorStateChanged"];
10
10
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
11
11
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
12
12
  function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
13
13
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
14
14
  import React, { useMemo, useRef, useLayoutEffect } from 'react';
15
- import { EditorView, DecorationSet } from '@atlaskit/editor-prosemirror/view';
15
+ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
16
16
  import { EditorState } from '@atlaskit/editor-prosemirror/state';
17
17
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
18
18
  import { EventDispatcher, createDispatch } from '@atlaskit/editor-common/event-dispatcher';
@@ -20,34 +20,127 @@ import { ProviderFactory } from '@atlaskit/editor-common/provider-factory';
20
20
 
21
21
  // The copy of type from prosemirror-view.
22
22
  // Probably, we need to fix this package exports and add `NodeViewConstructor` and `MarkViewConstructor` types here.
23
- var SSREditorView = /*#__PURE__*/function (_EditorView) {
24
- function SSREditorView() {
23
+ /**
24
+ * A lightweight EditorView implementation for SSR environments.
25
+ *
26
+ * It's kind of aggressive right now, we need to test and see how it works.
27
+ * Probably, we will need to implement more methods/properties in the future.
28
+ *
29
+ * If it doesn't work, we can extend `EditorView` directly instead of implementing `Partial<EditorView>`,
30
+ * but it will perform some DOM operation during the construction.
31
+ */
32
+ var SSREditorView = /*#__PURE__*/function () {
33
+ function SSREditorView(place, props) {
25
34
  _classCallCheck(this, SSREditorView);
26
- return _callSuper(this, SSREditorView, arguments);
35
+ this.state = props.state;
36
+ this.dom = document.createElement('div');
37
+ this.editable = true;
38
+ this.dragging = null;
39
+ this.composing = false;
40
+ this.props = props;
41
+ this.root = document;
42
+ this.isDestroyed = false;
27
43
  }
28
- _inherits(SSREditorView, _EditorView);
29
44
  return _createClass(SSREditorView, [{
30
45
  key: "update",
31
46
  value: function update() {
32
- // Skip any updates in SSR
47
+ // No-op in SSR
33
48
  }
34
49
  }, {
35
50
  key: "setProps",
36
51
  value: function setProps() {
37
- // Skip any updates in SSR
52
+ // No-op in SSR
38
53
  }
39
54
  }, {
40
55
  key: "dispatchEvent",
41
56
  value: function dispatchEvent() {
42
- // Don't notify about events in SSR
57
+ // No-op in SSR
43
58
  }
44
59
  }, {
45
60
  key: "dispatch",
46
61
  value: function dispatch() {
47
- // Don't notify about events in SSR
62
+ // No-op in SSR
63
+ }
64
+ }, {
65
+ key: "hasFocus",
66
+ value: function hasFocus() {
67
+ return false;
68
+ }
69
+ }, {
70
+ key: "focus",
71
+ value: function focus() {
72
+ // No-op in SSR
73
+ }
74
+ }, {
75
+ key: "updateRoot",
76
+ value: function updateRoot() {
77
+ // No-op in SSR
78
+ }
79
+ }, {
80
+ key: "posAtCoords",
81
+ value: function posAtCoords() {
82
+ return null;
83
+ }
84
+ }, {
85
+ key: "coordsAtPos",
86
+ value: function coordsAtPos() {
87
+ return {
88
+ left: 0,
89
+ right: 0,
90
+ top: 0,
91
+ bottom: 0
92
+ };
93
+ }
94
+ }, {
95
+ key: "domAtPos",
96
+ value: function domAtPos() {
97
+ return {
98
+ node: this.root,
99
+ offset: 0
100
+ };
101
+ }
102
+ }, {
103
+ key: "nodeDOM",
104
+ value: function nodeDOM() {
105
+ return null;
106
+ }
107
+ }, {
108
+ key: "posAtDOM",
109
+ value: function posAtDOM() {
110
+ return 0;
111
+ }
112
+ }, {
113
+ key: "endOfTextblock",
114
+ value: function endOfTextblock() {
115
+ return false;
116
+ }
117
+ }, {
118
+ key: "pasteHTML",
119
+ value: function pasteHTML() {
120
+ return false;
121
+ }
122
+ }, {
123
+ key: "pasteText",
124
+ value: function pasteText() {
125
+ return false;
126
+ }
127
+ }, {
128
+ key: "destroy",
129
+ value: function destroy() {
130
+ // No-op in SSR
131
+ }
132
+ }, {
133
+ key: "updateState",
134
+ value: function updateState() {
135
+ // No-op in SSR
136
+ }
137
+ }, {
138
+ key: "someProp",
139
+ value: function someProp() {
140
+ return undefined;
48
141
  }
49
142
  }]);
50
- }(EditorView);
143
+ }();
51
144
  var SSREventDispatcher = /*#__PURE__*/function (_EventDispatcher) {
52
145
  function SSREventDispatcher() {
53
146
  _classCallCheck(this, SSREventDispatcher);
@@ -67,6 +160,7 @@ export function EditorSSRRenderer(_ref) {
67
160
  doc = _ref.doc,
68
161
  portalProviderAPI = _ref.portalProviderAPI,
69
162
  intl = _ref.intl,
163
+ onEditorStateChanged = _ref.onEditorStateChanged,
70
164
  divProps = _objectWithoutProperties(_ref, _excluded);
71
165
  // PMPluginFactoryParams use `getIntl` function to get current intl instance,
72
166
  // so we don't need to add `intl` as a dependency to `useMemo`.
@@ -113,15 +207,26 @@ export function EditorSSRRenderer(_ref) {
113
207
  return Object.assign(acc, plugin.props.markViews);
114
208
  }, {});
115
209
  }, [pmPlugins]);
210
+ var editorState = useMemo(function () {
211
+ return EditorState.create({
212
+ doc: doc,
213
+ schema: schema,
214
+ plugins: pmPlugins
215
+ });
216
+ }, [doc, pmPlugins, schema]);
217
+
218
+ // In React 19 could be replaced by `useEffectEvent` hook.
219
+ var onEditorStateChangedRef = useRef(onEditorStateChanged);
220
+ onEditorStateChangedRef.current = onEditorStateChanged;
221
+ useLayoutEffect(function () {
222
+ var _onEditorStateChanged;
223
+ (_onEditorStateChanged = onEditorStateChangedRef.current) === null || _onEditorStateChanged === void 0 || _onEditorStateChanged.call(onEditorStateChangedRef, editorState);
224
+ }, [editorState]);
116
225
  var editorView = useMemo(function () {
117
226
  return new SSREditorView(null, {
118
- state: EditorState.create({
119
- doc: doc,
120
- schema: schema,
121
- plugins: pmPlugins
122
- })
227
+ state: editorState
123
228
  });
124
- }, [doc, pmPlugins, schema]);
229
+ }, [editorState]);
125
230
  var _useMemo = useMemo(function () {
126
231
  var nodePositions = new WeakMap();
127
232
  var toDomNodeRenderers = Object.fromEntries(Object.entries(schema.nodes).map(function (_ref3) {
@@ -153,6 +258,31 @@ export function EditorSSRRenderer(_ref) {
153
258
  var _nodePositions$get;
154
259
  return (_nodePositions$get = nodePositions.get(node)) !== null && _nodePositions$get !== void 0 ? _nodePositions$get : 0;
155
260
  }, [], DecorationSet.create(node, []));
261
+
262
+ // ProseMirror View adds <br class="ProseMirror-trailingBreak" /> to empty nodes. Because we are using
263
+ // DOMSerializer, we should simulate the same behaviour to get the same HTML document.
264
+ //
265
+ // There are a lot of conditions that check for adding `<br />` but we could implement only the case when we
266
+ // are adding `<br />` to empty texblock, because if we add `<br />` in other cases it will change order of DOM nodes inside
267
+ // this node (`<br />`) will be the first, after will be other nodes. It's because we are adding `<br />` to root node before
268
+ // we are rendering child node.
269
+ //
270
+ // See: https://discuss.prosemirror.net/t/where-can-i-read-about-prosemirror-trailingbreak/6665
271
+ // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L803
272
+ // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L1365
273
+ if (nodeViewInstance.contentDOM &&
274
+ // if (this.node.isTextblock) updater.addTextblockHacks()
275
+ node.isTextblock &&
276
+ // !lastChild || // Empty textblock
277
+ !node.lastChild
278
+ // NOT IMPLEMENTED CASE !(lastChild instanceof TextViewDesc) ||
279
+ // NOT IMPLEMENTED CASE /\n$/.test(lastChild.node.text!) ||
280
+ // NOT IMPLEMENTED CASE (this.view.requiresGeckoHackNode && /\s$/.test(lastChild.node.text!))
281
+ ) {
282
+ var br = document.createElement('br');
283
+ br.classList.add('ProseMirror-trailingBreak');
284
+ nodeViewInstance.contentDOM.appendChild(br);
285
+ }
156
286
  return {
157
287
  dom: nodeViewInstance.dom,
158
288
  contentDOM: nodeViewInstance.contentDOM
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { IntlShape } from 'react-intl-next';
3
+ import { EditorState } from '@atlaskit/editor-prosemirror/state';
3
4
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
4
5
  import { type Schema } from '@atlaskit/editor-prosemirror/model';
5
6
  import type { EditorPlugin } from '@atlaskit/editor-common/types';
@@ -12,9 +13,10 @@ interface Props {
12
13
  doc: PMNode | undefined;
13
14
  id: string;
14
15
  intl: IntlShape;
16
+ onEditorStateChanged?: (state: EditorState) => void;
15
17
  plugins: EditorPlugin[];
16
18
  portalProviderAPI: PortalProviderAPI;
17
19
  schema: Schema;
18
20
  }
19
- export declare function EditorSSRRenderer({ plugins, schema, doc, portalProviderAPI, intl, ...divProps }: Props): React.JSX.Element;
21
+ export declare function EditorSSRRenderer({ plugins, schema, doc, portalProviderAPI, intl, onEditorStateChanged, ...divProps }: Props): React.JSX.Element;
20
22
  export {};
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { IntlShape } from 'react-intl-next';
3
+ import { EditorState } from '@atlaskit/editor-prosemirror/state';
3
4
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
4
5
  import { type Schema } from '@atlaskit/editor-prosemirror/model';
5
6
  import type { EditorPlugin } from '@atlaskit/editor-common/types';
@@ -12,9 +13,10 @@ interface Props {
12
13
  doc: PMNode | undefined;
13
14
  id: string;
14
15
  intl: IntlShape;
16
+ onEditorStateChanged?: (state: EditorState) => void;
15
17
  plugins: EditorPlugin[];
16
18
  portalProviderAPI: PortalProviderAPI;
17
19
  schema: Schema;
18
20
  }
19
- export declare function EditorSSRRenderer({ plugins, schema, doc, portalProviderAPI, intl, ...divProps }: Props): React.JSX.Element;
21
+ export declare function EditorSSRRenderer({ plugins, schema, doc, portalProviderAPI, intl, onEditorStateChanged, ...divProps }: Props): React.JSX.Element;
20
22
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-ssr-renderer",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "SSR Renderer based on Editor",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "atlassian": {
@@ -33,7 +33,7 @@
33
33
  "react-intl-next": "npm:react-intl@^5.18.1"
34
34
  },
35
35
  "peerDependencies": {
36
- "@atlaskit/editor-common": "^110.44.0",
36
+ "@atlaskit/editor-common": "^110.49.0",
37
37
  "react": "^18.2.0"
38
38
  }
39
39
  }