ガイド: 羊皮紙を使ったクローニング培地 Github で編集する

羊皮紙を使ったクローニング培地

一貫した編集エクスペリエンスを提供するには、一貫したデータと予測可能な動作の両方が必要です。残念ながら、DOM にはこれらの両方が欠けています。現代の編集者にとっての解決策は、コンテンツを表現する独自の文書モデルを維持することです。羊皮紙それが Quill のソリューションです。これは、独自の API レイヤーを備えた独自のコードベースで編成されています。 Parchment を通じて、Quill が認識するコンテンツと形式をカスタマイズしたり、まったく新しいものを追加したりできます。

このガイドでは、Parchment と Quill が提供する構成要素を使用して、Medium 上でエディターを複製します。テーマ、無関係なモジュール、またはフォーマットを含まない、Quill の基本的な部分から始めます。この基本レベルでは、Quill はプレーン テキストのみを理解します。しかし、このガイドを読み終える頃には、リンク、ビデオ、さらにはツイートさえも理解できるようになります。

下地

Quill を使用せずに、テキストエリアとボタンだけを使用して、ダミーのイベント リスナーに接続してみましょう。このガイドでは便宜上 jQuery を使用しますが、Quill も Parchment もこれに依存しません。また、次の助けを借りて、いくつかの基本的なスタイルを追加します。Google フォントと素晴らしいフォント。これは羽根ペンや羊皮紙とは何の関係もないので、すぐに先に進みます。

Quillコアの追加

次に、テキストエリアをテーマ、フォーマット、無関係なモジュールを除いた Quill コアに置き換えます。開発者コンソールを開いて、エディターに入力しながらデモを調べます。羊皮紙文書の基本的な構成要素が実際に機能しているのを確認できます。

DOM と同様、Parchment ドキュメントはツリーです。ブロットと呼ばれるそのノードは、DOM ノードを抽象化したものです。スクロール、ブロック、インライン、テキスト、ブレークなど、いくつかのブロットがすでに定義されています。入力すると、テキスト ブロットが対応する DOM テキスト ノードと同期されます。入力は、新しいブロックブロットを作成することによって処理されます。羊皮紙では、子を持つことができるブロットには少なくとも 1 つの子が必要であるため、空のブロックはブレイク ブロットで埋められます。これにより、葉の取り扱いが簡単になり、予測可能になります。これらすべてはルート スクロール ブロットの下に編成されます。

インラインブロットはドキュメントに意味のある構造や書式設定を提供しないため、この時点で入力するだけではインラインブロットを観察することはできません。有効な Quill ドキュメントは正規かつコンパクトでなければなりません。特定のドキュメントを表すことができる有効な DOM ツリーは 1 つだけあり、その DOM ツリーには最小限の数のノードが含まれます。

以来<p><span>Text</span></p><p>Text</p>同じコンテンツを表しますが、前者は無効であり、これは Quill の最適化プロセスの一部であり、<span>。同様に、書式設定を追加すると、<p><em>Te</em><em>st</em></p><p><em><em>Test</em></em></p>最もコンパクトな表現ではないため、これらも無効です。

こうした制約があるため、Quill は任意の DOM ツリーと HTML の変更をサポートできません。ただし、これから説明するように、この構造が提供する一貫性と予測可能性により、豊かな編集エクスペリエンスを簡単に構築できるようになります。

基本的な書式設定

インラインは書式設定に寄与しないと前述しました。これは、基本 Inline クラスに対して設けられた規則ではなく例外です。ベースのブロック ブロットは、ブロック レベルの要素に対して同じように機能します。

太字と斜体を実装するには、Inline から継承し、blotNametagNameをクリックしてQuillに登録します。継承された静的メソッドと変数のシグネチャの完全なリファレンスについては、以下を参照してください。羊皮紙。

let Inline = Quill.import('blots/inline');

class BoldBlot extends Inline { }
BoldBlot.blotName = 'bold';
BoldBlot.tagName = 'strong';

class ItalicBlot extends Inline { }
ItalicBlot.blotName = 'italic';
ItalicBlot.tagName = 'em';

Quill.register(BoldBlot);
Quill.register(ItalicBlot);

ここでは Medium の例に従って使用します。strongと39e2ca6f-bab4-4dfc-a​​f5e-45656a061c22タグを使用することもできますが、biタグ。ブロットの名前は、Quill によってフォーマットの名前として使用されます。ブロットを登録することで、新しいフォーマットで Quill の完全な API を使用できるようになります。

Quill.register(BoldBlot);
Quill.register(ItalicBlot);

var quill = new Quill('#editor');

quill.insertText(0, 'Test', { bold: true });
quill.formatText(0, 4, 'italic', true);
// If we named our italic blot "myitalic", we would call
// quill.formatText(0, 4, 'myitalic', true);

ダミーのボタン ハンドラーを削除し、太字と斜体のボタンを Quill のボタンに接続しましょう。format()。ハードコーディングしますtrue簡素化するために常に書式設定を追加します。アプリケーションで使用できるのは、getFormat()任意の範囲で現在の書式設定を取得し、書式を追加するか削除するかを決定します。のツールバーmodule はこれを Quill 用に実装しますが、ここでは再実装しません。

開発者コンソールを開いて Quill を試してくださいAPI新しい太字と斜体の書式を使用してください。にアクセスできるように、コンテキストを正しい CodePen iframe に設定してください。quillデモ内の変数。

一部のテキストに太字と斜体の両方を適用すると、その順序に関係なく、Quille は文字列を折り返すことに注意してください。<strong>の外側のタグ<em>タグを一貫した順序で並べます。

リンク URL を保存するにはブール値以上の値が必要なので、リンクは少し複雑になります。これは、作成と形式の取得という 2 つの方法でリンク ブロットに影響を与えます。 URL を文字列値として表しますが、URL キーを持つオブジェクトなど、他の方法でも簡単に表現でき、他のキーと値のペアを設定してリンクを定義できます。これについては後で説明します。画像

class LinkBlot extends Inline {
  static create(value) {
    let node = super.create();
    // Sanitize url value if desired
    node.setAttribute('href', value);
    // Okay to set other non-format related attributes
    // These are invisible to Parchment so must be static
    node.setAttribute('target', '_blank');
    return node;
  }

  static formats(node) {
    // We will only be called with a node already
    // determined to be a Link blot, so we do
    // not need to check ourselves
    return node.getAttribute('href');
  }
}
LinkBlot.blotName = 'link';
LinkBlot.tagName = 'a';

Quill.register(LinkBlot);

これで、リンク ボタンを派手なボタンに接続できるようになりました。promptQuill's に進む前に、物事を単純にするためにもう一度説明します。format()

ブロッククオートとヘッダー

Blockquote は、基本ブロック レベルの Blot である Block を継承することを除いて、Bold blot と同じ方法で実装されます。インライン ブロットはネストできますが、ブロック ブロットはネストできません。ブロックブロットは、折り返す代わりに、同じテキスト範囲に適用すると相互に置き換えられます。

let Block = Quill.import('blots/block');

class BlockquoteBlot extends Block { }
BlockquoteBlot.blotName = 'blockquote';
BlockquoteBlot.tagName = 'blockquote';

ヘッダーはまったく同じ方法で実装されますが、唯一の違いは、複数の DOM 要素で表現できることです。デフォルトでは、形式の値は単なるタグ名ではなく、タグ名になります。true。拡張することでこれをカスタマイズできますformats()、私たちがそうしたのと同じように、リンク

class HeaderBlot extends Block {
  static formats(node) {
    return HeaderBlot.tagName.indexOf(node.tagName) + 1;
  }
}
HeaderBlot.blotName = 'header';
// Medium only supports two header sizes, so we will only demonstrate two,
// but we could easily just add more tags into this array
HeaderBlot.tagName = ['H1', 'H2'];

これらの新しいブロットをそれぞれのボタンに接続し、CSS を追加しましょう。<blockquote>鬼ごっこ。

テキストを H1 に設定して、コンソールで次のコマンドを実行してみてください。quill.getContents()。カスタム静的変数が表示されます。formats()仕事での機能。にアクセスできるように、コンテキストを正しい CodePen iframe に設定してください。quillデモ内の変数。

ディバイダー

それでは、最初のリーフブロットを実装しましょう。これまでの Blot の例ではフォーマットと実装に貢献しましたが、format()、リーフブロットはコンテンツを提供し、実装しますvalue()。リーフ ブロットはテキスト ブロットまたは埋め込みブロットのいずれかであるため、セクション分割線は埋め込みになります。一旦作成されると、Embed Blots の値は不変であるため、その場所のコンテンツを変更するには削除して再挿入する必要があります。

私たちの方法論は以前と似ていますが、BlockEmbed から継承する点が異なります。 Embed は以下にも存在しますblots/embedですが、これはインライン レベルのブロットを対象としています。ディバイダーの代わりにブロックレベルの実装が必要です。

let BlockEmbed = Quill.import('blots/block/embed');

class DividerBlot extends BlockEmbed { }
DividerBlot.blotName = 'divider';
DividerBlot.tagName = 'hr';

クリック ハンドラー呼び出しinsertEmbed()これは、ユーザーの選択を都合よく決定、保存、復元する機能はありません。format()そのため、選択内容を維持するには、もう少し作業を行う必要があります。さらに、BlockEmbed をブロックの途中に挿入しようとすると、Quill がブロックを分割します。この動作をより明確にするために、区切り線を挿入する前に改行を挿入してブロック oursevles を明示的に分割します。詳細については、CodePen の [Babel] タブを参照してください。

画像

画像には、構築で学んだ内容を追加できます。リンクディバイダーしみ。これがどのようにサポートされるかを示すために、値のオブジェクトを使用します。画像を挿入するボタン ハンドラーは静的な値を使用するため、画像に関係のないツールチップ UI コードに気を取られることはありません。羊皮紙、このガイドの焦点です。

let BlockEmbed = Quill.import('blots/block/embed');

class ImageBlot extends BlockEmbed {
  static create(value) {
    let node = super.create();
    node.setAttribute('alt', value.alt);
    node.setAttribute('src', value.url);
    return node;
  }

  static value(node) {
    return {
      alt: node.getAttribute('alt'),
      url: node.getAttribute('src')
    };
  }
}
ImageBlot.blotName = 'image';
ImageBlot.tagName = 'img';

動画

これまでと同様の方法でビデオを実装します画像。 HTML5を使用することもできます<video>タグを使用しますが、この方法では YouTube 動画を再生できません。これはおそらくより一般的で関連性の高い使用例であるため、<iframe>これをサポートするために。ここでそうする必要はありませんが、複数のブロットで同じタグを使用したい場合は、次のように使用できます。classNameに加えてtagName、次で説明しますつぶやき例。

さらに、未登録フォーマットとして幅と高さのサポートを追加します。登録された形式と名前空間が衝突しない限り、Embed に固有の形式を個別に登録する必要はありません。ブロットは未知の形式をその子に渡すだけで、最終的にはリーフに到達するため、これは機能します。これにより、異なる埋め込みが未登録の形式を異なる方法で処理することもできます。たとえば、私たちの画像以前の embed は、widthここでのビデオとは形式が異なります。

class VideoBlot extends BlockEmbed {
  static create(url) {
    let node = super.create();
    node.setAttribute('src', url);
    // Set non-format related attributes with static values
    node.setAttribute('frameborder', '0');
    node.setAttribute('allowfullscreen', true);

    return node;
  }

  static formats(node) {
    // We still need to report unregistered embed formats
    let format = {};
    if (node.hasAttribute('height')) {
      format.height = node.getAttribute('height');
    }
    if (node.hasAttribute('width')) {
      format.width = node.getAttribute('width');
    }
    return format;
  }

  static value(node) {
    return node.getAttribute('src');
  }

  format(name, value) {
    // Handle unregistered embed formats
    if (name === 'height' || name === 'width') {
      if (value) {
        this.domNode.setAttribute(name, value);
      } else {
        this.domNode.removeAttribute(name, value);
      }
    } else {
      super.format(name, value);
    }
  }
}
VideoBlot.blotName = 'video';
VideoBlot.tagName = 'iframe';

コンソールを開いて電話をかける場合は注意してくださいgetContents, Quill はビデオを次のように報告します。

{
  ops: [{
    insert: {
      video: 'https://www.youtube.com/embed/QHH3iSeDBLo?showinfo=0'
    },
    attributes: {
      height: '170',
      width: '400'
    }
  }]
}

ツイート

Medium は多くの埋め込みタイプをサポートしていますが、このガイドではツイートにのみ焦点を当てます。ツイートブロットは、以下とほぼ同じように実装されています。画像。埋め込みブロットが void ノードに対応する必要がないという事実を利用します。これは任意のノードにすることができ、Quill はそれを void ノードのように扱い、その子や子孫を走査しません。これにより、<div>そしてネイティブの Twitter Javascript ライブラリは、その内部で必要なことを実行します。<div>当社が指定するコンテナ。

ルート Scroll Blot も使用しているため、<div>、また、className曖昧さをなくすために。インラインブロットでは使用することに注意してください<span>とブロックブロットの使用<p>デフォルトでは、これらのタグをカスタム ブロットに使用したい場合は、classNameに加えてtagName

ブロットを定義する値としてツイート ID を使用します。繰り返しになりますが、クリック ハンドラーは静的な値を使用して、無関係な UI コードから気が散ることを避けます。

class TweetBlot extends BlockEmbed {
  static create(id) {
    let node = super.create();
    node.dataset.id = id;
    // Allow twitter library to modify our contents
    twttr.widgets.createTweet(id, node);
    return node;
  }

  static value(domNode) {
    return domNode.dataset.id;
  }
}
TweetBlot.blotName = 'tweet';
TweetBlot.tagName = 'div';
TweetBlot.className = 'tweet';

仕上げ磨き

私たちは、たくさんのボタンと、平文を理解するだけの Quill コアから始めました。 Parchment を使用すると、太字、斜体、リンク、引用符、ヘッダー、セクション区切り、画像、ビデオ、さらにはツイートを追加することができます。これらすべては、予測可能で一貫性のあるドキュメントを維持しながら行われ、Quil の強力な機能を使用できるようになります。APIこれらの新しい形式とコンテンツを使用して。

最終的な磨きを加えてデモを完成させましょう。 Medium の UI とは比較できませんが、近づけるように努力していきます。


オープンソースプロジェクト

Quill は次によって開発および保守されています。スラブ。 BSD の下で寛容にライセンスされています。個人または商業プロジェクトで自由に使用してください。
8,000