「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > スマート エディタの構築: URL を自動的に検出し、ハイパーリンクに変換する

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

2024 年 11 月 4 日に公開
ブラウズ:164

これは、ユーザー エクスペリエンスを向上させるために仕事中に思いついたアイデアです。これには、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  = `${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
}

変換後のカーソル位置の復元

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

結論

この記事では、onkeyup や onpaste などの一般的なイベント、Selection と Range を使用してカーソル位置を復元する方法、エディターの機能を実現するために要素のノードを処理する方法など、単純なエディターを実装するために使用されるいくつかの実用的なテクニックについて説明します。機能性。正規表現はこの記事の焦点では​​ありませんが、完全な正規表現により、特定の文字列を識別する際のエディタの堅牢性が向上します (この記事で使用されている正規表現は、変更のために公開されたままになります)。プロジェクトに役立つ場合は、Github/AutolinkEditor 経由でソース コードにアクセスして詳細を取得できます。

リリースステートメント この記事は次の場所に転載されています: https://dev.to/oninebx/building-a-smart-editor-automatically-detect-urls-and-convert-them-to-hyperlinks-ilg?1 侵害がある場合は、 Study_golang@163 .comdelete に連絡してください
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3