페이징 기능 추가하기

각종 css와 shortcode들을 추가했던 이전 글에 이어 이번에는 pagination을 추가해보려고 합니다.

저는 각 카테고리의 메인 페이지에서는 해당 카테고리의 글들을 paging처리하여 모두 볼 수 있게 구성을 하고 싶었는 데, 제가 사용하고 있는 docport테마는 paging처리가 들어있지 않은 테마였습니다. 그래서 hugo 공식사이트를 참고하여 적용시킨 방법을 공유하려고 합니다.


1. pagination.html작성

우선 /latyouts/partials/pagination.html을 생성해줍니다. 지난 번에 설명한것 처럼 이 경로에 파일을 추가해주면 해당 내용을 테마폴더의 내용을 오버라이딩하는 방식으로 적용이 됩니다. 현재 저는 theme파일에도 해당 이름의 파일은 존재하지 않기때문에 바로 적용이 됩니다.

파일을 생성했으면 다음과 같이 파일을 작성해주면 됩니다.

{{ $paginator := .Paginate (where .Data.Pages "Type" .Section ) }}
<div class = "content">
  <ul class="page">
{{ range $paginator.Pages }}
    <div class = "content-page">
      <a href="{{.RelPermalink}}" >{{ .Title }}</a>
      {{ if gt .WordCount 0 }}
      <p>{{ slicestr .Summary 0 100 }} ...</p>
      {{ end }}
  </div>
{{ end }}
</ul>

<!--  PAGE NUMBERS 그리는 부분 -->
{{ $paginator := .Paginator }}
{{ $adjacent_links := 2 }}
<!-- $최대 표시 가능한 페이지 개수 = ($adjacent_links * 2) + 1 -->
{{ $max_links := (add (mul $adjacent_links 2) 1) }}

<!-- 하한 페이지 = $adjacent_links + 1 -->
{{ $lower_limit := (add $adjacent_links 1) }}
<!-- 상한 페이지 = $paginator.TotalPages - $adjacent_links -->
{{ $upper_limit := (sub $paginator.TotalPages $adjacent_links) }}

<!-- 해당 카테고리의 글이 있을때만 페이지를 render -->
{{ if gt $paginator.TotalPages 1 }}
<div class = "pagination-container">
  <ul class="pagination">

    <!-- 이전 페이지로 이동 버튼 -->
    {{ if $paginator.HasPrev }}
    <li class="page-item">
      <a href="{{ $paginator.Prev.URL }}" class="page-link">
        «
      </a>
    </li>
    {{ end }}

    <!-- 페이지 숫자 표시 부분 -->
    {{ range $paginator.Pagers }}
      {{ $.Scratch.Set "page_number_flag" false }}
      <!-- 현재 표시한 페이지 번호보다 페이지수가 더 많을 경우 -->
      {{ if gt $paginator.TotalPages $max_links }}
        <!-- 하한 페이지 수(1-3)보다 작을때 -->
        {{ if le $paginator.PageNumber $lower_limit }}
          <!-- hugo의 .Scratch를 이용해 렌더한 page번호가 max보다 작다면 기본설정이 false로 되어있는데 true로 변경 -->
          {{ if le .PageNumber $max_links }}
            {{ $.Scratch.Set "page_number_flag" true }}
          {{ end }}

        <!-- 상한 페이지수 보다 클 때 -->
        {{ else if ge $paginator.PageNumber $upper_limit }}
          {{ if gt .PageNumber (sub $paginator.TotalPages $max_links) }}
            {{ $.Scratch.Set "page_number_flag" true }}
          {{ end }}
       <!-- Middle pages. -->
        {{ else }}
          {{ if and ( ge .PageNumber (sub $paginator.PageNumber $adjacent_links) ) ( le .PageNumber (add $paginator.PageNumber $adjacent_links) ) }}
            {{ $.Scratch.Set "page_number_flag" true }}
          {{ end }}
        {{ end }}
      <!-- Simple page numbers. -->
      {{ else }}
        {{ $.Scratch.Set "page_number_flag" true }}
      {{ end }}


      <!-- 페이지 번호 출력 -->
      {{ if eq ($.Scratch.Get "page_number_flag") true }}
      <!-- 현재 페이지라면 page-item-active로 -->
      {{ if eq . $paginator }}
      <li class="page-item-active">
        <a href="{{ .URL }}" class="page-link">
          {{ .PageNumber }}
        </a>
      </li>
    {{ else }}
    <li class="page-item">
      <a href="{{ .URL }}" class="page-link">
        {{ .PageNumber }}
      </a>
    </li>
    {{ end }}
      {{ end }}

    {{ end }}

    <!-- 다음 page 버튼. -->
    {{ if $paginator.HasNext }}
    <li class="page-item">
      <a href="{{ $paginator.Next.URL }}" class="page-link">
        »
      </a>
    </li>
    {{ end }}
  </ul>
</div>
{{ end }}


코드 설명

코드만 보면 굉장히 어려워 보일 수 있는데 지금부터 간단하게 설명드리겠습니다.

{{ $paginator := .Paginate (where .Data.Pages "Type" .Section ) }}
<div class = "content">
  <ul class="page">
{{ range $paginator.Pages }}
    <div class = "content-page">
      <a href="{{.RelPermalink}}" >{{ .Title }}</a>
      {{ if gt .WordCount 0 }}
      <p>{{ slicestr .Summary 0 100 }} ...</p>
      {{ end }}
  </div>
{{ end }}
</ul>

위의 코드부분에서 .Section을 통해 현재 url을 통해 카테고리를 받아와 해당 폴더 밑에 있는 페이지들을 불러와 글 요약 표시를 div로 감싸 만들어 주었고 if gt .WordCount 0 부분이 없다면 내용이 없는 페이지를 그리려고 하면 그 밑의 .Summary이가 null 이기 때문에 error가 뜨기 때문에 다음과 같이 조건문을 추가해 에러를 방지했습니다.

{{ slicestr .Summary 0 100 }}부분을 통해 내용을 요약 하며 조금 더 길거나 짧게 표시하고 싶다면 100을 원하는 숫자로 변경하시면 됩니다. 해당 코드 아래부분은 모두 pagination의 핵심 기능인 page버튼을 그리는 부분이고 최대 표시가능 한 페이지 개수를 설정해 조금더 똑똑한 paging처리를 해주었습니다.


{{ $adjacent_links := 2 }}
<!-- $최대 표시 가능한 페이지 개수 = ($adjacent_links * 2) + 1 -->
{{ $max_links := (add (mul $adjacent_links 2) 1) }}

adjacent_links가 2이면 최대 표시가능한 페이지 개수가 5입니다. 만약 더 늘리거나 줄이고 싶으시면 이를 변경하면 되고 3,5,7 같이 홀수로 바뀌게 됩니다.


  <!-- 페이지 번호 출력 -->
  {{ if eq ($.Scratch.Get "page_number_flag") true }}
    <!-- 현재 페이지라면 page-item-active로 -->
    {{ if eq . $paginator }}
      <li class="page-item-active">
        <a href="{{ .URL }}" class="page-link">
          {{ .PageNumber }}
        </a>
      </li>
    {{ else }}
      <li class="page-item">
        <a href="{{ .URL }}" class="page-link">
          {{ .PageNumber }}
        </a>
      </li>
    {{ end }}
  {{ end }}

이 부분은 실제로 페이지 번호를 그리는 부분으로 if eq . $paginator를 통해 현재 페이지 번호와 그렇지 않은 페이지를 구분해 class 명을 작성해 주었습니다.

이렇게 해준 이유는 클래스명에 따라 현재 페이지의 css속성을 다르게 하여 가시성을 좋게 하기 위함입니다.



2. paination.html을 layout내에 추가

이렇게 작성한 pagination.html을 실제 content를 그리는 layout에 추가해주어야 실제로 render가 되기 때문에 main content를 그리는 layout에 추가해주면 됩니다.

저의 테마는 body-article-content.html이 실제 내용의 부분이기 때문에 해당 파일의 적절한 부분에 다음과 같이 추가해 주었습니다.

<!-- pagination -->
{{if (in .Params.page "true")}} {{ partial "pagination.html" . }} {{end}}

{{ partial "pagination.html" . }}이 실제로 해당 html을 추가해 그리는 부분인데 그 앞에 if문을 추가해주어야 에러가 발생하지 않습니다.

조건문이 없다면 페이징이 적용되지 않는 페이지들, 예를 들어 실제 포스팅하는 글들도 해당 레이아웃을 이용하고 이런 페이지에는 sub page가 존재하지 않기 때문입니다. 그래서 글 작성시 파라미터로 paging을 처리할 페이지임을 명시하고 이 파리미터가 true라면 paging처리를 하도록 해주어야 합니다.

이 부분때문에 에러가 발생해 정말 많은 골머리를 앓았었습니다.



3. paging처리할 페이지에 파라미터 추가해주기

md파일 상단에 active: - page의 파라미터를 추가해주면 쉽게 적용이 됩니다.

아래의 내용은 예시페이지의 상단 부분입니다.

---
title: 페이징 예시 페이지
date: 2021-02-02T01:15:40+09:00
Description: '페이징 예시 페이지 입니다'
weight: 1
images:
hide:
  - breadcrumb
  - comment
  - nextpage
active:
  - page  //다음과 같이 파라미터를 추가 해주면 되고 page기능을 사용안한다면 생략하면 됩니다.
---



4. config.toml / config.yml 파일 수정

한 페이지에 최대 출력하는 글의 개수는 기본 10으로 설정되어있는데 이를 변경하고자 하면, 해당 설정파일에 paginate = 6과 같이 설정을 해주면 쉽게 변경이 가능합니다.

그리고 이제 build를 해보면 페이징이 추가된 것을 볼 수 있을 겁니다.

list 하지만 기대했던과는 다른 모양인 리스트형태의 페이징이 되었는 데 이는 css 설정을 안해 주었기 때문에 css만 추가해주면 간단하게 해결이 됩니다.



css 추가

위의 코드를 그대로 이용하여 만들었다면 제가 설정한 클래스이름대로이기 때문에 아래의 css 파일을 /static/css 밑에 하나 만들어 추가해주시고 커스텀해서 사용하시면 됩니다.

article section.page div.content div.pagination-container {
  text-align: center;
  margin: 25px auto;
  width: 100%;
}
article section.page div.content div.pagination-container ul.pagination {
  display: inline-flex;
  width: auto;
  list-style: none;
  border-radius: 0.25rem;
}

article section.page div.content div.pagination-container ul.pagination li.page-item a.page-link {
  position: relative;
  display: block;
  padding: 0.5rem 0.75rem;
  margin-left: -1px;
  line-height: 1.25;
  color: #027f51;
  background-color: #fff;
  border: 1px solid #dee2e6;
}

article section.page div.content div.pagination-container ul.pagination li.page-item .page-link:hover {
  z-index: 2;
  color: #0056b3;
  text-decoration: none;
  background-color: #e9ecef;
  border-color: #dee2e6;
}

article section.page div.content div.pagination-container ul.pagination li.page-item-active a.page-link {
  position: relative;
  display: block;
  padding: 0.5rem 0.75rem;
  margin-left: -1px;
  line-height: 1.25;
  color: #fff;
  background-color: #027f51;
  border: 1px solid #dee2e6;
}

article section.page div.content div.pagination-container ul.pagination li.page-item-active .page-link:hover {
  z-index: 2;
  color: #0056b3;
  text-decoration: none;
  background-color: #dee2e6;
  border-color: #e9ecef;
}

article section.page div.content div.pagination-container ul.pagination li.page-item .page-link:focus {
  z-index: 3;
  outline: 0;
  box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}

article section.page div.content div.pagination-container ul.pagination li.page-item:first-child .page-link {
  margin-left: 0;
  border-top-left-radius: 0.25rem;
  border-bottom-left-radius: 0.25rem;
}

article section.page div.content div.pagination-container ul.pagination li.page-item:last-child .page-link {
  border-top-right-radius: 0.25rem;
  border-bottom-right-radius: 0.25rem;
}

그리고 해당 css파일을 헤더에서 load해주면 되는데 페이지네이션 기능이 없는데 굳이 css파일을 로드할 필요가 없으므로 /layouts/partials/head.html 에 아래와 같이 추가해주면 적용이됩니다.

<!-- pagination -->
{{if (in .Params.active "page")}}
<link rel="stylesheet" href='{{"/css/pagination.css?ver=1" | relURL}}'>
{{end}}


5. 결과

paging

🎉 짜잔!

정상적인 페이지 버튼의 모습의 페이징이 추가된 것을 볼 수 있습니다.

다음과 같이 적용했는데 css모습이 그대로인 경우가 있는데, css 캐싱기능 때문에 서버에서 가져오는 것이 아니라 웹캐시에서 이전에 저장된 css데이터를 불러와 css적용이 안될 수가 있습니다. 이때는 인터넷 설정에서 쿠키 및 사이트 데이터를 초기화 시켜주면 간단하게 해결이 됩니다.



추가) 처음, 마지막 페이지 이동 버튼 추가하기

<!-- First page. -->
{{ if ne $paginator.PageNumber 1 }}
  <li class="page-item">
    <a class="page-link" href="{{ $paginator.First.URL }}">
      ««
    </a>
  </li>
{{ end }}

<!-- Last page. -->
{{ if ne $paginator.PageNumber $paginator.TotalPages }}
  <li class="page-item">
    <a  href="{{ $paginator.Last.URL }}" class="page-link">
      »»
    </a>
  </li>
{{ end }}

다음과 같이 두개의 코드 부분을 페이지 버튼 그리는 가장 윗 부분, 마지막 부분에 추가해주면 처음/마지막 페이지로 이동하는 버튼이 쉽게 추가가 가능합니다.

설명은 여기까지이며 마음에 드는 테마를 고르고 봤더니 페이징기능이 없어 다른 테마로 옮겨야 하나 고민했던 저와 비슷한 분들에게 많은 도움이 되면 좋겠습니다.





Reference

https://gohugo.io/variables/page/

https://glennmccomb.com/articles/how-to-build-custom-hugo-pagination/