From 4cd1ddebd097729fdb2a26cf9d0c8f08e1a606da Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 28 Apr 2017 12:14:34 -0700 Subject: [PATCH 01/16] add an issue template --- .github/Issue-template.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/Issue-template.md diff --git a/.github/Issue-template.md b/.github/Issue-template.md new file mode 100644 index 0000000000..a0ba1b6ca8 --- /dev/null +++ b/.github/Issue-template.md @@ -0,0 +1,28 @@ + +#### Do you want to request a *feature* or report a *bug*? + + + +#### What's the current behavior? + + + +#### What's the expected behavior? + + From 79275d47b691e877696a61f24c6f123d9ff61db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Fri, 28 Apr 2017 21:17:23 +0200 Subject: [PATCH 02/16] Add "data-key" to root div for the whole document (#759) * Add "data-key" to div for the whole document * Fix unit tests for rendering --- src/components/content.js | 1 + test/rendering/index.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/content.js b/src/components/content.js index ba9101205d..544859666d 100644 --- a/src/components/content.js +++ b/src/components/content.js @@ -836,6 +836,7 @@ class Content extends React.Component { data-slate-editor key={this.tmp.forces} ref={this.ref} + data-key={document.key} contentEditable={!readOnly} suppressContentEditableWarning className={className} diff --git a/test/rendering/index.js b/test/rendering/index.js index 550307ea34..1b01086989 100644 --- a/test/rendering/index.js +++ b/test/rendering/index.js @@ -59,6 +59,7 @@ function clean(html) { $.root().children().removeAttr('autocorrect') $.root().children().removeAttr('spellcheck') $.root().children().removeAttr('style') + $.root().children().removeAttr('data-gramm') return $.html() } From e855ed5aade79f4f24b28ace32a666610462e4cf Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 28 Apr 2017 12:20:47 -0700 Subject: [PATCH 03/16] update issue template --- .github/Issue-template.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/Issue-template.md b/.github/Issue-template.md index a0ba1b6ca8..7bd6bd9da0 100644 --- a/.github/Issue-template.md +++ b/.github/Issue-template.md @@ -13,6 +13,9 @@ https://slate-slack.herokuapp.com/ For bugs, please create a JSFiddle that reproduces the issue using our template, and include a GIF showing how to easily reproduce it. +And for bugs please include your OS, browser, Slate version and any +other bit of info that may track down why it's happening. + https://jsfiddle.net/2zokvrvt/7/ http://recordit.co/ --> From 2eea02a55ca4d3e6ec64a45cff10017fe1565d7d Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 28 Apr 2017 12:55:48 -0700 Subject: [PATCH 04/16] fix to maintain focus on switching tabs, closes #756 --- src/components/content.js | 46 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/components/content.js b/src/components/content.js index ba9101205d..6ee26a38dc 100644 --- a/src/components/content.js +++ b/src/components/content.js @@ -112,10 +112,20 @@ class Content extends React.Component { } /** - * On mount, update the selection, and focus the editor if `autoFocus` is set. + * When the editor first mounts in the DOM we need to: + * + * - Setup the original state for `isWindowFocused`. + * - Attach window focus and blur listeners for tab switching. + * - Update the selection, in case it starts focused. + * - Focus the editor if `autoFocus` is set. */ componentDidMount = () => { + const window = getWindow(this.element) + window.addEventListener('focus', this.onWindowFocus, true) + window.addEventListener('blur', this.onWindowBlur, true) + this.tmp.isWindowFocused = !window.document.hidden + this.updateSelection() if (this.props.autoFocus) { @@ -131,6 +141,16 @@ class Content extends React.Component { this.updateSelection() } + /** + * Before unmounting, detach our window focus and blur listeners. + */ + + componentWillUnmount = () => { + const window = getWindow(this.element) + window.removeEventListener('focus', this.onWindowFocus, true) + window.removeEventListener('blur', this.onWindowBlur, true) + } + /** * Update the native DOM selection to reflect the internal model. */ @@ -274,6 +294,7 @@ class Content extends React.Component { onBlur = (event) => { if (this.props.readOnly) return if (this.tmp.isCopying) return + if (!this.tmp.isWindowFocused) return if (!this.isInEditor(event)) return // If the element that is now focused is actually inside the editor, we @@ -296,6 +317,7 @@ class Content extends React.Component { onFocus = (event) => { if (this.props.readOnly) return if (this.tmp.isCopying) return + if (!this.tmp.isWindowFocused) return if (!this.isInEditor(event)) return // COMPAT: If the editor has nested editable elements, the focus can go to @@ -795,6 +817,28 @@ class Content extends React.Component { this.props.onSelect(event, data) } + /** + * On window blur, unset the `isWindowFocused` flag. + * + * @param {Event} event + */ + + onWindowBlur = (e) => { + debug('onWindowBlur') + this.tmp.isWindowFocused = false + } + + /** + * On window focus, update the `isWindowFocused` flag. + * + * @param {Event} event + */ + + onWindowFocus = (e) => { + debug('onWindowFocus') + this.tmp.isWindowFocused = true + } + /** * Render the editor content. * From 17ea3ed349963e91454b3d788a5242036565ab55 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 28 Apr 2017 13:04:00 -0700 Subject: [PATCH 05/16] fix to depend on prop-types for react 15.5 --- package.json | 1 + src/components/content.js | 62 ++++++++++++++++++----------------- src/components/editor.js | 40 +++++++++++----------- src/components/leaf.js | 26 ++++++++------- src/components/node.js | 18 +++++----- src/components/placeholder.js | 13 ++++---- src/components/void.js | 18 +++++----- yarn.lock | 2 +- 8 files changed, 96 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index bdfe6d2e97..14f950ce07 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "is-empty": "^1.0.0", "is-in-browser": "^1.1.3", "keycode": "^2.1.2", + "prop-types": "^15.5.8", "react-portal": "^3.0.0", "selection-is-backward": "^1.0.0", "type-of": "^2.0.1" diff --git a/src/components/content.js b/src/components/content.js index 2d441a1898..153b28c103 100644 --- a/src/components/content.js +++ b/src/components/content.js @@ -1,17 +1,19 @@ -import Base64 from '../serializers/base-64' import Debug from 'debug' +import React from 'react' +import Types from 'prop-types' +import getWindow from 'get-window' +import keycode from 'keycode' + +import TYPES from '../constants/types' +import Base64 from '../serializers/base-64' import Node from './node' -import getPoint from '../utils/get-point' +import Selection from '../models/selection' import extendSelection from '../utils/extend-selection' import findClosestNode from '../utils/find-closest-node' -import React from 'react' -import Selection from '../models/selection' -import getTransferData from '../utils/get-transfer-data' -import TYPES from '../constants/types' -import getWindow from 'get-window' import findDeepestNode from '../utils/find-deepest-node' -import keycode from 'keycode' +import getPoint from '../utils/get-point' +import getTransferData from '../utils/get-transfer-data' import { IS_FIREFOX, IS_MAC } from '../constants/environment' /** @@ -37,28 +39,28 @@ class Content extends React.Component { */ static propTypes = { - autoCorrect: React.PropTypes.bool.isRequired, - autoFocus: React.PropTypes.bool.isRequired, - children: React.PropTypes.array.isRequired, - className: React.PropTypes.string, - editor: React.PropTypes.object.isRequired, - onBeforeInput: React.PropTypes.func.isRequired, - onBlur: React.PropTypes.func.isRequired, - onChange: React.PropTypes.func.isRequired, - onCopy: React.PropTypes.func.isRequired, - onCut: React.PropTypes.func.isRequired, - onDrop: React.PropTypes.func.isRequired, - onFocus: React.PropTypes.func.isRequired, - onKeyDown: React.PropTypes.func.isRequired, - onPaste: React.PropTypes.func.isRequired, - onSelect: React.PropTypes.func.isRequired, - readOnly: React.PropTypes.bool.isRequired, - role: React.PropTypes.string, - schema: React.PropTypes.object, - spellCheck: React.PropTypes.bool.isRequired, - state: React.PropTypes.object.isRequired, - style: React.PropTypes.object, - tabIndex: React.PropTypes.number + autoCorrect: Types.bool.isRequired, + autoFocus: Types.bool.isRequired, + children: Types.array.isRequired, + className: Types.string, + editor: Types.object.isRequired, + onBeforeInput: Types.func.isRequired, + onBlur: Types.func.isRequired, + onChange: Types.func.isRequired, + onCopy: Types.func.isRequired, + onCut: Types.func.isRequired, + onDrop: Types.func.isRequired, + onFocus: Types.func.isRequired, + onKeyDown: Types.func.isRequired, + onPaste: Types.func.isRequired, + onSelect: Types.func.isRequired, + readOnly: Types.bool.isRequired, + role: Types.string, + schema: Types.object, + spellCheck: Types.bool.isRequired, + state: Types.object.isRequired, + style: Types.object, + tabIndex: Types.number } /** diff --git a/src/components/editor.js b/src/components/editor.js index 1eec43abe0..48225ef707 100644 --- a/src/components/editor.js +++ b/src/components/editor.js @@ -2,6 +2,8 @@ import Debug from 'debug' import Portal from 'react-portal' import React from 'react' +import Types from 'prop-types' + import Stack from '../models/stack' import State from '../models/state' import noop from '../utils/noop' @@ -62,24 +64,24 @@ class Editor extends React.Component { */ static propTypes = { - autoCorrect: React.PropTypes.bool, - autoFocus: React.PropTypes.bool, - className: React.PropTypes.string, - onBeforeChange: React.PropTypes.func, - onChange: React.PropTypes.func, - onDocumentChange: React.PropTypes.func, - onSelectionChange: React.PropTypes.func, - placeholder: React.PropTypes.any, - placeholderClassName: React.PropTypes.string, - placeholderStyle: React.PropTypes.object, - plugins: React.PropTypes.array, - readOnly: React.PropTypes.bool, - role: React.PropTypes.string, - schema: React.PropTypes.object, - spellCheck: React.PropTypes.bool, - state: React.PropTypes.instanceOf(State).isRequired, - style: React.PropTypes.object, - tabIndex: React.PropTypes.number, + autoCorrect: Types.bool, + autoFocus: Types.bool, + className: Types.string, + onBeforeChange: Types.func, + onChange: Types.func, + onDocumentChange: Types.func, + onSelectionChange: Types.func, + placeholder: Types.any, + placeholderClassName: Types.string, + placeholderStyle: Types.object, + plugins: Types.array, + readOnly: Types.bool, + role: Types.string, + schema: Types.object, + spellCheck: Types.bool, + state: Types.instanceOf(State).isRequired, + style: Types.object, + tabIndex: Types.number, } /** @@ -258,7 +260,7 @@ class Editor extends React.Component { */ for (const property of EVENT_HANDLERS) { - Editor.propTypes[property] = React.PropTypes.func + Editor.propTypes[property] = Types.func } /** diff --git a/src/components/leaf.js b/src/components/leaf.js index 4b5a0d98c7..2a370a01a6 100644 --- a/src/components/leaf.js +++ b/src/components/leaf.js @@ -1,8 +1,10 @@ import Debug from 'debug' -import OffsetKey from '../utils/offset-key' import React from 'react' import ReactDOM from 'react-dom' +import Types from 'prop-types' + +import OffsetKey from '../utils/offset-key' import findDeepestNode from '../utils/find-deepest-node' import { IS_FIREFOX } from '../constants/environment' @@ -29,17 +31,17 @@ class Leaf extends React.Component { */ static propTypes = { - block: React.PropTypes.object.isRequired, - editor: React.PropTypes.object.isRequired, - index: React.PropTypes.number.isRequired, - marks: React.PropTypes.object.isRequired, - node: React.PropTypes.object.isRequired, - offset: React.PropTypes.number.isRequired, - parent: React.PropTypes.object.isRequired, - ranges: React.PropTypes.object.isRequired, - schema: React.PropTypes.object.isRequired, - state: React.PropTypes.object.isRequired, - text: React.PropTypes.string.isRequired + block: Types.object.isRequired, + editor: Types.object.isRequired, + index: Types.number.isRequired, + marks: Types.object.isRequired, + node: Types.object.isRequired, + offset: Types.number.isRequired, + parent: Types.object.isRequired, + ranges: Types.object.isRequired, + schema: Types.object.isRequired, + state: Types.object.isRequired, + text: Types.string.isRequired } /** diff --git a/src/components/node.js b/src/components/node.js index cef8633fb1..c50472a749 100644 --- a/src/components/node.js +++ b/src/components/node.js @@ -1,9 +1,11 @@ -import Base64 from '../serializers/base-64' import Debug from 'debug' import React from 'react' import ReactDOM from 'react-dom' +import Types from 'prop-types' + import TYPES from '../constants/types' +import Base64 from '../serializers/base-64' import Leaf from './leaf' import Void from './void' import getWindow from 'get-window' @@ -32,13 +34,13 @@ class Node extends React.Component { */ static propTypes = { - block: React.PropTypes.object, - editor: React.PropTypes.object.isRequired, - node: React.PropTypes.object.isRequired, - parent: React.PropTypes.object.isRequired, - readOnly: React.PropTypes.bool.isRequired, - schema: React.PropTypes.object.isRequired, - state: React.PropTypes.object.isRequired + block: Types.object, + editor: Types.object.isRequired, + node: Types.object.isRequired, + parent: Types.object.isRequired, + readOnly: Types.bool.isRequired, + schema: Types.object.isRequired, + state: Types.object.isRequired } /** diff --git a/src/components/placeholder.js b/src/components/placeholder.js index 0452509656..123638dcb2 100644 --- a/src/components/placeholder.js +++ b/src/components/placeholder.js @@ -1,5 +1,6 @@ import React from 'react' +import Types from 'prop-types' /** * Placeholder. @@ -16,12 +17,12 @@ class Placeholder extends React.Component { */ static propTypes = { - children: React.PropTypes.any.isRequired, - className: React.PropTypes.string, - node: React.PropTypes.object.isRequired, - parent: React.PropTypes.object.isRequired, - state: React.PropTypes.object.isRequired, - style: React.PropTypes.object + children: Types.any.isRequired, + className: Types.string, + node: Types.object.isRequired, + parent: Types.object.isRequired, + state: Types.object.isRequired, + style: Types.object } /** diff --git a/src/components/void.js b/src/components/void.js index 14c5c595f4..78469ce75f 100644 --- a/src/components/void.js +++ b/src/components/void.js @@ -1,9 +1,11 @@ import Debug from 'debug' +import React from 'react' +import Types from 'prop-types' + import Leaf from './leaf' import Mark from '../models/mark' import OffsetKey from '../utils/offset-key' -import React from 'react' import { IS_FIREFOX } from '../constants/environment' /** @@ -29,13 +31,13 @@ class Void extends React.Component { */ static propTypes = { - block: React.PropTypes.object, - children: React.PropTypes.any.isRequired, - editor: React.PropTypes.object.isRequired, - node: React.PropTypes.object.isRequired, - parent: React.PropTypes.object.isRequired, - schema: React.PropTypes.object.isRequired, - state: React.PropTypes.object.isRequired, + block: Types.object, + children: Types.any.isRequired, + editor: Types.object.isRequired, + node: Types.object.isRequired, + parent: Types.object.isRequired, + schema: Types.object.isRequired, + state: Types.object.isRequired, } /** diff --git a/yarn.lock b/yarn.lock index 292fd2398f..c483db2eb8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4841,7 +4841,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.7, prop-types@~15.5.7: +prop-types@^15.5.7, prop-types@^15.5.8, prop-types@~15.5.7: version "15.5.8" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.8.tgz#6b7b2e141083be38c8595aa51fc55775c7199394" dependencies: From 26777b43a5bdc2155ee35bc9957317abd59df952 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 28 Apr 2017 13:17:00 -0700 Subject: [PATCH 06/16] fix error when dragging void nodes without selection, closes #757 --- src/utils/scroll-to-selection.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/scroll-to-selection.js b/src/utils/scroll-to-selection.js index 62b0cab1e4..618e4795cf 100644 --- a/src/utils/scroll-to-selection.js +++ b/src/utils/scroll-to-selection.js @@ -9,6 +9,8 @@ import isBackward from 'selection-is-backward' */ function scrollToSelection(selection) { + if (!selection.anchorNode) return + const window = getWindow(selection.anchorNode) const backward = isBackward(selection) const range = selection.getRangeAt(0) From 9cf24b6081c9866623dd3194e997d638846d4b98 Mon Sep 17 00:00:00 2001 From: tashburn Date: Sun, 30 Apr 2017 22:26:56 -0600 Subject: [PATCH 07/16] Fixed the link to comparisons, which was broken (#769) --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 1e6d786db5..f180fd1e18 100644 --- a/Readme.md +++ b/Readme.md @@ -57,7 +57,7 @@ Here's how Slate compares to some of the existing editors out there: - [**Quill**](http://quilljs.com/) — I never used Quill directly, so my hesitations about it are solely from considering it in early stages—and it has changed since then. The issues I see with it are: that the concept of "toolbars" is too coupled with the editor itself, that the configuration is too coupled to HTML classes and DOM nodes, that the idea of "formats" and "toolbars" being linked is limiting, and generally that too much "core" logic is given special privileges and is hard to customize. -For more opinionated, and potentially useless, comparisons check out the [Comparisons](./docs/comparisons.md) document._ +For more opinionated, and potentially useless, comparisons check out the [Comparisons](./docs/general/comparisons.md) document._ Of course those are my own opinions, and if those libraries solve your needs, use them! But if you've tried using any of those libraries you might have run into similar problems. If so, you might like Slate. Which brings me to how Slate solves all of that... From 78fefa1115240a0ecb8a6067af9a25732d3d1fd0 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Mon, 1 May 2017 11:18:35 -0700 Subject: [PATCH 08/16] update table example to make scope clearer --- examples/tables/state.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tables/state.json b/examples/tables/state.json index 1a2d315379..e9f2c4ecd9 100644 --- a/examples/tables/state.json +++ b/examples/tables/state.json @@ -233,7 +233,7 @@ "nodes": [ { "kind": "text", - "text": "This table is just a basic example, but you could augment it to add support for table headers, adding column and rows, or even formulas if you wanted to get really crazy..." + "text": "This table is just a basic example of rendering a table, and it doesn\'t have fancy functionality. But you could augment it to add support for navigating with arrow keys, displaying table headers, adding column and rows, or even formulas if you wanted to get really crazy!" } ] } From 460498b5ddfcecee7439eafe4f4d31cacde69f41 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Mon, 1 May 2017 11:23:49 -0700 Subject: [PATCH 09/16] 0.19.21 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 14f950ce07..8e94016b62 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "slate", "description": "A completely customizable framework for building rich text editors.", - "version": "0.19.20", + "version": "0.19.21", "license": "MIT", "repository": "git://github.com/ianstormtaylor/slate.git", "main": "./lib/index.js", From cb98408115927a9d34d9562a16a9110abb16f7ee Mon Sep 17 00:00:00 2001 From: Brian Kabiro Date: Thu, 4 May 2017 02:51:13 +0300 Subject: [PATCH 10/16] Update defining-custom-block-nodes.md (#776) --- docs/walkthroughs/defining-custom-block-nodes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/walkthroughs/defining-custom-block-nodes.md b/docs/walkthroughs/defining-custom-block-nodes.md index c89197f349..a9bc84cbee 100644 --- a/docs/walkthroughs/defining-custom-block-nodes.md +++ b/docs/walkthroughs/defining-custom-block-nodes.md @@ -50,7 +50,7 @@ class App extends React.Component { Now let's add "code blocks" to our editor. -The problem is, code blocks won't just be rendered as a plain paragraph, they'll need to be renderer differently. To make that happen, we need to define a "renderer" for `code` nodes +The problem is, code blocks won't just be rendered as a plain paragraph, they'll need to be rendered differently. To make that happen, we need to define a "renderer" for `code` nodes Node renderers are just simple React components, like so: From 2e3b294fd3be4c9e6c328dd404a2f561b8dde2bc Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Wed, 3 May 2017 18:33:05 -0700 Subject: [PATCH 11/16] remove warn throwing since console.warn includes callsites now --- src/utils/warn.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/utils/warn.js b/src/utils/warn.js index ae6125907f..2a1d89a44b 100644 --- a/src/utils/warn.js +++ b/src/utils/warn.js @@ -16,15 +16,6 @@ function warn(message, ...args) { if (typeof console !== 'undefined') { console.warn(`Warning: ${message}`, ...args) // eslint-disable-line no-console } - - try { - // --- Welcome to debugging Slate! --- - // This error was thrown as a convenience so that you can use this stack - // to find the callsite that caused this warning to fire. - throw new Error(message) - } catch (x) { - // This error is only for debugging. - } } /** From 0fcb9530153b973924298c6c6023ca20da9c5555 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Wed, 3 May 2017 18:48:42 -0700 Subject: [PATCH 12/16] fix to restrict window blur/focus handling, closes #773 --- package.json | 1 + src/components/content.js | 7 +++++-- yarn.lock | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8e94016b62..6b27f0c85b 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "immutable": "^3.8.1", "is-empty": "^1.0.0", "is-in-browser": "^1.1.3", + "is-window": "^1.0.2", "keycode": "^2.1.2", "prop-types": "^15.5.8", "react-portal": "^3.0.0", diff --git a/src/components/content.js b/src/components/content.js index 153b28c103..bcd4903c91 100644 --- a/src/components/content.js +++ b/src/components/content.js @@ -3,6 +3,7 @@ import Debug from 'debug' import React from 'react' import Types from 'prop-types' import getWindow from 'get-window' +import isWindow from 'is-window' import keycode from 'keycode' import TYPES from '../constants/types' @@ -825,7 +826,8 @@ class Content extends React.Component { * @param {Event} event */ - onWindowBlur = (e) => { + onWindowBlur = (event) => { + if (!isWindow(event.target)) return debug('onWindowBlur') this.tmp.isWindowFocused = false } @@ -836,7 +838,8 @@ class Content extends React.Component { * @param {Event} event */ - onWindowFocus = (e) => { + onWindowFocus = (event) => { + if (!isWindow(event.target)) return debug('onWindowFocus') this.tmp.isWindowFocused = true } diff --git a/yarn.lock b/yarn.lock index c483db2eb8..65df5b7204 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3374,6 +3374,10 @@ is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" +is-window@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-window/-/is-window-1.0.2.tgz#2c896ca53db97de45d3c33133a65d8c9f563480d" + isarray@0.0.1, isarray@~0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" From 29c67fd45f0a43b37db2b7ce28a87bf1d86284c8 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Wed, 3 May 2017 19:44:20 -0700 Subject: [PATCH 13/16] fix selection handling for changing tabs, and inside embedded inputs, closes #749 --- src/components/content.js | 100 +++++++++++--------------------------- 1 file changed, 28 insertions(+), 72 deletions(-) diff --git a/src/components/content.js b/src/components/content.js index bcd4903c91..3bc9b530e4 100644 --- a/src/components/content.js +++ b/src/components/content.js @@ -3,7 +3,6 @@ import Debug from 'debug' import React from 'react' import Types from 'prop-types' import getWindow from 'get-window' -import isWindow from 'is-window' import keycode from 'keycode' import TYPES from '../constants/types' @@ -117,18 +116,11 @@ class Content extends React.Component { /** * When the editor first mounts in the DOM we need to: * - * - Setup the original state for `isWindowFocused`. - * - Attach window focus and blur listeners for tab switching. * - Update the selection, in case it starts focused. * - Focus the editor if `autoFocus` is set. */ componentDidMount = () => { - const window = getWindow(this.element) - window.addEventListener('focus', this.onWindowFocus, true) - window.addEventListener('blur', this.onWindowBlur, true) - this.tmp.isWindowFocused = !window.document.hidden - this.updateSelection() if (this.props.autoFocus) { @@ -144,16 +136,6 @@ class Content extends React.Component { this.updateSelection() } - /** - * Before unmounting, detach our window focus and blur listeners. - */ - - componentWillUnmount = () => { - const window = getWindow(this.element) - window.removeEventListener('focus', this.onWindowFocus, true) - window.removeEventListener('blur', this.onWindowBlur, true) - } - /** * Update the native DOM selection to reflect the internal model. */ @@ -167,10 +149,10 @@ class Content extends React.Component { // If both selections are blurred, do nothing. if (!native.rangeCount && selection.isBlurred) return - // If the selection has been blurred, but hasn't been updated in the DOM, - // blur the DOM selection. + // If the selection has been blurred, but is still inside the editor in the + // DOM, blur it manually. if (selection.isBlurred) { - if (!this.element.contains(native.anchorNode)) return + if (!this.isInEditor(native.anchorNode)) return native.removeAllRanges() this.element.blur() debug('updateSelection', { selection, native }) @@ -255,17 +237,16 @@ class Content extends React.Component { } /** - * Check if an `event` is being fired from within the contenteditable element. - * This will return false for edits happening in non-contenteditable children, - * such as void nodes and other nested Slate editors. + * Check if an event `target` is fired from within the contenteditable + * element. This should be false for edits happening in non-contenteditable + * children, such as void nodes and other nested Slate editors. * - * @param {Event} event + * @param {Element} target * @return {Boolean} */ - isInEditor = (event) => { + isInEditor = (target) => { const { element } = this - const { target } = event return ( (target.isContentEditable) && (target == element || findClosestNode(target, '[data-slate-editor]') == element) @@ -280,7 +261,7 @@ class Content extends React.Component { onBeforeInput = (event) => { if (this.props.readOnly) return - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return const data = {} @@ -297,13 +278,13 @@ class Content extends React.Component { onBlur = (event) => { if (this.props.readOnly) return if (this.tmp.isCopying) return - if (!this.tmp.isWindowFocused) return - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return - // If the element that is now focused is actually inside the editor, we - // need to ignore it. This can happen in situations where there is a nested - // `contenteditable="true"` node that isn't another Slate editor. - if (this.element.contains(event.relatedTarget)) return + // If the active element is still the editor, the blur event is due to the + // window itself being blurred (eg. when changing tabs) so we should ignore + // the event, since we want to maintain focus when returning. + const window = getWindow(this.element) + if (window.document.activeElement == this.element) return const data = {} @@ -320,8 +301,7 @@ class Content extends React.Component { onFocus = (event) => { if (this.props.readOnly) return if (this.tmp.isCopying) return - if (!this.tmp.isWindowFocused) return - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return // COMPAT: If the editor has nested editable elements, the focus can go to // those elements. In Firefox, this must be prevented because it results in @@ -355,7 +335,7 @@ class Content extends React.Component { */ onCompositionStart = (event) => { - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return this.tmp.isComposing = true this.tmp.compositions++ @@ -372,7 +352,7 @@ class Content extends React.Component { */ onCompositionEnd = (event) => { - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return this.tmp.forces++ const count = this.tmp.compositions @@ -395,7 +375,7 @@ class Content extends React.Component { */ onCopy = (event) => { - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return const window = getWindow(event.target) this.tmp.isCopying = true @@ -420,7 +400,7 @@ class Content extends React.Component { onCut = (event) => { if (this.props.readOnly) return - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return const window = getWindow(event.target) this.tmp.isCopying = true @@ -444,7 +424,7 @@ class Content extends React.Component { */ onDragEnd = (event) => { - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return this.tmp.isDragging = false this.tmp.isInternalDrag = null @@ -459,7 +439,7 @@ class Content extends React.Component { */ onDragOver = (event) => { - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return const { dataTransfer } = event.nativeEvent const data = getTransferData(dataTransfer) @@ -483,7 +463,7 @@ class Content extends React.Component { */ onDragStart = (event) => { - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return this.tmp.isDragging = true this.tmp.isInternalDrag = true @@ -509,7 +489,7 @@ class Content extends React.Component { onDrop = (event) => { if (this.props.readOnly) return - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return event.preventDefault() @@ -567,7 +547,7 @@ class Content extends React.Component { onInput = (event) => { if (this.tmp.isComposing) return if (this.props.state.isBlurred) return - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return debug('onInput', { event }) const window = getWindow(event.target) @@ -638,7 +618,7 @@ class Content extends React.Component { onKeyDown = (event) => { if (this.props.readOnly) return - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return const { altKey, ctrlKey, metaKey, shiftKey, which } = event const key = keycode(which) @@ -716,7 +696,7 @@ class Content extends React.Component { onPaste = (event) => { if (this.props.readOnly) return - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return event.preventDefault() const data = getTransferData(event.clipboardData) @@ -740,7 +720,7 @@ class Content extends React.Component { if (this.tmp.isCopying) return if (this.tmp.isComposing) return if (this.tmp.isSelecting) return - if (!this.isInEditor(event)) return + if (!this.isInEditor(event.target)) return const window = getWindow(event.target) const { state, editor } = this.props @@ -820,30 +800,6 @@ class Content extends React.Component { this.props.onSelect(event, data) } - /** - * On window blur, unset the `isWindowFocused` flag. - * - * @param {Event} event - */ - - onWindowBlur = (event) => { - if (!isWindow(event.target)) return - debug('onWindowBlur') - this.tmp.isWindowFocused = false - } - - /** - * On window focus, update the `isWindowFocused` flag. - * - * @param {Event} event - */ - - onWindowFocus = (event) => { - if (!isWindow(event.target)) return - debug('onWindowFocus') - this.tmp.isWindowFocused = true - } - /** * Render the editor content. * From 5258b38508f1ac24abcaa954cd91931d5ac8a2eb Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Wed, 3 May 2017 19:45:52 -0700 Subject: [PATCH 14/16] 0.19.22 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6b27f0c85b..4c93405d84 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "slate", "description": "A completely customizable framework for building rich text editors.", - "version": "0.19.21", + "version": "0.19.22", "license": "MIT", "repository": "git://github.com/ianstormtaylor/slate.git", "main": "./lib/index.js", From fb6f3d5b6103832ef1936a91ea35c7864bb8ee8a Mon Sep 17 00:00:00 2001 From: Anuj Date: Thu, 4 May 2017 05:57:52 -0700 Subject: [PATCH 15/16] Documenting node's 'getFirstText' & 'getLastText' (#779) Noticed a couple of useful functions I found useful that weren't documented. Also, fixed a couple of broken links. --- docs/reference/models/node.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/reference/models/node.md b/docs/reference/models/node.md index 467890a253..101455fd8c 100644 --- a/docs/reference/models/node.md +++ b/docs/reference/models/node.md @@ -12,7 +12,7 @@ - [Methods](#methods) - [`filterDescendants`](#filterdescendants) - [`findDescendant`](#finddescendant) - - [`getBlocksAtRange`](#getblockatrange) + - [`getBlocksAtRange`](#getblocksatrange) - [`getBlocks`](#getblocks) - [`getCharactersAtRange`](#getcharactersatrange) - [`getChild`](#getchild) @@ -21,13 +21,15 @@ - [`getClosest`](#getclosest) - [`getDepth`](#getdepth) - [`getDescendant`](#getdescendant) + - [`getFirstText`](#getfirsttext) - [`getFragmentAtRange`](#getfragmentatrange) - [`getInlinesAtRange`](#getinlinesatrange) + - [`getLastText`](#getlasttext) - [`getMarksAtRange`](#getmarksatrange) - [`getNextBlock`](#getnextblock) - [`getNextSibling`](#getnextsibling) - [`getNextText`](#getnexttext) - - [`getParent`](#parent) + - [`getParent`](#getparent) - [`getPreviousBlock`](#getpreviousblock) - [`getPreviousSibling`](#getprevioussibling) - [`getPreviousText`](#getprevioustext) @@ -129,6 +131,11 @@ Get the depth of a descendant node by `key`. Get a descendant node by `key`. +### `getFirstText` +`getFirstText() => Node || Void` + +Get the first child text node inside a node. + ### `getFragmentAtRange` `getFragmentAtRange(range: Selection) => Document` @@ -139,6 +146,11 @@ Get a document fragment of the nodes in a `range`. Get all of the top-most [`Inline`](./inline.md) nodes in a `range`. +### `getLastText` +`getLastText() => Node || Void` + +Get the last child text node inside a node. + ### `getMarksAtRange` `getMarksAtRange(range: Selection) => Set` From 9250baafe6f7f60cf71764dc509c892bb18cd3c9 Mon Sep 17 00:00:00 2001 From: Anuj Date: Thu, 4 May 2017 05:58:21 -0700 Subject: [PATCH 16/16] Clarifying insertTextByKey description (#778) As per #775 --- docs/reference/models/transform.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/models/transform.md b/docs/reference/models/transform.md index 2258303b1e..e99de69852 100644 --- a/docs/reference/models/transform.md +++ b/docs/reference/models/transform.md @@ -335,7 +335,7 @@ Insert a `node` at `index` inside a parent [`Node`](./node.md) by its `key`. ### `insertTextByKey` `insertTextByKey(key: String, offset: Number, text: String, [marks: Set]) => Transform` -Insert `text` at an `offset` in a [`Node`](./node.md) with optional `marks`. +Insert `text` at an `offset` in a [`Text Node`](./text.md) with optional `marks`. ### `moveNodeByKey` `moveNodeByKey(key: String, newKey: String, newIndex: Number) => Transform`