Jekyll は Liquid および markdown 記法を使って各ページやレイアウトを記述するが、画像を Lightbox をつかってポップアップするには一手間加える必要がありました。

Jekyll での投稿での画像の扱い

ブログのページに写真やその他の画像を貼り付けるというのは至ってよくある状況なのですが、リンク先に画像がポンとあるだけでは正直寂しい。ページを動的生成するサイトならその辺はどうにでもなるかもしれませんが、Jekyll は静的ジェネレータ。必要なら先に生成しておく必要があるわけです。
まぁ、そういうサイトは Lightbox を使うことで簡単にデコレーションできる訳ですが…… jekyll は生の HTML で記述するわけじゃないので一手間加える必要があるわけですね。

Liquid 記法を使うのは HTML ページであり、あくまでも変数や繰り返しなどの制御構造を埋め込むためのものなので、Lightbox 自体は普通に link 要素で CSS を、script 要素で動作のためのスクリプトを埋め込んでやればいいだけです。 が……問題は markdown で記述する投稿内容の方です。

[![csh(tcsh)のみという意味?](/images/2019-10-02-tcsh_only_s.png)](/images/2019-10-02-tcsh_only.png)

markdown 記法でサムネイル付きの画像リンクを書くとこうなるわけですが、これをクリックしてもページ遷移して画像だけがポンと表示されるだけです。

<a href="/images/2019-10-02-tcsh_only.png" rel="lightbox" data-title="csh(tcsh)のみという意味?">![csh(tcsh)のみという意味?](/images/2019-10-02-tcsh_only_s.png)</a>

対して、LightBox を使用して画像をポップアップするように修正したバージョン。rel 属性よりは data-lightbox 属性を使う方が簡便ですね。 markdown 記法のままでは a タグに対して追加の属性を記述できないので、生の HTML でゴリゴリ書けばいいわけですが、これだと当然 Lightbox に依存してしまいますし、リンク要素であるという情報が markdown のパーサーに伝わらないため、状況によってはリンクそのものを無効化したいとか、後々 Lightbox 以外のテクノロジーを使って画像リンクを取り扱いたいときに手作業でやることが増えてしまいます。出来ればやりたくない。

つー事で。
markdown 記法の拡張ではなく、HTML 形式に変換された後に特定の書式の部分だけ a タグを書き換えるという対応で行こうかと思います。

既存のプラグインとか探せばあるかもしれませんが、今回はドシンプルな決め打ちに近い方法でいいので自前でささっと実装してみました。

module Jekyll
  class LightBox
    ConfigName = 'lightbox'
    GroupConfigName = 'lightbox_group'

    def initialize(site, page)
      @config = page[ConfigName] || site.config[ConfigName] || false
      if @config
        @config = case @config
        when true; { ConfigName => Hash.new }
        when Array; { ConfigName => config_value_normalize(@config) }
        when Hash; @config
        else
          raise ArgumentError, "#{ConfigName} オプションの指定が間違っています。(enabled? => #{@config.inspect})"
        end
      end
      case hash = page[GroupConfigName]
      when false;
      when nil;
      when Hash;
        hash.each {|key, value| @config[key] = config_value_normalize(value) }
      else
        raise ArgumentError, "#{GroupConfigName} オプションの指定が間違っています。(#{hash.inspect})"
      end
    end
    def enable?; !!@config end

    def replace(input)
      find_lightbox_node(input)
      replace_lightbox_node!(input)
      input
    end

    private

    def config_value_normalize(value)
      case value
      when Hash; value
      when Array; value.to_h {|x| [x, x] }
      when String; Hash.new(value => value)
      else
        raise ArgumentError, "lightbox オプションの指定が間違っています。(value => #{value.inspect})"
      end
    end

    LightBoxMatcher = Regexp.new('<a href=\"([^\"]*?)\"><img src=\"(?:[^\"]*?)\" alt=\"([^\"]*?)\" />')

    def find_lightbox_node(input)
      input = input.to_s
      lb = @config[ConfigName]
      while md = LightBoxMatcher.match(input)
        lb[md[1]] = md[2]
        input = md.post_match
      end
    end
    def replace_lightbox_node!(input)
      @config.each do |group, hash|
        hash.each do |url, alt|
          input.gsub!("<a href=\"#{url}\">", "<a href=\"#{url}\" data-lightbox=\"#{group}\" data-title=\"#{alt}\">")
        end
      end
    end
  end
  
  module LightBoxExtender
    def lightbox_extend(input)
      lightbox = LightBox.new(@context.registers[:site], @context.registers[:page])
      lightbox.enable? ? lightbox.replace(input) : input
    end
  end
end

Liquid::Template.register_filter(Jekyll::LightBoxExtender)

Liquid 記法向けのフィルターで実装。レイアウトファイルなどで {{ content | lightbox_extend }} と記述するだけで該当するリンク要素を Lightbox によるdecorationできます。他で使い回す可能性も鑑みて変換処理は別クラスでまとめました。 やってることは非常に単純。_config.yaml および各投稿の Front Matter 部分に lightbox: true と書けばリンク要素でその直下に画像要素がある場合に Lightbox に必要な属性を追加するようになっています。

自動で見つけてくれるのは画像要素を直下に持つリンク要素のみで、それ以外は lightbox: {"image_path.png" => "画像", "foobar.png" => "サンプル" } 等とすることで明示的に指定することもできます。リンク要素の中に画像要素がなくても明示的に指定していれば属性追加の処理を行います。

おまけ(Liquid 記法を投稿記事に埋め込む)

で、この記事を書いているときに困ったのが、このテキストの中に Liquid 記法を書いたらそれが展開されてしまったと言うこと。 いちおう記法としてエスケープする手段はあるらしいのですが。

うん、メンドイ(ぉぃ HTMLのエスケープ文字を使う方が楽かも……かと思ったけど、こっちはこっちで多重エスケープされてしまった…… orz


西 啓一朗

日々を適当に生きています。