爱折腾的我不满足于even这个主题,它太简单了,我也不是很心水它对md的排版。我希望我的主页能加上日历这个元素,想了想就设计成两栏吧,把文字放在偏左的位置,留下一小块放其它小组件。

这时候 maupassant 这个主题进入了我的世界,嗯,很美。

本来打算直接用的,但作为一个CS背景的人,却老是拿来主义有点羞愧,看看一个主题是怎么写的吧,顺便修改一些小细节。

第一步:整体目录结构

来自 https://github.com/JokerQyou/maupassant-hugo

  1. [archetypes] 里就一个文件 default.md,是创建新 post 时会用到的模版文件。
  2. [layouts] 中里面包含了网页的元素结构 html 文件。
  3. [static] 存放静态文件,比如css、js、img等文件。
  4. theme.toml 是博客的全局配置文件。

于是乎,要制做出满足自己需求的网页,我们只需要关注 layouts 和 static 两个文件夹

第二步:了解模版

学习了一下官方的文档和视频,知道了 hugo 主要有三种类型的模版:single、list and partial。

Single Template 用于渲染单个页面,可以理解为一篇文章;List Template 用于渲染包含多个项目的内容,可以理解为文章目录;Partial Template 可以被理解为组件。

baseof.html 是其它模版的基石,只有它比较完整的包含了 html、body 等 tag。它的 body 包含了以下代码:

    {{ partial "header" . }}
    <div id="body">
        <div class="container">
            <div class="col-group">

                <div class="col-8" id="main">
                    {{ block "content" . }}{{ end }}
                </div>

                {{ partial "sidebar" . }}
            </div>
        </div>
    </div>
    {{ partial "footer" . }}

由此可以看出每个页面都包含 header 和 footer 组件,id 为 body 的块包裹着 container,里面囊括了包含左右两栏(其中右边栏由 sidebar 组件渲染)的 col-group。No idea why it named it “col-8” only contains 4 items in my screen.

成分清晰,我们先来剖析一下 baseof.html 包含的组件吧,它们都存在于 partials 文件夹中。


首先 header.html,第一行 class="clearfix",去查了查原来这是为了清除浮动,以前添加一个clear属性的空标签的做法已经过时,目前的做法是:

.clearfix{
    zoom: 1;
}

.clearfix:after{
    clear: both;
}

style.css 文件中,col-group类也采取了相同的做法,防止它包裹的两栏浮动出现问题。

不知道为什么后面莫名其妙的要判断一下是否在主页,如果是用一个 <h1> 框住标题,实际也没什么效果。

看到一个新语法:with INPUT 官方解释:Rebinds the context (.) within its scope and skips the block if the variable is absent. 看起来很好用,用户只需要在配置文件写一下该字段即可。

又一个新语法:range INPUT 不过这次猜猜就知道什么意思了。

{{ if eq .URL $.RelPermalink }} 判断当前 menu 中哪个 item 被选中。我觉得作者把首页单独拎出来有点奇怪,我打算把它们全部写入配置中。


打开 footer.html 有点崩溃,这也太长了吧…原因就是加载了几个模块支持graphviz、flowchart、sequence、latex、小火箭和 busuanzi。插件什么的先扔在一边。

页脚学习一下这种写法:&copy; {{ now.Format "2006" }}


最后看看 sidebar.html,结构简单明了,在 id 为 secondary 的 <div> 标签下包裹着 search_form、recent_post、categories、tags 和其它小部件。

所有的小部件都遵循这一格式:

        <h3 class="widget-title">其它</h3>
        <ul class="widget-list">
            <li><a href="{{ "index.xml" | absURL }}">文章 RSS</a></li>
        </ul>

先来看看 {{ partial "links" . }} 是什么(它没有被 <section> 框住,不知道是不是因为有 with 条件限定,为了整齐起见?)原来是友情链接呀。如果有设置 .Site.Params.links 参数,则会在侧边栏显示。它包含至少3个子参数:url、title、name。


然后看看 search_form,它的 <form> 标签包含了很多参数,一个一个来解析。

  1. action=‘{{if .Site.Params.localSearch }}{{ “search/”| absURL}}{{else}}//www.google.com/search{{end}}’ 规定当提交表单时向何处发送表单数据。github 作者有在 README 中讲解如何开启 Local Search 站内搜索,但我目前不是很理解这个选项,难道是直接在当前网页就能搜索?不用跳出到 google?其次对于没有开启站内搜索的情况,使用谷歌搜索,以 “//” 开头,有点摸不着头脑,不过 Stack Overflow 给我解答了疑惑。It is a “scheme relative” or “protocol relative” URI.
  2. method=“get” 表单数据附加在URL之后,由客户端直接发送至服务器,所以速度上会比post快。W3School 有关于如何选择方法的建议。
  3. accept-charset=“utf-8” 规定服务器处理表单数据所接受的字符集。除了 Internet Explorer,accept-charset 属性得到几乎所有浏览器的支持。
  4. target=”_blank” 在新窗口中打开。
  5. _lpchecked=“1” 这个字段很奇怪诶,从来没看到过,去搜了搜发现是 LastPass browser extension 自动添加的,表示它已经 check 过这个表单是否用来 login。

<form> 内还包含了两个 <input> 标签,它们的名字遵循 google 搜索的 url 命名规则。


接下来看看 recent_post,整体结构和其它小部件一样,重点是 {{ range first 10 (where (where .Site.Pages "Type" "in" (slice "post" "posts")) "Kind" "page") }} 这段代码。 slice ITEM... Creates a slice (array) of all passed arguments. first LIMIT COLLECTION Slices an array to only the first N elements. where COLLECTION KEY [OPERATOR] MATCH Filters an array to only the elements containing a matching value for a given field. 看官方的例子,后面匹配的条件用引号框住。

目前我不需要展示这么多,就三个好了。


下面我们研究 categories,重点在于 {{ range .Site.Taxonomies.categories }}。Taxonomy 被 hugo 用于对 content 的分组,默认有 category 和 tag 两种分类方式,当然你也可以添加其它方式。通过官方文档,还了解到 Term 在分类中用于分类的关键词,Value 被用于指向特定关键词的 content 列表。

The .Site.Taxonomies variable holds all the taxonomies defined site-wide. .Site.Taxonomies is a map of the taxonomy name to a list of its values (e.g., "tags" -> ["tag1", "tag2", "tag3"]). Each value, though, is not a string but rather a Taxonomy variable.

但是我不能理解为什么列出每个分类后,可以直接使用 {{ .Page.Permalink }} 和 {{ .Page.Title }}?我在这个说明里找到了蛛丝马迹。我目前的理解是:对于 category 和 tag 这两种默认的分组,hugo 会帮忙创建一个 页面 指向分组的 list,或者对于 list 中单个 keyword 创建页面。页面的 title 即为分组名。 根据这里的例子,.Data.Terms.Alphabetical.Site.Taxonomies.categories 返回的应该是同一种类型。但我做了替换之后,就显示不出来内容了,不知道是什么情况。这个地方能查到的资料不多,暂且搁置。🤔️


有了对 categories 的研究,tags.html 应该能猜到怎么写的吧。唯一的区别在于分类是一列一列排布,而标签是一行一行排布,所以少了 <li> 标签。

第三步:大刀阔斧的修改css

对网页结构有了大概的了解之后,就开始修改样式的细节。主要就是各种 padding 的调整。 遇到标签栏标签写不下换行的问题,觉得很不好看:

在对应的 class 中加入 white-space: nowrap; 就好了。

经过一番改动,基本上主页已经是想象中的样子了。

我发现这样的排版不太适合 markdown 数字序列,左边会空太多,之后调整 markdown 渲染样式的时候来解决。总体来说,还是很喜欢这个主题对 md 的渲染状况的,比 even 主题好看很多~

整体的效果虽然改完了,但是还有些小细节想要完善,这需要再回过头研究模版代码。

第四步:再次研究模版

之前木有研究 _default 文件夹中的 section.html,去查官方文档,看到下面这个表,一清二楚。

KindDescriptionExample
homeThe home page/index.html
pageA page showing a regular pagemy-post page (/posts/my-post/index.html)
sectionA page listing regular pages from a given sectionposts section (/posts/index.html)
taxonomyA page listing regular pages from a given taxonomy termpage for the term awesomefrom tags taxonomy (/tags/awesome/index.html)
taxonomyTermA page listing terms from a given taxonomypage for the tags taxonomy (/tags/index.html)

第二行 {{- $paginator := .Paginate .Data.Pages.ByDate.Reverse 10 }} 看得有点懵逼。查了查 := 在 go 语言里表示声明并赋值,估计 $paginator 是取的一个变量名?[是的,根据文档$ 代表用户自定义变量]

最让人疑惑的是 {{ 后面的 - 符号,百思不得其解。[Go 1.6 includes the ability to trim the whitespace from either side of a Go tag by including a hyphen (-) and space immediately beside the corresponding {{ or }} delimiter.]

根据官方文档

There are two ways to configure and use a .Paginator:

  1. The simplest way is just to call .Paginator.Pages from a template. It will contain the pages for that page.
  2. Select a subset of the pages with the available template functions and ordering options, and pass the slice to .Paginate, e.g. {{ range (.Paginate ( first 50 .Pages.ByTitle )).Pages }}.

The global page size setting (Paginate) can be overridden by providing a positive integer as the last argument. The examples below will give five items per page:

  • {{ range (.Paginator 5).Pages }}
  • {{ $paginator := .Paginate (where .Pages "Type" "posts") 5 }}

absLangURL: Adds the absolute URL with correct language prefix according to site configuration for multilingual.

urlize: Takes a string, sanitizes it for usage in URLs, and converts spaces to hyphens.

最后 {{ partial "paginator" . }} 是页面1、2、3…跳转的组件。


研读了 paginator.html 的代码,还是很好理解的,case 比较多,目前我的文章没到10页那么多,也就全部页面展示了。


related.html 比较简单,根据官方文档,Hugo uses a set of factors to identify a page’s related content based on Front Matter parameters. 直接拿来主义就好。

第五步:制作日历

对于日历,我采用了 github 上 jsCalendar 的实现,并对 css 效果进行了微调。最后的效果看起来还不错

不过 js 加载会有点慢,要是以后有想法再改改。其次我希望日历现实的那天如果有文章,点击那一天会列出当前书写的全部文章。 加入日历后发现只有主页才有会显示,其它页面无法显示。捣鼓了半天发现如果我采用和 `` 一样的写法就能正常显示,估计应该就是相对路径的问题。

Variables

用到什么记录什么

.IsHometrue in the context of the homepage.
.Site.Titlea string representing the title of the site.
.Titlethe title for this page.
.Site.Paramsa container holding the values from the params section of your site configuration
.Site.BaseURLthe base URL for the site as defined in the site configuration.
.Site.Menusall of the menus in the site.
.URLstring URL that the menu entry points to. The url key, if set for the menu entry, sets this value. If that key is not set, and if the menu entry is set in a page front-matter, this value defaults to the page’s .RelPermalink.
.Typethe content type of the content (e.g., posts).Hugo resolves the content type from either the type in front matter or, if not set, the first directory in the file path. 一般为文件夹名字
.RelPermalinkThe Relative permanent link for this page.
absURLCreates an absolute URL based on the configured baseURL. https://gohugo.io/functions/absurl/
.Kindthe page’s kind. Possible return values are page, home, section, taxonomy, or taxonomyTerm. Note that there are also RSS, sitemap, robotsTXT, and 404 kinds, but these are only available during the rendering of each of these respective page’s kind and therefore not available in any of the Pages collections.

总结

Hugo 有好几种类型的变量,比如 Site变量 定义在站点配置文件中,包含:

  1. .Site.Menus:站点下的所有菜单
  2. .Site.Pages:站点下的所有页面
  3. .Site.Params: 站点下的参数

比如 Page变量 定义在页面头部的front matter中,我觉得 .Params.XXX 的都是?