Обратные ссылки в Hugo
Можно создавать обратные ссылки и в самом Hugo.
Содержание
1 Источники обратных ссылок
- У меня обратные ссылки получаются из заметок в Org-roam, которые я стараюсь вести по методике Zettelkasten (см. Метод Zettelkasten).
2 Создание обратных ссылок из emacs
- Обратные ссылки можно создавать напрямую при экспорте из emacs.
- Для экспорта обратных ссылок я использовал следующий скрипт на elisp:
1;;; -*- mode: emacs-lisp; lexical-binding: t; coding: utf-8-unix; -*- 2;;; Export Backlinks 3 4(defun ecf/org-roam--backlinks-list (file) 5 (with-temp-buffer 6 (if-let* ((backlinks (org-roam--get-backlinks file)) 7 (grouped-backlinks (--group-by (nth 0 it) backlinks))) 8 (progn 9 (dolist (group grouped-backlinks) 10 (let ((file-from (car group)) 11 (bls (cdr group))) 12 (insert (format "- [[file:%s][%s]]\n" 13 file-from 14 (org-roam-db--get-title file-from))) 15 (dolist (backlink bls) 16 (pcase-let ((`(,file-from _ ,props) backlink)) 17 (s-trim (s-replace "\n" " " (prin1-to-string (plist-get props :content)))) 18 (insert "\n\n"))))))) 19 (buffer-string))) 20 21(defun ecf/org-export-preprocessor (backend) 22 (let ( 23 (links (ecf/org-roam--backlinks-list (buffer-file-name)))) 24 (unless (string= links "") 25 (save-excursion 26 (goto-char (point-max)) 27 (insert (concat "\n* Backlinks\n") links))))) 28 29;;; Export backlinks only for ox-hugo 30(defun ecf/org-export-preprocessor-hugo (backend) 31 (when (org-export-derived-backend-p backend 'hugo) 32 (ecf/org-export-preprocessor 'hugo))) 33 34(add-hook 'org-export-before-processing-hook 'ecf/org-export-preprocessor-hugo)
- Недостатки:
- Использовался вызов внутреннего API org-roam. При этом он постоянно менялся.
- Расположение обратных ссылок жёстко кодируется.
- Обратные ссылки получались статичными: при изменении связей необходимо повторно экспортировать все связанные ноды.
3 Создание обратных ссылок с помощью генератора Hugo
- Но, возможно, оптимальнее создавать их с помощью самого Hugo.
3.1 Общая информация
- Примеры создания обратных ссылок в Hugo:
- Есть тема для Hugo, основным свойством которой является поддержка обратных ссылок:
- Для создания обратных ссылок необходимо поправить шаблон.
- Саму генерацию обратных ссылок будем выполнять с помощью механизма частичных шаблонов:
- Шаблон страницы будем делать на основе шаблона single page:
3.2 Каталоги для новой структуры
- Добавим каталог
layouts
в корень нашего сайта для создания собственных шаблонов:
3.3 Шаблон генерации обратных ссылок
- Зададим частичный шаблон
layouts/partials/backlinks.html
, создающий список обратных ссылок и отображающий его:1{{ $re := $.File.BaseFileName }} 2{{ $backlinks := slice }} 3{{ range .Site.AllPages }} 4 {{ if and (findRE $re .RawContent) (not (eq $re .File.BaseFileName)) }} 5 {{ $backlinks = $backlinks | append . }} 6 {{ end }} 7{{ end }} 8 9<hr> 10{{ if gt (len $backlinks) 0 }} 11 <div class="bl-section"> 12 <h4>Links to this note</h4> 13 <div class="backlinks"> 14 <ul> 15 {{ range $backlinks }} 16 <li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li> 17 {{ end }} 18 </ul> 19 </div> 20 </div> 21{{ else }} 22 <div class="bl-section"> 23 <h4>No notes link to this note</h4> 24 </div> 25{{ end }}
3.4 Шаблон страницы
- Шаблон страницы лучше делать на основе используемой темы.
- Я использую шаблон Academic (
https://github.com/wowchemy/starter-hugo-academic
), основанную на модуле wowchemy (https://github.com/wowchemy/wowchemy-hugo-modules.git
).- Скачиваю модуль:
1git clone https://github.com/wowchemy/wowchemy-hugo-modules.git
- Копирую файл
wowchemy-hugo-modules/wowchemy/layouts/_default/single.html
в локальный каталог. Он имеет следующий вид (зависит от темы): - Добавляю использование частичного шаблона обратных ссылок:
- Получаю следующий файл
layouts/_default/single.html
:
- Скачиваю модуль:
4 Исправление обратных ссылок
- При экспорте ox-hugo экспортирует ссылки как указатели на имя файла.
- Имя файла в каталоге org-roam может отличаться от имени, которое имеет файл в каталоге для сайта.
- Имя для сайта задаётся полем
#+EXPORT_FILE_NAME:
. - Предлагается после экспорта заменять ссылки на файлы на внутренние ссылки Hugo типа
{{< relref имя_файла >}}
(см. Синтаксис Markdown для генератора сайтов Hugo). - Имя файла предполагается брать из поля
#+EXPORT_FILE_NAME:
. - Для этого был написан скрипт:
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# org-roam-links 4 5import sys 6import re 7import os 8 9orgroamdir = "~/work/org/notes/" 10 11def find_md_links(md): 12 """Returns dict of links in markdown: 13 'regular': [foo](some.url) 14 'footnotes': [foo][3] 15 16 [3]: some.url 17 """ 18 # https://stackoverflow.com/a/30738268/2755116 19 20 INLINE_LINK_RE = re.compile(r'\[([^\]]+)\]\(([^)]+)\)') 21 FOOTNOTE_LINK_TEXT_RE = re.compile(r'\[([^\]]+)\]\[(\d+)\]') 22 FOOTNOTE_LINK_URL_RE = re.compile(r'\[(\d+)\]:\s+(\S+)') 23 24 links = list(INLINE_LINK_RE.findall(md)) 25 footnote_links = dict(FOOTNOTE_LINK_TEXT_RE.findall(md)) 26 footnote_urls = dict(FOOTNOTE_LINK_URL_RE.findall(md)) 27 28 footnotes_linking = [] 29 30 for key in footnote_links.keys(): 31 footnotes_linking.append((footnote_links[key], footnote_urls[footnote_links[key]])) 32 33 return {'regular': links, 'footnotes': footnotes_linking} 34 35 36def replace_md_links(md, f): 37 """Replace links url to f(url)""" 38 39 links = find_md_links(md) 40 newmd = md 41 42 for r in links['regular']: 43 newmd = newmd.replace(r[1], f(r[1])) 44 45 for r in links['footnotes']: 46 newmd = newmd.replace(r[1], f(r[1])) 47 48 return newmd 49 50if __name__ == "__main__": 51 filename = sys.argv[1] 52 print(filename) 53 fin = open(filename, "rt") 54 filetext = fin.read() 55 56 export_file_name_re = re.compile(r'#\+EXPORT_FILE_NAME:\s+\S+', re.IGNORECASE) 57 relref_re = re.compile(r"{{< relref \"(.+)\" >}}") 58 59 60 links = find_md_links(filetext) 61 for mdlink in links['regular']: 62 fulllink = mdlink[1] 63 isrelref = relref_re.match(fulllink) 64 if isrelref: 65 linkpath = isrelref.group(1) 66 else: 67 # drop extention 68 linkpath = os.path.splitext(fulllink)[0] 69 # add extention 70 md2org = linkpath + '.org' 71 orgfile = os.path.expanduser(orgroamdir + md2org) 72 if os.path.exists(orgfile): 73 orgfiletext = open(orgfile).read() 74 export_file_name_text = export_file_name_re.findall(orgfiletext) 75 export_file_name_link = export_file_name_text[0].split() 76 hugolink = "{{< relref \"" + export_file_name_link[1] + "\" >}}" 77 if isrelref: 78 filetext = filetext.replace("{{< relref \"" + linkpath + "\" >}}",hugolink) 79 else: 80 filetext = filetext.replace(linkpath + '.md',hugolink) 81 82 fin.close() 83 fin = open(filename, "wt") 84 fin.write(filetext) 85 fin.close()
- Запуск скрипта осуществляется через
Makefile
: - Запуск осуществляется следующим образом:
1make fixlinks clean
- Скрипты помещаются в каталог
content/
.