|
23 | 23 | (java.lang Throwable)
|
24 | 24 | (java.awt.image BufferedImage)
|
25 | 25 | (java.util Base64)
|
26 |
| - (java.net URL) |
| 26 | + (java.net URI URL) |
27 | 27 | (java.nio.file Files StandardOpenOption)
|
28 | 28 | (javax.imageio ImageIO))))
|
29 | 29 |
|
|
316 | 316 | (-> (with-viewer {:name `html-viewer- :render-fn 'identity} wrapped-value)
|
317 | 317 | mark-presented
|
318 | 318 | (update :nextjournal/value
|
319 |
| - (fn [{:as node :keys [text content]}] |
| 319 | + (fn [{:as node :keys [text content] ::keys [doc]}] |
320 | 320 | (into (cond-> markup (fn? markup) (apply [node]))
|
321 | 321 | (cond text [text]
|
322 |
| - content (mapv #(-> (with-md-viewer %) |
323 |
| - (assoc :nextjournal/viewers viewers) |
| 322 | + content (mapv #(-> (ensure-wrapped-with-viewers viewers (assoc % ::doc doc)) |
| 323 | + (with-md-viewer) |
324 | 324 | (apply-viewers)
|
325 | 325 | (as-> w
|
326 | 326 | (if (= `html-viewer- (:name (->viewer w)))
|
|
397 | 397 | result))
|
398 | 398 |
|
399 | 399 | #?(:clj
|
400 |
| - (defn base64-encode-value [{:as result :nextjournal/keys [content-type]}] |
401 |
| - (update result :nextjournal/value (fn [data] (str "data:" content-type ";base64," |
402 |
| - (.encodeToString (Base64/getEncoder) data)))))) |
| 400 | + (defn data-uri-base64-encode [x content-type] |
| 401 | + (str "data:" content-type ";base64," (.encodeToString (Base64/getEncoder) x)))) |
403 | 402 |
|
404 | 403 | #?(:clj
|
405 | 404 | (defn store+get-cas-url! [{:keys [out-path ext]} content]
|
406 |
| - (assert out-path) (assert ext) |
407 |
| - (let [cas-url (str "_data/" (analyzer/sha2-base58 content) "." ext) |
| 405 | + (assert out-path) |
| 406 | + (let [cas-url (str "_data/" (analyzer/sha2-base58 content) (when ext ".") ext) |
408 | 407 | cas-path (fs/path out-path cas-url)]
|
409 | 408 | (fs/create-dirs (fs/parent cas-path))
|
410 | 409 | (when-not (fs/exists? cas-path)
|
|
433 | 432 |
|
434 | 433 | #?(:clj
|
435 | 434 | (defn process-blobs [{:as doc+blob-opts :keys [blob-mode blob-id]} presented-result]
|
436 |
| - (w/postwalk #(if (get % :nextjournal/content-type) |
| 435 | + (w/postwalk #(if-some [content-type (get % :nextjournal/content-type)] |
437 | 436 | (case blob-mode
|
438 | 437 | :lazy-load (assoc % :nextjournal/value {:blob-id blob-id :path (:path %)})
|
439 |
| - :inline (base64-encode-value %) |
| 438 | + :inline (update % :nextjournal/value data-uri-base64-encode content-type) |
440 | 439 | :file (maybe-store-result-as-file doc+blob-opts %))
|
441 | 440 | %)
|
442 | 441 | presented-result)))
|
|
463 | 462 |
|
464 | 463 | (declare result-viewer)
|
465 | 464 |
|
466 |
| -(defn transform-result [{:as _cell :keys [doc result form]}] |
| 465 | +(defn transform-result [{:as _cell :keys [result form] ::keys [doc]}] |
467 | 466 | (let [{:keys [auto-expand-results? inline-results? bundle?]} doc
|
468 | 467 | {:nextjournal/keys [value blob-id viewers]} result
|
469 | 468 | blob-mode (cond
|
|
507 | 506 | #_(->display {:result {:nextjournal.clerk/visibility {:code :fold :result :show}}})
|
508 | 507 | #_(->display {:result {:nextjournal.clerk/visibility {:code :fold :result :hide}}})
|
509 | 508 |
|
510 |
| -(defn process-sidenotes [{:as doc :keys [footnotes]} cell-doc] |
| 509 | +(defn process-sidenotes [cell-doc {:keys [footnotes]}] |
511 | 510 | (if (seq footnotes)
|
512 | 511 | (md.parser/insert-sidenote-containers (assoc cell-doc :footnotes footnotes))
|
513 | 512 | cell-doc))
|
514 | 513 |
|
| 514 | +(defn process-image-source [src {:as doc :keys [file bundle?]}] |
| 515 | + #?(:cljs src |
| 516 | + :clj (cond |
| 517 | + (not (fs/exists? src)) src |
| 518 | + (false? bundle?) (str (relative-root-prefix-from (map-index doc file)) |
| 519 | + (store+get-cas-url! (assoc doc :ext (fs/extension src)) |
| 520 | + (fs/read-all-bytes src))) |
| 521 | + bundle? (data-uri-base64-encode (fs/read-all-bytes src) (Files/probeContentType (fs/path src))) |
| 522 | + :else (str "_fs/" src)))) |
| 523 | + |
| 524 | +#?(:clj |
| 525 | + (defn read-image [image-or-url] |
| 526 | + (ImageIO/read |
| 527 | + (if (string? image-or-url) |
| 528 | + (URL. (cond->> image-or-url (not (.getScheme (URI. image-or-url))) (str "file:"))) |
| 529 | + image-or-url)))) |
| 530 | + |
| 531 | +#?(:clj |
| 532 | + (defn image-width [image] |
| 533 | + (let [w (.getWidth image) h (.getHeight image) r (float (/ w h))] |
| 534 | + (if (and (< 2 r) (< 900 w)) :full :wide)))) |
| 535 | + |
| 536 | +(defn md-image->viewer [doc {:keys [attrs]}] |
| 537 | + (with-viewer `html-viewer |
| 538 | + #?(:clj {:nextjournal.clerk/width (try (image-width (read-image (:src attrs))) |
| 539 | + (catch Throwable _ :prose))}) |
| 540 | + [:div.flex.flex-col.items-center.not-prose.mb-4 |
| 541 | + [:img (update attrs :src process-image-source doc)]])) |
| 542 | + |
515 | 543 | (defn with-block-viewer [doc {:as cell :keys [type]}]
|
516 | 544 | (case type
|
517 |
| - :markdown [(with-viewer `markdown-viewer (process-sidenotes doc (:doc cell)))] |
| 545 | + :markdown (let [{:keys [content]} (:doc cell)] |
| 546 | + (mapcat (fn [fragment] |
| 547 | + (if (= :image (:type (first fragment))) |
| 548 | + (map (partial md-image->viewer doc) fragment) |
| 549 | + [(with-viewer `markdown-viewer (process-sidenotes {:type :doc |
| 550 | + :content (vec fragment) |
| 551 | + ::doc doc} doc))])) |
| 552 | + (partition-by (comp #{:image} :type) content))) |
| 553 | + |
518 | 554 | :code (let [cell (update cell :result apply-viewer-unwrapping-var-from-def)
|
519 | 555 | {:as display-opts :keys [code? result?]} (->display cell)
|
520 | 556 | eval? (-> cell :result :nextjournal/value (get-safe :nextjournal/value) viewer-eval?)]
|
521 |
| - ;; TODO: use vars instead of names |
522 | 557 | (cond-> []
|
523 | 558 | code?
|
524 | 559 | (conj (with-viewer `code-block-viewer {:nextjournal.clerk/opts (select-keys cell [:loc])}
|
|
528 | 563 | (conj (with-viewer (if result?
|
529 | 564 | (:name result-viewer)
|
530 | 565 | (assoc result-viewer :render-fn '(fn [_] [:<>])))
|
531 |
| - (assoc cell :doc doc))))))) |
| 566 | + (assoc cell ::doc doc))))))) |
532 | 567 |
|
533 | 568 | #_(nextjournal.clerk.view/doc->viewer @nextjournal.clerk.webserver/!doc)
|
534 | 569 |
|
|
614 | 649 | (def markdown-viewers
|
615 | 650 | [{:name :nextjournal.markdown/doc
|
616 | 651 | :transform-fn (into-markup [:div.markdown-viewer])}
|
617 |
| - |
618 |
| - ;; blocks |
619 | 652 | {:name :nextjournal.markdown/heading
|
620 | 653 | :transform-fn (into-markup
|
621 | 654 | (fn [{:keys [attrs heading-level]}]
|
622 | 655 | [(str "h" heading-level) attrs]))}
|
623 |
| - {:name :nextjournal.markdown/image :transform-fn #(with-viewer `html-viewer [:img.inline (-> % ->value :attrs)])} |
| 656 | + {:name :nextjournal.markdown/image |
| 657 | + :transform-fn (fn [{node :nextjournal/value}] |
| 658 | + (with-viewer `html-viewer |
| 659 | + [:img.inline (-> node :attrs (update :src process-image-source (::doc node)))]))} |
624 | 660 | {:name :nextjournal.markdown/blockquote :transform-fn (into-markup [:blockquote])}
|
625 | 661 | {:name :nextjournal.markdown/paragraph :transform-fn (into-markup [:p])}
|
626 | 662 | {:name :nextjournal.markdown/plain :transform-fn (into-markup [:<>])}
|
|
749 | 785 | (def image-viewer
|
750 | 786 | {#?@(:clj [:pred #(instance? BufferedImage %)
|
751 | 787 | :transform-fn (fn [{image :nextjournal/value}]
|
752 |
| - (let [w (.getWidth image) |
753 |
| - h (.getHeight image) |
754 |
| - r (float (/ w h))] |
755 |
| - (-> {:nextjournal/value (.. (PngEncoder.) |
756 |
| - (withBufferedImage image) |
757 |
| - (withCompressionLevel 1) |
758 |
| - (toBytes)) |
759 |
| - :nextjournal/content-type "image/png" |
760 |
| - :nextjournal/width (if (and (< 2 r) (< 900 w)) :full :wide)} |
761 |
| - mark-presented)))]) |
| 788 | + (-> {:nextjournal/value (.. (PngEncoder.) |
| 789 | + (withBufferedImage image) |
| 790 | + (withCompressionLevel 1) |
| 791 | + (toBytes)) |
| 792 | + :nextjournal/content-type "image/png" |
| 793 | + :nextjournal/width (image-width image)} |
| 794 | + mark-presented))]) |
762 | 795 | :render-fn '(fn [blob-or-url] [:div.flex.flex-col.items-center.not-prose
|
763 |
| - [:img {:src #?(:clj (nextjournal.clerk.render/url-for blob-or-url) |
| 796 | + [:img {:src #?(:clj (nextjournal.clerk.render/url-for blob-or-url) |
764 | 797 | :cljs blob-or-url)}]])})
|
765 | 798 |
|
766 | 799 | (def ideref-viewer
|
|
1527 | 1560 | (defn image
|
1528 | 1561 | ([image-or-url] (image {} image-or-url))
|
1529 | 1562 | ([viewer-opts image-or-url]
|
1530 |
| - (with-viewer image-viewer viewer-opts #?(:clj (ImageIO/read (if (string? image-or-url) (URL. image-or-url) image-or-url)) |
1531 |
| - :cljs image-or-url)))) |
| 1563 | + (with-viewer image-viewer viewer-opts |
| 1564 | + #?(:cljs image-or-url :clj (read-image image-or-url))))) |
1532 | 1565 |
|
1533 | 1566 | (defn caption [text content]
|
1534 | 1567 | (col
|
|
0 commit comments