1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-29 01:50:06 +02:00

Improved Types (#4119)

* Experimental release to see if CustomTypes holds up through a publish

* Add experimental release script

* Fix lint

* v0.60.5-alpha.0

* Allow null properties in setNodes

* v0.60.6-alpha.0

* Revert null properties on Transforms.setNodes

* v0.60.7-alpha.0

* Update examples to use custom Element and Text with discriminated unions

* Add documentation for using TypeScript improvements

* Be explicit about typescript version in package.json

* Force lerna bootstrap to fix build issues on CI and fix a few type examples

* Add slate devDependencies with * back

* v0.60.7

* Switch to a non prerelease version to fix lerna not linking in root

* Add documentation for not using prerelease versions and on how to create experimental releases

* Try removing lerna bootstrap and see if it works
This commit is contained in:
Sunny Hirai
2021-03-11 11:48:31 -08:00
committed by GitHub
parent 5267f07175
commit a72958e6c3
37 changed files with 4006 additions and 116 deletions

View File

@@ -80,6 +80,10 @@ If you only want to run a specific test or tests, you can run `yarn test --fgrep
## Publishing Releases
**Important**: When creating releases using Lerna with the instructions below, you will be given choices around how to increase version numbers. You should always use a `major`, `minor` or `patch` release and must never use a `prerelease`. If a prerelease is used, the root package will not link to the packages in the `packages` directory creating hard to diagnose issues.
### Publishing Normal `@latest` Release
Since we use [Lerna](https://lerna.js.org) to manage the Slate packages this is fairly easy, just run:
```shell
@@ -90,7 +94,7 @@ And follow the prompts Lerna gives you.
Note that this will automatically run the prelease script first that will build, test and lint before attempting to publish.
## Publishing `@next` Releases
### Publishing `@next` Release
If we are unsure as to the stability of a release because there are significant changes and/or particularly complex changes, release with the `@next` tag.
@@ -100,7 +104,15 @@ yarn release:next
And follow the prompts Lerna gives you.
## Running Prerelease Script
### Publishing `@experimental` Release
If you need to create an experimental release to see how a published package will behave during an actual publish, release with the `@experimental` tag. End users should have no expectation that an `@experimental` release will be usable.
```shell
yarn release:experimental
```
### Running Prerelease Script
If we want to make sure that Slate code follows the preparations for a release but without actually publishing, run:

View File

@@ -22,6 +22,7 @@
- [Rendering](./concepts/08-rendering.md)
- [Serializing](./concepts/09-serializing.md)
- [Normalizing](./concepts/10-normalizing.md)
- [TypeScript](./concepts/11-typescript.md)
- [Migrating](./concepts/XX-migrating.md)
## API

View File

@@ -0,0 +1,91 @@
# TypeScript
Slate supports typing of one Slate document model (eg. one set of custom `Element` and `Text` types).
If you need to support more than one document model, see the section Multiple Document Models.
## Defining Custom Element and Text Types
To define a custom `Element` or `Text` type, extend the `CustomTypes` interface in the `slate` module like this.
```ts
declare module 'slate' {
interface CustomTypes {
Element: { type: 'paragraph'; children: Descendant[] }
Text: { text: string; bold: boolean; italic: boolean }
}
}
```
## Recommended Custom Element and Text
While you can define types directly in the `CustomTypes` interface, best practice is to:
- define and export each custom `Element`/`Text` type
- merge these into and export `CustomElement`/`CustomText` type
- Use the `CustomElement`/`CustomText` in the `CustomTypes` definition
These are best practices because elsewhere in your code, you may want to directly reference a specific `Element` type like a bullet or image.
Using best practices, the custom types will look something like:
```ts
export type ParagraphElement = {
type: 'paragraph'
children: Descendant
}
export type HeadingElement = {
type: 'paragraph'
level: number
children: Descendant
}
export type CustomElement = ParagraphElement | HeadingElement
export type FormattedText = { text: 'string'; bold: boolean; italic: boolean }
export type CustomText = FormattedText
declare module 'slate' {
interface CustomTypes {
Element: CustomElement
Text: CustomText
}
}
```
In this example, `CustomText` is equal to `FormattedText` but in a real editor, there can be more types of text like text in a code block which may not allow formatting for example.
## Why Is The Type Definition Unusual
Because it gets asked often, this section explains why Slate's type definition is atypical.
Slate needs to support a feature called type discrimination which is available when using union types (e.g. `ParagraphElement | HeadingElement`). This allows a user to narrow a type. If presented with code like `if (node.type === 'paragraph') { ... }` the inside of the block, will narrow the type of node to `ParagraphElement`.
Slate also needs a way to allow developers to get their custom types into Slate. This is done through declaration merging which is a feature of an `interface`.
Slate combines a union type and an interface to deliver this feature.
For more information see [Proposal: Add Custom TypeScript Types to Slate](https://github.com/ianstormtaylor/slate/issues/3725)
## Multiple Document Models
At the moment, Slate only supports types for a single document model at a time. For example, it cannot support a full Rich Text Editor for editing documents while also having a less featured Editor for editing comments.
Slate's TypeScript support was designed this way because some improved typing support was better than none. The goal is to also support typing for multiple editor definitions but this will depend on community support.
One workaround for support multiple document models is to create each editor in a separate package and then import them. This hasn't been tested but should work.
## Extending Other Types
Currently there is also support for extending:
- `Editor`
- `Selection`
- `Range`
- `Point`
Feel free to extend these types but extended these types should be considered experimental. We are actively looking for better ways to implement this.
For some examples of how to extend these types, see `packages/slate-react/src/custom-types.ts` in the slate repository.

View File

@@ -1,6 +1,6 @@
{
"lerna": "2.7.1",
"version": "0.60.4",
"version": "0.60.7",
"npmClient": "yarn",
"useWorkspaces": true
}

View File

@@ -19,8 +19,10 @@
"prerelease": "cross-env NODE_ENV=production yarn build:rollup && yarn test && yarn lint",
"release:publish:latext": "lerna publish",
"release:publish:next": "lerna publish --dist-tag next",
"release:publish:experimental": "lerna publish --dist-tag experimental",
"release:latest": "yarn prerelease && lerna publish",
"release:next": "yarn prerelease && lerna publish --dist-tag next",
"release:experimental": "yarn prerelease && lerna publish --dist-tag experimental",
"serve": "cd ./site && next",
"start": "npm-run-all --parallel --print-label watch serve",
"test": "mocha --require ./config/babel/register.cjs ./packages/*/test/index.js",
@@ -91,6 +93,6 @@
"slate-hyperscript": "*",
"slate-react": "*",
"source-map-loader": "^0.2.4",
"typescript": "^3.7.2"
"typescript": "3.9.7"
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "slate-history",
"description": "An operation-based history implementation for Slate editors.",
"version": "0.60.4",
"version": "0.60.7",
"license": "MIT",
"repository": "git://github.com/ianstormtaylor/slate.git",
"main": "dist/index.js",
@@ -18,8 +18,8 @@
"is-plain-object": "^3.0.0"
},
"devDependencies": {
"slate": "^0.60.4",
"slate-hyperscript": "^0.60.4"
"slate": "^0.60.7",
"slate-hyperscript": "^0.60.7"
},
"peerDependencies": {
"slate": ">=0.55.0"

View File

@@ -1,7 +1,7 @@
{
"name": "slate-hyperscript",
"description": "A hyperscript helper for creating Slate documents.",
"version": "0.60.4",
"version": "0.60.7",
"license": "MIT",
"repository": "git://github.com/ianstormtaylor/slate.git",
"main": "dist/index.js",
@@ -17,7 +17,7 @@
"is-plain-object": "^3.0.0"
},
"devDependencies": {
"slate": "^0.60.4"
"slate": "^0.60.7"
},
"peerDependencies": {
"slate": ">=0.55.0"

View File

@@ -1,7 +1,7 @@
{
"name": "slate-react",
"description": "Tools for building completely customizable richtext editors with React.",
"version": "0.60.4",
"version": "0.60.7",
"license": "MIT",
"repository": "git://github.com/ianstormtaylor/slate.git",
"main": "dist/index.js",
@@ -23,9 +23,9 @@
"scroll-into-view-if-needed": "^2.2.20"
},
"devDependencies": {
"slate": "^0.60.4",
"slate-history": "^0.60.4",
"slate-hyperscript": "^0.60.4"
"slate": "^0.60.7",
"slate-history": "^0.60.7",
"slate-hyperscript": "^0.60.7"
},
"peerDependencies": {
"react": ">=16.8.0",

View File

@@ -1,12 +1,12 @@
import { CustomTypes } from 'slate'
import { BaseRange, BaseText } from 'slate'
declare module 'slate' {
interface CustomTypes {
Text: {
placeholder: string
}
} & BaseText
Range: {
placeholder?: string
}
} & BaseRange
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "slate",
"description": "A completely customizable framework for building rich text editors.",
"version": "0.60.4",
"version": "0.60.7",
"license": "MIT",
"repository": "git://github.com/ianstormtaylor/slate.git",
"main": "dist/index.js",

View File

@@ -8,4 +8,4 @@ export interface CustomTypes {
export type ExtendedType<K extends string, B> = unknown extends CustomTypes[K]
? B
: B & CustomTypes[K]
: CustomTypes[K]

View File

@@ -9,7 +9,7 @@ import { ExtendedType } from './custom-types'
*/
export type BaseNode = Editor | Element | Text
export type Node = ExtendedType<'Node', BaseNode>
export type Node = Editor | Element | Text
export interface NodeInterface {
ancestor: (root: Node, path: Path) => Ancestor
@@ -317,7 +317,9 @@ export const Node: NodeInterface = {
}
}
if (Editor.isEditor(r)) delete r.selection
if (Editor.isEditor(r)) {
r.selection = null
}
})
return newRoot.children

View File

@@ -3,6 +3,8 @@ import { isBoldText } from './type-guards'
export const input: Text = {
placeholder: 'heading',
bold: false,
italic: false,
text: 'mytext',
}

View File

@@ -1,30 +1,72 @@
import { Descendant, Element, Text, CustomTypes } from 'slate'
// import { Descendant, Element, Text, CustomTypes, BaseText } from 'slate'
export interface HeadingElement {
// export type HeadingElement = {
// type: 'heading'
// level: number
// children: Descendant[]
// }
// export type ListItemElement = {
// type: 'list-item'
// depth: number
// children: Descendant[]
// }
// export type CustomText = {
// placeholder: string
// bold: boolean
// italic: boolean
// text: string
// }
// export type BoldCustomText = {
// bold: boolean
// text: string
// }
// declare module 'slate' {
// interface CustomTypes {
// Element: HeadingElement | ListItemElement
// Text: CustomText
// }
// }
import {
BaseText,
BaseEditor,
BaseSelection,
BasePoint,
BaseRange,
BaseElement,
} from 'slate'
// import { Prettify } from './prettify'
export type HeadingElement = {
type: 'heading'
level: number
children: Descendant[]
}
} & BaseElement
export interface ListItemElement {
export type ListItemElement = {
type: 'list-item'
depth: number
children: Descendant[]
}
} & BaseElement
export interface CustomText {
export type CustomText = {
placeholder: string
text: string
}
export interface BoldCustomText {
bold: boolean
text: string
}
italic: boolean
} & BaseText
export type CustomElement = HeadingElement | ListItemElement
declare module 'slate' {
interface CustomTypes {
Element: HeadingElement | ListItemElement
Text: CustomText | BoldCustomText
Editor: BaseEditor
Element: CustomElement
Text: CustomText
Node: CustomElement | CustomText
Point: BasePoint
Range: BaseRange
Selection: BaseSelection
}
}

View File

@@ -1,8 +1,8 @@
import { Element, Text } from 'slate'
import { BoldCustomText, CustomText, HeadingElement } from './custom-types'
import { CustomText, HeadingElement } from './custom-types'
export const isBoldText = (text: Text): text is BoldCustomText =>
!!(text as BoldCustomText).bold
export const isBoldText = (text: Text): text is CustomText =>
!!(text as CustomText).bold
export const isCustomText = (text: Text): text is CustomText =>
!!(text as CustomText).placeholder

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,7 @@ import {
import { css } from 'emotion'
import { withHistory } from 'slate-history'
const initialValue: Descendant[] = [
const initialValue: SlateElement[] = [
{
type: 'paragraph',
children: [

View File

@@ -17,7 +17,7 @@ import { withHistory } from 'slate-history'
import { css } from 'emotion'
const CodeHighlightingExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const [language, setLanguage] = useState('html')
const renderLeaf = useCallback(props => <Leaf {...props} />, [])
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
@@ -149,8 +149,9 @@ const Leaf = ({ attributes, children, leaf }) => {
)
}
const initialValue = [
const initialValue: SlateElement[] = [
{
type: 'paragraph',
children: [
{
text: '<h1>Hi!</h1>',

View File

@@ -1,18 +1,87 @@
import { Text, createEditor, Node, Element, Editor, Descendant } from 'slate'
export type BlockQuoteElement = { type: 'block-quote'; children: Descendant[] }
export type BulletedListElement = {
type: 'bulleted-list'
children: Descendant[]
}
export type CheckListItemElement = {
type: 'check-list-item'
checked: boolean
children: Descendant[]
}
export type EditableVoidElement = {
type: 'editable-void'
children: EmptyText[]
}
export type HeadingElement = { type: 'heading'; children: Descendant[] }
export type HeadingTwoElement = { type: 'heading-two'; children: Descendant[] }
export type ImageElement = {
type: 'image'
url: string
children: EmptyText[]
}
export type LinkElement = { type: 'link'; url: string; children: Descendant[] }
export type ListItemElement = { type: 'list-item'; children: Descendant[] }
export type MentionElement = {
type: 'mention'
character: string
children: CustomText[]
}
export type ParagraphElement = { type: 'paragraph'; children: Descendant[] }
export type TableElement = { type: 'table'; children: TableRow[] }
export type TableCellElement = { type: 'table-cell'; children: CustomText[] }
export type TableRowElement = { type: 'table-row'; children: TableCell[] }
export type TitleElement = { type: 'title'; children: Descendant[] }
export type VideoElement = { type: 'video'; url: string; children: EmptyText[] }
type CustomElement =
| BlockQuoteElement
| BulletedListElement
| CheckListItemElement
| EditableVoidElement
| HeadingElement
| HeadingTwoElement
| ImageElement
| LinkElement
| ListItemElement
| MentionElement
| ParagraphElement
| TableElement
| TableRowElement
| TableCellElement
| TitleElement
| VideoElement
export type CustomText = {
bold?: boolean
italic?: boolean
code?: boolean
text: string
}
export type EmptyText = {
text: string
}
declare module 'slate' {
interface CustomTypes {
Element: CustomElement
Node: CustomNode
Text: CustomText | EmptyText
}
}
interface CustomElement {
type?: string
checked?: boolean
url?: string
children: Descendant[]
}
type CustomNode = Editor | CustomElement | Text

View File

@@ -1,14 +1,21 @@
import React, { useState, useMemo } from 'react'
import { Transforms, createEditor, Node } from 'slate'
import {
Transforms,
createEditor,
Node,
Element as SlateElement,
Descendant,
} from 'slate'
import { Slate, Editable, useSlateStatic, withReact } from 'slate-react'
import { withHistory } from 'slate-history'
import { css } from 'emotion'
import RichTextEditor from './richtext'
import { Button, Icon, Toolbar } from '../components'
import { EditableVoidElement } from './custom-types'
const EditableVoidsExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const editor = useMemo(
() => withEditableVoids(withHistory(withReact(createEditor()))),
[]
@@ -40,7 +47,10 @@ const withEditableVoids = editor => {
const insertEditableVoid = editor => {
const text = { text: '' }
const voidNode = { type: 'editable-void', children: [text] }
const voidNode: EditableVoidElement = {
type: 'editable-void',
children: [text],
}
Transforms.insertNodes(editor, voidNode)
}
@@ -49,7 +59,7 @@ const Element = props => {
switch (element.type) {
case 'editable-void':
return <EditableVoidElement {...props} />
return <EditableVoid {...props} />
default:
return <p {...attributes}>{children}</p>
}
@@ -59,7 +69,7 @@ const unsetWidthStyle = css`
width: unset;
`
const EditableVoidElement = ({ attributes, children, element }) => {
const EditableVoid = ({ attributes, children, element }) => {
const [inputValue, setInputValue] = useState('')
return (
@@ -127,7 +137,7 @@ const InsertEditableVoidButton = () => {
)
}
const initialValue = [
const initialValue: SlateElement[] = [
{
type: 'paragraph',
children: [

View File

@@ -1,5 +1,11 @@
import React, { useState, useMemo } from 'react'
import { Transforms, createEditor, Node, Element as SlateElement } from 'slate'
import {
Transforms,
createEditor,
Node,
Element as SlateElement,
Descendant,
} from 'slate'
import {
Slate,
Editable,
@@ -11,7 +17,7 @@ import {
} from 'slate-react'
const EmbedsExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const editor = useMemo(() => withEmbeds(withReact(createEditor())), [])
return (
<Slate editor={editor} value={value} onChange={value => setValue(value)}>
@@ -98,8 +104,9 @@ const UrlInput = ({ url, onChange }) => {
)
}
const initialValue = [
const initialValue: SlateElement[] = [
{
type: 'paragraph',
children: [
{
text:
@@ -113,6 +120,7 @@ const initialValue = [
children: [{ text: '' }],
},
{
type: 'paragraph',
children: [
{
text:

View File

@@ -1,7 +1,14 @@
import React, { useState, useCallback, useMemo } from 'react'
import { Slate, Editable, withReact } from 'slate-react'
import { Transforms, createEditor, Node, Element as SlateElement } from 'slate'
import {
Transforms,
createEditor,
Node,
Element as SlateElement,
Descendant,
} from 'slate'
import { withHistory } from 'slate-history'
import { ParagraphElement, TitleElement } from './custom-types'
const withLayout = editor => {
const { normalizeNode } = editor
@@ -9,12 +16,18 @@ const withLayout = editor => {
editor.normalizeNode = ([node, path]) => {
if (path.length === 0) {
if (editor.children.length < 1) {
const title = { type: 'title', children: [{ text: 'Untitled' }] }
const title: TitleElement = {
type: 'title',
children: [{ text: 'Untitled' }],
}
Transforms.insertNodes(editor, title, { at: path.concat(0) })
}
if (editor.children.length < 2) {
const paragraph = { type: 'paragraph', children: [{ text: '' }] }
const paragraph: ParagraphElement = {
type: 'paragraph',
children: [{ text: '' }],
}
Transforms.insertNodes(editor, paragraph, { at: path.concat(1) })
}
@@ -35,7 +48,7 @@ const withLayout = editor => {
}
const ForcedLayoutExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const renderElement = useCallback(props => <Element {...props} />, [])
const editor = useMemo(
() => withLayout(withHistory(withReact(createEditor()))),
@@ -62,7 +75,7 @@ const Element = ({ attributes, children, element }) => {
}
}
const initialValue = [
const initialValue: SlateElement[] = [
{
type: 'title',
children: [{ text: 'Enforce Your Layout!' }],

View File

@@ -1,6 +1,14 @@
import React, { useState, useMemo, useRef, useEffect } from 'react'
import { Slate, Editable, ReactEditor, withReact, useSlate } from 'slate-react'
import { Editor, Transforms, Text, createEditor, Node } from 'slate'
import {
Editor,
Transforms,
Text,
createEditor,
Node,
Element,
Descendant,
} from 'slate'
import { css } from 'emotion'
import { withHistory } from 'slate-history'
@@ -8,7 +16,7 @@ import { Button, Icon, Menu, Portal } from '../components'
import { Range } from 'slate'
const HoveringMenuExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
return (
@@ -140,8 +148,9 @@ const FormatButton = ({ format, icon }) => {
)
}
const initialValue = [
const initialValue: Element[] = [
{
type: 'paragraph',
children: [
{
text:
@@ -154,6 +163,7 @@ const initialValue = [
],
},
{
type: 'paragraph',
children: [
{ text: 'Try it out yourself! Just ' },
{ text: 'select any piece of text and the menu will appear', bold: true },

View File

@@ -1,11 +1,11 @@
import React, { useState, useMemo, useCallback } from 'react'
import faker from 'faker'
import { createEditor } from 'slate'
import { Element as SlateElement, Node, createEditor, Descendant } from 'slate'
import { Slate, Editable, withReact } from 'slate-react'
const HEADINGS = 100
const PARAGRAPHS = 7
const initialValue = []
const initialValue: SlateElement[] = []
for (let h = 0; h < HEADINGS; h++) {
initialValue.push({
@@ -15,13 +15,14 @@ for (let h = 0; h < HEADINGS; h++) {
for (let p = 0; p < PARAGRAPHS; p++) {
initialValue.push({
type: 'paragraph',
children: [{ text: faker.lorem.paragraph() }],
})
}
}
const HugeDocumentExample = () => {
const [value, setValue] = useState(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const renderElement = useCallback(props => <Element {...props} />, [])
const editor = useMemo(() => withReact(createEditor()), [])
return (

View File

@@ -2,7 +2,13 @@ import React, { useCallback, useMemo, useState } from 'react'
import { createPortal } from 'react-dom'
import isHotkey from 'is-hotkey'
import { Editable, withReact, useSlate, Slate, ReactEditor } from 'slate-react'
import { Editor, createEditor, Node } from 'slate'
import {
Editor,
Element as SlateElement,
createEditor,
Node,
Descendant,
} from 'slate'
import { withHistory } from 'slate-history'
import { Button, Icon, Toolbar } from '../components'
@@ -15,7 +21,7 @@ const HOTKEYS = {
}
const IFrameExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const renderElement = useCallback(
({ attributes, children }) => <p {...attributes}>{children}</p>,
[]
@@ -117,7 +123,7 @@ const IFrame = ({ children, ...props }) => {
)
}
const initialValue = [
const initialValue: SlateElement[] = [
{
type: 'paragraph',
children: [

View File

@@ -1,7 +1,13 @@
import React, { useState, useMemo } from 'react'
import imageExtensions from 'image-extensions'
import isUrl from 'is-url'
import { Node, Transforms, createEditor } from 'slate'
import {
Node,
Transforms,
createEditor,
Element as SlateElement,
Descendant,
} from 'slate'
import {
Slate,
Editable,
@@ -14,9 +20,10 @@ import { withHistory } from 'slate-history'
import { css } from 'emotion'
import { Button, Icon, Toolbar } from '../components'
import { ImageElement } from './custom-types'
const ImagesExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const editor = useMemo(
() => withImages(withHistory(withReact(createEditor()))),
[]
@@ -72,7 +79,7 @@ const withImages = editor => {
const insertImage = (editor, url) => {
const text = { text: '' }
const image = { type: 'image', url, children: [text] }
const image: ImageElement = { type: 'image', url, children: [text] }
Transforms.insertNodes(editor, image)
}
@@ -81,13 +88,13 @@ const Element = props => {
switch (element.type) {
case 'image':
return <ImageElement {...props} />
return <Image {...props} />
default:
return <p {...attributes}>{children}</p>
}
}
const ImageElement = ({ attributes, children, element }) => {
const Image = ({ attributes, children, element }) => {
const selected = useSelected()
const focused = useFocused()
return (
@@ -131,7 +138,7 @@ const isImageUrl = url => {
return imageExtensions.includes(ext)
}
const initialValue = [
const initialValue: SlateElement[] = [
{
type: 'paragraph',
children: [

View File

@@ -8,13 +8,15 @@ import {
Range,
createEditor,
Element as SlateElement,
Descendant,
} from 'slate'
import { withHistory } from 'slate-history'
import { LinkElement } from './custom-types'
import { Button, Icon, Toolbar } from '../components'
const LinkExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const editor = useMemo(
() => withLinks(withHistory(withReact(createEditor()))),
[]
@@ -89,7 +91,7 @@ const wrapLink = (editor, url) => {
const { selection } = editor
const isCollapsed = selection && Range.isCollapsed(selection)
const link = {
const link: LinkElement = {
type: 'link',
url,
children: isCollapsed ? [{ text: url }] : [],
@@ -133,8 +135,9 @@ const LinkButton = () => {
)
}
const initialValue = [
const initialValue: SlateElement[] = [
{
type: 'paragraph',
children: [
{
text: 'In addition to block nodes, you can create inline nodes, like ',
@@ -150,6 +153,7 @@ const initialValue = [
],
},
{
type: 'paragraph',
children: [
{
text:

View File

@@ -1,7 +1,7 @@
import Prism from 'prismjs'
import React, { useState, useCallback, useMemo } from 'react'
import { Slate, Editable, withReact } from 'slate-react'
import { Node, Text, createEditor } from 'slate'
import { Node, Text, createEditor, Element, Descendant } from 'slate'
import { withHistory } from 'slate-history'
import { css } from 'emotion'
@@ -9,7 +9,7 @@ import { css } from 'emotion'
;Prism.languages.markdown=Prism.languages.extend("markup",{}),Prism.languages.insertBefore("markdown","prolog",{blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},code:[{pattern:/^(?: {4}|\t).+/m,alias:"keyword"},{pattern:/``.+?``|`[^`\n]+`/,alias:"keyword"}],title:[{pattern:/\w+.*(?:\r?\n|\r)(?:==+|--+)/,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#+.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])([\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:/(^|[^\\])(\*\*|__)(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^\*\*|^__|\*\*$|__$/}},italic:{pattern:/(^|[^\\])([*_])(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^[*_]|[*_]$/}},url:{pattern:/!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)| ?\[[^\]\n]*\])/,inside:{variable:{pattern:/(!?\[)[^\]]+(?=\]$)/,lookbehind:!0},string:{pattern:/"(?:\\.|[^"\\])*"(?=\)$)/}}}}),Prism.languages.markdown.bold.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.italic.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.bold.inside.italic=Prism.util.clone(Prism.languages.markdown.italic),Prism.languages.markdown.italic.inside.bold=Prism.util.clone(Prism.languages.markdown.bold); // prettier-ignore
const MarkdownPreviewExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const renderLeaf = useCallback(props => <Leaf {...props} />, [])
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
const decorate = useCallback(([node, path]) => {
@@ -109,8 +109,9 @@ const Leaf = ({ attributes, children, leaf }) => {
)
}
const initialValue = [
const initialValue: Element[] = [
{
type: 'paragraph',
children: [
{
text:
@@ -119,9 +120,11 @@ const initialValue = [
],
},
{
type: 'paragraph',
children: [{ text: '## Try it out!' }],
},
{
type: 'paragraph',
children: [{ text: 'Try it out for yourself!' }],
},
]

View File

@@ -8,8 +8,10 @@ import {
Point,
createEditor,
Element as SlateElement,
Descendant,
} from 'slate'
import { withHistory } from 'slate-history'
import { BulletedListElement } from './custom-types'
const SHORTCUTS = {
'*': 'list-item',
@@ -25,7 +27,7 @@ const SHORTCUTS = {
}
const MarkdownShortcutsExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const renderElement = useCallback(props => <Element {...props} />, [])
const editor = useMemo(
() => withShortcuts(withReact(withHistory(createEditor()))),
@@ -71,7 +73,10 @@ const withShortcuts = editor => {
})
if (type === 'list-item') {
const list = { type: 'bulleted-list', children: [] }
const list: BulletedListElement = {
type: 'bulleted-list',
children: [],
}
Transforms.wrapNodes(editor, list, {
match: n =>
!Editor.isEditor(n) &&
@@ -156,7 +161,7 @@ const Element = ({ attributes, children, element }) => {
}
}
const initialValue = [
const initialValue: SlateElement[] = [
{
type: 'paragraph',
children: [

View File

@@ -1,5 +1,13 @@
import React, { useMemo, useCallback, useRef, useEffect, useState } from 'react'
import { Node, Editor, Transforms, Range, createEditor } from 'slate'
import {
Node,
Editor,
Transforms,
Range,
createEditor,
Element as SlateElement,
Descendant,
} from 'slate'
import { withHistory } from 'slate-history'
import {
Slate,
@@ -11,10 +19,11 @@ import {
} from 'slate-react'
import { Portal } from '../components'
import { MentionElement } from './custom-types'
const MentionExample = () => {
const ref = useRef<HTMLDivElement | null>()
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const [target, setTarget] = useState<Range | undefined>()
const [index, setIndex] = useState(0)
const [search, setSearch] = useState('')
@@ -154,7 +163,11 @@ const withMentions = editor => {
}
const insertMention = (editor, character) => {
const mention = { type: 'mention', character, children: [{ text: '' }] }
const mention: MentionElement = {
type: 'mention',
character,
children: [{ text: '' }],
}
Transforms.insertNodes(editor, mention)
Transforms.move(editor)
}
@@ -163,13 +176,13 @@ const Element = props => {
const { attributes, children, element } = props
switch (element.type) {
case 'mention':
return <MentionElement {...props} />
return <Mention {...props} />
default:
return <p {...attributes}>{children}</p>
}
}
const MentionElement = ({ attributes, children, element }) => {
const Mention = ({ attributes, children, element }) => {
const selected = useSelected()
const focused = useFocused()
return (
@@ -193,8 +206,9 @@ const MentionElement = ({ attributes, children, element }) => {
)
}
const initialValue = [
const initialValue: SlateElement[] = [
{
type: 'paragraph',
children: [
{
text:
@@ -203,6 +217,7 @@ const initialValue = [
],
},
{
type: 'paragraph',
children: [
{ text: 'Try mentioning characters, like ' },
{

View File

@@ -1,6 +1,12 @@
import React, { useState, useCallback, useMemo } from 'react'
import { jsx } from 'slate-hyperscript'
import { Node, Transforms, createEditor } from 'slate'
import {
Node,
Transforms,
createEditor,
Element as SlateElement,
Descendant,
} from 'slate'
import { withHistory } from 'slate-history'
import { css } from 'emotion'
import {
@@ -80,7 +86,7 @@ export const deserialize = el => {
}
const PasteHtmlExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const renderElement = useCallback(props => <Element {...props} />, [])
const renderLeaf = useCallback(props => <Leaf {...props} />, [])
const editor = useMemo(
@@ -211,8 +217,9 @@ const Leaf = ({ attributes, children, leaf }) => {
return <span {...attributes}>{children}</span>
}
const initialValue = [
const initialValue: SlateElement[] = [
{
type: 'paragraph',
children: [
{
text:
@@ -228,9 +235,11 @@ const initialValue = [
],
},
{
type: 'paragraph',
children: [{ text: 'This is an example of doing exactly that!' }],
},
{
type: 'paragraph',
children: [
{
text:

View File

@@ -1,10 +1,10 @@
import React, { useState, useMemo } from 'react'
import { Node, createEditor } from 'slate'
import { createEditor, Element, Descendant } from 'slate'
import { Slate, Editable, withReact } from 'slate-react'
import { withHistory } from 'slate-history'
const PlainTextExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
return (
<Slate editor={editor} value={value} onChange={value => setValue(value)}>
@@ -13,8 +13,9 @@ const PlainTextExample = () => {
)
}
const initialValue = [
const initialValue: Element[] = [
{
type: 'paragraph',
children: [
{ text: 'This is editable plain text, just like a <textarea>!' },
],

View File

@@ -1,9 +1,9 @@
import React, { useState, useMemo } from 'react'
import { createEditor, Node } from 'slate'
import { createEditor, Descendant, Element, Node } from 'slate'
import { Slate, Editable, withReact } from 'slate-react'
const ReadOnlyExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const editor = useMemo(() => withReact(createEditor()), [])
return (
<Slate editor={editor} value={value} onChange={value => setValue(value)}>
@@ -12,10 +12,13 @@ const ReadOnlyExample = () => {
)
}
const initialValue = [
const initialValue: Element[] = [
{
type: 'paragraph',
children: [
{ text: 'This is editable plain text, just like a <textarea>!' },
{
text: 'This is editable plain text, just like a <textarea>!',
},
],
},
]

View File

@@ -5,6 +5,7 @@ import {
Editor,
Transforms,
createEditor,
Descendant,
Node,
Element as SlateElement,
} from 'slate'
@@ -22,7 +23,7 @@ const HOTKEYS = {
const LIST_TYPES = ['numbered-list', 'bulleted-list']
const RichTextExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const renderElement = useCallback(props => <Element {...props} />, [])
const renderLeaf = useCallback(props => <Leaf {...props} />, [])
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
@@ -175,7 +176,7 @@ const MarkButton = ({ format, icon }) => {
)
}
const initialValue = [
const initialValue: SlateElement[] = [
{
type: 'paragraph',
children: [

View File

@@ -1,13 +1,13 @@
import React, { useState, useCallback, useMemo } from 'react'
import { Slate, Editable, withReact } from 'slate-react'
import { Text, Node, createEditor } from 'slate'
import { Text, Node, Descendant, createEditor } from 'slate'
import { css } from 'emotion'
import { withHistory } from 'slate-history'
import { Icon, Toolbar } from '../components'
const SearchHighlightingExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const [search, setSearch] = useState<string | undefined>()
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
const decorate = useCallback(
@@ -85,8 +85,9 @@ const Leaf = ({ attributes, children, leaf }) => {
)
}
const initialValue = [
const initialValue: Descendant[] = [
{
type: 'paragraph',
children: [
{
text:
@@ -97,6 +98,7 @@ const initialValue = [
],
},
{
type: 'paragraph',
children: [
{ text: 'Try it out for yourself by typing in the search box above!' },
],

View File

@@ -4,14 +4,14 @@ import {
Editor,
Range,
Point,
Node,
Descendant,
createEditor,
Element as SlateElement,
} from 'slate'
import { withHistory } from 'slate-history'
const TablesExample = () => {
const [value, setValue] = useState<Node[]>(initialValue)
const [value, setValue] = useState<Descendant[]>(initialValue)
const renderElement = useCallback(props => <Element {...props} />, [])
const renderLeaf = useCallback(props => <Leaf {...props} />, [])
const editor = useMemo(
@@ -123,8 +123,9 @@ const Leaf = ({ attributes, children, leaf }) => {
return <span {...attributes}>{children}</span>
}
const initialValue = [
const initialValue: Descendant[] = [
{
type: 'paragraph',
children: [
{
text:
@@ -201,6 +202,7 @@ const initialValue = [
],
},
{
type: 'paragraph',
children: [
{
text:

View File

@@ -10109,7 +10109,7 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^3.7.2:
typescript@3.9.7:
version "3.9.7"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==