Markdown에서의 AA 입력을 대응해 보았다 【나의 암 가에타사이쿄의 게시판】

11104 단어 5CSSMarkdown
개인으로 만들고 있는 게시판 사이트가 있다. 최근 일본어 대응을 실장하고 있지만, 도중인 것을 깨달았다.

"모처럼 일본어 번역한 곳에서 AA 붙일 수 없다면 의미 없다!"

AA는 일본어 게시판의 필수적인 문화이다. 그러나 현대의 PC나 스마트폰에서는 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_light 1 라는 글꼴은 가볍고 깨끗하기 때문에 사용하기로 했다. 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 를 지정할 필요가 있지만 스마트폰이라고 너무 커서 일단 지웠다. 그래도 어쩐지 괜찮았다.

결과





스마트폰에서도 AA는 좋은 느낌으로 표시된다. 했어!

「나의 칸가에사이쿄의 게시판」을 시리즈로서 다시 기사를 쓰고 싶기 때문에 다음도 잘 부탁드립니다!



htps : // 이 m/scrp1l/이고 ms/b8b로 1257아135d173585 

좋은 웹페이지 즐겨찾기