某掲示板投稿時にずれない用、HTMLの空白周りの挙動に対応したAAプレビューを作ってみた

某掲示板投稿時にずれない用、HTMLの空白周りの挙動に対応したAAプレビューをWebComponents(Stencil)で作ってみる記事です。

## AA投稿に影響するHTMLの空白周りの挙動って？
行頭の半角と途中の半角空白が連続すると削除される仕様です。こちらエディタでは普通に使えるため投稿するまで非常に気がつきにくいところなんです。例えば下のモナー、めっちゃ顔ずれてますよね？

![Screen Shot 2018-12-23 at 1.42.11.png](https://qiita-image-store.s3.amazonaws.com/0/165335/e9e14f08-1b26-21d1-5252-fe3aedec21fb.png)

でも、このズレは半角空白なので実際、掲示板に投稿しても下のように表示されます。特にずれてませんね。
![Screen Shot 2018-12-23 at 1.43.03.png](https://qiita-image-store.s3.amazonaws.com/0/165335/82a35e7c-1549-750c-c048-b63b78961548.png)

今回、この挙動に対応したAAプレビューを作りました。


## 完成品
下のリンクをクリックしてね。
https://b.aahub.org/preview
![Screen Shot 2018-12-23 at 1.46.25.png](https://qiita-image-store.s3.amazonaws.com/0/165335/60497701-80f4-fe70-66fa-04590e013bc7.png)

テキストエディタ部分とプレビュー部分わけることでHTMLの空白のズレに準拠した形でAAを表示していますね。

## 実装内容
今回もStencilを使っています。取り回ししやすいようにIonicは使ってません。処理の内容は大きく分けて以下のとおり。

1. textareaのinputイベントをハンドリングする
2. inputイベントが走ったとき、textareaの内容をpreviewへinnerHtmlで描画する

以上です。

```typescript:src/components/app-preview/app-preview.ts
import { Component, Element, State } from "@stencil/core";

@Component({
  tag: "app-preview",
  styleUrl: "app-preview.css"
})
export class PreviewPage {
  @State() aa: string = "";
  @Element() el: HTMLElement;

  setTextareaHeight(ta) {
    ta.style.fontSize = "12px";
    ta.style.lineHeight = "12px";
    ta.style.height = "30px";

    const minHeight = 300;
    if (ta.scrollHeight > ta.offsetHeight) {
      ta.style.height = ta.scrollHeight + "px";
    } else {
      var height, lineHeight;
      let idx = 0;
      while (true) {
        idx = idx + 1;
        height = Number(ta.style.height.split("px")[0]);
        lineHeight = Number(ta.style.lineHeight.split("px")[0]);
        ta.style.height = height - lineHeight + "px";
        if (
          ta.scrollHeight > ta.offsetHeight ||
          minHeight > ta.scrollHeight ||
          idx > 50000
        ) {
          ta.style.height = ta.scrollHeight + "px";
          break;
        }
      }
    }
  }

  getBytes(text: string = "") {
    let count = 0;
    for (let i = 0; i < text.length; i++) {
      let n = encodeURIComponent(text.charAt(i));
      if (n.length < 4) count++;
      else count += 2;
    }
    return count;
  }
  textInput(el) {
    this.setTextareaHeight(el.srcElement);
    this.aa = el.srcElement.value;

    let value = this.aa.replace(/</g, "&lt;");
    this.el.querySelector("#preview").innerHTML = value.replace(
      /\\n|\r\n|\r|\n/g,
      "<br>"
    );
  }

  componentWillLoad() {
    this.aa = "";
    let textarea: any = this.el.querySelector("#aa-textarea");
    textarea.value = "";
    this.el.querySelector("#preview").innerHTML = "";
  }

  render() {
    return [
      <div class="content-wrapper">
        <div class="u-pt20 u-pl8">{this.getBytes(this.aa)}byte</div>
        <div class="input-wrapper">
          <div class="input-aa-wrapper u-flex-wrap">
            <div class="textarea-wrapper">
              <textarea
                id="aa-textarea"
                class="input-aa"
                placeholder="アスキーアートを入力"
                onInput={e => this.textInput(e)}
              />
            </div>
            <div class="preview-wrapper">
              <div class="scroll-wrapper">
                {(() => {
                  if (this.aa == "") {
                    return (
                      <div class="u-aa">
                        プレビュー<br />行頭の半角と途中の半角2以上は除去されます
                      </div>
                    );
                  }
                })()}
                <div id="preview" />
              </div>
            </div>
          </div>
        </div>
      </div>
    ];
  }
}
```

CSSは長いので割愛。

## ソースコード
[https://github.com/AAHub/AAPreviewComponent](https://github.com/AAHub/AAPreviewComponent)


## まとめ
以上、StencilでAAプレビューのWebComponentsを作りました。だいぶ、Stencilにも慣れてきてこれくらいのものなら10分くらいでつくれそうです(その他細かい仕様をしっているからというのもありますが)

Stencil、超おすすめなのでまだ触ったことない人はすぐさわってみてください。　
https://stenciljs.com/

それでは、