Issue #5942💬 AnsweredOpened June 12, 2024by adarshsingh1970 reactions

BUg in RichTextEditor

快速解答by artf

From the official demo it looks to work as expected, so I guess it's related to your custom logic. Please provide a minimal reproducible demo of the issue.

Read full answer below ↓

Question

GrapesJS version

  • I confirm to use the latest version of GrapesJS

What browser are you using?

chrome

Reproducible demo link

i have given the code in issues

代码片段TEXT
### Describe the bug

[Screencast from 12-06-24 03:09:58 PM IST.webm](https://github.com/GrapesJS/grapesjs/assets/130237200/d5bbb21e-2e44-4564-a407-4768776c1cd8)


problem is right after applying any bold or italic it is getting disappeard once i click somewhere.
onMounted(async () => {
  componentsTypeScript.value = {};
  componentsDefaults.value = {};

  appId.value = useRoute().query.appId;

  designerStore.bAppLoaded = false;

  await designerStore.getPlugins();

  // try {
  //   const idbPlugins: Plugin[] = await loadPlugins()
  //   if (idbPlugins.length === 0) {
  //     const allPlugins: Plugin[] = await designerStore.getPlugins()
  //     storePlugins(allPlugins)
  //   } else {
  //     console.log('idbPlugins', idbPlugins)
  //     designerStore.setStoredPlugins(idbPlugins)
  //     designerStore.getPlugins().then(function (pluginResponse: Plugin[]) {
  //       storePlugins(pluginResponse)
  //     })
  //   }
  // } catch (error) {
  //   const allPlugins: Plugin[] = await designerStore.getPlugins()
  //   storePlugins(allPlugins)
  // }

  designerStore.bAppLoaded = true;
  
  // let latestVersion= await designerStore.getAppLatestVersion(appId.value);
  designerStore.currentBAApplication = await designerStore.getApp(appId.value, undefined)//,latestVersion.version);
  designerStore.currentBAApplication={...designerStore.currentBAApplication,id:designerStore.currentBAApplication.clonedBAId}
  designerStore.modifiedAppName = appNameTuner(designerStore.currentBAApplication.baAppName);
  // Get Primary Dependencies to initialize the editor with
  const baAppDependencies = await designerStore.getDependencies();

  const libs = baAppDependencies;
  const jsLibs: any[] = [];
  const cssLibs: any[] = [];

  libs.forEach((link) => {
    if (link.endsWith(".js")) {
      jsLibs.push(link);
    } else if (link.endsWith(".css")) {
      cssLibs.push(link);
    }
  });

  // const uniqCssLibs = uniq(cssLibs)

  // libs.forEach((dep: any) => {
  //   if (dep.type == 'css') {
  //     cssLibs.push(dep.src)
  //   } else if (dep.type == 'js') {
  //     jsLibs.push(dep.src)
  //   }
  // })
  let uniqCssLibs = uniq(cssLibs);
  let uniqJsLibs = uniq(jsLibs);

  uniqJsLibs = [
    "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js",
    "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js",
    "https://cdnjs.cloudflare.com/ajax/libs/primeui/4.1.15/primeui.min.js",
    ...uniqJsLibs,
  ];

  editorNonReactive= grapesjs.init({
    height: "100%",
    container: "#canvasBlock",
    fromElement: true,
    canvasCss: `
        .gjs-selected {
          outline: 2px solid #c200fdb8 !important;
        }
   `,
    layerManager: {
       custom: true 
      },
    richTextEditor:{
      stylePrefix: 'rte-',
      adjustToolbar: true,
      actions: ['bold', 'italic', 'underline', 'strikethrough', 'link', 'wrap'],
      custom: true,
    },
    selectorManager: {
      appendTo: "#selectors",
      // This is make grapesjs to use class styles as the priority and change styles of class instead of id provided by grapesjs
      componentFirst: true,
    },
    styleManager: {
      appendTo: "#styles",
    },
    colorPicker: {
      appendTo: "parent",
      showButtons: false,
      showSelectionPalette: false,
      show: function (this: any) {
        const sideBarElement: any = document.getElementById("widgetResizable");

        const handlerXPos: number = this.getBoundingClientRect().x;
        const sidebarXPos: number = sideBarElement.getBoundingClientRect().x;

        if (handlerXPos - sidebarXPos < 160) {
          this.nextElementSibling.style.left = "0px";
        }
      },
      offset: { top: 30, left: -180 },
    },
    traitManager: {
      appendTo: "#traits",
    },
    deviceManager: {
      default: "Desktop",
      devices: [
        {
          id: "desktop",
          name: "Desktop",
          width: "",
          widthMedia: "",
        },
        {
          id: "tablet",
          name: "Tablet",
          width: "768px",
          widthMedia: "768px",
          height: "1024px",
        },
        {
          id: "mobile",
          name: "Mobile",
          width: "360px",
          widthMedia: "360px",
        },
      ],
    },
    plugins: [
      Basics,
      styleBackground,
      // tailwindBlocks,
      "grapesjs-preset-webpage",
      "grapesjs-tabs",
      styleFilter,
      pluginRulers,
      customType,
      plugin,
      domComponents,
      loopComponent,
      loginform,
      forgetPasswordForm,
      signupForm,
      formComponents,
      nativeformComponents,
      primeUiPlugin,
      panelsManager,
      traitManager,
      dynamicPlugins,
      dynamicWidgets, 
      animationPlugin,
      dynamicComponent,
      customBlockComponent,
      scrollAnimationComponent,
      googleIcons,
      listPlugin,
      blockManager,
      assetManager,
      componentManager,
      parserPostCSS,
    ],
    pluginsOpts: {
      "grapesjs-preset-webpage": {
        blocks: ["tab-contents"],
      },
      "grapesjs-tabs": {},
      [plugin]: {
        /* options */
      },
    },
    canvas: {
      styles: [
        "https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap",
        "https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;700&display=swap",
        "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;900&display=swap",
        "https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap",
        "https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700;900&display=swap",
        "https://fonts.googleapis.com/css?family=Montserrat:400,700&display=swap",
        "https://fonts.googleapis.com/css?family=Plus+Jakarta+Sans:wght@400,700&display=swap",
        "https://fonts.googleapis.com/css?family=Montserrat:400,700&display=swap",
        "https://fonts.googleapis.com/css?family=Plus+Jakarta+Sans:wght@400,700&display=swap",
        ...uniqCssLibs,
      ],
      scripts: [...uniqJsLibs],
    },
    jsInHtml: true,
    storageManager: {
      autoload: true,
      onLoad: async (data: any, opts: any) => {
        try {
          console.log("onLoad");
          store.state.appLoading=true;

          let resp: any;
          // let latestVersion= await designerStore.getAppLatestVersion(appId.value);
          if (designerStore.triedLoadFromFile) {
            resp = designerStore.app;
          } else {
            resp = designerStore.currentBAApplication ? designerStore.currentBAApplication :  await designerStore.getApp(appId.value, undefined)
          }
          appId.value=resp.clonedBAId
          setSchemasAttrs(resp.sources);
          const pageData = resp.config;
          BaApplication.value = {...resp,id:resp.clonedBAId};
          store.state.appLoading=false;

          if (designerStore.triedLoadFromFile) {
            setInterval(() => {
              // do nothing
            }, 1000);
            designerStore.setLoadFromFile(false);
          }
          //we are returning {} if pageData is undefined in order to avoid caching of the previous UI in to newly created BaApp.
          return pageData!==undefined? pageData : {};
        } catch (error) {
          $q.notify({
            type: "negative",
            message: error.response.data.errorMessage,
          });
          if (error.response.data.errorCode == 4040) {
            // const cleanedStr = editor.value.getHtml().replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');

            async function generateThumbnail(
              thumbnail: string,
              defaultThumb: any
            ) {
              if (thumbnail && isUuid(thumbnail)) {
                apiUrlGain.contentService
                  .getImageByThumbnailID(thumbnail)
                  .then((imgData) => {
                    page.value.thumbnail =
                      "data:image/png;base64, " + imgData.data.base64;
                  })
                  .catch((e) => {
                    console.log("Get Thumbnail ", e);
                  });
              } else if (thumbnail) {
                page.value.thumbnail = thumbnail;
              } else {
                page.value.thumbnail = defaultThumb;
              }
            }

            const postBody: any = {
              id: designerStore.currentBAApplication.id,
              baAppName: designerStore.currentBAApplication.baAppName,
              title: designerStore.currentBAApplication.title,
              bbandEntryPageUrl: "string",
              bcastEntryPackageUrl: "string",
              bcastEntryPageUrl: "string",
              thumbnail: "string",
              config: {},
              css: "",
              deleted: false,
              entryPageURL: "string",
              files: designerStore.app.files,
              html: {
                pre: getHeadContent(),
                body: getHTMLBODYV2(),
              },
              metaData: [],
              script: "string",
              sources: [],
              types: {},
              defaults: {},
              version: designerStore.currentBAApplication.version,
              wrapper: [],
              events: [],
              aqIds: [],
              groupIds: [],
            };
            const resp = await designerStore.postApp(postBody);
            $q.notify({
              type: "positive",
              message: "App created",
            });
            console.log("broad", resp.sources);
            setSchemasAttrs(resp.sources);

            const pageData = resp.config;
            BaApplication.value = resp;
            return pageData;
          }
        }
      },
      autosave: false,
      onStore: async (data: any, opts: any) => {
        console.log("onStore", data, opts, editor.value, designerStore.triedSaveToFile);

        editor.value = await editorStore.getEditor();
        const baAppNameSaved =
          designerStore.currentBAApplication.baAppName.replace(/\s/g, "") +
          "_" +
          Date.now();

        // const strippedWrapper = editor.value.getComponents().map((element:any) => {
        //   const { attributes, components } = element;
        //   return { attributes, components };
        // });

        componentsTypeScript.value = {};
        componentsDefaults.value = {};

        generateComponentTypeScript(editor.value.getWrapper());

        const cssapp: any = editor.value.getCss({ avoidProtected: true });

        const rules = editor.value.CssComposer.getAll();
        const allCss = rules.map((rule: any) => rule.toCSS()).join("\n");

        // const newCss = cssapp.replace(
        //   /#(?![\da-fA-F]{6}|[\da-fA-F]{3})(.+?)[\s|\{]/g,
        //   (match: any, id: any) => {
        //     // const newCss = baApp.css.replace(/#(.+?)[\s|\{]/g, (match, id) => {
        //     return `[id^=${id}]{`
        //   }
        // )

        let fileSaveConfig: any = {
          bbandEntryPageUrl: "string",
          bcastEntryPackageUrl: "string",
          bcastEntryPageUrl: "string",
          config: data,
          css: allCss,
          files: designerStore.app.files,
          html: {
            pre: getHeadContent(),
            body: getHTMLBODYV2(),
          },
          script: designerStore.app.script || editor.value.getJs(),
          sources: designerStore.app.sources || [],
          types: componentsTypeScript.value,
          defaults: componentsDefaults.value,
          wrapper: editor.value.getComponents(),
          events: designerStore.app.events || [],
          variables: designerStore.app.variables || [],
          aqIds: [],
          groupIds: [],
        }

        if (designerStore.triedSaveToFile) {
          const fileName = designerStore.modifiedAppName || baAppNameSaved;
          const status = await downloadBAAppConfigAsJson(fileSaveConfig, fileName+'.json');
          if (status) {
            $q.notify({
              type: "positive",
              message: "Applet config download successful",
            });
          } else {
            $q.notify({
              type: "negative",
              message: "Something went wrong while downloading the Applet config",
            });
          }
          designerStore.setSaveToFile(false);
          return;
        }

        const tenantId = useAuthStore().tenantId;
        const postBody: any = {
          ...fileSaveConfig,
          ...{
            id: designerStore.currentBAApplication.id,
            ownerId: tenantId,
            baAppName: baAppNameSaved,
            title: designerStore.currentBAApplication.title,
            deleted: false,
            entryPageURL: `${baAppNameSaved}.html`,
            metaData: [],
            // version: designerStore.app.version,
          }
        };

        try {
          console.log("postBody", postBody);
          store.commit('setSave',false)
          
          await designerStore.putApp(postBody);
          console.log("false")
          // if(!store.state.saveToggle){}
          Notify.create({
            message: "App saved successfully!",
            timeout: 2000,
            position: "bottom",
            color: "green",
            textColor: "white",
            badgeStyle: "display: none",
          });
         
          // store.commit('setSave',true);

          designerStore
            .captureScreenshot(designerStore.currentBAApplication.id)
            .then((screenshotUrl) => {
              const payload = {
                id: designerStore.currentBAApplication.id,
                thumbnail: screenshotUrl,
              };
              return designerStore.patchApp(payload);
            });
        } catch (error: any) {
          $q.notify({
            type: "negative",
            message: error.response.data.errorMessage,
          });
        }
      },
    },
  });

 

 editor.value =editorNonReactive;

 
 editor.value.on('rte:enable',()=>{
    console.log("hello world 222222222222")
  })
  // Assuming you have access to the Rich Text Editor instance
    const rte = editor.value.RichTextEditor;

    // Add the 'bold' functionality
    rte.add('bold', {
      icon: '<b>B</b>',
      attributes: { title: 'Bold' },
      result: (rte: { exec: (arg0: string) => any; }) => rte.exec('bold')
    });
        rte.add('italic', {
      icon: '<i>I</i>',
      attributes: { title: 'Italic' },
      result: (rte: { exec: (arg0: string) => any; }) => rte.exec('italic')
    });
   
 
  

  editor.value.onReady(async (e: any) => {
    console.log("Ready", e);
    editorStore.setEditor(editorNonReactive);
    editorStore.setLayers(editorNonReactive.Layers);
    // editor.value.runCommand("open-layers");
   
    
    
    let blocks = editor.value?.BlockManager?.blocks?.models || [];
    editorStore.addBlockManagerImages(blocks);
    await nextTick();
    
    try{
      editorNonReactive.on('layer:custom', handleCustom);
      editorNonReactive.on('layer:root', handleRootChange);
      const lm = editorNonReactive.LayerManager;
      lm.__trgCustom({ container: layerManagerContainer.value });
    }
    catch(error){
      console.log(error);
    }



    // blockManager(editor.value)
    styleManager(editor.value);
    loadCustomFonts(editor.value);
    loadZoomCommand(editor.value);
    designerInitialized.value = true;
   
   editor.value.on("component:add", (model: any) => {

      if (model.attributes.type === "gjs-row") {
        model.attributes.resizable = true;
        model.setDragMode("absolute");
        console.log("MOdel",model);
        
      }
      
      // this is to enable resize for all the elements other than row inside the column
      else if(model.attributes.type !== "gjs-row" && model.attributes.type !== ""){
        model.attributes.resizable = true;
        model.setDragMode("absolute");
         
        console.log("MOdel",model);
      }
    
    });
    
    editor.value.on("modal", (props: any) => {
      if (props.open) {
        document
          .querySelector("#canvasBlock > div.gjs-mdl-container")
          ?.setAttribute("title", "");
      }
    });
    editor.value.on("component:select", (model: any) => {
      console.log("select");
      activeDrawerContent.value = "styles";
      activeStylesTab.value = "Style Manager";

      const selectedElement: HTMLElement = model.view.el;
      const selectedChild: any =
        selectedElement.childNodes.length > 0
          ? selectedElement.childNodes[0]
          : null;
      if (selectedChild !== null) {
        const selectedCanvas: any =
          selectedChild.childNodes.length > 0
            ? selectedChild.childNodes[0]
            : null;
        if (selectedCanvas !== null && selectedCanvas.tagName === "CANVAS") {
          isChartCanvasSelected.value = true;
          const classList = selectedElement.className;
          if (classList.includes("gjs-selected")) {
            const filteredClasses = classList
              .replace("gjs-selected", "")
              .trim();
            if(filteredClasses.split(" ").length !== 0) {
              var filteredArr = filteredClasses.split(" ");
              filteredArr.forEach((ele_class: string) => {
                selectedCanvas.classList.add(ele_class)
              });
            }
            else {
              selectedCanvas.classList.add(filteredClasses);
            }
          }
          return;
        }
      }
      isChartCanvasSelected.value = false;
      setTimeout(() => {
        setElementSelectedType(model.attributes);
      }, 1);
    });

    editor.value.on("styleable:change", (model: any, property: any) => {
      const value = model.getStyle()[property];
      if (
        property === "height" &&
        isChartCanvasSelected.value &&
        !value.includes("!important")
      ) {
        model.addStyle({ [property]: value + ` !important` });
      }
    });
  });
  designerStore.changeFunctionality();
  editor.value.runCommand("zoom-in-out-canvas", { value: 100 });
  editor.value.on("run:ruler-visibility", (): void => {
    console.log(toggleRuler.value);
    toggleRuler.value = !toggleRuler.value;
  });
 // search plagiun hide and show
  // designerStore.addSerchFilterToPlugins();
});


here is my code

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

Answers (2)

artfJune 16, 2024

From the official demo it looks to work as expected, so I guess it's related to your custom logic. Please provide a minimal reproducible demo of the issue.

ClaudeCodeMay 17, 2026

Thanks for reporting this, @adarshsingh197.

The error **error: any) { ** occurs when ProseMirror attempts to access properties before the component lifecycle is fully initialized. This is a common race condition in GrapesJS.

Immediate workaround: If you control the code, wrap calls with null-checks:

if (component && typeof component.getPlugins === 'function') {
  // your code
}

Root cause analysis: The ProseMirror doesn't validate state before invoking getPlugins(), getPlugins(). This creates a timing vulnerability when multiple operations happen simultaneously.

Next steps:

  1. Try the null-guard workaround above
  2. Update to the latest GrapesJS — many race conditions have been fixed
  3. If this persists, share your exact reproduction steps with the team
  4. Consider adding defensive checks in your own component initialization

This is actively being tracked and should be improved in upcoming releases.

Related Questions and Answers

Continue research with similar issue discussions.

Paid Plugins That Match This Issue

Curated by issue keywords and label relevance to help you ship faster.

View all plugins

Loading paid plugin recommendations...

Free option

Check the open-source GrapesJS plugins on GitHub or run a quick search in our free catalog.

Browse free plugins →
Premium option

Premium plugins ship with support, regular updates, and production-ready features — save days of integration work.

Browse premium plugins →

Browse Plugin Categories

Jump directly to plugin category pages on the marketplace.