PlateJS:无法添加图像 - 添加图像时出现空白屏幕(图像元素)

问题描述 投票:0回答:1

我用 npx @udecode/plate-ui@latest add image-element

 添加了 
Image Element

组件

这添加了标题、媒体弹出框和可调整大小的组件。

在 Platejs 文档中,当我添加图像时一切正常

图1

图2

图3

视频

https://github.com/udecode/plate/assets/163764831/51b85949-253a-4663-b239-0062928e186e

但是在我的编辑器上,当我通过单击固定工具栏中的按钮添加图像(与 Platejs 文档中相同)时,它不会出现,所有内容都会消失并留下空白页面

图1

图2

图3

视频

https://github.com/udecode/plate/assets/163764831/fddeb502-0afa-42af-81c7-55e9a2c3fb96

我收到错误

固定工具栏按钮.tsx

import {
  MARK_BOLD,
  MARK_CODE,
  MARK_ITALIC,
  MARK_STRIKETHROUGH,
  MARK_UNDERLINE,
} from '@udecode/plate-basic-marks';
import { useEditorReadOnly } from '@udecode/plate-common';

import { Icons } from '@/components/plate-ui/icons';

import { InsertDropdownMenu } from './insert-dropdown-menu';
import { MarkToolbarButton } from './mark-toolbar-button';
import { ModeDropdownMenu } from './mode-dropdown-menu';
import { ToolbarGroup } from './toolbar';
import { TurnIntoDropdownMenu } from './turn-into-dropdown-menu';
import { AlignDropdownMenu } from '@/components/plate-ui/align-dropdown-menu';

import { MediaToolbarButton } from '@/components/plate-ui/media-toolbar-button';

import { ELEMENT_IMAGE } from '@udecode/plate-media';

export function FixedToolbarButtons() {
  const readOnly = useEditorReadOnly();

  return (
    <div className="w-full overflow-hidden">
      <div
        className="flex flex-wrap"
        style={{
          transform: 'translateX(calc(-1px))',
        }}
      >
        {!readOnly && (
          <>
            <ToolbarGroup noSeparator>
              <InsertDropdownMenu />
              <TurnIntoDropdownMenu />
            </ToolbarGroup>

            <ToolbarGroup>
              <MarkToolbarButton tooltip="Bold (⌘+B)" nodeType={MARK_BOLD}>
                <Icons.bold />
              </MarkToolbarButton>
              
              <MarkToolbarButton tooltip="Italic (⌘+I)" nodeType={MARK_ITALIC}>
                <Icons.italic />
              </MarkToolbarButton>
              
              <MarkToolbarButton
                tooltip="Underline (⌘+U)"
                nodeType={MARK_UNDERLINE}
              >
                <Icons.underline />
              </MarkToolbarButton>

              <MarkToolbarButton
                tooltip="Strikethrough (⌘+⇧+M)"
                nodeType={MARK_STRIKETHROUGH}
              >
                <Icons.strikethrough />
              </MarkToolbarButton>

              <MarkToolbarButton tooltip="Code (⌘+E)" nodeType={MARK_CODE}>
                <Icons.code />
              </MarkToolbarButton>

              <AlignDropdownMenu />
            </ToolbarGroup>

            <ToolbarGroup>
              <MediaToolbarButton nodeType={ELEMENT_IMAGE} />
            </ToolbarGroup>
          </>
        )}

        <div className="grow" />

        <ToolbarGroup noSeparator>
          <ModeDropdownMenu />
        </ToolbarGroup>
      </div>
    </div>
  );
}

标题.tsx

import { cn, withCn, withVariants } from '@udecode/cn';
import {
  Caption as CaptionPrimitive,
  CaptionTextarea as CaptionTextareaPrimitive,
} from '@udecode/plate-caption';
import { cva } from 'class-variance-authority';

const captionVariants = cva('max-w-full', {
  variants: {
    align: {
      left: 'mr-auto',
      center: 'mx-auto',
      right: 'ml-auto',
    },
  },
  defaultVariants: {
    align: 'center',
  },
});

export const Caption = withVariants(CaptionPrimitive, captionVariants, [
  'align',
]);

export const CaptionTextarea = withCn(
  CaptionTextareaPrimitive,
  cn(
    'mt-2 w-full resize-none border-none bg-inherit p-0 font-[inherit] text-inherit',
    'focus:outline-none focus:[&::placeholder]:opacity-0',
    'text-center print:placeholder:text-transparent'
  )
);

media-popover.tsx

import React, { useEffect } from 'react';
import {
  isSelectionExpanded,
  useEditorSelector,
  useElement,
  useRemoveNodeButton,
} from '@udecode/plate-common';
import {
  floatingMediaActions,
  FloatingMedia as FloatingMediaPrimitive,
  useFloatingMediaSelectors,
} from '@udecode/plate-media';
import { useReadOnly, useSelected } from 'slate-react';

import { Icons } from '@/components/plate-ui/icons';

import { Button, buttonVariants } from './button';
import { inputVariants } from './input';
import { Popover, PopoverAnchor, PopoverContent } from './popover';
import { Separator } from './separator';

export interface MediaPopoverProps {
  pluginKey?: string;
  children: React.ReactNode;
}

export function MediaPopover({ pluginKey, children }: MediaPopoverProps) {
  const readOnly = useReadOnly();
  const selected = useSelected();

  const selectionCollapsed = useEditorSelector(
    (editor) => !isSelectionExpanded(editor),
    []
  );
  const isOpen = !readOnly && selected && selectionCollapsed;
  const isEditing = useFloatingMediaSelectors().isEditing();

  useEffect(() => {
    if (!isOpen && isEditing) {
      floatingMediaActions.isEditing(false);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isOpen]);

  const element = useElement();
  const { props: buttonProps } = useRemoveNodeButton({ element });

  if (readOnly) return <>{children}</>;

  return (
    <Popover open={isOpen} modal={false}>
      <PopoverAnchor>{children}</PopoverAnchor>

      <PopoverContent
        className="w-auto p-1"
        onOpenAutoFocus={(e) => e.preventDefault()}
      >
        {isEditing ? (
          <div className="flex w-[330px] flex-col">
            <div className="flex items-center">
              <div className="flex items-center pl-3 text-muted-foreground">
                <Icons.link className="size-4" />
              </div>

              <FloatingMediaPrimitive.UrlInput
                className={inputVariants({ variant: 'ghost', h: 'sm' })}
                placeholder="Paste the embed link..."
                options={{
                  pluginKey,
                }}
              />
            </div>
          </div>
        ) : (
          <div className="box-content flex h-9 items-center gap-1">
            <FloatingMediaPrimitive.EditButton
              className={buttonVariants({ variant: 'ghost', size: 'sm' })}
            >
              Edit link
            </FloatingMediaPrimitive.EditButton>

            <Separator orientation="vertical" className="my-1" />

            <Button variant="ghost" size="sms" {...buttonProps}>
              <Icons.delete className="size-4" />
            </Button>
          </div>
        )}
      </PopoverContent>
    </Popover>
  );
}

可调整大小.tsx

import { cn, withRef, withVariants } from '@udecode/cn';
import {
  Resizable as ResizablePrimitive,
  ResizeHandle as ResizeHandlePrimitive,
} from '@udecode/plate-resizable';
import { cva } from 'class-variance-authority';

export const mediaResizeHandleVariants = cva(
  cn(
    'top-0 flex w-6 select-none flex-col justify-center',
    "after:flex after:h-16 after:w-[3px] after:rounded-[6px] after:bg-ring after:opacity-0 after:content-['_'] group-hover:after:opacity-100"
  ),
  {
    variants: {
      direction: {
        left: '-left-3 -ml-3 pl-3',
        right: '-right-3 -mr-3 items-end pr-3',
      },
    },
  }
);

const resizeHandleVariants = cva(cn('absolute z-40'), {
  variants: {
    direction: {
      left: 'h-full cursor-col-resize',
      right: 'h-full cursor-col-resize',
      top: 'w-full cursor-row-resize',
      bottom: 'w-full cursor-row-resize',
    },
  },
});

const ResizeHandleVariants = withVariants(
  ResizeHandlePrimitive,
  resizeHandleVariants,
  ['direction']
);

export const ResizeHandle = withRef<typeof ResizeHandlePrimitive>(
  (props, ref) => (
    <ResizeHandleVariants
      ref={ref}
      direction={props.options?.direction}
      {...props}
    />
  )
);

const resizableVariants = cva('', {
  variants: {
    align: {
      left: 'mr-auto',
      center: 'mx-auto',
      right: 'ml-auto',
    },
  },
});

export const Resizable = withVariants(ResizablePrimitive, resizableVariants, [
  'align',
]);

插件.ts

import { createPlugins } from '@udecode/plate-common';
import { withDraggables } from '@/components/plate-ui/with-draggables';
import { withPlaceholders } from '@/components/plate-ui/placeholder';
import {
  createImagePlugin,
  createMediaEmbedPlugin,
  ELEMENT_IMAGE,
} from '@udecode/plate-media';
import { ImageElement } from '@/components/plate-ui/image-element';

export const plugins = createPlugins(
    [
        createImagePlugin(),
        createMediaEmbedPlugin(),
    ],
    {
        components: withDraggables(
            withPlaceholders({
                [ELEMENT_IMAGE]: ImageElement,
            })
        ),
    }
);

我的编辑

import { Plate, Value } from '@udecode/plate-common';
import { Editor } from '@/components/plate-ui/editor';
import { useRef, useState } from 'react';
import { cn } from '@udecode/cn';
import { ELEMENT_PARAGRAPH } from '@udecode/plate-paragraph';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { plugins } from '@/lib/plate/plugins';
import { CursorOverlay } from '@/components/plate-ui/cursor-overlay';
import { FixedToolbar } from '@/components/plate-ui/fixed-toolbar';
import { FixedToolbarButtons } from '@/components/plate-ui/fixed-toolbar-buttons';
import { FloatingToolbar } from '@/components/plate-ui/floating-toolbar';
import { FloatingToolbarButtons } from '@/components/plate-ui/floating-toolbar-buttons';
import { TooltipProvider } from '@radix-ui/react-tooltip';

const initialValue = [
    {
      id: '1',
      type: ELEMENT_PARAGRAPH,
      children: [{ text: 'Hello, World!' }],
    },
    {
      id: '2',
      type: ELEMENT_PARAGRAPH,
      children: [{ text: 'Hello, test!' }],
    },
];

export function CreateProject(){
    const [contentJson, setContentJson] = useState<Value>(initialValue);
    const containerRef = useRef(null);

    return (
        <div className='h-[60vh] w-[60%] mx-auto mt-20'>
            <TooltipProvider>
                <DndProvider backend={HTML5Backend}>
                    <div className="relative">   
                        <Plate 
                            plugins={plugins}
                            initialValue={initialValue}
                            onChange={(newValue) => {
                                setContentJson(newValue);
                            }}
                        >
                            <div
                                ref={containerRef}
                                className={cn(
                                    'relative',
                                    // Block selection
                                    '[&_.slate-start-area-left]:!w-[64px] [&_.slate-start-area-right]:!w-[64px] [&_.slate-start-area-top]:!h-4'
                                )}
                            >
                                {/* <TooltipProvider> */}
                                    <FixedToolbar>
                                        <FixedToolbarButtons />
                                    </FixedToolbar>
                                {/* </TooltipProvider> */}

                                <Editor 
                                    // className="px-[96px] py-16"
                                    className="px-[50px] py-16"
                                    autoFocus
                                    focusRing={false}
                                    variant="ghost"
                                    size="md"
                                />

                                {/* <TooltipProvider> */}
                                    <FloatingToolbar>
                                        <FloatingToolbarButtons />
                                    </FloatingToolbar>
                                {/* </TooltipProvider> */}

                                <CursorOverlay containerRef={containerRef} />
                            </div>
                        </Plate>
                    </div> 
                </DndProvider>
            </TooltipProvider>
            
        </div>
    )
}
reactjs typescript shadcnui radix-ui
1个回答
0
投票

我有一个预览部分,我将 json 转换为 html

useEffect(() => {
        const element = createElement("div", null,
            contentJson.map((element) => {
                let className:string="";
                switch (element.type){
                    case "p":
                        className += " text-lg"
                        break;
                    case "h1":
                        className += " text-4xl font-bold mt-0 mb-1"
                        break;
                    case "h2":
                        className += " text-2xl font-semibold mb-px mt-[1.4em]"
                        break;
                    case "h3":
                        className += " text-xl font-semibold mb-px mt-[1em]"
                        break;
                }

                if (element.align) {
                    switch (element.align){
                        case "center":
                            className += " text-center"
                            break;
                        case "left":
                            className += " text-left"
                            break;
                        case "right":
                            className += " text-right"
                            break;
                    }
                }

                return (
                    createElement(element.type, { className }, element.children.map((text) => {
                        let className:string="";
                        if (text.bold){ className += " font-bold" }
                        if (text.italic){ className += " italic" }
                        if (text.underline){ className += " underline" }
                        if (text.strikethrough){ className += " line-through" }

                        return (
                            createElement("span", { className }, text.text as string)
                        )
                    }))
                )
            })
        )

        setContent(element)
    }, [contentJson])

我使用CreateElement创建html标签,因此当它是图像(element.type)时,它会创建2个img标签,这会导致问题。

我变了

return (
    createElement(element.type, { className }, element.children.map((text) => {
         let className:string="";
         if (text.bold){ className += " font-bold" }
         if (text.italic){ className += " italic" }
         if (text.underline){ className += " underline" }
         if (text.strikethrough){ className += " line-through" }

         return (
               createElement("span", { className }, text.text as string)
          )
      }))
)

return (
    element.type === "img" ?
        createElement("img", { className, src: element.url }, null)
    :
    createElement(element.type, { className }, element.children.map((text) => {
         let className:string="";
         if (text.bold){ className += " font-bold" }
         if (text.italic){ className += " italic" }
         if (text.underline){ className += " underline" }
         if (text.strikethrough){ className += " line-through" }

         return (
               createElement("span", { className }, text.text as string)
          )
      }))
)

只需要为图像添加类

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.