1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

中学生が自分だけのMarkdownエディターを作ってみた話

Last updated at Posted at 2023-03-09

続編・https://qiita.com/I-AM-RAILWAY-FAN/items/7b8752dbfe9d08be2800

はじめに制作物

自己紹介

こんにちは
現役中学生の「よー」です。
今回は、自分だけのオリジナルマークダウンエディターをJavaScriptで作ってみた話をしたいと思います。

<基礎知識>Markdownとは

Markdownは、文書を記述するための軽量マークアップ言語のひとつである
要は、メモ帳だと装飾ができなくて寂しいので作られた言語、っていうイメージです(爆)

要件

  • 誰でも使えるよう、機能は必要最低限に。
  • 非常に軽いレスポンス。
  • CDNを使用せず、継続的な運用を
  • HTMLへの変換も楽々
  • それなりにちゃんとしたリポジトリにする(github)

制作リポジトリ

html、css、javascriptにて制作しました。
libフォルダの中身は、ライブラリ集なので私はほぼ関知しておりません。

index.html

<html lang="ja">
    <head>
        <title>Markdownエディターのまくまく</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <script src="https://yo-train-ch.github.io/Markdown-editor-MAKUMAKU//lib/bootstrap-5.0.2/js/bootstrap.bundle.min.js"></script>
        <link href="https://yo-train-ch.github.io/Markdown-editor-MAKUMAKU//lib/bootstrap-5.0.2/css/bootstrap.min.css" rel="stylesheet">
        <script src="https://yo-train-ch.github.io/Markdown-editor-MAKUMAKU//lib/jquery/jquery-3.6.3.min.js"></script>
        <script src="https://yo-train-ch.github.io/Markdown-editor-MAKUMAKU//lib/marked/marked.min.js"></script>
        <!-- highlight.js -->
        <link rel="stylesheet" href="https://yo-train-ch.github.io/Markdown-editor-MAKUMAKU//lib/highlightjs/style.css">
        <script src="https://yo-train-ch.github.io/Markdown-editor-MAKUMAKU//lib/highlightjs/highlight.min.js"></script>
        <style>
            pre, code, var, samp, kbd, .mono {
                /*等幅フォント化*/
                font-family: Consolas, 'Courier New', Courier, Monaco, monospace;
                font-size: 14px;
                line-height: 1.2;
            }
            #editorWrapper #editor {
                height: 500px;
            }
            #preview {
                height: 500px;
                overflow: auto;
                overflow-y: auto;
            }
        </style>
    </head>
    <body class="p-3">
        <div class="container">
            <input class="form-control" type="text" placeholder="title" aria-label="default input example">
            <div class="row pb-2 pt-2">
                <div class="col">
                    <textarea id="editor" name="editor" class="form-control" rows="20" placeholder="Write something here..."></textarea>
                </div>
                <div class="col" id="previewWrapper">
                    <div id="preview"></div>
                </div>
            </div>
            <!-- Button trigger modal -->
            <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">HTMLを表示</button>
            <div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
                <div class="modal-dialog">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title" id="exampleModalLabel">HTML</h5>
                            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                        </div>
                        <div class="modal-body">
                            <textarea id="converted" class="form-control" rows="20"></textarea>
                        </div>
                        <div class="modal-footer">
                            <button type="button" class="btn btn-primary" data-bs-dismiss="modal">閉じる</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
                <script>
            window.onload = function(){
                var editorSource = document.querySelector('#editor');
                // MarkdownからHTMLに変換する
                var convertedSource = document.querySelector('#converted ');
                var sync = function(){
                    var value = editorSource.value;
                    var md = marked(value);		
                    var convertedSource = document.getElementById('converted').value = md;
                    //プレビュー機能
                    var previewSource = $('#preview').html(md);
                    
                    $('pre code').each(function(i, block) {
                        hljs.highlightBlock(block);
                    });
                    
                    
                    //自動見出し位置合わせ
                    var editor_code = document.getElementById("editor").value;
                    var editors = document.getElementById("editor");
                    var previewer = document.getElementById("preview");
                    var pos      = editors.selectionStart;
                    var before   = editor_code.substr(0, pos);
                    var count = (before.match(/#+ (w*[・一-龠_ぁ-ん_ァ-ヴーa-zA-Za-zA-Z0-9]+|[a-zA-Z0-9_]+|[a-zA-Z0-9_]w*)/g) || []).length;
                    var elm = document.getElementById('preview');
                    var counts = elm.querySelectorAll('h1,h2,h3,h4,h5,h6');
                    
                    var hElems = elm.querySelectorAll("h1, h2, h3, h4, h5, h6");
                    var elem = hElems[count - 2];
                    var main = document.getElementById('preview');
                    main.scrollTop = elem.offsetTop;
                };
                editorSource.oninput = sync;
                sync();
            };
            $('#editor').on('keydown', function(e){
                if (e.keyCode === 9) {
                    e.preventDefault();
                    var elem = e.target;
                    var val = elem.value;
                    var pos = elem.selectionStart;
                    elem.value = val.substr(0, pos) + '\t' + val.substr(pos, val.length);
                    elem.setSelectionRange(pos + 1, pos + 1);
                }
            });
        </script>
    </body>
</html>

最低限、動くものが作れました。
変数宣言とか、だんだん面倒くさくなって(セキュリティーもいらないため)varが続出していますが、気が向いたら直そうかと思っています。

デザインはbootstrap、ライブラリ関係ではjqueryの他markedjsやhighlightjsを使用しています。

難しかったこと

自動位置合わせ(Qiita編集画面でいう、「同時スクロール」)が難しかったです。
メカニズムとしては、編集している文章が何個目の見出し配下にあるか正規表現で調べ、プレビュータブ内指定見出し部分のscrollTopを取得して代入しています。
理想としては、同時スクロールのように全て同時に動かしたかったのですが、技術的にも難しいことや重くしたくないことから、諦めました。

終わりに

ライブラリを使えば簡単にできてしまうし、同時スクロール等、javascriptの技術習得にもなるし、何よりも実用的です。
githubの練習にもなったので、一石四鳥でした。

どうぞ、使ってみてください!

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?