給博客添加純文本版本

看了 wiwi 的 偷偷加入的功能 (另見 Zine#54) 我也給博客的文章添加了純文本版本,你可以在每篇文章的底部找到純文本版本的連結,也可以將頁面 URL 的 .html 換成 .txt 訪問純文本版本 (如果換成 .org 就能看到文章的原始文件)。最近看到 Wiwi.Blog 純文字版,我也跟著做了一個 純文本版本的索引頁

純文本的好处是極簡,基本上就是文字,沒有其它干擾元素,其次是體積小,在網絡不好時也能較快的加載 (不過網絡條件不好,和服務器建立連結也需要時間,能節省的是頁面需要的流量)。

製作純文本版本,只要將原始文件想辦法轉成 txt 版本就好了 (現在只是轉換成了 HTML),然後給所有的 txt 文件做一個索引頁 (建議掛載在 /txt 這個路徑下),基本上就够用了。

我的博客是用 org-publishdenote 構建的,接下來我會分享我是如何通過它們給博客添加純文本版本的,包擴:

  • 將原始 org 文件轉換出一個 txt 版本
  • 給每個頁面添加對應的純文本版本連結
  • 製作索引頁

相關代碼可以在 init-org-publish.elinit-denote.el 找到。

將原始 org 文件轉換出一個 txt 版本

因為我是用 org-publish 構建的,要構建純文本版本,可以向 org-publish-project-alist 添加一個 project 來實現。

(setq org-publish-project-alist
      `(
        ;; 省略其它

        ("plain-text-all"
         :base-directory "~/git/taxodium/posts"
         :base-extension "org"
         :exclude ".*"
         :with-toc nil
         :include  ,(spike-leung/get-file-list-from-denote-silo "~/git/taxodium/posts" (rx (or "_blackhole" "_published")))
         :publishing-directory ,spike-leung/org-publish-default-publishing-directory
         :publishing-function spike-leung/org-publish-plain-text)

        ;; 省略其它
        ))

這個 project 做的事就是 ,用 spike-leung/org-publish-plain-text 处理成純文本版本。

(defun spike-leung/org-publish-plain-text (_plist filename pub-dir)
  "Publish a org file and use export_file_name as filename.

FILENAME is the filename of the Org file to be published.  PLIST
is the property list for the given project.  PUB-DIR is the
publishing directory.

Return output file name."
  (unless (file-directory-p pub-dir)
    (make-directory pub-dir t))
  (let* ((export-file-name
          (or (spike-leung/org-publish-get-org-keyword nil nil "export_file_name" filename) filename))
         (base-filename (expand-file-name (file-name-nondirectory export-file-name) pub-dir))
         (org-file (file-name-with-extension base-filename "org"))
         (text-file (file-name-with-extension base-filename "txt")))
    ;; generate .txt file
    (org-publish-org-to 'ascii filename ".txt" _plist pub-dir)
    ;; copy original org file to pub-dir
    (copy-file filename org-file t)
    ;; Return file name.
    org-file))

spike-leung/org-publish-plain-text 會從文章的 org 文件裡找到 URL (),然後用 org-publish-org-to 导出一個 ascii 版本到用於發布的目录󠄃,這樣就很到了每篇文章對應的純文本版本了。

另外為了讓純文本版本的內容看起來更簡洁,我還覆盖了 ox-ascii 的一些實現,移除了加粗等標記。

ox-ascii 的改動

(use-package ox-ascii
  :straight nil
  :custom
  (org-ascii-text-width 88)
  (org-ascii-quote-margin 2)
  (org-ascii-charset 'ascii)
  (org-ascii-links-to-notes t)
  :config
  ;; do not interpret *word*, /word/, _word_ and +word+
  (defun org-ascii-bold (_bold contents _info) contents)
  (defun org-ascii-italic (_italic contents _info) contents)
  (defun org-ascii-underline (_underline contents _info) contents)
  (defun org-ascii-strike-through (_strike-through contents _info) contents)
  ;; override `org-ascii-template--document-title', change center align to left align
  (defun org-ascii-template--document-title (info)
    "Return document title, as a string.
INFO is a plist used as a communication channel."
    (let* ((text-width (plist-get info :ascii-text-width))
           ;; Links in the title will not be resolved later, so we make
           ;; sure their path is located right after them.
           (info (org-combine-plists info '(:ascii-links-to-notes nil)))
           (with-title (plist-get info :with-title))
           (title (org-export-data
                   (when with-title (plist-get info :title)) info))
           (subtitle (org-export-data
                      (when with-title (plist-get info :subtitle)) info))
           (author (and (plist-get info :with-author)
                        (let ((auth (plist-get info :author)))
                          (and auth (org-export-data auth info)))))
           (email (and (plist-get info :with-email)
                       (org-export-data (plist-get info :email) info)))
           (date (and (plist-get info :with-date)
                      (org-export-data (org-export-get-date info) info))))
      ;; There are two types of title blocks depending on the presence
      ;; of a title to display.
      (if (string= title "")
          ;; Title block without a title.  DATE is positioned at the top
          ;; right of the document, AUTHOR to the top left and EMAIL
          ;; just below.
          (cond
           ((and (org-string-nw-p date) (org-string-nw-p author))
            (concat
             author
             (make-string (- text-width (string-width date) (string-width author))
                          ?\s)
             date
             (when (org-string-nw-p email) (concat "\n" email))
             "\n\n\n"))
           ((and (org-string-nw-p date) (org-string-nw-p email))
            (concat
             email
             (make-string (- text-width (string-width date) (string-width email))
                          ?\s)
             date "\n\n\n"))
           ((org-string-nw-p date)
            (concat
             (org-ascii--justify-lines date text-width 'right)
             "\n\n\n"))
           ((and (org-string-nw-p author) (org-string-nw-p email))
            (concat author "\n" email "\n\n\n"))
           ((org-string-nw-p author) (concat author "\n\n\n"))
           ((org-string-nw-p email) (concat email "\n\n\n")))
        ;; Title block with a title.  Document's TITLE, along with the
        ;; AUTHOR and its EMAIL are both overlined and an underlined,
        ;; centered.  Date is just below, also centered.
        (let* ((utf8p (eq (plist-get info :ascii-charset) 'utf-8))
               ;; Format TITLE.  It may be filled if it is too wide,
               ;; that is wider than the two thirds of the total width.
               (title-len (min (apply #'max
                                      (mapcar #'string-width
                                              (org-split-string
                                               (concat title "\n" subtitle) "\n")))
                               (/ (* 2 text-width) 3)))
               (formatted-title (org-ascii--fill-string title title-len info))
               (formatted-subtitle (when (org-string-nw-p subtitle)
                                     (org-ascii--fill-string subtitle title-len info)))
               (line
                (make-string
                 (min (+ (max title-len
                              (string-width (or author ""))
                              (string-width (or email "")))
                         2)
                      text-width) (if utf8p ?━ ?_))))
          (org-ascii--justify-lines
           (concat (upcase formatted-title)
                   (and formatted-subtitle (concat " - " formatted-subtitle))
                   (when (org-string-nw-p date) (concat "\n\n" date))
                   "\n" line "\n\n")
           text-width 'left))))))

給每個頁面添加對應的純文本版本連結

接下來要給文章添加純文本連結,只要获取到文章的 URL,然後加上 .txt 後輟就行。連結我放在了頁脚,頁脚的內容是通過 :html-postamble spike-leung/html-postamble 指定的 (:html-postambleorg-publish-project-alist 裡的一個配置項,用於配置頁脚)。

spike-leung/html-postamble 是一個函數:

spike-leung/html-postamble

(defun spike-leung/html-postamble (info)
  "Return a string for html-postamble.
INFO is a plist holding contextual information."
  (let* ((timestamp-format "%Y-%m-%d %a %H:%M")
         (display-timestamp-format "%Y-%m-%d")
         (input-file (plist-get info :input-file))
         (output-file (plist-get info :output-file))
         (title (org-export-data (plist-get info :title) info))
         (subtitle (org-export-data (plist-get info :subtitle) info))
         (create-date (org-export-data (org-export-get-date info timestamp-format) info))
         (modified-date (format-time-string timestamp-format
                                            (and input-file (file-attribute-modification-time
                                                             (file-attributes input-file)))))
         (create-date-display (org-export-data (org-export-get-date info display-timestamp-format) info))
         (modified-date-display (format-time-string display-timestamp-format
                                                    (and input-file (file-attribute-modification-time
                                                                     (file-attributes input-file)))))
         (output-filename (file-name-base output-file)))
    (concat
     ;; webmention
     "
Webmentions (加载中...)

如果你想回应这篇文章,可以在你的文章或社交媒体帖子中链接这篇文章,然后提交你的 URL,你的回应随后会显示在此页面上。 (关于 Webmention)

" ;; microformat " Spike Leung Spike Leung " ;; footer (format-spec "

感谢你的阅读!(´。• ᵕ •。`) ♡

文章创建於 %C,更新於 %M,

所有原创內容均遵循 署名、非商业性使用、相同方式共享,

所有源代碼以及内联文檔遵循 AGPL v3。

如果你有什么想说的,尽管给 Spike Leung 发一封 純文本邮件 :)

如果文章对你有帮助,请考虑 用你喜欢的方式 支持我。

純文本版本 原始 org 文件 " `((?c . ,create-date) (?C . ,create-date-display) (?m . ,modified-date) (?M . ,modified-date-display) (?t . ,title) (?s . ,subtitle) (?u . ,output-filename))) ;; scripts " ")))

函數的參數 info 是一個包含了 org-publish 相關上下文的 plist,其中的 :output-file 對應的就是最終导出的文件名字,也就是 URL,用這個 URL 拼接上 .txt ,展示成一個 元素,就得到了文章的純文本版本連結。

製作索引頁

純文本索引頁首頁 基本是一樣的,都是用 Denote Org 获取文章的 denote 連結 (denote:),不同之处在於,首頁是將 denote 的的連結轉換成 .html 的連結,而純文本索引頁需要轉換成 .txt 的連結,這需要覆盖 denote: 連結的导出函數。

denote 相關改動

;;; make denote-link-ol-export support #+export_file_name
;; see also: https://jiewawa.me/2024/03/blogging-with-denote-and-hugo/
(defun spike-leung/my-denote--get-export-file-name (file)
  "Find #+export_file_name in FILE and return its value.
Return nil if not found or FILE does not exist."
  (when (and file (file-exists-p file))
    (with-temp-buffer
      (insert-file-contents file)
      (goto-char (point-min))
      (when (re-search-forward "^#\\+export_file_name: \\(.*\\)$" nil t)
        (string-trim (match-string-no-properties 1))))))

(defun spike-leung/denote-link-ol-export (link description format)
  "Export a `denote:' link from Org files.
The LINK, DESCRIPTION, and FORMAT are handled by the export
backend."
  (pcase-let* ((`(,path ,query ,file-search) (denote-link--ol-resolve-link-to-target link :full-data))
               (export-file-name (when path (spike-leung/my-denote--get-export-file-name path)))
               (anchor (if export-file-name
                           export-file-name
                         (when path (file-relative-name (file-name-sans-extension path)))))
               (desc (cond
                      (description)
                      (file-search (format "denote:%s::%s" query file-search))
                      (t (concat "denote:" query))))
               (ext org-html-extension))
    (if path
        (pcase format
          ('html (if file-search
                     (format "%s" (url-encode-url anchor) ext file-search desc)
                   (format "%s" (url-encode-url anchor) ext desc)))
          ('latex (format "\\href{%s}{%s}" (replace-regexp-in-string "[\\{}$%&_#~^]" "\\\\\\&" path) desc))
          ('texinfo (format "@uref{%s,%s}" path desc))
          ('ascii (if file-search
                      (format "[%s] <https://taxodium.ink/%s.html%s>" desc (url-encode-url anchor) file-search ) (format "[%s] <https://taxodium.ink/%s.html>"  desc (url-encode-url anchor))))
          ('md (format "[%s](%s)" desc path))
          (_ path))
      (format-message "[[Denote query for `%s']]" query))))

;; 修改 `denote:' 链接的导出,使其读取 `#+export_file_name'
(add-hook 'org-export-before-processing-hook
          #'(lambda (backend)
              (org-link-set-parameters "denote" :export #'spike-leung/denote-link-ol-export)))

更多見: init-denote.el

主要的改動是讓 denote: 在导出時讀取 org-html-extension 作為後輟,默認是 html ,在純文本索引頁我會添加一個 File Variables

# Local Variables:
# org-html-extension: "txt"
# End:

org-html-extension 的默認值覆盖為 txt ,然後將索引頁用 org-publish 导出就行。

除此之外就是移除了頁頭、頁脚和樣式,讓索引頁盡可能精簡一些,索引頁目前資源大小只有 35K,HTML 只有 8K,應該足够小了,網絡不好時應該也能較快地加載。

如果你是博客作者,也可以考慮加一下純文本版本。如果你是讀者,要是在網絡不好時你還想看我的博客,你可以訪問 https://taxodium.ink/txt.html ;至於頁面,網絡不好時,你可以將 URL 上的 .html 換成 .txt ,先湊合看著。

感谢你的阅读!

欢迎 邮件 跟我分享你的想法 :)

你也可以 我的博客,保持更新 :P

祝好,素未某面的读者。

純文本版本 原始 org 文件

添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论