From e1c8d5a2d63488e39005f20c22b5121003fa9782 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Thu, 30 Mar 2023 16:58:53 +0200 Subject: [PATCH 01/14] parent 4b959dff5c26d0edba14456f2061ec639af7f11b author Andrea Amantini 1680188333 +0200 committer Andrea Amantini 1693237293 +0200 Add internal-link tokenizer Make basic example functional Allow internal links to namespaces Make internal link scroll to var Put the notebook title as link content Make linter happy Refactor Fix hash fragment in links to var Omit reader quote-var dispatch in link text Kondo Include notebook presentation in doc-url rebinding scope Simplify Explain Coherent keys Handle fragments from vars Naming Reduce noise --- src/nextjournal/clerk/parser.cljc | 10 +++---- src/nextjournal/clerk/render.cljs | 2 +- src/nextjournal/clerk/viewer.cljc | 46 ++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/nextjournal/clerk/parser.cljc b/src/nextjournal/clerk/parser.cljc index 3607cc4ab..07a31276b 100644 --- a/src/nextjournal/clerk/parser.cljc +++ b/src/nextjournal/clerk/parser.cljc @@ -287,15 +287,17 @@ (defn markdown-context [] (update markdown.parser/empty-doc - :text-tokenizers (partial map markdown.parser/normalize-tokenizer))) - -#_(markdown-context) + :text-tokenizers + (comp (partial mapv markdown.parser/normalize-tokenizer) + (partial cons markdown.parser/internal-link-tokenizer)))) (defn parse-markdown "Like `n.markdown.parser/parse` but allows to reuse the same context in successive calls" [ctx md] (markdown.parser/apply-tokens ctx (markdown/tokenize md))) +#_(parse-markdown-string {:doc? true} "# Title\nSome [[internal-link]] to be followed.") + (defn update-markdown-blocks [{:as state :keys [md-context]} md] (let [{::markdown.parser/keys [path]} md-context doc (parse-markdown md-context md) @@ -348,9 +350,7 @@ state)))) #_(parse-clojure-string {:doc? true} "'code ;; foo\n;; bar") -#_(parse-clojure-string "'code , ;; foo\n;; bar") #_(parse-clojure-string "'code\n;; foo\n;; bar") -#_(keys (parse-clojure-string {:doc? true} (slurp "notebooks/viewer_api.clj"))) #_(parse-clojure-string {:doc? true} ";; # Hello\n;; ## 👋 Section\n(do 123)\n;; ## 🤚🏽 Section") (defn parse-markdown-cell [{:as state :keys [nodes]} opts] diff --git a/src/nextjournal/clerk/render.cljs b/src/nextjournal/clerk/render.cljs index 4c65bd53c..20f711098 100644 --- a/src/nextjournal/clerk/render.cljs +++ b/src/nextjournal/clerk/render.cljs @@ -865,7 +865,7 @@ [:path {:fill-rule "evenodd" :d "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" :clip-rule "evenodd"}]]) (defn render-code-block [code-string {:as opts :keys [id]}] - [:div.viewer.code-viewer.w-full.max-w-wide {:data-block-id id} + [:div.viewer.code-viewer.w-full.max-w-wide {:id id} [code/render-code code-string (assoc opts :language "clojure")]]) (defn render-folded-code-block [code-string {:as opts :keys [id]}] diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 9b4f48bb3..a0487b11e 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -31,6 +31,8 @@ (java.nio.file Files StandardOpenOption) (javax.imageio ImageIO)))) +(declare doc-url) + (defrecord ViewerEval [form]) (defrecord ViewerFn [form #?(:cljs f)] @@ -714,6 +716,43 @@ (doto (java.text.SimpleDateFormat. "yyyy-MM-dd'T'HH:mm:ss.SSS-00:00") (.setTimeZone (java.util.TimeZone/getTimeZone "GMT"))))) +#?(:clj (defn resolve-internal-link [link] + (if (fs/exists? link) + {:path link} + (let [sym (symbol link)] + (if (qualified-symbol? sym) + (when-some [var (try (requiring-resolve sym) + (catch Exception _ nil))] + (merge {:var var} (resolve-internal-link (-> var symbol namespace)))) + (when-some [ns (try (require sym) + (find-ns sym) + (catch Exception _ nil))] + (cond-> {:ns ns} + (fs/exists? (analyzer/ns->file sym)) + (assoc :path (analyzer/ns->file sym))))))))) + +#_(resolve-internal-link "notebooks/hello.clj") +#_(resolve-internal-link "nextjournal.clerk.tap") +#_(resolve-internal-link "rule-30/board") + +(defn process-internal-link [link] + #?(:clj + (let [{:keys [path var]} (resolve-internal-link link)] + {:path path + :fragment (when var (str (-> var symbol str) "-code")) + :title (or (when var (-> var symbol str)) + (when path (:title (parser/parse-file {:doc? true} path))) + link)}) + :cljs + {:path link :title link})) + +#_(process-internal-link "notebooks/rule_30.clj") +#_(process-internal-link "viewers.html") +#_(process-internal-link "how-clerk-works/hashes") +#_(process-internal-link "rule-30/first-generation") + +(declare html) + (def markdown-viewers [{:name :nextjournal.markdown/doc :transform-fn (into-markup (fn [{:keys [id]}] [:div.viewer.markdown-viewer.w-full.max-w-prose.px-8 {:data-block-id id}]))} @@ -742,6 +781,11 @@ {:name :nextjournal.markdown/monospace :transform-fn (into-markup [:code])} {:name :nextjournal.markdown/strikethrough :transform-fn (into-markup [:s])} {:name :nextjournal.markdown/link :transform-fn (into-markup #(vector :a (:attrs %)))} + {:name :nextjournal.markdown/internal-link + :transform-fn (update-val + (fn [{:keys [text]}] + (let [{:keys [path fragment title]} (process-internal-link text)] + (html [:a.internal-link {:href (doc-url path fragment)} title]))))} ;; inlines {:name :nextjournal.markdown/text :transform-fn (into-markup [:<>])} @@ -1129,7 +1173,7 @@ #_(update-if {:n "42"} :n #(Integer/parseInt %)) -(declare html doc-url) +(declare html) (defn home? [{:keys [nav-path]}] (contains? #{"src/nextjournal/home.clj" "'nextjournal.clerk.home"} nav-path)) From 4315c5b5b11c3ecacea58a0d512b6a23212b8110 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 29 Aug 2023 12:34:23 +0200 Subject: [PATCH 02/14] Parse wikilinks in calls to clerk/md as well --- src/nextjournal/clerk/parser.cljc | 5 +++-- src/nextjournal/clerk/viewer.cljc | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/nextjournal/clerk/parser.cljc b/src/nextjournal/clerk/parser.cljc index 07a31276b..f842ce43a 100644 --- a/src/nextjournal/clerk/parser.cljc +++ b/src/nextjournal/clerk/parser.cljc @@ -293,8 +293,9 @@ (defn parse-markdown "Like `n.markdown.parser/parse` but allows to reuse the same context in successive calls" - [ctx md] - (markdown.parser/apply-tokens ctx (markdown/tokenize md))) + ([md] (parse-markdown (markdown-context) md)) + ([ctx md] + (markdown.parser/apply-tokens ctx (markdown/tokenize md)))) #_(parse-markdown-string {:doc? true} "# Title\nSome [[internal-link]] to be followed.") diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index a0487b11e..45231ec32 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -986,7 +986,7 @@ :transform-fn (fn [wrapped-value] (-> wrapped-value mark-presented - (update :nextjournal/value #(cond->> % (string? %) md/parse)) + (update :nextjournal/value #(cond->> % (string? %) parser/parse-markdown)) (with-md-viewer)))}) (def code-viewer From af9875b9f5d47818bd1bca9b14f646d843054627 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 29 Aug 2023 12:45:42 +0200 Subject: [PATCH 03/14] Allow to use unqualified symbols to link to current ns --- src/nextjournal/clerk/viewer.cljc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 45231ec32..6f7adfd70 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -724,12 +724,13 @@ (when-some [var (try (requiring-resolve sym) (catch Exception _ nil))] (merge {:var var} (resolve-internal-link (-> var symbol namespace)))) - (when-some [ns (try (require sym) - (find-ns sym) - (catch Exception _ nil))] + (if-some [ns (try (require sym) + (find-ns sym) + (catch Exception _ nil))] (cond-> {:ns ns} (fs/exists? (analyzer/ns->file sym)) - (assoc :path (analyzer/ns->file sym))))))))) + (assoc :path (analyzer/ns->file sym))) + (resolve-internal-link (str (ns-name *ns*) "/" link)))))))) #_(resolve-internal-link "notebooks/hello.clj") #_(resolve-internal-link "nextjournal.clerk.tap") From 98eb3f42d791f3acaf3539306cf3693d7e1a98ac Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 29 Aug 2023 13:11:00 +0200 Subject: [PATCH 04/14] First take at wikilink interations --- src/nextjournal/clerk/doc.clj | 28 ++++++++++++++++++++++++++++ src/nextjournal/clerk/viewer.cljc | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/nextjournal/clerk/doc.clj b/src/nextjournal/clerk/doc.clj index f18f61ccf..a60761be5 100644 --- a/src/nextjournal/clerk/doc.clj +++ b/src/nextjournal/clerk/doc.clj @@ -195,3 +195,31 @@ #_(deref nextjournal.clerk.webserver/!doc) +(defn resolve-internal-link [link] + (viewer/resolve-internal-link (cond->> link + (and (not (qualified-symbol? (symbol link))) @!active-ns) + (str @!active-ns "/")))) + +(def custom-markdown-viewers + [{:name :nextjournal.markdown/internal-link + :transform-fn (comp clerk/mark-presented + (fn [wv] + (when-some [info (-> wv :nextjournal/value :text resolve-internal-link)] + (-> info + (viewer/update-if :var symbol) + (viewer/update-if :ns ns-name))))) + :render-fn '(fn [{:keys [var ns]} _] + [:a {:href (str "#" var) + :on-click (fn [e] (.stopPropagation e) (.preventDefault e) + (when (and var ns) + (let [scroll-to-target #(when-some [el (js/document.getElementById (name var))] + (.scrollIntoView el))] + (if (not= @!active-ns (str ns)) + (do (reset! !active-ns (str ns)) + (js/setTimeout scroll-to-target 500)) ;; TODO: smarter + (scroll-to-target)))))} (str var)])}]) + +(def custom-internal-links + (update viewer/markdown-viewer :add-viewers viewer/add-viewers custom-markdown-viewers)) + +(viewer/add-viewers! [custom-internal-links]) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 6f7adfd70..6f964a676 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -1333,7 +1333,7 @@ hide-result-viewer]) (defonce - ^{:doc "atom containing a map of and per-namespace viewers or `:defaults` overridden viewers."} + ^{:doc "atom containing a map of and per-namespace viewers or `:defaults` overridden viewers. See also [[get-default-viewers]]."} !viewers (#?(:clj atom :cljs ratom/atom) {})) From d43394778b8e86d6ed7b91cd1bf1913a2ec5180c Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 29 Aug 2023 14:53:17 +0200 Subject: [PATCH 05/14] Pass at docs --- src/nextjournal/clerk.clj | 9 ++++++--- src/nextjournal/clerk/viewer.cljc | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/nextjournal/clerk.clj b/src/nextjournal/clerk.clj index 7c506d9c4..5f1b40deb 100644 --- a/src/nextjournal/clerk.clj +++ b/src/nextjournal/clerk.clj @@ -222,13 +222,16 @@ ([viewer-opts x] (v/html viewer-opts x))) (defn md - "Displays `x` with the markdown viewer. + "Displays `x` with the markdown viewer. Accepts strings or a structure as returned by [[nextjournal.markdown/parse]]. Supports an optional first `viewer-opts` map arg with the following optional keys: * `:nextjournal.clerk/width`: set the width to `:full`, `:wide`, `:prose` * `:nextjournal.clerk/viewers`: a seq of viewers to use for presentation of this value and its children - * `:nextjournal.clerk/render-opts`: a map argument that will be passed as a secong arg to the viewers `:render-fn`" + * `:nextjournal.clerk/render-opts`: a map argument that will be passed as a secong arg to the viewers `:render-fn`. + + See also [[nextjournal.clerk.viewer/markdown-viewer]]." + ([x] (v/md x)) ([viewer-opts x] (v/md viewer-opts x))) @@ -553,7 +556,7 @@ #_(with-cache (do (Thread/sleep 4200) 42)) (defmacro defcached - "Like `clojure.core/def` but with Clerk's caching of the value." + "Like `clojure.core/def` but with Clerk's caching of the value. See also [[with-cache]]." [name expr] `(let [result# (-> ~(v/->edn expr) eval/eval-string :blob->result first val :nextjournal/value)] (def ~name result#))) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 6f964a676..8e68f5c24 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -472,7 +472,9 @@ %) presented-result))) -(defn get-default-viewers [] +(defn get-default-viewers + "Returns viewers from the global scope when set, defaults to [[default-viewers]] (see also [[!viewers]])." + [] (:default @!viewers default-viewers)) (defn datafy-scope [scope] @@ -982,6 +984,7 @@ {:name `vega-lite-viewer :render-fn 'nextjournal.clerk.render/render-vega-lite :transform-fn mark-presented}) (def markdown-viewer + "A clerk viewer for rendering markdown. See also [[nextjournal.clerk/md]]." {:name `markdown-viewer :add-viewers markdown-viewers :transform-fn (fn [wrapped-value] From 64f266e2e84899cff9e42e3f5017465fc3126adb Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 29 Aug 2023 16:54:23 +0200 Subject: [PATCH 06/14] Make links to namespace work --- src/nextjournal/clerk.clj | 7 ++++++- src/nextjournal/clerk/doc.clj | 26 +++++++++++++++++--------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/nextjournal/clerk.clj b/src/nextjournal/clerk.clj index 5f1b40deb..42bac38b8 100644 --- a/src/nextjournal/clerk.clj +++ b/src/nextjournal/clerk.clj @@ -1,5 +1,10 @@ (ns nextjournal.clerk - "Clerk's Public API." + "Clerk's Public API. + + Further API: + * [[nextjournal.clerk.parser]] + * [[nextjournal.clerk.viewer]] + * [[nextjournal.clerk.analyzer]]" (:require [babashka.fs :as fs] [clojure.java.browse :as browse] [clojure.java.io :as io] diff --git a/src/nextjournal/clerk/doc.clj b/src/nextjournal/clerk/doc.clj index a60761be5..5cb4b7fb1 100644 --- a/src/nextjournal/clerk/doc.clj +++ b/src/nextjournal/clerk/doc.clj @@ -161,7 +161,7 @@ (into [:div.text-sm.font-sans.px-5.mt-2] (map render-ns) (ns-tree (str-match-nss @!active-ns)))]])]] - [:div.flex-auto.max-h-screen.overflow-y-auto.px-8.py-5 + [:div#main-column.flex-auto.max-h-screen.overflow-y-auto.px-8.py-5 (let [ns (some-> @!active-ns symbol find-ns)] (cond ns [:<> @@ -197,7 +197,9 @@ (defn resolve-internal-link [link] (viewer/resolve-internal-link (cond->> link - (and (not (qualified-symbol? (symbol link))) @!active-ns) + (and @!active-ns (not= :all @!active-ns) + (not (find-ns (symbol link))) + (not (qualified-symbol? (symbol link)))) (str @!active-ns "/")))) (def custom-markdown-viewers @@ -211,13 +213,19 @@ :render-fn '(fn [{:keys [var ns]} _] [:a {:href (str "#" var) :on-click (fn [e] (.stopPropagation e) (.preventDefault e) - (when (and var ns) - (let [scroll-to-target #(when-some [el (js/document.getElementById (name var))] - (.scrollIntoView el))] - (if (not= @!active-ns (str ns)) - (do (reset! !active-ns (str ns)) - (js/setTimeout scroll-to-target 500)) ;; TODO: smarter - (scroll-to-target)))))} (str var)])}]) + (when (resolve '!active-ns) + (let [scroll-to-target (fn [] + (if var + (when-some [el (js/document.getElementById (name var))] + (.scrollIntoView el)) + (when ns + (when-some [page (js/document.getElementById "main-column")] + (.scroll page (applied-science.js-interop/obj :top 0))))))] + (when ns + (if (not= @!active-ns (str ns)) + (do (reset! !active-ns (str ns)) + (js/setTimeout scroll-to-target 500)) ;; TODO: smarter + (scroll-to-target))))))} (str (or var ns))])}]) (def custom-internal-links (update viewer/markdown-viewer :add-viewers viewer/add-viewers custom-markdown-viewers)) From 634e534703082c768d9bd930f7dcf1aabe1e3b6f Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 29 Aug 2023 16:57:13 +0200 Subject: [PATCH 07/14] Happy kondo --- src/nextjournal/clerk/viewer.cljc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 8e68f5c24..4b18a4dc9 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -19,7 +19,6 @@ [sci.lang] [applied-science.js-interop :as j]]) [nextjournal.clerk.parser :as parser] - [nextjournal.markdown :as md] [nextjournal.markdown.parser :as md.parser] [nextjournal.markdown.transform :as md.transform]) #?(:clj (:import (com.pngencoder PngEncoder) From 317b3c090579052b1d9e36ae6c8950c334e51706 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 29 Aug 2023 17:26:20 +0200 Subject: [PATCH 08/14] Override behaviour for regular links --- src/nextjournal/clerk.clj | 6 +-- src/nextjournal/clerk/doc.clj | 73 +++++++++++++++++++++---------- src/nextjournal/clerk/viewer.cljc | 2 +- 3 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/nextjournal/clerk.clj b/src/nextjournal/clerk.clj index 42bac38b8..9e78de0c7 100644 --- a/src/nextjournal/clerk.clj +++ b/src/nextjournal/clerk.clj @@ -2,9 +2,9 @@ "Clerk's Public API. Further API: - * [[nextjournal.clerk.parser]] - * [[nextjournal.clerk.viewer]] - * [[nextjournal.clerk.analyzer]]" + * [Parsing](nextjournal.clerk.parser) + * [Viewers API](nextjournal.clerk.viewer) + * [Static analysis and caching](nextjournal.clerk.analyzer)" (:require [babashka.fs :as fs] [clojure.java.browse :as browse] [clojure.java.io :as io] diff --git a/src/nextjournal/clerk/doc.clj b/src/nextjournal/clerk/doc.clj index 5cb4b7fb1..e4bbc1c44 100644 --- a/src/nextjournal/clerk/doc.clj +++ b/src/nextjournal/clerk/doc.clj @@ -3,7 +3,8 @@ {:nextjournal.clerk/visibility {:code :hide :result :hide}} (:require [clojure.string :as str] [nextjournal.clerk :as clerk] - [nextjournal.clerk.viewer :as viewer])) + [nextjournal.clerk.viewer :as viewer] + [nextjournal.markdown.transform :as md.transform])) (def render-input '(fn [!query] @@ -202,32 +203,58 @@ (not (qualified-symbol? (symbol link)))) (str @!active-ns "/")))) +(defn spy [x] (println :x (str "'" x "'") + :t (type x) + :ns (ns-name *ns*)) x) + +^::clerk/no-cache +(clerk/eval-cljs + '(defn handle-click [{:keys [label var ns]} e] + (js/console.log :handle-click/ns ns :var var) + (.stopPropagation e) (.preventDefault e) + (when (resolve '!active-ns) + (let [scroll-to-target (fn [] + (if var + (when-some [el (js/document.getElementById (name var))] + (.scrollIntoView el)) + (when ns + (when-some [page (js/document.getElementById "main-column")] + (.scroll page (applied-science.js-interop/obj :top 0))))))] + (when ns + (if (not= @!active-ns (str ns)) + (do (reset! !active-ns (str ns)) + (js/setTimeout scroll-to-target 500)) ;; TODO: smarter + (scroll-to-target))))))) + +^::clerk/no-cache +(clerk/eval-cljs + '(defn render-link [{:as info :keys [label]} _] + [:a {:href "#" :on-click (partial handle-click info)} label])) + +(def get-info + (comp clerk/mark-presented + (fn [wv] + (let [{:as node :keys [type text attrs]} (-> wv :nextjournal/value)] + (when-some [{:as info :keys [ns var]} + (some-> (resolve-internal-link (case type :internal-link text :link (:href attrs))) + (viewer/update-if :ns ns-name) + (viewer/update-if :var symbol))] + (assoc info :label + (str (case type + :internal-link (or var ns) + :link (md.transform/->text node))))))))) + (def custom-markdown-viewers [{:name :nextjournal.markdown/internal-link - :transform-fn (comp clerk/mark-presented - (fn [wv] - (when-some [info (-> wv :nextjournal/value :text resolve-internal-link)] - (-> info - (viewer/update-if :var symbol) - (viewer/update-if :ns ns-name))))) - :render-fn '(fn [{:keys [var ns]} _] - [:a {:href (str "#" var) - :on-click (fn [e] (.stopPropagation e) (.preventDefault e) - (when (resolve '!active-ns) - (let [scroll-to-target (fn [] - (if var - (when-some [el (js/document.getElementById (name var))] - (.scrollIntoView el)) - (when ns - (when-some [page (js/document.getElementById "main-column")] - (.scroll page (applied-science.js-interop/obj :top 0))))))] - (when ns - (if (not= @!active-ns (str ns)) - (do (reset! !active-ns (str ns)) - (js/setTimeout scroll-to-target 500)) ;; TODO: smarter - (scroll-to-target))))))} (str (or var ns))])}]) + :render-fn 'render-link + :transform-fn get-info} + {:name :nextjournal.markdown/link + :render-fn 'render-link + :transform-fn get-info}]) (def custom-internal-links (update viewer/markdown-viewer :add-viewers viewer/add-viewers custom-markdown-viewers)) (viewer/add-viewers! [custom-internal-links]) + +#_(clerk/clear-cache!) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 4b18a4dc9..78539badb 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -1335,7 +1335,7 @@ hide-result-viewer]) (defonce - ^{:doc "atom containing a map of and per-namespace viewers or `:defaults` overridden viewers. See also [[get-default-viewers]]."} + ^{:doc "An atom containing a map of per-namespace viewers or `:default` overridden viewers. See also how to [get default viewers](get-default-viewers)."} !viewers (#?(:clj atom :cljs ratom/atom) {})) From a3db3ee6b28bde4e8488dd6ac32529ada57551e8 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 29 Aug 2023 17:32:30 +0200 Subject: [PATCH 09/14] Better names --- src/nextjournal/clerk/doc.clj | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/nextjournal/clerk/doc.clj b/src/nextjournal/clerk/doc.clj index e4bbc1c44..2fdc7542a 100644 --- a/src/nextjournal/clerk/doc.clj +++ b/src/nextjournal/clerk/doc.clj @@ -203,10 +203,6 @@ (not (qualified-symbol? (symbol link)))) (str @!active-ns "/")))) -(defn spy [x] (println :x (str "'" x "'") - :t (type x) - :ns (ns-name *ns*)) x) - ^::clerk/no-cache (clerk/eval-cljs '(defn handle-click [{:keys [label var ns]} e] @@ -252,9 +248,9 @@ :render-fn 'render-link :transform-fn get-info}]) -(def custom-internal-links +(def markdown-viewer (update viewer/markdown-viewer :add-viewers viewer/add-viewers custom-markdown-viewers)) -(viewer/add-viewers! [custom-internal-links]) +(viewer/add-viewers! [markdown-viewer]) #_(clerk/clear-cache!) From 589e007c2ba274bebf3396f379b7452b4b30585e Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 30 Aug 2023 10:20:48 +0200 Subject: [PATCH 10/14] Fix render of links on reload (correct eval order) --- src/nextjournal/clerk/doc.clj | 51 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/nextjournal/clerk/doc.clj b/src/nextjournal/clerk/doc.clj index 2fdc7542a..023611b5a 100644 --- a/src/nextjournal/clerk/doc.clj +++ b/src/nextjournal/clerk/doc.clj @@ -6,6 +6,29 @@ [nextjournal.clerk.viewer :as viewer] [nextjournal.markdown.transform :as md.transform])) +(clerk/eval-cljs + '(defn handle-click [{:keys [label var ns]} e] + (.stopPropagation e) + (.preventDefault e) + (when (resolve '!active-ns) + (let [scroll-to-target (fn [] + (if var + (when-some [el (js/document.getElementById (name var))] + (.scrollIntoView el)) + (when ns + (when-some [page (js/document.getElementById "main-column")] + (.scroll page (applied-science.js-interop/obj :top 0))))))] + (when ns + (if (not= @!active-ns (str ns)) + (do (reset! !active-ns (str ns)) + ;; TODO: smarter + (js/setTimeout scroll-to-target 500)) + (scroll-to-target))))))) + +(clerk/eval-cljs + '(defn render-link [{:as info :keys [label]} _] + [:a {:href "#" :on-click (partial handle-click info)} label])) + (def render-input '(fn [!query] (nextjournal.clerk.render.hooks/use-effect @@ -203,30 +226,6 @@ (not (qualified-symbol? (symbol link)))) (str @!active-ns "/")))) -^::clerk/no-cache -(clerk/eval-cljs - '(defn handle-click [{:keys [label var ns]} e] - (js/console.log :handle-click/ns ns :var var) - (.stopPropagation e) (.preventDefault e) - (when (resolve '!active-ns) - (let [scroll-to-target (fn [] - (if var - (when-some [el (js/document.getElementById (name var))] - (.scrollIntoView el)) - (when ns - (when-some [page (js/document.getElementById "main-column")] - (.scroll page (applied-science.js-interop/obj :top 0))))))] - (when ns - (if (not= @!active-ns (str ns)) - (do (reset! !active-ns (str ns)) - (js/setTimeout scroll-to-target 500)) ;; TODO: smarter - (scroll-to-target))))))) - -^::clerk/no-cache -(clerk/eval-cljs - '(defn render-link [{:as info :keys [label]} _] - [:a {:href "#" :on-click (partial handle-click info)} label])) - (def get-info (comp clerk/mark-presented (fn [wv] @@ -242,10 +241,10 @@ (def custom-markdown-viewers [{:name :nextjournal.markdown/internal-link - :render-fn 'render-link + :render-fn 'nextjournal.clerk.doc/render-link :transform-fn get-info} {:name :nextjournal.markdown/link - :render-fn 'render-link + :render-fn 'nextjournal.clerk.doc/render-link :transform-fn get-info}]) (def markdown-viewer From ff7ebc2f065a5bd396371f986787e224ff8a9711 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 30 Aug 2023 11:46:14 +0200 Subject: [PATCH 11/14] Process href of regular links to resolve vars or nss --- src/nextjournal/clerk/viewer.cljc | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 78539badb..d0ed39a19 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -748,11 +748,24 @@ :cljs {:path link :title link})) +(defn process-href [^String href] + #?(:cljs href + :clj (if (.getScheme (URI. href)) + href + (let [{:keys [path fragment]} (process-internal-link href)] + (doc-url path fragment))))) + +#_(process-href "foo.bar.dang") +#_(process-internal-link "foo.bar.dang") + #_(process-internal-link "notebooks/rule_30.clj") #_(process-internal-link "viewers.html") #_(process-internal-link "how-clerk-works/hashes") #_(process-internal-link "rule-30/first-generation") +(defn update-if [m k f] (if (k m) (update m k f) m)) +#_(update-if {:n "42"} :n #(Integer/parseInt %)) + (declare html) (def markdown-viewers @@ -782,7 +795,7 @@ {:name :nextjournal.markdown/strong :transform-fn (into-markup [:strong])} {:name :nextjournal.markdown/monospace :transform-fn (into-markup [:code])} {:name :nextjournal.markdown/strikethrough :transform-fn (into-markup [:s])} - {:name :nextjournal.markdown/link :transform-fn (into-markup #(vector :a (:attrs %)))} + {:name :nextjournal.markdown/link :transform-fn (into-markup #(vector :a (update-if (:attrs %) :href process-href)))} {:name :nextjournal.markdown/internal-link :transform-fn (update-val (fn [{:keys [text]}] @@ -1169,13 +1182,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) (defn home? [{:keys [nav-path]}] From 145205318e7cf83dae3fe7b23f5ebd56f77685cd Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 30 Aug 2023 11:53:57 +0200 Subject: [PATCH 12/14] Remove dead code --- src/nextjournal/clerk/doc.clj | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/nextjournal/clerk/doc.clj b/src/nextjournal/clerk/doc.clj index 023611b5a..5122aeeaf 100644 --- a/src/nextjournal/clerk/doc.clj +++ b/src/nextjournal/clerk/doc.clj @@ -124,17 +124,6 @@ #_(ns-tree ns-matches) #_(ns-tree ()) -(defn parent-ns [ns-str] - (when (str/includes? ns-str ".") - (str/join "." (butlast (str/split ns-str #"\."))))) - -(defn prepend-parent [nss] - (when-let [parent (parent-ns (first nss))] - (cons parent nss))) - -(defn path-to-ns [ns-str] - (last (take-while some? (iterate prepend-parent [ns-str])))) - ^{::clerk/visibility {:result :show}} (clerk/html (let [matches (try From 8e013d1e904c5717902dc794aa94a2ca07cbe624 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 30 Aug 2023 12:06:12 +0200 Subject: [PATCH 13/14] Demo in static build --- notebooks/document_linking.clj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/notebooks/document_linking.clj b/notebooks/document_linking.clj index 47c68cca1..875b09238 100644 --- a/notebooks/document_linking.clj +++ b/notebooks/document_linking.clj @@ -23,3 +23,10 @@ [:li [:a {:href (nextjournal.clerk.viewer/doc-url "notebooks/viewers/html")} "HTML"]] [:li [:a {:href (nextjournal.clerk.viewer/doc-url "notebooks/markdown")} "Markdown"]] [:li [:a {:href (nextjournal.clerk.viewer/doc-url "notebooks/viewer_api")} "Viewer API / Tables"]]]) nil) + +;; ## Links in prose +;; * Link to a namespace `[[rule-30]]` renders as [[rule-30]] +;; * Link to a var `[[how-clerk-works/hashes]]` renders as [[how-clerk-works/hashes]] +;; * Link to a path `[[notebooks/viewers/image.clj]]` renders as [[notebooks/viewers/image.clj]] +;; +;; The href of regular links is processed in a similar fashion: `[Clerk Analyzer](how-clerk-works/hashes)` renders as [Clerk Analyzer](how-clerk-works/hashes). From e2c9512f4a3453a94118b01866df1002fb999d74 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 30 Aug 2023 12:45:00 +0200 Subject: [PATCH 14/14] Fix static build (allow links to #fragments) --- src/nextjournal/clerk/viewer.cljc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index d0ed39a19..5e9874d91 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -725,13 +725,12 @@ (when-some [var (try (requiring-resolve sym) (catch Exception _ nil))] (merge {:var var} (resolve-internal-link (-> var symbol namespace)))) - (if-some [ns (try (require sym) - (find-ns sym) - (catch Exception _ nil))] + (when-some [ns (try (require sym) + (find-ns sym) + (catch Exception _ nil))] (cond-> {:ns ns} (fs/exists? (analyzer/ns->file sym)) - (assoc :path (analyzer/ns->file sym))) - (resolve-internal-link (str (ns-name *ns*) "/" link)))))))) + (assoc :path (analyzer/ns->file sym))))))))) #_(resolve-internal-link "notebooks/hello.clj") #_(resolve-internal-link "nextjournal.clerk.tap") @@ -750,13 +749,14 @@ (defn process-href [^String href] #?(:cljs href - :clj (if (.getScheme (URI. href)) + :clj (if (or (.getScheme (URI. href)) (str/starts-with? href "/")) href (let [{:keys [path fragment]} (process-internal-link href)] - (doc-url path fragment))))) + (if (or path fragment) (doc-url path fragment) href))))) -#_(process-href "foo.bar.dang") -#_(process-internal-link "foo.bar.dang") +#_(process-href "rule-30") +#_(process-href "#some-id") +#_(process-internal-link "#some-id") #_(process-internal-link "notebooks/rule_30.clj") #_(process-internal-link "viewers.html")