4
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

MarkdownでのAA入力を対応してみた 【ぼくのがんがえたさいきょうの掲示板】

個人で作っている掲示板サイトがある。最近日本語対応を実装しているが、途中であることに気づいてしまった。

「せっかく和訳したところでAA貼れないなら意味ない!」

AAは日本語の掲示板の不可欠な文化である。しかし現代のパソコンやスマホではAAが正しく表示されない場合が多い。これはどうにか解決したい。

やりたいこと

私の掲示板はMarkdown記法で書き込む形だ。AAをそのまま入力すると、太字として認識されたり自動的に改行が入ったりする。2chの専ブラみたいにいい感じにAAを表示したい。

<aa>(´・ω・`)</aa>

自動的にAAを認識するのは大変そうなので、AAタグを用意する。AAタグの中身をAA用のフォントに変えて、Markdown記法の*bold*などを無視する。

無理矢理AAタグを対応する

利用しているMarkdownライブラリはrussross/blackfridayだ。諸々があって今だにv1を使ってる。
このライブラリはHTMLの入力を対応しているが、中身をMarkdown記法としてパースしてしまう。

まず、フォークしよう。そして、markdown.goblockTagsというマップを見てみる。

markdown.go
// blockTags is a set of tags that are recognized as HTML block tags.
// Any of these can be included in markdown text without special escaping.
var blockTags = map[string]struct{}{
    "blockquote": {},
    "del":        {},
    "div":        {},
    // ...
    "aa":         {}, // ← 追加した
}

blockTagsに入っているタグはブロック(パラグラフ)になり、エスケープされないとコメントが丁寧に教えてくれた。よし、"aa": {},を追加する。これで<aa>の中身はMarkdownにならずそのまま出力される。

XSS対策としてmicrocosm-cc/bluemondayを使っている。blackfridayでMarkdownをHTMLに変換した後に、bluemondayでsanitizeをかける。bluemondayにAAタグを許可する必要がある。

var sanitizer *bluemonday.Policy

func init() {
    sanitizer = bluemonday.UGCPolicy() // よくある書式設定用のタグなどを許可
    sanitizer.AllowNoAttrs().OnElements("aa") // 属性なしの<aa>を許可
}

こうしないと<aa>が丸ごと消されてしまう。

しかし非公式のHTMLタグをそのまま出力するのは気持ち悪いので、<aa><p class="aa">に変えよう。PuerkitoBio/goqueryはHTMLをいじるのに便利だ。

上記の処理を組み合わせるとこうなる。


func renderMarkdown(content string) template.HTML {
    // Markdownをrender
    renderer := blackfriday.HtmlRendererWithParameters(commonHtmlFlags, "", "", blackfriday.HtmlRendererParameters{
        // ...
    })
    md := blackfriday.MarkdownOptions([]byte(content), renderer, blackfriday.Options{
        // ...
    })
    // XSS対策としてsanitize
    sanitized := sanitizer.SanitizeBytes(md)

    // goqueryを使ってHTMLをいじる
    doc, err := goquery.NewDocumentFromReader(bytes.NewReader(sanitized))
    if err != nil {
        panic(err)
    }
    // <aa>を<p class="aa">に変換
    doc.Find("aa").Each(func(_ int, sel *goquery.Selection) {
        sel.SetAttr("class", "aa")
        sel.Nodes[0].Data = "p"
    })

    // <html><body>が勝手に追加されるので省略
    html, err := doc.Find("body").Html()
    if err != nil {
        panic(err)
    }
    return template.HTML(html)
}

※ 本当はpanicせずにerrorを返すべき

たぶん、こういう処理はmarkdownのライブラリの中でやるべきだが、blackfridayのコードはヤバイのでこっちの方が楽である。実際のコードでは<aa>の対応だけじゃなくてYouTubeのembedなどは似た手段で対応している。

AAをいい感じに表示する

@scrpgilさんは素敵なAAフォントのまとめを提供してくれているので参考にした。めっちゃ助かる。ありがとうございます!

aahub_light1というフォントは軽くて綺麗なので使うことにした。woffファイルをS3にアップてして、キャッシュするようにCache-Controlヘッダをpublic, max-age=31536000, immutableにした。AA用のCSSも作った。これも永遠にキャッシュかけたいのでメインのCSSと分けてCache-Controlを指定した。

aa.css
@font-face {
  font-family: "aahub_light";
  src: url("[CDNのURL]/aahub_light.woff") format("woff");
  font-display: swap;
}

p.aa {
  font-family: "aahub_light";
  white-space: pre;
  overflow: scroll;
  word-break: keep-all;
  overflow-wrap: normal;
  /*font-size: 16px;*/
  /*line-height: 18px;*/
}

スマホなどで大きいAAがoverflowしがちなのでoverflow: scrollにした。もっとも正しい表示の仕方はfont-sizeline-heightを指定する必要があるがスマホだと大きすぎるのでとりあえず消した。それでもなんとなく大丈夫だった。

結果

Screen Shot 2020-01-12 at 22.34.31.png

スマホでもAAはいい感じに表示される。やったぜ!

「ぼくのかんがえたさいきょうの掲示板」をシリーズとしてまた記事を書きたいと思うので次もよろしくお願いします!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
4
Help us understand the problem. What are the problem?