のエディタをクローニングする


私は最近、私のポートフォリオプロジェクトの一部としてQuora Webアプリの完全なスタッククローンを作り始めました.私はこれのためにgolangとvuejsを使っています、そして、フロントエンドのより挑戦的な部分のうちの1つはカスタムWYSIWYGエディタでした.

内容


Comparison
A suitable editor
Features
Challenges
Implementation

オリジナルとの比較


技術空間の企業が自分の社内道具を開発するのは珍しいことではない.いくつかの研究を行った後、私はあなたが見たリッチテキストエディタを発見したquora 社内で開発され、オープンソースではない.もちろん、これは複製するのが難しいビットになります.私はオープンソースエディタの多面からのピッキングを使用するオプションを残していた(男の子はそこにそんなに…)そして、それを見て、quoraのように振る舞うことをカスタマイズします
私のキーボードと一定の頭痛をたたきの2日後に、私は最終的にそれを引き離しました、そして、私はそれがクローン(まるで私が知っている非常に大胆な主張)のようにオリジナルの観察をすると言います.
元のVSクローン


適切なエディタの選択


WYSIWYGエディタに精通している場合は、悪名高く知られている知っているよあなたのニーズにカスタマイズするのは難しい.今、あなたは最初から独自のエディタを書くことができますが、それはほとんどの場合、特に1つのオフポートフォリオプロジェクトの苦悩の価値がない暗い暗い道です.
より人気のあるエディタの一握りを考慮した後(CKEditor、tinymce、froala、quilljs、editorjs)、私はquilljs いくつかの理由に最適です.
  • きれいでクリーンなAPIを持っています
  • それはよく文書化されている
  • それは合理的にカスタマイズ可能です
  • カスタムモジュール
  • 特長クオラのエディタ


    エディタ自体は完全に複雑ではありません.これは一般的な書式設定オプションのほとんどがありますbold text, italic, ordered and unordered lists, blockquotes, code-blocks, embedded media, HTML links, and edit history . 一般的なthoは、あなたがタイプすることができる言及機能です@ ユーザー、スペース、またはトピックの一覧から選択するシンボル.また、ツールバーは、1つだけで一度に表示されるように上下にスライドする3層を持っています.あまり複雑ではない.

    挑戦


    私が直面した課題のいくつかは、以下を含みます
  • スライディングツールバーの実装
  • Quillのデフォルトイメージハンドラのオーバーライド
  • 記述機能の実装
  • 取り扱い複数のエディタ
  • 実装


    私は、色とアイコンの私自身の少しの改善で、FigmaでUIを引き出すことによって始まりました.

    私はフロントエンドを構築しているのでVue , 私はコンポーネントライブラリを使用して私の人生を容易にすることを考え出した.私は2つの人気のあるものを見つけましたvue-quill-editor and vue2-editor ). どちらも非常に良いですが、私はVue - Quillエディタでは、ツールバーのためのより良いカスタマイズを提供した.しかし、それは私の自身のイメージハンドラを実装することになった.後でそれ以上.
    エディタの設定は簡単です.
    // register globally
    // [main.js]
    import VueQuill from "vue-quill-editor";
    import 'quill/dist/quill.core.css' // import styles
    import 'quill/dist/quill.snow.css' // for snow theme
    
    //---
    Vue.use(VueQuill);
    //---
    
    
    // [Editor.vue]
    <template>
       <quill-editor 
          class="editor" 
          v-model="content" 
          :options="editorConfig['options']" 
          @ready="onEditorReady($event)" 
          ref="editor"
       >
          <div :id="'toolbar'+toolbarId" slot="toolbar">
          // toolbar markup
             <button class="ql-bold" type="button"><button/>
          </div>
       </quill-editor/>
    </template>
    <script>
    export default {
       //---
       name: "Editor",
       data: () => ({
          content: ""
       }),
       computed: {
          editorConfig: function() {
             return {
                options: {
                   placeholder: "Write your answer...",
                   modules: {
                      toolbar: "#toolbar"
                   } 
                }
             }
          } 
       }
    }
    </script>
    
    これはエディタの基本的な設定です.クルスは、いくつかのCSSスタイルをオーバーライドすることにより、エディタの外観をカスタマイズすることができます.このように<quill-editor> コンポーネントは、名前付きスロットtoolbar . これは我々が我々の独自のツールバーのマークアップを書くことができますが、また、クールスはロジックを処理させることです.
    たとえば、特定のクラス名を持つボタン要素を探しますql-bold ) これは、そのツールバー要素の機能を定義します.しかし、私たちは私たち自身のツールバーのマークアップとスタイルを定義することができますが、便利なスタイルを参照してください<head> . これは、注入されたスタイルが優先されるので、我々はマークアップを制御しないエディタの他の部分のスタイルを難しくすることができます.
    では、どうやってこれに取り組むのですか?私たちはプログラムも私たち自身のスタイルを注入する!同じセレクタを使用して、ドキュメントヘッドに独自のCSSスタイルを注入することにより、我々はどんなスタイルの悪臭を注入オーバーライドすることができます.それで、技術的に、我々は彼ら自身のオーバーライドを越えます邪悪な笑顔

    いくつかの方法がある.非常にシンプルなものはstyle エレメント付きdocument.createElement() また、以下のようにスタイルを設定します.
    const injectStyles = () => {
       const style = document.createElement("style");
       style.innerHTML = `
       selector {
          property: value
       }
       `
    }
    
    // call injectStyles in the `mounted` lifecycle hook
    
    次に、VEEのライフサイクルフックを利用して、エディターコンポーネントがマウントされるたびにこのメソッドを呼び出します.私は、私がちょうど私の検査官タブを開けて、私がオーバーライドしたい各々の要素のために選択者クィル使用を見つけることによって、私が意志をエディタに曲げさせることができたので、これが非常に強力であるとわかりました.エディターコンテナーの背景色を変更したい場合は、単純にそのクラスを使用して要素を対象とすることができます.ql-container.ql-snow .

    I won't be able to write all the code for the editor in this post but here's a link to the GitHub repo for your perusal.


    ツールバーに戻る.Quoraのエディタを見れば、ツールバーには3つの層があることがわかります.これを実装する非常に簡単な方法は、ツールバーに指定された高さを与えることです44px ), ツールバーと同じ高さを持っている3つの他のコンテナをラップして、層として機能するツールバーの中に容器を持ってください.アイデアは、上の要素を作るためには、ツールバーの下には、ツールバーからオーバーフローをカバーするようにZ -インデックスを使用して前に座ることです.我々はプログラムを上下にツールバーを引くことができますtop or transform: translate-y ) 値によって、素晴らしい遷移効果を加えている間、ツールバーの高さに等しい.このようにして、一度に1層しか見えない.

    We could have given the toolbar a negative z-index but that will hinder pointer events so we give the elements above and below a higher z-index instead.


    私は、あなたが現在GISTを得ると思います.ただし、ツールバーとスタイルに応じて、各ボタンを配置することができます.

    取り扱い画像


    エディタの次の機能は画像処理です.デフォルトでは、画像をアップロードするとき、Quillはblobに変換します.しかし、それは我々が望むものでありません.我々は空想を取得し、クラウドやアマゾンのS 3のようなプラットフォームに画像を保存し、URLを返し、そのURLをエディタで埋め込む.このように、独自のイメージハンドラを定義することで、これを行うことができます.
    // quill - quill instance, can be gotten from the editor ref
    const quill = this.$refs['editor']
    
    const handleImageUpload = () => {
       const fileInput = document.createElement("input");
       fileInput.setAttribute("type", "file");
       fileInput.click();
    
       fileInput.onchange = () => { // Listen for image upload
          const file = fileInput.files[0];
          const formData = new FormData();
          formData.append('image', file);
    
          if (/^image\//.test(file.type)) {
             // handle server request and return url
             const url = await serverRequest(formData);
             // embed url in editor
             const range = quill.getSelection();
             quill.insertEmbed(range.index, "image", url);
          } else {
             console.warn("[WARNING]: You can only upload images")
          }
    
       }
    }
    
    上記の関数はtype="file" , アップロードイベント(すなわち、ローカルシステムからファイルを選択するとき)を聞き、サーバーにイメージを送信し、エディタに埋め込まれたURLを返します.次のようにして、この関数をキューイルモジュールとして登録できます:
    // ---
    onEditorReady: function(quill) {
       quill.getModule("toolbar").addHandler("image", () => {
          this.handleImageUpload();
       });
    }
    //---
    
    onEditorReadyready イベントは、クエイルエディタコンポーネントから送信されます.

    言及機能


    言及の特徴は、別のトリッキーな部分だった.基本的に、あなたがタイプするとき@ または@ ボタンは、検索バーを使用してユーザー、スペース、およびトピックの一覧を示しています.サードパーティを見つけましたmodule それはこれを実装します、そして、私はちょうど外観をカスタマイズしなければなりませんでした.
    <script>
    import "quill-mention";
    import { suggestions } from "../constants/suggestions";
    
    //---
    handleEditorMention: function(searchTerm, renderList, mentionChar) {
       let values;
    
       if (mentionChar === "@") {
          values = suggestions;
       }
    
       if (searchTerm.length === 0) {
          renderList(values, searchTerm);
       } else {
          const matches = [];
          for (let i = 0; i < values.length; i++)
             if (
             ~values[i].value.toLowerCase().indexOf(searchTerm.toLowerCase())
             )
             matches.push(values[i]);
          renderList(matches, searchTerm);
       }
    },
    //---
    </script>
    
    このモジュールは、インポートした後に自身を登録します.基本的に、あなたのリストがありますsuggestions 理想的には、あなたは
    サーバにリクエストし、このリストを取得する@... . もう一度、あなたのスタイルのドロップダウンリストのあなたのスタイルを書くのは簡単ですinjectedStyles 関数.

    複数のエディタ


    これは別のつまずいたブロックだった.私は、同じページの2つ以上のエディタインスタンスを持っていることを発見しました、彼らが同じツールバーIDによって参照されているように、ツールバーで混沌を引き起こします.しかし、私のエディタコンポーネントは、順番にループでレンダリングされた親コンポーネントでレンダリングされたので、どのように我々は常にユニークなIDを確保するのですか?
    ライブラリのようなユニークなIDを生成するshortid そして、エディタのコンポーネントにプロップとして渡します.その後、ツールバーIDと組み合わせることができます.
    <template>
       <quill-editor 
          class="editor" 
          v-model="content" 
          :options="editorConfig['options']" 
          @ready="onEditorReady($event)" 
          ref="editor"
       >
          // id is merged with the generated shortid
          <div :id="'toolbar'+toolbarId" slot="toolbar">
          // toolbar markup
             <button class="ql-bold" type="button"><button/>
          </div>
       </quill-editor/>
    </template>
    
    それはQuoraのような独自のカスタムエディタを構築する方法の簡単な概要です.うまくいけば、あなたはこれが役に立つことがわかりました.もう一度、コードはこれで利用できますrepository . 興味があればそれをチェックすることができます.それは良い挑戦でした、そして、私は完全なプロジェクトに取り組み続けます.下記のあなたの考えを共有するか、質問をしてください、私は議論に開いています.