Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
skogsmaskin committed Oct 31, 2023
1 parent 932d160 commit 948f55b
Show file tree
Hide file tree
Showing 11 changed files with 353 additions and 146 deletions.
2 changes: 1 addition & 1 deletion packages/sanity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,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
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ export function PortableTextInput(props: PortableTextInputProps) {
// Handle editor changes
const handleEditorChange = useCallback(
(change: EditorChange): void => {
if (editorRef.current && onEditorChange) {
onEditorChange(change, editorRef.current)
}
switch (change.type) {
case 'mutation':
onChange(toFormPatches(change.patches))
Expand Down Expand Up @@ -279,7 +282,7 @@ export function PortableTextInput(props: PortableTextInputProps) {
default:
}
},
[onBlur, onChange, onPathFocus, toast],
[onBlur, onChange, onEditorChange, onPathFocus, toast],
)

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,6 @@ export const DefaultAnnotationComponent = (props: BlockAnnotationProps) => {
}
return 'default'
}, [isLink, hasError, hasWarning])

return (
<Root
$toneKey={toneKey}
Expand Down
3 changes: 2 additions & 1 deletion packages/sanity/src/core/form/types/definitionExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
BooleanInputProps,
NumberInputProps,
ObjectInputProps,
PortableTextInputProps,
StringInputProps,
} from './inputProps'
import {ObjectItem, ObjectItemProps, PrimitiveItemProps} from './itemProps'
Expand All @@ -45,7 +46,7 @@ export interface ArrayOfObjectsComponents {
diff?: ComponentType<any>
field?: ComponentType<ArrayFieldProps>
inlineBlock?: ComponentType<BlockProps>
input?: ComponentType<ArrayOfObjectsInputProps | PortableTextInputProps>
input?: ComponentType<ArrayOfObjectsInputProps>
item?: ComponentType<ObjectItemProps>
preview?: ComponentType<PreviewProps>
}
Expand Down
3 changes: 2 additions & 1 deletion packages/sanity/src/core/form/types/inputProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
HotkeyOptions,
OnCopyFn,
OnPasteFn,
PortableTextEditor,
RangeDecoration,
} from '@sanity/portable-text-editor'
import {FormPatch, PatchEvent} from '../patch'
Expand Down Expand Up @@ -497,7 +498,7 @@ export interface PortableTextInputProps
/**
* Returns changes from the underlying editor
*/
onEditorChange?: (change: EditorChange) => void
onEditorChange?: (change: EditorChange, editor: PortableTextEditor) => void
/**
* Custom copy function
*/
Expand Down
217 changes: 171 additions & 46 deletions packages/sanity/src/scratchPad/components/editor/Editable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, {PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {Stack} from '@sanity/ui'
import React, {PropsWithChildren, useCallback, useEffect, useMemo, useRef} from 'react'
import {BoundaryElementProvider, Stack} from '@sanity/ui'
import {
BlockAnnotationRenderProps,
BlockChildRenderProps,
BlockDecoratorRenderProps,
BlockRenderProps,
BlockStyleRenderProps,
Expand All @@ -12,17 +14,18 @@ import {
} from '@sanity/portable-text-editor'
import styled, {css} from 'styled-components'
import {debounce} from 'lodash'
import isHotkey from 'is-hotkey'
import {PortableTextTextBlock} from '@sanity/types'
import {useScratchPad} from '../../hooks/useScratchPad'
import {ScratchPadContextValue} from '../../context/ScratchPadProvider'
import {PortableTextInputProps} from '../../../core'
import {TextBlock} from '../rendering/renderBlock'
import {Style} from '../../../core/form/inputs/PortableText/text/Style'
import {Decorator} from '../../../core/form/inputs/PortableText/text'
import {Annotation} from '../../../core/form/inputs/PortableText/object/Annotation'
import {InlineObject} from '../../../core/form/inputs/PortableText/object/InlineObject'
import {TextBlock} from '../../../core/form/inputs/PortableText/text/TextBlock'
import {AssistanceRange} from './AssistanceRange'

const INLINE_STYLE: React.CSSProperties = {outline: 'none'}
const MODIFIER_KEY = 'Alt+Control'

const EditableWrapStack = styled(Stack)(() => {
return css`
Expand All @@ -48,7 +51,6 @@ export function Editable(props: EditableProps) {
const editableRef = useRef<HTMLDivElement | null>(null)
const editor = usePortableTextEditor()
const editorSelection = usePortableTextEditorSelection()
const [modifierKeyPressed, setIsModifierKeyPressed] = useState(false)

const {onEditorBeforeInput, onAssistanceRangeSelect, assistanceSelection, editorFocused} =
useScratchPad()
Expand All @@ -68,47 +70,69 @@ export function Editable(props: EditableProps) {

const renderPlaceholder = useCallback(() => <span>{placeholder}</span>, [placeholder])

const handleSetAssistantRangeKey = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {
if (isHotkey(MODIFIER_KEY, event)) {
setIsModifierKeyPressed(true)
}
}, [])

const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
handleSetAssistantRangeKey(event)
if (onKeyDown) {
onKeyDown(event)
}
},
[handleSetAssistantRangeKey, onKeyDown],
[onKeyDown],
)

const handleKeyUp = useCallback(() => {
setIsModifierKeyPressed(false)
}, [])

// Update the assistance selection when user is done selecting something, and modifier key is pressed
// Update the assistance selection when user is 'done' selecting something
useEffect(() => {
createAssistSelectionDebounced(editor, onAssistanceRangeSelect)
}, [editor, modifierKeyPressed, onAssistanceRangeSelect])
}, [editor, onAssistanceRangeSelect, editorSelection])

// These render functions piggybacks on the PT-input's rendering components
// These render functions starting with a underscore piggybacks on the PT-input's rendering components
const _renderBlock = useCallback(
(editorRenderProps: BlockRenderProps) => {
if (!rootElementRef.current) {
return <></>
}
return (
<TextBlock
formProps={formProps}
blockProps={editorRenderProps}
boundaryElement={rootElementRef.current}
scrollElement={rootElementRef.current}
/>
floatingBoundary={editorRenderProps.editorElementRef.current}
focused={editorRenderProps.focused}
isFullscreen
// eslint-disable-next-line react/jsx-handler-names
onItemClose={formProps.onItemClose}
// eslint-disable-next-line react/jsx-handler-names
onItemOpen={formProps.onItemOpen}
// eslint-disable-next-line react/jsx-handler-names
onItemRemove={formProps.onItemRemove}
// eslint-disable-next-line react/jsx-handler-names
onPathFocus={formProps.onPathFocus}
path={formProps.path.concat(editorRenderProps.path)}
readOnly={formProps.readOnly}
referenceBoundary={rootElementRef.current}
renderAnnotation={formProps.renderAnnotation}
renderField={formProps.renderField}
renderInlineBlock={formProps.renderInlineBlock}
renderInput={formProps.renderInput}
renderItem={formProps.renderItem}
renderPreview={formProps.renderPreview}
renderBlock={formProps.renderBlock}
schemaType={editorRenderProps.schemaType}
selected={editorRenderProps.selected}
value={editorRenderProps.value as PortableTextTextBlock}
>
{editorRenderProps.children}
</TextBlock>
)
},
[formProps],
[
formProps.onItemClose,
formProps.onItemOpen,
formProps.onItemRemove,
formProps.onPathFocus,
formProps.path,
formProps.readOnly,
formProps.renderAnnotation,
formProps.renderBlock,
formProps.renderField,
formProps.renderInlineBlock,
formProps.renderInput,
formProps.renderItem,
formProps.renderPreview,
],
)

const _renderDecorator = useCallback((editorRenderProps: BlockDecoratorRenderProps) => {
Expand All @@ -119,24 +143,125 @@ export function Editable(props: EditableProps) {
return <Style {...editorRenderProps} />
}, [])

const _renderAnnotation = useCallback(
(editorRenderProps: BlockAnnotationRenderProps) => {
return (
<Annotation
floatingBoundary={editorRenderProps.editorElementRef.current}
editorNodeFocused={editorFocused}
focused={editorRenderProps.focused}
// eslint-disable-next-line react/jsx-handler-names
onItemClose={formProps.onItemClose}
// eslint-disable-next-line react/jsx-handler-names
onItemOpen={formProps.onItemOpen}
// eslint-disable-next-line react/jsx-handler-names
onPathFocus={formProps.onPathFocus}
path={formProps.path.concat(editorRenderProps.path)}
referenceBoundary={rootElementRef.current}
renderAnnotation={formProps.renderAnnotation}
renderField={formProps.renderField}
renderInput={formProps.renderInput}
renderItem={formProps.renderItem}
renderPreview={formProps.renderPreview}
selected={editorRenderProps.selected}
schemaType={editorRenderProps.schemaType}
value={editorRenderProps.value}
>
{editorRenderProps.children}
</Annotation>
)
},
[
editorFocused,
formProps.onItemClose,
formProps.onItemOpen,
formProps.onPathFocus,
formProps.path,
formProps.renderAnnotation,
formProps.renderField,
formProps.renderInput,
formProps.renderItem,
formProps.renderPreview,
],
)

const _renderChild = useCallback(
(editorRenderProps: BlockChildRenderProps) => {
const {
children,
focused: childFocused,
path: childPath,
selected,
schemaType: childSchemaType,
value: child,
} = editorRenderProps
const isSpan = child._type === editor.schemaTypes.span.name
if (isSpan) {
return children
}
return (
<InlineObject
floatingBoundary={editorRenderProps.editorElementRef.current}
focused={childFocused}
// eslint-disable-next-line react/jsx-handler-names
onItemClose={formProps.onItemClose}
// eslint-disable-next-line react/jsx-handler-names
onItemOpen={formProps.onItemOpen}
// eslint-disable-next-line react/jsx-handler-names
onPathFocus={formProps.onPathFocus}
path={formProps.path.concat(childPath)}
readOnly={formProps.readOnly}
referenceBoundary={rootElementRef.current}
relativePath={childPath}
renderAnnotation={formProps.renderAnnotation}
renderBlock={formProps.renderBlock}
renderField={formProps.renderField}
renderInlineBlock={formProps.renderInlineBlock}
renderInput={formProps.renderInput}
renderItem={formProps.renderItem}
renderPreview={formProps.renderPreview}
schemaType={childSchemaType}
selected={selected}
value={child}
/>
)
},
[
editor.schemaTypes.span.name,
formProps.onItemClose,
formProps.onItemOpen,
formProps.onPathFocus,
formProps.path,
formProps.readOnly,
formProps.renderAnnotation,
formProps.renderBlock,
formProps.renderField,
formProps.renderInlineBlock,
formProps.renderInput,
formProps.renderItem,
formProps.renderPreview,
],
)

return (
<EditableWrapStack ref={rootElementRef} data-ui="EditableWrapStack">
<PortableTextEditable
onBeforeInput={onEditorBeforeInput}
onBlur={onBlur}
onFocus={onFocus}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
ref={editableRef}
renderBlock={_renderBlock}
renderDecorator={_renderDecorator}
// renderChild={renderChild}
// renderAnnotation={renderAnnotation}
renderStyle={_renderStyle}
rangeDecorations={rangeDecorations}
renderPlaceholder={renderPlaceholder}
style={INLINE_STYLE}
/>
<BoundaryElementProvider element={rootElementRef.current}>
<PortableTextEditable
onBeforeInput={onEditorBeforeInput}
onBlur={onBlur}
onFocus={onFocus}
onKeyDown={handleKeyDown}
ref={editableRef}
renderBlock={_renderBlock}
renderDecorator={_renderDecorator}
renderChild={_renderChild}
renderAnnotation={_renderAnnotation}
renderStyle={_renderStyle}
rangeDecorations={rangeDecorations}
renderPlaceholder={renderPlaceholder}
style={INLINE_STYLE}
/>
</BoundaryElementProvider>
</EditableWrapStack>
)
}
Expand Down
Loading

0 comments on commit 948f55b

Please sign in to comment.