1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-22 06:53:25 +02:00

cleanup many things in ./utils (#1061)

This commit is contained in:
Ian Storm Taylor
2017-09-06 15:27:55 -07:00
committed by GitHub
parent a5ce937986
commit e51cf5e028
20 changed files with 218 additions and 207 deletions

View File

@@ -59,7 +59,7 @@ class RichText extends React.Component {
state = {
state: Raw.deserialize(initialState, { terse: true })
};
}
/**
* Check if the current selection has a mark with `type` in it.

View File

@@ -9,13 +9,14 @@ import TRANSFER_TYPES from '../constants/transfer-types'
import Base64 from '../serializers/base-64'
import Node from './node'
import Selection from '../models/selection'
import SlateTypes from '../utils/prop-types'
import extendSelection from '../utils/extend-selection'
import findClosestNode from '../utils/find-closest-node'
import findDeepestNode from '../utils/find-deepest-node'
import getHtmlFromNativePaste from '../utils/get-html-from-native-paste'
import getPoint from '../utils/get-point'
import getTransferData from '../utils/get-transfer-data'
import setTransferData from '../utils/set-transfer-data'
import getHtmlFromNativePaste from '../utils/get-html-from-native-paste'
import { IS_FIREFOX, IS_MAC, IS_IE } from '../constants/environment'
/**
@@ -58,12 +59,12 @@ class Content extends React.Component {
onSelect: Types.func.isRequired,
readOnly: Types.bool.isRequired,
role: Types.string,
schema: Types.object,
schema: SlateTypes.schema.isRequired,
spellCheck: Types.bool.isRequired,
state: Types.object.isRequired,
state: SlateTypes.state.isRequired,
style: Types.object,
tabIndex: Types.number,
tagName: Types.string
tagName: Types.string,
}
/**
@@ -74,7 +75,7 @@ class Content extends React.Component {
static defaultProps = {
style: {},
tagName: 'div'
tagName: 'div',
}
/**

View File

@@ -6,7 +6,7 @@ import Types from 'prop-types'
import Stack from '../models/stack'
import State from '../models/state'
import SlatePropTypes from '../utils/prop-types'
import SlateTypes from '../utils/prop-types'
import noop from '../utils/noop'
/**
@@ -81,7 +81,7 @@ class Editor extends React.Component {
role: Types.string,
schema: Types.object,
spellCheck: Types.bool,
state: SlatePropTypes.state.isRequired,
state: SlateTypes.state.isRequired,
style: Types.object,
tabIndex: Types.number,
}

View File

@@ -5,6 +5,7 @@ import ReactDOM from 'react-dom'
import Types from 'prop-types'
import OffsetKey from '../utils/offset-key'
import SlateTypes from '../utils/prop-types'
import findDeepestNode from '../utils/find-deepest-node'
import { IS_FIREFOX } from '../constants/environment'
@@ -31,17 +32,17 @@ class Leaf extends React.Component {
*/
static propTypes = {
block: Types.object.isRequired,
block: SlateTypes.block.isRequired,
editor: Types.object.isRequired,
index: Types.number.isRequired,
marks: Types.object.isRequired,
node: Types.object.isRequired,
marks: SlateTypes.marks.isRequired,
node: SlateTypes.node.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
parent: SlateTypes.node.isRequired,
ranges: SlateTypes.ranges.isRequired,
schema: SlateTypes.schema.isRequired,
state: SlateTypes.state.isRequired,
text: Types.string.isRequired,
}
/**

View File

@@ -7,6 +7,7 @@ import Types from 'prop-types'
import TRANSFER_TYPES from '../constants/transfer-types'
import Base64 from '../serializers/base-64'
import Leaf from './leaf'
import SlateTypes from '../utils/prop-types'
import Void from './void'
import getWindow from 'get-window'
import scrollToSelection from '../utils/scroll-to-selection'
@@ -35,13 +36,13 @@ class Node extends React.Component {
*/
static propTypes = {
block: Types.object,
block: SlateTypes.block,
editor: Types.object.isRequired,
node: Types.object.isRequired,
parent: Types.object.isRequired,
node: SlateTypes.node.isRequired,
parent: SlateTypes.node.isRequired,
readOnly: Types.bool.isRequired,
schema: Types.object.isRequired,
state: Types.object.isRequired
schema: SlateTypes.schema.isRequired,
state: SlateTypes.state.isRequired,
}
/**

View File

@@ -2,6 +2,8 @@
import React from 'react'
import Types from 'prop-types'
import SlateTypes from '../utils/prop-types'
/**
* Placeholder.
*
@@ -20,10 +22,10 @@ class Placeholder extends React.Component {
children: Types.any.isRequired,
className: Types.string,
firstOnly: Types.bool,
node: Types.object.isRequired,
parent: Types.object,
state: Types.object.isRequired,
style: Types.object
node: SlateTypes.node.isRequired,
parent: SlateTypes.node,
state: SlateTypes.state.isRequired,
style: Types.object,
}
/**
@@ -33,7 +35,7 @@ class Placeholder extends React.Component {
*/
static defaultProps = {
firstOnly: true
firstOnly: true,
}
/**

View File

@@ -6,6 +6,7 @@ import Types from 'prop-types'
import Leaf from './leaf'
import Mark from '../models/mark'
import OffsetKey from '../utils/offset-key'
import SlateTypes from '../utils/prop-types'
import { IS_FIREFOX } from '../constants/environment'
/**
@@ -31,14 +32,14 @@ class Void extends React.Component {
*/
static propTypes = {
block: Types.object,
block: SlateTypes.block,
children: Types.any.isRequired,
editor: Types.object.isRequired,
node: Types.object.isRequired,
parent: Types.object.isRequired,
node: SlateTypes.node.isRequired,
parent: SlateTypes.node.isRequired,
readOnly: Types.bool.isRequired,
schema: Types.object.isRequired,
state: Types.object.isRequired,
schema: SlateTypes.schema.isRequired,
state: SlateTypes.state.isRequired,
}
/**
@@ -49,7 +50,7 @@ class Void extends React.Component {
state = {
dragCounter: 0,
editable: false
editable: false,
}
/**

View File

@@ -108,6 +108,17 @@ class Block extends Record(DEFAULTS) {
return !!(value && value[MODEL_TYPES.BLOCK])
}
/**
* Check if a `value` is a block list.
*
* @param {Any} value
* @return {Boolean}
*/
static isBlockList(value) {
return List.isList(value) && value.size > 0 && Block.isBlock(value.first())
}
/**
* Get the node's kind.
*

View File

@@ -85,6 +85,17 @@ class Character extends Record(DEFAULTS) {
return !!(value && value[MODEL_TYPES.CHARACTER])
}
/**
* Check if a `value` is a character list.
*
* @param {Any} value
* @return {Boolean}
*/
static isCharacterList(value) {
return List.isList(value) && value.size > 0 && Character.isCharacter(value.first())
}
/**
* Deprecated.
*/

View File

@@ -108,6 +108,17 @@ class Inline extends Record(DEFAULTS) {
return !!(value && value[MODEL_TYPES.INLINE])
}
/**
* Check if a `value` is a list of inlines.
*
* @param {Any} value
* @return {Boolean}
*/
static isInlineList(value) {
return List.isList(value) && value.size > 0 && Inline.isInline(value.first())
}
/**
* Get the node's kind.
*

View File

@@ -118,6 +118,17 @@ class Mark extends Record(DEFAULTS) {
return !!(value && value[MODEL_TYPES.MARK])
}
/**
* Check if a `value` is a set of marks.
*
* @param {Any} value
* @return {Boolean}
*/
static isMarkSet(value) {
return Set.isSet(value) && value.size > 0 && Mark.isMark(value.first())
}
/**
* Get the kind.
*/

View File

@@ -113,6 +113,17 @@ class Node {
)
}
/**
* Check if a `value` is a list of nodes.
*
* @param {Any} value
* @return {Boolean}
*/
static isNodeList(value) {
return List.isList(value) && value.size > 0 && Node.isNode(value.first())
}
/**
* True if the node has both descendants in that order, false otherwise. The
* order is depth-first, post-order.

View File

@@ -3,7 +3,7 @@ import MODEL_TYPES from '../constants/model-types'
import Character from './character'
import Mark from './mark'
import isPlainObject from 'is-plain-object'
import { Record, Set } from 'immutable'
import { List, Record, Set } from 'immutable'
/**
* Default properties.
@@ -64,6 +64,17 @@ class Range extends Record(DEFAULTS) {
return !!(value && value[MODEL_TYPES.RANGE])
}
/**
* Check if a `value` is a list of ranges.
*
* @param {Any} value
* @return {Boolean}
*/
static isRangeList(value) {
return List.isList(value) && value.size > 0 && Range.isRange(value.first())
}
/**
* Get the node's kind.
*

View File

@@ -94,6 +94,17 @@ class Text extends Record(DEFAULTS) {
return !!(value && value[MODEL_TYPES.TEXT])
}
/**
* Check if a `value` is a list of texts.
*
* @param {Any} value
* @return {Boolean}
*/
static isTextList(value) {
return List.isList(value) && value.size > 0 && Text.isText(value.first())
}
/**
* Deprecated.
*/

View File

@@ -1,41 +1,49 @@
/**
* Extends the given selection to a given node and offset
* Extends a DOM `selection` to a given `el` and `offset`.
*
* @param {Selection} selection Selection instance
* @param {Element} el Node to extend to
* @param {Number} offset Text offset to extend to
* @returns {Selection} Mutated Selection instance
* COMPAT: In IE11, `selection.extend` doesn't exist natively, so we have to
* polyfill it with this. (2017/09/06)
*
* https://gist.github.com/tyler-johnson/0a3e8818de3f115b2a2dc47468ac0099
*
* @param {Selection} selection
* @param {Element} el
* @param {Number} offset
* @return {Selection}
*/
function extendSelection(selection, el, offset) {
// Use native method when possible
if (typeof selection.extend === 'function') return selection.extend(el, offset)
// Use native method whenever possible.
if (typeof selection.extend === 'function') {
return selection.extend(el, offset)
}
// See https://gist.github.com/tyler-johnson/0a3e8818de3f115b2a2dc47468ac0099
const range = document.createRange()
const anchor = document.createRange()
anchor.setStart(selection.anchorNode, selection.anchorOffset)
const focus = document.createRange()
anchor.setStart(selection.anchorNode, selection.anchorOffset)
focus.setStart(el, offset)
const v = focus.compareBoundaryPoints(Range.START_TO_START, anchor)
if (v >= 0) { // Focus is after anchor
// If the focus is after the anchor...
if (v >= 0) {
range.setStart(selection.anchorNode, selection.anchorOffset)
range.setEnd(el, offset)
} else { // Anchor is after focus
}
// Otherwise, if the anchor if after the focus...
else {
range.setStart(el, offset)
range.setEnd(selection.anchorNode, selection.anchorOffset)
}
selection.removeAllRanges()
selection.addRange(range)
return selection
}
/**
* Export.
*

View File

@@ -2,6 +2,11 @@
/**
* Find the closest ancestor of a DOM `element` that matches a given selector.
*
* COMPAT: In IE11, the `Node.closest` method doesn't exist natively, so we
* have to polyfill it. (2017/09/06)
*
* https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
*
* @param {Element} node
* @param {String} selector
* @return {Element}
@@ -10,10 +15,10 @@
function findClosestNode(node, selector) {
if (typeof node.closest === 'function') return node.closest(selector)
// See https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
const matches = (node.document || node.ownerDocument).querySelectorAll(selector)
let i
let parentNode = node
let i
do {
i = matches.length
while (--i >= 0 && matches.item(i) !== parentNode);

View File

@@ -10,9 +10,7 @@ function findDOMNode(node) {
const el = window.document.querySelector(`[data-key="${node.key}"]`)
if (!el) {
throw new Error(`Unable to find a DOM node for "${node.key}". This is
often because of forgetting to add \`props.attributes\` to a component
returned from \`renderNode\`.`)
throw new Error(`Unable to find a DOM node for "${node.key}". This is often because of forgetting to add \`props.attributes\` to a component returned from \`renderNode\`.`)
}
return el

View File

@@ -1,3 +1,4 @@
import { findDOMNode } from 'react-dom'
/**
@@ -7,28 +8,27 @@ import { findDOMNode } from 'react-dom'
* is needed to return the HTML content. This solution was adapted from
* http://stackoverflow.com/a/6804718.
*
* @param {React.Component} component
* @param {Component} component
* @param {Function} callback
*/
function getHtmlFromNativePaste(component, callback) {
const el = findDOMNode(component)
// Clone contentedible element, move out of screen and set focus.
// Create an off-screen clone of the element and give it focus.
const clone = el.cloneNode()
clone.setAttribute('class', '')
clone.setAttribute('style', 'position: fixed; left: -9999px')
el.parentNode.insertBefore(clone, el)
clone.focus()
// Clear call stack to let native paste behaviour occur on cloned element,
// then get what was pasted from the DOM and remove cloned element.
// Tick forward so the native paste behaviour occurs in cloned element and we
// can get what was pasted from the DOM.
setTimeout(() => {
if (clone.childElementCount > 0) {
// If the node contains any child nodes, that is the HTML content.
const html = clone.innerHTML
clone.parentNode.removeChild(clone)
callback(html)
} else {
// Only plain text, no HTML.

View File

@@ -1,172 +1,84 @@
import { Set } from 'immutable'
import Block from '../models/block'
import Change from '../models/change'
import Character from '../models/character'
import Data from '../models/data'
import Document from '../models/document'
import History from '../models/history'
import Inline from '../models/inline'
import Mark from '../models/mark'
import Node from '../models/node'
import Range from '../models/range'
import Schema from '../models/schema'
import Selection from '../models/selection'
import Stack from '../models/stack'
import State from '../models/state'
import Text from '../models/text'
/**
* HOC Function that takes in a predicate prop type function, and allows an isRequired chain
* Create a prop type checker for Slate objects with `name` and `validate`.
*
* @param {Function} predicate
* @param {String} name
* @param {Function} validate
* @return {Function}
*/
function createChainablePropType(predicate) {
function propType(props, propName, componentName) {
if (props[propName] == null) return
return predicate(props, propName, componentName)
function create(name, validate) {
function check(isRequired, props, propName, componentName, location) {
const value = props[propName]
if (value == null && !isRequired) return null
if (value == null && isRequired) return new Error(`The ${location} \`${propName}\` is marked as required in \`${componentName}\`, but it was not supplied.`)
if (validate(value)) return null
return new Error(`Invalid ${location} \`${propName}\` supplied to \`${componentName}\`, expected a Slate \`${name}\` but received: ${value}`)
}
propType.isRequired = (props, propName, componentName) => {
if (props[propName] == null) return new Error(`Required prop \`${propName}\` was not specified in \`${componentName}\`.`)
function propType(...args) {
return check(false, ...args)
}
return predicate(props, propName, componentName)
propType.isRequired = function (...args) {
return check(true, ...args)
}
return propType
}
/**
* Exported Slate proptype that checks if a prop is a Slate Block
*
* @type {Function}
*/
const block = createChainablePropType(
(props, propName, componentName) => {
return !Block.isBlock(props[propName]) ? new Error(`${propName} in ${componentName} is not a Slate Block`) : null
}
)
/**
* Exported Slate proptype that checks if a prop is a Slate Character
*
* @type {Function}
*/
const character = createChainablePropType(
(props, propName, componentName) => {
return !Character.isCharacter(props[propName]) ? new Error(`${propName} in ${componentName} is not a Slate Character`) : null
}
)
/**
* Exported Slate proptype that checks if a prop is a Slate Document
*
* @type {Function}
*/
const document = createChainablePropType(
(props, propName, componentName) => {
return !Document.isDocument(props[propName]) ? new Error(`${propName} in ${componentName} is not a Slate Document`) : null
}
)
/**
* Exported Slate proptype that checks if a prop is a Slate Inline
*
* @type {Function}
*/
const inline = createChainablePropType(
(props, propName, componentName) => {
return !Inline.isInline(props[propName]) ? new Error(`${propName} in ${componentName} is not a Slate Inline`) : null
}
)
/**
* Exported Slate proptype that checks if a prop is a Slate Mark
*
* @type {Function}
*/
const mark = createChainablePropType(
(props, propName, componentName) => {
return !Mark.isMark(props[propName]) ? new Error(`${propName} in ${componentName} is not a Slate Mark`) : null
}
)
/**
* Exported Slate proptype that checks if a prop is a Slate Range
*
* @type {Function}
*/
const range = createChainablePropType(
(props, propName, componentName) => {
return !Range.isRange(props[propName]) ? new Error(`${propName} in ${componentName} is not a Slate Range`) : null
}
)
/**
* Exported Slate proptype that checks if a prop is a Slate Schema
*
* @type {Function}
*/
const schema = createChainablePropType(
(props, propName, componentName) => {
return !Schema.isSchema(props[propName]) ? new Error(`${propName} in ${componentName} is not a Slate Schema`) : null
}
)
/**
* Exported Slate proptype that checks if a prop is a Slate Selection
*
* @type {Function}
*/
const selection = createChainablePropType(
(props, propName, componentName) => {
return !Selection.isSelection(props[propName]) ? new Error(`${propName} in ${componentName} is not a Slate Selection`) : null
}
)
/**
* Exported Slate proptype that checks if a prop is a Slate State
*
* @type {Function}
*/
const state = createChainablePropType(
(props, propName, componentName) => {
return !State.isState(props[propName]) ? new Error(`${propName} in ${componentName} is not a Slate State ${props[propName]}`) : null
}
)
/**
* Exported Slate proptype that checks if a prop is a Slate Text
*
* @type {Function}
*/
const text = createChainablePropType(
(props, propName, componentName) => {
return !Text.isText(props[propName]) ? new Error(`${propName} in ${componentName} is not a Slate Text`) : null
}
)
/**
* Exported Slate proptypes
* Prop type checkers.
*
* @type {Object}
*/
export default {
block,
character,
document,
inline,
mark,
range,
schema,
selection,
state,
text,
const Types = {
block: create('Block', v => Block.isBlock(v)),
blocks: create('List<Block>', v => Block.isBlockList(v)),
change: create('Change', v => Change.isChange(v)),
character: create('Character', v => Character.isCharacter(v)),
characters: create('List<Character>', v => Character.isCharacterList(v)),
data: create('Data', v => Data.isData(v)),
document: create('Document', v => Document.isDocument(v)),
history: create('History', v => History.isHistory(v)),
inline: create('Inline', v => Inline.isInline(v)),
mark: create('Mark', v => Mark.isMark(v)),
marks: create('Set<Mark>', v => (Set.isSet(v) && v.size === 0) || Mark.isMarkSet(v)),
node: create('Node', v => Node.isNode(v)),
nodes: create('List<Node>', v => Node.isNodeList(v)),
range: create('Range', v => Range.isRange(v)),
ranges: create('List<Range>', v => Range.isRangeList(v)),
schema: create('Schema', v => Schema.isSchema(v)),
selection: create('Selection', v => Selection.isSelection(v)),
stack: create('Stack', v => Stack.isStack(v)),
state: create('State', v => State.isState(v)),
text: create('Text', v => Text.isText(v)),
texts: create('List<Text>', v => Text.isTextList(v)),
}
/**
* Export.
*
* @type {Object}
*/
export default Types

View File

@@ -1,5 +1,7 @@
/**
* Set data on dataTransfer
* Set data with `type` and `content` on a `dataTransfer` object.
*
* COMPAT: In Edge, custom types throw errors, so embed all non-standard
* types in text/plain compound object. (2017/7/12)
*
@@ -13,23 +15,26 @@ function setTransferData(dataTransfer, type, content) {
dataTransfer.setData(type, content)
} catch (err) {
const prefix = 'SLATE-DATA-EMBED::'
let obj = {}
const text = dataTransfer.getData('text/plain')
let obj = {}
// If prefixed, assume embedded drag data
// If the existing plain text data is prefixed, it's Slate JSON data.
if (text.substring(0, prefix.length) === prefix) {
try {
obj = JSON.parse(text.substring(prefix.length))
} catch (err2) {
throw new Error('Unable to parse custom embedded drag data')
} catch (e) {
throw new Error('Failed to parse Slate data from `DataTransfer` object.')
}
} else {
}
// Otherwise, it's just set it as is.
else {
obj['text/plain'] = text
}
obj[type] = content
dataTransfer.setData('text/plain', `${prefix}${JSON.stringify(obj)}`)
const string = `${prefix}${JSON.stringify(obj)}`
dataTransfer.setData('text/plain', string)
}
}