一周前,我构建了 Notion Flow 浏览器扩展:

Xheldon on Twitter / X

而刚刚更新的 0.4.1 版本:

0.4.1 | Notion Flow

支持了兼容 AWS S3 API 的自建 OSS 服务,如 Cloudflare R2:

Cloudflare R2 | 零出口费用分布式对象存储 | Cloudflare | Cloudflare

本篇文章简单介绍一下我是如何使用这个浏览器扩展用于我的 Github Jekyll 博客的。


Jekyll 静态博客是基于 Ruby 构建的,支持插件。所以我自己写了几个插件(Jekyll 博客的插件位于 _plugins 目录下,写好 ruby 文件后,丢到该目录下,重启服务即可)来处理 Liquid 模板语言,而内容就是来自 Notion Flow 转换的 Notion 内容。如处理 bookmark 的插件内容如下:

{%raw%}

module Jekyll
    class RenderBookMarkBlock < Liquid::Block
        def initialize(tag_name, attr, tokens)
            super
            # 普通的链接没有 yid 和 bid
            attrs = attr.scan(/url\\=\\"(.*)\\"\\stitle\\=\\"(.*)\\"\\simg\\=\\"(.*)\\"\\syid\\=\\"(.*)\\"\\sbid\\=\\"(.*)\\"/)
            if !attrs.empty?
		            # 外部的 video 链接,youtube、bilibili(如本文上一篇博客就是)
                @url = attrs[0][0]
                @title = attrs[0][1]
                @img = attrs[0][2]
                @yid = attrs[0][3]
                @bid = attrs[0][4]
                @firstChar = (@title)[0].upcase
                @error = ""
            else
			          # 正常和 notion 一样的 bookmark(如本文上面三个链接就是)
                attrs = attr.scan(/url\\=\\"(.*)\\"\\stitle\\=\\"(.*)\\"\\simg\\=\\"(.*)\\"/)
                @url = attrs[0][0]
                @title = attrs[0][1]
                @img = attrs[0][2]
                @firstChar = (@title)[0].upcase
                @error = ""
            end
        end
        def render(context)
            @desc = super
            if [email protected]? && [email protected]?
                "<p class='embed-responsive embed-responsive-16by9'><iframe src='<https://www.youtube.com/embed/#{@yid}?rel=0>' title='YouTube video player' frameborder='0' allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture' allowfullscreen></iframe></p>"
            elsif [email protected]? && [email protected]?
                "<p class='embed-responsive embed-responsive-16by9' style='border-bottom: 1px solid #ddd;'><iframe src='//player.bilibili.com/player.html?bvid=#{@bid}&high_quality=1&as_wide=1' scrolling='no' border='0' frameborder='no' framespacing='0' allowfullscreen></iframe></p>"
            else
                "<p><a class='link-bookmark' href='#{@url}' target='_blank'><span data-bookmark-img='#{@img}' data-bookmark-title='#{@firstChar}'><img src='#{@img}'/></span><span><span>#{@title}</span><span>#{@desc}</span><span>#{@url}</span></span></a></p>"
            end
        end
    end
end

# 上传的 video
module Jekyll
    class RenderVideoBlock < Liquid::Block
        def initialize(tag_name, attr, tokens)
            super
            attrs = attr.scan(/caption\\=\\"(.*)\\"\\simg\\=\\"(.*)\\"\\ssuffix\\=\\"(.*)\\"/)
            @caption = attrs[0][0]
            @img = attrs[0][1]
            @suffix = attrs[0][2]
        end
        def render(context)
            text = super
            "<p caption='#{@caption}'><video controls muted><source src='#{@img}' type='video/#{@suffix}' /></video></p>"
        end
    end
end

Liquid::Template.register_tag('render_bookmark', Jekyll::RenderBookMarkBlock)
Liquid::Template.register_tag('render_video', Jekyll::RenderVideoBlock)

{%endraw%}

这段的逻辑是如果遇到 Notion 的 bookmark 模块链接是 Youtube、Bilibili,则转成嵌入视频的 HTML(iframe),否则转成类似于 Notion bookmark 的 HTML(需要配合 CSS 实现)。

所以我使用 Notion Flow 将 Notion 内容转换成 Markdown 格式的同时,自定义了 bookmark 等模块的转换规则,以让博客能够显示 Youtube、Bilibili 和与 Notion 一样的 bookmark 样式内容,如下:

video: function video(block) {
	if (block.type === 'file') {
			// 用户自己上传的 video 文件,用默认 video 插件处理
	    return `{% render_video caption="${block.caption}" img="${block.url}" suffix="${block.suffix}" %}\\n![${block.caption}](${block.url})\\n{% endrender_video %}\\n`;
	} else if (block.type === 'external') {
			// 外部链接、youtube 和 bilibili 视频链接,用 bookmark 处理
	    return `{% render_bookmark url="${block.url}" title="${block.caption || ''}" img="" yid="${block.yid}" bid="${block.bid}" %}{% endrender_bookmark %}\\n`;
	}
}

这里需要注意(我不太懂 ruby), Liquid 模板的标签之间,必须有文本内容(你可以不用),否则,ruby 插件无法生成 HTML。即:

{% render_video  %}这里必须有任意内容!{% endrender_video %}

这样在 ruby 插件中,super 变量拿到的就是「这里必须有任意内容!」这句话(你可以不使用该变量)。如果没有这段内容,则插件压根不会返回任何内容。