diff --git a/common/src/app/common/attrs.cljc b/common/src/app/common/attrs.cljc index 205b02ebd4..95e43d268c 100644 --- a/common/src/app/common/attrs.cljc +++ b/common/src/app/common/attrs.cljc @@ -128,5 +128,7 @@ (merge defaults (get-attrs-multi (->> (txt/node-seq txt/is-root-node? content)) root-attrs) - (get-attrs-multi (->> (txt/node-seq txt/is-paragraph-node? content)) paragraph-attrs) + (get-attrs-multi (->> (txt/node-seq txt/is-paragraph-node? content) + (map #(merge txt/default-list-attrs %))) + paragraph-attrs) (get-attrs-multi (->> (txt/node-seq txt/is-text-node? content)) text-node-attrs)))) diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc index 202b41b1d1..6012c702bf 100644 --- a/common/src/app/common/flags.cljc +++ b/common/src/app/common/flags.cljc @@ -191,6 +191,8 @@ :available-viewer-wasm :stroke-path :stroke-per-side + ;; Enables bullet lists in the v3 (WASM) text editor. + :text-editor-wasm-v1-lists :custom-shortcuts :remote-media-processing diff --git a/common/src/app/common/render_wasm/enums.clj b/common/src/app/common/render_wasm/enums.clj index 0be29aacf9..0e73f76142 100644 --- a/common/src/app/common/render_wasm/enums.clj +++ b/common/src/app/common/render_wasm/enums.clj @@ -38,6 +38,7 @@ ["text-direction" "RawTextDirection"] ["text-decoration" "RawTextDecoration"] ["text-transform" "RawTextTransform"] + ["list-style-type" "RawListStyleType"] ["multiple-state" "MultipleState"] ["transform-entry-kind" "RawTransformEntryKind"] ["segment-data" "RawSegmentData"] diff --git a/common/src/app/common/render_wasm/serializers.cljs b/common/src/app/common/render_wasm/serializers.cljs index 02fe6632d2..b3db247e10 100644 --- a/common/src/app/common/render_wasm/serializers.cljs +++ b/common/src/app/common/render_wasm/serializers.cljs @@ -302,6 +302,16 @@ [text-direction] (untranslate "text-direction" text-direction "ltr")) +(defn translate-list-style-type + [list-style-type] + (let [values (unchecked-get wasm/serializers "list-style-type") + default (unchecked-get values "none")] + (d/nilv (unchecked-get values (d/name list-style-type)) default))) + +(defn untranslate-list-style-type + [list-style-type] + (untranslate "list-style-type" list-style-type "none")) + (defn translate-font-style [font-style] diff --git a/common/src/app/common/render_wasm/text_content.cljs b/common/src/app/common/render_wasm/text_content.cljs index c98c00b4c4..a19fb47c47 100644 --- a/common/src/app/common/render_wasm/text_content.cljs +++ b/common/src/app/common/render_wasm/text_content.cljs @@ -24,7 +24,7 @@ [app.common.uuid :as uuid] [cuerdas.core :as str])) -(def ^:const PARAGRAPH-ATTR-U8-SIZE 12) +(def ^:const PARAGRAPH-ATTR-U8-SIZE 16) (def ^:const SPAN-ATTR-U8-SIZE 64) (def ^:const MAX-TEXT-FILLS types.fills.impl/MAX-FILLS) @@ -109,7 +109,8 @@ text-decoration (sr/translate-text-decoration (get paragraph :text-decoration)) text-transform (sr/translate-text-transform (get paragraph :text-transform)) line-height (serialize-line-height (get paragraph :line-height)) - letter-spacing (serialize-letter-spacing (get paragraph :letter-spacing))] + letter-spacing (serialize-letter-spacing (get paragraph :letter-spacing)) + list-style-type (sr/translate-list-style-type (get paragraph :list-style-type))] (-> offset (mem/write-u8 dview text-align) (mem/write-u8 dview text-direction) @@ -117,6 +118,11 @@ (mem/write-u8 dview text-transform) (mem/write-f32 dview line-height) (mem/write-f32 dview letter-spacing) + (mem/write-u8 dview list-style-type) + ;; padding + (mem/write-u8 dview 0) + (mem/write-u8 dview 0) + (mem/write-u8 dview 0) (mem/assert-written offset PARAGRAPH-ATTR-U8-SIZE)))) (defn- write-spans diff --git a/common/src/app/common/types/shape/attrs.cljc b/common/src/app/common/types/shape/attrs.cljc index eb8dc69193..400538033d 100644 --- a/common/src/app/common/types/shape/attrs.cljc +++ b/common/src/app/common/types/shape/attrs.cljc @@ -357,6 +357,8 @@ :text-direction + :list-style-type + :line-height :letter-spacing diff --git a/common/src/app/common/types/text.cljc b/common/src/app/common/types/text.cljc index 4e53c9b753..fbeb80695c 100644 --- a/common/src/app/common/types/text.cljc +++ b/common/src/app/common/types/text.cljc @@ -43,6 +43,9 @@ (def text-direction-attrs [:text-direction]) +(def text-list-attrs + [:list-style-type]) + (def text-spacing-attrs [:line-height :letter-spacing]) @@ -102,7 +105,8 @@ (def paragraph-attrs (d/concat-vec text-align-attrs - text-direction-attrs)) + text-direction-attrs + text-list-attrs)) (def text-node-attrs (d/concat-vec @@ -138,6 +142,15 @@ {:text-align "left" :text-direction "ltr"}) +(def default-list-attrs + "Older paragraphs have no list attrs; read them as no list." + {:list-style-type "none"}) + +(defn list-paragraph? + "True when a paragraph (or its merged style map) is a list item." + [node] + (= "disc" (:list-style-type node))) + (def default-text-attrs {:typography-ref-file nil :typography-ref-id nil diff --git a/common/test/common_tests/attrs_test.cljc b/common/test/common_tests/attrs_test.cljc index 340a89e0da..8d06d1126b 100644 --- a/common/test/common_tests/attrs_test.cljc +++ b/common/test/common_tests/attrs_test.cljc @@ -148,4 +148,26 @@ {:x -153}] result (attrs/get-attrs-multi objs [:x])] (t/is (= {:x :multiple} result) - "Different positions should be :multiple")))) \ No newline at end of file + "Different positions should be :multiple")))) +(defn- text-shape-with-paragraphs + [& paragraphs] + {:content {:type "root" + :children [{:type "paragraph-set" + :children (mapv #(merge {:type "paragraph" + :children [{:text "item"}]} + %) + paragraphs)}]}}) + +(t/deftest get-text-attrs-multi-list-style-type + (t/testing "paragraphs with the same list style return it" + (let [shape (text-shape-with-paragraphs {:list-style-type "disc"} + {:list-style-type "disc"})] + (t/is (= "disc" (:list-style-type (attrs/get-text-attrs-multi shape {} [:list-style-type])))))) + + (t/testing "a paragraph without list style counts as no list" + (let [shape (text-shape-with-paragraphs {})] + (t/is (= "none" (:list-style-type (attrs/get-text-attrs-multi shape {} [:list-style-type])))))) + + (t/testing "a bullet paragraph next to one without list style is mixed" + (let [shape (text-shape-with-paragraphs {:list-style-type "disc"} {})] + (t/is (= :multiple (:list-style-type (attrs/get-text-attrs-multi shape {} [:list-style-type]))))))) diff --git a/common/test/common_tests/types/text_test.cljc b/common/test/common_tests/types/text_test.cljc index ace36f0c4d..e0027b7b16 100644 --- a/common/test/common_tests/types/text_test.cljc +++ b/common/test/common_tests/types/text_test.cljc @@ -368,3 +368,8 @@ (t/is (every? #(= % "14") original-font-sizes)) (t/is (every? #(= % "32") updated-font-sizes)))) + +(t/deftest test-list-paragraph + (t/is (true? (cttx/list-paragraph? {:type "paragraph" :list-style-type "disc"}))) + (t/is (false? (cttx/list-paragraph? {:type "paragraph" :list-style-type "none"}))) + (t/is (false? (cttx/list-paragraph? {:type "paragraph"})))) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 0464cee98b..ace273017c 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -437,7 +437,9 @@ (-> (merge default-text-attrs node) (assoc :fills fills))) - node))))] + (cond->> node + (txt/is-paragraph-node? node) + (merge txt/default-list-attrs))))))] (attrs/get-attrs-multi nodes attrs))) (defn current-root-values diff --git a/frontend/src/app/main/ui/inspect/attributes/text.cljs b/frontend/src/app/main/ui/inspect/attributes/text.cljs index e6434bd722..799f8e0db7 100644 --- a/frontend/src/app/main/ui/inspect/attributes/text.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/text.cljs @@ -129,6 +129,14 @@ "uppercase" (tr "inspect.attributes.typography.text-transform.uppercase") "unset" (tr "inspect.attributes.typography.text-transform.unset"))]]]]) + (when (types.text/list-paragraph? style) + [:div {:class (stl/css :text-row)} + [:div {:class (stl/css :global/attr-label)} "List Style"] + [:div {:class (stl/css :global/attr-value)} + [:> copy-button* {:data (copy-style-data style :list-style-type)} + [:div {:class (stl/css :button-children)} + (tr "inspect.attributes.typography.list-style-type.disc")]]]]) + [:> copy-button* {:data (str/trim text) :class (stl/css :attributes-content-row)} [:span {:class (stl/css :content) diff --git a/frontend/src/app/main/ui/inspect/styles/panels/text.cljs b/frontend/src/app/main/ui/inspect/styles/panels/text.cljs index f68c764913..a2491d78f7 100644 --- a/frontend/src/app/main/ui/inspect/styles/panels/text.cljs +++ b/frontend/src/app/main/ui/inspect/styles/panels/text.cljs @@ -180,6 +180,12 @@ :property (str "text-transform: " (:text-transform style) ";") :copiable true}])) + (when (txt/list-paragraph? style) + [:> properties-row* {:term "List Style" + :detail (:list-style-type style) + :property (str "list-style-type: " (:list-style-type style) ";") + :copiable true}]) + [:pre {:class (stl/css :text-content-wrapper) :role "presentation"} [:> property-detail-copiable* {:copied copied diff --git a/frontend/src/app/main/ui/shapes/text/html_text.cljs b/frontend/src/app/main/ui/shapes/text/html_text.cljs index 299ee49de7..7ffd80c38c 100644 --- a/frontend/src/app/main/ui/shapes/text/html_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/html_text.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.text :as legacy.txt] + [app.common.types.text :as txt] [app.main.ui.shapes.text.styles :as sts] [rumext.v2 :as mf])) @@ -39,17 +40,48 @@ [:div.paragraph-set {:style style :class class} children])) (mf/defc render-paragraph* - [{:keys [node children shape is-code]}] + [{:keys [node children shape is-code is-list-item]}] (let [style (when-not is-code (sts/generate-paragraph-styles shape node)) class (when is-code (:$id node)) dir (:text-direction node "auto")] - [:p.paragraph {:style style :dir dir :class class} children])) + (if is-list-item + [:li {:style style :dir dir :class class} children] + [:p.paragraph {:style style :dir dir :class class} children]))) + +(declare render-node*) + +(defn- render-children + "Child elements of `parent`. In code mode, runs of list paragraphs are + grouped into a `