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

Help Needed: Custom Block Plugin #357

Open
1 task done
Quietscher opened this issue Oct 22, 2024 · 16 comments
Open
1 task done

Help Needed: Custom Block Plugin #357

Quietscher opened this issue Oct 22, 2024 · 16 comments

Comments

@Quietscher
Copy link

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

  • I agree to follow this project's Code of Conduct
@Darginec05
Copy link
Owner

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

@Quietscher
Copy link
Author

Quietscher commented Oct 22, 2024

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
Copy link
Author

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
Copy link
Author

Quietscher commented Oct 24, 2024

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

@Darginec05
Copy link
Owner

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

@Darginec05
Copy link
Owner

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

@Quietscher
Copy link
Author

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

  • A Date Picker Element (i will use the one from shadcn)
  • Different Text inputs
  • Picture Upload an carousel

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
Copy link
Author

Habe you had some time yet? @Darginec05 😄

@Darginec05
Copy link
Owner

@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
Copy link
Author

image

@Quietscher
Copy link
Author

maybe this helps

@Darginec05
Copy link
Owner

Darginec05 commented Nov 11, 2024

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

@Quietscher
Copy link
Author

@Quietscher yes! much better

give me time to think about the structure of this plugin

Nice 😁 yeah sure

@Darginec05
Copy link
Owner

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
Copy link
Owner

@Quietscher did this example help you?

@Quietscher
Copy link
Author

@Quietscher did this example help you?

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants