How to Modify Display without Altering Model in GrapesJS Editor with Custom Media Library Integration

53 views Asked by At

I am currently working on utilizing the GrapesJS editor for creating email templates, and I've encountered a specific challenge that I could use some assistance with.

My situation involves certain constraints regarding how templates should be passed to the server. Specifically, I plan to utilize placeholders actively within the editor, such as @firstname, to personalize the email content.

Additionally, I've implemented a custom media library that allows for adding images to the template with a placeholder attribute in the src field, following this format: @media-item-url(${itemID}).

My goal is to have the actual image displayed on the canvas while still using the placeholder within the src attribute. Essentially, I aim to modify only the representation of the component without altering its underlying model.

Below, I've attached some code snippets for reference.

I would greatly appreciate any insights or guidance on how to achieve this functionality within the GrapesJS editor. Thank you in advance for your time and assistance.

import { useEffect, useRef, useState } from "react";
import { useParams } from "react-router-dom";
import grapesjs, { Editor } from "grapesjs";
import grapesjsMJML from "grapesjs-mjml";
import "grapesjs/dist/css/grapes.min.css";
import "../../css/grapesJsEditor.css";
import MediaLibraryExperimental from "../Template/MediaLibrary/MediaLibraryExperimental";

// Ensure that the id doesn't start with a number, as it's not valid HTML format. 
// If it does, prepend "editor-" to it; otherwise, keep it as is.
const getId = (id: string) => {
  return /^[0-9]/.test(id) ? `editor-${id}` : `${id}`;
};

interface Props {
  currentContent: string;
  onContentChange: (field: string, value: string | number) => void;
}

const GrapesJSEditor = ({ currentContent, onContentChange }: Props) => {
  const { id } = useParams();
  const [showMediaLibrary, setShowMediaLibrary] = useState(false);
  const [mediaLibrary, setMediaLibrary] = useState<any[]>([]);
  const assetManager = useRef<any>(null);

  const initializeEditor = () => {
    if (!id) return;
    const selector = `#${getId(id)}`;
    const editor = grapesjs.init({
      container: selector,
      storageManager: false,
      assetManager: {
        assets: [...mediaLibrary],
        custom: {
          open(props) {
            setShowMediaLibrary(true);
            // `props` are the same used in `asset:custom` event
            // Init and open your external Asset Manager
            assetManager.current = props;
          },
          close(props) {
            setShowMediaLibrary(false);
          },
        },
      },
      fromElement: true,
      avoidInlineStyle: false,
      plugins: [grapesjsMJML],
      pluginsOpts: {
        // @ts-ignore
        [grapesjsMJML]: {
          blocks: [
            "mj-1-column",
            "mj-2-columns",
            "mj-3-columns",
            "mj-text",
            "mj-button",
            "mj-divider",
            "mj-spacer",
            "mj-navbar",
            "mj-navbar-link",
          ],
        },
      },
      height: "70vh",
    });

    editor.setComponents(currentContent);
    assetsHandler(editor);
    componentUpdateHandler(editor);
    addBlocks(editor);
    closeDefaultCategories(editor);
    return () => editor.destroy();
  };

  const addBlocks = (editor: any) => {
    editor.Blocks.add("add-media-item", {
      label: "Media Item",
      content: `<mj-image src="#"/>`,
      attributes: { class: "fa fa-file-image-o", src: "#" },
      category: "Media Library",
      activate: true,
    });

    const placeholders = [
      {
        name: "salutation",
        icon: "fa-venus-mars",
        content: "salutation(Sehr geehrte Frau;Sehr geehrter Herr)",
      },
      { name: "first name", icon: "fa-user-o" },
      { name: "last name", icon: "fa-user-o" },
      { name: "company", icon: "fa-building-o" },
      { name: "email", icon: "fa-envelope-o" },
      { name: "phone", icon: "fa-phone" },
    ];

    placeholders.forEach((placeholder) => {
      editor.Blocks.add(`${placeholder.name}-button`, {
        // first name -> First name
        label:
          placeholder.name.charAt(0).toUpperCase() + placeholder.name.slice(1),
        content: `<mj-text>@${
          placeholder.name === "salutation"
            ? placeholder.content
            : placeholder.name
        }</mj-text>`,
        attributes: { class: `fa ${placeholder.icon}` },
        category: "Placeholders",
        activate: false,
      });
    });
  };


  // Define the action when the custom button is clicked
  const assetsHandler = (editor: Editor) => {
    editor.on("asset:custom", (props) => {
      // Define the select function for the asset manager
      props.select = (asset: string, isSelected: boolean) => {
        // Check if the asset is selected
        if (asset) {
          // If selected, set the URL of the image component to the provided imgUrl
          const imgComponent = editor.getSelected();
          const srcPlaceHolder = asset;
          if (imgComponent) {
            imgComponent.setAttributes({
              src: srcPlaceHolder,
              alt: asset,
            });
          }
        } else {
          console.log("Asset deselected");
        }
      };
      componentsRenderHandler(editor);
    });
  };

  const componentUpdateHandler = (editor: any) => {
    editor.on("update", () => {
      const mjmlContent = editor.getHtml();
      onContentChange("email_content", mjmlContent);
    });
  };

  editor.DomComponents.addType("add-media-item", {
      view: {
        init() {
          console.log("Local hook: view.init");
        },
        onRender({ el }: { el: any }) {
          console.log("render in view");
          const src = el.getAttribute("src");
          const id = src.match(/@media-item-url\(([^)]+)\)/); // between @media-item-url()
          const newSrc = `images/${id}.jpg`;
          el.setAttribute("src", newSrc);
        },
      },
    });
  };

  useEffect(initializeEditor, []);

  // Handle the selection of an item from the media library
  const handleItemSelect = (itemID: any) => {
    assetManager.current.select(`@media-item-url(${itemID})`, true);
    assetManager.current.close();
  };

  return (
    <div>
      {id ? <div id={getId(id)} /> : null}

      <MediaLibraryExperimental
        onItemSelectHandler={handleItemSelect}
        isVisible={showMediaLibrary}
        setIsVisible={setShowMediaLibrary}
        setMediaLibrary={setMediaLibrary}
      ></MediaLibraryExperimental>
    </div>
  );
};
export default GrapesJSEditor;

0

There are 0 answers