0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-23 15:26:29 -05:00

Information panels

This commit is contained in:
alonso.torres 2020-10-22 18:08:40 +02:00 committed by Hirunatan
parent 04f620ec00
commit 1e48221d7b
10 changed files with 713 additions and 18 deletions

View file

@ -3360,5 +3360,73 @@
"ru" : "Кликни чтобы закончить фигуру",
"es" : "Pulsar para cerrar la ruta"
}
}
},
"handoff.tabs.info": "Info",
"handoff.tabs.code": "Code",
"handoff.attributes.color.hex": "HEX",
"handoff.attributes.color.rgba": "RGBA",
"handoff.attributes.color.hsla": "HSLA",
"handoff.attributes.layout": "Layout",
"handoff.attributes.layout.width": "Width",
"handoff.attributes.layout.height": "Height",
"handoff.attributes.layout.left": "Left",
"handoff.attributes.layout.top": "Top",
"handoff.attributes.layout.rotation": "Rotation",
"handoff.attributes.fill": "Fill",
"handoff.attributes.stroke": "Stroke",
"handoff.attributes.stroke.width": "Width",
"handoff.attributes.stroke.style.solid": "Solid",
"handoff.attributes.stroke.style.dotted": "Dotted",
"handoff.attributes.stroke.style.dashed": "Dashed",
"handoff.attributes.stroke.style.mixed": "Mixed",
"handoff.attributes.stroke.style.none": "None",
"handoff.attributes.stroke.alignment.center": "Center",
"handoff.attributes.stroke.alignment.inner": "Inner",
"handoff.attributes.stroke.alignment.outer": "Outer",
"handoff.attributes.shadow": "Shadow",
"handoff.attributes.shadow.shorthand.offset-x": "X",
"handoff.attributes.shadow.shorthand.offset-y": "Y",
"handoff.attributes.shadow.shorthand.blur": "B",
"handoff.attributes.shadow.shorthand.spread": "S",
"handoff.attributes.shadow.style.inner-shadow": "Inner",
"handoff.attributes.shadow.style.drop-shadow": "Drop",
"handoff.attributes.blur": "Blur",
"handoff.attributes.blur.value": "Value",
"handoff.attributes.image.width": "Width",
"handoff.attributes.image.height": "Height",
"handoff.attributes.image.download": "Dowload source image",
"handoff.attributes.typography": "Typography",
"handoff.attributes.typography.font-family": "Font Family",
"handoff.attributes.typography.font-style": "Font Style",
"handoff.attributes.typography.font-size": "Font Size",
"handoff.attributes.typography.line-height": "Line Height",
"handoff.attributes.typography.letter-spacing": "Letter Spacing",
"handoff.attributes.typography.text-decoration": "Text Decoration",
"handoff.attributes.typography.text-transform": "Text Transform",
"handoff.attributes.content": "Content",
"handoff.attributes.typography.text-decoration.none": "None",
"handoff.attributes.typography.text-decoration.underline": "Underline",
"handoff.attributes.typography.text-decoration.strikethrough": "Strikethrough",
"handoff.attributes.typography.text-transform.none": "None",
"handoff.attributes.typography.text-transform.uppercase": "Upper Case",
"handoff.attributes.typography.text-transform.lowercase": "Lower Case",
"handoff.attributes.typography.text-transform.titlecase": "Title Case"
}

View file

@ -14,3 +14,213 @@
align-items: center;
justify-content: center;
}
.attributes-block {
user-select: text;
border-bottom: 1px solid $color-gray-60;
padding-bottom: 0.5rem;
font-size: $fs12;
.attributes-copy-button {
visibility: hidden;
opacity: 0;
transition: opacity 0.3s;
position: absolute;
right: 0;
top: 0;
background: none;
border: none;
padding: 0;
cursor: pointer;
svg {
width: 16px;
height: 16px;
fill: $color-gray-20;
transition: fill 0.3s;
&:hover {
fill: $color-primary;
}
}
}
.attributes-label {
color: $color-gray-20;
}
.attributes-value {
color: $color-white;
}
.attributes-block-title {
position: relative;
color: $color-gray-10;
padding: 0.5rem;
font-size: $fs14;
.attributes-copy-button {
padding: 0.5rem;
margin-top: 0.25rem;
}
}
.attributes-unit-row {
position: relative;
display: flex;
flex-direction: row;
padding: 1rem 0.5rem;
.attributes-label,
.attributes-value {
width: 50%;
}
.attributes-copy-button {
padding: 1rem 0.5rem;
margin-top: 0.25rem;
}
}
.attributes-color-row {
display: flex;
padding: 1rem 0;
position: relative;
align-items: center;
.attributes-color-display {
display: flex;
}
.color-bullet {
width: 24px;
height: 24px;
}
.attributes-copy-button {
padding: 1rem 0.5rem;
margin-top: 0.25rem;
}
& > * {
margin: 0 0.5rem;
}
& :last-child {
margin-right: 0;
}
select {
font-size: $fs12;
margin: 0;
background: none;
color: $color-gray-20;
border: none;
border-bottom: 1px solid $color-gray-30;
padding: 0 1rem 0.25rem 0.25rem;
margin-top: 2px;
background-image: url("/images/icons/arrow-down-white.svg");
background-repeat: no-repeat;
background-position: 95% 48%;
background-size: 10px;
cursor: pointer;
option {
padding: 1rem;
background-color: $color-gray-50;
border: none;
}
}
}
.attributes-content-row {
position: relative;
margin: 0.5rem;
width: calc(100% - 1rem);
.attributes-content {
overflow-y: auto;
max-height: 10rem;
background: $color-gray-60;
border-radius: 4px;
padding: 1rem 0.5rem;
color: $color-gray-10;
white-space: pre-wrap;
}
}
.attributes-image-row {
position: relative;
display: flex;
padding: 0.25rem;
align-items: center;
justify-content: center;
margin: 0.5rem;
background: $color-gray-60;
border-radius: 4px;
width: calc(100% - 1rem);
min-height: 5rem;
img {
max-height: 8rem;
width: auto;
}
}
.attributes-shadow-row {
position: relative;
display: flex;
margin: 0.5rem;
padding-right: 2rem;
justify-content: space-between;
& > :first-child {
color: $color-gray-10;
}
.attributes-shadow {
display: flex;
.attributes-label {
margin-right: 2px;
}
}
}
.attributes-stroke-row {
position: relative;
display: flex;
margin: 0.5rem;
padding-right: 2rem;
justify-content: space-between;
}
.download-button {
display: block;
text-align: center;
border: 1px solid $color-gray-60;
background-color: $color-gray-60;
padding: 0.5rem 1rem;
color: $color-gray-10;
width: calc(100% - 1rem);
border-radius: 4px;
margin: 0.5rem;
cursor: pointer;
&:hover {
background-color: $color-primary;
color: $color-black;
}
}
.attributes-block-title,
.attributes-unit-row,
.attributes-color-row,
.attributes-shadow-row,
.attributes-stroke-row {
&:hover .attributes-copy-button {
visibility: visible;
opacity: 1;
}
}
}

View file

@ -93,12 +93,13 @@
(update [_ state]
(let [objects (:objects page)
frames (extract-frames objects)]
(assoc state :viewer-data {:project project
:objects objects
:file file
:page page
:frames frames
:share-token share-token})))))
(-> state
(assoc :viewer-data {:project project
:objects objects
:file file
:page page
:frames frames
:share-token share-token}))))))
(def create-share-link
(ptk/reify ::create-share-link

View file

@ -36,7 +36,7 @@
(let [color (if (string? color) {:color color :opacity 1} color)
{:keys [name color opacity gradient]} color
color-str (or name color (gradient-type->string (:type gradient)))]
(when (= size :big)
(when (or (not size) (= size :big))
[:span.color-text {:on-click #(when on-click (on-click %))
:on-double-click #(when on-double-click (on-double-click %))
:title name } color-str])))

View file

@ -43,6 +43,12 @@
frames (:frames data [])
objects (:objects data)
frame (get frames index)]
(mf/use-effect
(mf/deps index)
(fn []
(st/emit! (dv/select-shape (:id frame)))))
[:section.viewer-preview
(cond
(empty? frames)
@ -60,7 +66,7 @@
[:& render-frame-svg {:frame-id (:id frame)
:zoom (:zoom local)
:objects objects}]]
[:& attributes-sidebar]])]))
[:& attributes-sidebar {:frame frame}]])]))
(mf/defc handoff-content
[{:keys [data local index] :as props}]

View file

@ -0,0 +1,248 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attrib-panel
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.config :as cfg]
[app.util.i18n :refer [locale t]]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.main.ui.icons :as i]
[app.util.color :as uc]
[app.util.text :as ut]
[app.main.fonts :as fonts]
[app.main.ui.components.color-bullet :refer [color-bullet color-name]]))
(mf/defc color-row [{:keys [color]}]
(let [locale (mf/deref locale)]
[:div.attributes-color-row
[:& color-bullet {:color color}]
[:*
[:& color-name {:color color}]
(when-not (:gradient color) [:div (str (* 100 (:opacity color)) "%")])]
[:select
[:option (t locale "handoff.attributes.color.hex")]
[:option (t locale "handoff.attributes.color.rgba")]
[:option (t locale "handoff.attributes.color.hsla")]]
[:button.attributes-copy-button i/copy]]))
(mf/defc layout-panel
[{:keys [shape locale]}]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.layout")]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.width")]
[:div.attributes-value (mth/precision (:width shape) 2) "px"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.height")]
[:div.attributes-value (mth/precision (:height shape) 2) "px"]
[:button.attributes-copy-button i/copy]]
(when (not= (:x shape) 0)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.left")]
[:div.attributes-value (mth/precision (:x shape) 2) "px"]
[:button.attributes-copy-button i/copy]])
(when (not= (:y shape) 0)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.top")]
[:div.attributes-value (mth/precision (:y shape) 2) "px"]
[:button.attributes-copy-button i/copy]])
(when (not= (:rotation shape) 0)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.rotation")]
[:div.attributes-value (mth/precision (:rotation shape) 2) "deg"]
[:button.attributes-copy-button i/copy]])])
(mf/defc fill-panel
[{:keys [shape locale]}]
(let [{:keys [fill-color fill-opacity fill-color-gradient fill-ref-id fill-ref-file-id]} shape]
(when (or fill-color fill-color-gradient)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.fill")]
[:button.attributes-copy-button i/copy]]
(let [color {:color fill-color
:opacity fill-opacity
:gradient fill-color-gradient
:id fill-ref-id
:file-id fill-ref-file-id}]
[:& color-row {:color color}])])))
(mf/defc stroke-panel
[{:keys [shape locale]}]
(when (and (:stroke-style shape) (not= (:stroke-style shape) :none))
(let [{:keys [stroke-style stroke-alignment stroke-width
stroke-color stroke-opacity stroke-color-gradient
stroke-color-ref-id stroke-color-file-id]} shape
color {:color stroke-color
:opacity stroke-opacity
:gradient stroke-color-gradient
:id stroke-color-ref-id
:file-id stroke-color-file-id}]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.stroke")]
[:button.attributes-copy-button i/copy]]
[:& color-row {:color color}]
[:div.attributes-stroke-row
[:div.attributes-label (t locale "handoff.attributes.stroke.width")]
[:div.attributes-value (str stroke-width) "px"]
[:div.attributes-value (->> stroke-style name (str "handoff.attributes.stroke.style.") (t locale))]
[:div.attributes-label (->> stroke-alignment name (str "handoff.attributes.stroke.alignment.") (t locale))]
[:button.attributes-copy-button i/copy]]])))
(mf/defc shadow-panel [{:keys [shape locale]}]
(when (seq (:shadow shape))
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.shadow")]
[:button.attributes-copy-button i/copy]]
(for [shadow (:shadow shape)]
(do
(prn "???" (:spread shadow))
[:*
[:div.attributes-shadow-row
[:div.attributes-label (->> shadow :style name (str "handoff.attributes.shadow.style.") (t locale))]
[:div.attributes-shadow
[:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.offset-x")]
[:div.attributes-value (str (:offset-x shadow))]]
[:div.attributes-shadow
[:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.offset-y")]
[:div.attributes-value (str (:offset-y shadow))]]
[:div.attributes-shadow
[:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.blur")]
[:div.attributes-value (str (:blur shadow))]]
[:div.attributes-shadow
[:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.spread")]
[:div.attributes-value (str (:spread shadow))]]
[:button.attributes-copy-button i/copy]]
[:& color-row {:color (:color shadow)}]]))]))
(mf/defc blur-panel [{:keys [shape locale]}]
(when (:blur shape)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.blur")]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.blur.value")]
[:div.attributes-value (-> shape :blur :value) "px"]
[:button.attributes-copy-button i/copy]]]))
(mf/defc image-panel [{:keys [shape locale]}]
[:div.attributes-block
[:div.attributes-image-row
[:div.attributes-image
[:img {:src (cfg/resolve-media-path (-> shape :metadata :path))}]]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.image.width")]
[:div.attributes-value (-> shape :metadata :width) "px"]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.image.height")]
[:div.attributes-value (-> shape :metadata :height) "px"]]
(let [filename (last (str/split (-> shape :metadata :path) "/"))]
[:a.download-button {:target "_blank"
:download filename
:href (cfg/resolve-media-path (-> shape :metadata :path))}
(t locale "handoff.attributes.image.download")])])
(mf/defc typography-panel [{:keys [shape locale]}]
(let [font (ut/search-text-attrs (:content shape)
(keys ut/default-text-attrs))
font (merge ut/default-text-attrs font)]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.typography")]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.font-family")]
[:div.attributes-value (-> font :font-id fonts/get-font-data :name)]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.font-style")]
[:div.attributes-value (str (:font-style font))]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.font-size")]
[:div.attributes-value (str (:font-size font)) "px"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.line-height")]
[:div.attributes-value (str (:line-height font)) "px"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.letter-spacing")]
[:div.attributes-value (str (:letter-spacing font)) "px"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.text-decoration")]
[:div.attributes-value (->> font :text-decoration (str "handoff.attributes.typography.text-decoration.") (t locale))]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.text-transform")]
[:div.attributes-value (->> font :text-transform (str "handoff.attributes.typography.text-transform.") (t locale))]
[:button.attributes-copy-button i/copy]]]))
(mf/defc content-panel [{:keys [shape locale]}]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.content")]
[:button.attributes-copy-button i/copy]]
[:div.attributes-content-row
[:pre.attributes-content (ut/content->text (:content shape))]
[:button.attributes-copy-button i/copy]]])
(mf/defc attrib-panel [{:keys [shape frame options]}]
(let [locale (mf/deref locale)]
[:div.element-options
(for [option options]
[:>
(case option
:layout layout-panel
:fill fill-panel
:stroke stroke-panel
:shadow shadow-panel
:blur blur-panel
:image image-panel
:typography typography-panel
:content content-panel
)
{:shape (gsh/translate-to-frame shape frame)
:frame frame
:locale locale}])]))

View file

@ -0,0 +1,110 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attributes.layout
(:require
[rumext.alpha :as mf]
[app.main.ui.icons :as i]
[app.main.ui.components.color-bullet :refer [color-bullet color-name]]))
(mf/defc layout-panel [{:keys [shapes]}]
(prn "???" shapes)
[:*
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text "Layout"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label "Width"]
[:div.attributes-value "100px"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label "Height"]
[:div.attributes-value "100px"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label "Top"]
[:div.attributes-value "100px"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label "Left"]
[:div.attributes-value "100px"]
[:button.attributes-copy-button i/copy]]]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text "Fill"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-shadow-row
[:div.attributes-label "Drop"]
[:div.attributes-shadow
[:div.attributes-label "X"]
[:div.attributes-value "4"]]
[:div.attributes-shadow
[:div.attributes-label "Y"]
[:div.attributes-value "4"]]
[:div.attributes-shadow
[:div.attributes-label "B"]
[:div.attributes-value "0"]]
[:div.attributes-shadow
[:div.attributes-label "B"]
[:div.attributes-value "0"]]
[:button.attributes-copy-button i/copy]]
[:div.attributes-color-row
[:& color-bullet {:color {:color "#000000" :opacity 0.5}}]
[:*
[:div "#000000"]
[:div "100%"]]
[:select
[:option "Hex"]
[:option "RGBA"]
[:option "HSLA"]]
[:button.attributes-copy-button i/copy]]
[:div.attributes-stroke-row
[:div.attributes-label "Width"]
[:div.attributes-value "1px"]
[:div.attributes-value "Solid"]
[:div.attributes-label "Center"]
[:button.attributes-copy-button i/copy]]]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text "Content"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-content-row
[:div.attributes-content
"Hi, how are you"]
[:button.attributes-copy-button i/copy]]]
[:div.attributes-block
[:div.attributes-image-row
[:div.attributes-image
#_[:img {:src "https://www.publico.es/tremending/wp-content/uploads/2019/05/Cxagv.jpg"}]
#_[:img {:src "https://i.blogs.es/3861b2/grumpy-cat/1366_2000.png"}]
[:img {:src "https://abs.twimg.com/favicons/twitter.ico"}]
]]
[:button.download-button "Dowload source image"]]
])

View file

@ -10,16 +10,48 @@
(ns app.main.ui.viewer.handoff.attributes-sidebar
(:require
[rumext.alpha :as mf]
[okulary.core :as l]
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.main.ui.components.tab-container :refer [tab-container tab-element]]))
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
[app.main.ui.viewer.handoff.attrib-panel :refer [attrib-panel]]))
(mf/defc info-panel []
[:div.element-options])
(defn make-selected-shapes-iref
[]
(let [selected->shapes
(fn [state]
(let [selected (get-in state [:viewer-local :selected])
objects (get-in state [:viewer-data :page :objects])
resolve-shape #(get objects %)]
(mapv resolve-shape selected)))]
#(l/derived selected->shapes st/state)))
(mf/defc info-panel [{:keys [frame]}]
(let [selected-ref (mf/use-memo (make-selected-shapes-iref))
shapes (mf/deref selected-ref)]
(if (> (count shapes) 1)
;; Multiple selection
nil
;; Single shape
(when-let [shape (first shapes)]
(let [options
(case (:type shape)
:frame [:layout :fill]
:group [:layout]
:rect [:layout :fill :stroke :shadow :blur]
:circle [:layout :fill :stroke :shadow :blur]
:path [:layout :fill :stroke :shadow :blur]
:curve [:layout :fill :stroke :shadow :blur]
:image [:image :layout :shadow :blur]
:text [:layout :fill :typography :content :shadow :blur])]
[:& attrib-panel {:frame frame
:shape shape
:options options}])))))
(mf/defc code-panel []
[:div.element-options])
(mf/defc attributes-sidebar []
(mf/defc attributes-sidebar [{:keys [frame]}]
(let [section (mf/use-state :info #_:code)]
[:aside.settings-bar.settings-bar-right
[:div.settings-bar-inside
@ -31,8 +63,7 @@
[:& tab-container {:on-change-tab #(reset! section %)
:selected @section}
[:& tab-element {:id :info :title "Info"}
[:& info-panel]]
[:& info-panel {:frame frame}]]
[:& tab-element {:id :code :title "Code"}
[:& code-panel]]]]]]]))

View file

@ -46,7 +46,7 @@
(let [hover (get-in state [:viewer-local :hover])
objects (get-in state [:viewer-data :page :objects])
resolve-shape #(get objects %)]
(map resolve-shape hover)))]
(mapv resolve-shape hover)))]
#(l/derived hover->shapes st/state)))
(mf/defc selection-feedback [{:keys [frame]}]

View file

@ -1,4 +1,6 @@
(ns app.util.text)
(ns app.util.text
(:require
[cuerdas.core :as str]))
(defonce default-text-attrs
{:font-id "sourcesanspro"
@ -11,7 +13,9 @@
:letter-spacing "0"
:text-transform "none"
:text-align "left"
:text-decoration "none"})
:text-decoration "none"
:fill-color "#000000"
:fill-opacity 1})
(def typography-fields
[:font-id
@ -38,3 +42,20 @@
[map-fn node]
(cond-> (map-fn node)
(:children node) (update :children (fn [children] (mapv #(map-node map-fn %) children)))))
(defn content->text
[node]
(str
(if (:children node)
(str/join (if (= "paragraph-set" (:type node)) "\n" "") (map content->text (:children node)))
(:text node ""))))
(defn search-text-attrs
[node attrs]
(let [rec-fn
(fn rec-fn [current node]
(let [current (reduce rec-fn current (:children node []))]
(merge current
(select-keys node attrs))))]
(rec-fn {} node)))