Issue #5942Opened June 12, 2024by adarshsingh1970 reactions

BUg in RichTextEditor

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

Code SnippetTEXT
### 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 (1)

artfJune 16, 20240 reactions

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.

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...

Browse Plugin Categories

Jump directly to plugin category pages on the marketplace.