BUg en RichTextEditor
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
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 proyectoRespuestas (2)
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.
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.
Issue #5457
Editor congelado en loadProjectData 0.21.7
Versión GrapesJS [X] Confirmo que se debe usar la última versión de GrapesJS ¿Qué navegador usas? Chrome 117.0 Enlace de demo reproducible...
Issue #6152
El CSS añadido mediante código personalizado persiste después de que se elimina un componente de código personalizado
Versión GrapesJS [X] Confirmo que se debe usar la última versión de GrapesJS ¿Qué navegador usas? Cualquiera Enlace de demo reproducible ht...
Issue #5720
Propagar componentes por defecto puede romper capas cuando se introducen los comentarios hijos
Versión GrapesJS [X] Confirmo que se debe usar la última versión de GrapesJS ¿Qué navegador usas? Chrome v122 Enlace de demo reproducible h...
Issue #5263
¿Eliminar un componente sin eliminar su estilo correspondiente, añadiendo nuevos componentes más adelante causará conflictos de estilo?
Versión GrapesJS [X] Confirmo que se debe usar la última versión de GrapesJS ¿Qué navegador usas? última versión de Chrome Enlace de demo r...
Plugins de pago que cumplen con este problema
Seleccionado por temas clave y relevancia de etiquetas para ayudarte a enviar más rápido.
Cargando recomendaciones de plugins de pago...
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 →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.