Esta es una idea que se me ocurrió en el trabajo para mejorar la experiencia del usuario. Implica implementar un cuadro de texto que detecta automáticamente las URL y las convierte en hipervínculos a medida que el usuario escribe (código fuente Github/AutolinkEditor). Esta interesante característica es algo complicada de implementar y se deben abordar los siguientes problemas.
... if(target && target.contentEditable){ ... target.contentEditable = true; target.focus(); } ...
La conversión se realiza mediante eventos "onkeyup" y "onpaste". Para reducir la frecuencia de las conversiones, se implementa un mecanismo de retraso con "setTimeout", donde la lógica de conversión se activa solo después de que el usuario deja de escribir durante 1 segundo de forma predeterminada.
idle(func, delay = 1000) { ... const idleHandler = function(...args) { if(this[timer]){ clearTimeout(this[timer]); this[timer] = null; } this[timer] = setTimeout(() => { func(...args); this[timer] = null; }, delay); }; return idleHandler.bind(this); }
No tenía intención de perder tiempo creando la expresión regular perfecta para las URL coincidentes, así que encontré una utilizable a través de un motor de búsqueda. Si alguien tiene uno mejor, ¡no dude en hacérmelo saber!
... const URLRegex = /^(https?:\/\/(([a-zA-Z0-9] -?) [a-zA-Z0-9] \.) (([a-zA-Z0-9] -?) [a-zA-Z0-9] ))(:\d )?(\/.*)?(\?.*)?(#.*)?$/; const URLInTextRegex = /(https?:\/\/(([a-zA-Z0-9] -?) [a-zA-Z0-9] \.) (([a-zA-Z0-9] -?) [a-zA-Z0-9] ))(:\d )?(\/.*)?(\?.*)?(#.*)?/; ... if(URLRegex.test(text)){ result = `${escapeHtml(text)}`; }else { // text contains url let textContent = text; let match; while ((match = URLInTextRegex.exec(textContent)) !== null) { const url = match[0]; const beforeUrl = textContent.slice(0, match.index); const afterUrl = textContent.slice(match.index url.length); result = escapeHtml(beforeUrl); result = `${escapeHtml(url)}`; textContent = afterUrl; } result = escapeHtml(textContent); // Append any remaining text }
Con las funciones document.createRange y window.getSelection, calcula la posición del cursor dentro del texto del nodo. Dado que la conversión de URL en hipervínculos solo agrega etiquetas sin modificar el contenido del texto, el cursor se puede restaurar según la posición registrada previamente. Para obtener más detalles, lea No se puede restaurar la selección después de modificar el HTML, incluso si es el mismo HTML.
Actualizar o eliminar al editar el hipervínculo
A veces creamos hipervínculos donde el texto y la URL de destino son los mismos (aquí se denominan "hipervínculos simples"). Por ejemplo, el siguiente HTML muestra este tipo de hipervínculo.
http://www.ejemplo.com
Para dichos enlaces, cuando se modifica el texto del hipervínculo, la URL de destino también debe actualizarse automáticamente para mantenerlos sincronizados. Para que la lógica sea más sólida, el enlace se volverá a convertir a texto sin formato cuando el texto del hipervínculo ya no sea una URL válida.
handleAnchor: anchor => { ... const text = anchor.textContent; if(URLRegex.test(text)){ return nodeHandler.makePlainAnchor(anchor); }else { return anchor.textContent; } ... } ... makePlainAnchor: target => { ... const result = document.createElement("a"); result.href = target.href; result.textContent = target.textContent; return result; ... }
Para implementar esta función, almaceno los 'hipervínculos simples' en un objeto y los actualizo en tiempo real durante los eventos onpaste, onkeyup y onfocus para garantizar que la lógica anterior solo maneje hipervínculos simples.
target.onpaste = initializer.idle(e => { ... inclusion = contentConvertor.indexAnchors(target); }, 0); const handleKeyup = initializer.idle(e => { ... inclusion = contentConvertor.indexAnchors(target); ... }, 1000); target.onkeyup = handleKeyup; target.onfocus = e => { inclusion = contentConvertor.indexAnchors(target); } ... indexAnchors(target) { const inclusion = {}; ... const anchorTags = target.querySelectorAll('a'); if(anchorTags) { const idPrefix = target.id === "" ? target.dataset.id : target.id; anchorTags.forEach((anchor, index) => { const anchorId = anchor.dataset.id ?? `${idPrefix}-anchor-${index}`; if(anchor.href.replace(/\/ $/, '').toLowerCase() === anchor.textContent.toLowerCase()) { if(!anchor.dataset.id){ anchor.setAttribute('data-id', anchorId); } inclusion[[anchorId]] = anchor.href; } }); } return Object.keys(inclusion).length === 0 ? null : inclusion; ... }
Al manejar texto enriquecido pegado, el editor automáticamente diseñará el texto con los estilos de texto del editor. Para mantener el formato, se conservarán las etiquetas
en el texto enriquecido y todos los hipervínculos. Manejar el texto de entrada es más complejo. Cuando el usuario presiona Enter para agregar una nueva línea, se agrega un elemento div al editor, que el editor reemplaza con
para mantener el formato.
node.childNodes.forEach(child => { if (child.nodeType === 1) { if(child.tagName === 'A') { // anchar element const key = child.id === "" ? child.dataset.id : child.id; if(inclusion && inclusion[key]){ const disposedAnchor = handleAnchor(child); if(disposedAnchor){ if(disposedAnchor instanceof HTMLAnchorElement) { disposedAnchor.href = disposedAnchor.textContent; } result = disposedAnchor.outerHTML ?? disposedAnchor; } }else { result = makePlainAnchor(child)?.outerHTML ?? ""; } }else { result = compensateBR(child) this.extractTextAndAnchor(child, inclusion, nodeHandler); } } }); ... const ElementsOfBR = new Set([ 'block', 'block flex', 'block flow', 'block flow-root', 'block grid', 'list-item', ]); compensateBR: target => { if(target && (target instanceof HTMLBRElement || ElementsOfBR.has(window.getComputedStyle(target).display))){ return "
"; } return ""; }
Este artículo describe algunas técnicas prácticas utilizadas para implementar un editor simple, como eventos comunes como onkeyup y onpaste, cómo usar Selección y Rango para restaurar la posición del cursor y cómo manejar los nodos de un elemento para lograr las funciones del editor. funcionalidad. Si bien las expresiones regulares no son el tema central de este artículo, una expresión regular completa puede mejorar la solidez del editor a la hora de identificar cadenas específicas (la expresión regular utilizada en este artículo permanecerá abierta a modificaciones). Puede acceder al código fuente a través de Github/AutolilnkEditor para obtener más detalles si es útil para su proyecto.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3