From 2b33638a8dcf3f334225cd40f83533cca162f574 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Mon, 22 May 2023 10:31:09 +0200 Subject: [PATCH 01/17] Improve render performance by only evaluating each render-fn once --- src/nextjournal/clerk/render.cljs | 5 ++++- src/nextjournal/clerk/view.clj | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/nextjournal/clerk/render.cljs b/src/nextjournal/clerk/render.cljs index 86806eb0c..766e0be01 100644 --- a/src/nextjournal/clerk/render.cljs +++ b/src/nextjournal/clerk/render.cljs @@ -698,12 +698,15 @@ (let [re-eval (fn [{:keys [form]}] (viewer/->viewer-fn form))] (w/postwalk (fn [x] (cond-> x (viewer/viewer-fn? x) re-eval)) doc))) +(defn replace-viewer-fns [doc] + (w/postwalk-replace (:hash->viewer doc) (dissoc doc :hash->viewer))) + (defn ^:export set-state! [{:as state :keys [doc]}] (when (contains? state :doc) (when (exists? js/window) ;; TODO: can we restore the scroll position when navigating back? (.scrollTo js/window #js {:top 0})) - (reset! !doc doc)) + (reset! !doc (replace-viewer-fns doc))) ;; (when (and error (contains? @!doc :status)) ;; (swap! !doc dissoc :status)) (when (remount? doc) diff --git a/src/nextjournal/clerk/view.clj b/src/nextjournal/clerk/view.clj index 6f4ea108e..9edf13c08 100644 --- a/src/nextjournal/clerk/view.clj +++ b/src/nextjournal/clerk/view.clj @@ -1,15 +1,25 @@ (ns nextjournal.clerk.view - (:require [nextjournal.clerk.viewer :as v] - [hiccup.page :as hiccup] + (:require [clojure.java.io :as io] + [clojure.set :as set] [clojure.string :as str] - [clojure.java.io :as io]) + [clojure.walk :as walk] + [hiccup.page :as hiccup] + [nextjournal.clerk.viewer :as v]) (:import (java.net URI))) +(defn ^:private extract-hash->viewer [presentation] + (into {} + (map (juxt :hash identity)) + (keep :nextjournal/viewer (tree-seq (some-fn map? vector?) #(cond-> % (map? %) vals) presentation)))) + (defn doc->viewer ([doc] (doc->viewer {} doc)) ([opts {:as doc :keys [ns file]}] (binding [*ns* ns] - (-> (merge doc opts) v/notebook v/present)))) + (let [presentation (-> (merge doc opts) v/notebook v/present) + hash->viewer (extract-hash->viewer presentation)] + (assoc (walk/postwalk-replace (set/map-invert hash->viewer) presentation) + :hash->viewer hash->viewer))))) #_(doc->viewer (nextjournal.clerk/eval-file "notebooks/hello.clj")) #_(nextjournal.clerk/show! "notebooks/test.clj") From f73a784f9698041b0333c36aac43dfb87dea20d6 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Mon, 22 May 2023 12:35:16 +0200 Subject: [PATCH 02/17] Use unambigous qualified symbol for key --- src/nextjournal/clerk/view.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nextjournal/clerk/view.clj b/src/nextjournal/clerk/view.clj index 9edf13c08..8d176d2d1 100644 --- a/src/nextjournal/clerk/view.clj +++ b/src/nextjournal/clerk/view.clj @@ -7,9 +7,10 @@ [nextjournal.clerk.viewer :as v]) (:import (java.net URI))) + (defn ^:private extract-hash->viewer [presentation] (into {} - (map (juxt :hash identity)) + (map (juxt #(symbol "nextjournal.clerk" (str "viewer-fn$" (:hash %))) identity)) (keep :nextjournal/viewer (tree-seq (some-fn map? vector?) #(cond-> % (map? %) vals) presentation)))) (defn doc->viewer From 83c9a8cd7fcd185ae45b4127a0ac13ff4ec022ae Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 23 May 2023 13:15:24 +0200 Subject: [PATCH 03/17] Use render-opts to defer closing parens --- src/nextjournal/clerk/render.cljs | 13 ++++++------- src/nextjournal/clerk/viewer.cljc | 7 +++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/nextjournal/clerk/render.cljs b/src/nextjournal/clerk/render.cljs index 766e0be01..948425244 100644 --- a/src/nextjournal/clerk/render.cljs +++ b/src/nextjournal/clerk/render.cljs @@ -428,9 +428,9 @@ [triangle expanded?]] [:span.group-hover:text-indigo-700 opening-paren]])) -(defn render-coll [xs {:as opts :keys [path viewer !expanded-at] :or {path []}}] +(defn render-coll [xs {:as opts :keys [closing-paren path viewer !expanded-at] :or {path []}}] (let [expanded? (get @!expanded-at path) - {:keys [opening-paren closing-paren]} viewer] + {:keys [opening-paren]} viewer] [:span.inspected-value.whitespace-nowrap {:class (when expanded? "inline-flex")} [:span @@ -455,9 +455,8 @@ :on-click #(when (fn? fetch-fn) (fetch-fn fetch-opts))} (- total offset) (when unbounded? "+") (if (fn? fetch-fn) " more…" " more elided")])]) -(defn render-map [xs {:as opts :keys [path viewer !expanded-at] :or {path []}}] - (let [expanded? (get @!expanded-at path) - {:keys [closing-paren]} viewer] +(defn render-map [xs {:as opts :keys [closing-paren path viewer !expanded-at] :or {path []}}] + (let [expanded? (get @!expanded-at path)] [:span.inspected-value.whitespace-nowrap {:class (when expanded? "inline-flex")} [:span @@ -481,8 +480,8 @@ (inspect-presented opts %))) (if (string? s) [s] s)))) -(defn render-quoted-string [s {:as opts :keys [path viewer !expanded-at] :or {path []}}] - (let [{:keys [opening-paren closing-paren]} viewer] +(defn render-quoted-string [s {:as opts :keys [closing-paren path viewer !expanded-at] :or {path []}}] + (let [{:keys [opening-paren]} viewer] [:span.inspected-value.inline-flex [:span.cmt-string (if (some #(and (string? %) (str/includes? % "\n")) (if (string? s) [s] s)) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 18bcda49e..0573fbf7f 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -1640,10 +1640,9 @@ (or (-> value last :nextjournal/viewer :closing-paren) ;; the last element can carry parens (and (= `map-entry-viewer (-> value last :nextjournal/viewer :name)) ;; the last element is a map entry whose value can carry parens (-> value last :nextjournal/value last :nextjournal/viewer :closing-paren))))] - (cond-> (cond - (not closing) node - defer-closing? (update node :nextjournal/viewer dissoc :closing-paren) - :else (update-in node [:nextjournal/viewer :closing-paren] cons closing-parens)) + (cond-> (if (or (not closing) defer-closing?) + node + (assoc-in node [:nextjournal/opts :closing-paren] (cons closing closing-parens))) non-leaf? (update :nextjournal/value (fn [xs] (into [] From 10474bf8f50b33d3c0d41813ac95293bcfd06e14 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 23 May 2023 14:50:49 +0200 Subject: [PATCH 04/17] Fix closing-parens tests --- test/nextjournal/clerk/viewer_test.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/nextjournal/clerk/viewer_test.clj b/test/nextjournal/clerk/viewer_test.clj index ddfc41a9a..e07970bf8 100644 --- a/test/nextjournal/clerk/viewer_test.clj +++ b/test/nextjournal/clerk/viewer_test.clj @@ -218,13 +218,13 @@ (-> after (get-in (path-to-value [0 1 1])) (get 2) - v/->viewer + :nextjournal/opts :closing-paren))) (is (= '(")" "}") (-> after (get-in (path-to-value [1])) (get 1) - v/->viewer + :nextjournal/opts :closing-paren)))))) (defn tree-re-find [data re] From ad55c8910460b0b63de7a15a589cde152661d9d5 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Wed, 24 May 2023 21:14:27 +0200 Subject: [PATCH 05/17] Fix viewer tests --- src/nextjournal/clerk/view.clj | 5 ++++- src/nextjournal/clerk/viewer.cljc | 6 +++--- test/nextjournal/clerk/viewer_test.clj | 11 +++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/nextjournal/clerk/view.clj b/src/nextjournal/clerk/view.clj index 8d176d2d1..a7854d5fa 100644 --- a/src/nextjournal/clerk/view.clj +++ b/src/nextjournal/clerk/view.clj @@ -10,7 +10,10 @@ (defn ^:private extract-hash->viewer [presentation] (into {} - (map (juxt #(symbol "nextjournal.clerk" (str "viewer-fn$" (:hash %))) identity)) + (map (juxt (fn [viewer] + (or (when (qualified-symbol? (:name viewer)) + (symbol (namespace (:name viewer)) (str (name (:name viewer)) "$" (:hash viewer)))) + (symbol "nextjournal.clerk.viewer" (str "viewer-fn$" (:hash viewer))))) identity)) (keep :nextjournal/viewer (tree-seq (some-fn map? vector?) #(cond-> % (map? %) vals) presentation)))) (defn doc->viewer diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 0573fbf7f..3a46108fb 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -801,13 +801,13 @@ (nextjournal.clerk.render/render-unreadable-edn x))))}) (def vector-viewer - {:pred vector? :render-fn 'nextjournal.clerk.render/render-coll :opening-paren "[" :closing-paren "]" :page-size 20}) + {:pred vector? :name `vector-viewer :render-fn 'nextjournal.clerk.render/render-coll :opening-paren "[" :closing-paren "]" :page-size 20}) (def set-viewer - {:pred set? :render-fn 'nextjournal.clerk.render/render-coll :opening-paren "#{" :closing-paren "}" :page-size 20}) + {:pred set? :name `set-viewer :render-fn 'nextjournal.clerk.render/render-coll :opening-paren "#{" :closing-paren "}" :page-size 20}) (def sequential-viewer - {:pred sequential? :render-fn 'nextjournal.clerk.render/render-coll :opening-paren "(" :closing-paren ")" :page-size 20}) + {:pred sequential? :name `sequential-viewer :render-fn 'nextjournal.clerk.render/render-coll :opening-paren "(" :closing-paren ")" :page-size 20}) (def map-viewer {:pred map? :name `map-viewer :render-fn 'nextjournal.clerk.render/render-map :opening-paren "{" :closing-paren "}" :page-size 10}) diff --git a/test/nextjournal/clerk/viewer_test.clj b/test/nextjournal/clerk/viewer_test.clj index e07970bf8..079fac2b0 100644 --- a/test/nextjournal/clerk/viewer_test.clj +++ b/test/nextjournal/clerk/viewer_test.clj @@ -339,23 +339,26 @@ (is (= 5 (count (->> (eval/eval-string "^{:nextjournal.clerk/budget 5}(reduce (fn [acc _i] (vector acc)) :fin (range 100 0 -1))") - view/doc->viewer v/->value :blocks + view/doc->viewer v/->value + :blocks (tree-seq coll? seq) - (filter (every-pred map? (comp #{'nextjournal.clerk.render/render-coll} :form :render-fn))))))) + (keep :nextjournal/viewer) + (filter #(str/starts-with? (str %) "nextjournal.clerk.viewer/vector-viewer")))))) (is (= 5 (count (->> (eval/eval-string "(nextjournal.clerk/with-viewer {} {:nextjournal.clerk/budget 5} (reduce (fn [acc i] (vector acc)) :fin (range 15 0 -1)))") view/doc->viewer v/->value :blocks (tree-seq coll? seq) - (filter (every-pred map? (comp #{'nextjournal.clerk.render/render-coll} :form :render-fn))))))) + (keep :nextjournal/viewer) + (filter #(str/starts-with? (str %) "nextjournal.clerk.viewer/vector-viewer")))))) (is (= 101 (count (->> (eval/eval-string "^{:nextjournal.clerk/budget nil}(reduce (fn [acc i] (vector i acc)) :fin (range 101 0 -1))") view/doc->viewer v/->value :blocks (tree-seq coll? seq) - (filter (every-pred map? (comp #{'nextjournal.clerk.render/render-coll} :form :render-fn))))))))) + (filter #(str/starts-with? (str %) "nextjournal.clerk.viewer/vector-viewer")))))))) (deftest ->edn (testing "normal symbols and keywords" From 6568cfcfde656d13a709b1cce5b5645004ad8cd0 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Wed, 24 May 2023 21:33:41 +0200 Subject: [PATCH 06/17] Simplify test --- test/nextjournal/clerk/viewer_test.clj | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/nextjournal/clerk/viewer_test.clj b/test/nextjournal/clerk/viewer_test.clj index 079fac2b0..74344f934 100644 --- a/test/nextjournal/clerk/viewer_test.clj +++ b/test/nextjournal/clerk/viewer_test.clj @@ -343,7 +343,7 @@ :blocks (tree-seq coll? seq) (keep :nextjournal/viewer) - (filter #(str/starts-with? (str %) "nextjournal.clerk.viewer/vector-viewer")))))) + (filter #{'nextjournal.clerk.viewer/vector-viewer$5dsD1KJESfc8Dy8gPeGQfZCX2ayE8f}))))) (is (= 5 (count @@ -351,14 +351,15 @@ view/doc->viewer v/->value :blocks (tree-seq coll? seq) (keep :nextjournal/viewer) - (filter #(str/starts-with? (str %) "nextjournal.clerk.viewer/vector-viewer")))))) + (filter #{'nextjournal.clerk.viewer/vector-viewer$5dsD1KJESfc8Dy8gPeGQfZCX2ayE8f}))))) (is (= 101 (count (->> (eval/eval-string "^{:nextjournal.clerk/budget nil}(reduce (fn [acc i] (vector i acc)) :fin (range 101 0 -1))") view/doc->viewer v/->value :blocks (tree-seq coll? seq) - (filter #(str/starts-with? (str %) "nextjournal.clerk.viewer/vector-viewer")))))))) + (keep :nextjournal/viewer) + (filter #{'nextjournal.clerk.viewer/vector-viewer$5dsD1KJESfc8Dy8gPeGQfZCX2ayE8f}))))))) (deftest ->edn (testing "normal symbols and keywords" From ce628dfd5fc6bde77e7603cc5de3383c9323cf46 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Wed, 24 May 2023 21:50:16 +0200 Subject: [PATCH 07/17] Fix eval tests --- test/nextjournal/clerk/eval_test.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/nextjournal/clerk/eval_test.clj b/test/nextjournal/clerk/eval_test.clj index 57290359e..8617a2948 100644 --- a/test/nextjournal/clerk/eval_test.clj +++ b/test/nextjournal/clerk/eval_test.clj @@ -166,14 +166,14 @@ (mapv #(select-keys % [:nextjournal/width])))))) (testing "can handle uncounted sequences" - (is (match? [{:nextjournal/viewer {:name `viewer/code-block-viewer} + (is (match? [{:nextjournal/viewer `viewer/code-block-viewer$5dru1FUcVRTRrVKJFbNw4FG2wXmiwB :nextjournal/value "(range)"} {:nextjournal/value {:nextjournal/fetch-opts {:blob-id string?} :nextjournal/hash string?}}] (eval+extract-doc-blocks "(range)")))) (testing "assigns folded visibility" - (is (match? [{:nextjournal/viewer {:name `viewer/folded-code-block-viewer} + (is (match? [{:nextjournal/viewer `viewer/folded-code-block-viewer$5dt3F3pXDCJHWEKwRWd1FTwBTC7bQ1 :nextjournal/value "{:some :map}"} {:nextjournal/value {:nextjournal/fetch-opts {:blob-id string?} :nextjournal/hash string?}}] From eda1dfb2af8d209f38781f1e2fe1b0311c9e0f91 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Wed, 24 May 2023 21:51:48 +0200 Subject: [PATCH 08/17] Fix webserver test --- test/nextjournal/clerk/webserver_test.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nextjournal/clerk/webserver_test.clj b/test/nextjournal/clerk/webserver_test.clj index 14da28667..1495414d5 100644 --- a/test/nextjournal/clerk/webserver_test.clj +++ b/test/nextjournal/clerk/webserver_test.clj @@ -16,7 +16,7 @@ {:nextjournal/keys [value]} presented {elision-viewer :nextjournal/viewer elision-fetch-opts :nextjournal/value} (peek value) {:keys [body]} (webserver/serve-blob doc (merge fetch-opts {:fetch-opts elision-fetch-opts}))] - (is (= `nextjournal.clerk.viewer/elision-viewer (:name elision-viewer))) + (is (= `nextjournal.clerk.viewer/elision-viewer$5drduatKq2QJCDhSX1Pu45i4whSPHk elision-viewer)) (is body) (is (= (-> body webserver/read-msg :nextjournal/value first :nextjournal/value) 20))))) From e5e18261da47de4b70bd170f49b7120bfae0dd8b Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Thu, 25 May 2023 14:51:27 +0200 Subject: [PATCH 09/17] Drop render-map, use different key for assigned parens --- src/nextjournal/clerk/render.cljs | 33 +++++++++---------------------- src/nextjournal/clerk/viewer.cljc | 8 ++++---- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/nextjournal/clerk/render.cljs b/src/nextjournal/clerk/render.cljs index 948425244..84bb0cd3d 100644 --- a/src/nextjournal/clerk/render.cljs +++ b/src/nextjournal/clerk/render.cljs @@ -428,21 +428,20 @@ [triangle expanded?]] [:span.group-hover:text-indigo-700 opening-paren]])) -(defn render-coll [xs {:as opts :keys [closing-paren path viewer !expanded-at] :or {path []}}] +(defn render-coll [xs {:as opts :keys [closing-parens path viewer !expanded-at] :or {path []}}] (let [expanded? (get @!expanded-at path) - {:keys [opening-paren]} viewer] + {:keys [opening-paren closing-paren]} viewer] [:span.inspected-value.whitespace-nowrap {:class (when expanded? "inline-flex")} [:span - (if (< 1 (count xs)) + (if (expandable? xs) [expand-button !expanded-at opening-paren path] [:span opening-paren]) (into [:<>] (comp (inspect-children opts) (interpose (if expanded? [:<> [:br] triangle-spacer nbsp (when (= 2 (count opening-paren)) nbsp)] " "))) xs) - [:span - (cond->> closing-paren (list? closing-paren) (into [:<>]))]]])) + (into [:span] (or closing-parens [closing-paren]))]])) (defn render-elision [{:as fetch-opts :keys [total offset unbounded?]} _] [view-context/consume :fetch-fn @@ -455,20 +454,6 @@ :on-click #(when (fn? fetch-fn) (fetch-fn fetch-opts))} (- total offset) (when unbounded? "+") (if (fn? fetch-fn) " more…" " more elided")])]) -(defn render-map [xs {:as opts :keys [closing-paren path viewer !expanded-at] :or {path []}}] - (let [expanded? (get @!expanded-at path)] - [:span.inspected-value.whitespace-nowrap - {:class (when expanded? "inline-flex")} - [:span - (if (expandable? xs) - [expand-button !expanded-at "{" path] - [:span "{"]) - (into [:<>] - (comp (inspect-children opts) - (interpose (if expanded? [:<> [:br] triangle-spacer nbsp #_(repeat (inc (count path)) nbsp)] " "))) - xs) - (cond->> closing-paren (list? closing-paren) (into [:<>]))]])) - (defn render-string [s {:as opts :keys [path !expanded-at] :or {path []}}] (let [expanded? (get @!expanded-at path)] @@ -480,16 +465,16 @@ (inspect-presented opts %))) (if (string? s) [s] s)))) -(defn render-quoted-string [s {:as opts :keys [closing-paren path viewer !expanded-at] :or {path []}}] - (let [{:keys [opening-paren]} viewer] +(defn render-quoted-string [s {:as opts :keys [closing-parens path viewer !expanded-at] :or {path []}}] + (let [{:keys [opening-paren closing-paren]} viewer] [:span.inspected-value.inline-flex [:span.cmt-string (if (some #(and (string? %) (str/includes? % "\n")) (if (string? s) [s] s)) [expand-button !expanded-at opening-paren path] [:span opening-paren])] - [:div - [:span.cmt-string (viewer/->value (render-string s opts)) (first closing-paren)] - (when (list? closing-paren) (into [:<>] (rest closing-paren)))]])) + (into [:div + [:span.cmt-string (viewer/->value (render-string s opts)) (first closing-paren)] + (rest closing-parens)])])) (defn render-number [num] [:span.cmt-number.inspected-value diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 3a46108fb..1ab8ef402 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -810,7 +810,7 @@ {:pred sequential? :name `sequential-viewer :render-fn 'nextjournal.clerk.render/render-coll :opening-paren "(" :closing-paren ")" :page-size 20}) (def map-viewer - {:pred map? :name `map-viewer :render-fn 'nextjournal.clerk.render/render-map :opening-paren "{" :closing-paren "}" :page-size 10}) + {:pred map? :name `map-viewer :render-fn 'nextjournal.clerk.render/render-coll :opening-paren "{" :closing-paren "}" :page-size 10}) #?(:cljs (defn var->symbol [v] (if (instance? sci.lang.Var v) (sci.impl.vars/toSymbol v) (symbol v)))) @@ -1640,9 +1640,9 @@ (or (-> value last :nextjournal/viewer :closing-paren) ;; the last element can carry parens (and (= `map-entry-viewer (-> value last :nextjournal/viewer :name)) ;; the last element is a map entry whose value can carry parens (-> value last :nextjournal/value last :nextjournal/viewer :closing-paren))))] - (cond-> (if (or (not closing) defer-closing?) - node - (assoc-in node [:nextjournal/opts :closing-paren] (cons closing closing-parens))) + (cond-> (assoc-in node [:nextjournal/opts :closing-parens] (if (or (not closing) defer-closing?) + '() + (cons closing closing-parens))) non-leaf? (update :nextjournal/value (fn [xs] (into [] From 82bc223f59c20b4d1fdebd4c1bc395ebeb795838 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Wed, 24 May 2023 22:11:44 +0200 Subject: [PATCH 10/17] Naming pass, extract viewers for serve blob --- src/nextjournal/clerk/render.cljs | 7 +++---- src/nextjournal/clerk/view.clj | 12 +++++++----- src/nextjournal/clerk/webserver.clj | 2 +- test/nextjournal/clerk/webserver_test.clj | 1 - 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/nextjournal/clerk/render.cljs b/src/nextjournal/clerk/render.cljs index 84bb0cd3d..78a128d0f 100644 --- a/src/nextjournal/clerk/render.cljs +++ b/src/nextjournal/clerk/render.cljs @@ -300,13 +300,15 @@ (defn read-string [s] (js/nextjournal.clerk.sci_env.read-string s)) +(defn replace-viewer-fns [doc] + (w/postwalk-replace (:name->viewer doc) (dissoc doc :name->viewer))) (defn fetch! [{:keys [blob-id]} opts] #_(js/console.log :fetch! blob-id opts) (-> (js/fetch (str "/_blob/" blob-id (when (seq opts) (str "?" (opts->query opts))))) (.then #(.text %)) - (.then #(try (read-string %) + (.then #(try (replace-viewer-fns (read-string %)) (catch js/Error e (js/console.error #js {:message "sci read error" :blob-id blob-id :code-string % :error e}) (render-unreadable-edn %)))))) @@ -682,9 +684,6 @@ (let [re-eval (fn [{:keys [form]}] (viewer/->viewer-fn form))] (w/postwalk (fn [x] (cond-> x (viewer/viewer-fn? x) re-eval)) doc))) -(defn replace-viewer-fns [doc] - (w/postwalk-replace (:hash->viewer doc) (dissoc doc :hash->viewer))) - (defn ^:export set-state! [{:as state :keys [doc]}] (when (contains? state :doc) (when (exists? js/window) diff --git a/src/nextjournal/clerk/view.clj b/src/nextjournal/clerk/view.clj index a7854d5fa..59172af6f 100644 --- a/src/nextjournal/clerk/view.clj +++ b/src/nextjournal/clerk/view.clj @@ -8,7 +8,7 @@ (:import (java.net URI))) -(defn ^:private extract-hash->viewer [presentation] +(defn ^:private extract-name->viewer [presentation] (into {} (map (juxt (fn [viewer] (or (when (qualified-symbol? (:name viewer)) @@ -16,14 +16,16 @@ (symbol "nextjournal.clerk.viewer" (str "viewer-fn$" (:hash viewer))))) identity)) (keep :nextjournal/viewer (tree-seq (some-fn map? vector?) #(cond-> % (map? %) vals) presentation)))) +(defn +name->viewer [presentation] + (let [name->viewer (extract-name->viewer presentation)] + (assoc (walk/postwalk-replace (set/map-invert name->viewer) presentation) + :name->viewer name->viewer))) + (defn doc->viewer ([doc] (doc->viewer {} doc)) ([opts {:as doc :keys [ns file]}] (binding [*ns* ns] - (let [presentation (-> (merge doc opts) v/notebook v/present) - hash->viewer (extract-hash->viewer presentation)] - (assoc (walk/postwalk-replace (set/map-invert hash->viewer) presentation) - :hash->viewer hash->viewer))))) + (-> (merge doc opts) v/notebook v/present +name->viewer)))) #_(doc->viewer (nextjournal.clerk/eval-file "notebooks/hello.clj")) #_(nextjournal.clerk/show! "notebooks/test.clj") diff --git a/src/nextjournal/clerk/webserver.clj b/src/nextjournal/clerk/webserver.clj index 57dcda149..174c923d4 100644 --- a/src/nextjournal/clerk/webserver.clj +++ b/src/nextjournal/clerk/webserver.clj @@ -84,7 +84,7 @@ (if (contains? desc :nextjournal/content-type) {:body (v/->value desc) :content-type (:nextjournal/content-type desc)} - {:body (v/->edn desc)})) + {:body (v/->edn (view/+name->viewer desc))})) {:status 404})) (defn extract-blob-opts [{:as _req :keys [uri query-string]}] diff --git a/test/nextjournal/clerk/webserver_test.clj b/test/nextjournal/clerk/webserver_test.clj index 1495414d5..d5f15cf16 100644 --- a/test/nextjournal/clerk/webserver_test.clj +++ b/test/nextjournal/clerk/webserver_test.clj @@ -19,4 +19,3 @@ (is (= `nextjournal.clerk.viewer/elision-viewer$5drduatKq2QJCDhSX1Pu45i4whSPHk elision-viewer)) (is body) (is (= (-> body webserver/read-msg :nextjournal/value first :nextjournal/value) 20))))) - From 9259a32d622d176bd3a7e72a93230d04e33d7560 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Fri, 26 May 2023 10:45:15 +0200 Subject: [PATCH 11/17] Fix tests --- test/nextjournal/clerk/viewer_test.clj | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/test/nextjournal/clerk/viewer_test.clj b/test/nextjournal/clerk/viewer_test.clj index 74344f934..69ca961a2 100644 --- a/test/nextjournal/clerk/viewer_test.clj +++ b/test/nextjournal/clerk/viewer_test.clj @@ -173,17 +173,20 @@ (v/present 10/33)))) (testing "opts are not propagated to children during presentation" - (let [count-opts (fn [o] + (let [count-opts (fn [o k] (let [c (atom 0)] - (w/postwalk (fn [f] (when (= :nextjournal/opts f) (swap! c inc)) f) o) + (w/postwalk (fn [f] (when (and (map? f) + (contains? f :nextjournal/opts) + (-> f :nextjournal/opts k)) + (swap! c inc)) f) o) @c))] (let [presented (v/present (v/col {:nextjournal.clerk/opts {:width 150}} 1 2 3))] - (is (= {:width 150} (:nextjournal/opts presented))) - (is (= 1 (count-opts presented)))) + (is (match? {:width 150} (:nextjournal/opts presented))) + (is (= 1 (count-opts presented :width)))) (let [presented (v/present (v/table {:col1 [1 2] :col2 '[a b]}))] - (is (= {:num-cols 2 :number-col? #{0}} (:nextjournal/opts presented))) - (is (= 1 (count-opts presented)))))) + (is (match? {:num-cols 2 :number-col? #{0}} (:nextjournal/opts presented))) + (is (= 1 (count-opts presented :num-cols)))))) (testing "viewer opts are normalized" (is (= (v/desc->values (v/present {:nextjournal/value (range 10) :nextjournal/budget 3})) @@ -219,13 +222,13 @@ (get-in (path-to-value [0 1 1])) (get 2) :nextjournal/opts - :closing-paren))) + :closing-parens))) (is (= '(")" "}") (-> after (get-in (path-to-value [1])) (get 1) :nextjournal/opts - :closing-paren)))))) + :closing-parens)))))) (defn tree-re-find [data re] (->> data From 4a6a64aaee0fff37d07b31afe4c161beb86f7153 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 30 May 2023 12:20:23 +0200 Subject: [PATCH 12/17] Fix closing parens in fetched elisions --- src/nextjournal/clerk/viewer.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 1ab8ef402..16ccd682f 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -1466,7 +1466,7 @@ (defn ^:private present-elision* [!path->wrapped-value {:as fetch-opts :keys [path]}] (if-let [wrapped-value (@!path->wrapped-value path)] - (present* (merge wrapped-value (make-!budget-opts wrapped-value) fetch-opts)) + (assign-closing-parens (present* (merge wrapped-value (make-!budget-opts wrapped-value) fetch-opts))) (throw (ex-info "could not find wrapped-value at path" {:!path->wrapped-value !path->wrapped-value :fetch-otps fetch-opts})))) From 7a94358d93ead68adee7c0a37fe486573d2a2b93 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 30 May 2023 12:42:28 +0200 Subject: [PATCH 13/17] Remove redundant declare --- src/nextjournal/clerk/webserver.clj | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/nextjournal/clerk/webserver.clj b/src/nextjournal/clerk/webserver.clj index 174c923d4..b5b88dc50 100644 --- a/src/nextjournal/clerk/webserver.clj +++ b/src/nextjournal/clerk/webserver.clj @@ -173,8 +173,6 @@ (apply swap! nextjournal.clerk.atom/my-state (eval '[update :counter inc])) (eval '(nextjournal.clerk/recompute!))) -(declare present+reset!) - (defn ->nav-path [file-or-ns] (cond (or (symbol? file-or-ns) (instance? clojure.lang.Namespace file-or-ns)) (str "'" file-or-ns) From 55b58e84d89a39adf7f47eb121cc4998c64cb010 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 30 May 2023 14:58:22 +0200 Subject: [PATCH 14/17] Fix patching client-side doc state --- src/nextjournal/clerk/render.cljs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/nextjournal/clerk/render.cljs b/src/nextjournal/clerk/render.cljs index 78a128d0f..4b097d9f1 100644 --- a/src/nextjournal/clerk/render.cljs +++ b/src/nextjournal/clerk/render.cljs @@ -300,8 +300,9 @@ (defn read-string [s] (js/nextjournal.clerk.sci_env.read-string s)) -(defn replace-viewer-fns [doc] - (w/postwalk-replace (:name->viewer doc) (dissoc doc :name->viewer))) +(defn replace-viewer-fns [{:as doc :keys [name->viewer]}] + (assoc (w/postwalk-replace name->viewer doc) + :name->viewer name->viewer)) (defn fetch! [{:keys [blob-id]} opts] #_(js/console.log :fetch! blob-id opts) @@ -596,6 +597,8 @@ (if (valid-react-element? x) x (let [{:nextjournal/keys [value viewer] :keys [path]} x] + (when-not (:render-fn viewer) + (throw (ex-info "A render function is missing" {:viewer viewer}))) #_(prn :inspect-presented value :valid-element? (react/isValidElement value) :viewer viewer) ;; each view function must be called in its own 'functional component' so that it gets its own hook state. ^{:key (str (:hash viewer) "@" (peek (:path opts)))} @@ -702,10 +705,10 @@ (defn patch-state! [{:keys [patch]}] (if (remount? patch) - (do (swap! !doc #(re-eval-viewer-fns (apply-patch % patch))) + (do (swap! !doc #(re-eval-viewer-fns (replace-viewer-fns (apply-patch % patch)))) ;; TODO: figure out why it doesn't work without `js/setTimeout` (js/setTimeout #(swap! !eval-counter inc) 10)) - (swap! !doc apply-patch patch))) + (swap! !doc #(replace-viewer-fns (apply-patch % patch))))) (defonce !pending-clerk-eval-replies (atom {})) From 3962858339f8605db118a9cee77a7c8b076f5265 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Fri, 16 Jun 2023 15:52:56 +0200 Subject: [PATCH 15/17] render-opts --- src/nextjournal/clerk/viewer.cljc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 5d1c0e6f9..f6656cde3 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -1700,9 +1700,9 @@ (or (-> value last :nextjournal/viewer :closing-paren) ;; the last element can carry parens (and (= `map-entry-viewer (-> value last :nextjournal/viewer :name)) ;; the last element is a map entry whose value can carry parens (-> value last :nextjournal/value last :nextjournal/viewer :closing-paren))))] - (cond-> (assoc-in node [:nextjournal/opts :closing-parens] (if (or (not closing) defer-closing?) - '() - (cons closing closing-parens))) + (cond-> (assoc-in node [:nextjournal/render-opts :closing-parens] (if (or (not closing) defer-closing?) + '() + (cons closing closing-parens))) non-leaf? (update :nextjournal/value (fn [xs] (into [] From 00e9bdb8c4e168c1efb66119d60b297be466d9f6 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Fri, 16 Jun 2023 16:04:51 +0200 Subject: [PATCH 16/17] render-opts 2 --- test/nextjournal/clerk/viewer_test.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/nextjournal/clerk/viewer_test.clj b/test/nextjournal/clerk/viewer_test.clj index ec9d478ea..bf4b1e7ed 100644 --- a/test/nextjournal/clerk/viewer_test.clj +++ b/test/nextjournal/clerk/viewer_test.clj @@ -228,13 +228,13 @@ (-> after (get-in (path-to-value [0 1 1])) (get 2) - :nextjournal/opts + :nextjournal/render-opts :closing-parens))) (is (= '(")" "}") (-> after (get-in (path-to-value [1])) (get 1) - :nextjournal/opts + :nextjournal/render-opts :closing-parens)))))) (defn tree-re-find [data re] From 493b1b8f7c66aa198a7c348bf47fe5a9875dca20 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Fri, 16 Jun 2023 18:47:33 +0200 Subject: [PATCH 17/17] render-coll --- src/nextjournal/clerk/viewer.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index f6656cde3..9888f72cf 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -824,7 +824,7 @@ {:name `sequential-viewer :pred sequential? :render-fn 'nextjournal.clerk.render/render-coll :opening-paren "(" :closing-paren ")" :page-size 20}) (def map-viewer - {:name `map-viewer :pred map? :render-fn 'nextjournal.clerk.render/render-map :opening-paren "{" :closing-paren "}" :page-size 10}) + {:name `map-viewer :pred map? :render-fn 'nextjournal.clerk.render/render-coll :opening-paren "{" :closing-paren "}" :page-size 10}) #?(:cljs (defn var->symbol [v] (if (instance? sci.lang.Var v) (sci.impl.vars/toSymbol v) (symbol v))))