diff --git a/docs/Introduction.md b/docs/Introduction.md index 5ca044216..0cbe2371f 100644 --- a/docs/Introduction.md +++ b/docs/Introduction.md @@ -76,7 +76,7 @@ If you have an idea for an example that shows a common use case, pull request it ## Documentation -If you're using Slate for the first time, check out the [Getting Started](http://docs.slatejs.org/walkthroughs/installing-slate) walkthroughs and the [Concepts](http://docs.slatejs.org/concepts) to familiarize yourself with Slate's architecture and mental models. +If you're using Slate for the first time, check out the [Getting Started](http://docs.slatejs.org/walkthroughs/01-installing-slate) walkthroughs and the [Concepts](http://docs.slatejs.org/concepts) to familiarize yourself with Slate's architecture and mental models. - [**Walkthroughs**](http://docs.slatejs.org/walkthroughs) - [**Concepts**](http://docs.slatejs.org/concepts) diff --git a/docs/general/faq.md b/docs/general/faq.md index fc9296b84..11b3cb802 100644 --- a/docs/general/faq.md +++ b/docs/general/faq.md @@ -18,6 +18,6 @@ Since Slate knows nothing about your domain, it can't know how to parse pasted H Slate's goal is to support all the modern browsers on both desktop and mobile devices. -However, right now Slate is in beta and is community-driven, so its support is not as robust as it could be. It's currently tested against the latest few versions of Chrome, Edge, Firefox and Safari on desktops. It is not regularly tested on mobile devices. And it does not work in Internet Explorer. If you want to add more browser or device support, we'd love for you to submit a pull request! Or in the case of incompatible browsers, build a plugin. +However, right now Slate is in beta and is community-driven, so its support is not as robust as it could be. It's currently tested against the latest few versions of Chrome, Edge, Firefox and Safari on desktops. And it does not work in Internet Explorer. On mobile, iOS devices are supported but not regularly tested. Chrome on Android is supported on Slate 0.47 but is not currently supported in Slate 0.50+. If you want to add more browser or device support, we'd love for you to submit a pull request! Or in the case of incompatible browsers, build a plugin. For older browsers, such as IE11, a lot of the now standard native APIs aren't available. Slate's position on this is that it is up to the user to bring polyfills (like https://polyfill.io) when needed for things like `el.closest`, etc. Otherwise we'd have to bundle and maintain lots of polyfills that others may not even need in the first place. diff --git a/packages/slate-react/src/components/editable.tsx b/packages/slate-react/src/components/editable.tsx index b1ed6c1d4..ad3738e1d 100644 --- a/packages/slate-react/src/components/editable.tsx +++ b/packages/slate-react/src/components/editable.tsx @@ -697,8 +697,8 @@ export const Editable = (props: EditableProps) => { if (Hotkeys.isRedo(nativeEvent)) { event.preventDefault() - if (editor.undo) { - editor.undo() + if (editor.redo) { + editor.redo() } return @@ -707,8 +707,8 @@ export const Editable = (props: EditableProps) => { if (Hotkeys.isUndo(nativeEvent)) { event.preventDefault() - if (editor.redo) { - editor.redo() + if (editor.undo) { + editor.undo() } return diff --git a/packages/slate-react/src/components/text.tsx b/packages/slate-react/src/components/text.tsx index 6f7c5b139..4778ab775 100644 --- a/packages/slate-react/src/components/text.tsx +++ b/packages/slate-react/src/components/text.tsx @@ -25,7 +25,7 @@ const Text = (props: { const { decorations, isLast, parent, renderLeaf, text } = props const editor = useEditor() const ref = useRef(null) - const leaves = getLeaves(text, decorations) + const leaves = SlateText.decorations(text, decorations) const key = ReactEditor.findKey(editor, text) const children = [] @@ -63,79 +63,6 @@ const Text = (props: { ) } -/** - * Get the leaves for a text node given decorations. - */ - -const getLeaves = (node: SlateText, decorations: Range[]): SlateText[] => { - let leaves: SlateText[] = [{ ...node }] - - for (const dec of decorations) { - const { anchor, focus, ...rest } = dec - const [start, end] = Range.edges(dec) - const next = [] - let o = 0 - - for (const leaf of leaves) { - const { length } = leaf.text - const offset = o - o += length - - // If the range encompases the entire leaf, add the range. - if (start.offset <= offset && end.offset >= offset + length) { - Object.assign(leaf, rest) - next.push(leaf) - continue - } - - // If the range starts after the leaf, or ends before it, continue. - if ( - start.offset > offset + length || - end.offset < offset || - (end.offset === offset && offset !== 0) - ) { - next.push(leaf) - continue - } - - // Otherwise we need to split the leaf, at the start, end, or both, - // and add the range to the middle intersecting section. Do the end - // split first since we don't need to update the offset that way. - let middle = leaf - let before - let after - - if (end.offset < offset + length) { - const off = end.offset - offset - after = { ...middle, text: middle.text.slice(off) } - middle = { ...middle, text: middle.text.slice(0, off) } - } - - if (start.offset > offset) { - const off = start.offset - offset - before = { ...middle, text: middle.text.slice(0, off) } - middle = { ...middle, text: middle.text.slice(off) } - } - - Object.assign(middle, rest) - - if (before) { - next.push(before) - } - - next.push(middle) - - if (after) { - next.push(after) - } - } - - leaves = next - } - - return leaves -} - const MemoizedText = React.memo(Text, (prev, next) => { return ( next.parent === prev.parent && diff --git a/packages/slate/src/interfaces/text.ts b/packages/slate/src/interfaces/text.ts index a1e178e90..80ca65f22 100755 --- a/packages/slate/src/interfaces/text.ts +++ b/packages/slate/src/interfaces/text.ts @@ -1,5 +1,5 @@ import isPlainObject from 'is-plain-object' -import { Path } from '..' +import { Range } from '..' /** * `Text` objects represent the nodes that contain the actual text content of a @@ -83,4 +83,77 @@ export const Text = { return true }, + + /** + * Get the leaves for a text node given decorations. + */ + + decorations(node: Text, decorations: Range[]): Text[] { + let leaves: Text[] = [{ ...node }] + + for (const dec of decorations) { + const { anchor, focus, ...rest } = dec + const [start, end] = Range.edges(dec) + const next = [] + let o = 0 + + for (const leaf of leaves) { + const { length } = leaf.text + const offset = o + o += length + + // If the range encompases the entire leaf, add the range. + if (start.offset <= offset && end.offset >= offset + length) { + Object.assign(leaf, rest) + next.push(leaf) + continue + } + + // If the range starts after the leaf, or ends before it, continue. + if ( + start.offset > offset + length || + end.offset < offset || + (end.offset === offset && offset !== 0) + ) { + next.push(leaf) + continue + } + + // Otherwise we need to split the leaf, at the start, end, or both, + // and add the range to the middle intersecting section. Do the end + // split first since we don't need to update the offset that way. + let middle = leaf + let before + let after + + if (end.offset < offset + length) { + const off = end.offset - offset + after = { ...middle, text: middle.text.slice(off) } + middle = { ...middle, text: middle.text.slice(0, off) } + } + + if (start.offset > offset) { + const off = start.offset - offset + before = { ...middle, text: middle.text.slice(0, off) } + middle = { ...middle, text: middle.text.slice(off) } + } + + Object.assign(middle, rest) + + if (before) { + next.push(before) + } + + next.push(middle) + + if (after) { + next.push(after) + } + } + + leaves = next + } + + return leaves + }, } diff --git a/packages/slate/test/interfaces/Text/decorations/end.js b/packages/slate/test/interfaces/Text/decorations/end.js new file mode 100644 index 000000000..dadd76c7f --- /dev/null +++ b/packages/slate/test/interfaces/Text/decorations/end.js @@ -0,0 +1,31 @@ +import { Text } from 'slate' + +export const input = [ + { + anchor: { + path: [0], + offset: 2, + }, + focus: { + path: [0], + offset: 3, + }, + decoration: 'decoration', + }, +] + +export const test = decorations => { + return Text.decorations({ text: 'abc', mark: 'mark' }, decorations) +} + +export const output = [ + { + text: 'ab', + mark: 'mark', + }, + { + text: 'c', + mark: 'mark', + decoration: 'decoration', + }, +] diff --git a/packages/slate/test/interfaces/Text/decorations/middle.js b/packages/slate/test/interfaces/Text/decorations/middle.js new file mode 100644 index 000000000..983a61297 --- /dev/null +++ b/packages/slate/test/interfaces/Text/decorations/middle.js @@ -0,0 +1,35 @@ +import { Text } from 'slate' + +export const input = [ + { + anchor: { + path: [0], + offset: 1, + }, + focus: { + path: [0], + offset: 2, + }, + decoration: 'decoration', + }, +] + +export const test = decorations => { + return Text.decorations({ text: 'abc', mark: 'mark' }, decorations) +} + +export const output = [ + { + text: 'a', + mark: 'mark', + }, + { + text: 'b', + mark: 'mark', + decoration: 'decoration', + }, + { + text: 'c', + mark: 'mark', + }, +] diff --git a/packages/slate/test/interfaces/Text/decorations/overlapping.js b/packages/slate/test/interfaces/Text/decorations/overlapping.js new file mode 100644 index 000000000..2dd83d785 --- /dev/null +++ b/packages/slate/test/interfaces/Text/decorations/overlapping.js @@ -0,0 +1,49 @@ +import { Text } from 'slate' + +export const input = [ + { + anchor: { + path: [0], + offset: 1, + }, + focus: { + path: [0], + offset: 2, + }, + decoration1: 'decoration1', + }, + { + anchor: { + path: [0], + offset: 0, + }, + focus: { + path: [0], + offset: 3, + }, + decoration2: 'decoration2', + }, +] + +export const test = decorations => { + return Text.decorations({ text: 'abc', mark: 'mark' }, decorations) +} + +export const output = [ + { + text: 'a', + mark: 'mark', + decoration2: 'decoration2', + }, + { + text: 'b', + mark: 'mark', + decoration1: 'decoration1', + decoration2: 'decoration2', + }, + { + text: 'c', + mark: 'mark', + decoration2: 'decoration2', + }, +] diff --git a/packages/slate/test/interfaces/Text/decorations/start.js b/packages/slate/test/interfaces/Text/decorations/start.js new file mode 100644 index 000000000..53db2dfe4 --- /dev/null +++ b/packages/slate/test/interfaces/Text/decorations/start.js @@ -0,0 +1,31 @@ +import { Text } from 'slate' + +export const input = [ + { + anchor: { + path: [0], + offset: 0, + }, + focus: { + path: [0], + offset: 1, + }, + decoration: 'decoration', + }, +] + +export const test = decorations => { + return Text.decorations({ text: 'abc', mark: 'mark' }, decorations) +} + +export const output = [ + { + text: 'a', + mark: 'mark', + decoration: 'decoration', + }, + { + text: 'bc', + mark: 'mark', + }, +]