Issue #5942💬 RespondidoAbierto el 12 de junio de 2024por adarshsingh197Reacciones 0

BUg en RichTextEditor

Respuesta rápidapor artf

Por la demo oficial parece funcionar como se espera, así que supongo que está relacionado con tu lógica personalizada. Por favor, proporcionad una demo mínima y reproducible del número.

Lee la respuesta completa abajo ↓

Pregunta

Versión GrapesJS

  • Confirmo que se debe usar la última versión de GrapesJS

¿Qué navegador usas?

Chrome

Enlace de demo reproducible

Fragmento de códigoTEXT
He dado el código en los problemas

### Describe el bicho

[Emisión de pantalla del 24-06-12 15:09:58 IST.webm](https://github.com/GrapesJS/grapesjs/assets/130237200/d5bbb21e-2e44-4564-a407-4768776c1cd8)

El problema es que justo después de aplicar cualquier letra en negrita o cursiva, desaparece en cuanto hago clic en algún sitio.
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);
  Obtén Dependencias Primarias para inicializar el editor con
  const baAppDependencies = await designerStore.getDependencies();

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

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

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)
  //   }
  // })
  sea uniqCssLibs = uniq(cssLibs);
  sea 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({
    Altura: "100%",
    contenedor: "#canvasBlock",
    fromElement: cierto,
    canvasCss: '
        .gjs-selected {
          Esquema: 2 px sólidos #c200fdb8 ¡importante;
        }
   `,
    layerManager: {
       Personalizado: Verdadero 
      },
    richTextEditor:{
      stylePrefijo: 'rte-',
      adjustToolbar: cierto,
      Acciones: ['Negrita', 'Cursiva', 'Subrayado', 'Tachado', 'Enlace', 'Envolver'],
      Personalizado: Cierto,
    },
    selectorManager: {
      appendTo: "#selectors",
      Esto es hacer que grapesjs use los estilos de clase como prioridad y cambie los estilos de clase en lugar del id proporcionado por grapesjs
      componentFirst: cierto,
    },
    StyleManager: {
      appendTo: "#styles",
    },
    ColorPicker: {
      appendTo: "padre",
      mostrarButtons: false,
      mostrarPaletaSelection: false,
      mostrar: función (esto: cualquiera) {
        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";
        }
      },
      desplazamiento: { top: 30, izquierda: -180 },
    },
    traitManager: {
      appendTo: "#traits",
    },
    deviceManager: {
      por defecto: "Escritorio",
      Dispositivos: [
        {
          id: "escritorio",
          nombre: "Escritorio",
          ancho: "",
          widthMedia: "",
        },
        {
          id: "tablet",
          nombre: "Tablet",
          Ancho: "768px",
          widthMedia: "768px",
          Altura: "1024px",
        },
        {
          id: "móvil",
          nombre: "Mobile",
          Ancho: "360px",
          widthMedia: "360px",
        },
      ],
    },
    Plugins: [
      Conceptos básicos,
      EstiloAntecedentes,
      Bloques de viento de cola,
      "página web de presets grapesjs",
      "uvasjs-tabs",
      StyleFilter,
      pluginRulers,
      customType,
      plugin,
      domComponents,
      loopComponent,
      loginform,
      OlvidaFormularioContraseña,
      FormaciónRegistro,
      formComponents,
      nativeformComponents,
      primeUiPlugin,
      PanelesManager,
      traitManager,
      dynamicPlugins,
      dynamicWidgets, 
      animationPlugin,
      dynamicComponent,
      customBlockComponent,
      scrollAnimationComponent,
      googleIcons,
      listPlugin,
      BlockManager,
      Gestor de Activos,
      componentManager,
      ParserPostCSS,
    ],
    pluginsOpts: {
      "grapesjs-preset-webpage": {
        bloques: ["contenido de pestañas"],
      },
      "grapesjs-tabs": {},
      [plugin]: {
        /* opciones */
      },
    },
    lienzo: {
      Estilos: [
        "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,
      ],
      Guiones: [... uniqJsLibs],
    },
    jsInHtml: cierto,
    storageManager: {
      autoload: cierto,
      onLoad: async (data: any, opts: any) => {
        try {
          console.log("onLoad");
          store.state.appLoading=true;

Sea resp: cualquiera;
          let latestVersion= await designerStore.getAppLatestVersion(appId.value);
          if (designerStore.triedLoadFromFile) {
            resp = designerStore.app;
          } else {
            resp = designerStore.currentBAApplication ? designerStore.currentBAApplication : await designerStore.getApp(appId.value, indefinited)
          }
          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(() => {
              No hagas nada
            }, 1000);
            designerStore.setLoadFromFile(false);
          }
          estamos devolvendo {} si pageData no está definido para evitar el almacenamiento en caché de la interfaz anterior en BaApp recién creada.
          return pageData!==indefinido? pageData : {};
        } catch (error) {
          $q.notify({
            Tipo: "Negativo",
            mensaje: error.response.data.errorMessage,
          });
          if (error.response.data.errorCode == 4040) {
            const cleanedStr = editor.value.getHtml().replace(/<script\b[^<]*(?:(?! <\/script>)<[^<]*)*<\/script>/gi, '');

función asíncrona generarMiniatura(
              miniatura: cuerda,
              defaultThumb: cualquiera
            ) {
              if (miniatura && isUuid(miniatura)) {
                apiUrlGain.contentService
                  .getImageByThumbnailID(miniatura)
                  .then((imgData) => {
                    página.valor.miniatura =
                      "Datos:imagen/PNG; base64, " + imgData.data.base64;
                  })
                  .catch((e) => {
                    console.log("Conseguir miniatura", e);
                  });
              } si no (miniatura) {
                página.valor.miniatura = miniatura;
              } else {
                página.valor.miniatura = defaultThumb;
              }
            }

const postBody: any = {
              id: designerStore.currentBAApplication.id,
              baAppName: designerStore.currentBAApplication.baAppName,
              título: designerStore.currentBAApplication.title,
              bbandEntryPageUrl: "string",
              bcastEntryPackageUrl: "string",
              bcastEntryPageUrl: "string",
              miniatura: "cuerda",
              config: {},
              CSS: "",
              eliminado: falso,
              entryPageURL: "string",
              archivos: designerStore.app.archivos,
              html: {
                pre: getHeadContent(),
                cuerpo: getHTMLBODYV2(),
              },
              metadatos: [],
              script: "string",
              Fuentes: [],
              tipos: {},
              predeterminados: {},
              versión: designerStore.currentBAApplication.version,
              envoltorio: [],
              Eventos: [],
              aqIds: [],
              groupIds: [],
            };
            const resp = await designerStore.postApp(postBody);
            $q.notify({
              Tipo: "positivo",
              mensaje: "App creada",
            });
            console.log("amplia", respetando fuentes);
            setSchemasAttrs (resp.sources);

const pageData = resp.config;
            BaApplication.value = resp;
            devolver páginaData;
          }
        }
      },
      guardado automático: falso,
      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, "") +
          "_" +
          Fecha.ahora();

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

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

generateComponentTypeScript(editor.value.getWrapper());

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

reglas const = 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, (coincidencia, id) => {
        return '[id^=${id}]{'
        //   }
        // )

let fileSaveConfig: any = {
          bbandEntryPageUrl: "string",
          bcastEntryPackageUrl: "string",
          bcastEntryPageUrl: "string",
          config: data,
          CSS: allCss,
          archivos: designerStore.app.archivos,
          html: {
            pre: getHeadContent(),
            cuerpo: getHTMLBODYV2(),
          },
          script: designerStore.app.script || editor.value.getJs(),
          Fuentes: designerStore.app.fuentes || [],
          tipos: componentsTypeScript.value,
          defaults: componentsDefaults.value,
          envoltorio: editor.value.getComponents(),
          eventos: designerStore.app.eventos || [],
          variables: designerStore.app.variables || [],
          aqIds: [],
          groupIds: [],
        }

if (designerStore.triedSaveToFile) {
          const fileName = designerStore.modifiedAppName || baAppNameSased;
          const status = await downloadBAAppConfigAsJson(fileSaveConfig, fileName+'.json');
          si (estado) {
            $q.notify({
              Tipo: "positivo",
              mensaje: "Descarga de configuración del applet exitosa",
            });
          } else {
            $q.notify({
              Tipo: "Negativo",
              mensaje: "Algo salió mal al descargar la configuración del Applet",
            });
          }
          designerStore.setSaveToFile(false);
          regresar;
        }

const tenantId = useAuthStore().tenantId;
        const postBody: any = {
          ... fileSaveConfig,
          ... {
            id: designerStore.currentBAApplication.id,
            propietarioId: tenantId,
            baAppName: baAppNameSaved,
            título: designerStore.currentBAApplication.title,
            eliminado: falso,
            entryPageURL: '${baAppNameSaved}.html',
            metadatos: [],
            versión: designerStore.app.version,
          }
        };

try {
          console.log("postBody", postBody);
          store.commit('setSave',false)
          
await designerStore.putApp(postBody);
          console.log("falso")
          if(!store.state.saveToggle){}
          Notify.create({
            mensaje: "¡App guardada correctamente!",
            Tiempo muerto: 2000,
            posición: "bottom",
            color: "verde",
            textoColor: "blanco",
            DistintivoEstilo: "display: none",
          });
         
store.commit('setSave', true);

designerStore
            .capturaCapturaCaptura(designerStore.currentBAApplication.id)
            .then((screenshotUrl) => {
              Carga útil const = {
                id: designerStore.currentBAApplication.id,
                miniatura: screenshotUrl,
              };
              return designerStore.patchApp(payload);
            });
        } catch (error: any) {
          $q.notify({
            Tipo: "Negativo",
            mensaje: error.response.data.errorMessage,
          });
        }
      },
    },
  });

editor.value =editorNonReactive;

editor.value.on('rte:enable',()=>{
    console.log("hola mundo 222222222222")
  })
  Suponiendo que tengas acceso a la instancia del Editor de Texto Enriquecido
    const rte = editor.value.RichTextEditor;

Añadir la funcionalidad 'en negrita'
    rte.add('bold', {
      icono: '<b>B</b>',
      atributos: { título: 'Bold' },
      Resultado: (RTE: { Exec: (Arg0: String) => any; }) => rte.exec('bold')
    });
        rte.add('italic', {
      icono: '<i>Yo</i>',
      atributos: { título: 'cursiva' },
      Resultado: (RTE: { Exec: (Arg0: String) => cualquiera; }) => rte.exec('cursiva')
    });
   
editor.value.onReady(async (e: any) => {
    console.log ("Listo", e);
    editorStore.setEditor (editorNonReactive);
    editorStore.setLayers (editorNonReactive.Layers);
    editor.value.runCommand("open-layers");
   
Lets Blocks = valor.editor?. ¿BlockManager? ¿bloqueos?. Modelos || [];
    editorStore.addBlockManagerImágenes(bloques);
    esperar nextTick();
    
try{
      editorNonReactive.on('layer:custom', handleCustom);
      editorNonReactive.on('layer:root', handleRootChange);
      const lm = editorNonReactive.LayerManager;
      lm.__trgCustom({ contenedor: 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) => {

si (model.atributos.tipo === "gjs-row") {
        model.attributes.resizable = true;
        model.setDragMode("absoluto");
        console.log("MOdel",modelo);
        
}
      
Esto permite redimensionar para todos los elementos excepto la fila dentro de la columna
      else if(model.attributes.type !== "gjs-row" && model.attributes.type !== ""){
        model.attributes.resizable = true;
        model.setDragMode("absoluto");
         
console.log("MOdel",modelo);
      }
    
});
    
editor.value.on("modal", (props: any) => {
      si (props.open) {
        documento
          .querySelector("#canvasBlock > div.gjs-mdl-container")
          ?. setAttribute("título", "");
      }
    });
    editor.value.on("component:select", (model: any) => {
      console.log("select");
      activeDrawerContent.value = "estilos";
      activeStylesTab.value = "Gestor de estilos";

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);
            }
          }
          regresar;
        }
      }
      isChartCanvasSelected.value = false;
      setTimeout(() => {
        setElementSelectType(model.attributes);
      }, 1);
    });

editor.value.on("styleable:change", (model: any, property: any) => {
      valor const = model.getStyle()[propiedad];
      si (
        Propiedad === "altura" &&
        isChartCanvasSelected.value &&
        !value.includes("!importante")
      ) {
        model.addStyle({ [propiedad]: 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;
  });
 Buscar plagiun Esconder y mostrar
  designerStore.addSerchFilterToPlugins();
});

Aquí está mi código

### Código de conducta

- [X] Acepto seguir el Código de Conducta de este proyecto

Respuestas (2)

artf16 de junio de 2024

Por la demo oficial parece funcionar como se espera, así que supongo que está relacionado con tu lógica personalizada. Por favor, proporcionad una demo mínima y reproducible del número.

ClaudeCode17 de mayo de 2026

Gracias por informar de esto, @adarshsingh197.

El error **error: any) { ** ocurre cuando ProseMirror intenta acceder a propiedades antes de que el ciclo de vida del componente esté completamente inicializado. Esta es una condición común de raza en GrapesJS.

Solución inmediata: Si controlas el código, envuelve las llamadas con comprobaciones nulas: '''javascript if (component && typeof component.getPlugins === 'función') { tu código }


**Análisis de causa raíz:**
ProseMirror no valida el estado antes de invocar 'getPlugins()', 'getPlugins()'. Esto crea una vulnerabilidad temporal cuando se realizan múltiples operaciones simultáneamente.

**Próximos pasos:**
1. Prueba la solución alternativa del nulo de guardia anterior
2. Actualización a la última versión de GrapesJS — muchas condiciones de carrera han sido corregidas
3. Si esto persiste, comparte tus pasos exactos de reproducción con el equipo
4. Considera añadir comprobaciones defensivas en la inicialización de tu propio componente

Esto se está monitorizando activamente y debería mejorarse en próximas versiones.

Preguntas y respuestas relacionadas

Continúa investigando con debates sobre temas similares.

Plugins de pago que cumplen con este problema

Seleccionado por temas clave y relevancia de etiquetas para ayudarte a enviar más rápido.

Ver todos los plugins

Cargando recomendaciones de plugins de pago...

Opción gratuita

Consulta los plugins de código abierto de GrapesJS en GitHub O haz una búsqueda rápida en nuestro catálogo gratuito.

Explora plugins gratuitos →
Opción premium

Los plugins premium incluyen soporte, actualizaciones regulares y funciones listas para producción — ahorrando días de trabajo de integración.

Explora plugins premium →

Explorar categorías de plugins

Ve directamente a las páginas de categorías de plugins en el marketplace.