Como Sports.ru escribió su editor WYSIWYG

A mediados de 2018, Sports.ru pensó en pasar a un nuevo editor de texto WYSIWYG para las publicaciones de los usuarios. Desde junio de 2019, el editor ha estado en modo beta. Durante este tiempo, resolvimos muchos problemas asociados tanto con el diseño de la arquitectura de todo el servicio como con la implementación del editor en el navegador basado en la biblioteca ProseMirror , y decidimos compartir nuestra experiencia.



Tabla de contenido


1. Introducción
1.1. ¿Por qué necesitabas WYSIWYG
1.2? Descripción de la tarea que enfrentaron los desarrolladores
2. Cómo elegir la herramienta
3. Qué sucedió
3.1. Arquitectura de servicio
3.2. Cuáles son los desafíos
4. Resultados de la prueba beta


1. Introducción



1.1. ¿Por qué necesitabas WYSIWYG?


Sports.ru es un medio sobre deportes con una audiencia de 20 millones de usuarios por mes. Nuestras principales diferencias con los medios clásicos son la comunidad y UGC . El contenido del usuario (calificaciones, comentarios, chats, publicaciones) no solo complementa el valor editorial, sino que también crea una plataforma para que los usuarios interactúen entre sí. Cada mes, nuestros usuarios escriben casi 10 mil publicaciones. Los mejores de ellos se envían a la página principal del sitio junto con los editoriales, enviados a aplicaciones móviles, redes sociales. El contenido del usuario representa aproximadamente el 40% de todas las lecturas en las páginas de Sports.ru.

Queremos ser la plataforma más conveniente para los autores deportivos, para ayudar a crear contenido y entregarlo a un público interesado. 10 años utilizamos el editor TinyMCE- y al final quedó obsoleto, dejó de adaptarse tanto al equipo como a los usuarios que estaban acostumbrados a los editores modernos.


Higo. 1. La interfaz del antiguo editor basado en TinyMCE

De los autores de blogs recibidos regularmente sobre las siguientes quejas:

  • Escribí durante mucho tiempo, luego accidentalmente cerré la pestaña y todo desapareció;
  • escribir textos largos es muy inconveniente;
  • Es molesto que para insertar cada imagen, primero debe cargarla en el alojamiento de imágenes.

El equipo también tenía sus propias quejas:

  • en TinyMCE no puede cargar imágenes directamente desde un archivo, solo puede adjuntar enlaces a imágenes, y debido a que los usuarios no pudieron cargar imágenes a nuestro almacenamiento, si los enlaces a ellos murieron, no podríamos hacer nada al respecto;
  • Las posibilidades de editar y formatear el texto no están equilibradas. Por un lado, no había suficientes estilos, por ejemplo, para encabezados internos en el texto. Por otro lado, fue posible utilizar las herramientas disponibles en cualquier combinación. Como resultado, las publicaciones no parecían uniformes (en Sports.ru, se comenzó a trabajar en la implementación del sistema de diseño y las publicaciones de los usuarios deberían verse de acuerdo con él);
  • el contenido se crea y almacena en HTML, por lo que es difícil administrar estilos en publicaciones en diferentes clientes, y solo hacer cambios en el diseño de las publicaciones.

A continuación hay una historia sobre cómo resolvimos el problema de crear un nuevo editor en el lado frontal. Es cierto que también habrá algo sobre el producto, el diseño y las partes del backend, porque sin esto será difícil entender por qué se tomó una decisión particular en el front-end.


1.2. Descripción de la tarea que enfrentan los desarrolladores.


En resumen, la tesis de la que inicialmente nos basamos: bloguear en Sports.ru es un fastidio. En principio, sería posible no crear un nuevo editor, sino simplemente agregar el autoguardado y la capacidad de cargar imágenes en su propio almacenamiento, y la mayoría de las quejas de los usuarios y empleados desaparecerían. Pero todavía quería no apoyar la herramienta en tecnologías antiguas, sino crear un nuevo editor moderno que podamos desarrollar y escalar fácilmente.

Además de la interfaz inconveniente, uno de los principales problemas técnicos del antiguo editor era que el contenido de la publicación se guardaba inmediatamente como una cadena HTML, y los cambios en la apariencia de la publicación requerían la intervención de desarrolladores de back-end o se implementaban en tiempo de ejecución en el cliente (por ejemplo, la colocación de bloques de anuncios). en el cuerpo del post). Nuestra tarea, entre otras cosas, era separar los datos de su presentación y, en consecuencia, dejar el diseño y la interfaz en el código del cliente, y trabajar con los datos en el código del servidor.

Como modelo a seguir, tomamos Medium , a veces espiando ideas de Google Doc . Además de resolver los problemas ya identificados, decidimos agregar varias características nuevas que harían más cómodo el uso del editor:

  • WYSIWYG, .. what you see is what you get (. « , », ), , . , ;
  • ( , , , , ; , ) .

Al mismo tiempo, el editor mismo no debería haber estado vinculado a las características de Sports.ru, porque Sports.ru, aunque es el proyecto principal de nuestra empresa, todavía no es el único. La compañía también está desarrollando los medios deportivos internacionales Tribuna , una red social para los entusiastas de las apuestas Betting Insider , y recientemente lanzó su propio estudio de producción dedicado a proyectos publicitarios. Desarrollar un editor en línea es lo suficientemente costoso como para no querer reutilizar este código en otro sitio con diferentes tipos de letra y estilos, con su propio conjunto de herramientas para editar y formatear.

Tenemos mucho contenido de texto, y antes de comenzar a trabajar en la creación de un nuevo editor de publicaciones, pensamos en cómo debería almacenarse este contenido. TinyMCE no nos dio una opción y el contenido tuvo que almacenarse solo en HTML, que, como se mencionó anteriormente, no era adecuado para el equipo. Como resultado, se nos ocurrió nuestro propio formato para almacenar datos de texto que cumplen con nuestros requisitos, y lo llamamos cuerpo estructurado.

El cuerpo estructurado es una matriz de objetos que refleja la estructura del contenido. En este caso, el contenido se divide en elementos que son bloques independientes, por ejemplo, párrafo, lista, imagen. Un elemento almacena información sobre qué tipo es y qué propiedades tiene. Por ejemplo, el bloque de subtítulos describe el título en el texto, debe contener el texto y los campos de nivel. En consecuencia, el texto contiene el texto de este encabezado y el nivel contiene el nivel (del 1 al 4). Un cuerpo estructurado, que consta de un encabezado de segundo nivel, podría verse, por ejemplo, así:

const structuredBody = [
    {
        type: 'subtitle',
        value: {
            text: '    ',
            level: 2,
        },
    },
];

La transición al cuerpo estructurado nos permitió comenzar el proceso de separación de la lógica empresarial, los datos y su presentación. En última instancia, queremos que el servidor y los clientes intercambien solo datos. Y cómo y por qué mostrar estos datos al usuario final, cada cliente determinará de forma independiente.

El contenido en el formato de cuerpo formatd se almacena en JSON, y para validar su contenido, creamos un esquema JSON llamado esquema de cuerpo estructurado. Este diagrama describe todos los elementos válidos y sus propiedades. Por lo tanto, podemos estar seguros de que donde sea que se necesite un cuerpo estructurado, se usa un conjunto de claves y valores.

Además, permite que diferentes equipos usen los mismos servicios para procesar contenido en este formato. Por ejemplo, un servicio para generar HTML a partir de un cuerpo estructurado para mostrar contenido o un editor para crear contenido. Esto reduce significativamente el costo de desarrollar y soportar todo el núcleo de servicios relacionados con la creación y visualización de contenido.

Se supuso que el nuevo editor debería aceptar contenido de entrada y salida exclusivamente en el formato de cuerpo estructurado. Y aquí era necesario tener en cuenta el punto sutil: dado que anteriormente las publicaciones se guardaron inmediatamente en HTML, esta cadena HTML de la base de datos se transmitió al cliente para su visualización (en adelante, por el cliente nos referimos solo al navegador, a menos que se especifique lo contrario). Ahora queremos almacenar el contenido de todas las publicaciones en el cuerpo estructurado, pero los clientes solo pueden procesar HTML. Entonces, junto con la tarea de pasar a un nuevo editor, la tarea de implementar una nueva forma para que los clientes muestren publicaciones para leer directamente desde un cuerpo estructurado continúa simultáneamente. Decidimos que es mejor comer un elefante poco a poco, por lo que primero debe abandonar TinyMCE por completo, y solo entonces asumir la lógica de mostrar publicaciones para leer. Además,no todas las publicaciones antiguas lograron traducir el contenido a un nuevo formato, lo que significa que estas publicaciones siempre se almacenarán solo en HTML y es necesario que también conserven la capacidad de leer.

Total: parte de las publicaciones (todas las nuevas y antiguas que se transfirieron correctamente al nuevo formato) se almacenarán en dos formatos: HTML y cuerpo estructurado, hasta que se implemente la nueva lógica de visualización para la lectura, y el resto (la mayoría de las antiguas y muy antiguas) publicaciones) solo permanecerá en HTML.


2. Cómo elegir una herramienta


Tuvimos que darnos cuenta de la capacidad de editar y crear una publicación en el cliente, teniendo en cuenta las características y limitaciones anteriores. Como siempre, puede tomar una solución preparada, o puede encontrar la suya.

Para empezar, examinamos qué bibliotecas ya están preparadas para crear editores WYSIWYG y si son adecuadas para nosotros. Nos decidimos por Slate , Draft.js y ProseMirror .

Además de almacenar contenido en una estructura de datos, el momento crítico para nosotros también fue la capacidad de trabajar con Vue o JS puro, porque ya habíamos comenzado a mover el sitio a una nueva pila tecnológica usando Vue + Vuex. Además, me gustaría ampliar las capacidades de la biblioteca terminada con la ayuda de nuevos módulos (de terceros o autoescritos) si es necesario.

Lengüeta. 1. Comparación de las bibliotecas revisadas por los parámetros más importantes para Sports.ru


Como puede ver en la tabla, ProseMirror cumplió completamente con nuestros requisitos, por lo tanto, ya no consideramos la idea de escribir nuestra propia biblioteca para editar contenido de texto, pero comenzamos a estudiar esta biblioteca con más detalle. Todavía hay una pluma bastante popular , que no entró en nuestra comparación solo porque honestamente lo olvidamos en la etapa de selección de una herramienta. De acuerdo con nuestros requisitos clave, también pasa, pero sucedió. Ya hablamos sobre qué es ProseMirror y cómo trabajar con él en otro artículo .


3. Que paso



3.1. Arquitectura de servicio


El editor de contenido en el cliente está lejos de ser todo. Debe colocar el editor en un proyecto existente, mostrarlo en algún lugar de la página web y también considerar la interacción con el backend, resolver el problema de admitir simultáneamente dos editores (no puede abandonar inmediatamente el anterior) y almacenar el contenido en dos formatos (HTML y estructurado cuerpo).

Todas estas tareas se pueden dividir en aquellas relacionadas con el frontend, el backend y su integración. Nos interesan principalmente los problemas de front-end y de integración, aunque también mencionamos algunos aspectos importantes de las tareas de back-end.

Los servicios frontend para el editor se pueden dividir en varios niveles:

  1. página web para crear y editar una publicación;
  2. Vue-app, . , , Vue, -, , , , .., , , ;
  3. WYSIWYG- ProseMirror, Vue. , , ;
  4. SB2HTML – HTML structured body, . , structured body – , . , , , . Sports.ru HTML structured body, - HTML . HTML Node.Js, JS- .

El proceso de guardar la publicación se muestra en la Fig. 2. El contenido de la publicación en el formato del cuerpo estructurado y sus metadatos se transfieren al backend. El servidor envía contenido al servicio SB2HTML, recibe HTML listo en la respuesta, pone todo esto en la base de datos y le dice al cliente que la publicación se ha guardado correctamente o informa un error.


Higo. 2. Esquema para guardar una publicación al crear o editar en un editor WYSIWYG


3.2. ¿Qué dificultades enfrentaste?


Hubo muchas dificultades, surgieron constantemente y con frecuencia en los momentos más inesperados.

Como ya dijimos, el editor de contenido se encuentra dentro del formulario, lo que le permite ingresar datos adicionales necesarios para crear una publicación, como título, anotación, etc. Como anotación, debería ser posible descargar imágenes de un archivo y a través de un enlace de Internet. Pero para el contenido también queremos cargar imágenes de un archivo y, por referencia, además, de acuerdo con las mismas reglas. Y aquí nos enfrentamos a un dilema: por un lado, el contenido de una publicación está aislado del formulario externo durante la edición y es atendido por ProseMirror, pero por otro lado, quiero observar el principio DRYy no duplique el mismo código. Resolvimos esto de la siguiente manera: describimos la carga de imágenes como un conjunto de métodos en un objeto en el nivel de formulario Vue y pasamos este objeto como uno de los parámetros al constructor del editor WYSIWYG.

Las entidades que describen el contenido (Nodo y Fragmento) se definen en el modelo ProseMirror. Sin embargo, solo se utilizan índices para una transacción para determinar el rango de caracteres a los que se aplica esta transacción (los índices se cuentan tanto desde el principio del documento como desde el principio del nodo principal). La indexación de caracteres es uno de los conceptos centrales de ProseMirror, pero al editar y formatear texto es mucho más conveniente pensar en entidades del modelo ProseMirror. Como resultado, para un trabajo cómodo con el contenido, escribimos nuestros ayudantes para simplificar la interacción con un documento para transacciones. Después del comienzo de nuestro trabajo, apareció la biblioteca de tiptap , que es un conjunto de ayudantes similares.

El siguiente problema fue que en la etapa de creación del esquema, nos dimos cuenta de que ya tenemos un formato interno aprobado para almacenar contenido, un cuerpo estructurado que satisface nuestras necesidades, y ProseMirror almacena el contenido en su propio formato en una historia. Cambiar al formato ProseMirror fue difícil y poco práctico. Nos encontramos en una situación en la que los datos en un formato llegan al cliente a través de la API, y es necesario mostrar otro. Una situación similar surge cuando es necesario guardar contenido modificado o creado. Para hacer esto, implementamos un convertidor que convierte formatos de un lado a otro. Escribieron una prueba simple para él, que toma el contenido de una publicación en formato de cuerpo formateado, lo traduce al formato ProseMirror, luego lo compara y compara la versión original con la recibida. Resultó rápida y fácilmente.

Más tarde, a medida que el esquema del documento cambió y, como regla, se volvió más complicado, quedó claro que el más mínimo cambio podría conducir a errores en el editor, y tal prueba parece dar una cobertura muy pobre. Como resultado, tuve que escribir pruebas en casi todas las combinaciones de nodos y marcas en dos pequeños métodos de conversión. Ahora, sin estas pruebas, es imposible determinar si el próximo cambio en el circuito romperá algo o no.

El siguiente problema está nuevamente relacionado con la necesidad de compatibilidad con las tecnologías antiguas y nuevas. Nuestro editor WYSIWYG se implementa solo en navegadores (escritorio y, pronto, móvil). En consecuencia, para editar contenido en el cliente se da en JSON en el cuerpo estructurado de formato, sin embargo, la lectura de publicaciones en navegadores se realiza solo desde HTML. Al mismo tiempo, la mayoría de las aplicaciones móviles ya han cambiado para mostrar las publicaciones de los usuarios directamente desde el cuerpo estructurado.

Para las aplicaciones móviles, era necesario prever el caso cuando el cliente no puede procesar algún elemento del cuerpo estructurado. Por ejemplo, si se agrega un nuevo elemento al cuerpo estructurado, cuya visualización se implementa solo en una versión más reciente de la aplicación. Como no todos los usuarios actualizan sus aplicaciones al mismo tiempo, era necesario proporcionar un plan "B" para versiones anteriores: en lugar de crear HTML a partir de un cuerpo estructurado, inserte un fragmento HTML listo para el elemento deseado. La presencia de fragmentos HTML para cada elemento no se proporcionó en el esquema del cuerpo estructurado, porque la idea misma de esta estructura era negarse a almacenar datos en HTML. Pero al final, llegamos a la conclusión de que necesitamos dos esquemas de cuerpo estructurados: uno para mostrar y otro para editar. Las diferencias entre los esquemas sonque el cuerpo estructurado para editar contiene solo el contenido del artículo, y para mostrarlo agregamos algunos elementos adicionales. En particular, se crea un fragmento HTML para cada elemento cuando se guarda una publicación en el servicio SB2HTML y se agrega solo al cuerpo estructurado para mostrar la publicación. Además, el cuerpo estructurado también muestra espacio publicitario en el contenido para su visualización.

Cuando abrimos contenido para editarlo en un navegador, básicamente no podemos encontrar un elemento desconocido, porque todas las publicaciones se crean y se muestran de la misma manera. Pero decidieron prever tal caso para el futuro también. Para hacer esto, agregamos un elemento de código auxiliar predeterminado al esquema ProseMirror. Llamamos a este elemento sin soporteBlock. El trozo aparece en lugar de un elemento no admitido. Lo estilizamos como un rectángulo gris con texto que indica que este elemento no es compatible y no se puede editar. Cuando se guarda una publicación, dicho elemento permanece sin cambios en el cuerpo estructurado. El usuario puede cambiar su ubicación en relación con otros elementos, pero el contenido interno de un elemento desconocido no se puede cambiar ni editar. Sin embargo, el usuario puede eliminar dicho elemento, luego, por supuesto,no se guardará en el documento final.

Todos los problemas descritos estaban relacionados con las dificultades de implementar el editor WYSIWYG. Pero mientras existía en modo beta, no pudimos abandonar el antiguo editor en TinyMCE y nos vimos obligados a admitir ambos editores, proporcionando compatibilidad hacia atrás entre ellos. Por ejemplo, puede crear una publicación en el editor WYSIWYG, guardarla, luego editarla en TinyMCE, guardarla, abrirla nuevamente en WYSIWYG, etc. Como resultado, al abrir en WYSIWYG, vimos el mismo contenido que en el guardado anterior en TinyMCE. Para implementar la compatibilidad con versiones anteriores, era necesario enviar contenido HTML a TinyMCE, que ya aprendimos a crear desde un cuerpo estructurado y guardar en la base de datos mientras guardaba la publicación. Y al guardar una publicación a través de TinyMCE, el contenido creado en el servidor se ejecuta a través del servicio HTML2SB,Como resultado, podemos guardar tanto HTML fresco como cuerpo estructurado.

HTML2SB es lo opuesto a lo que SB2HTML hace, es decir, convierte contenido de HTML a cuerpo estructurado. Cronológicamente, este servicio apareció antes que cualquier otra cosa, porque antes de la creación del editor WYSIWYG, la única forma de publicar contenido en formato de cuerpo estructurado era el análisis directo desde HTML. HTML2SB era parte de la infraestructura de back-end alrededor del editor de publicaciones, pero después de abandonar TinyMCE, ya no era necesario.


4. Resultados Beta


Ahora el editor WYSIWYG está disponible para todos los usuarios en versión beta, y pronto se convertirá en el editor principal de las publicaciones de Sports.ru. Ya hemos recibido una herramienta para crear y editar publicaciones que cumple con la mayoría de nuestros requisitos:

  • la interfaz del editor se ha vuelto clara, concisa y moderna, escribir publicaciones largas se ha vuelto mucho más fácil;
  • Ahora puede descargar imágenes de un archivo y mediante un enlace que se colocan inmediatamente en nuestro repositorio;
  • se agregó la opción de incrustar incrustaciones de las principales redes sociales y sitios de alojamiento de videos;
  • limpiado estilos de formato de texto;
  • Las aplicaciones móviles ya han pasado a mostrar publicaciones desde un cuerpo estructurado y pueden establecer sus propios estilos para el contenido.

Por supuesto, el editor aún no está completamente depurado, detectamos periódicamente nuevos errores. Vienen las siguientes actualizaciones:

  • guardado automático
  • Versión WYSIWYG para usuarios con derechos extendidos (administradores, editores de tiempo completo);
  • crear y editar publicaciones desde navegadores móviles;
  • Mensajes sobre la edición paralela de un documento por varios usuarios;
  • consejos e incorporación;
  • widgets estadísticos para equipos deportivos, partidos y alineaciones.

En el momento de escribir este artículo, ya se han publicado más de 13,000 publicaciones a través de la versión beta del editor, que es aproximadamente el 20% del número total de textos de usuarios en Sports.ru para el período comprendido entre junio de 2019 y febrero de 2020 inclusive. La proporción de publicaciones creadas y publicadas a través del nuevo editor está creciendo constantemente.


Higo. 3. La proporción de publicaciones de usuarios creadas y publicadas en el nuevo editor

Parece que el crecimiento orgánico en la proporción de publicaciones de usuarios creadas y publicadas a través del nuevo editor es una señal de que los usuarios están contentos con la actualización, que también se confirma mediante comentarios en el anuncio de su lanzamiento en las pruebas beta (algunas de ellas se muestran en la Fig.4). Por lo tanto, en los próximos meses planeamos transferir completamente la creación de publicaciones al nuevo editor, para centrarnos solo en su soporte y desarrollo. 
Por cierto, ¿qué funcionalidad agregarías a nuestro editor WYSIWYG?


Higo. 4. Comentarios del usuario en una publicación con el anuncio de la actualización del editor WYSIWYG 

All Articles