静态博客中的shortcode实现


前言

shortcode,直译过来就是 短代码。 使用过 hugo 的人对此也一定有所了解,shortcode 允许用户使用部分关键参数就可以在文章内部插入设置好的HTML模版内容,而不用写大段的HTML代码,比如插入指定视频或者音乐,使用 shortcode 之前需要使用 iframe

<iframe frameborder="no" border="0"
        marginwidth="0" marginheight="0"
        width=330 height=86
        src="//music.163.com/outchain/player?type=2&id=xxx&auto=0&height=66"></iframe>

而使用 shortcode 后就可以只用指定关键的 id 参数

{{<163music xxx>}}

当网易云音乐的分享代码更新后,可以直接更新 shortcode 代码, 而不是搜索替换每一篇包括该iframe代码的文章, 同时 shortcode 也可以扩展 Markdown 的功能,比如文本的居中,居左,居右

{{<align left>}}
  文本内容
  {{</align>}}

实现方式

而想要在静态博客中实现 shortcode 功能, 目前有两种方式

  • 方式一: 修改 Markdown 解析器, 在静态生成器解析文章内容时就可以对 shortcode 进行解析,并将 shortcode 替换成写好的模版,至少,我看到 hugo 就是这么实现的

  • 方式二: 如果不想修改 Markdown 解析器,还可以直接修改生成好的 HTML 内容,这种方式实现原理很简单,Markdown允许插入HTML代码, 那么我们就可以直接在文章中写一个

    <shortcode _name="163music" id="xxx" />

    然后在现有的 Markdown 解析库解析文章并生成 HTML 后再次对HTML内容进行解析,目前主流语言都有现成的库很方便地解析HTML内容, 下面以 Go 为例

Markdown渲染

比如有一篇文章,我想要添加网易云音乐的某首歌到文章内部,当使用现有的Markdown库对内容进行渲染

package main

import (
    "github.com/russross/blackfriday/v2"
)

func main() {
    content := `# Hello World
<shortcode _name="163music" id="xxx" />
This is one of my favorite **music**.
  `
    d := blackfriday.Run(content)
    fmt.Println(string(d))
}

输出结果

<h1>Hello World</h1>

<p><shortcode _name="163music" id="xxx" />
  This is one of my favorite <strong>music</strong>.</p>

查找HTML标签

可以使用 golang.org/x/net/html 对HTML内容进行解析, 并查找到 shortcode

package main

import (
    "bytes"
    "fmt"
    "github.com/russross/blackfriday/v2"
    "golang.org/x/net/html"
    "text/template"
)

func main() {
    // ...
    var (
        w bytes.Buffer
        z = html.NewTokenizer(bytes.NewReader(d))
    )
    for {
        next := z.Next()
        if next == html.ErrorToken {
            break
        }

        token := z.Token()
        switch next {
        case html.StartTagToken, html.SelfClosingTagToken:
            // 找到shortcode标签
            if token.Data == "shortcode" {

            }
        }
        w.WriteString(token.String())
    }
    fmt.Println(w.String())
}

替换HTML

首先把需要替换的 iframe 转换成模版

replace := `<iframe
frameborder="no" border="0"
marginwidth="0" marginheight="0"
width=330 height=86
src="//music.163.com/outchain/player?type=2&id={{ .id }}&auto=0&height=66"></iframe>`

tmpl, err := template.New("test").Parse(replace)
if err != nil {
    panic(err)
}

然后根据标签参数替换原来的HTML

// 获取该标签的参数
attrs := make(map[string]string)
for _, attr := range token.Attr {
    attrs[attr.Key] = attr.Val
}
// 确认是163music
if attrs["_name"] == "163music" {
    // 获取替换的内容
    var buf bytes.Buffer
    err := tmpl.Execute(&buf, attrs)
    if err != nil {
        panic(err)
    }
    w.WriteString(buf.String())
    continue
}

最后的输出结果

<h1>Hello World</h1>

<p><iframe
     frameborder="no" border="0"
     marginwidth="0" marginheight="0"
     width=330 height=86
     src="//music.163.com/outchain/player?type=2&id=xxx&auto=0&height=66"></iframe>
  This is one of my favorite <strong>music</strong>.</p>

结尾

目前snowshortcode 功能就是根据第二种方式实现的,具体可以参考 hooks/shortcode/shortcode.go, 利用此特性甚至还可以实现类似 Hugo 中的 Markdown Render Hook 功能,用于自定义 img, a 等标签

作者: honmaple
链接: https://honmaple.me/articles/2023/02/静态博客中的shortcode实现.html
版权: CC BY-NC-SA 4.0 知识共享署名-非商业性使用-相同方式共享4.0国际许可协议
wechat
alipay

加载评论