Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PK testing #5095

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dev/test-studio/sanity.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {schemaTypes} from './schema'
import {defaultDocumentNode, newDocumentOptions, structure} from './structure'
import {workshopTool} from './workshop'
import {presenceTool} from './plugins/presence'
import {scratchPadTool} from 'sanity/scratchPad'
import {
CustomLayout,
CustomLogo,
Expand Down Expand Up @@ -85,6 +86,7 @@ const sharedSettings = definePlugin({
structure,
defaultDocumentNode,
}),
scratchPadTool(),
languageFilter({
defaultLanguages: ['nb'],
supportedLanguages: [
Expand Down
134 changes: 94 additions & 40 deletions packages/@sanity/portable-text-editor/src/editor/Editable.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import {BaseRange, Transforms, Text} from 'slate'
import React, {useCallback, useMemo, useEffect, forwardRef, useState, KeyboardEvent} from 'react'
import {BaseRange, Transforms, Text, NodeEntry, Range as SlateRange} from 'slate'
import React, {forwardRef, KeyboardEvent, useCallback, useEffect, useMemo, useState} from 'react'
import {
Editable as SlateEditable,
ReactEditor,
RenderElementProps,
RenderLeafProps,
useSlate,
} from 'slate-react'
import {noop} from 'lodash'
import {flatten, noop} from 'lodash'
import {PortableTextBlock} from '@sanity/types'
import {
EditorChange,
EditorSelection,
OnCopyFn,
OnPasteFn,
OnPasteResult,
PortableTextSlateEditor,
RangeDecoration,
RenderAnnotationFunction,
RenderBlockFunction,
RenderChildFunction,
Expand Down Expand Up @@ -59,6 +61,7 @@ export type PortableTextEditableProps = Omit<
onBeforeInput?: (event: InputEvent) => void
onPaste?: OnPasteFn
onCopy?: OnCopyFn
rangeDecorations?: RangeDecoration[]
renderAnnotation?: RenderAnnotationFunction
renderBlock?: RenderBlockFunction
renderChild?: RenderChildFunction
Expand Down Expand Up @@ -86,6 +89,7 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
onBeforeInput,
onPaste,
onCopy,
rangeDecorations,
renderAnnotation,
renderBlock,
renderChild,
Expand Down Expand Up @@ -149,28 +153,39 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
)

const renderLeaf = useCallback(
(lProps: RenderLeafProps & {leaf: Text & {placeholder?: boolean}}) => {
const rendered = (
<Leaf
{...lProps}
schemaTypes={schemaTypes}
renderAnnotation={renderAnnotation}
renderChild={renderChild}
renderDecorator={renderDecorator}
readOnly={readOnly}
/>
)
if (renderPlaceholder && lProps.leaf.placeholder && lProps.text.text === '') {
return (
<>
<span style={PLACEHOLDER_STYLE} contentEditable={false}>
{renderPlaceholder()}
</span>
{rendered}
</>
(
lProps: RenderLeafProps & {
leaf: Text & {placeholder?: boolean; rangeDecoration?: RangeDecoration}
},
) => {
if (lProps.leaf._type === 'span') {
let rendered = (
<Leaf
{...lProps}
schemaTypes={schemaTypes}
renderAnnotation={renderAnnotation}
renderChild={renderChild}
renderDecorator={renderDecorator}
readOnly={readOnly}
/>
)
if (renderPlaceholder && lProps.leaf.placeholder && lProps.text.text === '') {
return (
<>
<span style={PLACEHOLDER_STYLE} contentEditable={false}>
{renderPlaceholder()}
</span>
{rendered}
</>
)
}
const decoration = lProps.leaf.rangeDecoration
if (decoration) {
rendered = decoration.component({children: rendered})
}
return rendered
}
return rendered
return lProps.children
},
[readOnly, renderAnnotation, renderChild, renderDecorator, renderPlaceholder, schemaTypes],
)
Expand Down Expand Up @@ -393,24 +408,34 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
}
}, [portableTextEditor, scrollSelectionIntoView])

const decorate = useCallback(() => {
if (isEqualToEmptyEditor(slateEditor.children, schemaTypes)) {
return [
{
anchor: {
path: [0, 0],
offset: 0,
const decorate: (entry: NodeEntry) => BaseRange[] = useCallback(
([node, path]) => {
if (isEqualToEmptyEditor(slateEditor.children, schemaTypes)) {
return [
{
anchor: {
path: [0, 0],
offset: 0,
},
focus: {
path: [0, 0],
offset: 0,
},
placeholder: true,
},
focus: {
path: [0, 0],
offset: 0,
},
placeholder: true,
},
]
}
return EMPTY_DECORATORS
}, [schemaTypes, slateEditor])
]
}
return rangeDecorations && rangeDecorations.length
? getChildNodeToRangeDecorations({
slateEditor,
portableTextEditor,
rangeDecorations,
nodeEntry: [node, path],
})
: EMPTY_DECORATORS
},
[slateEditor, schemaTypes, portableTextEditor, rangeDecorations],
)

// Set the forwarded ref to be the Slate editable DOM element
useEffect(() => {
Expand Down Expand Up @@ -442,3 +467,32 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
/>
)
})

const getChildNodeToRangeDecorations = ({
rangeDecorations = [],
nodeEntry,
slateEditor,
portableTextEditor,
}: {
rangeDecorations: RangeDecoration[]
nodeEntry: NodeEntry
slateEditor: PortableTextSlateEditor
portableTextEditor: PortableTextEditor
}): SlateRange[] => {
if (rangeDecorations.length === 0) {
return EMPTY_DECORATORS
}
const [, path] = nodeEntry
return flatten(
rangeDecorations.map((decoration) => {
const slateRange = toSlateRange(decoration.selection, slateEditor)
if (decoration.isRangeInvalid(portableTextEditor)) {
return EMPTY_DECORATORS
}
if (slateRange && SlateRange.includes(slateRange, path) && path.length > 0) {
return {...slateRange, rangeDecoration: decoration}
}
return EMPTY_DECORATORS
}),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class PortableTextEditor extends React.Component<PortableTextEditorProps>
super(props)

if (!props.schemaType) {
throw new Error('PortableTextEditor: missing "type" property')
throw new Error('PortableTextEditor: missing "schemaType" property')
}

if (props.incomingPatches$) {
Expand Down Expand Up @@ -271,4 +271,8 @@ export class PortableTextEditor extends React.Component<PortableTextEditorProps>
debug(`Host toggling mark`, mark)
editor.editable?.toggleMark(mark)
}
static getFragment = (editor: PortableTextEditor): PortableTextBlock[] | undefined => {
debug(`Host getting fragment`)
return editor.editable?.getFragment()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,9 @@ export function createWithEditableAPI(
editor.insertBreak()
editor.onChange()
},
getFragment: () => {
return fromSlateValue(editor.getFragment(), types.block.name)
},
})
return editor
}
Expand Down
39 changes: 37 additions & 2 deletions packages/@sanity/portable-text-editor/src/types/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
import {Subject, Observable} from 'rxjs'
import {Descendant, Node as SlateNode, Operation as SlateOperation} from 'slate'
import {ReactEditor} from 'slate-react'
import {FocusEvent} from 'react'
import {FocusEvent, PropsWithChildren, ReactElement} from 'react'
import {DOMNode} from 'slate-react/dist/utils/dom'
import type {Patch} from '../types/patch'
import {PortableTextEditor} from '../editor/PortableTextEditor'

Expand All @@ -37,11 +38,12 @@ export interface EditableAPI {
blur: () => void
delete: (selection: EditorSelection, options?: EditableAPIDeleteOptions) => void
findByPath: (path: Path) => [PortableTextBlock | PortableTextChild | undefined, Path | undefined]
findDOMNode: (element: PortableTextBlock | PortableTextChild) => Node | undefined
findDOMNode: (element: PortableTextBlock | PortableTextChild) => DOMNode | undefined
focus: () => void
focusBlock: () => PortableTextBlock | undefined
focusChild: () => PortableTextChild | undefined
getSelection: () => EditorSelection
getFragment: () => PortableTextBlock[] | undefined
getValue: () => PortableTextBlock[] | undefined
hasBlockStyle: (style: string) => boolean
hasListStyle: (listStyle: string) => boolean
Expand Down Expand Up @@ -96,6 +98,7 @@ export interface PortableTextSlateEditor extends ReactEditor {
isTextSpan: (value: unknown) => value is PortableTextSpan
isListBlock: (value: unknown) => value is PortableTextListBlock
subscriptions: (() => () => void)[]
nodeToRangeDecorations?: Map<Node, Range[]>

/**
* Increments selected list items levels, or decrements them if `reverse` is true.
Expand Down Expand Up @@ -481,6 +484,38 @@ export type ScrollSelectionIntoViewFunction = (
domRange: globalThis.Range,
) => void

/**
* A range decoration is a UI affordance that wraps a given selection range in the editor
* with a custom component. This can be used to highlight search results,
* mark validation errors on specific words, draw user presence and similar.
* @alpha */
export interface RangeDecoration {
/**
* A component for rendering the range decoration.
* This component takes only children, and you could render
* your own component with own props by wrapping those children.
*
* @example
* ```ts
* (rangeComponentProps: PropsWithChildren) => (
* <BlackListHighlighter {...location}>
* {rangeComponentProps.children}
* </BlackListHighlighter>
* )
* ```
*/
component: (props: PropsWithChildren) => ReactElement
/**
* A function that will can tell if the range has become invalid.
* The range will not be rendered when you return `true` from this function.
*/
isRangeInvalid: (editor: PortableTextEditor) => boolean
/**
* The editor content selection range
*/
selection: EditorSelection
}

/** @internal */
export type PortableTextMemberSchemaTypes = {
annotations: ObjectSchemaType[]
Expand Down
1 change: 1 addition & 0 deletions packages/sanity/exports/scratchPad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../src/scratchPad'
17 changes: 16 additions & 1 deletion packages/sanity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@
"import": "./lib/router.esm.js",
"default": "./lib/router.esm.js"
},
"./scratchPad": {
"types": "./lib/exports/scratchPad.d.ts",
"source": "./exports/scratchPad.ts",
"require": "./lib/scratchPad.js",
"node": {
"import": "./lib/scratchPad.cjs.mjs",
"require": "./lib/scratchPad.js"
},
"import": "./lib/scratchPad.esm.js",
"default": "./lib/scratchPad.esm.js"
},
"./package.json": "./package.json"
},
"main": "./lib/index.js",
Expand All @@ -94,6 +105,9 @@
"desk": [
"./lib/exports/desk.d.ts"
],
"scratchPad": [
"./lib/exports/scratchPad.d.ts"
],
"router": [
"./lib/exports/router.d.ts"
]
Expand All @@ -107,6 +121,7 @@
"bin",
"cli.js",
"desk.js",
"scratchPad.js",
"lib",
"router.js",
"src",
Expand All @@ -117,7 +132,7 @@
"build": "pkg-utils build --tsconfig tsconfig.lib.json",
"postbuild": "run-s check:package",
"check:package": "pkg-utils --tsconfig tsconfig.lib.json",
"clean": "rimraf _internal.js cli.js desk.js lib router.js",
"clean": "rimraf _internal.js cli.js desk.js lib router.js scratchPad.js",
"coverage": "jest --coverage",
"prepublishOnly": "run-s write:version write:ui-version",
"test": "pkg-utils --strict && jest",
Expand Down
Loading
Loading