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

Fix @keystone-6/fields-document package breaking when compiling in SSR environments #9041

Merged
merged 22 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions .changeset/split-document-field.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/fields-document': minor
---

Fix `@keystone-6/fields-document` package breaking when compiling in SSR environments (#8717)
14 changes: 10 additions & 4 deletions docs/components/docs/DocumentEditorDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ import React, { type ReactNode, useContext, useEffect, useMemo, useState } from
import { type DocumentFeatures } from '@keystone-6/fields-document/views'
import {
type ComponentBlock,
fields,
type InferRenderersForComponentBlocks,
fields,
} from '@keystone-6/fields-document/component-blocks'
import { Global, jsx } from '@emotion/react'

import { getInitialPropsValue } from '../../../packages/fields-document/src/DocumentEditor/component-blocks/initial-values'
import { DocumentEditor, } from '../../../packages/fields-document/src/DocumentEditor'
import {
createDocumentEditor,
DocumentEditor,
Editor,
} from '../../../packages/fields-document/src/DocumentEditor'
ReactEditor,
withReact
} from '../../../packages/fields-document/src/DocumentEditor/demo'
import { FormValueContentFromPreviewProps } from '../../../packages/fields-document/src/DocumentEditor/component-blocks/form-from-preview'
import { createGetPreviewProps } from '../../../packages/fields-document/src/DocumentEditor/component-blocks/preview-props'
import { componentBlocks as componentBlocksInSandboxProject } from '../../../tests/sandbox/component-blocks'
Expand Down Expand Up @@ -279,7 +282,10 @@ export const DocumentEditorDemo = () => {
useEffect(() => {
// we want to force normalize when the document features change so
// that no invalid things exist after a user changes something
const editor = createDocumentEditor(documentFeatures, componentBlocks, emptyObj)
const editor = createDocumentEditor(documentFeatures, componentBlocks, emptyObj, {
ReactEditor: ReactEditor as any, // TODO: somehow incompatible
withReact
})
editor.children = value
Editor.normalize(editor, { force: true })
setValue(editor.children)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import React from 'react'
import { DocumentRenderer, type DocumentRendererProps } from '@keystone-6/document-renderer'
import {
type DocumentRendererProps,
DocumentRenderer,
} from '@keystone-6/document-renderer'

// By default the DocumentRenderer will render unstyled html elements.
// We're customising how headings are rendered here but you can customise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { allowAll } from '@keystone-6/core/access'
import { text, timestamp } from '@keystone-6/core/fields'
import { document } from '@keystone-6/fields-document'

import type { Lists } from '.keystone/types'
import { type Lists } from '.keystone/types'

export const lists = {
User: list({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { createYoga } from 'graphql-yoga'
import type { NextApiRequest, NextApiResponse } from 'next'
import {
type NextApiRequest,
type NextApiResponse
} from 'next'
import { keystoneContext } from '../../keystone/context'

/*
Expand Down
4 changes: 2 additions & 2 deletions packages/document-renderer/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const defaultRenderers: Renderers = {
},
divider: 'hr',
heading: ({ level, children, textAlign }) => {
let Heading = `h${level}` as 'h1'
const Heading = `h${level}` as 'h1'
return <Heading style={{ textAlign }} children={children} />
},
code: 'pre',
Expand Down Expand Up @@ -204,7 +204,7 @@ function set (obj: Record<string, any>, propPath: (string | number)[], value: an
if (propPath.length === 1) {
obj[propPath[0]] = value
} else {
let firstElement = propPath.shift()!
const firstElement = propPath.shift()!
set(obj[firstElement], propPath, value)
}
}
Expand Down
12 changes: 8 additions & 4 deletions packages/fields-document/src/DocumentEditor/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
/** @jsx jsx */

import {
Fragment,
type ReactNode,
type HTMLAttributes,
Fragment,
forwardRef,
useState,
type HTMLAttributes,
useMemo,
useContext,
} from 'react'
Expand All @@ -25,7 +25,7 @@ import { Maximize2Icon } from '@keystone-ui/icons/icons/Maximize2Icon'
import { Minimize2Icon } from '@keystone-ui/icons/icons/Minimize2Icon'
import { MoreHorizontalIcon } from '@keystone-ui/icons/icons/MoreHorizontalIcon'

import { type DocumentFeatures } from '../views'
import { type DocumentFeatures } from '../views-shared'
import {
InlineDialog,
KeyboardInTooltip,
Expand All @@ -35,7 +35,11 @@ import {
} from './primitives'
import { linkButton } from './link'
import { BlockComponentsButtons, ComponentBlockContext } from './component-blocks'
import { clearFormatting, type Mark, modifierKeyText } from './utils'
import {
type Mark,
clearFormatting,
modifierKeyText
} from './utils'
import { LayoutsButton } from './layouts'
import { ListButton } from './lists'
import { blockquoteButton } from './blockquote'
Expand Down
7 changes: 4 additions & 3 deletions packages/fields-document/src/DocumentEditor/alignment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ import { Tooltip } from '@keystone-ui/tooltip'
import { applyRefs } from 'apply-ref'
import { useState, type ComponentProps, useMemo } from 'react'
import { Transforms } from 'slate'
import { type DocumentFeatures } from '../views'

import { type DocumentFeatures } from '../views-shared'
import { InlineDialog, ToolbarButton, ToolbarGroup } from './primitives'
import { useToolbarState } from './toolbar-state'

export const TextAlignMenu = ({
export function TextAlignMenu ({
alignment,
}: {
alignment: DocumentFeatures['formatting']['alignment']
}) => {
}) {
const [showMenu, setShowMenu] = useState(false)
const { dialog, trigger } = useControlledPopover(
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Element, Editor, Transforms, Range } from 'slate'
import { type DocumentFeatures } from '../views'
import { type ComponentBlock } from './component-blocks/api'
import { insertDivider } from './divider'
import { type DocumentFeatures } from '../views-shared'
import { type ComponentBlock } from './component-blocks/api-shared'
import { insertDivider } from './divider-shared'
import { type DocumentFeaturesForNormalization } from './document-features-normalization'
import { getAncestorComponentChildFieldDocumentFeatures } from './toolbar-state'
import { getAncestorComponentChildFieldDocumentFeatures } from './toolbar-state-shared'

export function withBlockMarkdownShortcuts (
documentFeatures: DocumentFeatures,
Expand Down
76 changes: 76 additions & 0 deletions packages/fields-document/src/DocumentEditor/blockquote-shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
Editor,
Node,
Path,
Range,
Transforms
} from 'slate'

import { isElementActive } from './utils'

export function insertBlockquote (editor: Editor) {
const isActive = isElementActive(editor, 'blockquote')
if (isActive) {
Transforms.unwrapNodes(editor, {
match: node => node.type === 'blockquote',
})
} else {
Transforms.wrapNodes(editor, {
type: 'blockquote',
children: [],
})
}
}

function getDirectBlockquoteParentFromSelection (editor: Editor) {
if (!editor.selection) return { isInside: false } as const
const [, parentPath] = Editor.parent(editor, editor.selection)
if (!parentPath.length) {
return { isInside: false } as const
}
const [maybeBlockquoteParent, maybeBlockquoteParentPath] = Editor.parent(editor, parentPath)
const isBlockquote = maybeBlockquoteParent.type === 'blockquote'
return isBlockquote
? ({ isInside: true, path: maybeBlockquoteParentPath } as const)
: ({ isInside: false } as const)
}

export function withBlockquote (editor: Editor): Editor {
const { insertBreak, deleteBackward } = editor
editor.deleteBackward = unit => {
if (editor.selection) {
const parentBlockquote = getDirectBlockquoteParentFromSelection(editor)
if (
parentBlockquote.isInside &&
Range.isCollapsed(editor.selection) &&
// the selection is at the start of the paragraph
editor.selection.anchor.offset === 0 &&
// it's the first paragraph in the panel
editor.selection.anchor.path[editor.selection.anchor.path.length - 2] === 0
) {
Transforms.unwrapNodes(editor, {
match: node => node.type === 'blockquote',
split: true,
})
return
}
}
deleteBackward(unit)
}
editor.insertBreak = () => {
const panel = getDirectBlockquoteParentFromSelection(editor)
if (editor.selection && panel.isInside) {
const [node, nodePath] = Editor.node(editor, editor.selection)
if (Path.isDescendant(nodePath, panel.path) && Node.string(node) === '') {
Transforms.unwrapNodes(editor, {
match: node => node.type === 'blockquote',
split: true,
})
return
}
}
insertBreak()
}

return editor
}
70 changes: 2 additions & 68 deletions packages/fields-document/src/DocumentEditor/blockquote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,83 +2,17 @@
/** @jsx jsx */

import { type ComponentProps, Fragment, useMemo } from 'react'
import { Editor, Node, Path, Range, Transforms } from 'slate'
import { type RenderElementProps } from 'slate-react'

import { jsx, useTheme } from '@keystone-ui/core'
import { Tooltip } from '@keystone-ui/tooltip'

import { IconBase } from './Toolbar'
import { KeyboardInTooltip, ToolbarButton } from './primitives'
import { isElementActive } from './utils'
import { useToolbarState } from './toolbar-state'
import { insertBlockquote } from './blockquote-shared'

export const insertBlockquote = (editor: Editor) => {
const isActive = isElementActive(editor, 'blockquote')
if (isActive) {
Transforms.unwrapNodes(editor, {
match: node => node.type === 'blockquote',
})
} else {
Transforms.wrapNodes(editor, {
type: 'blockquote',
children: [],
})
}
}

function getDirectBlockquoteParentFromSelection (editor: Editor) {
if (!editor.selection) return { isInside: false } as const
const [, parentPath] = Editor.parent(editor, editor.selection)
if (!parentPath.length) {
return { isInside: false } as const
}
const [maybeBlockquoteParent, maybeBlockquoteParentPath] = Editor.parent(editor, parentPath)
const isBlockquote = maybeBlockquoteParent.type === 'blockquote'
return isBlockquote
? ({ isInside: true, path: maybeBlockquoteParentPath } as const)
: ({ isInside: false } as const)
}

export function withBlockquote (editor: Editor): Editor {
const { insertBreak, deleteBackward } = editor
editor.deleteBackward = unit => {
if (editor.selection) {
const parentBlockquote = getDirectBlockquoteParentFromSelection(editor)
if (
parentBlockquote.isInside &&
Range.isCollapsed(editor.selection) &&
// the selection is at the start of the paragraph
editor.selection.anchor.offset === 0 &&
// it's the first paragraph in the panel
editor.selection.anchor.path[editor.selection.anchor.path.length - 2] === 0
) {
Transforms.unwrapNodes(editor, {
match: node => node.type === 'blockquote',
split: true,
})
return
}
}
deleteBackward(unit)
}
editor.insertBreak = () => {
const panel = getDirectBlockquoteParentFromSelection(editor)
if (editor.selection && panel.isInside) {
const [node, nodePath] = Editor.node(editor, editor.selection)
if (Path.isDescendant(nodePath, panel.path) && Node.string(node) === '') {
Transforms.unwrapNodes(editor, {
match: node => node.type === 'blockquote',
split: true,
})
return
}
}
insertBreak()
}

return editor
}
export * from './blockquote-shared'

export const BlockquoteElement = ({ attributes, children }: RenderElementProps) => {
const { colors, spacing } = useTheme()
Expand Down
60 changes: 60 additions & 0 deletions packages/fields-document/src/DocumentEditor/code-block-shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
Editor,
Element,
Point,
Range,
Text,
Transforms,
} from 'slate'

export function withCodeBlock (editor: Editor): Editor {
const { insertBreak, normalizeNode } = editor

editor.insertBreak = () => {
const [node, path] = Editor.above(editor, {
match: n => Element.isElement(n) && Editor.isBlock(editor, n),
}) || [editor, []]
if (node.type === 'code' && Text.isText(node.children[0])) {
const text = node.children[0].text
if (
text[text.length - 1] === '\n' &&
editor.selection &&
Range.isCollapsed(editor.selection) &&
Point.equals(Editor.end(editor, path), editor.selection.anchor)
) {
insertBreak()
Transforms.setNodes(editor, { type: 'paragraph', children: [] })
Transforms.delete(editor, {
distance: 1,
at: { path: [...path, 0], offset: text.length - 1 },
})
return
}
editor.insertText('\n')
return
}
insertBreak()
}
editor.normalizeNode = ([node, path]) => {
if (node.type === 'code' && Element.isElement(node)) {
for (const [index, childNode] of node.children.entries()) {
if (!Text.isText(childNode)) {
if (editor.isVoid(childNode)) {
Transforms.removeNodes(editor, { at: [...path, index] })
} else {
Transforms.unwrapNodes(editor, { at: [...path, index] })
}
return
}
const marks = Object.keys(childNode).filter(x => x !== 'text')
if (marks.length) {
Transforms.unsetNodes(editor, marks, { at: [...path, index] })
return
}
}
}
normalizeNode([node, path])
}

return editor
}
Loading