improved options

This commit is contained in:
alonso.torres 2026-07-16 12:35:19 +02:00
parent e3367c8534
commit abd290eba1
27 changed files with 490 additions and 144 deletions

View File

@ -49,6 +49,7 @@
[:map {:title "ProfileProps"}
[:plugins {:optional true} schema:plugin-registry]
[:renderer {:optional true} [::sm/one-of #{:svg :wasm}]]
[:japanese-layout-all-files {:optional true} ::sm/boolean]
[:mcp-enabled {:optional true} ::sm/boolean]
[:newsletter-updates {:optional true} ::sm/boolean]
[:newsletter-news {:optional true} ::sm/boolean]

View File

@ -150,6 +150,21 @@
(t/is (nil? (:error out)))
(t/is (nil? (:photo-id (:result out))))))))
(t/deftest update-profile-japanese-layout-setting
(let [profile (th/create-profile* 1)
params {::th/type :update-profile-props
::rpc/profile-id (:id profile)
:props {:japanese-layout-all-files true}}
out (th/command! params)]
(t/is (nil? (:error out)))
(t/is (true? (get-in out [:result :japanese-layout-all-files])))
(let [params {::th/type :get-profile
::rpc/profile-id (:id profile)}
out (th/command! params)]
(t/is (nil? (:error out)))
(t/is (true? (get-in out [:result :props :japanese-layout-all-files]))))))
(t/deftest profile-deletion-1
(let [prof (th/create-profile* 1)
file (th/create-file* 1 {:profile-id (:id prof)

View File

@ -56,7 +56,6 @@
"text-editor/v2-html-paste"
"text-editor/v2"
"text-editor-wasm/v1"
"japanese-layout/v1"
"render-wasm/v1"
"variants/v1"})
@ -82,7 +81,6 @@
"text-editor/v2-html-paste"
"text-editor/v2"
"text-editor-wasm/v1"
"japanese-layout/v1"
"tokens/numeric-input"
"render-wasm/v1"})
@ -133,7 +131,6 @@
:feature-text-editor-v2 "text-editor/v2"
:feature-text-editor-v2-html-paste "text-editor/v2-html-paste"
:feature-text-editor-wasm "text-editor-wasm/v1"
:feature-japanese-layout "japanese-layout/v1"
:feature-render-wasm "render-wasm/v1"
:feature-variants "variants/v1"
:feature-token-input "tokens/numeric-input"

View File

@ -427,7 +427,12 @@
[:set-base-font-size
[:map {:title "ModBaseFontSize"}
[:type [:= :set-base-font-size]]
[:base-font-size :string]]]])
[:base-font-size :string]]]
[:set-japanese-layout
[:map {:title "SetJapaneseLayout"}
[:type [:= :set-japanese-layout]]
[:enabled ::sm/boolean]]]])
(def schema:changes
[:sequential {:gen/max 5 :gen/min 1} schema:change])
@ -1065,6 +1070,10 @@
[data {:keys [base-font-size]}]
(ctf/set-base-font-size data base-font-size))
(defmethod process-change :set-japanese-layout
[data {:keys [enabled]}]
(ctf/set-japanese-layout-enabled data enabled))
;; === Operations

View File

@ -1084,6 +1084,18 @@
:base-font-size previous-font-size})
(apply-changes-local))))
(defn set-japanese-layout
[changes enabled?]
(assert-file-data! changes)
(let [file-data (::file-data (meta changes))
previous-enabled (ctf/japanese-layout-enabled? file-data)]
(-> changes
(update :redo-changes conj {:type :set-japanese-layout
:enabled enabled?})
(update :undo-changes conj {:type :set-japanese-layout
:enabled previous-enabled})
(apply-changes-local))))
;; Misc changes
(defn reorder-children

View File

@ -75,7 +75,8 @@
(def schema:options
[:map {:title "FileOptions"}
[:components-v2 {:optional true} ::sm/boolean]
[:base-font-size {:optional true} :string]])
[:base-font-size {:optional true} :string]
[:japanese-layout {:optional true} ::sm/boolean]])
(def schema:data
[:map {:title "FileData"}
@ -1225,6 +1226,19 @@
(assoc-in file-data [:options :base-font-size] base-font-size))
;; Japanese text layout
(defn japanese-layout-enabled?
[file-data]
(true? (get-in file-data [:options :japanese-layout])))
(defn set-japanese-layout-enabled
[file-data enabled?]
(if enabled?
(assoc-in file-data [:options :japanese-layout] true)
(d/update-in-when file-data [:options] dissoc :japanese-layout)))
;; Ref Chains
(defn get-ref-chain-until-target-ref
"Returns a vector with the shape ref chain until target-ref, including itself"

View File

@ -26,6 +26,7 @@
;; Ruby (furigana) annotation text and customization carried per span.
(def text-ruby-attrs
[:ruby
:ruby-hidden
:ruby-size
:ruby-align
:ruby-overhang

View File

@ -24,6 +24,26 @@
(binding [ffeat/*current* #{"components/v2"}]
(ctf/make-file-data file-id page-id)))
(t/deftest set-japanese-layout-file-option
(let [file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1)
data (make-file-data file-id page-id)
enabled (ch/process-changes data [{:type :set-japanese-layout
:enabled true}])
disabled (ch/process-changes enabled [{:type :set-japanese-layout
:enabled false}])
changes (-> (pcb/empty-changes)
(pcb/with-file-data data)
(pcb/set-japanese-layout true))]
(t/is (false? (ctf/japanese-layout-enabled? data)))
(t/is (true? (ctf/japanese-layout-enabled? enabled)))
(t/is (false? (ctf/japanese-layout-enabled? disabled)))
(t/is (not (contains? (:options disabled) :japanese-layout)))
(t/is (= [{:type :set-japanese-layout :enabled true}]
(:redo-changes changes)))
(t/is (= '({:type :set-japanese-layout :enabled false})
(:undo-changes changes)))))
(t/deftest add-obj
(let [file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1)

View File

@ -604,6 +604,17 @@
(dm/export layout/toggle-layout-flag)
(dm/export layout/remove-layout-flag)
(defn set-japanese-layout
[enabled?]
(ptk/reify ::set-japanese-layout
ptk/WatchEvent
(watch [it state _]
(let [file-data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes it)
(pcb/with-file-data file-data)
(pcb/set-japanese-layout enabled?))]
(rx/of (dch/commit-changes changes))))))
;; --- Profile
(defn update-nudge

View File

@ -61,7 +61,7 @@
(declare v2-sync-wasm-text-layout)
(def ruby-presentation-attrs
[:ruby-size :ruby-align :ruby-overhang :ruby-side])
[:ruby-hidden :ruby-size :ruby-align :ruby-overhang :ruby-side])
;; -- Content helpers
@ -340,7 +340,6 @@
(count (:text node)))))
(defn decorate-range-info
"Adds information about ranges inside the metadata of the text nodes"
[content]

View File

@ -115,10 +115,37 @@
:on-change handle-render-change}]]
[:> text* {:typography t/body-medium :class (stl/css :feedback)} [:a {:href "#" :on-click go-settings-feedback :class (stl/css :link)} (tr "dashboard.webgl-switch.feedback") [:> icon* {:icon-id "arrow-up-right" :size "s"}]]]]))
(mf/defc japanese-layout-settings*
[{:keys [enabled]}]
(let [handle-change
(mf/use-fn
(fn [enabled?]
(st/emit! (ev/event {::ev/name (if enabled?
"enable-japanese-layout-all-files"
"disable-japanese-layout-all-files")
::ev/origin "settings"})
(du/update-profile-props
{:japanese-layout-all-files enabled?})
(ntf/success (tr "notifications.profile-saved")))))]
[:section {:class (stl/css :japanese-layout-container)}
[:> heading* {:class (stl/css :title)
:level 2
:typography t/title-large}
(tr "dashboard.japanese-layout.title")]
[:> text* {:class (stl/css :description) :typography t/body-medium}
(tr "dashboard.japanese-layout.description")]
[:form {:class (stl/css :japanese-layout-form)}
[:> switch* {:label (tr "dashboard.japanese-layout.enable-all-files")
:default-checked enabled
:on-change handle-change}]]]))
(mf/defc options-page*
[]
(let [profile (mf/deref refs/profile)
renderer (or (-> profile :props :renderer) :svg)]
renderer (or (-> profile :props :renderer) :svg)
japanese-layout-enabled (true? (-> profile
:props
:japanese-layout-all-files))]
(mf/use-effect
#(dom/set-html-title (tr "title.settings.options")))
@ -127,5 +154,6 @@
[:div {:class (stl/css :form-container) :data-testid "settings-form"}
[:h2 (tr "labels.settings")]
[:> options-form*]]
[:> japanese-layout-settings* {:enabled japanese-layout-enabled}]
(when (contains? cf/flags :render-switch)
[:> webgl-settings* {:renderer renderer}])]]))

View File

@ -21,9 +21,9 @@
}
}
/* Copied from profile.scss .form-container, but without included nested
rules, since we want custom styles for it */
.webgl-container {
/* Settings sections with standalone controls. */
.webgl-container,
.japanese-layout-container {
display: grid;
grid-auto-rows: auto;
gap: var(--sp-s);
@ -34,6 +34,10 @@
color: var(--color-foreground-primary);
}
.japanese-layout-form {
padding-block-start: var(--sp-xs);
}
.webgl-header {
display: flex;
flex-direction: row;

View File

@ -22,7 +22,9 @@
(sts/generate-text-styles shape parent)
(sts/generate-text-styles shape node))
ruby (:ruby node)
ruby? (and (string? ruby) (not (str/blank? ruby)))]
ruby? (and (not (true? (:ruby-hidden node)))
(string? ruby)
(not (str/blank? ruby)))]
(if ruby?
[:ruby.ruby-node {:style (sts/generate-ruby-container-styles node)}
[:span.text-node {:style style} text]

View File

@ -22,7 +22,9 @@
(sts/generate-text-styles shape node))
class (when is-code (:$id node))
ruby (:ruby node)
ruby? (and (string? ruby) (not (str/blank? ruby)))]
ruby? (and (not (true? (:ruby-hidden node)))
(string? ruby)
(not (str/blank? ruby)))]
(if ruby?
[:ruby.ruby-node {:style (sts/generate-ruby-container-styles node)}
[:span.text-node {:style style :class class} text]

View File

@ -97,7 +97,8 @@
font-features (:font-features data)
annotation-clearance (:annotation-clearance data)
annotation-layers (if (= "auto" annotation-clearance)
(+ (if (and (string? (:ruby data))
(+ (if (and (not (true? (:ruby-hidden data)))
(string? (:ruby data))
(seq (:ruby data))) 1 0)
(if (and (string? text-emphasis)
(not= "none" text-emphasis)) 1 0))

View File

@ -10,6 +10,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.common :as dcm]
@ -228,8 +229,13 @@
(mf/defc preferences-menu*
{::mf/private true
::mf/wrap [mf/memo]}
[{:keys [layout profile toggle-flag on-close toggle-theme toggle-render]}]
(let [renderer (or (-> profile :props :renderer) :svg)
[{:keys [japanese-layout-enabled layout profile toggle-flag on-close
toggle-theme toggle-render toggle-japanese-layout]}]
(let [renderer (or (-> profile :props :renderer) :svg)
japanese-layout-all-files? (true? (-> profile
:props
:japanese-layout-all-files))
read-only? (mf/use-ctx ctx/workspace-read-only?)
show-nudge-options
(mf/use-fn
@ -312,6 +318,19 @@
:id "file-menu-nudge"}
[:span {:class (stl/css :item-name)} (tr "modals.nudge-title")]]
(when-not (or read-only? japanese-layout-all-files?)
[:> dropdown-menu-item* {:on-click toggle-japanese-layout
:class (stl/css :base-menu-item :submenu-item)
:on-key-down (fn [event]
(when (kbd/enter? event)
(toggle-japanese-layout event)))
:data-testid "japanese-layout"
:id "file-menu-japanese-layout"}
[:span {:class (stl/css :item-name)}
(if japanese-layout-enabled
(tr "workspace.header.menu.disable-japanese-layout")
(tr "workspace.header.menu.enable-japanese-layout"))]])
[:> dropdown-menu-item* {:on-click toggle-theme
:class (stl/css :base-menu-item :submenu-item)
:on-key-down (fn [event]
@ -881,6 +900,9 @@
(mf/defc menu*
[{:keys [layout file]}]
(let [profile (mf/deref refs/profile)
file-data (mf/deref refs/workspace-data)
japanese-layout-enabled
(ctf/japanese-layout-enabled? file-data)
show-menu* (mf/use-state false)
show-menu? (deref show-menu*)
@ -949,6 +971,15 @@
(dom/stop-propagation event)
(st/emit! (du/toggle-theme))))
toggle-japanese-layout
(mf/use-fn
(mf/deps japanese-layout-enabled)
(fn [event]
(dom/stop-propagation event)
(st/emit! (dw/set-japanese-layout (not japanese-layout-enabled)))
(reset! show-menu* false)
(reset! selected-sub-menu* nil)))
toggle-render
(mf/use-fn
(mf/deps profile)
@ -1145,9 +1176,11 @@
:preferences
[:> preferences-menu* {:layout layout
:profile profile
:japanese-layout-enabled japanese-layout-enabled
:toggle-flag toggle-flag
:toggle-theme toggle-theme
:toggle-render toggle-render
:toggle-japanese-layout toggle-japanese-layout
:on-close close-sub-menu}]
:plugins

View File

@ -314,6 +314,8 @@
(get n-values :text-emphasis))
(identical? (get o-values :ruby)
(get n-values :ruby))
(identical? (get o-values :ruby-hidden)
(get n-values :ruby-hidden))
(identical? (get o-values :ruby-size)
(get n-values :ruby-size))
(identical? (get o-values :ruby-align)
@ -342,10 +344,17 @@
(let [;; --- UI state
menu-state* (mf/use-state {:main-menu true
:more-options false})
:more-options false
:japanese-layout true})
menu-state (deref menu-state*)
main-menu-open? (:main-menu menu-state)
more-options-open? (:more-options menu-state)
japanese-layout-open? (:japanese-layout menu-state)
profile (mf/deref refs/profile)
file-data (mf/deref refs/workspace-data)
japanese-layout-config-enabled?
(tjl/japanese-layout-config-enabled? file-data profile)
token-dropdown-open* (mf/use-state false)
token-dropdown-open? (deref token-dropdown-open*)
@ -431,6 +440,10 @@
(mf/use-fn
#(swap! menu-state* update :more-options not))
toggle-japanese-layout
(mf/use-fn
#(swap! menu-state* update :japanese-layout not))
toggle-token-dropdown
(mf/use-fn
#(swap! token-dropdown-open* not))
@ -555,94 +568,104 @@
(when token-dropdown-open?
(ts/schedule 0 #(some-> (mf/ref-val dropdown-ref) dom/focus!))))
[:section {:class (stl/css :element-set)
:aria-label (tr "workspace.options.text-options.text-section")}
[:div {:class (stl/css :element-title)}
[:> title-bar* {:collapsable true
:collapsed (not main-menu-open?)
:on-collapsed toggle-main-menu
:title label
:class (stl/css :title-spacing-text)}
[:*
(when (and token-typography-row-enabled? (some? (resolve-delay typography-tokens)) (not typography))
[:> icon-button* {:variant "ghost"
:aria-label (tr "ds.inputs.numeric-input.open-token-list-dropdown")
:on-click toggle-token-dropdown
:tooltip-placement "top-left"
:icon i/tokens}])
(when (and (not typography) (not multiple?) (not applied-token-name))
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.convert-to-typography")
:on-click on-convert-to-typography
:tooltip-placement "top-left"
:icon i/add}])]]
(when (and token-typography-row-enabled? token-dropdown-open?)
[:> searchable-options-dropdown* {:on-click on-option-click
:id listbox-id
:options (resolve-delay dropdown-options)
:selected selected-token-id
:align "right"
:placeholder (tr "workspace.tokens.search-by-token")
:ref set-option-ref}])]
(when main-menu-open?
[:div {:class (stl/css :element-content)}
(cond
(and token-typography-row-enabled? (= :multiple current-token-name) (= typography-id :multiple))
[:div {:class (stl/css :multiple-typography)}
[:span {:class (stl/css :multiple-text)}
(tr "workspace.libraries.text.mixed-tokens-and-assets")]]
(and token-typography-row-enabled? (= :multiple current-token-name))
[:div {:class (stl/css :multiple-typography)}
[:span {:class (stl/css :multiple-text)}
(tr "workspace.libraries.text.mixed-tokens")]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.libraries.text.multiple-token-tooltip")
[:*
[:section {:class (stl/css :element-set)
:aria-label (tr "workspace.options.text-options.text-section")}
[:div {:class (stl/css :element-title)}
[:> title-bar* {:collapsable true
:collapsed (not main-menu-open?)
:on-collapsed toggle-main-menu
:title label
:class (stl/css :title-spacing-text)}
[:*
(when (and token-typography-row-enabled? (some? (resolve-delay typography-tokens)) (not typography))
[:> icon-button* {:variant "ghost"
:aria-label (tr "ds.inputs.numeric-input.open-token-list-dropdown")
:on-click toggle-token-dropdown
:tooltip-placement "top-left"
:on-click handle-detach-all-tokens
:icon i/detach}]]
(and token-typography-row-enabled? current-token-name)
[:> token-typography-row* {:token-name current-token-name
:detach-token detach-token
:active-tokens (resolve-delay typography-tokens)}]
(= typography-id :multiple)
[:div {:class (stl/css :multiple-typography)}
[:span {:class (stl/css :multiple-text)}
(tr "workspace.libraries.text.mixed-typography")]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.libraries.text.multiple-assets-tooltip")
:on-click handle-detach-typography
:icon i/tokens}])
(when (and (not typography) (not multiple?) (not applied-token-name))
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.convert-to-typography")
:on-click on-convert-to-typography
:tooltip-placement "top-left"
:icon i/detach}]]
:icon i/add}])]]
(when (and token-typography-row-enabled? token-dropdown-open?)
[:> searchable-options-dropdown* {:on-click on-option-click
:id listbox-id
:options (resolve-delay dropdown-options)
:selected selected-token-id
:align "right"
:placeholder (tr "workspace.tokens.search-by-token")
:ref set-option-ref}])]
typography
[:> typography-entry* {:file-id typography-file-id
:typography typography
:is-local (= typography-file-id file-id)
:on-detach handle-detach-typography
:on-change handle-change-typography}]
(when main-menu-open?
[:div {:class (stl/css :element-content)}
(cond
(and token-typography-row-enabled? (= :multiple current-token-name) (= typography-id :multiple))
[:div {:class (stl/css :multiple-typography)}
[:span {:class (stl/css :multiple-text)}
(tr "workspace.libraries.text.mixed-tokens-and-assets")]]
(and token-typography-row-enabled? (= :multiple current-token-name))
[:div {:class (stl/css :multiple-typography)}
[:span {:class (stl/css :multiple-text)}
(tr "workspace.libraries.text.mixed-tokens")]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.libraries.text.multiple-token-tooltip")
:tooltip-placement "top-left"
:on-click handle-detach-all-tokens
:icon i/detach}]]
(and token-typography-row-enabled? current-token-name)
[:> token-typography-row* {:token-name current-token-name
:detach-token detach-token
:active-tokens (resolve-delay typography-tokens)}]
:else
[:> text-options* common-props])
(= typography-id :multiple)
[:div {:class (stl/css :multiple-typography)}
[:span {:class (stl/css :multiple-text)}
(tr "workspace.libraries.text.mixed-typography")]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.libraries.text.multiple-assets-tooltip")
:on-click handle-detach-typography
:tooltip-placement "top-left"
:icon i/detach}]]
[:div {:class (stl/css :text-align-options)}
[:> text-align-options* common-props]
[:> grow-options* common-props]
[:> icon-button* {:variant "ghost"
:aria-label (tr "labels.options")
:data-testid "text-align-options-button"
:on-click toggle-more-options
:icon i/menu}]]
typography
[:> typography-entry* {:file-id typography-file-id
:typography typography
:is-local (= typography-file-id file-id)
:on-detach handle-detach-typography
:on-change handle-change-typography}]
(when more-options-open?
[:*
:else
[:> text-options* common-props])
[:div {:class (stl/css :text-align-options)}
[:> text-align-options* common-props]
[:> grow-options* common-props]
[:> icon-button* {:variant "ghost"
:aria-label (tr "labels.options")
:data-testid "text-align-options-button"
:on-click toggle-more-options
:icon i/menu}]]
(when more-options-open?
[:div {:class (stl/css :text-decoration-options)}
[:> vertical-align* common-props]
[:> text-decoration-options* (mf/spread-props common-props {:token-applied current-token-name})]
[:> text-direction-options* common-props]]
[:> text-direction-options* common-props]])])]
(when japanese-layout-config-enabled?
[:section {:class (stl/css :element-set)
:aria-label (tr "workspace.options.text-options.japanese-layout")}
[:div {:class (stl/css :element-title)}
[:> title-bar* {:collapsable true
:collapsed (not japanese-layout-open?)
:on-collapsed toggle-japanese-layout
:title (tr "workspace.options.text-options.japanese-layout")
:class (stl/css :title-spacing-text)}]]
(when japanese-layout-open?
[:div {:class (stl/css :element-content)}
[:> tjl/japanese-layout-options* japanese-layout-props]])])]))

View File

@ -8,7 +8,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.main.features :as features]
[app.common.types.file :as ctf]
[app.main.ui.components.title-bar :refer [title-bar*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.input :refer [input*]]
@ -71,6 +71,13 @@
(#{"horizontal-tb" "vertical-rl"} writing-mode) true
:else false)))
(defn japanese-layout-config-enabled?
"Japanese layout controls are available when enabled for the current file or
globally in the user's profile."
[file-data profile]
(or (ctf/japanese-layout-enabled? file-data)
(true? (get-in profile [:props :japanese-layout-all-files]))))
(defn japanese-layout-toggle-attrs
"Attrs emitted by the Japanese layout switch. A nil writing mode removes the
persisted paragraph attribute, restoring the normal horizontal default."
@ -136,7 +143,7 @@
[:div {:class (stl/css :japanese-layout-toggle)}
[:> switch* {:default-checked enabled
:label (tr "workspace.options.text-options.japanese-layout")
:label (tr "workspace.options.text-options.japanese-layout-enable-for-text")
:on-change handle-change}]]))
(mf/defc writing-mode-options*
@ -330,6 +337,26 @@
:on-change on-change
:on-blur on-blur}))
(mf/defc ruby-hidden-option*
[{:keys [values on-change on-blur]}]
(let [value (:ruby-hidden values)
enabled? (cond
(mixed-span-value? value) nil
(true? value) true
:else false)
label (tr "workspace.options.text-options.ruby-hidden")
handle-change
(mf/use-fn
(mf/deps on-change on-blur)
(fn [checked?]
(on-change {:ruby-hidden checked?})
(when (some? on-blur) (on-blur))))]
[:div {:class (stl/css :ruby-hidden-option :japanese-select-option)}
[:span {:class (stl/css :japanese-option-label)} label]
[:> switch* {:default-checked enabled?
:aria-label label
:on-change handle-change}]]))
(mf/defc ruby-customization-options*
[{:keys [values on-change on-blur]}]
(let [common-props (ruby-common-props values on-change on-blur)
@ -345,6 +372,7 @@
side-options [{:id "over" :label (tr "workspace.options.text-options.ruby-side-over")}
{:id "under" :label (tr "workspace.options.text-options.ruby-side-under")}]]
[:div {:class (stl/css :japanese-layout-controls)}
[:> ruby-hidden-option* common-props]
[:> ruby-select-option* (mf/spread-props common-props
{:attr :ruby-size
:default-value "half"
@ -530,13 +558,10 @@
:options options
:on-change handle-change}]]))
(mf/defc japanese-layout-options*
[{:keys [ids values ruby-values text-selection-active
on-change on-ruby-presentation-change on-blur]}]
(let [enabled? (features/use-feature "japanese-layout/v1")
selection-key (hash ids)
(let [selection-key (hash ids)
previous-selection-key-ref (mf/use-ref selection-key)
active* (mf/use-state #(japanese-layout-enabled? values))
active? (deref active*)
@ -564,28 +589,26 @@
selection-changed?))
(mf/set-ref-val! previous-selection-key-ref selection-key)))
(when ^boolean enabled?
[:div {:class (stl/css :japanese-layout-options)}
[:> japanese-layout-toggle* toggle-props]
(when ^boolean active?
[:div {:class (stl/css :japanese-layout-controls)}
[:div {:class (stl/css :japanese-icon-options)}
[:> writing-mode-options* common-props]
(when ^boolean vertical?
[:*
[:> text-orientation-options* common-props]
[:> text-combine-upright-options*
(mf/spread-props common-props
{:text-selection-active text-selection-active})]])
(when ^boolean text-selection-active
[:> warichu-options* common-props])]
(when ^boolean vertical?
[:> text-combine-upright-count-options* common-props])
[:> font-features-options* common-props]
(when ^boolean text-selection-active
[:> text-emphasis-options* common-props])
[:> annotation-clearance-options* common-props]
(if text-selection-active
[:> ruby-advanced-options* common-props]
[:> ruby-presentation-options* ruby-presentation-props])])])))
[:div {:class (stl/css :japanese-layout-options)}
[:> japanese-layout-toggle* toggle-props]
(when ^boolean active?
[:div {:class (stl/css :japanese-layout-controls)}
[:div {:class (stl/css :japanese-icon-options)}
[:> writing-mode-options* common-props]
(when ^boolean vertical?
[:*
[:> text-orientation-options* common-props]
[:> text-combine-upright-options*
(mf/spread-props common-props
{:text-selection-active text-selection-active})]])
(when ^boolean text-selection-active
[:> warichu-options* common-props])]
(when ^boolean vertical?
[:> text-combine-upright-count-options* common-props])
[:> font-features-options* common-props]
(when ^boolean text-selection-active
[:> text-emphasis-options* common-props])
[:> annotation-clearance-options* common-props]
(if text-selection-active
[:> ruby-advanced-options* common-props]
[:> ruby-presentation-options* ruby-presentation-props])])]))

View File

@ -55,8 +55,7 @@
}
.japanese-layout-options {
padding-block-start: var(--sp-xs);
border-block-start: $b-1 solid var(--color-background-quaternary);
padding-block: var(--sp-xs);
}
.japanese-layout-toggle {
@ -65,6 +64,10 @@
min-block-size: $sz-32;
}
.ruby-hidden-option {
min-block-size: $sz-32;
}
.japanese-icon-options {
display: flex;
flex-wrap: wrap;

View File

@ -91,7 +91,6 @@
(def ^:private snapshot-capture-debounce-ms 250)
(defn initialized?
"True when the WASM render context is ready to receive design-state
operations. Use it to skip WASM work during transient states (e.g. while
@ -99,7 +98,6 @@
[]
(and wasm/context-initialized? (not @wasm/context-lost?)))
(defn set-transition-image-from-background!
"Sets `transition-image*` to a data URL representing a solid background color."
[background]
@ -511,7 +509,6 @@
(when (initialized?)
(h/call wasm/internal-module "_render_preview")))
(defonce pending-render (atom false))
(defonce shapes-loading? (atom false))
(defonce deferred-render? (atom false))
@ -735,7 +732,6 @@
[string]
(+ (count string) 1))
(defn- get-texture-id-for-gl-object
"Registers a WebGL texture with Emscripten's GL object system and returns its ID"
[texture]
@ -1666,7 +1662,6 @@
noop-fn)))))))]
(process-next-chunk 0 [] [] []))))))
;; This is a version of process-pending that doesn't have sideffects
;; with like request render or update layout.
(defn- process-pending-no-sideffects
@ -1859,7 +1854,6 @@
heapu32 (mem/get-heap-u32)
heapf32 (mem/get-heap-f32)]
(reduce (fn [offset {:keys [type parent id index value]}]
(-> offset
(mem.h32/write-u32 heapu32 (sr/translate-structure-modifier-type type))
@ -2537,7 +2531,11 @@
[element _start-pos _end-pos]
(let [text (:text element)
ruby (:ruby element)]
(when (and (string? text) (seq text) (string? ruby) (seq ruby))
(when (and (not (true? (:ruby-hidden element)))
(string? text)
(seq text)
(string? ruby)
(seq ruby))
ruby)))
(defn- ruby-strip-entry
@ -2546,7 +2544,8 @@
gutter placement the canvas paints."
[element {:keys [start-pos end-pos x y width height]}]
(let [ruby (get element :ruby)]
(when (string? ruby)
(when (and (not (true? (:ruby-hidden element)))
(string? ruby))
(let [text (subs ruby
(min start-pos (count ruby))
(min end-pos (count ruby)))
@ -2655,7 +2654,10 @@
(when (= "auto" clearance) clearance))
:annotation-has-ruby
(let [ruby (get element :ruby)]
(when (and (string? ruby) (seq ruby)) true))
(when (and (not (true? (:ruby-hidden element)))
(string? ruby)
(seq ruby))
true))
;; Horizontal position data has no separate ruby
;; strip, so carry the annotation on the base entry
;; for static SVG export. Vertical ruby has its own

View File

@ -50,7 +50,6 @@
padding-fills (max 0 (- MAX-TEXT-FILLS (count fills)))]
(+ new-ofset (* padding-fills types.fills.impl/FILL-U8-SIZE))))
(defn- write-paragraph
[offset dview paragraph]
(let [text-align (sr/translate-text-align (get paragraph :text-align))
@ -100,7 +99,9 @@
text-buffer (encode-text (get span :text ""))
text-length (mem/size text-buffer)
ruby-buffer (encode-text (get span :ruby ""))
ruby-buffer (encode-text (if (true? (:ruby-hidden span))
""
(get span :ruby "")))
ruby-length (mem/size ruby-buffer)
fills (take MAX-TEXT-FILLS (get span :fills []))
@ -206,7 +207,10 @@
text-buffer (encode-text text)
text-size (mem/size text-buffer)
ruby-text (apply str (map #(get % :ruby "") normalized-spans))
ruby-text (apply str (map #(if (true? (:ruby-hidden %))
""
(get % :ruby ""))
normalized-spans))
ruby-buffer (encode-text ruby-text)
ruby-size (mem/size ruby-buffer)

View File

@ -648,6 +648,7 @@
{:text-combine-upright "none"
:text-emphasis "none"
:ruby ""
:ruby-hidden false
:ruby-size "half"
:ruby-align "space-around"
:ruby-overhang "auto"

View File

@ -23,6 +23,12 @@
(def mapping
{:fills [encode decode]
:ruby [identity #(when-not (= "" %) %)]
:ruby-hidden [(fn [value] (when (some? value) (str value)))
(fn [value]
(case value
"true" true
"false" false
nil))]
:ruby-size [identity #(when-not (= "" %) %)]
:ruby-align [identity #(when-not (= "" %) %)]
:ruby-overhang [identity #(when-not (= "" %) %)]

View File

@ -46,6 +46,7 @@
(t/is (= {:text-combine-upright "none"
:text-emphasis "filled-dot"
:ruby "にち"
:ruby-hidden false
:ruby-size "half"
:ruby-align "space-around"
:ruby-overhang "auto"
@ -58,6 +59,7 @@
(t/is (= {:text-combine-upright "none"
:text-emphasis "none"
:ruby ""
:ruby-hidden false
:ruby-size "half"
:ruby-align "space-around"
:ruby-overhang "auto"
@ -74,17 +76,20 @@
:text-combine-upright "digits2"
:text-emphasis "filled-dot"
:ruby "へいせい"
:ruby-hidden true
:ruby-size "third"
:ruby-align "center"
:ruby-overhang "none"
:ruby-side "under"
:annotation-clearance "auto"}
{:text "年"
:ruby-hidden false
:warichu "warichu"
:font-features "vpal"}])]
(t/is (= {:text-combine-upright :multiple
:text-emphasis :multiple
:ruby :multiple
:ruby-hidden :multiple
:ruby-size :multiple
:ruby-align :multiple
:ruby-overhang :multiple
@ -181,6 +186,27 @@
(t/is (= [2 3 1 1 0 0]
(mapv #(.getUint8 dview %) (range 10 16))))))
(t/deftest write-spans-omits-hidden-ruby-bytes
(let [buffer (js/ArrayBuffer. 256)
dview (js/DataView. buffer)
paragraph {:font-size "16"
:font-weight "400"
:line-height "1"}]
(write-spans 0 dview [{:text "日"
:ruby "にち"
:ruby-hidden true
:font-size "16"
:font-weight "400"}]
paragraph)
(t/is (= 0 (.getInt32 dview 72 true)))
(write-spans 0 dview [{:text "日"
:ruby "にち"
:ruby-hidden false
:font-size "16"
:font-weight "400"}]
paragraph)
(t/is (pos? (.getInt32 dview 72 true)))))
(t/deftest write-spans-serializes-counted-digits-tcy
(let [sentinel (js-obj)
previous (if (.hasOwnProperty wasm/serializers "text-combine-upright")

View File

@ -9,6 +9,7 @@
["react-dom/server" :as rds]
[app.main.data.workspace.texts :as dwt]
[app.main.ui.ds.controls.select :as select]
[app.main.ui.shapes.text.html-text :as html-text]
[app.main.ui.shapes.text.styles :as text-styles]
[app.main.ui.workspace.sidebar.options.menus.text-japanese-layout :as tjl]
[app.util.text.content.styles :as content-styles]
@ -20,6 +21,7 @@
[:text-combine-upright
:text-emphasis
:ruby
:ruby-hidden
:ruby-size
:ruby-align
:ruby-overhang
@ -34,6 +36,18 @@
(def ^:private with-mixed-span-option @#'tjl/with-mixed-span-option)
(def ^:private ruby-common-props @#'tjl/ruby-common-props)
(t/deftest japanese-layout-controls-follow-file-or-profile-configuration
(t/is (false? (tjl/japanese-layout-config-enabled? {} {})))
(t/is (true? (tjl/japanese-layout-config-enabled?
{:options {:japanese-layout true}}
{})))
(t/is (true? (tjl/japanese-layout-config-enabled?
{}
{:props {:japanese-layout-all-files true}})))
(t/is (true? (tjl/japanese-layout-config-enabled?
{:options {:japanese-layout true}}
{:props {:japanese-layout-all-files false}}))))
(t/deftest text-emphasis-select-reports-and-restores-canonical-values
(let [options (tjl/text-emphasis-options identity)
reported-id (:id (select/get-option options "filled-dot"))]
@ -71,6 +85,27 @@
(t/is (str/includes? markup "aria-expanded=\"false\""))
(t/is (str/includes? markup "workspace.options.text-options.ruby-advanced-options"))))
(t/deftest advanced-furigana-options-include-hide-toggle
(let [markup (rds/renderToStaticMarkup
(mf/element tjl/ruby-customization-options*
#js {:values {:ruby-hidden true}
:on-change identity
:on-blur identity}))]
(t/is (str/includes? markup "workspace.options.text-options.ruby-hidden"))
(t/is (str/includes? markup "role=\"switch\""))
(t/is (str/includes? markup "aria-checked=\"true\""))))
(t/deftest hidden-furigana-renders-base-text-without-ruby-layout
(let [markup (rds/renderToStaticMarkup
(mf/element html-text/render-text*
#js {:node {:text "日"
:ruby "にち"
:ruby-hidden true}
:parent {}
:shape {}}))]
(t/is (str/includes? markup ">日</span>"))
(t/is (not (str/includes? markup "<ruby")))))
(t/deftest advanced-furigana-options-preserve-change-callbacks
(let [on-change identity
on-blur identity
@ -88,17 +123,23 @@
:children
[{:text "日"
:ruby "にち"
:ruby-hidden false
:ruby-size "half"}
{:text "本"
:ruby ""
:ruby-hidden false
:ruby-size "quarter"}]}]}]}}
values (dwt/current-ruby-values {:shape shape
:attrs dwt/ruby-presentation-attrs})
updated (dwt/update-ruby-presentation-attrs shape {:ruby-size "third"})
updated (dwt/update-ruby-presentation-attrs
shape {:ruby-hidden true :ruby-size "third"})
spans (get-in updated [:content :children 0 :children 0 :children])]
(t/is (= "half" (:ruby-size values)))
(t/is (false? (:ruby-hidden values)))
(t/is (= "third" (:ruby-size (first spans))))
(t/is (= "quarter" (:ruby-size (second spans))))))
(t/is (true? (:ruby-hidden (first spans))))
(t/is (= "quarter" (:ruby-size (second spans))))
(t/is (false? (:ruby-hidden (second spans))))))
(t/deftest mixed-japanese-span-values-have-distinct-control-states
(let [options (with-mixed-span-option
@ -122,6 +163,7 @@
:text-combine-upright "digits2"
:text-emphasis "filled-dot"
:ruby "にち"
:ruby-hidden true
:ruby-size "third"
:ruby-align "center"
:ruby-overhang "none"
@ -133,6 +175,7 @@
:text-combine-upright "none"
:text-emphasis "none"
:ruby ""
:ruby-hidden false
:ruby-size "half"
:ruby-align "space-around"
:ruby-overhang "auto"
@ -176,6 +219,16 @@
(content-styles/styles->attrs
{"text-combine-upright" "digits 3"}))))
(t/deftest hidden-furigana-round-trips-through-editor-styles
(t/is (= "true"
(unchecked-get
(content-styles/attrs->styles {:ruby-hidden true})
"--ruby-hidden")))
(t/is (= {:ruby-hidden false}
(content-styles/styles->attrs {"--ruby-hidden" "false"})))
(t/is (= {:ruby-hidden nil}
(content-styles/styles->attrs {"--ruby-hidden" ""}))))
(t/deftest japanese-layout-is-explicitly-enabled-by-writing-mode
(t/is (false? (tjl/japanese-layout-enabled? {})))
(t/is (false? (tjl/japanese-layout-enabled? {:writing-mode nil})))

View File

@ -1320,6 +1320,18 @@ msgstr "Unpublish Library"
msgid "dashboard.update-settings"
msgstr "Update settings"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.japanese-layout.description"
msgstr "Makes Japanese text layout controls available in every file."
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.japanese-layout.enable-all-files"
msgstr "Enable Japanese text layout for all files"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.japanese-layout.title"
msgstr "Japanese text layout"
#: src/app/main/ui/dashboard/sidebar.cljs:1328
msgid "dashboard.upgrade-plan.no-limits"
msgstr "No limits on creativity"
@ -6724,6 +6736,10 @@ msgstr "Disable snap to pixel"
msgid "workspace.header.menu.disable-snap-ruler-guides"
msgstr "Disable snap to ruler guides"
#: src/app/main/ui/workspace/main_menu.cljs
msgid "workspace.header.menu.disable-japanese-layout"
msgstr "Disable Japanese text layout"
#: src/app/main/ui/workspace/main_menu.cljs:336
msgid "workspace.header.menu.disable-webgl"
msgstr "Disable WebGL rendering (beta)"
@ -6732,6 +6748,10 @@ msgstr "Disable WebGL rendering (beta)"
msgid "workspace.header.menu.enable-dynamic-alignment"
msgstr "Enable dynamic alignment"
#: src/app/main/ui/workspace/main_menu.cljs
msgid "workspace.header.menu.enable-japanese-layout"
msgstr "Enable Japanese text layout"
#: src/app/main/ui/workspace/main_menu.cljs:250
msgid "workspace.header.menu.enable-scale-content"
msgstr "Enable proportional scale"
@ -8522,6 +8542,10 @@ msgstr "Normal"
msgid "workspace.options.text-options.japanese-layout"
msgstr "Japanese text layout"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.japanese-layout-enable-for-text"
msgstr "Enable for this text"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-all"
msgstr "Tate-chu-yoko"
@ -8626,6 +8650,10 @@ msgstr "Furigana"
msgid "workspace.options.text-options.ruby-advanced-options"
msgstr "Advanced furigana options"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.ruby-hidden"
msgstr "Hide furigana"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-size"
msgstr "Ruby size"
@ -10301,4 +10329,4 @@ msgid "workspace.viewport.click-to-close-path"
msgstr "Click to close the path"
msgid "notifications.invitation-canceled"
msgstr "This invitation is no longer available."
msgstr "This invitation is no longer available."

View File

@ -992,6 +992,10 @@ msgstr "通常"
msgid "workspace.options.text-options.japanese-layout"
msgstr "日本語組版"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.japanese-layout-enable-for-text"
msgstr "このテキストで有効にする"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-all"
msgstr "縦中横"
@ -1096,6 +1100,10 @@ msgstr "ふりがな"
msgid "workspace.options.text-options.ruby-advanced-options"
msgstr "ふりがなの詳細設定"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.ruby-hidden"
msgstr "ふりがなを非表示"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-size"
msgstr "ルビの大きさ"
@ -1171,3 +1179,23 @@ msgstr "横書き"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.writing-mode-vertical"
msgstr "縦書き(右から左)"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.japanese-layout.description"
msgstr "すべてのファイルで日本語組版コントロールを利用できるようにします。"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.japanese-layout.enable-all-files"
msgstr "すべてのファイルで日本語組版を有効にする"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.japanese-layout.title"
msgstr "日本語組版"
#: src/app/main/ui/workspace/main_menu.cljs
msgid "workspace.header.menu.disable-japanese-layout"
msgstr "日本語組版を無効にする"
#: src/app/main/ui/workspace/main_menu.cljs
msgid "workspace.header.menu.enable-japanese-layout"
msgstr "日本語組版を有効にする"