Summernoteのエディタ「内」に外部CSSを適用する

# 結論 LESSを使う

単なるCSSファイルを読み込む場合もLESSファイルとして読み込む必要があります。
でなければ、スコープ内に限定したCSS適用が出来ません

```css:/admin/css/import.less
.note-editable {
  @import (less) "/css/reset.css";
  @import (less) "/css/base.css";
  @import (less) "/css/styles.css";
}
```

```html:/admin/index.php
<link rel="stylesheet" type="text/css" media="all" href="/admin/plugins/summernote/summernote-bs4.css">

<script type="text/javascript" charset="utf-8" src="/admin/plugins/jquery/jquery.min.js"></script>
<script type="text/javascript" charset="utf-8" src="/admin/plugins/bootstrap-4.1.3/js/bootstrap.bundle.min.js"></script>
<script type="text/javascript" charset="utf-8" src="/admin/plugins/summernote/summernote-bs4.js"></script>

<!-- LESSファイルを読み込む際は rel="stylesheet/less" と書くことに注意 -->
<link rel="stylesheet/less" type="text/css" media="all" href="/admin/css/import.less" />
<script src="//cdnjs.cloudflare.com/ajax/libs/less.js/3.9.0/less.min.js" ></script>

<scirpt>
$(function(){
  $('#sample').summernote();
});
</scirpt>

<textarea name="sample" id="sample"></textarea>
```

# 背景

* 管理画面にSummernoteを使ってWYSIWYGエディタを実装した
* WYSIWYGエディタで入力したHTMLデータはDBに格納され、PHPなどを経由してユーザ向け画面にHTMLとして描写される
* ユーザー向け画面は、デザイナーとか、自分以外の人間が書いたCSSが適用される
* 管理画面のWYSIWYGエディタでも、出来る限りユーザ向け画面と同じ見た目でHTMLが記述できるようにしたい

# やりたいこと

* Summernoteのエディタ「内」に、ユーザ向け画面のCSS(外部CSS)を適用したい

# 何が問題か

* ユーザ向け画面のCSSは今後も変更される可能性があり、自分では編集できない
* SummernoteのHTML表現は、元のHTML内部にdivなどの要素を追加する形である
 * そのまま外部CSSを適用すれば、WYSIWYGエディタの外側である管理画面にもCSSの影響が出て、レイアウト崩れが発生

# 考え方

* SummernoteのWYSIWYGエディタ「内」とは、div.note-editable の中のことである
* このdiv.note-editableの中にだけ、次のような形でCSSが適用できればそれで要件は達成できる

```css
.note-editable h1 {
  font-size: 24px;
}

.note-editable h2 {
  font-size: 20px;
}

.note-editable h3 {
  font-size: 18px;
}

.note-editable .hoge ...
```

ユーザ向け画面のCSSは今後も事前の予告なく書き換わることが十分に考えられるため、
別名でコピーして全部のセレクタ―に`.note-editable`を付け加えまくるのは
メンテナンス性が悪すぎますし、そもそもやりたくありません。

ユーザ向け画面のCSSを、特定のセレクタの下にimportすることができれば、
ユーザ向け画面のCSSがいくら書き換わろうが、管理画面側(私側)は一切手を動かさないで済みます。
この願いをかなえてくれるのが、LESSでした。

# 余談

TinyMCEならば、content_cssというプロパティを用いて、エディタ内に外部CSSを適用できます。
TinyMCEはiframeで動いているため、iframe内にCSSを適用してもその外側(管理画面自体)にはレイアウト崩れなどの影響がありません。
もしこれを読んでいるあなたがまだ、WYSIWYGエディタを実装していないのであれば、TinyMCEを利用することをオススメします。。

# 参考にしたURL

* https://github.com/summernote/summernote/issues/329
* https://documentation.concrete5.org/tutorials/easily-scopenamespace-theme-and-block-css-using-less-and-sass-to-prevent-css-conflicts
* https://stackoverflow.com/questions/42425243/editor-html-using-rows-and-columns-from-bootstrap

# 追記

デザイナーから提供された外部CSSが次のような形で、
特定のクラスの配下にあることを前提としたものだった場合。

```css
.entry h1 {
  font-size : 24px;
}

.entry h2 {
  font-size : 20px;
}
```

順当に考えれば、次のようにSummernoteエディタ上で
そのクラスで囲んだHTMLを用意すれば、期待したレイアウトで表示されるはずです

```html
<div class="entry">
  <h1>h1テキスト</h1>
  <h2>h2テキスト</h2>
</div>
```

ですが、オペレータにおまじないで毎回divを書いてくれと言うのはヒューマンエラーのもと。
それに、DBに親要素の情報まで格納するのは今後、サイトリニューアルがあった時に足枷になりかねない。

ので、おまじないナシで、オペレータには次のような入力をさせて、
ユーザ向け画面のCSSを表示したいと思います。

```html
<h1>h1テキスト</h1>
<h2>h2テキスト</h2>
```

## 考え方

SummernoteのHTML構成は次のような形になっています

```html
<div class="note-editing-area">
	<div class="note-placeholder" style="display: none;">
		placeholder text is here
	</div>
	<div class="note-handle">
		<div class="note-control-selection" style="display: none;">
			<div class="note-control-selection-bg"></div>
			<div class="note-control-holder note-control-nw"></div>
			<div class="note-control-holder note-control-ne"></div>
			<div class="note-control-holder note-control-sw"></div>
			<div class="note-control-sizing note-control-se"></div>
			<div class="note-control-selection-info"></div>
		</div>
	</div>
	<textarea aria-multiline="true" class="note-codable" role="textbox"></textarea>
	<div aria-multiline="true" class="note-editable card-block" contenteditable="true" role="textbox">
		<h1>h1テキスト</h1>
		<h2>h2テキスト</h2>
	</div>
</div>
```

LESSによって今、次のような形で外部CSSが表現されています

```css
.note-editable .entry h1 {
  font-size : 24px;
}

.note-editable .entry h2 {
  font-size : 20px;
}
```

`.note-editable`に外部CSSの親要素のクラス名を付与し、
次のようなCSS組みが出来れば、要件は達成できそうです

```css
.note-editing-area .entry h1 {
  font-size : 24px;
}

.note-editing-area .entry h2 {
  font-size : 20px;
}
```

## 実装

### LESSの記述を変更

```css:/admin/css/import.less
.note-editing-area {
  @import (less) "/css/reset.css";
  @import (less) "/css/common.css";
  @import (less) "/css/category.css";
}
```

### エディタに親要素のクラスを付与

```js:/admin/js/admin.js
$(function(){
  $(".note-editable").addClass("entry");
});
```

# さらに追記

https://github.com/summernote/summernote/issues/3296#issuecomment-542146223

一応Summenoteの公式にもこの件でチケット起票していたのですが、
公式には対応する手段はないとのことでクローズされました(絶望)