caiorss / org-wiki

Wiki for Emacs org-mode built on top of Emacs org-mode.
https://caiorss.github.io/org-wiki/
The Unlicense
359 stars 31 forks source link

Is there anything like backlinks? #25

Open michaelsjackson opened 5 years ago

michaelsjackson commented 5 years ago

Backlinks known from wikis available in org-wiki?

mftrhu commented 5 years ago

Not in org-wiki itself, but, if it helps, I implemented them in the following (admittedly a bit convoluted) way.

I generate my wiki using make. I keep a more-or-less separate wiki.el file containing the elisp I use to export my wiki. Basically, I override org-wiki--org-link to:

  1. check that the file the current link points at exist (dest)[^1];
  2. and if so, put the name of the file being exported in _cache/dest.backlinks

After the export is done, I end up with a bunch of files in _cache. Each of them is named after an existing wiki page, and they contain a list of all the filenames that point to them, one per line.

At that point, my Makefile calls a make_backlinks script (and a few others, to spit out a list of files with update date, and some quick-and-dirty stats about the wiki itself), which goes over all those backlinks files, ensures that there are no duplicates, and which calls ed with a script to edit the HTML to add a backlinks section.

Another possible approach would be to write an elisp function to go over all the pages in your wiki, search for [[wiki:]] strings, extract the string and put the backlinks in your org-file itself.

[^1]: I also use this to make wiki: links turn red when pointing at a non-existent page, see the last section.

Override the link exporter

;; Override `org-wiki--org-link' to add backlinks support
;; The backlinks themselves will have to be added to the relevant files
;; in a second pass, implemented here with a shell & ed script.
(defun org-wiki--org-link (path desc backend)
  "Creates an html org-wiki page link when exporting to HTML,
if and only if the linked page already exists.

Example: the hyperlink [[wiki:Linux][Dealing with Linux]]
will be exported to <a href='Linux.html'>Dealing with Linux</a>
if Linux.org exists, or <span class='missing link'>Dealing with
Linux</span> if it does not."
  (cl-case backend
    (html
     (if (file-exists-p (org-wiki--page->file path))
         ;; If the destination file exists, then write out the path to
         ;; the current file to its backlinks file.
         (let* ((filename (concat "_cache/"
                                  (replace-regexp-in-string "/" "!" path)
                                  ".backlinks"))
                (filepath (org-wiki--page->file path))
                (folder (file-name-directory filepath)))
           (if (file-directory-p folder)
               (write-region (concat current-file "\n") nil
                             filename 'append))
           (format "<a href='%s.html'>%s</a>"
                   path (or desc path)))
       ;; Otherwise, wrap the `desc' of the link in a <span> with
       ;; the missing class and add the destination to missing
       (progn
         (write-region (concat path "\n") nil "_cache/missing" 'append)
         (format "<span class='missing link'>%s</span>"
                 (or desc path)))))))

;; Advice `org-html-publish-to-html' to save the bare name - no
;; path, no extension - of the file it is invoked on, so that
;; `org-wiki--org-link' can know which page is linking to which
;; when outputting backlinks.
(advice-add #'org-html-publish-to-html :before
            (lambda (plist filename pub-dir)
              (setq current-file (file-name-base filename))))

make_backlinks

#!/bin/sh

for filename in _cache/*.backlinks; do
  # Remove duplicates and sort by name
  sort --unique "$filename" | sponge "$filename"
  # Strip the leading "_cache/" and the extension
  target="${filename/!//}"
  target="${target#_cache/}"
  target="${target%.backlinks}"
  # Check that we haven't already added the backlinks
  if grep -q "_out/${target}.html" -e '<div id="backlinks">'; then
    continue
  fi
  # Add the backlinks to the HTML file
  ed -s "_out/${target}.html" >/dev/null <<EOF
# Find the closing main
g/<\\/main>/
# Split it out to its own line
s/<\\/main>/\\
<\\/main>/
# TODO: redundant, could do it with main
# Find the bottom of the file
g/<footer id="postamble"/
# Scroll up a bit so we are inside the main section
-1
# Open the backlinks section
i
<div id="backlinks">
<h2>Backlinks</h2>
.
# Gobble up the results of a sed turning the names into links
.r !sed 's|^.*$|<a href="&.html">&</a>|g' "$filename"
# Close the backlinks section
a
</div>
.
# We are done. Bye!
wq
EOF
done

Dynamic face for wiki: links

;; Defines a dynamic face for org-wiki links, inheriting from
;; `org-link' but making the text red when the linked page does
;; not exist.
(defun org-wiki--dynamic-face (path)
  (let ((org-wiki-file (org-wiki--page->file path)))
    (if (not (file-exists-p org-wiki-file))
        '(:inherit org-link :foreground "red")
        'org-link)))
(org-link-set-parameters "wiki" :face #'org-wiki--dynamic-face)