@atlaskit/prosemirror-collab 0.16.2 → 0.16.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 +9 -0
- package/dist/cjs/index.js +12 -3
- package/dist/cjs/movedContent.js +120 -0
- package/dist/es2019/index.js +10 -4
- package/dist/es2019/movedContent.js +110 -0
- package/dist/esm/index.js +11 -3
- package/dist/esm/movedContent.js +114 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/movedContent.d.ts +4 -0
- package/dist/types-ts4.5/index.d.ts +1 -1
- package/dist/types-ts4.5/movedContent.d.ts +4 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @atlaskit/prosemirror-collab
|
|
2
2
|
|
|
3
|
+
## 0.16.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#144227](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/144227)
|
|
8
|
+
[`6da190d8e3a24`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/6da190d8e3a24) -
|
|
9
|
+
Add ability to rebase drag and drop content
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
|
|
3
12
|
## 0.16.2
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
package/dist/cjs/index.js
CHANGED
|
@@ -4,6 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
+
exports.Rebaseable = void 0;
|
|
7
8
|
exports.collab = collab;
|
|
8
9
|
exports.getCollabState = getCollabState;
|
|
9
10
|
exports.getDocBeforeUnconfirmedSteps = getDocBeforeUnconfirmedSteps;
|
|
@@ -16,13 +17,13 @@ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/cl
|
|
|
16
17
|
var _uuid = require("uuid");
|
|
17
18
|
var _state = require("@atlaskit/editor-prosemirror/state");
|
|
18
19
|
var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
|
|
19
|
-
var
|
|
20
|
+
var _movedContent = require("./movedContent");
|
|
21
|
+
var Rebaseable = exports.Rebaseable = /*#__PURE__*/(0, _createClass2.default)(function Rebaseable(step, inverted, origin) {
|
|
20
22
|
(0, _classCallCheck2.default)(this, Rebaseable);
|
|
21
23
|
this.step = step;
|
|
22
24
|
this.inverted = inverted;
|
|
23
25
|
this.origin = origin;
|
|
24
|
-
});
|
|
25
|
-
/// redo them @internal
|
|
26
|
+
});
|
|
26
27
|
function rebaseSteps(steps, over, transform) {
|
|
27
28
|
for (var i = steps.length - 1; i >= 0; i--) {
|
|
28
29
|
transform.step(steps[i].inverted);
|
|
@@ -33,6 +34,7 @@ function rebaseSteps(steps, over, transform) {
|
|
|
33
34
|
var result = [];
|
|
34
35
|
for (var _i2 = 0, mapFrom = steps.length; _i2 < steps.length; _i2++) {
|
|
35
36
|
var mapped = steps[_i2].step.map(transform.mapping.slice(mapFrom));
|
|
37
|
+
var movedStep = (0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true) ? (0, _movedContent.mapStep)(steps, transform, _i2, mapped) : undefined;
|
|
36
38
|
mapFrom--;
|
|
37
39
|
if (mapped && !transform.maybeStep(mapped).failed) {
|
|
38
40
|
// Open ticket for setMirror https://github.com/ProseMirror/prosemirror/issues/869
|
|
@@ -40,6 +42,13 @@ function rebaseSteps(steps, over, transform) {
|
|
|
40
42
|
transform.mapping.setMirror(mapFrom, transform.steps.length - 1);
|
|
41
43
|
result.push(new Rebaseable(mapped, mapped.invert(transform.docs[transform.docs.length - 1]), steps[_i2].origin));
|
|
42
44
|
}
|
|
45
|
+
|
|
46
|
+
// If the step is a "move" step - apply the additional step
|
|
47
|
+
if ((0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true)) {
|
|
48
|
+
if (movedStep && !transform.maybeStep(movedStep).failed) {
|
|
49
|
+
result.push(new Rebaseable(movedStep, movedStep.invert(transform.docs[transform.docs.length - 1]), transform));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
43
52
|
}
|
|
44
53
|
return result;
|
|
45
54
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.mapStep = void 0;
|
|
7
|
+
var _model = require("@atlaskit/editor-prosemirror/model");
|
|
8
|
+
var _transform = require("@atlaskit/editor-prosemirror/transform");
|
|
9
|
+
var mapStep = exports.mapStep = function mapStep(steps, transform, index, mapped) {
|
|
10
|
+
if (index < 1) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
var previousRebaseableStep = steps[index - 1];
|
|
14
|
+
if (
|
|
15
|
+
// This checks the local steps are a "move" sequence
|
|
16
|
+
isMoveSequence(previousRebaseableStep.step, steps[index].step,
|
|
17
|
+
// Used to get the document prior to step changes
|
|
18
|
+
previousRebaseableStep)) {
|
|
19
|
+
var previousStep = transform.steps[transform.steps.length - 1];
|
|
20
|
+
// Creates a new step based on the "current" steps (partially through the rebase)
|
|
21
|
+
return createMoveMapStep(mapped, previousStep, transform);
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Only consider ReplaceStep (ReplaceAroundStep do not occur for moves)
|
|
27
|
+
var isReplaceTypeStep = function isReplaceTypeStep(step) {
|
|
28
|
+
return step instanceof _transform.ReplaceStep;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Determines if a step pairing is a move sequence (ie. drag + drop or cut + paste).
|
|
33
|
+
*
|
|
34
|
+
* We determine this if we have a deletion followed by insertion and their content matches
|
|
35
|
+
*
|
|
36
|
+
* @param previousStep
|
|
37
|
+
* @param currentStep
|
|
38
|
+
* @param previousRebaseable
|
|
39
|
+
*/
|
|
40
|
+
var isMoveSequence = function isMoveSequence(previousStep, currentStep, previousRebaseable) {
|
|
41
|
+
if (
|
|
42
|
+
// The both steps are replace
|
|
43
|
+
isReplaceTypeStep(previousStep) && isReplaceTypeStep(currentStep) &&
|
|
44
|
+
// The current step is a deletion
|
|
45
|
+
previousStep.slice.size === 0 &&
|
|
46
|
+
// The following step is an insertion with the same length that was deleted by the current step
|
|
47
|
+
Math.abs(previousStep.to - previousStep.from) === currentStep.slice.size) {
|
|
48
|
+
// Ensure we're getting the doc before our step changes so we can compare node contents
|
|
49
|
+
var originStepIndex = previousRebaseable.origin.steps.findIndex(function (s) {
|
|
50
|
+
return s === previousStep;
|
|
51
|
+
});
|
|
52
|
+
var originalDoc = previousRebaseable.origin.docs[originStepIndex];
|
|
53
|
+
var currentSlice = originalDoc.slice(previousStep.from, previousStep.to);
|
|
54
|
+
// The content from the deleted + inserted slice is exactly the same (cut + paste or drag + drop)
|
|
55
|
+
if (currentSlice.eq(currentStep.slice)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Update the replace step slice of the insert part of a move
|
|
64
|
+
* to contain the slice of the current document rather than what was sliced originally.
|
|
65
|
+
*
|
|
66
|
+
* @param mapped
|
|
67
|
+
* @param previousStep
|
|
68
|
+
* @param transform
|
|
69
|
+
* @returns Step to apply missing changes
|
|
70
|
+
*/
|
|
71
|
+
var createMoveMapStep = function createMoveMapStep(mapped, previousStep, transform) {
|
|
72
|
+
if (!isReplaceTypeStep(previousStep) || mapped && !isReplaceTypeStep(mapped) || !mapped) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
var newSlice = transform.docs[transform.docs.length - 1].slice(previousStep === null || previousStep === void 0 ? void 0 : previousStep.from, previousStep === null || previousStep === void 0 ? void 0 : previousStep.to);
|
|
76
|
+
var diff = getDiffRange(mapped.slice.content, newSlice.content);
|
|
77
|
+
if (diff === undefined) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
var start = mapped.from + diff.start;
|
|
81
|
+
var offset = newSlice.content.size - mapped.slice.content.size === 0 ? diff.end - diff.start : 0;
|
|
82
|
+
|
|
83
|
+
// If the new slice is smaller then we're doing a deletion of content - this needs
|
|
84
|
+
// to be a replace step with empty content to delete content
|
|
85
|
+
if (newSlice.content.size - mapped.slice.content.size < 0) {
|
|
86
|
+
return new _transform.ReplaceStep(start, start + mapped.slice.content.size - newSlice.content.size, _model.Slice.empty);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Replace the diff range with the latest content in the document (at the old position)
|
|
90
|
+
return new _transform.ReplaceStep(start, start + offset, transform.docs[transform.docs.length - 1].slice((previousStep === null || previousStep === void 0 ? void 0 : previousStep.from) + diff.start, (previousStep === null || previousStep === void 0 ? void 0 : previousStep.from) + diff.end));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get start and end diff position values for two fragments (old, new).
|
|
95
|
+
* @param {Fragment} before - content that is planned to move
|
|
96
|
+
* @param {Fragment} after - content that was in paragraph being deleted (updated content)
|
|
97
|
+
* @returns {object} - { start, end }
|
|
98
|
+
*/
|
|
99
|
+
function getDiffRange(before, after) {
|
|
100
|
+
// https://prosemirror.net/docs/ref/#model.Fragment.findDiffStart
|
|
101
|
+
var start = before.findDiffStart(after);
|
|
102
|
+
// Important: diffEnd value is {a,b} object since end pos will differ.
|
|
103
|
+
// https://prosemirror.net/docs/ref/#model.Fragment.findDiffEnd
|
|
104
|
+
var diffEnd = before.findDiffEnd(after);
|
|
105
|
+
if (start === null || diffEnd === null) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
// WARNING: diffEnd may be lower than diffStart.
|
|
109
|
+
// If so, add overlap to get correct range.
|
|
110
|
+
// https://discuss.prosemirror.net/t/overlap-handling-of-finddiffstart-and-finddiffend/2856
|
|
111
|
+
var overlap = start - Math.min(diffEnd.a, diffEnd.b);
|
|
112
|
+
var end = diffEnd.b;
|
|
113
|
+
if (overlap > 0) {
|
|
114
|
+
end += overlap;
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
start: start,
|
|
118
|
+
end: end
|
|
119
|
+
};
|
|
120
|
+
}
|
package/dist/es2019/index.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from 'uuid';
|
|
2
2
|
import { Plugin, PluginKey, TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
3
3
|
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
4
|
-
|
|
4
|
+
import { mapStep } from './movedContent';
|
|
5
|
+
export class Rebaseable {
|
|
5
6
|
constructor(step, inverted, origin) {
|
|
6
7
|
this.step = step;
|
|
7
8
|
this.inverted = inverted;
|
|
8
9
|
this.origin = origin;
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
/// Undo a given set of steps, apply a set of other steps, and then
|
|
13
|
-
/// redo them @internal
|
|
14
12
|
export function rebaseSteps(steps, over, transform) {
|
|
15
13
|
for (let i = steps.length - 1; i >= 0; i--) {
|
|
16
14
|
transform.step(steps[i].inverted);
|
|
@@ -21,6 +19,7 @@ export function rebaseSteps(steps, over, transform) {
|
|
|
21
19
|
const result = [];
|
|
22
20
|
for (let i = 0, mapFrom = steps.length; i < steps.length; i++) {
|
|
23
21
|
const mapped = steps[i].step.map(transform.mapping.slice(mapFrom));
|
|
22
|
+
const movedStep = editorExperiment('platform_editor_offline_editing_web', true) ? mapStep(steps, transform, i, mapped) : undefined;
|
|
24
23
|
mapFrom--;
|
|
25
24
|
if (mapped && !transform.maybeStep(mapped).failed) {
|
|
26
25
|
// Open ticket for setMirror https://github.com/ProseMirror/prosemirror/issues/869
|
|
@@ -28,6 +27,13 @@ export function rebaseSteps(steps, over, transform) {
|
|
|
28
27
|
transform.mapping.setMirror(mapFrom, transform.steps.length - 1);
|
|
29
28
|
result.push(new Rebaseable(mapped, mapped.invert(transform.docs[transform.docs.length - 1]), steps[i].origin));
|
|
30
29
|
}
|
|
30
|
+
|
|
31
|
+
// If the step is a "move" step - apply the additional step
|
|
32
|
+
if (editorExperiment('platform_editor_offline_editing_web', true)) {
|
|
33
|
+
if (movedStep && !transform.maybeStep(movedStep).failed) {
|
|
34
|
+
result.push(new Rebaseable(movedStep, movedStep.invert(transform.docs[transform.docs.length - 1]), transform));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
31
37
|
}
|
|
32
38
|
return result;
|
|
33
39
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Slice } from '@atlaskit/editor-prosemirror/model';
|
|
2
|
+
import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
3
|
+
export const mapStep = (steps, transform, index, mapped) => {
|
|
4
|
+
if (index < 1) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
const previousRebaseableStep = steps[index - 1];
|
|
8
|
+
if (
|
|
9
|
+
// This checks the local steps are a "move" sequence
|
|
10
|
+
isMoveSequence(previousRebaseableStep.step, steps[index].step,
|
|
11
|
+
// Used to get the document prior to step changes
|
|
12
|
+
previousRebaseableStep)) {
|
|
13
|
+
const previousStep = transform.steps[transform.steps.length - 1];
|
|
14
|
+
// Creates a new step based on the "current" steps (partially through the rebase)
|
|
15
|
+
return createMoveMapStep(mapped, previousStep, transform);
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Only consider ReplaceStep (ReplaceAroundStep do not occur for moves)
|
|
21
|
+
const isReplaceTypeStep = step => step instanceof ReplaceStep;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Determines if a step pairing is a move sequence (ie. drag + drop or cut + paste).
|
|
25
|
+
*
|
|
26
|
+
* We determine this if we have a deletion followed by insertion and their content matches
|
|
27
|
+
*
|
|
28
|
+
* @param previousStep
|
|
29
|
+
* @param currentStep
|
|
30
|
+
* @param previousRebaseable
|
|
31
|
+
*/
|
|
32
|
+
const isMoveSequence = (previousStep, currentStep, previousRebaseable) => {
|
|
33
|
+
if (
|
|
34
|
+
// The both steps are replace
|
|
35
|
+
isReplaceTypeStep(previousStep) && isReplaceTypeStep(currentStep) &&
|
|
36
|
+
// The current step is a deletion
|
|
37
|
+
previousStep.slice.size === 0 &&
|
|
38
|
+
// The following step is an insertion with the same length that was deleted by the current step
|
|
39
|
+
Math.abs(previousStep.to - previousStep.from) === currentStep.slice.size) {
|
|
40
|
+
// Ensure we're getting the doc before our step changes so we can compare node contents
|
|
41
|
+
const originStepIndex = previousRebaseable.origin.steps.findIndex(s => s === previousStep);
|
|
42
|
+
const originalDoc = previousRebaseable.origin.docs[originStepIndex];
|
|
43
|
+
const currentSlice = originalDoc.slice(previousStep.from, previousStep.to);
|
|
44
|
+
// The content from the deleted + inserted slice is exactly the same (cut + paste or drag + drop)
|
|
45
|
+
if (currentSlice.eq(currentStep.slice)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Update the replace step slice of the insert part of a move
|
|
54
|
+
* to contain the slice of the current document rather than what was sliced originally.
|
|
55
|
+
*
|
|
56
|
+
* @param mapped
|
|
57
|
+
* @param previousStep
|
|
58
|
+
* @param transform
|
|
59
|
+
* @returns Step to apply missing changes
|
|
60
|
+
*/
|
|
61
|
+
const createMoveMapStep = (mapped, previousStep, transform) => {
|
|
62
|
+
if (!isReplaceTypeStep(previousStep) || mapped && !isReplaceTypeStep(mapped) || !mapped) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const newSlice = transform.docs[transform.docs.length - 1].slice(previousStep === null || previousStep === void 0 ? void 0 : previousStep.from, previousStep === null || previousStep === void 0 ? void 0 : previousStep.to);
|
|
66
|
+
const diff = getDiffRange(mapped.slice.content, newSlice.content);
|
|
67
|
+
if (diff === undefined) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const start = mapped.from + diff.start;
|
|
71
|
+
const offset = newSlice.content.size - mapped.slice.content.size === 0 ? diff.end - diff.start : 0;
|
|
72
|
+
|
|
73
|
+
// If the new slice is smaller then we're doing a deletion of content - this needs
|
|
74
|
+
// to be a replace step with empty content to delete content
|
|
75
|
+
if (newSlice.content.size - mapped.slice.content.size < 0) {
|
|
76
|
+
return new ReplaceStep(start, start + mapped.slice.content.size - newSlice.content.size, Slice.empty);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Replace the diff range with the latest content in the document (at the old position)
|
|
80
|
+
return new ReplaceStep(start, start + offset, transform.docs[transform.docs.length - 1].slice((previousStep === null || previousStep === void 0 ? void 0 : previousStep.from) + diff.start, (previousStep === null || previousStep === void 0 ? void 0 : previousStep.from) + diff.end));
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get start and end diff position values for two fragments (old, new).
|
|
85
|
+
* @param {Fragment} before - content that is planned to move
|
|
86
|
+
* @param {Fragment} after - content that was in paragraph being deleted (updated content)
|
|
87
|
+
* @returns {object} - { start, end }
|
|
88
|
+
*/
|
|
89
|
+
function getDiffRange(before, after) {
|
|
90
|
+
// https://prosemirror.net/docs/ref/#model.Fragment.findDiffStart
|
|
91
|
+
const start = before.findDiffStart(after);
|
|
92
|
+
// Important: diffEnd value is {a,b} object since end pos will differ.
|
|
93
|
+
// https://prosemirror.net/docs/ref/#model.Fragment.findDiffEnd
|
|
94
|
+
const diffEnd = before.findDiffEnd(after);
|
|
95
|
+
if (start === null || diffEnd === null) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
// WARNING: diffEnd may be lower than diffStart.
|
|
99
|
+
// If so, add overlap to get correct range.
|
|
100
|
+
// https://discuss.prosemirror.net/t/overlap-handling-of-finddiffstart-and-finddiffend/2856
|
|
101
|
+
const overlap = start - Math.min(diffEnd.a, diffEnd.b);
|
|
102
|
+
let end = diffEnd.b;
|
|
103
|
+
if (overlap > 0) {
|
|
104
|
+
end += overlap;
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
start,
|
|
108
|
+
end
|
|
109
|
+
};
|
|
110
|
+
}
|
package/dist/esm/index.js
CHANGED
|
@@ -3,13 +3,13 @@ import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
4
|
import { Plugin, PluginKey, TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
5
5
|
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
6
|
-
|
|
6
|
+
import { mapStep } from './movedContent';
|
|
7
|
+
export var Rebaseable = /*#__PURE__*/_createClass(function Rebaseable(step, inverted, origin) {
|
|
7
8
|
_classCallCheck(this, Rebaseable);
|
|
8
9
|
this.step = step;
|
|
9
10
|
this.inverted = inverted;
|
|
10
11
|
this.origin = origin;
|
|
11
|
-
});
|
|
12
|
-
/// redo them @internal
|
|
12
|
+
});
|
|
13
13
|
export function rebaseSteps(steps, over, transform) {
|
|
14
14
|
for (var i = steps.length - 1; i >= 0; i--) {
|
|
15
15
|
transform.step(steps[i].inverted);
|
|
@@ -20,6 +20,7 @@ export function rebaseSteps(steps, over, transform) {
|
|
|
20
20
|
var result = [];
|
|
21
21
|
for (var _i2 = 0, mapFrom = steps.length; _i2 < steps.length; _i2++) {
|
|
22
22
|
var mapped = steps[_i2].step.map(transform.mapping.slice(mapFrom));
|
|
23
|
+
var movedStep = editorExperiment('platform_editor_offline_editing_web', true) ? mapStep(steps, transform, _i2, mapped) : undefined;
|
|
23
24
|
mapFrom--;
|
|
24
25
|
if (mapped && !transform.maybeStep(mapped).failed) {
|
|
25
26
|
// Open ticket for setMirror https://github.com/ProseMirror/prosemirror/issues/869
|
|
@@ -27,6 +28,13 @@ export function rebaseSteps(steps, over, transform) {
|
|
|
27
28
|
transform.mapping.setMirror(mapFrom, transform.steps.length - 1);
|
|
28
29
|
result.push(new Rebaseable(mapped, mapped.invert(transform.docs[transform.docs.length - 1]), steps[_i2].origin));
|
|
29
30
|
}
|
|
31
|
+
|
|
32
|
+
// If the step is a "move" step - apply the additional step
|
|
33
|
+
if (editorExperiment('platform_editor_offline_editing_web', true)) {
|
|
34
|
+
if (movedStep && !transform.maybeStep(movedStep).failed) {
|
|
35
|
+
result.push(new Rebaseable(movedStep, movedStep.invert(transform.docs[transform.docs.length - 1]), transform));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
30
38
|
}
|
|
31
39
|
return result;
|
|
32
40
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Slice } from '@atlaskit/editor-prosemirror/model';
|
|
2
|
+
import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
3
|
+
export var mapStep = function mapStep(steps, transform, index, mapped) {
|
|
4
|
+
if (index < 1) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
var previousRebaseableStep = steps[index - 1];
|
|
8
|
+
if (
|
|
9
|
+
// This checks the local steps are a "move" sequence
|
|
10
|
+
isMoveSequence(previousRebaseableStep.step, steps[index].step,
|
|
11
|
+
// Used to get the document prior to step changes
|
|
12
|
+
previousRebaseableStep)) {
|
|
13
|
+
var previousStep = transform.steps[transform.steps.length - 1];
|
|
14
|
+
// Creates a new step based on the "current" steps (partially through the rebase)
|
|
15
|
+
return createMoveMapStep(mapped, previousStep, transform);
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Only consider ReplaceStep (ReplaceAroundStep do not occur for moves)
|
|
21
|
+
var isReplaceTypeStep = function isReplaceTypeStep(step) {
|
|
22
|
+
return step instanceof ReplaceStep;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Determines if a step pairing is a move sequence (ie. drag + drop or cut + paste).
|
|
27
|
+
*
|
|
28
|
+
* We determine this if we have a deletion followed by insertion and their content matches
|
|
29
|
+
*
|
|
30
|
+
* @param previousStep
|
|
31
|
+
* @param currentStep
|
|
32
|
+
* @param previousRebaseable
|
|
33
|
+
*/
|
|
34
|
+
var isMoveSequence = function isMoveSequence(previousStep, currentStep, previousRebaseable) {
|
|
35
|
+
if (
|
|
36
|
+
// The both steps are replace
|
|
37
|
+
isReplaceTypeStep(previousStep) && isReplaceTypeStep(currentStep) &&
|
|
38
|
+
// The current step is a deletion
|
|
39
|
+
previousStep.slice.size === 0 &&
|
|
40
|
+
// The following step is an insertion with the same length that was deleted by the current step
|
|
41
|
+
Math.abs(previousStep.to - previousStep.from) === currentStep.slice.size) {
|
|
42
|
+
// Ensure we're getting the doc before our step changes so we can compare node contents
|
|
43
|
+
var originStepIndex = previousRebaseable.origin.steps.findIndex(function (s) {
|
|
44
|
+
return s === previousStep;
|
|
45
|
+
});
|
|
46
|
+
var originalDoc = previousRebaseable.origin.docs[originStepIndex];
|
|
47
|
+
var currentSlice = originalDoc.slice(previousStep.from, previousStep.to);
|
|
48
|
+
// The content from the deleted + inserted slice is exactly the same (cut + paste or drag + drop)
|
|
49
|
+
if (currentSlice.eq(currentStep.slice)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Update the replace step slice of the insert part of a move
|
|
58
|
+
* to contain the slice of the current document rather than what was sliced originally.
|
|
59
|
+
*
|
|
60
|
+
* @param mapped
|
|
61
|
+
* @param previousStep
|
|
62
|
+
* @param transform
|
|
63
|
+
* @returns Step to apply missing changes
|
|
64
|
+
*/
|
|
65
|
+
var createMoveMapStep = function createMoveMapStep(mapped, previousStep, transform) {
|
|
66
|
+
if (!isReplaceTypeStep(previousStep) || mapped && !isReplaceTypeStep(mapped) || !mapped) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
var newSlice = transform.docs[transform.docs.length - 1].slice(previousStep === null || previousStep === void 0 ? void 0 : previousStep.from, previousStep === null || previousStep === void 0 ? void 0 : previousStep.to);
|
|
70
|
+
var diff = getDiffRange(mapped.slice.content, newSlice.content);
|
|
71
|
+
if (diff === undefined) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
var start = mapped.from + diff.start;
|
|
75
|
+
var offset = newSlice.content.size - mapped.slice.content.size === 0 ? diff.end - diff.start : 0;
|
|
76
|
+
|
|
77
|
+
// If the new slice is smaller then we're doing a deletion of content - this needs
|
|
78
|
+
// to be a replace step with empty content to delete content
|
|
79
|
+
if (newSlice.content.size - mapped.slice.content.size < 0) {
|
|
80
|
+
return new ReplaceStep(start, start + mapped.slice.content.size - newSlice.content.size, Slice.empty);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Replace the diff range with the latest content in the document (at the old position)
|
|
84
|
+
return new ReplaceStep(start, start + offset, transform.docs[transform.docs.length - 1].slice((previousStep === null || previousStep === void 0 ? void 0 : previousStep.from) + diff.start, (previousStep === null || previousStep === void 0 ? void 0 : previousStep.from) + diff.end));
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get start and end diff position values for two fragments (old, new).
|
|
89
|
+
* @param {Fragment} before - content that is planned to move
|
|
90
|
+
* @param {Fragment} after - content that was in paragraph being deleted (updated content)
|
|
91
|
+
* @returns {object} - { start, end }
|
|
92
|
+
*/
|
|
93
|
+
function getDiffRange(before, after) {
|
|
94
|
+
// https://prosemirror.net/docs/ref/#model.Fragment.findDiffStart
|
|
95
|
+
var start = before.findDiffStart(after);
|
|
96
|
+
// Important: diffEnd value is {a,b} object since end pos will differ.
|
|
97
|
+
// https://prosemirror.net/docs/ref/#model.Fragment.findDiffEnd
|
|
98
|
+
var diffEnd = before.findDiffEnd(after);
|
|
99
|
+
if (start === null || diffEnd === null) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
// WARNING: diffEnd may be lower than diffStart.
|
|
103
|
+
// If so, add overlap to get correct range.
|
|
104
|
+
// https://discuss.prosemirror.net/t/overlap-handling-of-finddiffstart-and-finddiffend/2856
|
|
105
|
+
var overlap = start - Math.min(diffEnd.a, diffEnd.b);
|
|
106
|
+
var end = diffEnd.b;
|
|
107
|
+
if (overlap > 0) {
|
|
108
|
+
end += overlap;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
start: start,
|
|
112
|
+
end: end
|
|
113
|
+
};
|
|
114
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EditorState, Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
2
2
|
import { Plugin } from '@atlaskit/editor-prosemirror/state';
|
|
3
3
|
import type { Step as ProseMirrorStep, Transform as ProseMirrorTransform } from '@atlaskit/editor-prosemirror/transform';
|
|
4
|
-
declare class Rebaseable {
|
|
4
|
+
export declare class Rebaseable {
|
|
5
5
|
readonly step: ProseMirrorStep;
|
|
6
6
|
readonly inverted: ProseMirrorStep;
|
|
7
7
|
readonly origin: ProseMirrorTransform;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
2
|
+
import type { Step as ProseMirrorStep, Transform as ProseMirrorTransform } from '@atlaskit/editor-prosemirror/transform';
|
|
3
|
+
import type { Rebaseable } from './index';
|
|
4
|
+
export declare const mapStep: (steps: readonly Rebaseable[], transform: ProseMirrorTransform, index: number, mapped: ProseMirrorStep | null) => ReplaceStep | undefined;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EditorState, Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
2
2
|
import { Plugin } from '@atlaskit/editor-prosemirror/state';
|
|
3
3
|
import type { Step as ProseMirrorStep, Transform as ProseMirrorTransform } from '@atlaskit/editor-prosemirror/transform';
|
|
4
|
-
declare class Rebaseable {
|
|
4
|
+
export declare class Rebaseable {
|
|
5
5
|
readonly step: ProseMirrorStep;
|
|
6
6
|
readonly inverted: ProseMirrorStep;
|
|
7
7
|
readonly origin: ProseMirrorTransform;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
2
|
+
import type { Step as ProseMirrorStep, Transform as ProseMirrorTransform } from '@atlaskit/editor-prosemirror/transform';
|
|
3
|
+
import type { Rebaseable } from './index';
|
|
4
|
+
export declare const mapStep: (steps: readonly Rebaseable[], transform: ProseMirrorTransform, index: number, mapped: ProseMirrorStep | null) => ReplaceStep | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/prosemirror-collab",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.3",
|
|
4
4
|
"description": "Collaborative editing for ProseMirror - Atlassian Fork",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@atlaskit/editor-prosemirror": "7.0.0",
|
|
34
|
-
"@atlaskit/tmp-editor-statsig": "^4.
|
|
34
|
+
"@atlaskit/tmp-editor-statsig": "^4.21.0",
|
|
35
35
|
"@babel/runtime": "^7.0.0",
|
|
36
36
|
"uuid": "^3.1.0"
|
|
37
37
|
},
|