1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-16 12:14:14 +02:00

fix history to only merge contiguous inserts/removals, fixes #312

This commit is contained in:
Ian Storm Taylor
2016-09-14 16:46:42 -07:00
parent 79eafe26d8
commit b6566b9a8e
20 changed files with 435 additions and 13 deletions

View File

@@ -54,20 +54,10 @@ class Transform {
// If there's a previous save point, determine if the new operations should
// be merged into the previous ones.
if (previous && merge == null) {
const types = operations.map(op => op.type)
const prevTypes = previous.map(op => op.type)
const edits = types.filter(type => type != 'set_selection')
const prevEdits = prevTypes.filter(type => type != 'set_selection')
const onlySelections = types.every(type => type == 'set_selection')
const onlyInserts = edits.length && edits.every(type => type == 'insert_text')
const onlyRemoves = edits.length && edits.every(type => type == 'remove_text')
const prevOnlyInserts = prevEdits.length && prevEdits.every(type => type == 'insert_text')
const prevOnlyRemoves = prevEdits.length && prevEdits.every(type => type == 'remove_text')
merge = (
(onlySelections) ||
(onlyInserts && prevOnlyInserts) ||
(onlyRemoves && prevOnlyRemoves)
isOnlySelections(operations) ||
isContiguousInserts(operations, previous) ||
isContiguousRemoves(operations, previous)
)
}
@@ -92,6 +82,67 @@ Object.keys(Transforms).forEach((type) => {
}
})
/**
* Check whether a list of `operations` only contains selection operations.
*
* @param {Array} operations
* @return {Boolean}
*/
function isOnlySelections(operations) {
return operations.every(op => op.type == 'set_selection')
}
/**
* Check whether a list of `operations` and a list of `previous` operations are
* contiguous text insertions.
*
* @param {Array} operations
* @param {Array} previous
*/
function isContiguousInserts(operations, previous) {
const edits = operations.filter(op => op.type != 'set_selection')
const prevEdits = previous.filter(op => op.type != 'set_selection')
if (!edits.length || !prevEdits.length) return false
const onlyInserts = edits.every(op => op.type == 'insert_text')
const prevOnlyInserts = prevEdits.every(op => op.type == 'insert_text')
if (!onlyInserts || !prevOnlyInserts) return false
const first = edits[0]
const last = prevEdits[prevEdits.length - 1]
if (first.key != last.key) return false
if (first.offset != last.offset + last.text.length) return false
return true
}
/**
* Check whether a list of `operations` and a list of `previous` operations are
* contiguous text removals.
*
* @param {Array} operations
* @param {Array} previous
*/
function isContiguousRemoves(operations, previous) {
const edits = operations.filter(op => op.type != 'set_selection')
const prevEdits = previous.filter(op => op.type != 'set_selection')
if (!edits.length || !prevEdits.length) return false
const onlyRemoves = edits.every(op => op.type == 'remove_text')
const prevOnlyRemoves = prevEdits.every(op => op.type == 'remove_text')
if (!onlyRemoves || !prevOnlyRemoves) return false
const first = edits[0]
const last = prevEdits[prevEdits.length - 1]
if (first.key != last.key) return false
if (first.offset + first.length != last.offset) return false
return true
}
/**
* Export.
*

View File

@@ -0,0 +1,26 @@
import assert from 'assert'
export default function (state) {
const { document, selection } = state
const texts = document.getTexts()
const first = texts.first()
const next = state
.transform()
.moveTo({
anchorKey: first.key,
anchorOffset: 0,
focusKey: first.key,
focusOffset: first.length
})
.delete()
.apply()
.transform()
.undo()
.apply()
assert.deepEqual(next.selection.toJS(), selection.toJS())
return next
}

View File

@@ -0,0 +1,7 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: word

View File

@@ -0,0 +1,7 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: word

View File

@@ -0,0 +1,24 @@
import assert from 'assert'
export default function (state) {
const { document, selection } = state
const texts = document.getTexts()
const first = texts.get(0)
const next = state
.transform()
.collapseToStartOf(first)
.insertText('text')
.apply()
.transform()
.insertText('text')
.apply()
.transform()
.undo()
.apply()
return next
}

View File

@@ -0,0 +1,12 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: one
- kind: block
type: paragraph
nodes:
- kind: text
text: two

View File

@@ -0,0 +1,12 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: one
- kind: block
type: paragraph
nodes:
- kind: text
text: two

View File

@@ -0,0 +1,24 @@
import assert from 'assert'
export default function (state) {
const { document, selection } = state
const texts = document.getTexts()
const second = texts.get(1)
const next = state
.transform()
.collapseToStartOf(second)
.insertText('text')
.apply()
.transform()
.insertText('text')
.apply()
.transform()
.undo()
.apply()
return next
}

View File

@@ -0,0 +1,25 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: paragraph one
- kind: block
type: paragraph
nodes:
- kind: block
type: list
nodes:
- kind: text
text: list one
- kind: block
type: list
nodes:
- kind: text
text: list two
- kind: block
type: paragraph
nodes:
- kind: text
text: paragraph three

View File

@@ -0,0 +1,25 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: paragraph one
- kind: block
type: paragraph
nodes:
- kind: block
type: list
nodes:
- kind: text
text: list one
- kind: block
type: list
nodes:
- kind: text
text: list two
- kind: block
type: paragraph
nodes:
- kind: text
text: paragraph three

View File

@@ -0,0 +1,38 @@
import assert from 'assert'
export default function (state) {
const { document, selection } = state
const texts = document.getTexts()
const first = texts.get(0)
const second = texts.get(1)
const third = texts.get(2)
const fourth = texts.get(3)
const next = state
.transform()
.collapseToStartOf(first)
.insertText('text')
.apply()
.transform()
.collapseToStartOf(second)
.insertText('text')
.apply()
.transform()
.collapseToStartOf(third)
.insertText('text')
.apply()
.transform()
.collapseToStartOf(fourth)
.insertText('text')
.apply()
.transform()
.undo()
.apply()
return next
}

View File

@@ -0,0 +1,25 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: paragraph one
- kind: block
type: paragraph
nodes:
- kind: block
type: list
nodes:
- kind: text
text: list one
- kind: block
type: list
nodes:
- kind: text
text: list two
- kind: block
type: paragraph
nodes:
- kind: text
text: paragraph three

View File

@@ -0,0 +1,25 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: textparagraph one
- kind: block
type: paragraph
nodes:
- kind: block
type: list
nodes:
- kind: text
text: textlist one
- kind: block
type: list
nodes:
- kind: text
text: textlist two
- kind: block
type: paragraph
nodes:
- kind: text
text: paragraph three

View File

@@ -0,0 +1,26 @@
import assert from 'assert'
export default function (state) {
const { document, selection } = state
const texts = document.getTexts()
const first = texts.get(0)
const second = texts.get(1)
const next = state
.transform()
.collapseToStartOf(first)
.insertText('text')
.apply()
.transform()
.collapseToStartOf(second)
.insertText('text')
.apply()
.transform()
.undo()
.apply()
return next
}

View File

@@ -0,0 +1,12 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: one
- kind: block
type: paragraph
nodes:
- kind: text
text: two

View File

@@ -0,0 +1,12 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: textone
- kind: block
type: paragraph
nodes:
- kind: text
text: two

View File

@@ -0,0 +1,27 @@
import assert from 'assert'
export default function (state) {
const { document, selection } = state
const texts = document.getTexts()
const first = texts.first()
const range = selection.merge({
anchorKey: first.key,
anchorOffset: 0,
focusKey: first.key,
focusOffset: 0
})
const next = state
.transform()
.moveTo(range)
.insertText('text')
.apply()
.transform()
.undo()
.apply()
assert.deepEqual(next.selection.toJS(), selection.toJS())
return next
}

View File

@@ -0,0 +1,7 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: word

View File

@@ -0,0 +1,7 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: word

View File

@@ -127,4 +127,34 @@ describe('transforms', () => {
})
}
})
describe('on-history', () => {
const dir = resolve(__dirname, './fixtures/on-history')
const transforms = fs.readdirSync(dir)
for (const transform of transforms) {
if (transform[0] == '.') continue
describe(`${toCamel(transform)}()`, () => {
const transformDir = resolve(__dirname, './fixtures/on-history', transform)
const tests = fs.readdirSync(transformDir)
for (const test of tests) {
if (test[0] == '.') continue
it(test, () => {
const testDir = resolve(transformDir, test)
const fn = require(testDir).default
const input = readMetadata.sync(resolve(testDir, 'input.yaml'))
const expected = readMetadata.sync(resolve(testDir, 'output.yaml'))
let state = Raw.deserialize(input, { terse: true })
state = fn(state)
const output = Raw.serialize(state, { terse: true })
strictEqual(strip(output), strip(expected))
})
}
})
}
})
})