ホームページ  >  記事  >  ウェブフロントエンド  >  スマート エディタの構築: URL を自動的に検出し、ハイパーリンクに変換する

スマート エディタの構築: URL を自動的に検出し、ハイパーリンクに変換する

DDD
DDDオリジナル
2024-10-13 20:23:29925ブラウズ

これは、ユーザー エクスペリエンスを向上させるために仕事中に思いついたアイデアです。これには、URL を自動的に検出し、ユーザーの入力に応じて 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/AutolinkEditor 経由でソース コードにアクセスして詳細を取得できます。

以上がスマート エディタの構築: URL を自動的に検出し、ハイパーリンクに変換するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。