Agregar un sistema de comentarios a un editor WYSIWYG

 

 

 

  • Typography Masterclass, with Elliot Jay Stocks
  • Advertise on Smashing Magazine

  • Índice
    1. Representar comentarios en la estructura del documento
      1. Hilos de comentarios como marcas
    2. Resaltado de texto comentado
    3. Almacenamiento de UI para comentarios
    4. Agregar nuevos comentarios
      1. Insertion Rule

    En este artículo, reutilizaremos el editor WYSIWYG fundamental creado en el primer artículo para crear un sistema de comentarios para un editor WYSIWYG que permita a los usuarios seleccionar texto dentro de un documento y compartir sus comentarios sobre él. También incorporaremos RecoilJS para la gestión del estado en la aplicación UI. (El código para el sistema que construimos aquí está disponible en un repositorio de Github como referencia).

     

    En los últimos años, hemos visto que la colaboración penetra en muchos flujos de trabajo digitales y casos de uso en muchas profesiones. Solo dentro de la comunidad de Diseño e Ingeniería de Software, vemos a diseñadores colaborar en artefactos de diseño usando herramientas como Figma , equipos que realizan Sprint y Planificación de Proyectos usando herramientas como Mural y entrevistas realizadas usando CoderPad . Todas estas herramientas apuntan constantemente a cerrar la brecha entre una experiencia del mundo físico y en línea para ejecutar estos flujos de trabajo y hacer que la experiencia de colaboración sea lo más rica y fluida posible.

    Para la mayoría de herramientas de colaboración como estas, la capacidad de compartir opiniones entre sí y mantener debates sobre el mismo contenido es imprescindible. La esencia de este concepto es un sistema de comentarios que permite a los colaboradores anotar partes de un documento y tener conversaciones sobre ellas. Además de crear uno para texto en un editor WYSIWYG, el artículo intenta involucrar a los lectores en cómo intentamos sopesar los pros y los contras e intentar encontrar un equilibrio entre la complejidad de la aplicación y la experiencia del usuario cuando se trata de crear funciones para editores WYSIWYG o Procesadores de textos en general.

    Representar comentarios en la estructura del documento

    Para encontrar una manera de representar comentarios en la estructura de datos de un documento de texto enriquecido, veamos algunos escenarios en los que se podrían crear comentarios dentro de un editor.

    • Comentarios creados sobre texto que no tiene estilos (escenario básico);
    • Comentarios creados sobre texto que puede estar en negrita/cursiva/subrayado, etc.;
    • Comentarios que se superponen entre sí de alguna manera (superposición parcial cuando dos comentarios comparten solo unas pocas palabras o completamente contenidos cuando el texto de un comentario está completamente contenido dentro del texto de otro comentario);
    • Comentarios creados sobre texto dentro de un enlace (especial porque los enlaces son en sí mismos nodos en nuestra estructura de documento);
    • Comentarios que abarcan varios párrafos (especiales porque los párrafos son nodos en nuestra estructura de documento y los comentarios se aplican a nodos de texto que son hijos de párrafos).

    Al observar los casos de uso anteriores, parece que la forma en que los comentarios aparecen en un documento de texto enriquecido es muy similar a los estilos de caracteres (negrita, cursiva, etc.). Pueden superponerse entre sí, repasar texto en otros tipos de nodos, como enlaces, e incluso abarcar varios nodos principales, como párrafos.

     

    Por esta razón, utilizamos el mismo método para representar comentarios que para los estilos de caracteres, es decir, "Marcas" (como se les llama en la terminología de SlateJS). Las marcas son solo propiedades normales en los nodos; su especialidad es que la API de Slate alrededor de las marcas ( Editor.addMarky Editor.removeMark) maneja el cambio de la jerarquía de nodos a medida que se aplican varias marcas al mismo rango de texto. Esto es extremadamente útil para nosotros ya que tratamos con muchas combinaciones diferentes de comentarios superpuestos.

    Hilos de comentarios como marcas

    Cada vez que un usuario selecciona un rango de texto e intenta insertar un comentario, técnicamente está iniciando un nuevo hilo de comentarios para ese rango de texto. Debido a que les permitiríamos insertar un comentario y luego responder a ese comentario, tratamos este evento como la inserción de un nuevo hilo de comentarios en el documento.

    La forma en que representamos los hilos de comentarios como marcas es que cada hilo de comentarios está representado por una marca denominada donde commentThread_threadIDhay threadIDuna identificación única que asignamos a cada hilo de comentarios. Entonces, si el mismo rango de texto tiene dos hilos de comentarios encima, tendría dos propiedades establecidas en truecommentThread_thread1y commentThread_thread2. Aquí es donde los hilos de comentarios son muy similares a los estilos de caracteres, ya que si el mismo texto estuviera en negrita y cursiva, tendría ambas propiedades establecidas en trueboldy italic.

    Antes de sumergirnos en la configuración de esta estructura, vale la pena observar cómo cambian los nodos de texto a medida que se les aplican hilos de comentarios. La forma en que esto funciona (como lo hace con cualquier marca) es que cuando se establece una propiedad de marca en el texto seleccionado, la API Editor.addMark de Slate dividirá los nodos de texto si es necesario, de modo que en la estructura resultante, los nodos de texto están configurados de manera que cada nodo de texto tenga exactamente el mismo valor de la marca.

    Para comprender esto mejor, eche un vistazo a los siguientes tres ejemplos que muestran el estado antes y después de los nodos de texto una vez que se inserta un hilo de comentarios en el texto seleccionado:

     

    Un nodo de texto que se divide en tres como marca de hilo de comentario se inserta en el medio del texto. ( Vista previa grande )
    Agregar un hilo de comentarios sobre 'el texto tiene' crea dos nuevos nodos de texto. ( Vista previa grande )
    Agregar un hilo de comentarios sobre 'tiene enlace' también divide el nodo de texto dentro del enlace. ( Vista previa grande )

    Resaltado de texto comentado

    Ahora que sabemos cómo vamos a representar los comentarios en la estructura del documento, sigamos adelante y agreguemos algunos al documento de ejemplo del primer artículo y configuremos el editor para que realmente los muestre resaltados. Dado que tendremos muchas funciones de utilidad para manejar los comentarios en este artículo, creamos un EditorCommentUtilsmódulo que albergará todas estas utilidades. Para empezar, creamos una función que crea una marca para un ID de hilo de comentario determinado. Luego lo usamos para insertar algunos hilos de comentarios en nuestro archivo ExampleDocument.

    # src/utils/EditorCommentUtils.jsconst COMMENT_THREAD_PREFIX = "commentThread_";export function getMarkForCommentThreadID(threadID) { return `${COMMENT_THREAD_PREFIX}${threadID}`;}

    La imagen de abajo subraya en rojo los rangos de texto que tenemos como hilos de comentarios de ejemplo agregados en el siguiente fragmento de código. Tenga en cuenta que el texto 'Richard McClintock' tiene dos hilos de comentarios que se superponen. Específicamente, este es el caso en el que un hilo de comentarios está completamente contenido dentro de otro.

    Los rangos de texto que se comentarían están subrayados en rojo. ( Vista previa grande )

    # src/utils/ExampleDocument.jsimport { getMarkForCommentThreadID } from "../utils/EditorCommentUtils";import { v4 as uuid } from "uuid";const exampleOverlappingCommentThreadID = uuid();const ExampleDocument = [ ... { text: "Lorem ipsum", [getMarkForCommentThreadID(uuid())]: true, }, ... { text: "Richard McClintock", // note the two comment threads here. [getMarkForCommentThreadID(uuid())]: true, [getMarkForCommentThreadID(exampleOverlappingCommentThreadID)]: true, }, { text: ", a Latin scholar", [getMarkForCommentThreadID(exampleOverlappingCommentThreadID)]: true, }, ...];

    En este artículo nos centramos en el lado de la interfaz de usuario de un sistema de comentarios, por lo que les asignamos ID en el documento de ejemplo directamente utilizando el paquete npm uuid . Es muy probable que en una versión de producción de un editor, estos ID sean creados por un servicio backend.

     

    Ahora nos centraremos en modificar el editor para mostrar estos nodos de texto resaltados. Para hacer eso, al representar nodos de texto, necesitamos una forma de saber si tiene hilos de comentarios. Agregamos una utilidad getCommentThreadsOnTextNodepara eso. Nos basamos en el StyledTextcomponente que creamos en el primer artículo para manejar el caso en el que puede estar intentando representar un nodo de texto con comentarios. Dado que tenemos más funciones que se agregarán a los nodos de texto comentado más adelante, creamos un componente CommentedTextque representa el texto comentado. StyledTextcomprobará si el nodo de texto que está intentando representar tiene algún comentario. Si es así, se renderiza CommentedText. Utiliza una utilidad getCommentThreadsOnTextNodepara deducir eso.

    # src/utils/EditorCommentUtils.jsexport function getCommentThreadsOnTextNode(textNode) { return new Set( // Because marks are just properties on nodes, // we can simply use Object.keys() here. Object.keys(textNode) .filter(isCommentThreadIDMark) .map(getCommentThreadIDFromMark) );}export function getCommentThreadIDFromMark(mark) { if (!isCommentThreadIDMark(mark)) { throw new Error("Expected mark to be of a comment thread"); } return mark.replace(COMMENT_THREAD_PREFIX, "");}function isCommentThreadIDMark(mayBeCommentThread) { return mayBeCommentThread.indexOf(COMMENT_THREAD_PREFIX) === 0;}

    El primer artículo creó un componente StyledTextque representa nodos de texto (manejo de estilos de caracteres, etc.). Ampliamos ese componente para usar la utilidad anterior y renderizamos un CommentedTextcomponente si el nodo tiene comentarios.

    # src/components/StyledText.jsimport { getCommentThreadsOnTextNode } from "../utils/EditorCommentUtils";export default function StyledText({ attributes, children, leaf }) { ... const commentThreads = getCommentThreadsOnTextNode(leaf); if (commentThreads.size 0) { return ( CommentedText {...attributes} // We use commentThreads and textNode props later in the article. commentThreads={commentThreads} textNode={leaf} {children} /CommentedText ); } return span {...attributes}{children}/span;}

    A continuación se muestra la implementación CommentedTextque representa el nodo de texto y adjunta el CSS que lo muestra resaltado.

    # src/components/CommentedText.jsimport "./CommentedText.css";import classNames from "classnames";export default function CommentedText(props) { const { commentThreads, ...otherProps } = props; return ( span {...otherProps} className={classNames({ comment: true, })} {props.children} /span );}# src/components/CommentedText.css.comment { background-color: #feeab5;}

    Con todo el código anterior reunido, ahora vemos nodos de texto con hilos de comentarios resaltados en el editor.

    Los nodos de texto comentados aparecen resaltados después de que se hayan insertado los hilos de comentarios. ( Vista previa grande )

    Nota : Actualmente, los usuarios no pueden saber si cierto texto tiene comentarios superpuestos. Todo el rango de texto resaltado parece un hilo de comentarios único. Abordamos esto más adelante en el artículo, donde presentamos el concepto de hilo de comentarios activo que permite a los usuarios seleccionar un hilo de comentarios específico y poder ver su rango en el editor.

     

    Almacenamiento de UI para comentarios

    Antes de agregar la funcionalidad que permite a un usuario insertar nuevos comentarios, primero configuramos un estado de la interfaz de usuario para contener nuestros hilos de comentarios. En este artículo, utilizamos RecoilJS como nuestra biblioteca de administración de estado para almacenar hilos de comentarios, comentarios contenidos dentro de los hilos y otros metadatos como hora de creación, estado, autor del comentario, etc. Agreguemos Recoil a nuestra aplicación:

     yarn add recoil

    Usamos átomos de retroceso para almacenar estas dos estructuras de datos. Si no está familiarizado con Recoil, los átomos son los que mantienen el estado de la aplicación. Para diferentes partes del estado de la aplicación, normalmente querrás configurar diferentes átomos. La familia Atom es una colección de átomos; se puede pensar que es Mapuna clave única que identifica el átomo hasta los átomos mismos. Vale la pena repasar los conceptos básicos de Recoil en este punto y familiarizarnos con ellos.

    Para nuestro caso de uso, almacenamos hilos de comentarios como una familia Atom y luego envolvemos nuestra aplicación en un RecoilRootcomponente. RecoilRootse aplica para proporcionar el contexto en el que se van a utilizar los valores atómicos. Creamos un módulo separado CommentStateque contiene nuestras definiciones de átomos de Recoil a medida que agregamos más definiciones de átomos más adelante en el artículo.

    # src/utils/CommentState.jsimport { atom, atomFamily } from "recoil";export const commentThreadsState = atomFamily({ key: "commentThreads", default: [],});export const commentThreadIDsState = atom({ key: "commentThreadIDs", default: new Set([]),});

    Vale la pena mencionar algunas cosas sobre estas definiciones de átomos:

    • Cada átomo/familia de átomos se identifica de forma única mediante un keyy se puede configurar con un valor predeterminado.
    • A medida que avancemos en este artículo, necesitaremos una forma de iterar sobre todos los hilos de comentarios, lo que básicamente significaría necesitar una forma de iterar sobre la commentThreadsStatefamilia de átomos. Al momento de escribir este artículo, la forma de hacerlo con Recoil es configurar otro átomo que contenga todos los ID de la familia de átomos. Hacemos eso con commentThreadIDsStatearriba. Ambos átomos deberían mantenerse sincronizados cada vez que agreguemos o eliminemos hilos de comentarios.

    Agregamos un RecoilRootcontenedor en nuestro Appcomponente raíz para poder usar estos átomos más adelante. La documentación de Recoil también proporciona un componente depurador útil que tomamos tal como está y lo colocamos en nuestro editor. Este componente dejará console.debugregistros en nuestra consola de desarrollo a medida que los átomos de Recoil se actualicen en tiempo real. Blog sobre salud

    # src/components/App.jsimport { RecoilRoot } from "recoil";export default function App() { ... return ( RecoilRoot ... Editor document={document} onChange={updateDocument} / /RecoilRoot );}
    # src/components/Editor.jsexport default function Editor({ ... }): JSX.Element { ..... return ( Slate ..... /Slate DebugObserver / /);function DebugObserver(): React.Node { // see API link above for implementation.}

    También necesitamos agregar código que inicialice nuestros átomos con los hilos de comentarios que ya existen en el documento (los que agregamos a nuestro documento de ejemplo en la sección anterior, por ejemplo). Lo hacemos más adelante cuando creamos la barra lateral de comentarios que necesita leer todos los hilos de comentarios en un documento.

     

    En este punto, cargamos nuestra aplicación, nos aseguramos de que no haya errores que apunten a nuestra configuración de Recoil y seguimos adelante.

    Agregar nuevos comentarios

    En esta sección, agregamos un botón a la barra de herramientas que permite al usuario agregar comentarios (es decir, crear un nuevo hilo de comentarios) para el rango de texto seleccionado. Cuando el usuario selecciona un rango de texto y hace clic en este botón, debemos hacer lo siguiente:

    1. Asigne una identificación única al nuevo hilo de comentarios que se está insertando.
    2. Agregue una nueva marca a la estructura del documento de Slate con el ID para que el usuario vea ese texto resaltado.
    3. Agregue el nuevo hilo de comentarios a los átomos de retroceso que creamos en la sección anterior.

    Agreguemos una función util que EditorCommentUtilshace los números 1 y 2.

    # src/utils/EditorCommentUtils.jsimport { Editor } from "slate";import { v4 as uuidv4 } from "uuid";export function insertCommentThread(editor, addCommentThreadToState) { const threadID = uuidv4(); const newCommentThread = { // comments as added would be appended to the thread here. comments: [], creationTime: new Date(), // Newly created comment threads are OPEN. We deal with statuses // later in the article. status: "open", }; addCommentThreadToState(threadID, newCommentThread); Editor.addMark(editor, getMarkForCommentThreadID(threadID), true); return threadID;}

    Al utilizar el concepto de marcas para almacenar cada hilo de comentarios como su propia marca, podemos usar simplemente la Editor.addMarkAPI para agregar un nuevo hilo de comentarios en el rango de texto seleccionado. Esta llamada por sí sola maneja todos los diferentes casos de agregar comentarios, algunos de los cuales describimos en la sección anterior: comentarios parcialmente superpuestos, comentarios dentro de enlaces superpuestos, comentarios sobre texto en negrita/cursiva, comentarios que abarcan párrafos, etc. Esta llamada API ajusta la jerarquía de nodos para crear tantos nodos de texto nuevos como sean necesarios para manejar estos casos.

    addCommentThreadToStatees una función de devolución de llamada que maneja el paso 3: agregar el nuevo hilo de comentarios a Recoil atom. Lo implementamos a continuación como un enlace de devolución de llamada personalizado para que sea reutilizable. Esta devolución de llamada debe agregar el nuevo hilo de comentarios tanto a los átomos como commentThreadsStatea commentThreadIDsState. Para poder hacer esto utilizamos el useRecoilCallbackgancho. Este enlace se puede usar para construir una devolución de llamada que obtiene algunas cosas que se pueden usar para leer/configurar datos atómicos. La que nos interesa ahora es la setfunción que se puede utilizar para actualizar un valor de átomo como set(atom, newValueOrUpdaterFunction).

     

    # src/hooks/useAddCommentThreadToState.jsimport { commentThreadIDsState, commentThreadsState,} from "../utils/CommentState";import { useRecoilCallback } from "recoil";export default function useAddCommentThreadToState() { return useRecoilCallback( ({ set }) = (id, threadData) = { set(commentThreadIDsState, (ids) = new Set([...Array.from(ids), id])); set(commentThreadsState(id), threadData); }, [] );}

    La primera llamada setagrega el nuevo ID al conjunto existente de ID de hilo de comentarios y devuelve el nuevo Set(que se convierte en el nuevo valor del átomo).

    En la segunda llamada, obtenemos el átomo para el ID de la familia de átomos, commentThreadsStatecomo commentThreadsState(id)y luego establecemos que threadDatasea su valor. atomFamilyName(atomID)Así es como Recoil nos permite acceder a un átomo de su familia de átomos usando la clave única. En términos generales, podríamos decir que si commentThreadsStatefuera un mapa de JavaScript, esta llamada es básicamente — commentThreadsState.set(id, threadData).

    Ahora que tenemos todo este código configurado para manejar la inserción de un nuevo hilo de comentarios en el documento y los átomos de retroceso, agreguemos un botón a nuestra barra de herramientas y conectémoslo con la llamada a estas funciones.

    # src/components/Toolbar.jsimport { insertCommentThread } from "../utils/EditorCommentUtils";import useAddCommentThreadToState from "../hooks/useAddCommentThreadToState";export default function Toolbar({ selection, previousSelection }) { const editor = useEditor(); ... const addCommentThread = useAddCommentThreadToState(); const onInsertComment = useCallback(() = { const newCommentThreadID = insertCommentThread(editor, addCommentThread); }, [editor, addCommentThread]); return ( div className="toolbar" ... ToolBarButton isActive={false} label={i className={`bi ${getIconForButton("comment")}`} /} onMouseDown={onInsertComment} / /div );}

    Nota : Usamos onMouseDowny no onClicklo cual habría hecho que el editor perdiera el foco y se convirtiera en selección null. Lo analizamos con un poco más de detalle en la sección de inserción de enlaces del primer artículo .

    En el siguiente ejemplo, vemos la inserción en acción para un hilo de comentarios simple y un hilo de comentarios superpuesto con enlaces. Observe cómo recibimos actualizaciones de Recoil Debugger que confirman que nuestro estado se actualiza correctamente. También verificamos que se creen nuevos nodos de texto a medida que se agregan hilos al documento.

    Following the Shortest Comment Thread Rule, clicking on ‘B’ selects comment thread #1. (Large preview)

    In the above example, the user inserts the following comment threads in that order:

     

    1. Comment Thread #1 over character ‘B’ (length = 1).
    2. Comment Thread #2 over ‘AB’ (length = 2).
    3. Comment Thread #3 over ‘BC’ (length = 2).

    At the end of these insertions, because of the way Slate splits the text nodes with marks, we will have three text nodes — one for each character. Now, if the user clicks on ‘B’, going by the shortest length rule, we select thread #1 as it is the shortest of the three in length. If we don’t do that, we wouldn’t have a way to select Comment Thread #1 ever since it is only one-character in length and also a part of two other threads.

    Although this rule makes it easy to surface shorter-length comment threads, we could run into situations where longer comment threads become inaccessible since all the characters contained in them are part of some other shorter comment thread. Let’s look at an example for that.

    Let’s assume we have 100 characters (say, character ‘A’ typed 100 times that is) and the user inserts comment threads in the following order:

    1. Comment Thread # 1 of range 20,80
    2. Comment Thread # 2 of range 0,50
    3. Comment Thread # 3 of range 51,100

    All text under Comment Thread #1 is also part of some other comment thread shorter than #1. (Large preview)

    As you can see in the above example, if we follow the rule we just described here, clicking on any character between #20 and #80, would always select threads #2 or #3 since they are shorter than #1 and hence #1 would not be selectable. Another scenario where this rule can leave us undecided as to which comment thread to select is when there are more than one comment threads of the same shortest length on a text node.

    For such combination of overlapping comments and many other such combinations that one could think of where following this rule makes a certain comment thread inaccessible by clicking on text, we build a Comments Sidebar later in this article which gives user a view of all the comment threads present in the document so they can click on those threads in the sidebar and activate them in the editor to see the range of the comment. We still would want to have this rule and implement it as it should cover a lot of overlap scenarios except for the less-likely examples we cited above. We put in all this effort around this rule primarily because seeing highlighted text in the editor and clicking on it to comment is a more intuitive way of accessing a comment on text than merely using a list of comments in the sidebar.

    Insertion Rule

    The rule is:

    “If the text user has selected and is trying to comment on is already fully covered by comment thread(s), don’t allow that insertion.”

    This is so because if we did allow this insertion, each character in that range would end up having at least two comment threads (one existing and another the new one we just allowed) making it difficult for us to determine which one to select when the user clicks on that character later.

    Looking at this rule, one might wonder why we need it in the first place if we already have the Shortest Comment Range Rule that allows us to select the smallest text range. Why not allow all combinations of overlaps if we can use the first rule to deduce the right comment thread to show? As some of the examples we’ve discussed earlier, the first rule works for a lot of scenarios but not all of them. With the Insertion Rule, we try to minimize the number of scenarios where the first rule cannot help us and we have to fallback on the Sidebar as the only way for the user to access that comment thread. Insertion Rule also prevents exact-overlaps of comment threads. This rule is commonly implemented by a lot of popular editors.

     

    Below is an example where if this rule didn’t exist, we would allow the Comment Thread #3 and then as a result of the first rule, #3 would not be accessible since it would become the longest in length.

    where we implement selecting of comment threads. Since we now have a toolbar button to insert comments, we can implement the Insertion Rule right away by checking the rule when the user has some text selected. If the rule is not satisfied, we would disable the Comment button so users cannot insert a new comment thread on the selected text. Let’s get started!

    # src/utils/EditorCommentUtils.jsexport function shouldAllowNewCommentThreadAtSelection(editor, selection) { if (selection == null || Range.isCollapsed(selection)) { return false; } const textNodeIterator = Editor.nodes(editor, { at: selection, mode: "lowest", }); let nextTextNodeEntry = textNodeIterator.next().value; const textNodeEntriesInSelection = []; while (nextTextNodeEntry != null) { textNodeEntriesInSelection.push(nextTextNodeEntry); nextTextNodeEntry = textNodeIterator.next().value; } if (textNodeEntriesInSelection.length === 0) { return false; } return textNodeEntriesInSelection.some( ([textNode]) = getCommentThreadsOnTextNode(textNode).size === 0 );}

    The logic in this function is relatively straightforward.

    • If the user’s selection is a blinking caret, we don’t allow inserting a comment there as no text has been selected.
    • If the user’s selection is not a collapsed one, we find all the text nodes in the selection. Note the use of the mode: lowest in the call to Editor.nodes (a helper function by SlateJS) that helps us select all the text nodes since text nodes are really the leaves of the document tree.
    • If there is at least one text node that has no comment threads on it, we may allow the insertion. We use the util getCommentThreadsOnTextNode we wrote earlier here.

    We now use this util function inside the toolbar to control the disabled state of the button.

    # src/components/Toolbar.jsexport default function Toolbar({ selection, previousSelection }) { const editor = useEditor(); .... return ( div className="toolbar" .... ToolBarButton isActive={false} disabled={!shouldAllowNewCommentThreadAtSelection( editor, selection )} label={i className={`bi ${getIconForButton("comment")}`} /} onMouseDown={onInsertComment} / /div);

    Let’s test the implementation of the rule by recreating our example above.

    interface) are very helpful. Our iterators reverseTextNodeIterator and forwardTextNodeIterator call these helpers with two options mode: lowest and the match function Text.isText so we know we’re getting a text node from the traversal, if there is one.

    Now we implement updateCommentThreadLengthMap which traverses using these iterators and updates the lengths we’re tracking.

    # src/utils/EditorCommentUtils.jsfunction updateCommentThreadLengthMap( editor, commentThreads, nodeIterator, map) { let nextNodeEntry = nodeIterator(editor); while (nextNodeEntry != null) { const nextNode = nextNodeEntry[0]; const commentThreadsOnNextNode = getCommentThreadsOnTextNode(nextNode); const intersection = [...commentThreadsOnNextNode].filter((x) = commentThreads.has(x) ); // All comment threads we're looking for have already ended meaning // reached an uncommented text node OR a commented text node which // has none of the comment threads we care about. if (intersection.len 




    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Agregar un sistema de comentarios a un editor WYSIWYG

    Agregar un sistema de comentarios a un editor WYSIWYG

    Typography Masterclass, with Elliot Jay Stocks Advertise on Smashing Magazine Índice Representar comentar

    programar

    es

    https://aprendeprogramando.es/static/images/programar-agregar-un-sistema-de-comentarios-a-un-editor-wysiwyg-1101-0.jpg

    2024-05-22

     

    Agregar un sistema de comentarios a un editor WYSIWYG
    Agregar un sistema de comentarios a un editor WYSIWYG

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20