Esta é uma ideia que tive no trabalho para melhorar a experiência do usuário. Envolve a implementação de uma caixa de texto que detecta URLs automaticamente e os converte em hiperlinks conforme o usuário digita (código-fonte Github/AutolinkEditor). Esse recurso interessante é um tanto complicado de implementar e os seguintes problemas devem ser resolvidos.
... if(target && target.contentEditable){ ... target.contentEditable = true; target.focus(); } ...
A conversão é impulsionada pelos eventos “onkeyup” e “onpaste”. Para reduzir a frequência das conversões, é implementado um mecanismo de atraso com “setTimeout”, onde a lógica de conversão é acionada somente após o usuário parar de digitar por 1 segundo por padrão.
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); }
Eu não pretendia perder tempo criando o regex perfeito para URLs correspondentes, então encontrei um que pudesse ser usado por meio de um mecanismo de pesquisa. Se alguém tiver um melhor, fique à vontade para me avisar!
... 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 }
Com as funções document.createRange e window.getSelection, calcule a posição do cursor dentro do texto do nó. Como a conversão de URLs em hiperlinks apenas adiciona tags sem modificar o conteúdo do texto, o cursor pode ser restaurado com base na posição registrada anteriormente. Para obter mais detalhes, leia Não é possível restaurar a seleção após modificação do HTML, mesmo que seja o mesmo HTML.
Atualizar ou remover ao editar o hiperlink
Às vezes, criamos hiperlinks onde o texto e o URL de destino são iguais (chamados de ‘hiperlinks simples’ aqui). Por exemplo, o HTML a seguir mostra esse tipo de hiperlink.
http://www.exemplo.com
Para esses links, quando o texto do hiperlink é modificado, o URL de destino também deve ser atualizado automaticamente para mantê-los sincronizados. Para tornar a lógica mais robusta, o link será convertido novamente em texto simples quando o texto do hiperlink não for mais um URL válido.
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 esse recurso, eu armazeno os ‘hiperlinks simples’ em um objeto e os atualizo em tempo real durante os eventos onpaste, onkeyup e onfocus para garantir que a lógica acima lide apenas com hiperlinks 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; ... }
Ao manusear rich text colado, o editor estilizará automaticamente o texto com os estilos de texto do editor. Para manter a formatação, as tags
no rich text e todos os hiperlinks serão preservados. O tratamento do texto de entrada é mais complexo. Quando o usuário pressiona Enter para adicionar uma nova linha, um elemento div é adicionado ao editor, que o editor substitui por um
para manter a formatação.
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 artigo descreve algumas técnicas práticas usadas para implementar um editor simples, como eventos comuns como onkeyup e onpaste, como usar Seleção e Intervalo para restaurar a posição do cursor e como lidar com os nós de um elemento para obter o editor funcionalidade. Embora as expressões regulares não sejam o foco deste artigo, uma regex completa pode aumentar a robustez do editor na identificação de strings específicas (a regex usada neste artigo permanecerá aberta para modificação). Você pode acessar o código-fonte via Github/AutolilnkEditor para obter mais detalhes se for útil para o seu projeto.
Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.
Copyright© 2022 湘ICP备2022001581号-3