首页  >  文章  >  web前端  >  构建智能编辑器:自动检测 URL 并将其转换为超链接

构建智能编辑器:自动检测 URL 并将其转换为超链接

DDD
DDD原创
2024-10-13 20:23:29925浏览

这是我在工作中为了改善用户体验而想到的一个想法。它涉及实现一个文本框,自动检测 URL 并将其转换为用户输入的超链接(源代码 Github/AutolinkEditor)。这个很酷的功能实现起来有些棘手,必须解决以下问题。

  • 准确检测文本中的 URL
  • 将 URL 字符串转换为超链接后保持光标位置
  • 当用户编辑超链接文本时相应更新目标URL
  • 保留文本中的换行符
  • 支持粘贴富文本,同时保留文本和换行符,文本样式与文本框格式匹配。

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

...
 if(target && target.contentEditable){
  ...
  target.contentEditable = true;
  target.focus();
 }
...

转换是由“onkeyup”和“onpaste”事件驱动的。为了减少转换频率,通过“setTimeout”实现了延迟机制,默认只有用户停止输入1秒后才会触发转换逻辑。

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

使用正则表达式识别并提取 URL

我不想花时间制作完美的正则表达式来匹配 URL,所以我通过搜索引擎找到了一个可用的正则表达式。如果有人有更好的,请随时告诉我!

...
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 += `<a href="${escapeHtml(text)}">${escapeHtml(text)}</a>`;
}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 += `<a href="${escapeHtml(url)}">${escapeHtml(url)}</a>`;
    textContent = afterUrl;
  }
  result += escapeHtml(textContent); // Append any remaining text
}

转换后恢复光标位置

使用 document.createRange 和 window.getSelection 函数,计算节点文本内的光标位置。由于将URL转换为超链接只是添加标签而不修改文本内容,因此可以根据之前记录的位置恢复光标。有关更多详细信息,请阅读 HTML 修改后无法恢复选择,即使是相同的 HTML。

编辑超链接时更新或删除
有时我们会创建文本和目标 URL 相同的超链接(此处称为“简单超链接”)。例如,以下 HTML 显示了这种超链接。

http://www.example.com
对于此类链接,当超链接文本修改时,目标 URL 也应自动更新以保持同步。为了使逻辑更加稳健,当超链接文本不再是有效的 URL 时,链接将转换回纯文本。

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

为了实现此功能,我将“简单超链接”存储在一个对象中,并在 onpaste、onkeyup 和 onfocus 事件期间实时更新它们,以确保上述逻辑仅处理简单超链接。

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

处理换行符和样式

处理粘贴的富文本时,编辑器将自动使用编辑器的文本样式设置文本样式。为了保持格式,富文本中的
标签和所有超链接将被保留。处理输入文本更为复杂。当用户按 Enter 键添加新行时,div 元素将添加到编辑器中,编辑器将其替换为
以保持格式设置。

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 "<br />";
  }
  return "";
}

结论

本文介绍了一些实现简单编辑器的实用技巧,比如常见的 onkeyup 和 onpaste 事件,如何使用 Selection 和 Range 来恢复光标位置,以及如何处理元素的节点来实现编辑器的功能功能。虽然正则表达式不是本文的重点,但完整的正则表达式可以增强编辑器识别特定字符串的稳健性(本文中使用的正则表达式将保持开放以供修改)。如果对您的项目有帮助,您可以通过 Github/AutolilnkEditor 访问源代码以获取更多详细信息。

以上是构建智能编辑器:自动检测 URL 并将其转换为超链接的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn