Una de las características de Pulse Editor es que podemos configurar cuáles botones de todos los que nos provee el editor queremos usar. Pero no solo podemos usar esos botones, también podemos crear botones propios.
Si ya vieron como implementar Pulse Editor pueden luego crear botones propios. Vamos a ver cómo hacer esto.
Botón base
El editor nos provee de un editor base que podemos usar. Este es un simple wrapper de la etiqueta <button />
para colocar siempre ciertos atributos. Pueden acceder a este componente simplemente con un import
.
import { Base } from 'pulse-editor/buttons`
Luego ya pueden hacer render y pasarle lo que quieran, al final del día es un botón, lo únicos props
que deben pasar sí o sí son:
children
=> los componentes hijos, pueden ser lo que quieran.name
=> el nombre del botón, en el componente<Bold />
este esbold
, así que pueden darse una idea.onClick
=> la función que se va a ejecutar cuando el usuario haga click sobre el botón.
El botón además define un className
por defecto llamado PulseEditor-button
(que pueden cambiar si pasan su propio className
), define disabled
como false
(también sobre escribible) y fuerza que el type
sea button
. Porque por defecto las etiquetas <button />
son de tipo submit
. Así evitamos que cualquier botón del editor envíe un posible formulario que envuelva al editor.
Creando el botón
Vamos a crear un botón para la aplicación de escritorio Pulse. Este botón va a encargarse de crear un nuevo archivo en el editor. Como Pulse usa Next.js no necesitamos importar React
, pero como vamos a crear un componente de clase entonces sí vamos a importar Component
.
import { Component } from 'react';
Luego importamos el botón base como vimos antes y vamos a iniciar a crear nuestro componente.
import { Component } from 'react'
import { Base } from 'pulse-editor/buttons'
export default class NewButton extends Component {
render = () => (
<Base onClick={this.handleClick} name='new'>
<span title='New file [CMD+N]'>
New
</span>
</Base>
)
}
Con eso nuestro botón ya hace render de un botón normal de nuestro editor y definimos dos cosas extras sobre este: la primera es que tenemos que crear un handleClick
y la segunda es que vamos a usar el atajo de teclado cmd+n
en Mac o ctrl+n
en Windows y Linux.
Definiendo el atajo de teclado
Vamos primero a definir nuestro atajo de teclado. Para eso necesitamos acceder a dos funciones desde el contexto de React a los cuales vamos a tener acceso siempre que nuestro botón se renderice dentro del componente <Editor />
.
import { Component } from 'react'
import { Base } from 'pulse-editor/buttons'
import { func } from 'prop-types' // importamos el prop-type func para definir funciones
export default class NewButton extends Component {
static contextTypes = {
setShortcut: func.isRequired, // esta función nos permite definir un shortcut
removeShortcut: func.isRequired, // esta función nos permite quitar un shortcut
}
render = () => (
<Base onClick={this.handleClick} name='new'>
<span title='New file [CMD+N]'>
New
</span>
</Base>
)
}
Con eso tenemos acceso a la función. Ahora vamos a usar estas funciones, la primera se usa en componentDidMount
para agregar nuestro atajo de teclado y la segunda se usa en componentWillUnmount
cuando el componente se vaya a desmontar.
import { Component } from 'react'
import { Base } from 'pulse-editor/buttons'
import { func } from 'prop-types'
import isMac from 'pulse-editor/built/utils/is-mac' // importamos un util interno de pulse-editor para saber si estamos en Mac
export default class NewButton extends Component {
static contextTypes = {
setShortcut: func.isRequired,
removeShortcut: func.isRequired,
}
componentDidMount() {
this.context.setShortcut({
ctrlKey: !isMac(), // si no estamos en Mac usamos la tecla control
metaKey: isMac(), // si estamos en Mac usamos la tecla meta (command)
altKey: false, // no vamos a usar la tecla alt
shiftKey: false, // no vamos a usar la tecla shift
keyName: 'n', // vamos a usar la tecla N (cmd+n o ctrl+n)
updater: selected => selected, // nuestra función updater (más abajo vemos que es)
handler: event => event.selection, // nuestro función handler (más abajo vemos que es)
})
}
componentWillUnmount() {
// eliminamos el atajo de teclado que usa la tecla N al desmontar el componente
this.context.removeShortcut({ keyName: 'n' })
}
render = () => (
<Base onClick={this.handleClick} name='new'>
<span title='New file [CMD+N]'>
New
</span>
</Base>
)
}
Así configuramos atajos de teclados. Como vemos al momento de definir uno indicamos que atajo se va a usar, si vamos a usar las teclas ctrl
, meta
, alt
y shift
y otra tecla. Le tecla meta
puede significar cmd
(command) en Mac o win
(windows) en teclados no-mac en Windows y Linux.
Luego definimos dos funciones, updater
y handler
. Esta es probablemente la parte más complicada. La función updater recibe los siguientes datos:
selected
=> el texto seleccionado por el usuario al momento de ejecutarseupdater
value
=> el valor completo actual del editorselection
=> un objeto que nos indica la posición deselected
dentro devalue
Esta función updater
devuelve un nuevo string que sobreescribe selected
dentro de value
.
Por ejemplo si tenemos el texto
hola
comovalue
yol
comoselected
entonces el objetoselection
va a venir con{ start: 1, end: 3 }
. Siupdater
devuelve**ol**
entonces el valor completo nuevo va a serh**ol**a
.
La función handler
se ejecuta luego de aplicar updater
y actualizar el contenido del editor. Esto nos permite modificar la posición del cursor del usuario (para cambiar el texto seleccionado) o hacer cualquier cosa, por ejemplo mandar eventos de analytics.
Esta función debe devolver un objeto similar a selection
, con las propiedades start
y end
y recibe un objeto event
con las siguientes propiedades.
value
=> el valor completo actualizadofield
=> el elemento del DOM del textareaupdated
=> el valor generado por la funciónupdater
selected
=> el texto seleccionado originalselection
=> la posición delselected
dentro del valor original- Todas las propiedades de un evento nativo del DOM
Luego de convertir
ol
a**ol**
podemos decidir entre mantener seleccionadool
o**ol**
, para el primero casohandler
debe devolver{ start: 3, end: 5 }
y para el segundo{ start: 1, end: 7 }
.
Programando la funcionalidad
Vamos a programar la funcionalidad propia del botón, ya que hasta ahora el botón no hace nada de verdad.
import { Component } from 'react'
import { Base } from 'pulse-editor/buttons'
import { ipcRenderer } from 'electron' // módulo para de Electron para comunicarse con el proceso principal
import { func } from 'prop-types'
import isMac from 'pulse-editor/built/utils/is-mac'
import Icon from 'react-icons/lib/fa/file-o' // componente de ícono que renderiza un SVG directo
export default class NewButton extends Component {
static contextTypes = {
setShortcut: func.isRequired,
setFileName: func.isRequired, // función propia de la aplicación de escritorio Pulse para definir el nombre del archivo abierto actualmente
removeShortcut: func.isRequired,
writeValue: func.isRequired // función de Pulse Editor para sobreescribir el valor actual del editor
}
componentDidMount() {
// escuchamos el evento `new-file` que llega desde el proceso main
ipcRenderer.on('new-file', this.createFile)
this.context.setShortcut({
ctrlKey: !isMac(),
metaKey: isMac(),
altKey: false,
shiftKey: false,
keyName: 'n',
updater: selected => selected,
handler: event => event.selection,
})
}
componentWillUnmount() {
// dejamos de escuchar el evento `new-file` que llega desde el proceso main
ipcRenderer.removeListener('new-file', this.createFile)
this.context.removeShortcut({ keyName: 'n' })
}
createFile = () => {
// cuando creamos un nuevo archivo
// limpiamos el nombre del archivo actual de la aplicación de escritorio
this.context.setFileName(undefined)
// cambiamos el valor actual del editor a un string vacío
this.context.writeValue({ target: { value: '' } })
}
handleClick = () => this.createFile() // cuando se haga click ejecutamos la función `createFile`
render = () => (
<Base onClick={this.handleClick} name='new'>
<span title='New file [CMD+N]'>
New
</span>
</Base>
)
}
Ese es el botón completo, la funcionalidad principal del editor está definida en createFile
, en la que limpiamos el nombre de archivo y el valor actual del editor. La razón de limpiar el nombre de archivo es porque en nuestra aplicación de escritorio si abrimos un archivo o guardamos uno mantenemos la ruta completa del archivo para poder volver a guardarlo en el mismo lugar. Y vaciamos el valor del editor para limpiarlo.
Luego tenemos una conexión con el proceso main
de Electron para enterarnos cuando este nos pida crear un nuevo archivo.
Implementando nuestro botón
Con lo que hicimos ya tenemos nuestro botón programado. Ahora para usarlo es tan simple como renderizarlo dentro del componente <Editor />
.
import { Component } from 'react'
import { Editor, ButtonBar, ButtonGroup, Field, Preview, EmojiBar } from 'pulse-editor'
import { Bold, Italic, Underline } from 'pulse-editor/buttons'
import Head from 'next/head'
// importamos nuestro botón
import New from '../components/buttons/new-button.js'
export default () => (
<Editor>
<Head>
<link
rel='stylesheet'
href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'
/>
<link
rel='stylesheet'
href='https://raw.githubusercontent.com/PlatziDev/pulse-editor/master/examples/full-usage/static/styles.css'
/>
</Head>
<ButtonBar>
<ButtonGroup>
<Bold><i className="fa fa-bold" /></Bold>
<Italic><i className="fa fa-italic" /></Italic>
<Underline><i className="fa fa-underline" /></Underline>
</ButtonGroup>
<ButtonGroup>
<New /> {/* renderizamos nuestro botón */}
</ButtonGroup>
</ButtonBar>
<div className="PulseEditor-content">
<Field>
<Preview />
</div>
<EmojiBar />
</Editor>
)
Como se ve, usar un botón personalizado es tan simple como cualquier otro botón, con eso pueden empezar a crear botones propios para cualquier funcionalidad extra que deseen incluir.
Palabras finales
Crear un botón puede parecer complicado pero en general depende de que tantas cosas queremos hacer con nuestro botón, un botón de bold es super simple, un botón como este que se conecta con Electron es claramente mucho más complejo y así y todo no es tanto código al final del día.