diff --git a/site-lisp/ox-quip.el b/site-lisp/ox-quip.el index 7aa18f1..9c2a336 100644 --- a/site-lisp/ox-quip.el +++ b/site-lisp/ox-quip.el @@ -7,24 +7,149 @@ ;;; Code: (require 'cl-extra) (require 'ox-md) +(require 'whitespace) (require 'quip) -(org-export-define-derived-backend 'quip-html 'html - :options-alist - '((:section-numbers nil "num" nil) - (:with-toc nil "toc" nil)) +(defun org-quip--cleanup-quip-html () + "Remove things that convert badly." + (save-excursion + (goto-char (point-min)) + (while (re-search-forward "
" nil t) + (replace-match "") + (if (re-search-forward "
" nil t) (replace-match ""))) + (goto-char (point-min)) + (while (re-search-forward "" nil t) + (replace-match "") + (if (re-search-forward "" nil t) (replace-match ""))) + + (goto-char (point-min)) + (while (re-search-forward "
  • = (setq n (1- n)) 0) + (org-demote)) + (end-of-line 1))))) + + ;; Indent before whitespace cleanup so that blank lines are still blank. + ;; (If you indent after whitespace cleanup, indentation adds whitespace + ;; to align with the paragraph level.) + ;; + (indent-region (point-min) (point-max)) + + ;; Indent lists to match their enclosing headlines. These don't get + ;; auto-indented by the following indentation code, but must be done + ;; after the indentation pass to align with preceeding paragraphs. + (goto-char (point-min)) + (while (re-search-forward "^[-+] " nil t) + (move-beginning-of-line nil) + (while (<= (1+ (current-indentation)) (org-outline-level)) + (org-indent-item-tree))) + + ;; (Indent again; to get all the leftover paragraphs.) + (indent-region (point-min) (point-max)) + (whitespace-cleanup) + + ;; Fill all the paragraphs, but only the paragraphs. (Filling code blocks + ;; and tables does bizarre things, and filling headlines breaks them.) + (org-element-map (org-element-parse-buffer) 'paragraph + (lambda (paragraph) + (goto-char (org-element-property :begin paragraph)) + (fill-paragraph nil) + )) + + ;; Remove consecutive white lines. + (goto-char (point-min)) + (while (re-search-forward "\\(^\\s-*$\\)\n" nil t) + (replace-match "\n") + (forward-char 1)) + + ;; Tidy up all the property drawers. + (org-element-map (org-element-parse-buffer) 'property-drawer + (lambda (drawer) + (goto-char (org-element-property :begin drawer)) + (org-cycle) + )) + )) + +(defun org-quip--get-org-buffer-from-quip (thread-id buffer) + "Fetch the quip thread with THREAD-ID and convert it to org markup in BUFFER. + +We do this by (a) downloading the thread from quip, (b) cleaning +up the HTML in the buffer (a little bit), (c) running the HTML +through pandoc to convert it into acceptable org, and (d) running +a cleanup pass over the generated org markup. + +The end result is fairly clean 'org-mode' markup." + (let ((quip-html (alist-get 'html (quip-get-thread thread-id)))) + (with-current-buffer buffer + (erase-buffer) + (with-temp-buffer + (insert quip-html) + (org-quip--cleanup-quip-html) + (call-process-region (point-min) (point-max) + "pandoc" nil buffer nil + "-f" "html" + "-t" "org" + "--normalize" + "--smart" + )) + (goto-char (point-min)) + (org-mode) + (org-show-subtree) + (org-quip--cleanup-org-buffer) + + ;; HAX + (goto-char (point-min)) + (org-entry-put nil "QUIP_ID" thread-id) + + (goto-char (point-min)) + (insert "#+options: num:nil\n\n") + + + ))) (defun org-quip--get-thread-identifier () "Get the Quip thread identifier from the doc in the current buffer, if any." - (org-entry-get nil "quip-id" t)) + (org-entry-get nil "QUIP_ID" t)) (defun org-quip--put-thread-identifier (identifier) "Put the Quip thread identifier in IDENTIFIER into the doc." (save-excursion (while (org-up-heading-safe)) - (org-entry-put nil "quip-id" identifier))) + (org-entry-put nil "QUIP_ID" identifier))) (defun org-quip--publish-quip (content) "Publish CONTENT as a new Quip document. Return the ID of the new document." @@ -42,25 +167,14 @@ (let ((new-quip-id (org-quip--publish-quip content))) (org-quip--put-thread-identifier new-quip-id))))) -;; - -;; Org-to-quip filter: -;; -;; So, Quip HTML is a very specific, strict subset of HTML. Quip has only a -;; few different block types, and it can't do certain things (like -;; multi-paragraph list items.) -;; -;; Structure: -;; - A list of top-level items: -;; -;; Headlines:

    -;; Block quotes:
    -;; Code block:
    -;;
    -;;    

    -;; -;; +(defun org-quip-refresh () + "Refresh the current document from quip. +This replaces what's in the buffer so I hope you're OK with that." + (interactive) + (let ((thread-id (org-quip--get-thread-identifier))) + (unless thread-id (error "This org doc hasn't been published to quip")) + (org-quip--get-org-buffer-from-quip thread-id (current-buffer)))) (provide 'ox-quip) ;;; ox-quip.el ends here