JavaScript
Android
iOS
Codemirror

codemirrorのlint error/warningをモバイルでも表示する

CodeMirrorのlintによるエラーとワーニング表示があります

スクリーンショット 2018-03-24 19.12.17.png

これを使っていたのですが,マウスオーバーでtooltipが表示されるので,マウスのないスマホではエラーがあるのはわかるけど,何がエラーかわからない という状況になりました.

それでは困るので,マークをタップ(というか行番号付近をタップ)した時に,tooltipを表示するようにしてみました.

適当な部分をタップすると消えます.

仕組み

CodeMirrorのtouchendイベントで,マークをタップしたらそこに疑似マウスオーバーイベントを発生させることでtooltipを出す ということをしています.

tooltipをきちんと自分で生成して表示するところまでやれば,疑似マウスオーバーイベントは不要なのですが,
どうもこのtooltipの機能はcodemirrorと密接になっているようで,
その部分だけを取り出すことができなかったため,疑似イベントで対応しています.
(もちろんコピペしたらできますが,同じコードが複数箇所にあるのは好きではないので....)

touchendイベント自体がスマホじゃないと発生しないので,スマホのときだけ動作します.
PCの場合は通常通りマウスオーバーで表示されます.

コード

  CodeMirror.on(cm.display.scroller, "touchend", function (e) {
    let touch = cm.display.activeTouch;
    let date = new Date;
    if (touch &&  touch.left != null &&
        !touch.moved && date - touch.start < 300) {

      //エディタの何処かをsingle tapした
      if (!touch.prev) {

        //行に変換するならこれ
        var pos = cm.coordsChar(touch, "page");

        //gutterの範囲判定
        if(touch.left < Math.floor(cm.display.gutters.getBoundingClientRect().right)){
          showLintTooltip(pos.line);
          return;
        }
      }
    }

    //hide tooltip
    if(currentTooptipTarget) {
      dispatchMouseEvent(currentTooptipTarget, "mouseout", 0, 0);
      currentTooptipTarget = null;
    }
  });


  let currentTooptipTarget = null ;
  function showLintTooltip(line){

    let markers = jsEditor.state.lint.marked;
    let info = jsEditor.lineInfo(line);
    for (let marker of markers) {
      if (marker.lines[0].lineNo() === line) {
        //need to tooltip
        let element = info.gutterMarkers["CodeMirror-lint-markers"];
        if (element) {
          let rect = element.getBoundingClientRect();
          console.log(rect);

          if(currentTooptipTarget == element){
            //hide tooltip
            dispatchMouseEvent(element, "mouseout", rect.x, rect.y);
            currentTooptipTarget = null;
          }else {
            //hide tooltip
            if(currentTooptipTarget) {
              dispatchMouseEvent(currentTooptipTarget, "mouseout", rect.x, rect.y);
            }
            //show tooltip
            dispatchMouseEvent(element, "mouseover", rect.left+rect.width/2, rect.top+rect.height/2);

            currentTooptipTarget = element;
          }
          return;
        }
      }
    }
  }

  function dispatchMouseEvent(elm, type,x,y){

    var mouseMoveEvent = document.createEvent("MouseEvents");

    mouseMoveEvent.initMouseEvent(
        type, //event type : click, mousedown, mouseup, mouseover, mousemove, mouseout.
        true, //canBubble
        false, //cancelable
        window, //event's AbstractView : should be window
        1, // detail : Event's mouse click count
        x, // screenX
        y, // screenY
        x, // clientX
        y, // clientY
        false, // ctrlKey
        false, // altKey
        false, // shiftKey
        false, // metaKey
        0, // button : 0 = click, 1 = middle button, 2 = right button
        null // relatedTarget : Only used with some event types (e.g. mouseover and mouseout). In other cases, pass null.
    );

    elm.dispatchEvent(mouseMoveEvent);
  }

注意点

上のコードで表示はできるようになったものの,iOS(safari)のcssにバグ(?)があるようで,position:fixedでの位置指定が上手く行かないようでした.(縦方向にずれる)
その場合はcssでposition:absoluteでゴニョゴニョしてあげるとちょうどよくなると思います.

なお,Androidでは(自分の試した限り)問題なく動いています.

その他

iosでcodemirrorを使う場合,日本語入力が変になる問題があるので,こっちの対応も必須になると思います(参考にさせていただきました)
https://qiita.com/ttakuru88/items/e363267772cacf4c1fa2

そもそもスマホでコードを書きたいって需要はどのくらいあるんだろう・・・?