プログラマ・アゲイン blog

還暦を過ぎたけどプログラマ復帰を目指してブログ始めました

ソースコードを貼り付けるボックスを作成 (2)

カスタマイズの項目

前回の「ソースコードを貼り付けるボックスを作成 (1)」の続きです。

ソースコードの貼り付けボックス用に作成したScriptとCSSの内容について、メモります。

 

結果的にカスタマイズした項目は、以下のようになりました。

NO. カスタマイズ項目 修正部分
1 コンソール・コマンド用ブロックのスタイルを新規に作成する

HTML

CSS

2 ソースコード用のブロックを新規に作成する

HTML

Script

CSS

3 ソースコード用のブロックの文字を小さくし行間を詰める

CSS

4

ソースタイプのブロックをソースコードのブロックより少し上に出す

(結果的に、出すと別の問題があり出せませんでした)

CSS

5 ソースコードのテキスト部分に枠と背景色がつくのを消す

HTML

CSS

6 ソースコードを全選択するボタンを新規に作成する

Script

CSS

7 ソースコードを全選択するボタンの表示をソースコードだけにする

Script

8 ソースコードのブロックから外れたら全選択するボタンを消す

Script

順番に書いていこうと思いますが、NO.4とNO.5はデバッグで修正したので、デバッグ方法も含めて次のブログで書こうと思います。

したがって、NO.1/2/3/6/7/8について、順番に書いていきます。

 

カスタマイズ内容

NO.1 コンソール・コマンド用ブロックのスタイルの新規作成

新規にコンソール・コマンド用のブロックにスタイルを割り当てるために、preタグのclass属性を "console" としました。そして、それに対応するスタイルのCSSを作成しました。

基本的には、以前作成した単純なボックスのpreタグのstyle属性で指定していたものを、CSSで指定するようにしました。

追加/変更したのは、borderプロパティの「outset」です。「outset」の指定により、ボーダーで囲まれた領域全体が立体的に隆起したように表示されるようにしました。また、marginプロパティとpaddingプロパティも好みに合わせて調整しました。 

HTML
<pre class="console" data-unlink="" data-lang="">
 ここにコンソール・コマンドなどを記述
</pre>
CSS
pre.console {
    border: 5px outset #c4cbd3;
    font-family: Monaco, Consolas, 'Courier New', Courier, monospace, sans-serif;
    font-size: 1em;
    margin: 0px;
    overflow: auto;
    padding: 10px;
    white-space: pre;
}

 

NO.2 ソースコード用ブロックの作成

新規にソースコード用のブロックにスタイルを割り当てるために、preタグのclass属性を "source" としました。そして、それに対応するJavaScriptとスタイルのCSSを作成しました。

HTML

以前のボックスでは、codeタグを使用していませんでした。しかし、いろいろページを参照していると、<pre><code>...</code></pre>で記述したほうが良いと思い、codeタグを追加しました。そのお陰で苦労したところがあるのですが、それは次回にまとめたいと思います。 

<pre class="source lang-javascript" data-lang="JavaScript"><code class="source-code">
 ここにソースコードを記述
</code></pre>

 

JavaScript

ロジックは変わっていません。

「class="source"」と付いたところをすべて取ってきて、そのタグの中に「...lang...」の文字がある要素のみを処理するようになっています。そして、改行記号で分割して、それぞれの行の先頭に「<div class="source-line">」、行末に「</div>」を動的に追加しています。

変更したのは、以下の点です。

  • 変数名を自分なりに変更
  • varで変数定義していたのを、letとconstに変更
  • 追加するdivタグのクラス名を "source-line" に変更
// New ソースコードの行をdivタブで囲む
<script type="text/javascript">
    let sourceCodes = document.getElementsByClassName('source');
    [].forEach.call(sourceCodes, function(sourceCode) {
        if (!/lang/.test(sourceCode.className)) {
            return;
        }
        const sourceLines = sourceCode.innerHTML.split(/\n/);
        let sourceLine = "";
        sourceLines.forEach(function(line) {
            if(line !== "") {
                sourceLine += '<div class="source-line">' + line + '</div>';
            }
        });
        sourceCode.innerHTML = sourceLine;
    });
</script>

 

CSS

CSSも基本的には変わっていません。

クラス名が変わったので、classセレクタ名が「pre.source」「code.source-code」「div.source-line」になりました。

「pre.source」では、ボックス全体のスタイルを設定しています。

「pre.source::before」では、言語名を表示するボックスのスタイルを設定しています。

「code.source-code」では、テキスト部分のスタイルを設定しています。

「div.source-line」では、counter-incrementプロパティで、定義したカウンタ名に要素の連番(カウンタ)の値を進めています。

「div.source-line::before」では、行番号のスタイルを設定しています。

「div.source-line:nth-child(even)」では、行番号が偶数の背景色を設定しています。

pre.source {
    ・・・
}
pre.source::before {
    ・・・
}
code.source-code {
    ・・・
}
div.source-line {
    counter-increment: linenumber;
}
div.source-line::before {
    ・・・
}
div.source-line:nth-child(even){
    background: #eee;
}

 

NO.3 文字を小さくし行間を詰める

行間を詰めるために、CSSの「pre.source」に設定をしました。

line-heightプロパティで行の高さを指定し、font-sizeプロパティで文字の大きさを指定します。行の高さとフォントサイズの関係ですが、line-heightが16pxでfont-sizeが14pxにしたので、行間として上下均等に1pxずつ割り振られるようにしました。

pre.source {
    ・・・
    font-size: 14px;
    line-height: 16px;
    ・・・
}

 

NO.6 全選択ボタンの作成

全選択ボタンのスタイルも変えたいので、全選択ボタンのIDを「selectBtn」に変更しました。

JavaScript

全選択ボタンの基本部分のロジックは変わっていません。全選択ボタンに付けるIDを「selectBtn」に変更しました。

即時関数で、匿名関数を記述すると同時に実行しています。「function selectPre()」で、全選択ボタンが押された時にpreタグに含まれる要素を選択しています。「for(let i=pres.length; i--;)」で、mouseoverイベントが発生した時のコールバック関数を設定しています。「function addBtn(e)」で、mouseoverイベントで呼ばれて全選択ボタンを表示させています。

変更したのは、以下の点です。

  • varで変数定義していたのを、letに変更
  • btnのidプロパティを"selectBtn"に変更
<script type="text/javascript" defer>
    (function(d) {
        if(!window.getSelection) {
            return;
        }
        let pres = d.getElementsByTagName("pre");
        let btn = d.createElement("button");
        btn.id = "selectBtn";
        btn.textContent = "select all";
        btn.addEventListener("click", selectPre, false);
        function selectPre() {
            let sel = window.getSelection();
            let pre = this.parentNode;
            sel.selectAllChildren(pre);
            sel.extend(pre, pre.childNodes.length-1);
        }
        for(let i=pres.length; i--;) {
            pres[i].addEventListener("mouseover", addBtn, false);
            pres[i].addEventListener("mouseout", delBtn, false);
        }
        function addBtn(e) {
            if (this === addBtn.ele) return;
            if (/lang/.test(this.className)) {
                this.appendChild(btn);
                return addBtn.ele = this;
            }
        }
        ・・・
    })(document);
</script>

 

CSS

IDセレクタ「selectBtn」で、全選択ボタンのスタイルを設定しています。

#selectBtn {
    border: 2px solid;
    border-radius: 5px;
    font-family: consolas;
    position: absolute;
    top: 5px;
    right: 5px;
    padding: 2px 5px;
}

 

NO.7 全選択ボタンの表示をソースコード・ブロックのみにする

全選択ボタンがコンソール・コマンド用ボックスでも表示されるので、ソースコード用ボックスだけを対象にするように、「function addBtn(e)」にクラス名に "lang" の文字が含まれているものだけを対象とする、以下の判断ロジックを追加しました。

            if (/lang/.test(this.className)) {
                ・・・
            }

 

NO.8 ブロックから外れたら全選択ボタンを消す

ブロックからカーソルが外れても全選択ボタンが消えないので、消えるようにしました。

mouseoutイベントのリスナーを追加し、呼ばれるコールバック関数の「function delBtn(e)」を追加しました。

        for(let i=pres.length; i--;) {
            ・・・
            pres[i].addEventListener("mouseout", delBtn, false);
        }
        ・・・
        function delBtn(e) {
            if (/lang/.test(this.className)) {
                this.removeChild(btn);
                return addBtn.ele = null;
            }
        }

 

カスタマイズ結果

最終的に作成したScriptとCSSは、以下のようになりました。

HTML

HTMLは、コンソール・コマンド貼り付け用は preタグに "console" のクラス名をつけ、ソースコード貼り付け用は preタグに "source" のクラス名をつけて区別しています。

<pre class="console" data-unlink="" data-lang="">
 ここにコンソール・コマンドなどを記述
</pre>
<pre class="source lang-javascript" data-lang="JavaScript"><code class="source-code">
 ここにソースコードを記述
</code></pre>

この中で、「<code class="source-code">」の部分については、次のブログで書きます。

 

JavaScript

ソースコードの行をdivタグで囲むScriptは、それまでの物とクラス名を変えたので追加で対応できます。

全選択ボタンの処理をするScriptは、同じイベントをリッスンしているので、追加ではボタンが二つ出てきます。従って、入れ替えました。

// New ソースコードの行をdivタブで囲む
<script type="text/javascript">
    let sourceCodes = document.getElementsByClassName('source');
    [].forEach.call(sourceCodes, function(sourceCode) {
        if (!/lang/.test(sourceCode.className)) {
            return;
        }
        const sourceLines = sourceCode.innerHTML.split(/\n/);
        let sourceLine = "";
        sourceLines.forEach(function(line) {
            if(line !== "") {
                sourceLine += '<div class="source-line">' + line + '</div>';
            }
        });
        sourceCode.innerHTML = sourceLine;
    });
</script>
// New ソースコードを全選択するボタンの処理
<script type="text/javascript" defer>
    (function(d) {
        if(!window.getSelection) {
            return;
        }
        let pres = d.getElementsByTagName("pre");
        let btn = d.createElement("button");
        btn.id = "selectBtn";
        btn.textContent = "select all";
        btn.addEventListener("click", selectPre, false);
        function selectPre() {
            let sel = window.getSelection();
            let pre = this.parentNode;
            sel.selectAllChildren(pre);
            sel.extend(pre, pre.childNodes.length-1);
        }
        for(let i=pres.length; i--;) {
            pres[i].addEventListener("mouseover", addBtn, false);
            pres[i].addEventListener("mouseout", delBtn, false);
        }
        function addBtn(e) {
            if (this === addBtn.ele) return;
            if (/lang/.test(this.className)) {
                this.appendChild(btn);
                return addBtn.ele = this;
            }
        }
        function delBtn(e) {
            if (/lang/.test(this.className)) {
                this.removeChild(btn);
                return addBtn.ele = null;
            }
        }
    })(document);
</script>

 

CSS

CSSは、基本的にセレクタが違うので、追加できます。結果的に、CSSの作成が一番手間取りました。

/* New コンソール・コマンド用ブロックのスタイル */
pre.console {
    border: 5px outset #c4cbd3;
    font-family: Monaco, Consolas, 'Courier New', Courier, monospace, sans-serif;
    font-size: 1em;
    margin: 0px;
    overflow: auto;
    padding: 10px;
    white-space: pre;
}
/* New ソースコード用ブロックのスタイル */
pre.source {
    background-color: #f8f8f8;
    border:2px solid #ccc;
    font-size: 14px;
    line-height: 16px;
    overflow: auto;
    padding: 20px;
    padding-top: 30px;
    position:relative;
}
/* New ソースコード・ブロックに言語名を表示 */
pre.source::before {
    background: #eee;
    border: 3px outset #eceef1;
    border-radius: 10px;
    color: #333;
    content: attr(data-lang);
    display: inline-block;
    font-size: 12px;
    font-weight: bold;
    line-height: 12px;
    margin-left: -10px;
    margin-top: -30px;
    padding: 4px 10px 4px;
    position: absolute;
}
/* New ソースコード・ブロックのコードのスタイル */
code.source-code {
    background-color: transparent;
    border: none;
    color: #333;
}
/* New source-lineクラスの数でカウント */
div.source-line {
    counter-increment: linenumber;
}
/* New 上記でカウントした行番号を擬似要素として表示 */
div.source-line::before {
    color: #ccc;
    content: counter(linenumber);
    display: inline-block;
    padding: 0 15px 0 0;
    text-align: right;
    width: 35px;
}
/* New 上記カウントの偶数行のみ背景色を適用 */
div.source-line:nth-child(even){
    background: #eee;
}
/* New ソースコード全コピー用のボタンのスタイル */
#selectBtn {
    border: 2px solid;
    border-radius: 5px;
    font-family: consolas;
    position: absolute;
    top: 5px;
    right: 5px;
    padding: 2px 5px;
}

この中で、「overflow」と「code.source-code」の部分については、次のブログで書きます。