4
2

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 3 years have passed since last update.

esa.ioのエディタにリンター機能を付加する実験の記録

Posted at

この投稿は、esa.ioのエディタをハックして、リンター機能を付与できないかを検証した記録です。

esa.ioはチーム向けドキュメント共有サービスで、Markdownでドキュメントを書けることからQiitaと使い勝手が似ているウェブサービスです。

リンター機能とは

リンターはプログラムコードの良くない書き方を指摘してくれるツールです。たとえば、JavaScriptならeslintが有名です。

Markdownを対象としたリンターもあります。markdownlintやremark-lintがそれです。これらを使うと、Markdownの書き方で良くないところを指摘してもらえます。

日本語の校正を目的とした、textlintというのもあります。これは日本語の表現で直すべきところを指摘してくれるツールです。

なぜリンター機能を付与したいか

複数人でドキュメントを書いていると、書き方で気になるところが出てきます。たとえば、ドキュメント同士のリンクの書き方です。Markdownでサイト内リンクのURLを指定する方法として、https://から書き始める方法と、ドメイン部分を省略して絶対パスで書く方法があります。esaでは絶対パスで書くほうが望ましいです。ドメイン名はいつでも変更できるので、もしドメイン名が変わるとリンク切れになります。

[リンク](/posts/123)[リンク](https://example.esa.io/posts/123)

他にも、リスト記法を-*どちらで統一するとか、インデントは4スペースにするとか、細かい統一感が出したかったです。そのために以前、esa Webhookを使ってMarkdownを整形するツールを作ってみたりもしました。これはこれで便利なのですが、書いているときにフィードバックが無いのが課題でした。

そこで、esaエディタにリンター機能をつけられたらいいなと思いました。

esaエディタの仕様

esaのエディタはCode Mirrorというオープンソースのエディタをベースに作られています。リンター機能をつけるには、Code Mirrorをいじる必要があります。

esaエディタインスタンスのありか

Code Mirrorにふれるには、Code Mirrorのオブジェクトを取り出さないとなりません。これをするには、次のようなコードで.CodeMirror要素からCodeMirrorプロパティを参照するだけです。

const editor = document.querySelector(".CodeMirror").CodeMirror

Code Mirrorのリンター機能

Code Mirrorにはlint.jsというリンター機能のフレームワークになるアドオンがあります。これを利用するのが、esaエディタにリンターをつける近道です。

esaエディタにリンターをつける方法

lint.jsをesaの投稿ページにロードしてやれば、基本的にリンター機能のインストールができるのですが、lint.jsはグローバルオブジェクトにCodeMirrorプロパティを必要としているので、esaエディタのCodeMirrorオブジェクトをグローバル変数化してやる必要があります。注意点なのですが、lint.jsが依存するCodeMirrorオブジェクトはエディタのインスタンスではなく、コンストラクタです。なので、上で取り出したeditorをグローバル変数化しても意味がありません。esaのCodeMirrorコンストラクタはスコープが閉じたところにあるので直接アクセスできません。そこで、editor変数からコンストラクタを取り出す方法で対処します。

window.CodeMirror = editor.constructor;

これでlint.jsをロードするための条件がととのいます。あとは、次のファイルを動的にロードしてやればOKです。

最後に、エディタインスタンスeditorにリンターを追加するとリンターが動くようになります。

function lint(text) {
  return [
    {
      severity: "error",
      from: { line: 0, ch: 11 },
      to: { line: 0, ch: 40 },
      message: "リンクにはドメインを含めないようにしましょう。例: /posts/123",
    },
  ];
}

// setup gutter
editor.setOption("gutters", ["CodeMirror-lint-markers"]);
document.querySelector(".CodeMirror-lint-markers").style.width = 0;

// load addon
await loadJavaScript("https://codemirror.net/addon/lint/lint.js");
await loadStylesheet("https://codemirror.net/addon/lint/lint.css");

// enable lint feature
editor.setOption("lint", lint);

esa.ioのエディタにリンター機能を付加する技術的な解説は以上です。

完成版のコード

ここまでの手順をコードにしたものが次になります。これをGoogle ChromeのDevToolのコンソールで実行すると、1行目の11文字目〜40文字目に警告が表示されます。

20211110_121652.png

main();

async function main() {
  const editor = discoverCodeMirrorInstance();
  globalifyCodeMirrorClass(editor);
  await enableLinter(editor, lint);
}

function lint(text) {
  return [
    {
      severity: "error",
      from: { line: 0, ch: 11 },
      to: { line: 0, ch: 40 },
      message: "リンクにはドメインを含めないようにしましょう。例: /posts/123",
    },
  ];
}

function discoverCodeMirrorInstance() {
  return document.querySelector(".CodeMirror").CodeMirror;
}

function globalifyCodeMirrorClass(editorInstance) {
  window.CodeMirror = editorInstance.constructor;
}

async function enableLinter(editor, lint) {
  // setup gutter
  editor.setOption("gutters", ["CodeMirror-lint-markers"]);
  document.querySelector(".CodeMirror-lint-markers").style.width = 0;

  // load addon
  await loadJavaScript("https://codemirror.net/addon/lint/lint.js");
  await loadStylesheet("https://codemirror.net/addon/lint/lint.css");

  // enable lint feature
  editor.setOption("lint", lint);
}

function loadJavaScript(url) {
  return new Promise((resolve) => {
    const script = document.createElement("script");
    script.src = url;
    script.addEventListener("load", resolve);
    document.head.appendChild(script);
  });
}

function loadStylesheet(url) {
  return new Promise((resolve) => {
    const link = document.createElement("link");
    link.rel = "stylesheet";
    link.type = "text/css";
    link.href = url;
    link.addEventListener("load", resolve);
    document.head.appendChild(link);
  });
}

このコードをベースにGoogle Chromeの拡張を作ったりしたら、もっと便利になるかと思います。

4
2
1

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
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?