mirror of
https://github.com/codex-team/editor.js
synced 2024-06-08 00:42:31 +02:00
feat(collaborative): function for getting changes from string (#2101)
* create function for getting changes from a string * remove useless variables * add more test cases * refactor: return array of operations * refactor: rename function, conditions, add docs * refactor: use interfeces, move them to separate file, add comments * refactor: rename test cases * refactor: rename test cases * refactor: change types schema * refactor: rename function * test: add the case for multiple changes
This commit is contained in:
parent
0f77b7586d
commit
a513e1079a
|
@ -0,0 +1,100 @@
|
|||
import { InsertOperation, OperationType, RemoveOperation } from './types';
|
||||
|
||||
/**
|
||||
* Function finds difference between two different strings
|
||||
*
|
||||
* @param str1 - original string
|
||||
* @param str2 - new string that comes from the first
|
||||
*
|
||||
* @returns {object[]} array of operations
|
||||
*/
|
||||
export function createOperationsByStringsDiff(str1: string, str2: string): (InsertOperation<string> | RemoveOperation<string>)[] {
|
||||
if (str1 === str2) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const length1 = str1.length;
|
||||
const length2 = str2.length;
|
||||
|
||||
/**
|
||||
* Left border of changes
|
||||
*/
|
||||
let left = 0;
|
||||
|
||||
while (str1[left] === str2[left]) {
|
||||
left++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Right border of changes
|
||||
*/
|
||||
let right = 0;
|
||||
|
||||
while (str1[length1 - right - 1] === str2[length2 - right - 1]) {
|
||||
right++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of difference in the first string
|
||||
*/
|
||||
const diffLength1 = length1 - left - right;
|
||||
|
||||
/**
|
||||
* Length of difference in the second string
|
||||
*/
|
||||
const diffLength2 = length2 - left - right;
|
||||
|
||||
/**
|
||||
* There are differences in two strings
|
||||
* User replaced values by new
|
||||
*/
|
||||
if (diffLength1 > 0 && diffLength2 > 0) {
|
||||
const before = str1.slice(left, length1 - right);
|
||||
const after = str2.slice(left, length2 - right);
|
||||
|
||||
return [
|
||||
{
|
||||
type: OperationType.Remove,
|
||||
index: left,
|
||||
data: before,
|
||||
},
|
||||
{
|
||||
type: OperationType.Insert,
|
||||
index: left,
|
||||
data: after,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* There is a difference only in the second string
|
||||
* Used inserted new values
|
||||
*/
|
||||
if (diffLength2 > 0) {
|
||||
const data = str2.slice(left, length2 - right + (left + right - length1));
|
||||
|
||||
return [
|
||||
{
|
||||
type: OperationType.Insert,
|
||||
index: left,
|
||||
data,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* There is a difference only in the first string
|
||||
* Used removed values from it
|
||||
*/
|
||||
if (diffLength1 > 0) {
|
||||
const data = str1.slice(left, length1 - right + (left + right - length2));
|
||||
|
||||
return [
|
||||
{
|
||||
type: OperationType.Remove,
|
||||
index: left,
|
||||
data,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
18
src/components/collaborative-editing/diff/types/Operation.ts
Normal file
18
src/components/collaborative-editing/diff/types/Operation.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { OperationType } from './OperationType';
|
||||
|
||||
export interface Operation<DATA_TYPE = string> {
|
||||
/**
|
||||
* Type of the operation
|
||||
*/
|
||||
type: OperationType;
|
||||
|
||||
/**
|
||||
* Start index of changes
|
||||
*/
|
||||
index: number;
|
||||
|
||||
/**
|
||||
* Changed data
|
||||
*/
|
||||
data: DATA_TYPE;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Possible operation types
|
||||
*/
|
||||
export enum OperationType {
|
||||
/**
|
||||
* User inserts symbols
|
||||
*/
|
||||
Insert,
|
||||
|
||||
/**
|
||||
* User removes symbols
|
||||
*/
|
||||
Remove,
|
||||
}
|
3
src/components/collaborative-editing/diff/types/index.ts
Normal file
3
src/components/collaborative-editing/diff/types/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from './OperationType';
|
||||
export * from './operations/InsertOperation';
|
||||
export * from './operations/RemoveOperation';
|
|
@ -0,0 +1,9 @@
|
|||
import { Operation } from '../Operation';
|
||||
import { OperationType } from '../OperationType';
|
||||
|
||||
/**
|
||||
* Operation of inserting some data inside
|
||||
*/
|
||||
export interface InsertOperation<DATA_TYPE = string> extends Operation<DATA_TYPE> {
|
||||
type: OperationType.Insert;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { Operation } from '../Operation';
|
||||
import { OperationType } from '../OperationType';
|
||||
|
||||
/**
|
||||
* Operation of removing some data
|
||||
*/
|
||||
export interface RemoveOperation<DATA_TYPE = string> extends Operation<DATA_TYPE> {
|
||||
type: OperationType.Remove;
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
import { createOperationsByStringsDiff } from '../../../../../src/components/collaborative-editing/diff/createOperationsByStringsDiff';
|
||||
import { OperationType } from '../../../../../src/components/collaborative-editing/diff/types';
|
||||
|
||||
describe('createOperationsByStringsDiff function', () => {
|
||||
describe('User inserts symbols', () => {
|
||||
it('should create Insert operation when a user inserts symbols at the start of the string', () => {
|
||||
const before = 'string';
|
||||
const after = 'Changed string';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Insert,
|
||||
index: 0,
|
||||
data: 'Changed ',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should create Insert operation when a user inserts symbols inside the string', () => {
|
||||
const before = 'string';
|
||||
const after = 'stri123ng';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Insert,
|
||||
index: 4,
|
||||
data: '123',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should create Insert operation when a user inserts symbols at the end of the string', () => {
|
||||
const before = 'string';
|
||||
const after = 'string is changed';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Insert,
|
||||
index: 6,
|
||||
data: ' is changed',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should create Insert operation when a user inserts symbols inside the empty string', () => {
|
||||
const before = '';
|
||||
const after = 'String';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Insert,
|
||||
index: 0,
|
||||
data: 'String',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('User removes symbols', () => {
|
||||
it('should create Remove operation when a user removes symbols at the start of the string', () => {
|
||||
const before = 'string';
|
||||
const after = 'ing';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Remove,
|
||||
index: 0,
|
||||
data: 'str',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should create Remove operation when a user removes symbols from the center of the string', () => {
|
||||
const before = 'String is changed';
|
||||
const after = 'String changed';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Remove,
|
||||
index: 7,
|
||||
data: 'is ',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should create Remove operation when a user removes symbols at the end of the string', () => {
|
||||
const before = 'String is changed';
|
||||
const after = 'String is';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Remove,
|
||||
index: 9,
|
||||
data: ' changed',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should create Remove operation when a user removes full string', () => {
|
||||
const before = 'String';
|
||||
const after = '';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Remove,
|
||||
index: 0,
|
||||
data: 'String',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('User replaces symbols', () => {
|
||||
it('should create Remove and Insert operations when a user replaces symbols at the start of the string', () => {
|
||||
const before = 'string';
|
||||
const after = 'aaing';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Remove,
|
||||
index: 0,
|
||||
data: 'str',
|
||||
},
|
||||
{
|
||||
type: OperationType.Insert,
|
||||
index: 0,
|
||||
data: 'aa',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should create Remove and Insert operations when a user replaces symbols from the center of the string', () => {
|
||||
const before = 'abcaafd';
|
||||
const after = 'abceecfd';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Remove,
|
||||
index: 3,
|
||||
data: 'aa',
|
||||
},
|
||||
{
|
||||
type: OperationType.Insert,
|
||||
index: 3,
|
||||
data: 'eec',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should create Remove and Insert operations when a user replaces symbols at the end of the string', () => {
|
||||
const before = 'abcdef';
|
||||
const after = 'abcaa';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Remove,
|
||||
index: 3,
|
||||
data: 'def',
|
||||
},
|
||||
{
|
||||
type: OperationType.Insert,
|
||||
index: 3,
|
||||
data: 'aa',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should create Remove and Insert operations when a user replaces full string', () => {
|
||||
const before = 'abcd';
|
||||
const after = 'efg';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Remove,
|
||||
index: 0,
|
||||
data: 'abcd',
|
||||
},
|
||||
{
|
||||
type: OperationType.Insert,
|
||||
index: 0,
|
||||
data: 'efg',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should create Remove and Insert operations when there are two changes at the start and the end of the string', () => {
|
||||
const before = 'String is changed';
|
||||
const after = 'Number is changed many times';
|
||||
const expected = [
|
||||
{
|
||||
type: OperationType.Remove,
|
||||
index: 0,
|
||||
data: 'String is changed',
|
||||
},
|
||||
{
|
||||
type: OperationType.Insert,
|
||||
index: 0,
|
||||
data: 'Number is changed many times',
|
||||
},
|
||||
];
|
||||
|
||||
const result = createOperationsByStringsDiff(before, after);
|
||||
|
||||
expect(result).deep.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue