diff --git a/CHANGELOG.md b/CHANGELOG.md index ce35ae92d..04c060d33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,15 +16,25 @@ Changes can be: * 💫 Support toggling auto-expansion of results in tap viewer +* 💫 Redesign examples viewer to be more readable and in a way that doesn't force `display: flex` onto contents. + * 🛠 Bump depdendencies * `com.taoensso/nippy` to `3.4.0-beta1` * `io.github.nextjournal/markdown` to `0.5.146` +* 🐜 Fix blank screen caused by react unmounting when an exception occurs during `clerk/show!`, fixes [#586](https://github.com/nextjournal/clerk/issues/586) @elken + +* 🐜 Make edn transmission resilient to symbols and keywords containing multiple slashes like `foo/bar/baz`. Those can be read by `read-string` but not in ClojureScript which is based on `tools.reader`. + +* 🐞 Fix `:nextjournal.clerk/page-size` option throwing when set on string values, fixes [#584][https://github.com/nextjournal/clerk/issues/584] + * 🐞 Fix caching behaviour of `clerk/image` and support overriding image-viewer by name * 🐞 Fix tap viewer keeping open/collapsed state [#543](https://github.com/nextjournal/clerk/issues/543) @teodorlu +* 🐞 Fix `unquote` in experimental cljs Clerk editor, fixes [#576](https://github.com/nextjournal/clerk/issues/576) @sritchie + * 🐞 Fix `row` and `col` viewers not showing a first map argument, fixes [#567](https://github.com/nextjournal/clerk/issues/567) @teodorlu * 🐞 Fix long sidenotes overlapping with subsequent content, fixes [#564](https://github.com/nextjournal/clerk/issues/564) @hlship diff --git a/deps.edn b/deps.edn index 034ae0c55..6d0fbfd35 100644 --- a/deps.edn +++ b/deps.edn @@ -9,7 +9,7 @@ com.nextjournal/beholder {:mvn/version "1.0.2"} org.flatland/ordered {:mvn/version "1.15.11"} - io.github.nextjournal/markdown {:mvn/version "0.5.146"} + io.github.nextjournal/markdown {:mvn/version "0.5.148"} babashka/process {:mvn/version "0.4.16"} io.github.nextjournal/dejavu {:git/sha "4980e0cc18c9b09fb220874ace94ba6b57a749ca"} diff --git a/notebooks/meta_toc.clj b/notebooks/meta_toc.clj index 1d9a94052..2f3af28ac 100644 --- a/notebooks/meta_toc.clj +++ b/notebooks/meta_toc.clj @@ -18,7 +18,7 @@ (defn md-toc->navbar-items [current-notebook file {:keys [children]}] (mapv (fn [{:as item :keys [emoji attrs]}] - {:title (md.transform/->text item) + {:title (cond-> (md.transform/->text item) (seq emoji) (subs (count emoji))) :expanded? (= current-notebook file) :scroll-to-anchor? false :emoji emoji @@ -37,7 +37,7 @@ (fn [wrapped-value] (-> wrapped-value original-transform - (assoc :nextjournal/opts {:expandable? true}) + (assoc :nextjournal/render-opts {:expandable-toc? true}) (assoc-in [:nextjournal/value :toc] (meta-toc (:file (v/->value wrapped-value)) notebooks))))))) diff --git a/package.json b/package.json index 43e4f1880..236957144 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "markdown-it": "^12.2.0", "markdown-it-block-image": "^0.0.3", "markdown-it-footnote": "^3.0.3", - "markdown-it-texmath": "^0.9.1", + "markdown-it-texmath": "^1.0.0", "markdown-it-toc-done-right": "^4.2.0", "punycode": "2.1.1", "react": "^18.2.0", diff --git a/src/nextjournal/clerk/render.cljs b/src/nextjournal/clerk/render.cljs index 1c65bb235..0f83bd991 100644 --- a/src/nextjournal/clerk/render.cljs +++ b/src/nextjournal/clerk/render.cljs @@ -484,7 +484,7 @@ [:div.font-bold "Unhandled " type]) [:div.font-bold.mt-1 message] (when data - [:div.mt-1 [inspect data]])]) + [:div.mt-1 [inspect (viewer/inspect-wrapped-values data)]])]) via)) [:div.py-6.overflow-x-auto [:table.w-full diff --git a/src/nextjournal/clerk/render/editor.cljs b/src/nextjournal/clerk/render/editor.cljs index bfbcdc1d6..2425b7749 100644 --- a/src/nextjournal/clerk/render/editor.cljs +++ b/src/nextjournal/clerk/render/editor.cljs @@ -147,9 +147,9 @@ (update doc :blocks (partial map (fn [{:as cell :keys [type text var form]}] (cond-> cell (= :code type) - (assoc :result - {:nextjournal/value (cond->> (eval form) - var (hash-map :nextjournal.clerk/var-from-def))})))))) + (assoc :result {:nextjournal/value + (cond->> (eval-string text) + var (hash-map :nextjournal.clerk/var-from-def))})))))) (defn eval-notebook [code] (->> code diff --git a/src/nextjournal/clerk/render/navbar.cljs b/src/nextjournal/clerk/render/navbar.cljs index 3dada1142..f4ac2b05f 100644 --- a/src/nextjournal/clerk/render/navbar.cljs +++ b/src/nextjournal/clerk/render/navbar.cljs @@ -46,13 +46,14 @@ (.pushState js/history #js {} "" anchor)) (scroll-to-anchor! anchor))))) -(defn render-items [items {:as render-opts :keys [!expanded-at expandable-toc? mobile-toc?]}] +(defn render-items [items {:as render-opts :keys [!expanded-at mobile-toc?]}] (into [:div] (map-indexed - (fn [i {:as item :keys [emoji path title items]}] + (fn [i {:as item :keys [href css-class emoji path title items]}] (let [label (or title (str/capitalize (last (str/split path #"/")))) - expanded? (get-in @!expanded-at [:toc path])] + expanded? (get-in @!expanded-at [:toc path]) + {:keys [expandable-toc?]} (merge render-opts item)] [:div.text-base.leading-normal.dark:text-white {:class "md:text-[14px]"} (if (seq items) @@ -70,7 +71,7 @@ :class (if expanded? "rotate-90" "rotate-0")} [:path {:stroke-linecap "round" :stroke-linejoin "round" :d "M8.25 4.5l7.5 7.5-7.5 7.5"}]]]) [:a.py-1.flex.flex-auto.gap-1.group-hover:text-indigo-700.dark:group-hover:text-white.hover:underline.decoration-indigo-300.dark:decoration-slate-400.underline-offset-2 - {:href path + {:href (or href path) :class (when (and expandable-toc? expanded?) "font-medium") :on-click (fn [event] (navigate-or-scroll! event item render-opts) @@ -78,20 +79,20 @@ (swap! !expanded-at assoc :toc-open? false)))} (when emoji [:span.flex-shrink-0 emoji]) - [:span label]] + [:span {:class css-class} label]] (when (and expandable-toc? expanded?) [:span.absolute.bottom-0.border-l.border-slate-300.dark:border-slate-600 {:class "top-[25px] left-[10px]"}])] [:a.flex.flex-auto.gap-1.py-1.rounded.hover:bg-slate-200.dark:hover:bg-slate-900.hover:text-indigo-700.dark:hover:text-white.hover:underline.decoration-indigo-300.dark:decoration-slate-400.underline-offset-2.transition {:class "px-[6px] ml-[8px] mr-[4px]" - :href path + :href (or href path) :on-click (fn [event] (navigate-or-scroll! event item render-opts) (when mobile-toc? (swap! !expanded-at assoc :toc-open? false)))} (when emoji [:span.flex-shrink-0 emoji]) - [:span label]]) + [:span {:class css-class} label]]) (when (and (seq items) (or (not expandable-toc?) (and expandable-toc? expanded?))) [:div.relative {:class (str (if expandable-toc? "ml-[16px] " "ml-[19px] ") @@ -100,6 +101,7 @@ [:span.absolute.top-0.border-l.border-slate-300.dark:border-slate-600 {:class "left-[2px] bottom-[8px]"}]) [render-items items render-opts]])])) + items))) (def local-storage-key "clerk-navbar") diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index a4a629148..c48748e9e 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -8,6 +8,7 @@ [flatland.ordered.map :refer [ordered-map]] #?@(:clj [[babashka.fs :as fs] [clojure.repl :refer [demunge]] + [clojure.tools.reader :as tools.reader] [editscript.edit] [nextjournal.clerk.config :as config] [nextjournal.clerk.analyzer :as analyzer]] @@ -361,7 +362,7 @@ #?(:clj (defn roundtrippable? [x] (try - (= x (-> x str read-string)) + (= x (-> x str tools.reader/read-string)) (catch Exception _e false)))) #?(:clj @@ -375,7 +376,7 @@ #?(:clj (defmethod print-method clojure.lang.Symbol [o w] (if (or (roundtrippable? o) - (= (name o) "?@")) ;; splicing reader conditional, see issue #338 + (= (name o) "?@")) ;; splicing reader conditional, see issue #338 (print-simple o w) (.write w (pr-str (->viewer-eval (if-let [ns (namespace o)] (list 'symbol ns (name o)) @@ -609,6 +610,9 @@ #_(present @nextjournal.clerk.webserver/!doc) +(defn update-if [m k f] (if (k m) (update m k f) m)) +#_(update-if {:n "42"} :n #(Integer/parseInt %)) + (defn with-block-viewer [doc {:as cell :keys [type id]}] (case type :markdown (let [{:keys [content]} (:doc cell) @@ -622,7 +626,7 @@ ::doc doc} doc))])) (partition-by (comp #{:image} :type) content))) - :code (let [cell (update cell :result apply-viewer-unwrapping-var-from-def) + :code (let [cell (update-if cell :result apply-viewer-unwrapping-var-from-def) {:keys [code? result? fold?]} (->display cell) eval? (-> cell :result :nextjournal/value (get-safe :nextjournal/value) viewer-eval?)] (cond-> [] @@ -631,7 +635,7 @@ {:nextjournal/render-opts (assoc (select-keys cell [:loc]) :id (processed-block-id (str id "-code")))} (dissoc cell :result))) - (or result? eval?) + (and (:result cell) (or result? eval?)) (conj (cond-> (ensure-wrapped (-> cell (assoc ::doc doc) (set/rename-keys {:result ::result}))) (and eval? (not result?)) (assoc :nextjournal/viewer (assoc result-viewer :render-fn '(fn [_] [:<>]))))))))) @@ -866,7 +870,8 @@ {:name `throwable-viewer :render-fn 'nextjournal.clerk.render/render-throwable :pred (fn [e] (instance? #?(:clj Throwable :cljs js/Error) e)) - :transform-fn (comp mark-presented (update-val (comp demunge-ex-data datafy/datafy)))}) + :transform-fn (comp mark-presented (update-val (comp demunge-ex-data + datafy/datafy)))}) (def image-viewer {#?@(:clj [:pred #(instance? BufferedImage %) @@ -1105,7 +1110,7 @@ #?(:clj (defn edn-roundtrippable? [x] - (= x (-> x ->edn read-string)))) + (= x (-> x ->edn tools.reader/read-string)))) #?(:clj (defn throw-if-sync-var-is-invalid [var] @@ -1136,13 +1141,6 @@ (map (juxt #(list 'quote (symbol %)) #(->> % deref deref (list 'quote)))) (extract-sync-atom-vars doc))))) -(defn update-if [m k f] - (if (k m) - (update m k f) - m)) - -#_(update-if {:n "42"} :n #(Integer/parseInt %)) - (declare html doc-url) (defn home? [{:keys [nav-path]}] @@ -1513,15 +1511,13 @@ (defn get-elision [wrapped-value] (let [{:as fetch-opts :keys [n]} (->fetch-opts wrapped-value)] - (merge fetch-opts (bounded-count-opts n (->value wrapped-value))))) + (when (number? n) + (merge fetch-opts (bounded-count-opts n (->value wrapped-value)))))) #_(get-elision (present (range))) #_(get-elision (present "abc")) #_(get-elision (present (str/join (repeat 1000 "abc")))) -(defn get-fetch-opts-n [wrapped-value] - (-> wrapped-value ->fetch-opts :n)) - (defn present+paginate-children [{:as wrapped-value :nextjournal/keys [budget viewers preserve-keys?] :keys [!budget]}] (let [{:as fetch-opts :keys [offset n]} (->fetch-opts wrapped-value) xs (->value wrapped-value) @@ -1543,10 +1539,9 @@ (conj (let [fetch-opts (assoc elision :offset new-offset)] (make-elision viewers fetch-opts)))))) -(defn present+paginate-string [{:as wrapped-value :nextjournal/keys [viewers viewer value]}] - (let [{:as elision :keys [n total path offset]} (and (:page-size viewer) - (get-elision wrapped-value))] - (if (and n (< n total)) +(defn present+paginate-string [{:as wrapped-value :nextjournal/keys [viewers value]}] + (let [{:as elision :keys [n total path offset]} (get-elision wrapped-value)] + (if (and elision n (< n total)) (let [new-offset (min (+ (or offset 0) n) total)] (cond-> [(subs value (or offset 0) new-offset)] (pos? (- total new-offset)) (conj (let [fetch-opts (-> elision @@ -1861,12 +1856,10 @@ (-> wrapped-value mark-preserve-keys (assoc :nextjournal/viewer {:render-fn '(fn [{:keys [form val]} opts] - [:div.flex.flex-wrap - {:class "py-[7px]"} - [:div [:div.bg-slate-100.px-2.rounded - (nextjournal.clerk.render/inspect-presented opts form)]] - [:div.flex.mt-1 - [:div.mx-2.font-sans.text-xs.text-slate-500 {:class "mt-[2px]"} "⇒"] + [:div.mb-3.last:mb-0 + [:div.bg-slate-100.dark:bg-slate-800.px-4.py-2.border-l-2.border-slate-200.dark:border-slate-700 + (nextjournal.clerk.render/inspect-presented opts form)] + [:div.pt-2.px-4.border-l-2.border-transparent (nextjournal.clerk.render/inspect-presented opts val)]])}) (update-in [:nextjournal/value :form] code)))}) @@ -1874,6 +1867,7 @@ {:transform-fn (update-val (fn [examples] (mapv (partial with-viewer example-viewer) examples))) :render-fn '(fn [examples opts] - (into [:div.border-l-2.border-slate-300.pl-4 - [:div.uppercase.tracking-wider.text-xs.font-sans.text-slate-500.mt-4.mb-2 "Examples"]] - (nextjournal.clerk.render/inspect-children opts) examples))}) + [:div + [:div.uppercase.tracking-wider.text-xs.font-sans.font-bold.text-slate-500.dark:text-white.mb-2.mt-3 "Examples"] + (into [:div] + (nextjournal.clerk.render/inspect-children opts) examples)])}) diff --git a/test/nextjournal/clerk/eval_test.clj b/test/nextjournal/clerk/eval_test.clj index 8d157db21..a1c6b33ee 100644 --- a/test/nextjournal/clerk/eval_test.clj +++ b/test/nextjournal/clerk/eval_test.clj @@ -180,6 +180,11 @@ :nextjournal/hash string?}}] (eval+extract-doc-blocks "(range)")))) + (testing "Skipping pagination for strings" + (is (= "012345678910111213141516171819202122232425262728293031323334353637383940414243444546474849" + (-> (eval+extract-doc-blocks "^{:nextjournal.clerk/page-size nil} (apply str (range 50))") + second :nextjournal/value :nextjournal/presented :nextjournal/value)))) + (testing "assigns folded visibility" (is (match? [{:nextjournal/viewer {:name `viewer/folded-code-block-viewer} :nextjournal/value "{:some :map}"} @@ -240,4 +245,3 @@ (catch Exception _ nil)) (clerk/show! (java.io.StringReader. code)) (is (= result-first-run (get-result))))))) - diff --git a/test/nextjournal/clerk/parser_test.clj b/test/nextjournal/clerk/parser_test.clj index 95dcf3b6c..54a9f5fb9 100644 --- a/test/nextjournal/clerk/parser_test.clj +++ b/test/nextjournal/clerk/parser_test.clj @@ -3,7 +3,8 @@ [matcher-combinators.matchers :as m] [matcher-combinators.test :refer [match?]] [nextjournal.clerk.analyzer-test :refer [analyze-string]] - [nextjournal.clerk.parser :as parser])) + [nextjournal.clerk.parser :as parser] + [nextjournal.clerk.view :as view])) (defmacro with-ns-binding [ns-sym & body] `(binding [*ns* (find-ns ~ns-sym)] @@ -156,3 +157,13 @@ par two")))) (testing "unreadable forms" (is (= (parser/text-with-clerk-metadata-removed "^{:un :balanced :map} (do nothing)" clerk-ns-alias) "^{:un :balanced :map} (do nothing)")))) + +(deftest presenting-a-parsed-document + (testing "presenting a parsed document doesn't produce garbage" + (is (match? [{:nextjournal/viewer {:name 'nextjournal.clerk.viewer/code-block-viewer}} + {:nextjournal/viewer {:name 'nextjournal.clerk.viewer/code-block-viewer}} + {:nextjournal/viewer {:name 'nextjournal.clerk.viewer/code-block-viewer}}] + (-> (parser/parse-clojure-string {:doc? true} "(ns testing-presented-parsed) 123 :ahoi") + view/doc->viewer + :nextjournal/value + :blocks))))) diff --git a/test/nextjournal/clerk/viewer_test.clj b/test/nextjournal/clerk/viewer_test.clj index f13f079e1..064fdd8d0 100644 --- a/test/nextjournal/clerk/viewer_test.clj +++ b/test/nextjournal/clerk/viewer_test.clj @@ -386,6 +386,12 @@ (is (= "#viewer-eval (symbol \"~\")" (pr-str (symbol "~"))))) + (testing "symbols and keywords with two slashes readable by `read-string` but not `tools.reader/read-string` print as viewer-eval" + (is (= "#viewer-eval (symbol \"foo\" \"bar/baz\")" + (pr-str (read-string "foo/bar/baz")))) + (is (= "#viewer-eval (keyword \"foo\" \"bar/baz\")" + (pr-str (read-string ":foo/bar/baz"))))) + (testing "splicing reader conditional prints normally (issue #338)" (is (= "?@" (pr-str (symbol "?@"))))) diff --git a/yarn.lock b/yarn.lock index 2cdf27776..857439534 100644 --- a/yarn.lock +++ b/yarn.lock @@ -837,10 +837,10 @@ markdown-it-footnote@^3.0.3: resolved "https://registry.yarnpkg.com/markdown-it-footnote/-/markdown-it-footnote-3.0.3.tgz#e0e4c0d67390a4c5f0c75f73be605c7c190ca4d8" integrity sha512-YZMSuCGVZAjzKMn+xqIco9d1cLGxbELHZ9do/TSYVzraooV8ypsppKNmUJ0fVH5ljkCInQAtFpm8Rb3eXSrt5w== -markdown-it-texmath@^0.9.1: - version "0.9.7" - resolved "https://registry.yarnpkg.com/markdown-it-texmath/-/markdown-it-texmath-0.9.7.tgz#4d1256def5f49ac09c48e4a79d715a1bba5025c9" - integrity sha512-2oZ7WO+xQCvQpfCwxUsCzDpz5jRjiY+FbSJSVz+66+Z9NoPR7ljzUNaOp1CDHYj0JWx+drQLxO0XUjuSsuqc0A== +markdown-it-texmath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/markdown-it-texmath/-/markdown-it-texmath-1.0.0.tgz#65703b235d07a8f96bc58cbf9d478af57154e5f0" + integrity sha512-4hhkiX8/gus+6e53PLCUmUrsa6ZWGgJW2XCW6O0ASvZUiezIK900ZicinTDtG3kAO2kon7oUA/ReWmpW2FByxg== markdown-it-toc-done-right@^4.2.0: version "4.2.0"