org-static-blog
Генератор статического блога, который я сейчас использую для своего публичного цифрового сада.
- https://github.com/bastibe/org-static-blog/
- https://gitlab.com/_zngguvnf/org-static-blog-example - гитлабный пример
Плюсы вполне очевидны:
- простой;
- внутриемаксовый;
- учитывает настройки org-export и ox-html естественным образом - так как использует их.
Из минусов:
- неудобно смотреть сайт локально, малореально вешать в разных местах, почти всё завязано на указанное место публикации.
- очень хочет каталог для черновиков. Ну, сделала пустой, чо…
- не занимается картинками и всем таким. Ну, эт мы и через org-publish могем.
И конечно, жаль, что не умеет вычищать ссылки на то, чего на сайте нет. Вероятно, это плохо совместимо с простотой и скоростью.
Забавно, что ссылку на список всех постов оказалось удобно засунуть в org-static-blog-post-comments
. Для комментариев я всё равно ничего не использую, а если класть в org-static-blog-page-postamble
, то она во многих местах дублирует уже существующую. Тоже ничего страшного, но зачем, если можно обойтись без этого.
Мои настройки, доработки и наблюдения
- пробел после даты мешает сортировке по дате
Исключить пост из rss-ленты
(setq org-static-blog-rss-excluded-tag "norss")
Временами почему-то жалуется на то, что нет запрошенного бэкенда для экспорта
Так и не поняла, в каких случаях, но нашла лекарство:
(org-export-define-derived-backend 'org-static-blog-post-bare 'html :translate-alist '((template . (lambda (contents info) contents))))
Выполнить вот этот фрагмент из тамошнего же кода. Скопировала к себе поближе, иногда применяю.
Брать только файлы с расширением .publ.org
Собственно, каким образом я выбираю, какие файлы публиковать. Внутри файла уже работают механизмы самого org-mode.
Про drafts - реально смысла не имеет, но типа чтобы было.
(require 'el-patch)
El-patch очень рулит. advice и el-patch
(el-patch-defun org-static-blog-get-post-filenames () "Returns a list of all posts." (directory-files-recursively org-static-blog-posts-directory (el-patch-swap ".*\\.org$" ".*\\.publ\\.org$"))) (el-patch-defun org-static-blog-get-draft-filenames () "Returns a list of all drafts." (directory-files-recursively org-static-blog-drafts-directory (el-patch-swap ".*\\.org$" ".*\\.publ\\.org$")))
А потом ликвидировать следы этого самого publ
Добавила второе file-name-sans-extension
в стратегические места
В org-static-blog
(defun my-org-static-blog-get-relative-path (post-filename) "Removes absolute directory path from POST-FILENAME and changes file extension from `.publ.org` to `.html`. Returns filepath to HTML file relative to posts or drafts directories. Works with both posts and drafts directories. For example, when `org-static-blog-posts-directory` is set to '~/blog/posts' and `post-filename` is passed as '~/blog/posts/my-life-update.publ.org' then the function will return 'my-life-update.html'." (concat (file-name-sans-extension (file-name-sans-extension (file-relative-name post-filename org-static-blog-posts-directory))) ".html") ) (advice-add 'org-static-blog-get-relative-path :override #'my-org-static-blog-get-relative-path)
В ox-html
(defun my-org-html-link (link desc info) "Transcode a LINK object from Org to HTML. DESC is the description part of the link, or the empty string. INFO is a plist holding contextual information. See `org-export-data'." (let* ((html-ext (plist-get info :html-extension)) (dot (when (> (length html-ext) 0) ".")) (link-org-files-as-html-maybe (lambda (raw-path info) ;; Treat links to `file.org' as links to `file.html', if ;; needed. See `org-html-link-org-files-as-html'. (cond ((and (plist-get info :html-link-org-files-as-html) (string= ".org" (downcase (file-name-extension raw-path ".")))) (concat (file-name-sans-extension (file-name-sans-extension raw-path)) dot html-ext)) (t raw-path)))) (type (org-element-property :type link)) (raw-path (org-element-property :path link)) ;; Ensure DESC really exists, or set it to nil. (desc (org-string-nw-p desc)) (path (cond ((member type '("http" "https" "ftp" "mailto" "news")) (url-encode-url (concat type ":" raw-path))) ((string= "file" type) ;; During publishing, turn absolute file names belonging ;; to base directory into relative file names. Otherwise, ;; append "file" protocol to absolute file name. (setq raw-path (org-export-file-uri (org-publish-file-relative-name raw-path info))) ;; Possibly append `:html-link-home' to relative file ;; name. (let ((home (and (plist-get info :html-link-home) (org-trim (plist-get info :html-link-home))))) (when (and home (plist-get info :html-link-use-abs-url) (file-name-absolute-p raw-path)) (setq raw-path (concat (file-name-as-directory home) raw-path)))) ;; Maybe turn ".org" into ".html". (setq raw-path (funcall link-org-files-as-html-maybe raw-path info)) ;; Add search option, if any. A search option can be ;; relative to a custom-id, a headline title, a name or ;; a target. (let ((option (org-element-property :search-option link))) (if (not option) raw-path (let ((path (org-element-property :path link))) (concat raw-path "#" (org-publish-resolve-external-link option path t)))))) (t raw-path))) (attributes-plist (org-combine-plists ;; Extract attributes from parent's paragraph. HACK: Only ;; do this for the first link in parent (inner image link ;; for inline images). This is needed as long as ;; attributes cannot be set on a per link basis. (let* ((parent (org-export-get-parent-element link)) (link (let ((container (org-export-get-parent link))) (if (and (eq 'link (org-element-type container)) (org-html-inline-image-p link info)) container link)))) (and (eq link (org-element-map parent 'link #'identity info t)) (org-export-read-attribute :attr_html parent))) ;; Also add attributes from link itself. Currently, those ;; need to be added programmatically before `org-html-link' ;; is invoked, for example, by backends building upon HTML ;; export. (org-export-read-attribute :attr_html link))) (attributes (let ((attr (org-html--make-attribute-string attributes-plist))) (if (org-string-nw-p attr) (concat " " attr) "")))) (cond ;; Link type is handled by a special function. ((org-export-custom-protocol-maybe link desc 'html info)) ;; Image file. ((and (plist-get info :html-inline-images) (org-export-inline-image-p link (plist-get info :html-inline-image-rules))) (org-html--format-image path attributes-plist info)) ;; Radio target: Transcode target's contents and use them as ;; link's description. ((string= type "radio") (let ((destination (org-export-resolve-radio-link link info))) (if (not destination) desc (format "<a href=\"#%s\"%s>%s</a>" (org-export-get-reference destination info) attributes desc)))) ;; Links pointing to a headline: Find destination and build ;; appropriate referencing command. ((member type '("custom-id" "fuzzy" "id")) (let ((destination (if (string= type "fuzzy") (org-export-resolve-fuzzy-link link info) (org-export-resolve-id-link link info)))) (pcase (org-element-type destination) ;; ID link points to an external file. (`plain-text (let ((fragment (concat "ID-" path)) ;; Treat links to ".org" files as ".html", if needed. (path (funcall link-org-files-as-html-maybe destination info))) (format "<a href=\"%s#%s\"%s>%s</a>" path fragment attributes (or desc destination)))) ;; Fuzzy link points nowhere. (`nil (format "<i>%s</i>" (or desc (org-export-data (org-element-property :raw-link link) info)))) ;; Link points to a headline. (`headline (let ((href (org-html--reference destination info)) ;; What description to use? (desc ;; Case 1: Headline is numbered and LINK has no ;; description. Display section number. (if (and (org-export-numbered-headline-p destination info) (not desc)) (mapconcat #'number-to-string (org-export-get-headline-number destination info) ".") ;; Case 2: Either the headline is un-numbered or ;; LINK has a custom description. Display LINK's ;; description or headline's title. (or desc (org-export-data (org-element-property :title destination) info))))) (format "<a href=\"#%s\"%s>%s</a>" href attributes desc))) ;; Fuzzy link points to a target or an element. (_ (if (and destination (memq (plist-get info :with-latex) '(mathjax t)) (eq 'latex-environment (org-element-type destination)) (eq 'math (org-latex--environment-type destination))) ;; Caption and labels are introduced within LaTeX ;; environment. Use "ref" or "eqref" macro, depending on user ;; preference to refer to those in the document. (format (plist-get info :html-equation-reference-format) (org-html--reference destination info)) (let* ((ref (org-html--reference destination info)) (org-html-standalone-image-predicate #'org-html--has-caption-p) (counter-predicate (if (eq 'latex-environment (org-element-type destination)) #'org-html--math-environment-p #'org-html--has-caption-p)) (number (cond (desc nil) ((org-html-standalone-image-p destination info) (org-export-get-ordinal (org-element-map destination 'link #'identity info t) info 'link 'org-html-standalone-image-p)) (t (org-export-get-ordinal destination info nil counter-predicate)))) (desc (cond (desc) ((not number) "No description for this link") ((numberp number) (number-to-string number)) (t (mapconcat #'number-to-string number "."))))) (format "<a href=\"#%s\"%s>%s</a>" ref attributes desc))))))) ;; Coderef: replace link with the reference name or the ;; equivalent line number. ((string= type "coderef") (let ((fragment (concat "coderef-" (org-html-encode-plain-text path)))) (format "<a href=\"#%s\" %s%s>%s</a>" fragment (format "class=\"coderef\" onmouseover=\"CodeHighlightOn(this, \ '%s');\" onmouseout=\"CodeHighlightOff(this, '%s');\"" fragment fragment) attributes (format (org-export-get-coderef-format path desc) (org-export-resolve-coderef path info))))) ;; External link with a description part. ((and path desc) (format "<a href=\"%s\"%s>%s</a>" (org-html-encode-plain-text path) attributes desc)) ;; External link without a description part. (path (let ((path (org-html-encode-plain-text path))) (format "<a href=\"%s\"%s>%s</a>" path attributes path))) ;; No path, only description. Try to do something useful. (t (format "<i>%s</i>" desc)))))
(advice-add 'org-html-link :override #'my-org-html-link)
Вариант сортировки по дате создания
Сделала, попробовала и работает, но убрала, потому что решила, что пусть отображается по последней правке.
Если выполняется это, то посты в блоге отображают как дату и сортируются по created, а не по date, то есть, по времени типа-создания, а не изменения.
(defun my-org-static-blog-get-date (post-filename) "Extract the `#+created:` from POST-FILENAME as date-time." (let ((case-fold-search t)) (with-temp-buffer (insert-file-contents post-filename) (goto-char (point-min)) (if (search-forward-regexp "^\\#\\+created:[ ]*<\\([^]>]+\\)>$" nil t) (date-to-time (match-string 1)) (time-since 0))))) (advice-add 'org-static-blog-get-date :override #'my-org-static-blog-get-date)
Настройки, кроме путей, названий и ещё некоторых мелочей
(setq org-static-blog-enable-tags t) (setq org-static-blog-preview-link-p t) (setq org-static-blog-use-preview t) (setq org-export-with-toc t) (setq org-export-with-section-numbers nil) (setq org-static-blog-langcode "ru") (setq org-static-blog-preview-date-first-p nil) (setq org-static-blog-index-length 30)
Как определяем границы preview
(setq org-static-blog-preview-start "<div class=\"preview\"") (setq org-static-blog-preview-end "</div>")
Соответствующий див задаётся блоком #+begin_preview #+end_preview
Rss
(setq org-static-blog-rss-extra "<atom:link href=\"http://agnessa.pp.ru/rss.xml\" rel=\"self\" type=\"application/rss+xml\" />") (setq org-static-blog-rss-max-entries 10)
Даты в локали С, у rss замороченней заголовок.
(defun my-org-static-blog--write-rss (items &optional tag) "Generates an RSS file for the given TAG, or for all tags is TAG is nil." (let ((title (format "%s%s" org-static-blog-publish-title (if tag (concat " - " tag) ""))) (url (format "%s%s" org-static-blog-publish-url (if tag (concat "/tag-" (downcase tag) ".html") ""))) (items (sort items (lambda (x y) (time-less-p (car y) (car x)))))) (org-static-blog-with-find-file (org-static-blog--rss-filename tag) (concat "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" "<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n" "<channel>\n" "<title><![CDATA[" title "]]></title>\n" "<description><![CDATA[" title "]]></description>\n" "<link>" url "</link>\n" "<lastBuildDate>" (let ((system-time-locale "C")) ; force dates to render as per RSS spec (format-time-string "%a, %d %b %Y %H:%M:%S %z" (current-time))) "</lastBuildDate>\n" org-static-blog-rss-extra (apply 'concat (mapcar 'cdr (org-static-blog--prune-items items))) "</channel>\n" "</rss>\n")))) (advice-add 'org-static-blog--write-rss :override #'my-org-static-blog--write-rss)
Урленкодить ссылки, добавить guid, локаль C.
(defun my-org-static-blog-get-rss-item (post-filename) "Assemble RSS item from post-filename. The HTML content is taken from the rendered HTML post." (concat "<item>\n" " <title><![CDATA[" (org-static-blog-get-title post-filename) "]]></title>\n" " <description><![CDATA[" (org-static-blog-get-body post-filename t) ; exclude headline! "\n\n]]></description>\n" (let ((categories "")) (when (and (org-static-blog-get-tags post-filename) org-static-blog-enable-tags) (dolist (tag (org-static-blog-get-tags post-filename)) (setq categories (concat categories " <category><![CDATA[" tag "]]></category>\n")))) categories) " <link>" (url-encode-url (org-static-blog-get-post-url post-filename)) "</link>\n" " <guid>" (url-encode-url (org-static-blog-get-post-url post-filename)) "</guid>\n" " <pubDate>" (let ((system-time-locale "C")) ; force dates to render as per RSS spec (format-time-string "%a, %d %b %Y %H:%M:%S %z" (org-static-blog-get-date post-filename))) "</pubDate>\n" "</item>\n")) (advice-add 'org-static-blog-get-rss-item :override #'my-org-static-blog-get-rss-item)
Задаю всякий html, примерно
в head каждой страницы
Автор, стили, иконка. Потом, вероятно, надо будет сюда часть микроразметки по заветам indieweb.
(setq org-static-blog-page-header "<meta name=\"author\" content=\"Agnessa\"> <meta name=\"follow_it-verification-code\" content=\"code\"/> <link href= \"https://agnessa.pp.ru/static/style.css\" rel=\"stylesheet\" type=\"text/css\" /> <link rel=\"icon\" type=\"image/png\" sizes=\"150x150\" href=\"https://agnessa.pp.ru/static/bonsai-small.png\"> ")
follow.it - это сервис, через который можно подписаться на обновления.
Шапка страницы, оно же начало body каждой страницы
(setq org-static-blog-page-preamble "<div class=\"header\"> <p style=\"float:right\"><a href=\"https://agnessa.pp.ru\"><img src=\"https://agnessa.pp.ru/static/bonsai-small.png\" alt=\"Цифровой садик - приветственная\"/></a></p> <p><a href=\"https://agnessa.pp.ru\">Цифровой садик - приветственная</a> | <a href=\"https://agnessa.pp.ru/archive.html\">Полный список всего, что тут есть</a> | <a href=\"https://forms.yandex.ru/u/61057e8e2f1b2d9ca96ab333/\">Отправить сообщение через Яндекс.Форму</a> | <a href=\"https://agnessa.pp.ru/rss.xml\">RSS</a> | <a href=\"https://follow.it/m2wkzr?action=followPub\">Подписаться через follow.it</a></p> </div>")
Подвал каждой страницы
(setq org-static-blog-page-postamble "<hr/> <p>Если у вас есть мысли, комментарии, предложения или отклики по поводу этой страницы или этого цифрового сада в целом, <a href=\"https://forms.yandex.ru/u/61057e8e2f1b2d9ca96ab333/\">напишите мне сообщение через Яндекс.Форму</a>. Мне ооочень интересно!</p> ")
Под постами
(setq org-static-blog-post-comments "<div id=\"archive\"> <a href=\"https://agnessa.pp.ru/archive.html\">Все посты</a> </div>")
Начало приветственной страницы
(setq org-static-blog-index-front-matter "<h1>Привет!</h1>\n <div class=\"epigraph\"> <p>Я несу свою пургу,<br/> Потому что я — могу!<br/> — Я </p> <p>Нередко мы считаем свои заблуждения как раз находками, почему же ими не поделиться?..<br/> — Виктор Кротов</p> <p>Мнение автора может не совпадать с его точкой зрения.<br/> — Дисклеймер</p> <p>...надо всё-таки возделывать свой сад.<br/> — Вольтер «Кандид» </div> <p>Тут мой маленький <a href=\"https://agnessa.pp.ru/0-20210912/20210721053232-digital_garden.html\">цифровой садик</a>. Наполняю содержанием. Даже не столько создаю, сколько выбираю, что из существующего беспорядка мне ок показывать наружу. :) Делаю это медленно, по мере постоянного текущего разгребания базы.</p> <p><a href=\"https://agnessa.pp.ru/archive.html\">Полный список всего, что тут есть</a>, ссылка также внизу примерно на каждой странице. Там же возможность <a href=\"https://forms.yandex.ru/u/61057e8e2f1b2d9ca96ab333/\">написать мне в яндекс-форму</a>. Мне интересно, что вы думаете, и отклики сильно улучшают сад. Если хотите написать о конкретной странице — скопируйте ссылку на эту страницу в форму, пожалуйста. Форма не скажет мне сама, с какой страницы вы пришли.</p> <p>На существующих страничках есть сколько-то ссылок на страницы, которые я пока не решилась или не добралась выложить. К сожалению, пока не знаю ни как убрать, не убирая рабочие, ни как отмечать, что они ведут в никуда. Давайте думать, что это хорошо для читателя, потому что даёт возможность подглядеть в закрытую часть сада :)</p> <p>Страницы расположены в хронологическом порядке, раньше — глубже. Дата — последняя правка соответствующего файла. И блоговость тут — иллюзия. Это не окончательные тексты по каким-то поводам, которые не предполагают меняться, быв раз написаны, это «растения», они растут, увядают и меняются прочими способами.</p> <p>Некоторые «входные точки»:</p> <ul> <li><a href=\"https://agnessa.pp.ru/texts/20210721093953-рецензия.html\">Как писать рецензию</a></li> <li><a href=\"https://agnessa.pp.ru/0-20210912/20200712142832-тг_каналы.html\">Телеграм-каналы и чаты</a></li> <li><a href=\"https://agnessa.pp.ru/time/20210704083219-улучшающие%20и%20поддерживающие%20дела.html\">Улучшающие и поддерживающие дела</a></li> <li><a href=\"https://agnessa.pp.ru/computer/emacs/20200820235900-emacs.html\">Еmacs</a></li> <li><a href=\"https://agnessa.pp.ru/mine/20210826225955-мое.html\">мои стихи и что там ещё</a></li> <li><a href=\"https://agnessa.pp.ru/selfrelations/20200912193056-%D0%BE%D0%B1%D0%BE_%D0%BC%D0%BD%D0%B5.html\">обо мне</a></li> <li><a href=\"https://agnessa.pp.ru/me/20210820030406-%D0%B2%D0%B8%D1%88%D0%BB%D0%B8%D1%81%D1%82.html\">вишлист</a></li> </ul>")
Публикация
(defun my-auto-org-static-blog-publish() "Отправляет на место." (interactive) (org-static-blog-publish) (shell-command-to-string "syncgarden") )
Требует скриптика syncgarden, который живёт отдельно в папочке, добавленной в PATH. Там куда перейти и команда для rsync.
Hy, и
(add-hook 'midnight-hook 'my-auto-org-static-blog-publish)
чтоб временами автосрабатывало. https://www.emacswiki.org/emacs/MidnightMode
Потому что чем меньше рутины требует человеческого внимания, тем лучше. :)