Issue #3512💬 RespondidoAbierto el 2 de junio de 2021por Palash-MandalReacciones 1

Los elementos hijos no se pueden arrastrar, se pueden soltar y se pueden resaltar. también el CSS de todos los elementos hijos que no se insertan en la sección de estilo mientras se desarrolla un componente banner en un plugin

Respuesta rápidapor artf1

En la definición de tu modelo component, usas 'content: config.template' pero debería ser 'components: config.template'

Lee la respuesta completa abajo ↓

Pregunta

Hola @artf,

Estoy intentando desarrollar un plugin sencillo para banners que incluya un encabezado, un subtítulo y un enlace. y cada elemento puede ser arrastrable, soltable y resaltado, pero según el código inferior alguna parte del código no funciona. Por favor, ayudadme

! imagen

Fragmento de códigoTEXT
solo la clase banner CSS empujada, pero otro no ir en la sección de CSS
! [imagen](https://user-images.githubusercontent.com/17553816/120482976-4c426b80-c3cf-11eb-816a-689cf7d8b608.png)

------------ index.js----------

! [imagen](https://user-images.githubusercontent.com/17553816/120481948-46985600-c3ce-11eb-8239-7f1d28242320.png)

importar loadComponents desde './components';
importar loadBlocks desde './blocks';

Export default (editor, opts = {}) => {
    const options = {... {
            Opciones por defecto
            bannerBlock: {},
            claseEstandarte: "estandarte",
            Plantilla: '
            <div><h2 class="banner__heading">Encabezado del banner</h2></div>
            <div> <h3 class="banner__subheading">Encabezado del subtítulo</h3></div>
            <div> <a class="banner__link" href="#">Empieza</a></div>
            `,
            estilo: '
          .banner {
            Color de fondo: #c9efff;
            Repetición de fondo: sin repetición;
            Posición de antecedentes: Centro central;
            Tamaño de fondo: Portada;
            posición: relativa;
            acolchado: 80px 20px;
            radio de frontera:0;
            pantalla: flex;
            alinear-ítems: centro;
            justificar-contenido: centro;
            dirección flexible: columna;
            Altura mínima: 370px;
          }

.banner__heading {
            Peso de fuente: 700;
            color: #333;
            tamaño de fuente: 40px;
            margen inferior: 16px;
            text-align: center;
            altura de línea: 1,2;
          }
          .banner__subheading {
            tamaño de fuente: 26px;
            text-align: center;
            Margen arriba: 16px;
            Peso de fuente: 500;
            color: #333;
          }
          .banner__link {
            pantalla: bloqueo;
            acolchado: 10px 20px;
            Antecedentes: ninguno;
            margen: 5px;
            tamaño de fuente: 14px;
            color: #fff !importante;
            Color de fondo: #005A9E;
            transición: todos los 0.2 se acomodan con facilidad;
            Radio de frontera: 4px;
            Peso de fuente: 500;
            frontera: ¡ninguna importante!
            text-align: center;
            Ancho mínimo: 160px;
            Espaciado entre letras: 1px;
          }
        `,
        },
        ... OPS
    };

Añadir componentes
    loadComponents (editor, opciones);
    Añadir bloques
    loadBlocks (editor, opciones);
};

------------- Bloquear JS-------------
! [imagen](https://user-images.githubusercontent.com/17553816/120482131-70ea1380-c3ce-11eb-9624-700c200e45dc.png)

export default (editor, config = {}) => {
    const bm = editor. Jefe de bloque;
    const bannerBlock = config.bannerBlock;
    const style = config.style;
    Tipo const = "bannerblock";
    const content = '<div data-gjs-type="${type}"></div>
  ${estilo? '<style>${estilo}</style>' : ""}';

bannerBlock & &
  bm.add(type, {
    etiqueta: "Banner",
    categoría: 'Componentes',
    Atributos: { Clase: "FA FA-List" },
    activar: 1,
    select: 1,
    removible: cierto, // No se puede quitar
    arrastrable: cierto, // No se puede mover
    copiable: true, // Desactivar copiar/pegar
    contenido,
    ... bannerBlock,
  });
}

----------- Componente JS------------
! [imagen](https://user-images.githubusercontent.com/17553816/120482266-9414c300-c3ce-11eb-9132-2dad65d2d392.png)
export default (editor, config = {}) => {
    const domc = editor. DomComponents;
    Tipo const = 'bannerblock';
    const classbanner = config.classBanner;
    domc.addType(type, {
        modelo: {
            Predeterminados: {
                Atributos: { Clase: Estandarte de Clase },
                contenido: config.template,
                Arrastrable: Cierto,
                Droppable: Cierto,
                Copiable: Cierto,
                removible: cierto,
                Seleccionable: Cierto,
            },
        },
        Vista: {

},
    });
};

Respuestas (3)

artf6 de julio de 2021

En la definición de tu modelo component, usas 'content: config.template' pero debería ser 'components: config.template'

Palash-Mandal15 de junio de 2021

Hola @artf,

He buscado la solución, pero hay algunos problemas con los componentes. El componente hijo H2,H3 y Link presionaba para todos los demás componentes. Si suelto un componente de columna, esos elementos hijos también se empujan dentro de ahí. ¿Cómo puedo restringir el conflicto? A continuación, aquí está el ejemplo del codepen donde hice una demo. https://codepen.io/coderdesigners/full/XWMoNbZ

Si dejo una columna, la de abajo sube. Lo cual no es lo esperado. Debería ser así, según el valor por defecto. ! imagen

Cuando suelta el componente de banner. ! imagen

Por favor, por favor, ayúdame con esto. Sé que he cometido un error tonto que no puedo entender. Siento molestarte.

Gracias de antemano.

Y aquí están los detalles de mi plugin

! imagen exportar default { Objetar para extender el bloque de banner por defecto, por ejemplo, '{ etiqueta: 'Banner', atributos: { ... } } }` Pasa un valor falso para evitar añadir el bloque bannerBlock: {},

Objetar para extender las propiedades predeterminadas del banner, por ejemplo, '{ nombre: 'Mi Estandarte', droppable: false, ... }` bannerCreateProps: {},

Objeto para extender las propiedades predeterminadas del encabezado bannerHeadingProps: {},

Objetar para extender las propiedades predeterminadas de Subtítulo bannerSubHeadingProps: {},

Object para extender las propiedades predeterminadas del botón bannerButtonProps: {},

ID de componente de las tabulaciones tipoBanner: 'banner',

Identificador de componente TabContainer typeBannerHeading: 'Banner-Heading',

ID de componente de tabulación typeBannerSubHeading: 'Banner-Subheading',

ID de componente TabContent tipoBannerButton: 'Banner-Button',

Clase por defecto para usar en el Banner claseBannerContenedor: 'zslbanner',

Clase por defecto para usar en el Banner Encabezado de clase: 'zslbanner__heading',

Fragmento de códigoTEXT
Clase por defecto para usar en el Banner
    Subtítulo: 'zslbanner__subheading',

Clase por defecto para usar en el Banner
    claseLink: 'zslbanner__link',

Estilo predeterminado para las pestañas
    style: (config) => '
        .${config.classBannerContainer} {
        familia tipográfica: 'Montserrat', Arial, sans-serif;
        Color de fondo: #c9efff;
        Repetición de fondo: sin repetición;
        Posición de antecedentes: Centro central;
        Tamaño de fondo: Portada;
        posición: relativa;
        acolchado: 80px 20px;
        radio de frontera:0;
        pantalla: flex;
        alinear-ítems: centro;
        justificar-contenido: centro;
        dirección flexible: columna;
        Altura mínima: 370px;
      }
      .${config.classHeading} {
        Peso de fuente: 700;
        color: #333;
        tamaño de fuente: 40px;
        marje:0;
        margen inferior: 16px;
        text-align: center;
        altura de línea: 1,2;
      }
      .${config.classSubHeading} {
        tamaño de fuente: 26px;
        text-align: center;
        marje:0;
        margen inferior: 16px;
        Peso de fuente: 500;
        color: #333;
      }
      .${config.classLink} {
        pantalla: bloqueo;
        acolchado: 10px 20px;
        Antecedentes: ninguno;
        Margen: 20px 5px 5px 0 5px;
        tamaño de fuente: 14px;
        color: #fff !importante;
        Color de fondo: #005A9E;
        transición: todos los 0.2 se acomodan con facilidad;
        Radio de frontera: 4px;
        Peso de fuente: 500;
        frontera: ¡ninguna importante!
        text-align: center;
        Ancho mínimo: 160px;
        Espaciado entre letras: 1px;
      }
      .${config.classLink}:hover {
        Color de fondo: #037ede;  
       }
 `
}
! [imagen](https://user-images.githubusercontent.com/17553816/122001932-b6471180-cdce-11eb-822f-f81f75af60ef.png)

! [imagen](https://user-images.githubusercontent.com/17553816/122001962-c3640080-cdce-11eb-877a-56108e2adbba.png)

! [imagen](https://user-images.githubusercontent.com/17553816/122001992-cfe85900-cdce-11eb-8b1b-6c3fe60ee60f.png)

**Subheading.js**
rol de const export = 'Subtítulo';

Export default (DC, config) => {

dc.addType(config.typeBannerSubHeading, {
        modelo: {
            isComponent: function(t) {
                return ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'p'].indexOf(t.tagName) >= 0
            },
            Predeterminados: {
                nombre: 'Subtítulo',
                Arrastrable: Cierto,
                Copiable: Cierto,
                removible: cierto,
                Resaltable: Cierto,
                Editable: Cierto,
                atributos: { rol },
                clases: config.classSubHeading,
                etiquetaNombre: 'h3',
                contenido: 'Subtítulo',
                Rasgos: [
                    'id',
                    'título',
                    {
                        tipo: "select",
                        Opciones: [{
                            valor: "h1",
                            nombre: "Rumbo 1"
                        }, {
                            valor: "h2",
                            nombre: "Rumbo 2"
                        }, {
                            valor: "h3",
                            nombre: "Rumbo 3"
                        }, {
                            valor: "h4",
                            nombre: "Rumbo 4"
                        }, {
                            valor: "h5",
                            nombre: "Rumbo 5"
                        }, {
                            valor: "h6",
                            nombre: "Rumbo 6"
                        }, {
                            valor: "p",
                            nombre: "Paragraph"
                        }],
                        etiqueta: "Encabezado o párrafo",
                        nombre: "tagName",
                        cambioprop: 1
                    }
                ],
                ... config.bannerSubHeadingProps
            },
        },
        Vista: {
            eventos: {
                dblclick: 'onActive',
                Focusout: 'onDisable',
            },
            onActive() {
                this.el.contentEditable = true;
            },
            onDisable() {
                const { el, model } = this;
                el.contentEditable = false;
                model.set('content', el.innerHTML)
            },
        }
    });
}
! [imagen](https://user-images.githubusercontent.com/17553816/122002066-e55d8300-cdce-11eb-8216-e91659cfaaad.png)

! [imagen](https://user-images.githubusercontent.com/17553816/122002133-fad2ad00-cdce-11eb-9aab-d3f02d37c78b.png)

**Heading.js**

export const role = 'Encabezado';

Export default (DC, config) => {

dc.addType(config.typeBannerHeading, {
        modelo: {
            isComponent: function(t) {
                return ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'p'].indexOf(t.tagName) >= 0
            },
            Predeterminados: {
                nombre: 'Heading',
                Arrastrable: Cierto,
                Copiable: Cierto,
                removible: cierto,
                Resaltable: Cierto,
                Editable: Cierto,
                atributos: { rol },
                clases: config.classHeading,
                etiquetaNombre: 'h2',
                componentes: 'Rumbo de dirección',
                Rasgos: [
                    'id',
                    'título',
                    {
                        tipo: "select",
                        Opciones: [{
                            valor: "h1",
                            nombre: "Rumbo 1"
                        }, {
                            valor: "h2",
                            nombre: "Rumbo 2"
                        }, {
                            valor: "h3",
                            nombre: "Rumbo 3"
                        }, {
                            valor: "h4",
                            nombre: "Rumbo 4"
                        }, {
                            valor: "h5",
                            nombre: "Rumbo 5"
                        }, {
                            valor: "h6",
                            nombre: "Rumbo 6"
                        }, {
                            valor: "p",
                            nombre: "Paragraph"
                        }],
                        etiqueta: "Encabezado o párrafo",
                        nombre: "tagName",
                        cambioprop: 1
                    }
                ],
                ... config.bannerHeadingProps
            },
        },
        Vista: {
            eventos: {
                dblclick: 'onActive',
                Focusout: 'onDisable',
            },
            onActive() {
                this.el.contentEditable = true;
            },
            onDisable() {
                const { el, model } = this;
                el.contentEditable = false;
                model.set('content', el.innerHTML)
            },
        }
    });
}

**Button.js**
Export default (DC, config) => {

dc.addType(config.typeBannerButton, {
        modelo: {
            extiende: 'enlace',
            Predeterminados: {
                Editable: Cierto,
                nombre: 'Button',
                Arrastrable: Cierto,
                Copiable: Cierto,
                removible: cierto,
                Resaltable: Cierto,
                atributos: {
                    HFF: '#'
                },
                clases: config.classLink,
                contenido: 'Empezar',
                etiquetaNombre: 'a',
                Rasgos: [
                    'id',
                    'título',
                    'href',
                    'objetivo',
                ],
                ... config.bannerButtonProps
            },
        },
        Vista: {
            eventos: {
                dblclick: 'onActive',
                Focusout: 'onDisable',
            },
            onActive() {
                this.el.contentEditable = true;
            },
            onDisable() {
                const { el, model } = this;
                el.contentEditable = false;
                model.set('content', el.innerHTML)
            },
        }
    });
}

**BannerCreate.js**
Export default (DC, {
    typeBanner,
    typeBannerHeading,
    typeBannerSubHeading,
    typeBannerButton,
    estilo,
    ... Configuración
}) => {
    const type = config.typeBanner;
    dc.addType(type, {
        modelo: {
            Predeterminados: {
                nombre: 'Banner',
                clases: config.classBannerContainer,
                Componentes: [
                    { tipo: tipoEncabezadoBanner },
                    { tipo: tipoSubencabezadoBanner },
                    { tipo: tipoBannerButton },
                    style && '<style>${style(config)}</style>'
                ],
                ... config.bannerCreateProps
            },
        },
    });
}

Por favor, ayudadme.
ClaudeCode17 de mayo de 2026

Gracias por informar de esto, @Palash-Mandal.

Buena pregunta sobre elementos hijos que no se pueden arrastrar y que se pueden resaltar soltando. también el CSS de todos los elementos hijos que no se han subido en la sección de estilo mientras se desarrolla un componente banner en AS plugin. El enfoque recomendado con ProseMirror es usar la API orientada a eventos.

Empieza aquí:

  1. Consulta la documentación de GrapesJS de tu módulo específico
  2. Busca el método del oyente de eventos 'on()'
  3. La mayoría de las operaciones se pueden realizar escuchando eventos del editor y de los componentes

Patrones comunes: '''javascript Prestad atención a los cambios editor.on('Change', () => console.log('Something Changed'));

Ciclo de vida de los componentes editor.on('component:mount', (c) => console.log('component ready', c)); editor.on('component:update', (c) => console.log('component updated', c));


**Si sigues atascado:**
- Compartir una reproducción mínima de CodeSandbox
- Incluye lo que ya has probado
- Menciona tu versión GrapesJS
- ¡La comunidad está aquí para ayudar!

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 →

Tutoriales relacionados

Guías detalladas sobre el mismo tema.

Todos los tutoriales →

Explorar categorías de plugins

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