mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 05:50:35 +00:00
✨ Add language-specific CJK layout controls
Add independent Japanese, Chinese and Korean file switches and all-files preferences under the shared vertical-text-layout feature flag. Keep control availability separate from saved paragraph profiles and rendering. Expose explicit language profiles in separate sidebar sections. Preserve mixed values, report the active paragraph profile at the caret, and keep shared writing-mode controls consistent without radio group collisions. Validation: common JVM/JS and frontend suites, backend profile tests, exporter tests and lint pass. Existing runner formatting and Spanish translation QA issues remain unchanged. AI-assisted-by: GPT-6
This commit is contained in:
parent
0dd12b09e9
commit
1ca964e438
@ -63,6 +63,8 @@
|
||||
[:plugins {:optional true} ctp/schema:plugin-registry]
|
||||
[:renderer {:optional true} [::sm/one-of #{:svg :wasm}]]
|
||||
[:japanese-layout-all-files {:optional true} ::sm/boolean]
|
||||
[:chinese-layout-all-files {:optional true} ::sm/boolean]
|
||||
[:korean-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]
|
||||
|
||||
@ -179,6 +179,29 @@
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (true? (get-in out [:result :props :japanese-layout-all-files]))))))
|
||||
|
||||
(t/deftest update-profile-language-layout-settings
|
||||
(let [profile (th/create-profile* 1)
|
||||
props {:japanese-layout-all-files true
|
||||
:chinese-layout-all-files true
|
||||
:korean-layout-all-files false}
|
||||
params {::th/type :update-profile-props
|
||||
::rpc/profile-id (:id profile)
|
||||
:props props}
|
||||
out (th/command! params)]
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (= props (select-keys (:result out) (keys props))))
|
||||
(let [out (th/command! {::th/type :get-profile
|
||||
::rpc/profile-id (:id profile)})]
|
||||
(t/is (= props (select-keys (get-in out [:result :props]) (keys props)))))
|
||||
(let [out (th/command! (assoc params :props {:chinese-layout-all-files false}))]
|
||||
(t/is (nil? (:error out))))
|
||||
(let [out (th/command! {::th/type :get-profile
|
||||
::rpc/profile-id (:id profile)})]
|
||||
(t/is (= {:japanese-layout-all-files true
|
||||
:chinese-layout-all-files false
|
||||
:korean-layout-all-files false}
|
||||
(select-keys (get-in out [:result :props]) (keys props)))))))
|
||||
|
||||
(t/deftest profile-deletion-1
|
||||
(let [prof (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:profile-id (:id prof)
|
||||
|
||||
@ -42,6 +42,11 @@
|
||||
:multiple)
|
||||
(get obj attr ::unset)))
|
||||
|
||||
;; An absent language is the plain profile, so it must participate
|
||||
;; when reporting mixed text selections.
|
||||
(#{:text-language :korean-wrap} attr)
|
||||
(get obj attr)
|
||||
|
||||
:else
|
||||
(get obj attr ::unset))))
|
||||
|
||||
|
||||
@ -57,6 +57,7 @@
|
||||
"text-editor/v2"
|
||||
"text-editor-wasm/v1"
|
||||
"text-editor-wasm/v1-html-paste"
|
||||
"vertical-text-layout"
|
||||
"render-wasm/v1"
|
||||
"variants/v1"})
|
||||
|
||||
@ -84,6 +85,7 @@
|
||||
"text-editor-wasm/v1"
|
||||
"text-editor-wasm/v1-html-paste"
|
||||
"tokens/numeric-input"
|
||||
"vertical-text-layout"
|
||||
"render-wasm/v1"})
|
||||
|
||||
;; Features that are mainly backend only or there are a proper
|
||||
@ -134,6 +136,7 @@
|
||||
:feature-text-editor-v2-html-paste "text-editor/v2-html-paste"
|
||||
:feature-text-editor-wasm "text-editor-wasm/v1"
|
||||
:feature-text-editor-wasm-html-paste "text-editor-wasm/v1-html-paste"
|
||||
:vertical-text-layout "vertical-text-layout"
|
||||
:feature-render-wasm "render-wasm/v1"
|
||||
:feature-variants "variants/v1"
|
||||
:feature-token-input "tokens/numeric-input"
|
||||
|
||||
@ -440,9 +440,10 @@
|
||||
[:file-id ::sm/uuid]
|
||||
[:library-id [:maybe ::sm/uuid]]]]
|
||||
|
||||
[:set-japanese-layout
|
||||
[:map {:title "SetJapaneseLayout"}
|
||||
[:type [:= :set-japanese-layout]]
|
||||
[:set-text-layout
|
||||
[:map {:title "SetTextLayout"}
|
||||
[:type [:= :set-text-layout]]
|
||||
[:language [:enum :japanese :chinese :korean]]
|
||||
[:enabled ::sm/boolean]]]])
|
||||
|
||||
(def schema:changes
|
||||
@ -1097,9 +1098,9 @@
|
||||
[data {:keys [library-id]}]
|
||||
(cfo/set-tokens-source data library-id))
|
||||
|
||||
(defmethod process-change :set-japanese-layout
|
||||
[data {:keys [enabled]}]
|
||||
(ctf/set-japanese-layout-enabled data enabled))
|
||||
(defmethod process-change :set-text-layout
|
||||
[data {:keys [language enabled]}]
|
||||
(ctf/set-text-layout-enabled data language enabled))
|
||||
|
||||
;; === Operations
|
||||
|
||||
|
||||
@ -1150,15 +1150,17 @@
|
||||
:library-id prev-val})
|
||||
(apply-changes-local))))
|
||||
|
||||
(defn set-japanese-layout
|
||||
[changes enabled?]
|
||||
(defn set-text-layout
|
||||
[changes language enabled?]
|
||||
(assert-file-data! changes)
|
||||
(let [file-data (::file-data (meta changes))
|
||||
previous-enabled (ctf/japanese-layout-enabled? file-data)]
|
||||
(let [file-data (::file-data (meta changes))
|
||||
previous-enabled (ctf/text-layout-enabled? file-data language)]
|
||||
(-> changes
|
||||
(update :redo-changes conj {:type :set-japanese-layout
|
||||
(update :redo-changes conj {:type :set-text-layout
|
||||
:language language
|
||||
:enabled enabled?})
|
||||
(update :undo-changes conj {:type :set-japanese-layout
|
||||
(update :undo-changes conj {:type :set-text-layout
|
||||
:language language
|
||||
:enabled previous-enabled})
|
||||
(apply-changes-local))))
|
||||
|
||||
|
||||
@ -156,6 +156,7 @@
|
||||
;; Show WASM renderer info label (hidden by default).
|
||||
:render-wasm-info
|
||||
:render-switch
|
||||
:vertical-text-layout
|
||||
:hide-release-modal
|
||||
:subscriptions
|
||||
:inspect-styles
|
||||
|
||||
@ -78,7 +78,9 @@
|
||||
[:map {:title "FileOptions"}
|
||||
[:components-v2 {:optional true} ::sm/boolean]
|
||||
[:base-font-size {:optional true} :string]
|
||||
[:japanese-layout {:optional true} ::sm/boolean]])
|
||||
[:japanese-layout {:optional true} ::sm/boolean]
|
||||
[:chinese-layout {:optional true} ::sm/boolean]
|
||||
[:korean-layout {:optional true} ::sm/boolean]])
|
||||
|
||||
(def schema:data
|
||||
[:map {:title "FileData"}
|
||||
@ -1256,17 +1258,32 @@
|
||||
(assoc-in file-data [:options :base-font-size] base-font-size))
|
||||
|
||||
|
||||
;; Japanese text layout
|
||||
;; Text layout controls
|
||||
|
||||
(defn japanese-layout-enabled?
|
||||
[file-data]
|
||||
(true? (get-in file-data [:options :japanese-layout])))
|
||||
(defn text-layout-option
|
||||
[language]
|
||||
(case language
|
||||
:japanese :japanese-layout
|
||||
:chinese :chinese-layout
|
||||
:korean :korean-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)))
|
||||
(defn text-layout-preference
|
||||
[language]
|
||||
(case language
|
||||
:japanese :japanese-layout-all-files
|
||||
:chinese :chinese-layout-all-files
|
||||
:korean :korean-layout-all-files))
|
||||
|
||||
(defn text-layout-enabled?
|
||||
[file-data language]
|
||||
(true? (get-in file-data [:options (text-layout-option language)])))
|
||||
|
||||
(defn set-text-layout-enabled
|
||||
[file-data language enabled?]
|
||||
(let [option (text-layout-option language)]
|
||||
(if enabled?
|
||||
(assoc-in file-data [:options option] true)
|
||||
(d/update-in-when file-data [:options] dissoc option))))
|
||||
|
||||
|
||||
;; Ref Chains
|
||||
|
||||
@ -148,4 +148,12 @@
|
||||
{:x -153}]
|
||||
result (attrs/get-attrs-multi objs [:x])]
|
||||
(t/is (= {:x :multiple} result)
|
||||
"Different positions should be :multiple"))))
|
||||
"Different positions should be :multiple"))))
|
||||
(t/deftest unset-language-profile-counts-as-a-distinct-value
|
||||
(t/is (= {:text-language :multiple :korean-wrap :multiple}
|
||||
(attrs/get-attrs-multi [{} {:text-language "ko" :korean-wrap "spaces-only"}]
|
||||
[:text-language :korean-wrap])))
|
||||
(t/is (= {:text-language :multiple}
|
||||
(attrs/get-attrs-multi [{:text-language "ko"} {}] [:text-language])))
|
||||
(t/is (= {:text-language nil :korean-wrap nil}
|
||||
(attrs/get-attrs-multi [{} {}] [:text-language :korean-wrap]))))
|
||||
|
||||
@ -251,25 +251,39 @@
|
||||
"The parent must use the updated bounds of both children")
|
||||
(assert-idempotent data changes))))))
|
||||
|
||||
(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 language-layout-changes-are-independent
|
||||
(let [data (assoc (make-file-data (uuid/custom 2 2) (uuid/custom 1 1))
|
||||
:options {:japanese-layout true :base-font-size "16"})]
|
||||
(doseq [language [:japanese :chinese :korean]]
|
||||
(let [change {:type :set-text-layout :language language :enabled true}
|
||||
enabled (ch/process-changes data [change])
|
||||
disabled (ch/process-changes enabled [(assoc change :enabled false)])
|
||||
option (case language
|
||||
:japanese :japanese-layout
|
||||
:chinese :chinese-layout
|
||||
:korean :korean-layout)]
|
||||
(t/is (ch/valid-change? change))
|
||||
(t/is (true? (get-in enabled [:options option])))
|
||||
(t/is (not (contains? (:options disabled) option)))
|
||||
(t/is (= (dissoc (:options data) option) (:options disabled)))
|
||||
(t/is (= (dissoc data :options) (dissoc enabled :options))))))
|
||||
(t/is (not (ch/valid-change? {:type :set-text-layout :language :english
|
||||
:enabled true}))))
|
||||
|
||||
(t/deftest language-layout-switches-round-trip-through-undo
|
||||
(let [data (assoc (make-file-data (uuid/custom 2 2) (uuid/custom 1 1))
|
||||
:options {:japanese-layout true :base-font-size "16"})]
|
||||
(doseq [language [:japanese :chinese :korean]
|
||||
enabled? [true false]]
|
||||
(let [changes (-> (pcb/empty-changes)
|
||||
(pcb/with-file-data data)
|
||||
(pcb/set-text-layout language enabled?))
|
||||
edited (ch/process-changes data (:redo-changes changes))
|
||||
restored (ch/process-changes edited (:undo-changes changes))]
|
||||
(t/is (= enabled? (ctf/text-layout-enabled? edited language)))
|
||||
(t/is (= data restored))
|
||||
(t/is (sm/validate ctf/schema:options (json-roundtrip (:options edited))))
|
||||
(t/is (= (:options edited) (json-roundtrip (:options edited))))))))
|
||||
|
||||
(t/deftest add-obj
|
||||
(let [file-id (uuid/custom 2 2)
|
||||
|
||||
@ -679,15 +679,15 @@
|
||||
(dm/export layout/toggle-layout-flag)
|
||||
(dm/export layout/remove-layout-flag)
|
||||
|
||||
(defn set-japanese-layout
|
||||
[enabled?]
|
||||
(ptk/reify ::set-japanese-layout
|
||||
(defn set-text-layout
|
||||
[language enabled?]
|
||||
(ptk/reify ::set-text-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?))]
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/set-text-layout language enabled?))]
|
||||
(rx/of (dch/commit-changes changes))))))
|
||||
|
||||
;; --- Profile
|
||||
|
||||
@ -290,13 +290,21 @@
|
||||
"Currently working file data on workspace"
|
||||
(l/derived dsh/lookup-file-data st/state))
|
||||
|
||||
(def japanese-layout-config
|
||||
"Japanese layout switches: `:file?` for the workspace file and
|
||||
`:all-files?` from the profile."
|
||||
(l/derived (fn [state]
|
||||
{:file? (ctf/japanese-layout-enabled? (dsh/lookup-file-data state))
|
||||
:all-files? (true? (dm/get-in state [:profile :props :japanese-layout-all-files]))})
|
||||
st/state =))
|
||||
(def text-layout-config
|
||||
"Control availability by language; it never changes paragraph profiles."
|
||||
(l/derived
|
||||
(fn [state]
|
||||
(let [file-data (dsh/lookup-file-data state)
|
||||
props (dm/get-in state [:profile :props])
|
||||
feature? (contains? (:features state) "vertical-text-layout")
|
||||
config (fn [language]
|
||||
{:feature-enabled feature?
|
||||
:file? (ctf/text-layout-enabled? file-data language)
|
||||
:all-files? (true? (get props (ctf/text-layout-preference language)))})]
|
||||
{:japanese (config :japanese)
|
||||
:chinese (config :chinese)
|
||||
:korean (config :korean)}))
|
||||
st/state =))
|
||||
|
||||
(def workspace-file-colors
|
||||
(l/derived (fn [{:keys [id] :as data}]
|
||||
|
||||
@ -7,10 +7,12 @@
|
||||
(ns app.main.ui.settings.options
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.types.file :as ctf]
|
||||
[app.config :as cf]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
@ -115,35 +117,46 @@
|
||||
: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]}]
|
||||
(mf/defc language-layout-setting*
|
||||
[{:keys [language label enabled]}]
|
||||
(let [handle-change
|
||||
(mf/use-fn
|
||||
(mf/deps language)
|
||||
(fn [enabled?]
|
||||
(st/emit! (ev/event {::ev/name (if enabled?
|
||||
"enable-japanese-layout-all-files"
|
||||
"disable-japanese-layout-all-files")
|
||||
(st/emit! (ev/event {::ev/name (str (if enabled? "enable-" "disable-")
|
||||
(name language) "-layout-all-files")
|
||||
::ev/origin "settings"})
|
||||
(du/update-profile-props
|
||||
{:japanese-layout-all-files enabled?})
|
||||
{(ctf/text-layout-preference language) 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}]]]))
|
||||
[:> switch* {:label label
|
||||
:default-checked enabled
|
||||
:on-change handle-change}]))
|
||||
|
||||
(mf/defc language-layout-settings*
|
||||
[{:keys [config]}]
|
||||
[:section {:class (stl/css :text-layout-container)}
|
||||
[:> heading* {:class (stl/css :title) :level 2 :typography t/title-large}
|
||||
(tr "dashboard.text-layout.title")]
|
||||
[:> text* {:class (stl/css :description) :typography t/body-medium}
|
||||
(tr "dashboard.text-layout.description")]
|
||||
[:div {:class (stl/css :text-layout-form)}
|
||||
[:> language-layout-setting* {:language :japanese
|
||||
:label (tr "dashboard.japanese-layout.enable-all-files")
|
||||
:enabled (get-in config [:japanese :all-files?])}]
|
||||
[:> language-layout-setting* {:language :chinese
|
||||
:label (tr "dashboard.chinese-layout.enable-all-files")
|
||||
:enabled (get-in config [:chinese :all-files?])}]
|
||||
[:> language-layout-setting* {:language :korean
|
||||
:label (tr "dashboard.korean-layout.enable-all-files")
|
||||
:enabled (get-in config [:korean :all-files?])}]]])
|
||||
|
||||
(mf/defc options-page*
|
||||
[]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
renderer (or (-> profile :props :renderer) :svg)
|
||||
japanese-layout-enabled (:all-files? (mf/deref refs/japanese-layout-config))]
|
||||
layout-config (mf/deref refs/text-layout-config)
|
||||
layout-feature? (features/use-feature "vertical-text-layout")]
|
||||
(mf/use-effect
|
||||
#(dom/set-html-title (tr "title.settings.options")))
|
||||
|
||||
@ -157,6 +170,7 @@
|
||||
:id "options-section-title"}
|
||||
(tr "labels.settings")]
|
||||
[:> options-form*]]
|
||||
[:> japanese-layout-settings* {:enabled japanese-layout-enabled}]
|
||||
(when layout-feature?
|
||||
[:> language-layout-settings* {:config layout-config}])
|
||||
(when (contains? cf/flags :render-switch)
|
||||
[:> webgl-settings* {:renderer renderer}])]]))
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
/* Settings sections with standalone controls. */
|
||||
.webgl-container,
|
||||
.japanese-layout-container {
|
||||
.text-layout-container {
|
||||
display: grid;
|
||||
grid-auto-rows: auto;
|
||||
gap: var(--sp-s);
|
||||
@ -34,7 +34,10 @@
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.japanese-layout-form {
|
||||
.text-layout-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-s);
|
||||
padding-block-start: var(--sp-xs);
|
||||
}
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.text.writing-mode :as wm]
|
||||
[app.util.text.layout :as tl]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
@ -231,13 +231,28 @@
|
||||
[:span {:class (stl/css :feedback :item-name)}
|
||||
(tr "labels.give-feedback")]])]))
|
||||
|
||||
(mf/defc language-layout-menu-item*
|
||||
[{:keys [language config can-edit read-only label on-toggle]}]
|
||||
(let [toggle (mf/use-fn
|
||||
(mf/deps language on-toggle)
|
||||
#(on-toggle language %))]
|
||||
(when (tl/file-action-visible? config can-edit read-only)
|
||||
[:> dropdown-menu-item* {:on-click toggle
|
||||
:class (stl/css :base-menu-item :submenu-item)
|
||||
:on-key-down (fn [event]
|
||||
(when (kbd/enter? event) (toggle event)))
|
||||
:data-testid (str (name language) "-layout")
|
||||
:id (str "file-menu-" (name language) "-layout")}
|
||||
[:span {:class (stl/css :item-name)} label]])))
|
||||
|
||||
(mf/defc preferences-menu*
|
||||
{::mf/private true
|
||||
::mf/wrap [mf/memo]}
|
||||
[{:keys [japanese-layout-enabled japanese-layout-all-files layout profile toggle-flag
|
||||
on-close toggle-theme toggle-render toggle-japanese-layout]}]
|
||||
[{:keys [layout-config layout profile toggle-flag
|
||||
on-close toggle-theme toggle-render toggle-text-layout]}]
|
||||
(let [renderer (or (-> profile :props :renderer) :svg)
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
can-edit (:can-edit (mf/deref refs/permissions))
|
||||
|
||||
show-nudge-options
|
||||
(mf/use-fn
|
||||
@ -320,20 +335,24 @@
|
||||
:id "file-menu-nudge"}
|
||||
[:span {:class (stl/css :item-name)} (tr "modals.nudge-title")]]
|
||||
|
||||
(when (and (not read-only?)
|
||||
(not japanese-layout-all-files)
|
||||
(wm/vertical-layout-active?))
|
||||
[:> 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"))]])
|
||||
[:> language-layout-menu-item*
|
||||
{:language :japanese :config (:japanese layout-config)
|
||||
:can-edit can-edit :read-only read-only? :on-toggle toggle-text-layout
|
||||
:label (if (get-in layout-config [:japanese :file?])
|
||||
(tr "workspace.header.menu.disable-japanese-layout")
|
||||
(tr "workspace.header.menu.enable-japanese-layout"))}]
|
||||
[:> language-layout-menu-item*
|
||||
{:language :chinese :config (:chinese layout-config)
|
||||
:can-edit can-edit :read-only read-only? :on-toggle toggle-text-layout
|
||||
:label (if (get-in layout-config [:chinese :file?])
|
||||
(tr "workspace.header.menu.disable-chinese-layout")
|
||||
(tr "workspace.header.menu.enable-chinese-layout"))}]
|
||||
[:> language-layout-menu-item*
|
||||
{:language :korean :config (:korean layout-config)
|
||||
:can-edit can-edit :read-only read-only? :on-toggle toggle-text-layout
|
||||
:label (if (get-in layout-config [:korean :file?])
|
||||
(tr "workspace.header.menu.disable-korean-layout")
|
||||
(tr "workspace.header.menu.enable-korean-layout"))}]
|
||||
|
||||
[:> dropdown-menu-item* {:on-click toggle-theme
|
||||
:class (stl/css :base-menu-item :submenu-item)
|
||||
@ -904,8 +923,7 @@
|
||||
(mf/defc menu*
|
||||
[{:keys [layout file]}]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
japanese-layout (mf/deref refs/japanese-layout-config)
|
||||
japanese-layout-enabled (:file? japanese-layout)
|
||||
layout-config (mf/deref refs/text-layout-config)
|
||||
|
||||
show-menu* (mf/use-state false)
|
||||
show-menu? (deref show-menu*)
|
||||
@ -967,12 +985,12 @@
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (du/toggle-theme))))
|
||||
|
||||
toggle-japanese-layout
|
||||
toggle-text-layout
|
||||
(mf/use-fn
|
||||
(mf/deps japanese-layout-enabled)
|
||||
(fn [event]
|
||||
(mf/deps layout-config)
|
||||
(fn [language event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dw/set-japanese-layout (not japanese-layout-enabled)))
|
||||
(st/emit! (dw/set-text-layout language (not (get-in layout-config [language :file?]))))
|
||||
(reset! show-menu* false)
|
||||
(reset! selected-sub-menu* nil)))
|
||||
|
||||
@ -1175,13 +1193,12 @@
|
||||
:preferences
|
||||
[:> preferences-menu* {:layout layout
|
||||
:profile profile
|
||||
:japanese-layout-enabled japanese-layout-enabled
|
||||
:japanese-layout-all-files (:all-files? japanese-layout)
|
||||
:layout-config layout-config
|
||||
:toggle-flag toggle-flag
|
||||
:toggle-theme toggle-theme
|
||||
:toggle-render toggle-render
|
||||
:show-shortcuts show-shortcuts
|
||||
:toggle-japanese-layout toggle-japanese-layout
|
||||
:toggle-text-layout toggle-text-layout
|
||||
:on-close close-sub-menu}]
|
||||
|
||||
:plugins
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.workspace.sidebar.options.common :refer [radio-selected]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.text-japanese-layout :as tjl]
|
||||
[app.main.ui.workspace.sidebar.options.menus.text-language-layout :as tll]
|
||||
[app.main.ui.workspace.sidebar.options.menus.token-typography-row :refer [token-typography-row*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.typography :refer [text-options* typography-entry*]]
|
||||
[app.main.ui.workspace.tokens.management.forms.controls.utils :as csu]
|
||||
@ -39,6 +40,7 @@
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.object :as obj]
|
||||
[app.util.text.content :as content]
|
||||
[app.util.text.layout :as tl]
|
||||
[app.util.text.ui :as txu]
|
||||
[app.util.text.writing-mode :as wm]
|
||||
[app.util.timers :as ts]
|
||||
@ -314,6 +316,10 @@
|
||||
(get n-values :text-decoration))
|
||||
(identical? (get o-values :text-direction)
|
||||
(get n-values :text-direction))
|
||||
(identical? (get o-values :text-language)
|
||||
(get n-values :text-language))
|
||||
(identical? (get o-values :korean-wrap)
|
||||
(get n-values :korean-wrap))
|
||||
(identical? (get o-values :writing-mode)
|
||||
(get n-values :writing-mode))
|
||||
(identical? (get o-values :text-orientation)
|
||||
@ -355,6 +361,26 @@
|
||||
;; Main component
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(mf/defc language-layout-section*
|
||||
[{:keys [language title config options-props]}]
|
||||
(let [open* (mf/use-state true)
|
||||
open? (deref open*)
|
||||
toggle-open (mf/use-fn #(swap! open* not))]
|
||||
(when (tl/controls-enabled? config)
|
||||
[:section {:class (stl/css :element-set)
|
||||
:data-keep-editing-on-blur true
|
||||
:aria-label title}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:> title-bar* {:collapsable true
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-open
|
||||
:title title
|
||||
:class (stl/css :title-spacing-text)}]]
|
||||
(when open?
|
||||
[:div {:class (stl/css :element-content)}
|
||||
[:> tll/language-layout-options*
|
||||
(mf/spread-props options-props {:language language})]])])))
|
||||
|
||||
(mf/defc text-menu*
|
||||
{::mf/wrap [#(mf/memo' % check-props)]}
|
||||
[{:keys [ids type values ruby-values text-selection-active
|
||||
@ -362,15 +388,11 @@
|
||||
|
||||
(let [;; --- UI state
|
||||
menu-state* (mf/use-state {:main-menu true
|
||||
:more-options false
|
||||
:japanese-layout true})
|
||||
:more-options false})
|
||||
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)
|
||||
|
||||
japanese-layout-config-enabled?
|
||||
(tjl/japanese-layout-config-enabled? (mf/deref refs/japanese-layout-config))
|
||||
layout-config (mf/deref refs/text-layout-config)
|
||||
|
||||
font-id (or (:font-id values) (:font-id txt/default-typography))
|
||||
|
||||
@ -461,10 +483,6 @@
|
||||
(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))
|
||||
@ -563,7 +581,7 @@
|
||||
:show-recent true
|
||||
:on-blur on-text-blur})
|
||||
|
||||
japanese-layout-props
|
||||
language-layout-props
|
||||
(mf/props
|
||||
{:ids ids
|
||||
:values values
|
||||
@ -683,17 +701,15 @@
|
||||
[:> text-decoration-options* (mf/spread-props common-props {:token-applied current-token-name})]
|
||||
[:> text-direction-options* common-props]])])]
|
||||
|
||||
(when japanese-layout-config-enabled?
|
||||
[:section {:class (stl/css :element-set)
|
||||
;; Focusing these controls must not exit the v3 text editor (see `keep-editing-on-blur?`).
|
||||
:data-keep-editing-on-blur true
|
||||
: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]])])]))
|
||||
[:> language-layout-section* {:language :japanese
|
||||
:title (tr "workspace.options.text-options.japanese-layout")
|
||||
:config (:japanese layout-config)
|
||||
:options-props language-layout-props}]
|
||||
[:> language-layout-section* {:language :chinese
|
||||
:title (tr "workspace.options.text-options.chinese-layout")
|
||||
:config (:chinese layout-config)
|
||||
:options-props language-layout-props}]
|
||||
[:> language-layout-section* {:language :korean
|
||||
:title (tr "workspace.options.text-options.korean-layout")
|
||||
:config (:korean layout-config)
|
||||
:options-props language-layout-props}]]))
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
[app.main.ui.workspace.sidebar.options.common :refer [advanced-options* radio-selected]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.text.writing-mode :as wm]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private mixed-span-values
|
||||
@ -49,14 +48,6 @@
|
||||
:disabled true
|
||||
:dimmed true})))
|
||||
|
||||
(defn japanese-layout-config-enabled?
|
||||
"True under the WASM renderer when Japanese layout is enabled for the file
|
||||
or in the user's profile. `config` is a `refs/japanese-layout-config`
|
||||
value."
|
||||
[{:keys [file? all-files?]}]
|
||||
(and ^boolean (wm/vertical-layout-active?)
|
||||
(boolean (or file? all-files?))))
|
||||
|
||||
(defn vertical-japanese-layout?
|
||||
"True when the Japanese layout controls are editing vertical text."
|
||||
[values]
|
||||
@ -93,6 +84,10 @@
|
||||
"Radio group bound to one text attribute."
|
||||
[{:keys [values on-change on-blur attr default-value name class options]}]
|
||||
(let [selected (radio-selected (get values attr) default-value)
|
||||
group-id (mf/use-id)
|
||||
options (mf/with-memo [options group-id]
|
||||
(mapv #(update % :id (fn [id] (str group-id "-" id))) options))
|
||||
name (str group-id "-" name)
|
||||
handle-change
|
||||
(mf/use-fn
|
||||
(mf/deps attr on-change on-blur)
|
||||
@ -127,6 +122,24 @@
|
||||
:label (translate "workspace.options.text-options.text-orientation-upright")
|
||||
:icon i/text-orientation-upright}])
|
||||
|
||||
(mf/defc writing-mode-controls*
|
||||
[{:keys [values on-change on-blur]}]
|
||||
(let [props (mf/props {:values values :on-change on-change :on-blur on-blur})]
|
||||
[:div {:class (stl/css :japanese-icon-options)}
|
||||
[:> attr-radio-options* (mf/spread-props props
|
||||
{:attr :writing-mode
|
||||
:default-value (cjk/enum-default :writing-mode)
|
||||
:name "writing-mode-options"
|
||||
:class (stl/css :writing-mode-options)
|
||||
:options (writing-mode-options tr)})]
|
||||
(when (vertical-japanese-layout? values)
|
||||
[:> attr-radio-options* (mf/spread-props props
|
||||
{:attr :text-orientation
|
||||
:default-value (cjk/enum-default :text-orientation)
|
||||
:name "text-orientation-options"
|
||||
:class (stl/css :text-orientation-options)
|
||||
:options (text-orientation-options tr)})])]))
|
||||
|
||||
;; Warichu (割注): renders the selection as two half-size lines in one inline
|
||||
;; position (top/bottom horizontally, right/left vertically).
|
||||
(defn warichu-options
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
;; 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/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.main.ui.workspace.sidebar.options.menus.text-language-layout
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.ui.ds.controls.select :refer [select*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.text-japanese-layout :as tjl]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.text.layout :as tl]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn profile-options
|
||||
[language value translate]
|
||||
(let [options (into [{:id "" :label (translate "workspace.options.text-options.profile-plain")}]
|
||||
(case language
|
||||
:japanese [{:id "ja" :label (translate "workspace.options.text-options.profile-japanese")}]
|
||||
:chinese [{:id "zh-Hans" :label (translate "workspace.options.text-options.profile-chinese-simplified")}
|
||||
{:id "zh-Hant" :label (translate "workspace.options.text-options.profile-chinese-traditional")}]
|
||||
:korean [{:id "ko" :label (translate "workspace.options.text-options.profile-korean")}]))
|
||||
mixed? (= :multiple value)
|
||||
id (if mixed? "multiple" (or value ""))]
|
||||
(cond-> options
|
||||
(not (some #(= (:id %) id) options))
|
||||
(conj {:id id
|
||||
:label (if mixed? (translate "labels.mixed-values") value)
|
||||
:disabled true
|
||||
:dimmed true}))))
|
||||
|
||||
(defn profile-change
|
||||
[value]
|
||||
{:text-language (when (seq value) value)})
|
||||
|
||||
(mf/defc language-layout-options*
|
||||
[{:keys [language values ruby-values text-selection-active
|
||||
on-change on-ruby-presentation-change on-blur]}]
|
||||
(let [profile (:text-language values)
|
||||
selected (if (= :multiple profile) "multiple" (or profile ""))
|
||||
options (mf/with-memo [language profile] (profile-options language profile tr))
|
||||
matches? (tl/profile-matches? language profile)
|
||||
handle-profile-change (mf/use-fn
|
||||
(mf/deps on-change)
|
||||
#(on-change (profile-change %)))
|
||||
props (mf/props {:values values :on-change on-change :on-blur on-blur})]
|
||||
[:div {:class (stl/css :language-layout-options)}
|
||||
[:div {:class (stl/css :profile-option)}
|
||||
[:span {:class (stl/css :option-label)}
|
||||
(tr "workspace.options.text-options.language-profile")]
|
||||
[:> select* {:default-selected selected
|
||||
:aria-label (tr "workspace.options.text-options.language-profile")
|
||||
:options options
|
||||
:on-change handle-profile-change}]]
|
||||
(if (and (= :japanese language) matches?)
|
||||
[:> tjl/japanese-layout-options*
|
||||
(mf/spread-props props {:ruby-values ruby-values
|
||||
:text-selection-active text-selection-active
|
||||
:on-ruby-presentation-change on-ruby-presentation-change})]
|
||||
[:> tjl/writing-mode-controls* props])
|
||||
(when (and (= :korean language) matches?)
|
||||
[:> tjl/span-select-option*
|
||||
(mf/spread-props props
|
||||
{:attr :korean-wrap
|
||||
:default-value "syllable"
|
||||
:label (tr "workspace.options.text-options.korean-wrap")
|
||||
:options [{:id "syllable" :label (tr "workspace.options.text-options.korean-wrap-syllable")}
|
||||
{:id "spaces-only" :label (tr "workspace.options.text-options.korean-wrap-spaces-only")}]})])]))
|
||||
@ -0,0 +1,27 @@
|
||||
// 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/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
@use "ds/typography.scss" as t;
|
||||
|
||||
.language-layout-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
padding-block: var(--sp-xs);
|
||||
}
|
||||
|
||||
.profile-option {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
|
||||
align-items: center;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.option-label {
|
||||
@include t.use-typography("body-small");
|
||||
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
@ -808,13 +808,36 @@
|
||||
{}
|
||||
spans)))))
|
||||
|
||||
(defn selection-paragraph-styles
|
||||
"Profile attrs of the selected paragraphs, including unset and mixed values."
|
||||
[content selection]
|
||||
(when (and content selection)
|
||||
(let [{:keys [start-para end-para]} (normalize-selection selection)
|
||||
paragraphs (-> content :children first :children)]
|
||||
(reduce
|
||||
(fn [result paragraph]
|
||||
(reduce
|
||||
(fn [result attr]
|
||||
(let [value (get paragraph attr)]
|
||||
(assoc result attr
|
||||
(if (and (contains? result attr)
|
||||
(not= (get result attr) value))
|
||||
:multiple
|
||||
value))))
|
||||
result
|
||||
[:text-language :korean-wrap]))
|
||||
{}
|
||||
(subvec paragraphs start-para (inc end-para))))))
|
||||
|
||||
(defn text-editor-get-current-japanese-styles
|
||||
"Japanese span styles of the active WASM editor selection."
|
||||
"Japanese span styles and paragraph profiles of the active editor selection."
|
||||
[]
|
||||
(when (wasm/ready?)
|
||||
(let [shape-id (text-editor-get-active-shape-id)
|
||||
selection (text-editor-get-selection)]
|
||||
(selection-japanese-styles (get-cached-content shape-id) selection))))
|
||||
selection (text-editor-get-selection)
|
||||
content (get-cached-content shape-id)]
|
||||
(merge (selection-japanese-styles content selection)
|
||||
(selection-paragraph-styles content selection)))))
|
||||
|
||||
(defn apply-attrs-to-paragraph
|
||||
"Apply `styles` (attrs map, or a fn per span) within [sel-start, sel-end), splitting spans."
|
||||
|
||||
30
frontend/src/app/util/text/layout.cljs
Normal file
30
frontend/src/app/util/text/layout.cljs
Normal file
@ -0,0 +1,30 @@
|
||||
;; 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/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.util.text.layout
|
||||
(:require
|
||||
[app.util.text.writing-mode :as wm]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn controls-enabled?
|
||||
[{:keys [feature-enabled file? all-files?]}]
|
||||
(and (true? feature-enabled)
|
||||
^boolean (wm/vertical-layout-active?)
|
||||
(boolean (or file? all-files?))))
|
||||
|
||||
(defn file-action-visible?
|
||||
[{:keys [feature-enabled all-files?]} can-edit? read-only?]
|
||||
(and (true? feature-enabled)
|
||||
^boolean (wm/vertical-layout-active?)
|
||||
(true? can-edit?)
|
||||
(not read-only?)
|
||||
(not all-files?)))
|
||||
|
||||
(defn profile-matches?
|
||||
[language text-language]
|
||||
(and (string? text-language)
|
||||
(= (case language :japanese "ja" :chinese "zh" :korean "ko")
|
||||
(first (str/split (str/lower-case text-language) #"-")))))
|
||||
@ -81,6 +81,22 @@
|
||||
(text-editor/selection-japanese-styles
|
||||
content (selection 0 1 0 2))))))
|
||||
|
||||
(t/deftest paragraph-profiles-follow-the-editor-selection
|
||||
(let [content {:type "root"
|
||||
:children [{:type "paragraph-set"
|
||||
:children [{:type "paragraph" :children [{:text "Plain"}]}
|
||||
{:type "paragraph" :text-language "ko"
|
||||
:korean-wrap "spaces-only"
|
||||
:children [{:text "한국어"}]}]}]}]
|
||||
(t/is (= {:text-language nil :korean-wrap nil}
|
||||
(text-editor/selection-paragraph-styles content (selection 0 0 0 0))))
|
||||
(t/is (= {:text-language "ko" :korean-wrap "spaces-only"}
|
||||
(text-editor/selection-paragraph-styles content (selection 1 1 1 1))))
|
||||
(t/is (= {:text-language :multiple :korean-wrap :multiple}
|
||||
(text-editor/selection-paragraph-styles content (selection 0 0 1 1))))
|
||||
(t/is (= {:text-language :multiple :korean-wrap :multiple}
|
||||
(text-editor/selection-paragraph-styles content (selection 1 1 0 0))))))
|
||||
|
||||
(t/deftest japanese-styles-report-mixed-ranges-and-follow-the-caret
|
||||
(let [content (editor-content
|
||||
[{:text "平成"
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
[app.main.ui.workspace.sidebar.options.common :refer [radio-selected]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.text :as text-menu]
|
||||
[app.main.ui.workspace.sidebar.options.menus.text-japanese-layout :as tjl]
|
||||
[app.main.ui.workspace.sidebar.options.menus.text-language-layout :as tll]
|
||||
[app.util.text.layout :as tl]
|
||||
[app.util.text.writing-mode :as wm]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[cuerdas.core :as str]
|
||||
@ -35,14 +37,75 @@
|
||||
:annotation-clearance])
|
||||
|
||||
|
||||
(t/deftest japanese-layout-controls-follow-file-or-profile-configuration
|
||||
(t/deftest layout-controls-follow-file-or-profile-configuration
|
||||
(with-redefs [wm/vertical-layout-active? (constantly true)]
|
||||
(t/is (false? (tjl/japanese-layout-config-enabled? {})))
|
||||
(t/is (false? (tjl/japanese-layout-config-enabled? {:file? false :all-files? false})))
|
||||
(t/is (true? (tjl/japanese-layout-config-enabled? {:file? true :all-files? false})))
|
||||
(t/is (true? (tjl/japanese-layout-config-enabled? {:file? false :all-files? true}))))
|
||||
(t/is (false? (tl/controls-enabled? {})))
|
||||
(t/is (false? (tl/controls-enabled? {:feature-enabled true :file? false :all-files? false})))
|
||||
(t/is (true? (tl/controls-enabled? {:feature-enabled true :file? true :all-files? false})))
|
||||
(t/is (true? (tl/controls-enabled? {:feature-enabled true :file? false :all-files? true}))))
|
||||
(with-redefs [wm/vertical-layout-active? (constantly false)]
|
||||
(t/is (false? (tjl/japanese-layout-config-enabled? {:file? true :all-files? true})))))
|
||||
(t/is (false? (tl/controls-enabled? {:feature-enabled true :file? true :all-files? true})))))
|
||||
|
||||
(t/deftest language-controls-and-menu-actions-respect-independent-preferences
|
||||
(with-redefs [wm/vertical-layout-active? (constantly true)]
|
||||
(doseq [file? [true false] all-files? [true false]]
|
||||
(let [config {:feature-enabled true :file? file? :all-files? all-files?}]
|
||||
(t/is (= (or file? all-files?) (tl/controls-enabled? config)))
|
||||
(t/is (= (not all-files?) (tl/file-action-visible? config true false)))
|
||||
(t/is (false? (tl/file-action-visible? config false false)))
|
||||
(t/is (false? (tl/file-action-visible? config true true)))
|
||||
(t/is (false? (tl/controls-enabled? (assoc config :feature-enabled false)))))))
|
||||
(with-redefs [wm/vertical-layout-active? (constantly false)]
|
||||
(t/is (false? (tl/controls-enabled? {:feature-enabled true :file? true})))
|
||||
(t/is (false? (tl/file-action-visible? {:feature-enabled true :file? true} true false)))))
|
||||
|
||||
(t/deftest choosing-a-profile-only-changes-the-paragraph-language
|
||||
(t/is (= {:text-language "ko"} (tll/profile-change "ko")))
|
||||
(t/is (= {:text-language nil} (tll/profile-change "")))
|
||||
(t/is (tl/profile-matches? :japanese "ja-JP"))
|
||||
(t/is (tl/profile-matches? :chinese "zh-Hant-TW"))
|
||||
(t/is (tl/profile-matches? :korean "KO-kr"))
|
||||
(t/is (not (tl/profile-matches? :chinese "ja")))
|
||||
(t/is (not (tl/profile-matches? :korean :multiple)))
|
||||
(t/is (not (tl/profile-matches? :japanese nil))))
|
||||
|
||||
(t/deftest profile-choices-keep-mixed-and-other-language-values-visible
|
||||
(let [mixed (last (tll/profile-options :korean :multiple identity))
|
||||
other (last (tll/profile-options :chinese "ja" identity))]
|
||||
(t/is (= {:id "multiple" :label "labels.mixed-values" :disabled true :dimmed true} mixed))
|
||||
(t/is (= {:id "ja" :label "ja" :disabled true :dimmed true} other))
|
||||
(t/is (= ["" "zh-Hans" "zh-Hant"]
|
||||
(mapv :id (tll/profile-options :chinese nil identity))))))
|
||||
|
||||
(t/deftest language-specific-controls-require-an-explicit-matching-profile
|
||||
(with-redefs [hooks/use-portal-container (fn ([] nil) ([_] nil))]
|
||||
(doseq [[language attr label] [[:japanese "ja" "workspace.options.text-options.line-adjustment"]
|
||||
[:korean "ko" "workspace.options.text-options.korean-wrap"]]
|
||||
profile [nil :multiple "zh-Hans" attr]]
|
||||
(let [changes (atom [])
|
||||
markup (rds/renderToStaticMarkup
|
||||
(mf/element tll/language-layout-options*
|
||||
#js {:language language
|
||||
:values {:text-language profile}
|
||||
:on-change #(swap! changes conj %)}))]
|
||||
(t/is (= (= profile attr) (str/includes? markup label)))
|
||||
(t/is (str/includes? markup "horizontal-tb-writing-mode"))
|
||||
(t/is (empty? @changes) "Showing controls must not assign a profile")))))
|
||||
|
||||
(t/deftest shared-writing-mode-controls-have-independent-radio-groups
|
||||
(with-redefs [hooks/use-portal-container (fn ([] nil) ([_] nil))]
|
||||
(let [markup (rds/renderToStaticMarkup
|
||||
(mf/html
|
||||
[:div
|
||||
[:> tll/language-layout-options* {:language :japanese :values {} :on-change identity}]
|
||||
[:> tll/language-layout-options* {:language :chinese :values {} :on-change identity}]
|
||||
[:> tll/language-layout-options* {:language :korean :values {} :on-change identity}]]))
|
||||
inputs (re-seq #"<input[^>]+>" markup)
|
||||
ids (map #(second (re-find #"id=\"([^\"]+)\"" %)) inputs)
|
||||
names (map #(second (re-find #"name=\"([^\"]+)\"" %)) inputs)]
|
||||
(t/is (= 6 (count (set ids))))
|
||||
(t/is (= 3 (count (set names))))
|
||||
(t/is (= 3 (count (filter #(str/includes? % "checked=\"\"") inputs)))))))
|
||||
|
||||
(t/deftest text-emphasis-select-reports-and-restores-canonical-values
|
||||
(let [options (tjl/text-emphasis-options identity)
|
||||
|
||||
@ -9945,3 +9945,79 @@ msgstr ""
|
||||
#: src/app/main/ui/workspace/sidebar/versions.cljs:462
|
||||
msgid "workspace.versions.warning.text"
|
||||
msgstr "Autosaved versions will be kept for %s days."
|
||||
|
||||
#: src/app/main/ui/settings/options.cljs
|
||||
msgid "dashboard.text-layout.title"
|
||||
msgstr "Text layout by language"
|
||||
|
||||
#: src/app/main/ui/settings/options.cljs
|
||||
msgid "dashboard.text-layout.description"
|
||||
msgstr "Choose which text layout controls are available in all files."
|
||||
|
||||
#: src/app/main/ui/settings/options.cljs
|
||||
msgid "dashboard.chinese-layout.enable-all-files"
|
||||
msgstr "Enable Chinese text layout for all files"
|
||||
|
||||
#: src/app/main/ui/settings/options.cljs
|
||||
msgid "dashboard.korean-layout.enable-all-files"
|
||||
msgstr "Enable Korean text layout for all files"
|
||||
|
||||
#: src/app/main/ui/workspace/main_menu.cljs
|
||||
msgid "workspace.header.menu.enable-chinese-layout"
|
||||
msgstr "Enable Chinese text layout"
|
||||
|
||||
#: src/app/main/ui/workspace/main_menu.cljs
|
||||
msgid "workspace.header.menu.disable-chinese-layout"
|
||||
msgstr "Disable Chinese text layout"
|
||||
|
||||
#: src/app/main/ui/workspace/main_menu.cljs
|
||||
msgid "workspace.header.menu.enable-korean-layout"
|
||||
msgstr "Enable Korean text layout"
|
||||
|
||||
#: src/app/main/ui/workspace/main_menu.cljs
|
||||
msgid "workspace.header.menu.disable-korean-layout"
|
||||
msgstr "Disable Korean text layout"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
|
||||
msgid "workspace.options.text-options.chinese-layout"
|
||||
msgstr "Chinese text layout"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
|
||||
msgid "workspace.options.text-options.korean-layout"
|
||||
msgstr "Korean text layout"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.language-profile"
|
||||
msgstr "Language profile"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.profile-plain"
|
||||
msgstr "No language profile"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.profile-japanese"
|
||||
msgstr "Japanese"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.profile-chinese-simplified"
|
||||
msgstr "Simplified Chinese"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.profile-chinese-traditional"
|
||||
msgstr "Traditional Chinese"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.profile-korean"
|
||||
msgstr "Korean"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.korean-wrap"
|
||||
msgstr "Line wrapping"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.korean-wrap-syllable"
|
||||
msgstr "By syllable"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.korean-wrap-spaces-only"
|
||||
msgstr "At spaces (experimental)"
|
||||
|
||||
@ -9821,3 +9821,79 @@ msgstr "Si quieres aumentar este límite, contáctanos en [support@penpot.app](%
|
||||
#: src/app/main/ui/workspace/sidebar/versions.cljs:462
|
||||
msgid "workspace.versions.warning.text"
|
||||
msgstr "Los autoguardados duran %s días."
|
||||
|
||||
#: src/app/main/ui/settings/options.cljs
|
||||
msgid "dashboard.text-layout.title"
|
||||
msgstr "Disposición del texto por idioma"
|
||||
|
||||
#: src/app/main/ui/settings/options.cljs
|
||||
msgid "dashboard.text-layout.description"
|
||||
msgstr "Elige qué controles de disposición del texto están disponibles en todos los archivos."
|
||||
|
||||
#: src/app/main/ui/settings/options.cljs
|
||||
msgid "dashboard.chinese-layout.enable-all-files"
|
||||
msgstr "Activar la disposición del texto chino en todos los archivos"
|
||||
|
||||
#: src/app/main/ui/settings/options.cljs
|
||||
msgid "dashboard.korean-layout.enable-all-files"
|
||||
msgstr "Activar la disposición del texto coreano en todos los archivos"
|
||||
|
||||
#: src/app/main/ui/workspace/main_menu.cljs
|
||||
msgid "workspace.header.menu.enable-chinese-layout"
|
||||
msgstr "Activar la disposición del texto chino"
|
||||
|
||||
#: src/app/main/ui/workspace/main_menu.cljs
|
||||
msgid "workspace.header.menu.disable-chinese-layout"
|
||||
msgstr "Desactivar la disposición del texto chino"
|
||||
|
||||
#: src/app/main/ui/workspace/main_menu.cljs
|
||||
msgid "workspace.header.menu.enable-korean-layout"
|
||||
msgstr "Activar la disposición del texto coreano"
|
||||
|
||||
#: src/app/main/ui/workspace/main_menu.cljs
|
||||
msgid "workspace.header.menu.disable-korean-layout"
|
||||
msgstr "Desactivar la disposición del texto coreano"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
|
||||
msgid "workspace.options.text-options.chinese-layout"
|
||||
msgstr "Disposición del texto chino"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
|
||||
msgid "workspace.options.text-options.korean-layout"
|
||||
msgstr "Disposición del texto coreano"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.language-profile"
|
||||
msgstr "Perfil de idioma"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.profile-plain"
|
||||
msgstr "Sin perfil de idioma"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.profile-japanese"
|
||||
msgstr "Japonés"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.profile-chinese-simplified"
|
||||
msgstr "Chino simplificado"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.profile-chinese-traditional"
|
||||
msgstr "Chino tradicional"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.profile-korean"
|
||||
msgstr "Coreano"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.korean-wrap"
|
||||
msgstr "Salto de línea"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.korean-wrap-syllable"
|
||||
msgstr "Por sílaba"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
|
||||
msgid "workspace.options.text-options.korean-wrap-spaces-only"
|
||||
msgstr "En los espacios (experimental)"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user