David Schmudde / Jul 30 2019

Nextjournal in Nextjournal

Mount the Nextjournal Repository

How many lines of code are in the core codebase?

cd /nextjournal/journal/src/com/nextjournal
git ls-files | xargs wc -l

Look for the function of interest, insert-code-node.

grep -nri "insert-code-node" /nextjournal

The definition is listed at /nextjournal/journal/src/com/nextjournal/editor/events.cljs:1465: :insert-code-node. Print that:

sed -n 1463,1475p /nextjournal/journal/src/com/nextjournal/editor/events.cljs

Self Modification of the Nextjournal Notebook

Code from com.nextjournal.editor.operations.tree necessary to run the notebook update.

Shift+Enter to run
ClojureScript
(ns tree)

(defn root-id [db]
  (get-in db [:article :root]))

(defn root? [db node-id]
  (= node-id (root-id db)))

(defn parent-by-id [db node-id content-key]
  (->> db
       :article
       :nodes
       vals
       (filter #(some #{node-id} (content-key %)))
first))

(defn full-position-by-id
  "Returns a vector describing the position of the node in the form
  [parent-id content-key position]
  E.g
  [\"abc-123\" :nodes 2]
  Returns nil if node-id is the root node"
  [db node-id]
  (when-not (root? db node-id)
    (let [[parent content-key] (some (fn [key]
                                       (when-let [p (parent-by-id db node-id key)]
                                         [p key]))
                                     [:content :sections :nodes :inlines])
          content-pos (.indexOf (vec (content-key parent)) node-id)]
      (assert parent)
      (assert (<= 0 content-pos))
[(:id parent) content-key content-pos])))

(defn split-at-item [v i]
  (split-with #(not= % i) v))

(defn flat
  ([db] (assert db) (flat (get-in db [:article :nodes]) (get-in db [:article :root])))
  ([all-nodes current-node-id]
   (assert all-nodes)
   (let [node (all-nodes current-node-id)]
     (concat [current-node-id]
             (:content node)
(mapcat (partial flat all-nodes) (:sections node))))))

(defn closest-code-cell
  "Given an `article` and an absolute position `pos`, tries to find a code cell
   above the given position first. If there's no matching cell found above,
   it returns the closes below, nil otherwise. Accepts an optional predicate
   which all code cells considered should satisfy."
  ([article pos]
   (closest-code-cell article pos (constantly true)))
  ([article [section-id content-key idx :as pos] pred]
   (let [db {:article article}
         contents (vec (get-in article [:nodes section-id content-key]))
         node-at-pos (if (contains? contents idx)
                       (get contents idx)
                       (get-in article [:nodes section-id :sections 0]))
         flat (flat db)
         [before after] (split-at-item flat node-at-pos)
         closest-nodes (concat (reverse before) after)]
     (some
      (fn [id] (let [node (get-in article [:nodes id])]
                 (when (and (pred node) (= "code" (:kind node)))
                   node)))
closest-nodes))))

"[com.nextjournal.editor.operations.tree :as tree]"

The react dispatch :insert-code-node.

((fn [{:keys [db] :as ctx} [_ node-id attrs]]
   (let [db (dissoc db :auth-token)
         [p o s :as pos] (full-position-by-id db node-id)
         find-closest-lang (comp :language
                                 (partial tree/closest-code-cell (:article db)))
         attrs (update attrs :language #(or % (find-closest-lang pos) "julia"))]
     {:db db :dispatch {:insert-code-node-into p o s "code" attrs}}))
  {:db @re-frame.db/app-db} ["" "b4412f59-a73b-43af-b795-029e41e90852"])

Test the dispatch and update this notebook by inserting lorem ipsum above using the code cell below.

(defn api-call [text id kind]
  (re-frame.core/dispatch [:insert-after id kind {:content text}]))

(defn send-post [text & opts] 
   (let [{:keys [video sound image id kind]} opts]
     (api-call text id kind)))

(send-post "lorem ipsum" :id "b4412f59-a73b-43af-b795-029e41e90852" :kind "paragraph")