Darginec05 / Yoopta-Editor

Build Notion-like, Craft-like, Coda-like, Medium-like editors with Yoopta
https://yoopta.dev/
MIT License
1.44k stars 111 forks source link

Help Needed: Custom Block Plugin #357

Open Quietscher opened 1 month ago

Quietscher commented 1 month ago

What's the example?

Hello! I want to create a custom block component, and I've looked at the carousel example. It should have multiple text inputs and subcomponents. I can't get it to work that the use can input text (that is saved and so on)...

You can think of it as Card CardHeader CardContent

(where one can enter text as header and as content)

I would be very glad for some explanation, thanks!

Code of Conduct

Darginec05 commented 1 month ago

Hi @Quietscher 👋 Do you have UI or layout for this plugin/block? I want to take a look to understand the structure

Quietscher commented 1 month ago

Hi @Quietscher 👋 Do you have UI or layout for this plugin/block? I want to take a look to understand the structure

Hey 👋

<Card {...attributes} key={blockId}>
                <CardHeader>
                    <CardTitle>Marker</CardTitle>
                    <CardDescription>Card Description</CardDescription>
                    <DateTimePicker />
                </CardHeader>
                <CardContent>
                    <div className="flex flex-row items-start justify-start">
                        <div className="ml-10 max-w-xs">
                            <Carousel
                                plugins={[
                                    Autoplay({
                                        delay: 3000,
                                    }),
                                ]}
                            >
                                <CarouselContent>
                                    {Array.from({ length: 5 }).map((_, index) => (
                                        <CarouselItem key={index}>
                                            <Card className="mb-2 mx-1">
                                                <CardContent className="flex aspect-square items-center justify-center p-6">
                                                    <span className="text-4xl font-semibold">
                                                        {index + 1}
                                                    </span>
                                                </CardContent>
                                            </Card>
                                        </CarouselItem>
                                    ))}
                                </CarouselContent>
                                <CarouselPrevious />
                                <CarouselNext />
                            </Carousel>
                        </div>
                    </div>
                </CardContent>
                <CardFooter>
                    <p>Card Footer</p>
                </CardFooter>
            </Card>
        sth like this
Quietscher commented 1 month ago
export const MarkerBlock = ({
    attributes,
    children,
    element,
    blockId,
}: PluginElementRenderProps) => {
    return (
        <>
            <Card {...attributes}>
                {...children}
            </Card>
        </>
    );
};

export const MarkerCardContent = ({ children }: PluginElementRenderProps) => {
    return <CardContent>{...children}</CardContent>;
};

export const MarkerHeader = ({ children }: PluginElementRenderProps) => {
    return <CardHeader>{...children}</CardHeader>;
};

export const MarkerDate = ({ element }: PluginElementRenderProps) => {
    let newDate = new Date(element.props.timestamp);
    const [date, setDate] = useState<Date | undefined>(newDate ?? undefined);
    return <DateTimePicker date={date} setDate={setDate} />;
};

export const MarkerDescription = ({ children }: PluginElementRenderProps) => {
    return <CardDescription>{...children}</CardDescription>;
};

export const MarkerTitle = ({ children }: PluginElementRenderProps) => {
    return <CardTitle>{...children}</CardTitle>;
};

export const MarkerBody = ({
    attributes,
    children,
    element,
    blockId,
}: PluginElementRenderProps) => {
    const imgSrc: string[] | undefined = element.props.imgSrcs;

    return (
        <div {...attributes}>
            {imgSrc && imgSrc.length > 0 && (
                <div className="flex flex-row items-start justify-start">
                    <div className="ml-10 max-w-xs">
                        <Carousel
                            plugins={[
                                Autoplay({
                                    delay: 3000,
                                }),
                            ]}
                        >
                            <CarouselContent>
                                {imgSrc.map((src: string, index: any) => (
                                    <CarouselItem key={index}>
                                        <Card className="mb-2 mx-1">
                                            <CardContent className="flex aspect-square items-center justify-center p-0 m-0">
                                                <Image src={src} alt={index} />
                                            </CardContent>
                                        </Card>
                                    </CarouselItem>
                                ))}
                            </CarouselContent>
                            <CarouselPrevious />
                            <CarouselNext />
                        </Carousel>
                    </div>
                </div>
            )}
            {...children}
        </div>
    );
};

export type MarkerBlockType = SlateElement<'marker-block'>;
export type MarkerCardContentType = SlateElement<'marker-content'>;
export type MarkerBodyType = SlateElement<'marker-body', MarkerBodyProps>;
export type MarkerHeaderType =  SlateElement<'marker-header'>;
export type MarkerDateType =  SlateElement<'marker-date', MarkerDateProps>;
export type MarkerDescriptionType =  SlateElement<'marker-description'>;
export type MarkerTitleType =  SlateElement<'marker-title'>;

const MarkerBlockPlugin = new YooptaPlugin({
    type: 'Block',
    elements: {
        'marker-block': {
            render: MarkerBlock,
            children: ['marker-header','marker-content'],
            asRoot: true,
            props: {
                timestamp: new Date().getMilliseconds(),
            },
        },
        'marker-content': {
            render: MarkerCardContent,
            children: ['marker-body']
        },
        'marker-header': {
            render: MarkerHeader,
            children: ['marker-date','marker-title', 'marker-description'],
        },
        'marker-date': {
            render: MarkerDate,
            props: {
                timestamp: new Date().getMilliseconds(),
            },
        },
        'marker-title': {
            render: MarkerTitle,
        },
        'marker-description': {
            render: MarkerDescription,
        },
        'marker-body': {
            render: MarkerBody,
            props: {
                imgSrcs: [],
            },
        },
    },

This is what i currently have

Quietscher commented 4 weeks ago

Hey @Darginec05 😊 Have you had a chance to look at it yet? Thanks -Quirin

Darginec05 commented 4 weeks ago

Hi @Quietscher 👋 I'm currently preparing release and writing docs I think I can help you in more detail only on the weekend

Darginec05 commented 3 weeks ago

@Quietscher I'm finally here :D Do you have any UI for your custom plugin? This will help me understand the structure better

Quietscher commented 3 weeks ago

Nice! Hmm i dont have any Screenshot... I try to make a custom Element with the following attributes:

That all in one custom block

So after creating the block, the user can set a title a content, pictures, a date and so on.

Id like to save the content that the user creates in the block separately in an object ill push to My backend.

Quietscher commented 2 weeks ago

Habe you had some time yet? @Darginec05 😄

Darginec05 commented 2 weeks ago

@Quietscher I checked your code, but still really hard to understand what you want to build :D Maybe you have some UI sketch or something like that? This will really give me better idea of what plugin you want to create

Quietscher commented 1 week ago

image

Quietscher commented 1 week ago

maybe this helps

Darginec05 commented 1 week ago

@Quietscher yes! much better give me time to think about the structure of this plugin

Quietscher commented 1 week ago

@Quietscher yes! much better

give me time to think about the structure of this plugin

Nice 😁 yeah sure

Darginec05 commented 1 week ago

I did approximate elements structure for your plugin. It should be like this:

type SuperCarouselContainerElement = SlateElement<'super-carousel'>;
type CarouselListlement = SlateElement<'carousel-list'>;
type CarouselItemElement = SlateElement<'carousel-item'>;
type CarouselItemImageElement = SlateElement<'carousel-item-image'>;
type UserInputElement = SlateElement<'user-input'>;
type SelectorItem = SlateElement<'selector-item'>;
type DateSelector = SlateElement<'date-selector'>;
type LocationSelector = SlateElement<'location-selector'>;

type SuperBlockPluginMap = {
  'super-carousel': SuperCarouselContainerElement;
  'carousel-list': CarouselListlement;
  'carousel-item': CarouselItemElement;
  'carousel-item-image': CarouselItemImageElement;
  'user-input': UserInputElement;
  'date-selector': DateSelector;
  'location-selector': LocationSelector;
  'selector-item': SelectorItem;
};

const SuperCarouselBlock = new YooptaPlugin<SuperBlockPluginMap>({
  type: 'SuperCarousel',
  elements: {
    'super-carousel': {
      children: ['selector-item', 'carousel-list', 'user-input'],
      asRoot: true,
      render: ({ attributes, children, element }) => {
        return (
          <div {...attributes} style={{ border: '1px solid red', padding: 10 }}>
            {children}
          </div>
        );
      },
    },
    'carousel-list': {
      // do it using Commands API and use onBeforeCreate event to build block with three or more images
      children: ['carousel-item', 'carousel-item', 'carousel-item'],
      render: ({ attributes, children, element }) => {
        return (
          <div {...attributes} style={{ border: '1px solid red', display: 'flex', padding: 10 }}>
            {children}
          </div>
        );
      },
    },
    'carousel-item': {
      children: ['carousel-item-image'],
      render: ({ attributes, children, element }) => {
        return (
          <div {...attributes} style={{ border: '1px solid green', padding: 10, width: 150 }}>
            {children}
          </div>
        );
      },
    },
    'carousel-item-image': {
      children: [],
      render: ({ attributes, children, element }) => {
        return (
          <div {...attributes} contentEditable={false} style={{ border: '1px solid blue', padding: 10 }}>
            {children}
            <img
              src="https://res.cloudinary.com/ench-app/image/upload/v1731103395/c0dc98f5-da0b-430a-b9b4-144f9a25b3b9_rqcqcc_utkcnh.webp"
              width={150}
              height={150}
            />
          </div>
        );
      },
      props: {
        nodeType: 'void',
      },
    },
    'user-input': {
      children: [],
      render: ({ attributes, children, element }) => {
        return (
          <div {...attributes} style={{ border: '1px solid yellow', padding: 10 }}>
            {children}
          </div>
        );
      },
    },
    'selector-item': {
      children: ['location-selector', 'date-selector'],
      render: ({ attributes, children, element }) => {
        return (
          <div {...attributes} style={{ border: '1px solid pink', padding: 10, display: 'flex', width: '100%' }}>
            {children}
          </div>
        );
      },
    },
    'location-selector': {
      children: [],
      render: ({ attributes, children, element }) => {
        return (
          <div {...attributes} contentEditable={false} style={{ border: '1px solid pink', padding: 10, flex: 1 }}>
            {children}
            <input type="date" />
          </div>
        );
      },
      props: {
        nodeType: 'void',
      },
    },
    'date-selector': {
      children: [],
      render: ({ attributes, children, element }) => {
        return (
          <div {...attributes} contentEditable={false} style={{ border: '1px solid purple', padding: 10, flex: 1 }}>
            {children}
            <input type="date" />
          </div>
        );
      },
      props: {
        nodeType: 'void',
      },
    },
  },
});

P.S. if you want fully functional plugin for your needs, then I can develop it after support/donation

Darginec05 commented 1 week ago

@Quietscher did this example help you?

Quietscher commented 6 days ago

@Quietscher did this example help you?

Hey 👋🏻 Have Not had the time yet to try it out. But thanks so far :)