Emacs Live Edit CMS

Emacs Live Edit CMS

Blogging engine that supports multi-user collaborative editing and web hosting built with nothing else but Emacs.

Source code of the system and its design should be conceptually simple enough to fit into this very org-mode document but powerful enough to support publishing workflow of Emacs user group.

How it works

Basic content management system is based on crdt.el and org-mode files. Crdt mode allows for multi-user real-time content collaboration while org-mode provides rich text file experience. Files are served by Emacs as a web server.

Endpoints description:

  • GET / -> returns index.org
  • GET /foo.org -> returns foo.org
  • GET /all -> returns list of all available buffers that can be retrieved

Dependencies: web-server crdt

(require 'cl-lib)
(require 'crdt)

(setq org-matcher ".*.org$")
(setq base-dir ".")

(crdt-new-session 6530 "" "" crdt-default-name (prin1-to-string crdt-default-session-permissions))
;;Open all org-mode files in base dir
(let* ((files (cl-remove-if-not (lambda (str) (string-match-p org-matcher str)) (directory-files base-dir)))
       (paths (mapcar (lambda (file) (concat base-dir "/" file)) files)))
  (mapcar (lambda (path)
            (message path)
            (message "%d" (length crdt--session-list))
            (find-file path)
            (crdt--share-buffer (current-buffer) (car crdt--session-list)))
          paths))

(defun all-documents ()
  (cl-remove-if-not (lambda (str) (string-match-p org-matcher str)) (mapcar #'buffer-name (buffer-list))))

(ws-start
 '(((:GET . "/.*.org") .
    (lambda (req)
      (with-slots (process headers) req
        (let* ((path (string-clean-whitespace (cdr (nth 1 headers))))
               (document-name (substring path 1))
               (document (get-buffer document-name)))
          (if document
              (with-current-buffer document
                (org-html-export-as-html)
                (with-current-buffer (get-buffer "*Org HTML Export*")
                  (ws-response-header process 200 '("Content-type" . "text/html"))
                  (process-send-string process (buffer-string))))
              (progn
                (ws-response-header process 404 '("Content-type" . "text/plain"))
                (process-send-string process "Document does not exist")))))))

   ((:GET . "/all") .
    (lambda (req)
      (with-slots (process headers) req
        (ws-response-header process 200 '("Content-type" . "text/html"))
        (process-send-string process
        (apply
          'concat
          (append
            (list "<ul>")
            (mapcar (lambda (d) (format "<li><a href=\"%s\">%s</a></li>" d d)) (all-documents))
            (list "</ul>")))))))

   ((:GET . "/") .
    (lambda (req)
      (with-slots (process headers) req
        (ws-response-header process 301 '("Location" . "/emacs-live-cms.org")))))

   ((lambda (_) t) .
    (lambda (req)
      (with-slots (process) req
        (my-headers process 400)
        (process-send-string process "Bad request")))))

 8080 "*messages*" :host "0.0.0.0")

Starting emacs as a daemon that runs the server: guix shell –container –network -m manifest.scm curl coreutils emacs –daemon –eval '(org-babel-load-file "./emacs-live-cms.org")'

TODO List of future features [3/10]

  • [X] Open all .org files from a directory on launch to populate buffer list.
  • [X] Check if buffers saved to CRDT are reachable.
  • [ ] Periodically save crdt buffers to disk.
  • [ ] Evict buffers from crdt session after some time of inactivity (timeout should should be in days).
  • [ ] Start password protected crdt session.
  • [ ] Create index.org that features list of all available documents.
  • [ ] Provide theming support. Maybe render just basic HTML and provide single CSS theme. Should be doable with org export.
  • [ ] Provide endpoint for raw org-mode file viewing
  • [ ] Deployment
  • [X] Look into org-mode literate config and elisp tests