javascriptとcssでリストの字下げを揃える力技

公開:2021/02/07
更新:2021/02/12
5 min読了の目安(約4800字TECH技術記事

在宅 PC と会社 PC 間で Office 製品(特に Microsoft Word )のバージョンが違うと結構ストレスフルです。脱 Word を目指してあれこれ試した結果、最近は markdown で文書を書いて PowerShell で Html に変換するという運用に落ち着きました。

フォントは Noto Serif JP を使っているのですが、2021年現在このフォントには font-feature-settings での字詰め処理が適用されないようです。印刷文書ではないのでそこまでこだわる必要はないとはいえ、特に <li> で箇条書するときに といった全角記号があると行頭が揃わないのが気になって仕方ありません(職業病)。

今回はこの部分だけでも力技でキレイにしてみようと思います。

完成イメージ

before↓

after↓

行頭の字下げ以外は変化しないのでご注意ください。

中身

Html

  <ul>
    <li>
      <div class="wrapper-li">みかん</div>
    </li>
    <li>
      <div class="wrapper-li adjust-indent">『檸檬』
        <ul>
          <li>
            <div class="wrapper-li">
              <p>梶井基次郎</p>
            </div>
          </li>
          <li>
            <div class="wrapper-li adjust-indent">
              <p class="adjust-indent">『青空』創刊号(第1巻第1号・通巻1号)初出</p>
              <blockquote>
                <p>えたいの知れない不吉な塊が私の心を始終圧つけていた。</p>
              </blockquote>
              <p class="adjust-indent">(青空文庫に収録)</p>
              <p>ジャンル:短編小説</p>
            </div>
          </li>
        </ul>
      </div>
    </li>
    <li>
      <div class="wrapper-li">りんご</div>
    </li>
  </ul>

後述の PowerShell コマンドレットで markdown から変換する際に、単純な文字列置換で <li> の直下に <div class="wrapper-li"> を入れています。

<div><p>adjust-indent クラスは、下記の javascript を文書内に仕込んで といった全角約物から始まるものに対して機械的に設定しています。

javascript

    document.addEventListener("DOMContentLoaded", () => {
      const fullWidthSymbol = new RegExp("^[「『[〈《(〔]");

      const wrapperLi = Array.from(document.querySelectorAll(".wrapper-li"));
      wrapperLi.forEach(wl => {
        if (wl.innerText.match(fullWidthSymbol)) {
          wl.classList.add("adjust-indent");
        }
        Array.from(wl.children).filter(ch => ch.tagName == "P").forEach(p => {
          if (p.innerText.match(fullWidthSymbol)) {
            p.classList.add("adjust-indent");
          }
        });
      });
    });

リスト内に記述を重ねることもあるので、<li> 直下の <p> にもクラスを設定しています。

css

  body {
    font-family: 'Noto Serif JP', serif;
  }
  li .adjust-indent {
    text-indent: -0.5em;
  }
  li .adjust-indent :not(.adjust-indent) {
    text-indent: 0;
  }

全角約物がフォントに対して左右半分の幅で設定されているので、強引に text-indent を0.5字分マイナスにしています。

li .adjust-indent に対してのみ css を当てるとその子孫(この場合は <blockquote>)にもすべてマイナスの字下げが適用されてしまうので、擬似クラス :not() を使ってクラス指定のない要素の字下げを明示的にゼロにしています。

おまけ: markdown コンパイル用 PowerShell コマンドレット

function Convert-Markdown2Html {
    param (
        [string]$path
    )
    if ($path -notmatch "\.md$") {
        Write-ERROR "non-markdown file!"
        return
    }
    if (-not (Test-Path $path)) {
        Write-Error "invalid path!"
        return
    }

    $today = Get-Date -Format "yyyy-MM-dd"
    $lastModified = Get-Date (Get-Item -Path $path).LastWriteTime -Format "yyyy-MM-dd"
    $timestamp = ($today -eq $lastModified)?
        "<div class=`"timestamp`">update:{0}</div>`n" -f $lastModified :
        "<div class=`"timestamp`">contents updated:{0} / document generated:{1}</div>`n" -f $lastModified, $today

    $html = (ConvertFrom-Markdown -Path $path).Html | Join-String -Separator "`n"
    $html = $html -replace "(<li[^>]*?>)", '$1<div class="wrapper-li">' -replace "</li>", "</div></li>"

    $jsPath = "(自作 javascript へのパス)"
    $js = Get-Content $jsPath | Join-String -Separator "`n" -OutputPrefix "<script>`n" -OutputSuffix "`n</script>"

    $body = $timestamp + $html + $js

    $cssPath = "(自作 css へのパス)"
    $head = Get-Content $cssPath | Join-String -Separator "`n" -OutputPrefix "<base target=`"_blank`">`n<style>`n" -OutputSuffix "`n</style>"

    $extraStyle = $path | Split-Path -Parent | Get-ChildItem -File -Filter "*.css"
    if ($extraStyle) {
        $head += ($extraStyle | ForEach-Object {
            $fileName = $_.Name
            Get-Content $_.Fullname | Join-String -Separator "`n" -OutputPrefix "`n<style>`n/* ADDITIONAL CSS `"$fileName`" */`n" -OutputSuffix "`n</style>"
        } | Join-String -Separator "`n")
    }

    $markup = ConvertTo-Html -Head $head -Body $body

    $outPath = $path -replace "\.md$", ("_{0}.html" -f (Get-Date -Format "yyyyMMdd"))
    $markup | Out-File -FilePath $outPath
    Invoke-Item $outPath

}

ConvertFrom-Markdown で markdown を変換してから、下記の追加操作をしながら文字列結合で Html に書き出しています。

  • 最終更新日の挿入
  • 自作 javascript と css の挿入
  • 対象の markdown と同じディレクトリにスタイルシートがあれば追加で読み込む
  • 変換後のファイル名にはタイムスタンプを入れる
  • 変換済みファイルをブラウザで開く

2021/02/12追記

フォントに YakuHanMPs を指定すれば根本から解決することに気づきました……。行頭以外の字詰めもスッキリするのでそちらを使用する方式のほうが推奨です。

とはいえ、本記事の内容は Web フォントを使用できない場合には使えそうです。記録として残しておこうと思います。