めもてう

忘れっぽいTIPs、HowToのメモ帳です。

はてな記法で<h2>タグを意地でもちゃんと使う

前回はてな記法にて<h2>タグをちゃんと使うと言っておきながら使えませんでした。

が!やっぱり<h2>を使いたい!
使ってやるぜ!

と、いうことで使えるようにしました。

そもそもの原因

仕様です。

どうも、はてな記法からHTMLへ変換する際に見出し記法以外の見出しタグや<div>構造を確認するように出来ていない感じなので。どうしようもないです。

解決方法

<div>コンテナ、見出し全部HTMLタグ直打ちします。
とはいえ、そんなことやってられないので、Chrome拡張機能をつくりました。

はてな記法エディタ拡張(野良

chromeマーケットのアカウント持ってないので野良拡張です。

注意事項

  • この拡張機能は自身で使うために開発したもので、今後のバージョンアップ、バグフィックスをお約束することはありません。
  • この拡張機能を使用して被るいかなる被害、損害も補償いたしかねますので、ご使用の際は自己責任でお願いします。

インストール

野良拡張ですので解凍の後「デベロッパーモード」にて、「パッケージ化されていない拡張機能を読み込む」でインストールしてください。

ソースコードは後述しています。

使い方

インストールして有効化したら、はてなブログの記事編集ページへボタンが追加されます。

f:id:Hobbyist:20190331222035p:plain

すでに、編集ページを開いていた場合、更新をする必要がありますので一旦記事を保存してブラウザの更新をしてください。

では、ちゃんと動作するか確認をしてみましょう。
はてな記法で、

{ h2
{ h3
}
}

と、書いて、「HTML」ボタンを押してください。

><div class="section"><
><h2>h2</h2><
><div class="section"><
><h3>h3</h3><
></div><
></div><

と、変換できていたらOKです。

次に、「UnFormat」ボタンを押してください。
変換前へ戻ったらOKです。

書式と解説

{}の間がブロックとして判断されます。
{}は半角で行の先頭にないといけません。
{の後に続く同じ行の文字列は見出しとして使われます。
}の後に続く同じ行の文字列は破棄されるので注意してください。

入れ子にした場合、見出しの大きさは自動で調整されます。
見出しの大きさはh2h6までです。5階層以下の見出しサイズは全てh6となります。

ブロックスタート行{、ブロックエンド行}以外では普通にはてな記法を使用できます。

ソースコード

ファイル構成

  • [RootDirectory]

manifest.json

{
  "name": "HatenaKihou editor extension",
  "version": "1.0.0",
  "manifest_version": 2,
  "content_scripts": [
    {
      "matches":["https://blog.hatena.ne.jp/*/*/edit*"],
      "js": ["js/content.js"],
      "css": ["css/content.css"]
    }
  ]
}

content.css

.editor-toolbar-extension {
  padding-bottom: 1px;
  margin-bottom: 4px;
}

.toolbar-extension-button{
  display: inline-block;
  font-family: consolas;
  width: 9rem;
  height: 2rem;
  user-select: none;
  margin-bottom: 3px;
}

.toolbar-extension-button:hover {
  cursor: pointer;
  margin: 0;
  color: #f80;
  border-bottom: 3px solid #f80;
}
.toolbar-extension-button-label {
  font-family: consolas;
  font-weight: bold;
  line-height: 2rem;
  font-size: 0.9rem;
  text-align: center;
}

ブラウザのバージョンなどは特に考えてないので、もし不具合がありましたら、最悪このファイルは空でも大丈夫です。
ただ、ファイルを削除する場合はマニフェストファイルの編集が必要です。

content.js

//-----------------------------------------------------------
// Entry Script
//-----------------------------------------------------------

var _textElem = null;
var toolbars = document.getElementsByClassName("toolbar");

for(var i = 0; i < toolbars.length; i++) {
  if(toolbars[i].classList.contains("editor-toolbar")) {
    var container = CreateToolbarExtensionContainer();

    // add button
    container.appendChild(Create_h2_FormatterButton());
    container.appendChild(Create_h2_UnFormatterButton());

    // insert extension toolbar to hatena toolbar
    toolbars[i].appendChild(container);

  }
}

//-----------------------------------------------------------
// Create Functions
//-----------------------------------------------------------

//
// CreateToolbarExtensionContainer
//
function CreateToolbarExtensionContainer() {
  var toolbar_ex = document.createElement("div");
  toolbar_ex.classList.add("toolbar-extension");
  toolbar_ex.classList.add("editor-toolbar-extension");

  return toolbar_ex;
}

//
// Create_h2_FormatterButton
//
function Create_h2_FormatterButton() {
  return CreateToolButton({"class" : "h2_formatter_bt", "click": On_h2_FormatterClickListener, "label": "HTML"});
}

//
// Create_h2_UnFormatterButton
//
function Create_h2_UnFormatterButton() {
  return CreateToolButton({"class" : "h2_unformatter_bt", "click": On_h2_UnFormatterClickListener, "label": "UnFormat"});	
}

//
// CreateToolButton
//
function CreateToolButton(args) {
  var bt = document.createElement("div");
  bt.classList.add("toolbar-extension-button");

  if(args == null) {
    return bt;
  }

  // set ID
  if(typeof args.id !== "undefined") {
    var ids = args.id.split(" ");
    for(var i = 0; i < ids.length; i++) {
      bt.classList.add(ids[i]);
    }
  }

  // set Class
  if(typeof args.class !== "undefined") {
    var classes = args.class.split(" ");
    for(var i = 0; i < classes.length; i++) {
      bt.classList.add(classes[i]);
    }
  }

  // set Click Listener
  if(typeof args.click !== "undefined") {
    bt.addEventListener("click", args.click);
  }

  if(typeof args.label !== "undefined") {
    bt.innerHTML = "<p class=\"toolbar-extension-button-label\">" + args.label + "</p>";
  }

  return bt;
}

//-----------------------------------------------------------
// Utils
//-----------------------------------------------------------

//
// ReadTextAreaElement
//
function ReadTextAreaElement() {
  if(_textElem == null) {
    _textElem = document.getElementById("body");
  }
  return _textElem;
}

//
// GetNewLineCharcter
//
function GetNewLineCharcter(text) {
  if(text != null && text != "") {

    // 改行コードの判定と取得
    if(text.search(/\r\n/) != -1) {
      return "\r\n";
    }
    else if(text.search(/\n/) != -1) {
      return "\n";
    }
    else if(text.search(/\r/) != -1) {
      return "\r";
    }
  }

  return "";
}

//-----------------------------------------------------------
// Listeners
//-----------------------------------------------------------

//
// On_h2_UnFormatterClickListener
//
function On_h2_UnFormatterClickListener(){
  var txtElem = ReadTextAreaElement();
  if(txtElem !== null) {

    var text = txtElem.value;
    if(text != "") {
      
      var c = GetNewLineCharcter(text);
      if(c == "") {
        // テキストエリアが空か、一行しかない
        return;
      }
      var v = {
        nlc: c,
        isOpenPre: false,
        isOpenDiv: false,
        otherDivCount: 0
      }
      var lines = text.split(v.nlc);
      var current_hsize = 2;

      text = "";

      for(var i = 0; i < lines.length; i++) {
        if(v.isOpenDiv) {
          text += lines[i].replace(/^><h[2-6]>(.*)<\/h[2-6]></, "{ $1");
          v.isOpenDiv = false;
        }
        else if(v.isOpenPre) {
          if((/^\|\|</).test(lines[i])) {
            v.isOpenPre = false;
          }
          text += lines[i];
        }
        else if((/^>\|.*\|/).test(lines[i])) {
          v.isOpenPre = true;
          text += lines[i];

        }
        else if((/^><div class="section"></).test(lines[i])) {
          if(i + 1 < lines.length) {
            if((/^><h[2-6]>.*<\/h[2-6]></).test(lines[i+1])){
              v.isOpenDiv = true;
            }
            else {
              text += lines[i];
            }
          }
          else {
            text += lines[i];
          }
        }
        else if((/^>?<div.*><?/).test(lines[i])){
          text += lines[i];
          v.otherDivCount++;

        }
        else if((/^>?<\/div><?/).test(lines[i])){
          if(v.otherDivCount > 0) {
            v.otherDivCount--;
            text += lines[i];
          }
          else {
            text += lines[i].replace(/^><\/div></,"}");
          }
        }
        else {
          text += lines[i];
        }
      

        if(!v.isOpenDiv) {
          if(!(/^ *$/).test(lines[i]) || i + 1 < lines.length){
            text += v.nlc;
          }
        }
      }

      txtElem.value = text;
    }

    txtElem = null;
  }
}

//
// On_h2_FormatterClickListener
//
function On_h2_FormatterClickListener() {
  var txtElem = ReadTextAreaElement();

  if(txtElem !== null) {

    var text = txtElem.value;
    if(text != "") {
      var c = GetNewLineCharcter(text);
      if(c == "") {
        // テキストエリアが空か、一行しかない
        return;
      }
      var v = {
        nlc: c,
        hsize: 2,
        isOpenPre: false
      }
      var lines = text.split(v.nlc);
      var current_hsize = 2;

      text = "";
      
      for(var i = 0; i < lines.length; i++) {
        if(v.isOpenPre) {
          if((/^\|\|</).test(lines[i])) {
            v.isOpenPre = false;
          }
          text += lines[i];
        }
        else if((/^>\|.*\|/).test(lines[i])) {
          v.isOpenPre = true;
          text += lines[i];

        }
        else if((/^\{ *.*/).test(lines[i])) {
          current_hsize = v.hsize;
          if(current_hsize > 6) {
            current_hsize = 6;
          }
          text += lines[i].replace(/^\{ *(.*)/, "><div class=\"section\"><" + v.nlc + "><h" + current_hsize + ">$1</h" + current_hsize + "><");
          v.hsize++;
        }
        else if((/^\}/).test(lines[i])) {
          current_hsize = v.hsize;
          if(current_hsize > 6) {
            current_hsize = 6;
          }
          text += lines[i].replace(/^\}.*/,"></div><");
          v.hsize--;
        }
        else {
          text += lines[i];
        }

        if(!(/^ *$/).test(lines[i]) || i + 1 < lines.length){
          text += v.nlc;
        }
      }

      for(;v.hsize > 2; v.hsize--) {
        text += "></div><" + v.nlc;
      }

      txtElem.value = text;
    }

    txtElem = null;
  }
}

<textarea>から文字を読み込んで1行ずつ判定するという地獄みたいな実装になっています。

「HTML」ボタンがクリックされるとOn_h2_FormatterClickListenerが、「UnFormat」ボタンをクリックでOn_h2_UnFormatterClickListenerが実行されます。

最後に

ソースコードの中身と、そこまでしてセクションを分けたいか?という現実はさて置き、中々使えるものが出来たのではないでしょうか?
これでやっと<h2>が使えるようになりました。

最後になりますが、この記事で紹介しているコードを使用する際は自己責任でおねがいします。

プライバシーポリシー (Privacy policy)