1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-09-01 19:22:35 +02:00
* remove some key usage from core, refactor Operations.apply

* undeprecate some methods

* convert more key usage to paths

* update deprecations

* convert selection commands to use all paths

* refactor word boundary selection logic

* convert many at-range commands to use paths

* convert wrapBlock and wrapInline to not use keys

* cleanup

* remove chainability from editor

* simplify commands, queries and middleware

* convert deleteAtRange

* remove key usage from schema, deprecate *ByKey methods

* migrate *ByKey tests, remove index from *ByPath signatures

* rename at-current-range tests

* deprecate mode key usage, migrate more tests away from keys

* deprecate range and point methods which rely on keys to work

* refactor insertBlock, without fixing warnings

* add pathRef/pointRef, fix insertBlock/Inline deprecations, work on insertFragment

* refactor insertFragment

* get rich-text example rendering

* fix lint

* refactor query files, fix more tests

* remove unused queries, refactor others

* deprecate splitDescendantsByPath

* merge master

* add typescript, convert slate, slate-hyperscript, slate-plain-serializer

* add Point, Path, Range, Annotation tests

* add Annotation, Change, Element, Fragment, Mark, Range, Selection, Value interfaces tests

* add Operation and Text tests

* add Node tests

* get operations and normalization tests working for slate

* get *AtPath command tests passing

* rename *AtPath command tests

* rename

* get *AtPoint tests working

* rename

* rename

* add value queries tests

* add element, mark and path queries tests

* convert most on-selection tests

* convert on-selection commands

* rename

* get addMarks and delete commands working

* rename

* rename

* rename

* refactor value.positions(), work on delete tests

* progress on delete tests

* more delete work

* finish delete tests

* start converting to at-based commands

* restructure query tests

* restructure operations tests

* more work converting to multi-purpose commands

* lots of progress on converting to at-based commands

* add unwrapNodes

* remove setValue

* more progress

* refactor node commands to use consistent matching logic

* cleanup, get non-fragment commands passing

* remove annotations and isAtomic

* rename surround/pluck to cover/uncover

* add location concept, change at-path to from-path for iterables

* refactor batches

* add location-based queries

* refactor hanging logic

* more location query work

* renaming

* use getMatch more

* add split to wrap/unwrap

* flip levels/ancestors ordering

* switch splitNodes to use levels

* change split to always:false by default

* fix tests

* add more queries tests

* fixing more delete logic

* add more splitNodes tests

* get rest of delete tests passing

* fix location-based logic in some commands

* cleanup

* get previous packages tests passing again

* add slate-history package

* start slate-schema work

* start of react working

* rendering fixes

* get rich and plain text examples working

* get image example working with hooks and dropping

* refactor onDrop to be internal

* inline more event handlers

* refactor lots of event-related logic

* change rendering to use render props

* delete unused stuff

* cleanup dom utils

* remove unused deps

* remove unnecessary packages, add placeholder

* remove slate-react-placeholder package

* remove unused dep

* remove unnecessary tests, fix readonly example

* convert checklists example

* switch to next from webpack

* get link example working

* convert more examples

* preserve keys, memoized leafs/texts, fix node lookup

* fix to always useLayoutEffect for ordering

* fix annotations to be maps, memoize elements

* remove Change interface

* remove String interface

* rename Node.entries to Node.nodes

* remove unnecessary value queries

* default to selection when iterating, cleanup

* remove unused files

* update scroll into view logic

* fix undoing, remove constructor types

* dont sync selection while composing

* add workflows

* remove unused deps

* convert mentions example

* tweaks

* convert remaining examples

* rename h to jsx, update schema

* fix schema tests

* fix slate-schema logic and tests

* really fix slate-schema and forced-layout example

* get start of insertFragment tests working

* remove Fragment interface

* remove debugger

* get all non-skipped tests passing

* cleanup deps

* run prettier

* configure eslint for typescript

* more eslint fixes...

* more passing

* update some docs

* fix examples

* port windows undo hotkey change

* fix deps, add basic firefox support

* add event overriding, update walkthroughs

* add commands, remove classes, cleanup examples

* cleanup rollup config

* update tests

* rename queries tests

* update other tests

* update walkthroughs

* cleanup interface exports

* cleanup, change mark transforms to require location

* undo mark transform change

* more

* fix tests

* fix example

* update walkthroughs

* update docs

* update docs

* remove annotations

* remove value, move selection and children to editor

* add migrating doc

* fix lint

* fix tests

* fix DOM types aliasing

* add next export

* update deps, fix prod build

* fix prod build

* update scripts

* update docs and changelogs

* update workflow and pull request template
This commit is contained in:
Ian Storm Taylor
2019-11-27 20:54:42 -05:00
committed by GitHub
parent 02b87d5968
commit 4ff6972096
2367 changed files with 45706 additions and 80698 deletions

View File

@@ -0,0 +1,13 @@
# Changelog
A list of changes to the `slate-history` package with each new version. Until `1.0.0` is released, breaking changes will be added as minor version bumps, and smaller changes won't be accounted for since the library is moving quickly.
---
### `0.50.0` — November 27, 2019
###### BREAKING
**A complete overhaul.** The Slate codebase has had a complete overhaul and many pieces of its core architecture have been reconsidered from the ground up. There are lots of changes. We recommend re-reading the [Walkthroughs](https://docs.slatejs.org/walkthroughs) and [Concepts](https://docs.slatejs.org/concepts) documentation and the [Examples](../../site/examples) to get a sense for everything that has changed. As well as the [Migration](https://docs.slatejs.org/concepts/XX-migrating) writeup for what the major changes are.
---

View File

@@ -0,0 +1 @@
This package contains the core logic of Slate. Feel free to poke around to learn more!

View File

@@ -0,0 +1,41 @@
{
"name": "slate-history",
"type": "module",
"description": "An operation-based history implementation for Slate editors.",
"version": "0.47.8",
"license": "MIT",
"repository": "git://github.com/ianstormtaylor/slate.git",
"main": "lib/index.js",
"module": "lib/index.es.js",
"types": "lib/index.d.ts",
"umd": "dist/slate-history.js",
"umdMin": "dist/slate-history.min.js",
"files": [
"dist/",
"lib/"
],
"dependencies": {
"immer": "^5.0.0",
"is-plain-object": "^3.0.0"
},
"devDependencies": {
"slate": "^0.47.8",
"slate-hyperscript": "^0.13.8"
},
"peerDependencies": {
"slate": ">=0.50.0"
},
"umdGlobals": {
"slate": "Slate"
},
"keywords": [
"editor",
"history",
"operation",
"redo",
"save",
"slate",
"stack",
"undo"
]
}

View File

@@ -0,0 +1,39 @@
import { Command } from 'slate'
export interface RedoCommand {
type: 'redo'
}
export interface UndoCommand {
type: 'undo'
}
export type HistoryCommand = RedoCommand | UndoCommand
export const HistoryCommand = {
/**
* Check if a value is a `HistoryCommand` object.
*/
isHistoryCommand(value: any): value is HistoryCommand {
return (
HistoryCommand.isRedoCommand(value) || HistoryCommand.isUndoCommand(value)
)
},
/**
* Check if a value is a `RedoCommand` object.
*/
isRedoCommand(value: any): value is RedoCommand {
return Command.isCommand(value) && value.type === 'redo'
},
/**
* Check if a value is an `UndoCommand` object.
*/
isUndoCommand(value: any): value is UndoCommand {
return Command.isCommand(value) && value.type === 'undo'
},
}

View File

@@ -0,0 +1,68 @@
import { Editor } from 'slate'
import { History } from './history'
/**
* Weakmaps for attaching state to the editor.
*/
export const HISTORY = new WeakMap<Editor, History>()
export const SAVING = new WeakMap<Editor, boolean | undefined>()
export const MERGING = new WeakMap<Editor, boolean | undefined>()
/**
* `HistoryEditor` contains helpers for history-enabled editors.
*/
export interface HistoryEditor extends Editor {
history: History
}
export const HistoryEditor = {
/**
* Check if a value is a `HistoryEditor` object.
*/
isHistoryEditor(value: any): value is HistoryEditor {
return Editor.isEditor(value) && History.isHistory(value.history)
},
/**
* Get the merge flag's current value.
*/
isMerging(editor: Editor): boolean | undefined {
return MERGING.get(editor)
},
/**
* Get the saving flag's current value.
*/
isSaving(editor: Editor): boolean | undefined {
return SAVING.get(editor)
},
/**
* Apply a series of changes inside a synchronous `fn`, without merging any of
* the new operations into previous save point in the history.
*/
withoutMerging(editor: Editor, fn: () => void): void {
const prev = HistoryEditor.isMerging(editor)
MERGING.set(editor, false)
fn()
MERGING.set(editor, prev)
},
/**
* Apply a series of changes inside a synchronous `fn`, without saving any of
* their operations into the history.
*/
withoutSaving(editor: Editor, fn: () => void): void {
const prev = HistoryEditor.isSaving(editor)
SAVING.set(editor, false)
fn()
SAVING.set(editor, prev)
},
}

View File

@@ -0,0 +1,28 @@
import isPlainObject from 'is-plain-object'
import { Operation } from 'slate'
/**
* `History` objects hold all of the operations that are applied to a value, so
* they can be undone or redone as necessary.
*/
export interface History {
redos: Operation[][]
undos: Operation[][]
}
export const History = {
/**
* Check if a value is a `History` object.
*/
isHistory(value: any): value is History {
return (
isPlainObject(value) &&
Array.isArray(value.redos) &&
Array.isArray(value.undos) &&
(value.redos.length === 0 || Operation.isOperationList(value.redos[0])) &&
(value.undos.length === 0 || Operation.isOperationList(value.undos[0]))
)
},
}

View File

@@ -0,0 +1,4 @@
export * from './history'
export * from './history-command'
export * from './history-editor'
export * from './with-history'

View File

@@ -0,0 +1,174 @@
import { Editor, Command, Operation, Path } from 'slate'
import { HistoryCommand } from './history-command'
import { HistoryEditor } from './history-editor'
/**
* The `withHistory` plugin keeps track of the operation history of a Slate
* editor as operations are applied to it, using undo and redo stacks.
*/
export const withHistory = (editor: Editor): Editor => {
const { apply, exec } = editor
editor.history = { undos: [], redos: [] }
editor.exec = (command: Command) => {
if (HistoryEditor.isHistoryEditor(editor)) {
const { history } = editor
const { undos, redos } = history
if (redos.length > 0 && HistoryCommand.isRedoCommand(command)) {
const batch = redos[redos.length - 1]
HistoryEditor.withoutSaving(editor, () => {
Editor.withoutNormalizing(editor, () => {
for (const op of batch) {
editor.apply(op)
}
})
})
history.redos.pop()
history.undos.push(batch)
return
}
if (undos.length > 0 && HistoryCommand.isUndoCommand(command)) {
const batch = undos[undos.length - 1]
HistoryEditor.withoutSaving(editor, () => {
Editor.withoutNormalizing(editor, () => {
const inverseOps = batch.map(Operation.inverse).reverse()
for (const op of inverseOps) {
// If the final operation is deselecting the editor, skip it. This is
if (
op === inverseOps[inverseOps.length - 1] &&
op.type === 'set_selection' &&
op.newProperties == null
) {
continue
} else {
editor.apply(op)
}
}
})
})
history.redos.push(batch)
history.undos.pop()
return
}
}
exec(command)
}
editor.apply = (op: Operation) => {
if (HistoryEditor.isHistoryEditor(editor)) {
const { operations, history } = editor
const { undos } = history
const lastBatch = undos[undos.length - 1]
const lastOp = lastBatch && lastBatch[lastBatch.length - 1]
const overwrite = shouldOverwrite(op, lastOp)
let save = HistoryEditor.isSaving(editor)
let merge = HistoryEditor.isMerging(editor)
if (save == null) {
save = shouldSave(op, lastOp)
}
if (save) {
if (merge == null) {
if (lastBatch == null) {
merge = false
} else if (operations.length !== 0) {
merge = true
} else {
merge = shouldMerge(op, lastOp) || overwrite
}
}
if (lastBatch && merge) {
if (overwrite) {
lastBatch.pop()
}
lastBatch.push(op)
} else {
const batch = [op]
undos.push(batch)
}
while (undos.length > 100) {
undos.shift()
}
history.redos = []
}
}
apply(op)
}
return editor
}
/**
* Check whether to merge an operation into the previous operation.
*/
const shouldMerge = (op: Operation, prev: Operation | undefined): boolean => {
if (op.type === 'set_selection') {
return true
}
if (
prev &&
op.type === 'insert_text' &&
prev.type === 'insert_text' &&
op.offset === prev.offset + prev.text.length &&
Path.equals(op.path, prev.path)
) {
return true
}
if (
prev &&
op.type === 'remove_text' &&
prev.type === 'remove_text' &&
op.offset + op.text.length === prev.offset &&
Path.equals(op.path, prev.path)
) {
return true
}
return false
}
/**
* Check whether an operation needs to be saved to the history.
*/
const shouldSave = (op: Operation, prev: Operation | undefined): boolean => {
if (op.type === 'set_selection' && op.newProperties == null) {
return false
}
return true
}
/**
* Check whether an operation should overwrite the previous one.
*/
const shouldOverwrite = (
op: Operation,
prev: Operation | undefined
): boolean => {
if (prev && op.type === 'set_selection' && prev.type === 'set_selection') {
return true
}
return false
}

View File

@@ -0,0 +1,35 @@
import assert from 'assert'
import { fixtures } from '../../../support/fixtures'
import { createHyperscript } from 'slate-hyperscript'
import { withHistory } from '..'
describe('slate-history', () => {
fixtures(__dirname, 'undo', ({ module }) => {
const { input, run, output } = module
const editor = withTest(withHistory(input))
run(editor)
editor.exec({ type: 'undo' })
assert.deepEqual(editor.children, output.children)
})
})
export const jsx = createHyperscript({
elements: {
block: {},
inline: { inline: true },
},
})
const withTest = editor => {
const { isInline, isVoid } = editor
editor.isInline = element => {
return element.inline === true ? true : isInline(element)
}
editor.isVoid = element => {
return element.void === true ? true : isVoid(element)
}
return editor
}

View File

@@ -0,0 +1,22 @@
/** @jsx jsx */
import { jsx } from '../..'
export const run = editor => {
editor.exec({ type: 'add_mark', mark: { key: 'a' } })
}
export const input = (
<editor>
<block>
o<anchor />
ne
</block>
<block>
tw
<focus />o
</block>
</editor>
)
export const output = input

View File

@@ -0,0 +1,20 @@
/** @jsx jsx */
import { jsx } from '../..'
export const run = editor => {
editor.exec({ type: 'add_mark', mark: { key: 'a' } })
}
export const input = (
<editor>
<block>
<mark key="b">
w<anchor />o
</mark>
r<focus />d
</block>
</editor>
)
export const output = input

View File

@@ -0,0 +1,20 @@
/** @jsx jsx */
import { jsx } from '../..'
export const run = editor => {
editor.exec({ type: 'add_mark', mark: { key: 'a' } })
}
export const input = (
<editor>
<block>
<mark key="a">
w<anchor />o
</mark>
r<focus />d
</block>
</editor>
)
export const output = input

View File

@@ -0,0 +1,20 @@
/** @jsx jsx */
import { jsx } from '../..'
export const run = editor => {
editor.exec({ type: 'add_mark', mark: { key: 'a' } })
}
export const input = (
<editor>
<block>
<anchor />
wo
<focus />
rd
</block>
</editor>
)
export const output = input

View File

@@ -0,0 +1,27 @@
/** @jsx jsx */
import { jsx } from '../..'
export const run = editor => {
editor.exec({ type: 'delete_backward' })
}
export const input = (
<editor>
<block>Hello</block>
<block>
<cursor />
world!
</block>
</editor>
)
export const output = (
<editor>
<block>Hello</block>
<block>
<cursor />
world!
</block>
</editor>
)

View File

@@ -0,0 +1,31 @@
/** @jsx jsx */
import { jsx } from '../..'
export const run = editor => {
editor.exec({ type: 'delete_backward' })
}
export const input = (
<editor>
<block>Hello</block>
<block>
<block>
<cursor />
world!
</block>
</block>
</editor>
)
export const output = (
<editor>
<block>Hello</block>
<block>
<block>
<cursor />
world!
</block>
</block>
</editor>
)

View File

@@ -0,0 +1,22 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../..'
export const run = editor => {
editor.delete()
}
export const input = (
<editor>
<block>
wo
<cursor />
rd
</block>
</editor>
)
export const output = input
export const skip = true

View File

@@ -0,0 +1,25 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../..'
export const run = editor => {
editor.delete()
}
export const input = (
<editor>
<block a>
o<anchor />
ne
</block>
<block b>
tw
<focus />o
</block>
</editor>
)
export const output = input
export const skip = true

View File

@@ -0,0 +1,33 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../..'
export const run = editor => {
editor.delete()
}
export const input = (
<editor>
<block>
<text />
<inline a>
o<anchor />
ne
</inline>
<text />
</block>
<block>
<text />
<inline b>
tw
<focus />o
</inline>
<text />
</block>
</editor>
)
export const output = input
export const skip = true

View File

@@ -0,0 +1,27 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../..'
export const run = editor => {
editor.delete()
}
export const input = (
<editor>
<block>
<mark key="a">
on
<anchor />e
</mark>
<mark key="c">
tw
<focus />o
</mark>
</block>
</editor>
)
export const output = input
export const skip = true

View File

@@ -0,0 +1,22 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../..'
export const run = editor => {
editor.exec({ type: 'insert_break' })
}
export const input = (
<editor>
<block>
<block>
on
<cursor />e
</block>
<block>two</block>
</block>
</editor>
)
export const output = input

View File

@@ -0,0 +1,42 @@
/** @jsx jsx */
import { jsx } from '../..'
const fragment = (
<block type="d">
<block>A</block>
<block type="c">
<block type="d">
<block>B</block>
<block>
<block type="d">
<block>C</block>
</block>
</block>
</block>
<block type="d">
<block>D</block>
</block>
</block>
</block>
)
export const run = editor => {
editor.exec({ type: 'insert_fragment', fragment })
}
export const input = (
<editor>
<block type="d">
<block>
<text>
<cursor />
</text>
</block>
</block>
</editor>
)
export const output = input
export const skip = true

View File

@@ -0,0 +1,18 @@
/** @jsx jsx */
import { jsx } from '../..'
export const run = editor => {
editor.exec({ type: 'insert_text', text: 'text' })
}
export const input = (
<editor>
<block>
one
<cursor />
</block>
</editor>
)
export const output = input

View File

@@ -0,0 +1,20 @@
/** @jsx jsx */
import { jsx } from '../..'
export const run = editor => {
editor.exec({ type: 'insert_text', text: 't' })
editor.exec({ type: 'insert_text', text: 'w' })
editor.exec({ type: 'insert_text', text: 'o' })
}
export const input = (
<editor>
<block>
one
<cursor />
</block>
</editor>
)
export const output = input

View File

@@ -0,0 +1,31 @@
/** @jsx jsx */
import { jsx } from '../..'
export const run = editor => {
editor.exec({ type: 'insert_text', text: 't' })
// editor.move({ reverse: true })
editor.exec({ type: 'insert_text', text: 'w' })
// editor.move({ reverse: true })
editor.exec({ type: 'insert_text', text: 'o' })
}
export const input = (
<editor>
<block>
one
<cursor />
</block>
</editor>
)
export const output = (
<editor>
<block>
onew
<cursor />t
</block>
</editor>
)
export const skip = true

View File

@@ -0,0 +1,21 @@
/** @jsx jsx */
import { jsx } from '../..'
export const run = editor => {
editor.exec({ type: 'remove_mark', mark: { key: true } })
}
export const input = (
<editor>
<block>
<mark key>
<anchor />
one
<focus />
</mark>
</block>
</editor>
)
export const output = input

View File

@@ -0,0 +1,10 @@
{
"extends": "../../config/typescript/tsconfig.json",
"include": ["src/**/*"],
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"composite": true
},
"references": []
}