hugo建站小记

2021-05-13  本文已影响0人  剑山

安装

我先在wsl里面装了homebrew,然后用brew装了hugo。
安装homebrew的时候貌似默认的安装方式会报错,用了替代方案

git clone https://github.com/Homebrew/brew ~/.linuxbrew/Homebrew
mkdir ~/.linuxbrew/bin
ln -s ~/.linuxbrew/Homebrew/bin/brew ~/.linuxbrew/bin
eval $(~/.linuxbrew/bin/brew shellenv)

然后用这个添加环境变量

test -d ~/.linuxbrew && eval $(~/.linuxbrew/bin/brew shellenv)
test -d /home/linuxbrew/.linuxbrew && eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)
test -r ~/.bash_profile && echo "eval \$($(brew --prefix)/bin/brew shellenv)" >>~/.bash_profile
echo "eval \$($(brew --prefix)/bin/brew shellenv)" >>~/.profile

hugo的构成

hugo new site后会生成一个新的目录,里面有几个文件夹

archetypes

里面是当使用hugo new生成markdown文件的时候会默认的使用的一个模板。

assets

这个和hugo pip功能相关,暂时还没看懂。。。大概是说可以比如把css、js这些文件放在这个下面,然后在html里面写类似这样的函数来自动生成引用的html语句,还有些其他的功能,还没看。。。

{{- $bootcss := resources.Get "css/bootstrap.css" -}}

content

里面存储着所有的生成网站的内容,比如博客的md文件这些

data

里面存储着所有的配置文件,hugo支持YAML、JSON、TOML格式

layouts

里面存着html的模板文件

static

里面存着静态的文件,比如CSS、JavaScript这些

resources

一些加速文件,不用管

配置

hugo默认先找根目录下面的config.toml、 config.yaml 或者config.json,new site的时候会自动生成一个config.toml
所有可以配置的可以在这里看到https://gohugo.io/getting-started/configuration/

Hugo Modules

有点复杂没看懂。。。

内容管理

页面包

hugo会把一个页面用到的资源理解成一个page bundle,一个page bundle里面的页面可以直接引用自己bundle下面的资源,但是不能直接引用其他bundle的。根目录的bundle只能有一个页面,其他的地方是可以有多个页面的。访问的方式可以这样写,例如列出所有自己page bundle下面的图片


其他引用页面资源的方式可以看这里
https://gohugo.io/content-management/page-resources/
叶子包是指一个单页的页面包,下面不包含其他的内容,索引文件是index.md,而分支包下面包含了其他的内容,索引文件是_index.md

type layout section page

这几个概念有点复杂,因为要同时搞懂才能分别搞懂。。。反正我看了很久。。。我的理解大概的意思是,content目录下会有多个文件夹,如果某个文件夹下面有_index.md的话,那么这个文件夹就会被称为一个section,content也是一个section,这个默认的section类型或者说名字是page,初次以外,其他的md文件都有一个默认的type,这个默认的是就是section的名字,比如有一个文件夹叫blog,下面有一个_index.md,一个first.md,那么生成的时候会生成一个baseURL/blog/这个页面,会调用layouts/blog/list.html这个模板,同时生成一个baseURL/blog/first/这个页面,会调用layouts/blog/single.html这个模板。
同时可以在md文件的front matter里面显式的声明type和layout,比如在content/blog/second.md的front matter里面写type: project/layout: one的话,这个页面的地址还是会在baseURL/blog/second/但是渲染的时候会调用layouts/project/one这个模板。
还有一个情况是比如在content下面有一个contact.md,默认是会找layouts/page/single.html这个模板的,但是因为主页的页面,一般都要单独设置,所以可以在front matter里面声明layout:contact,然后就会引用layouts/page/contact.html这个模板

所以总结来说,最后生成的页面的路径是按照content下文件的结构来生成的,而这些页面的渲染模板和layouts相关,但是也可以显示的声明渲染模板的位置,例如


每个页面都会有一个寻找模板的顺序,这个顺序可以看hugo的文档在这里:https://gohugo.io/templates/lookup-order/

中文summary错误问题

调用{{.summary}}的时候,如果页面是中文的话会有问题,所以需要设置hasCJKLanguage为true,官方文档是这样写的


partials 里面调用变量错误问题

如果要在partial里面调用变量的话,需要在调用的时候把参数传进去才可以在partials里面引用


在markdown文件里面调用文件(例如加入图片)

方案1 建立page bundle

在文件同级目录下新建一个同名文件夹,然后里面加入一个index.md的文件,这样只要正常使用md语法加入就可以了

但是我遇到的问题是这样的,就是当网页比较窄,但是图片比较宽的时候,图片的宽度会比父级还要宽,所以有了方案2

方案2 shortcodes

在layouts/shortcodes/下建立一个img.html,然后里面填入

 <img src="{{ .Get "src" }}"  {{ if .Get "alt"}} alt="{{ .Get "alt" }}" {{ end }} {{ if .Get "class"}} class="{{ .Get "class" }}" {{ end }} {{ if .Get "style"}} style="{{ .Get "style" }}"   {{ end }} >

然后在md文件里面填入

{{<  img src="1.jpg" style="style-text" class="class-text" alt="alt text">}}

图片放在和刚刚一样的位置,这样就可以引用了,并且可以传入一些参数例如style和class什么的

2021年5月26日 UPDATE:
用了上面的方法后我发现一个问题就是每次使用hugo server的时候会默认渲染文件夹下面的index.md文件,例如我在content下面有一个first.md和一个first文件夹,文件夹下面有一个index.md,这时候会优先渲染index.md。除非你打开first.md后再保存一下,并且用hugo生成网站的时候也是有限index.md的,所以我现在的做法是把内容写在index.md里面,删除first.md这个文件。。。。目前看着有点奇怪不过能满足我所有的需求,暂时先这样吧。。。

分类 taxonomy

分类下面我只用用了tags,但是hugo支持多重分类方式,比如默认有tags和category。

使用分类功能,首先要在config.toml里面配置


因为我是用的json文件,所以这里是json格式

然后在写markdown的时候在front matter里面可以用tags加入标签


这样hugo会自动生种两个页面,一个是baseURL/tags/下面列出了所有的tags,另外是一系列页面baseURL/tags/xxx下面列出了每一个tags的页面。

lookup这个我没有很理解他的意思,不过我各种尝试以后的做法是这样的在/layouts/taxonomy/taxonomuy.html里面写所有tags的页面,每个tags的页面写到/layouts/taxonomy/term.html

分页

恩,我遇到了一个奇怪的问题,就是不管怎么样.paginator都会一直得到nil,后面好像是关了server重新运行了下hugo server好像就好了,同样的道理之前也遇到过,可能是某个bug吧

恩,正常的做法应该是这样的,首先你可以在config.toml里面设置两个参数

第一个是每一页显示多少个page,第二个是分页的那个路径的名字,比如本来是baseURL/blog,这里是默认的page,那么第一页就会是baseURL/blog/page/1,第二页就是baseURL/blog/page/2。

然后就是在list页面把{{range .Pages}的前面先设一个变量{{ $paginator := .Paginate .Pages }},然后调用{{ range $paginator.Pages }}。这样就会调用那一页应该显示的页面了,

比较麻烦的是要做导航栏,因为情况会比较多,比如防止页面一般显示的是当前页面的前后几页,比如现在显示的是第五页,那一般只有34567这几个页面的按钮。另外还要有上一页和下一页,但是第一页不应该显示上一页,最后一页没有下一页。这里我用了dsrkafuu这里写的教程,我把代码也复制过来

<!-- 开始 输出一定数量的位于 posts 分类下的文章 -->
{{ $paginator := .Paginate (where .Data.Pages "Type" "posts") }}
{{ range $paginator.Pages }}
<div class="post">
    <h2 class="post-title">
        <a href="{{ .Permalink }}">{{ .Title }}</a>
    </h2>
    <div class="post-summary">
        {{ .Summary }}
    </div>
</div>
{{ end }}
<!-- 结束 输出一定数量的位于 posts 分类下的文章 -->

<!-- 开始 分页导航 -->
{{ $paginator := .Paginator }}
<!-- 基础偏移变量 -->
{{ $offsetLinks := 2 }}
<!-- $maxLinks = ($offsetLinks * 2) + 1 -->
{{ $maxLinks := (add (mul $offsetLinks 2) 1) }}
<!-- $lowerLimit = $offsetLinks + 1 -->
{{ $lowerLimit := (add $offsetLinks 1) }}
<!-- $upperLimit = $paginator.TotalPages - $offsetLinks -->
{{ $upperLimit := (sub $paginator.TotalPages $offsetLinks) }}

<!-- 如果有超过一页的内容 (即需要导航栏) -->
{{ if gt $paginator.TotalPages 1 }}
<ul class="pagination">
    <!-- 上一页 -->
    {{ if $paginator.HasPrev }}
    <li class="pag-item pag-previous">
        <a href="{{ $paginator.Prev.URL }}" class="pag-link">«</a>
    </li>
    {{ end }}

    <!-- 数字页码部分 -->
    {{ range $paginator.Pagers }}
    {{ $.Scratch.Set "pageNumFlag" false }}
    <!-- 页码数足够多的情况 -->
    {{ if gt $paginator.TotalPages $maxLinks }}
        <!-- 如果当前页面为例子中的 1-3 区间  -->
        {{ if le $paginator.PageNumber $lowerLimit }}
            {{ if le .PageNumber $maxLinks }}
            {{ $.Scratch.Set "pageNumFlag" true }}
            {{ end }}
        <!-- 如果当前页面为例子中的 8-10 区间 -->
        {{ else if ge $paginator.PageNumber $upperLimit }}
            {{ if gt .PageNumber (sub $paginator.TotalPages $maxLinks) }}
            {{ $.Scratch.Set "pageNumFlag" true }}
            {{ end }}
        <!-- 如果当前页面为例子中的 4-7 区间 -->
        {{ else }}
            {{ if and ( ge .PageNumber (sub $paginator.PageNumber $offsetLinks) ) ( le .PageNumber (add $paginator.PageNumber $offsetLinks) ) }}
            {{ $.Scratch.Set "pageNumFlag" true }}
            {{ end }}
        {{ end }}
    <!-- 页码数不够多的情况 -->
    {{ else }}
        {{ $.Scratch.Set "pageNumFlag" true }}
    {{ end }}
    <!-- 输出页码 -->
    {{ if eq ($.Scratch.Get "pageNumFlag") true }}
    <li class="pag-item{{ if eq . $paginator }} pag-current{{ end }}">
        <a href="{{ .URL }}" class="pag-link">
            {{ .PageNumber }}
        </a>
    </li>
    {{ end }}
    {{ end }}

    <!-- 下一页 -->
    {{ if $paginator.HasNext }}
    <li class="pag-item pag-next">
        <a href="{{ $paginator.Next.URL }}" class="pag-link">»</a>
    </li>
    {{ end }}
</ul>
{{ end }}

可以自动实现刚刚的那些逻辑。但是我的网站还没有那么多页面。。。所以现在网站是看不到导航条的。。。

404

恩,发发现hugo server本地运行的时候是不支持调用layouts/404.html这个页面的,我查了半天,最近的讨论是18年的,大概的意思是不行,不过你要预览的话可以手动访问/404.html来看效果

搜索

不知道为什么我看了几个教程都不能用。。。只有这个里面的代码是可以用的https://blog.csdn.net/weixin_44903718/article/details/108541002,还有官方的这个链接这里面的这个代码在<script>里面的代码会自动的被search.js里面调用,用于排列搜索结果,所以你更改这里的内容,适配页面。而整个页面就是显示搜索结果的,所以可以参考list.htmlli里面的排版,样子就差不多了。

<section>
<div>
    <form action="{{ "search" | absURL }}">
    <input id="search-query" name="s"/>
    </form>
    <div id="search-results">
    <h3>Matching pages</h3>
    </div>
</div>
</section>
<script id="search-result-template" type="text/x-js-template">
<div id="summary-${key}">
    <h4><a href="${link}">${title}</a></h4>
    <p>${snippet}</p>
    ${ isset tags }<p>Tags: ${tags}</p>${ end }
    ${ isset categories }<p>Categories: ${categories}</p>${ end }
</div>
</script>
<script src="https://cdn.jsdelivr.net/gh/foxscallion11/webp/static/hugo/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.2.0/fuse.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/jquery.mark.min.js"></script>
<script src="{{ "js/search.js" | absURL }}"></script>

另外search.js这个里面主要就是jquery的一些函数。

summaryInclude = 60;
var fuseOptions = {
    shouldSort: true,
    includeMatches: true,
    threshold: 0.0,
    tokenize: true,
    location: 0,
    distance: 100,
    maxPatternLength: 32,
    minMatchCharLength: 1,
    keys: [
        { name: "title", weight: 0.8 },
        { name: "contents", weight: 0.5 },
        { name: "tags", weight: 0.3 },
        { name: "categories", weight: 0.3 }
    ]
};


var searchQuery = param("s");
if (searchQuery) {
    $("#search-query").val(searchQuery);
    executeSearch(searchQuery);
} else {
    $('#search-results').append("<p>Please enter a word or phrase above</p>");
}



function executeSearch(searchQuery) {
    $.getJSON("/index.json", function (data) {
        var pages = data;
        var fuse = new Fuse(pages, fuseOptions);
        var result = fuse.search(searchQuery);
        console.log({ "matches": result });
        if (result.length > 0) {
            populateResults(result);
        } else {
            $('#search-results').append("<p>No matches found</p>");
        }
    });
}

function populateResults(result) {
    $.each(result, function (key, value) {
        var contents = value.item.contents;
        var snippet = "";
        var snippetHighlights = [];
        var tags = [];
        if (fuseOptions.tokenize) {
            snippetHighlights.push(searchQuery);
        } else {
            $.each(value.matches, function (matchKey, mvalue) {
                if (mvalue.key == "tags" || mvalue.key == "categories") {
                    snippetHighlights.push(mvalue.value);
                } else if (mvalue.key == "contents") {
                    start = mvalue.indices[0][0] - summaryInclude > 0 ? mvalue.indices[0][0] - summaryInclude : 0;
                    end = mvalue.indices[0][1] + summaryInclude < contents.length ? mvalue.indices[0][1] + summaryInclude : contents.length;
                    snippet += contents.substring(start, end);
                    snippetHighlights.push(mvalue.value.substring(mvalue.indices[0][0], mvalue.indices[0][1] - mvalue.indices[0][0] + 1));
                }
            });
        }

        if (snippet.length < 1) {
            snippet += contents.substring(0, summaryInclude * 2);
        }
        //pull template from hugo templarte definition
        var templateDefinition = $('#search-result-template').html();
        //replace values
        var output = render(templateDefinition, { key: key, title: value.item.title, link: value.item.permalink, tags: value.item.tags, categories: value.item.categories, snippet: snippet });
        $('#search-results').append(output);

        $.each(snippetHighlights, function (snipkey, snipvalue) {
            $("#summary-" + key).mark(snipvalue);
        });

    });
}

function param(name) {
    return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' ');
}

function render(templateString, data) {
    var conditionalMatches, conditionalPattern, copy;
    conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g;
    //since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop
    copy = templateString;
    while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) {
        if (data[conditionalMatches[1]]) {
            //valid key, remove conditionals, leave contents.
            copy = copy.replace(conditionalMatches[0], conditionalMatches[2]);
        } else {
            //not valid, remove entire section
            copy = copy.replace(conditionalMatches[0], '');
        }
    }
    templateString = copy;
    //now any conditionals removed we can do simple substitution
    var key, find, re;
    for (key in data) {
        find = '\\$\\{\\s*' + key + '\\s*\\}';
        re = new RegExp(find, 'g');
        templateString = templateString.replace(re, data[key]);
    }
    return templateString;
}

也可以根据需要改一些,比如我不是用一个input来显示搜索结果的,所以我改成了这个样

var searchQuery = param("s");
if(searchQuery){
$("#search-query").html(searchQuery);
$("#search-query").append("&nbsp;的搜索结果")
executeSearch(searchQuery);
}else {
$('#search-results').append("<p>你总要写点什么要搜的先</p>");
}



function executeSearch(searchQuery){
$.getJSON( "/index.json", function( data ) {
    var pages = data;
    var fuse = new Fuse(pages, fuseOptions);
    var result = fuse.search(searchQuery);
    console.log({"matches":result});
    if(result.length > 0){
    populateResults(result);
    }else{
    $('#search-results').append("<p>什么都没有搜到。。。</p>");
    }
});
}

显示的结果会是这样


至于改config.toml和search.json就找照着官方教程做就行

字体

直接调用google fonts貌似也是可以用的,大概就是按照下面这样调用就行。但是不一定什么时候就会要很久才能打开。。。

<link href='https://fonts.googleapis.com/css?family=Noto+Sans+SC:100,300,500' rel='stylesheet'>

但是如果直接在google fonts网站下的话会得到otf格式的,体积非常之大的(大概6-8m每个文件)字体,又没有办法使用在网页上。。。然后我找到了这个https://google-webfonts-helper.herokuapp.com/fonts,他可以让你选字体,然后自动生成调用方式和对应的文件,每个文件大概1m多

markdown

恩,目前(2021年5月30日)hugo的版本是0.83,刚刚换用了新的markdown渲染引擎为goldmark,然后goldmark是支持渲染的时候增加自定义属性的(比如增加html的class),然后在0.81这个这版本支持了对于markdown block(不指导是个什么概念。。。)的支持,table啊list啊什么的这些都可以了,另外给标题加属性也是支持的。


不过目前这个功能默认的配置里是禁用的,所有的默认配置可以看这里

只要把这里的block改成true就可以了,例如这样给一个table增加.table的属性,转成html的时候会自动给这个表格增加一个class='table',就会调用bootstrap的格式美化了,不得不提goldmark默认的表格渲染真的是。。。太简陋了。。
上一篇下一篇

猜你喜欢

热点阅读