plate icon indicating copy to clipboard operation
plate copied to clipboard

Images in strings serialized by serializeHtml are displayed too small

Open seanbruce opened this issue 2 years ago • 3 comments

Description

When the string serialized by the serializeHtml() function contains img tags, there will be an inline style width:0 on a parent element of the tag. However, this style will cause the image to be displayed too small.

Steps to Reproduce

// https://github.com/udecode/plate/issues/2804#issuecomment-1931065187
const excludedSelectionPlugin = plugins?.filter(
  (plugin) => plugin.key !== "blockSelection"
);

const tmpEditor = createPlateEditor({ plugins: excludedSelectionPlugin });

interface ContentEditorProps {
  raw: Value;
  onContentUpdate: (raw: Value, html: string) => void;
}

function ContentEditor({ raw, onContentUpdate }: ContentEditorProps) {
  const [editorState, setEditorState] = React.useState<Value>([]);

  const handleSaveContent = () => {
    const html = serializeHtml(tmpEditor, {
      nodes: editorState,
      dndWrapper: (props) => <DndProvider backend={HTML5Backend} {...props} />,
    });
    onContentUpdate(editorState, html);
  };
  return (
    <>
      <RichTextEditor
        onEditorValueChange={setEditorState}
        initialValue={raw}
      />
      <Button className="my-4" onClick={handleSaveContent}>
        save
      </Button>
    </>
  );
}

I use the onContentUpdate function to save data to the database. The raw variable is the original state of the editor, and the html variable is the serialized string of the editor content, which is used to display directly on the page. The data in raw can be used to restore the editor state normally after being read from the API, but the img elements in the serialized html have incorrect inline styles, which causes the images to be displayed too small.

<div class="slate-img"><figure contenteditable="false"><div style="position:relative"><div style="width:0;min-width:92px;max-width:100%;position:relative"><div></div><img src="https://localhost:44392/api/cms-kit/media/9c6dc7ab-07d0-b34d-d549-3a112dffb475" draggable="true" alt="" style="\n"><div></div></div></div></figure></div>

Sandbox

Expected Behavior

The size of images edited in the editor should be the same as the size displayed on the page.

Environment

  • slate: "0.102.0"
  • slate-react: "0.102.0"
  • browser: brave

Bounty

Click here to add a bounty via Algora.

Funding

  • You can sponsor this specific effort via a Polar.sh pledge below
  • We receive the pledge once the issue is completed & verified
Fund with Polar

seanbruce avatar Mar 12 '24 10:03 seanbruce

I found a code snippet that I think might be causing the bug. https://github.com/udecode/plate/blob/304fc1ae7b7654008fddf444dfd9a3743cb3b28e/packages/resizable/src/components/Resizable.tsx#L28C1-L67C3

export const useResizableState = ({
  align = 'center',
  minWidth = 92,
  maxWidth = '100%',
}: ResizableOptions = {}) => {
  const element = useElement<TResizableElement>();
  const editor = useEditorRef();

  const nodeWidth = element?.width ?? '100%';

  const [width, setWidth] = useResizableStore().use.width();

  const setNodeWidth = React.useCallback(
    (w: number) => {
      const path = findNodePath(editor, element!);
      if (!path) return;

      if (w === nodeWidth) {
        // Focus the node if not resized
        select(editor, path);
      } else {
        setNodes<TResizableElement>(editor, { width: w }, { at: path });
      }
    },
    [editor, element, nodeWidth]
  );

  React.useEffect(() => {
    setWidth(nodeWidth);
  }, [nodeWidth, setWidth]);

  return {
    align,
    minWidth,
    maxWidth,
    setNodeWidth,
    setWidth,
    width,
  };
};
import React from 'react';
import { createAtomStore } from '@udecode/plate-common';

export const { resizableStore, useResizableStore, ResizableProvider } =
  createAtomStore(
    {
      width: 0 as React.CSSProperties['width'],
    },
    { name: 'resizable' }
  );

When I serialize the editor state to HTML, the value of nodeWidth is the width of the element, or 100% if I have not resize the element, but

useResizableStore always create store with default width value 0, in server rendering, useEffect won't fire. so 0 is the final value get render to static string

To summarize, when I call serializeHtml, and serializeHtml calls renderToStaticMarkup, the rendered image width will always be 0.

seanbruce avatar Mar 14 '24 10:03 seanbruce

I have the same issue. Is there any trick?

Babaktrad avatar Aug 22 '24 21:08 Babaktrad

I've found a simple workaround that addresses the issue temporarily. In the image-element.tsx component:

const width = useResizableStore().get.width()

to

const width = props.element.width

// ... rest of the component
<Resizable
              align={align}
              options={{
                align,
                readOnly,
              }}
              **style={{
                maxWidth: "100%",
                minWidth: "92px",
                position: "relative",
                width: width + "px",
              }}**
            >
// ... rest of the component

Babaktrad avatar Aug 22 '24 23:08 Babaktrad