"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Construindo um editor inteligente: detecte URLs automaticamente e converta-os em hiperlinks

Construindo um editor inteligente: detecte URLs automaticamente e converta-os em hiperlinks

Publicado em 2024-11-04
Navegar:865

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.

  • Detecte URLs com precisão no texto
  • Mantenha a posição do cursor após converter a string do URL em um hiperlink
  • Atualize o URL de destino adequadamente quando os usuários editarem o texto do hiperlink
  • Preservar quebras de linha no texto
  • Suporta a colagem de rich text, mantendo o texto e as quebras de linha, com o estilo do texto correspondendo ao formato da caixa de texto.

Building a Smart Editor: Automatically Detect URLs and Convert Them to Hyperlinks

...
 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);
    }

Identifique e extraia URLs com expressão regular

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
}

Restaurando a posição do cursor após a conversão

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;
  ...
}

Lidar com quebras de linha e estilos

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 ""; }

Conclusões

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.

Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/oninebx/building-a-smart-editor-automatically-detect-urls-and-convert-them-to-hyperlinks-ilg?1 Se houver alguma violação, por favor entre em contato com study_golang@163 .comdelete
Tutorial mais recente Mais>

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