0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-13 10:38:13 -05:00

Allow to unselect the text alignment.

Defaulting to 'start' (rtl friendly).
This commit is contained in:
Andrey Antukh 2021-03-16 13:50:28 +01:00
parent ca52f4f8ea
commit 7bc91e7224
7 changed files with 298 additions and 216 deletions

View file

@ -454,7 +454,6 @@
(if (empty? rch-operations) rch (conj rch rchg))
(if (empty? uch-operations) uch (conj uch uchg)))))))))))
(defn update-shapes-recursive
[ids f]
(us/assert ::coll-of-uuid ids)

View file

@ -121,8 +121,8 @@
;; --- TEXT EDITION IMPL
(defn- update-shape
[shape pred-fn attrs]
(let [merge-attrs #(attrs/merge % attrs)
[shape pred-fn merge-fn attrs]
(let [merge-attrs #(merge-fn % attrs)
transform #(txt/transform-nodes pred-fn merge-attrs %)]
(update shape :content transform)))
@ -134,7 +134,7 @@
(let [objects (dwc/lookup-page-objects state)
shape (get objects id)
update-fn #(update-shape % txt/is-root-node? attrs)
update-fn #(update-shape % txt/is-root-node? attrs/merge attrs)
shape-ids (cond (= (:type shape) :text) [id]
(= (:type shape) :group) (cp/get-children id objects))]
@ -154,7 +154,15 @@
(let [objects (dwc/lookup-page-objects state)
shape (get objects id)
update-fn #(update-shape % txt/is-paragraph-node? attrs)
merge-fn (fn [node attrs]
(reduce-kv (fn [node k v]
(if (= (get node k) v)
(dissoc node k)
(assoc node k v)))
node
attrs))
update-fn #(update-shape % txt/is-paragraph-node? merge-fn attrs)
shape-ids (cond (= (:type shape) :text) [id]
(= (:type shape) :group) (cp/get-children id objects))]
@ -174,7 +182,7 @@
(let [objects (dwc/lookup-page-objects state)
shape (get objects id)
update-fn #(update-shape % txt/is-text-node? attrs)
update-fn #(update-shape % txt/is-text-node? attrs/merge attrs)
shape-ids (cond (= (:type shape) :text) [id]
(= (:type shape) :group) (cp/get-children id objects))]
(rx/of (dwc/update-shapes shape-ids update-fn))))))))

View file

@ -47,7 +47,7 @@
(defn generate-paragraph-styles
[shape data]
(let [line-height (:line-height data)
text-align (:text-align data)
text-align (:text-align data "start")
grow-type (:grow-type shape)
base #js {:fontSize (str (:font-size txt/default-text-attrs) "px")

View file

@ -26,34 +26,62 @@
[cuerdas.core :as str]
[rumext.alpha :as mf]))
(def text-typography-attrs [:typography-ref-id :typography-ref-file])
(def text-fill-attrs [:fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file :fill-color-gradient :fill :opacity ])
(def text-font-attrs [:font-id :font-family :font-variant-id :font-size :font-weight :font-style])
(def text-align-attrs [:text-align])
(def text-spacing-attrs [:line-height :letter-spacing])
(def text-valign-attrs [:vertical-align])
(def text-decoration-attrs [:text-decoration])
(def text-transform-attrs [:text-transform])
(def text-typography-attrs
[:typography-ref-id
:typography-ref-file])
(def shape-attrs [:grow-type])
(def root-attrs (d/concat text-valign-attrs
text-align-attrs))
(def paragraph-attrs text-align-attrs)
(def text-attrs (d/concat text-typography-attrs
text-font-attrs
text-align-attrs
text-spacing-attrs
text-decoration-attrs
text-transform-attrs))
(def text-fill-attrs
[:fill-color
:fill-opacity
:fill-color-ref-id
:fill-color-ref-file
:fill-color-gradient])
(def text-font-attrs
[:font-id
:font-family
:font-variant-id
:font-size
:font-weight
:font-style])
(def text-align-attrs
[:text-align])
(def text-spacing-attrs
[:line-height
:letter-spacing])
(def text-valign-attrs
[:vertical-align])
(def text-decoration-attrs
[:text-decoration])
(def text-transform-attrs
[:text-transform])
(def shape-attrs
[:grow-type])
(def root-attrs
(d/concat text-valign-attrs text-align-attrs))
(def paragraph-attrs
text-align-attrs)
(def text-attrs
(d/concat text-typography-attrs
text-font-attrs
text-spacing-attrs
text-decoration-attrs
text-transform-attrs))
(def attrs (d/concat #{} shape-attrs root-attrs paragraph-attrs text-attrs))
(mf/defc text-align-options
[{:keys [ids values on-change] :as props}]
(let [{:keys [text-align]} values
text-align (or text-align "left")
handle-change
(fn [event new-align]
(on-change {:text-align new-align}))]
@ -169,13 +197,13 @@
{::mf/wrap [mf/memo]}
[{:keys [ids type values] :as props}]
(let [current-file-id (mf/use-ctx ctx/current-file-id)
(let [file-id (mf/use-ctx ctx/current-file-id)
typographies (mf/deref refs/workspace-file-typography)
shared-libs (mf/deref refs/workspace-libraries)
label (case type
:multiple (tr "workspace.options.text-options.title-selection")
:group (tr "workspace.options.text-options.title-group")
(tr "workspace.options.text-options.title"))
shared-libs (mf/deref refs/workspace-libraries)
label (case type
:multiple (tr "workspace.options.text-options.title-selection")
:group (tr "workspace.options.text-options.title-group")
(tr "workspace.options.text-options.title"))
emit-update!
(fn [id attrs]
@ -194,14 +222,14 @@
typography (cond
(and (:typography-ref-id values)
(not= (:typography-ref-id values) :multiple)
(not= (:typography-ref-file values) current-file-id))
(not= (:typography-ref-file values) file-id))
(-> shared-libs
(get-in [(:typography-ref-file values) :data :typographies (:typography-ref-id values)])
(assoc :file-id (:typography-ref-file values)))
(and (:typography-ref-id values)
(not= (:typography-ref-id values) :multiple)
(= (:typography-ref-file values) current-file-id))
(= (:typography-ref-file values) file-id))
(get typographies (:typography-ref-id values)))
on-convert-to-typography
@ -218,7 +246,7 @@
(let [id (uuid/next)]
(st/emit! (dwl/add-typography (assoc typography :id id) false))
(run! #(emit-update! % {:typography-ref-id id
:typography-ref-file current-file-id}) ids)))))
:typography-ref-file file-id}) ids)))))
handle-detach-typography
(fn []
@ -228,7 +256,7 @@
handle-change-typography
(fn [changes]
(st/emit! (dwl/update-typography (merge typography changes) current-file-id)))
(st/emit! (dwl/update-typography (merge typography changes) file-id)))
opts #js {:ids ids
:values values
@ -244,7 +272,7 @@
(cond
typography
[:& typography-entry {:typography typography
:read-only? (not= (:typography-ref-file values) current-file-id)
:read-only? (not= (:typography-ref-file values) file-id)
:file (get shared-libs (:typography-ref-file values))
:on-detach handle-detach-typography
:on-change handle-change-typography}]

View file

@ -1,57 +0,0 @@
/**
* Copyright (c) UXBOX Labs SL
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
import {CharacterMetadata} from "draft-js";
import {Map} from "immutable";
function removeStylePrefix(chmeta, stylePrefix) {
var withoutStyle = chmeta.set('style', chmeta.getStyle().filter((s) => !s.startsWith(stylePrefix)))
return CharacterMetadata.create(withoutStyle);
};
export function removeInlineStylePrefix(contentState, selectionState, stylePrefix) {
var blockMap = contentState.getBlockMap();
var startKey = selectionState.getStartKey();
var startOffset = selectionState.getStartOffset();
var endKey = selectionState.getEndKey();
var endOffset = selectionState.getEndOffset();
var newBlocks = blockMap.skipUntil(function (_, k) {
return k === startKey;
}).takeUntil(function (_, k) {
return k === endKey;
}).concat(Map([[endKey, blockMap.get(endKey)]])).map(function (block, blockKey) {
var sliceStart;
var sliceEnd;
if (startKey === endKey) {
sliceStart = startOffset;
sliceEnd = endOffset;
} else {
sliceStart = blockKey === startKey ? startOffset : 0;
sliceEnd = blockKey === endKey ? endOffset : block.getLength();
}
var chars = block.getCharacterList();
var current;
while (sliceStart < sliceEnd) {
current = chars.get(sliceStart);
chars = chars.set(sliceStart, removeStylePrefix(current, stylePrefix));
sliceStart++;
}
return block.set('characterList', chars);
});
return contentState.merge({
blockMap: blockMap.merge(newBlocks),
selectionBefore: selectionState,
selectionAfter: selectionState
});
}

View file

@ -11,7 +11,7 @@
"Draft related abstraction functions."
(:require
["draft-js" :as draft]
["./draft_helpers.js" :as helpers]
["./text_editor_impl.js" :as impl]
[app.common.attrs :as attrs]
[app.common.text :as txt]
[app.common.data :as d]
@ -206,27 +206,15 @@
(defn create-editor-state
([]
(.createEmpty ^js draft/EditorState))
(impl/createEditorState nil nil))
([content]
(.createWithContent ^js draft/EditorState content))
(impl/createEditorState content nil))
([content decorator]
(if (some? content)
(.createWithContent ^js draft/EditorState content decorator)
(.createEmpty ^js draft/EditorState decorator))))
(impl/createEditorState content decorator)))
(defn create-decorator
[type component]
(letfn [(find-entity [block callback content]
(.findEntityRanges ^js block
(fn [cmeta]
(let [ekey (.getEntity ^js cmeta)]
(boolean
(and (some? ekey)
(= type (.. ^js content (getEntity ekey) (getType)))))))
callback))]
(draft/CompositeDecorator.
#js [#js {:strategy find-entity
:component component}])))
(impl/createDecorator type component))
(defn import-content
[content]
@ -248,18 +236,7 @@
(defn editor-select-all
[state]
(let [content (get-editor-current-content state)
fblock (.. ^js content getBlockMap first)
lblock (.. ^js content getBlockMap last)
fbk (.getKey ^js fblock)
lbk (.getKey ^js lblock)
lbl (.getLength ^js lblock)
params #js {:anchorKey fbk
:anchorOffset 0
:focusKey lbk
:focusOffset lbl}
selection (draft/SelectionState. params)]
(.forceSelection ^js draft/EditorState state selection)))
(impl/selectAll state))
(defn get-editor-block-data
[block]
@ -272,9 +249,7 @@
(defn get-editor-current-block-data
[state]
(let [content (.getCurrentContent ^js state)
key (.. ^js state getSelection getStartKey)
block (.getBlockForKey ^js content key)]
(let [block (impl/getCurrentBlock state)]
(get-editor-block-data block)))
(defn get-editor-current-inline-styles
@ -284,103 +259,20 @@
(defn update-editor-current-block-data
[state attrs]
(loop [selection (.getSelection ^js state)
start-key (.getStartKey ^js selection)
end-key (.getEndKey ^js selection)
content (.getCurrentContent ^js state)
target selection]
(if (and (not= start-key end-key)
(zero? (.getEndOffset ^js selection)))
(let [before-block (.getBlockBefore ^js content end-key)]
(recur selection
start-key
(.getKey ^js before-block)
content
(.merge ^js target
#js {:anchorKey start-key
:anchorOffset (.getStartOffset ^js selection)
:focusKey end-key
:focusOffset (.getLength ^js before-block)
:isBackward false})))
(.push ^js draft/EditorState
state
(.mergeBlockData ^js draft/Modifier content target (clj->js attrs))
"change-block-data"))))
(defn get-editor-current-entity-key
[state]
(let [content (.getCurrentContent ^js state)
selection (.getSelection ^js state)
start-key (.getStartKey ^js selection)
start-offset (.getStartOffset ^js selection)
block (.getBlockForKey ^js content start-key)]
(.getEntityAt ^js block start-offset)))
(impl/updateCurrentBlockData state (clj->js attrs)))
(defn update-editor-current-inline-styles
[state attrs]
(let [selection (.getSelection ^js state)
styles (attrs-to-styles attrs)]
(reduce (fn [state style]
(let [[sk sv] (decode-style style)
prefix (encode-style-prefix sk)
content (.getCurrentContent ^js state)
content (helpers/removeInlineStylePrefix content
selection
prefix)
content (.applyInlineStyle ^js draft/Modifier
content
selection
style)]
(.push ^js draft/EditorState state content "change-inline-style")))
state
styles)))
(impl/applyInlineStyle state (attrs-to-styles attrs)))
(defn editor-split-block
[state]
(let [content (.getCurrentContent ^js state)
selection (.getSelection ^js state)
content (.splitBlock ^js draft/Modifier content selection)
block-data (.. ^js content -blockMap (get (.. content -selectionBefore getStartKey)) getData)
block-key (.. ^js content -selectionAfter getStartKey)
block-map (.. ^js content -blockMap (update block-key (fn [block] (.set ^js block "data" block-data))))]
(.push ^js draft/EditorState state (.set ^js content "blockMap" block-map) "split-block")))
(impl/splitBlockPreservingData state))
(defn add-editor-blur-selection
[state]
(let [content (.getCurrentContent ^js state)
selection (.getSelection ^js state)
content (.createEntity ^js content "PENPOT_SELECTION" "MUTABLE")
ekey (.getLastCreatedEntityKey ^js content)
content (.applyEntity draft/Modifier
content
selection
ekey)]
(.push draft/EditorState state content "apply-entity")))
(impl/addBlurSelectionEntity state))
(defn remove-editor-blur-selection
[state]
(let [content (get-editor-current-content state)
fblock (.. ^js content getBlockMap first)
lblock (.. ^js content getBlockMap last)
fbk (.getKey ^js fblock)
lbk (.getKey ^js lblock)
lbl (.getLength ^js lblock)
params #js {:anchorKey fbk
:anchorOffset 0
:focusKey lbk
:focusOffset lbl}
prev-selection (.getSelection state)
selection (draft/SelectionState. params)
content (.applyEntity draft/Modifier
content
selection
nil)]
(as-> state $
(.push draft/EditorState $ content "apply-entity")
(.forceSelection ^js draft/EditorState $ prev-selection))))
(impl/removeBlurSelectionEntity state))

View file

@ -0,0 +1,212 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This Source Code Form is "Incompatible With Secondary Licenses", as
* defined by the Mozilla Public License, v. 2.0.
*
* Copyright (c) UXBOX Labs SL
*/
'use strict';
import {
CharacterMetadata,
EditorState,
CompositeDecorator,
SelectionState,
Modifier
} from "draft-js";
import {Map} from "immutable";
function isDefined(v) {
return v !== undefined && v !== null;
}
export function createEditorState(content, decorator) {
if (content === null) {
return EditorState.createEmpty(decorator);
} else {
return EditorState.createWithContent(content, decorator);
}
}
export function createDecorator(type, component) {
const strategy = (block, callback, content) => {
return block.findEntityRanges((cmeta) => {
const entityKey = cmeta.getEntity();
return isDefined(entityKey) && (type === content.getEntity(entityKey).getType());
}, callback);
};
return new CompositeDecorator([
{strategy, component}
]);
}
function getSelectAllSelection(state) {
const content = state.getCurrentContent();
const firstBlock = content.getBlockMap().first();
const lastBlock = content.getBlockMap().last();
return new SelectionState({
"anchorKey": firstBlock.getKey(),
"anchorOffset": 0,
"focusKey": lastBlock.getKey(),
"focusOffset": lastBlock.getLength()
});
}
export function selectAll(state) {
return EditorState.forceSelection(state, getSelectAllSelection(state));
}
function modifySelectedBlocks(contentState, selectionState, operation) {
var startKey = selectionState.getStartKey();
var endKey = selectionState.getEndKey();
var blockMap = contentState.getBlockMap();
var newBlocks = blockMap.toSeq().skipUntil(function (_, k) {
return k === startKey;
}).takeUntil(function (_, k) {
return k === endKey;
}).concat(Map([[endKey, blockMap.get(endKey)]])).map(operation);
return contentState.merge({
blockMap: blockMap.merge(newBlocks),
selectionBefore: selectionState,
selectionAfter: selectionState
});
}
export function updateCurrentBlockData(state, attrs) {
const selection = state.getSelection();
let content = state.getCurrentContent();
content = modifySelectedBlocks(content, selection, (block) => {
let data = block.getData();
for (let key of Object.keys(attrs)) {
const oldVal = data.get(key);
if (oldVal === attrs[key]) {
data = data.delete(key);
} else {
data = data.set(key, attrs[key]);
}
}
return block.merge({
data: data
});
});
return EditorState.push(state, content, "change-block-data");
}
export function applyInlineStyle(state, styles) {
const selection = state.getSelection();
let state = state;
let content = null;
for (let style of styles) {
const [p, k, _] = style.split("$$$");
const prefix = [p, k, ""].join("$$$");
content = state.getCurrentContent();
content = removeInlineStylePrefix(content, selection, prefix);
content = Modifier.applyInlineStyle(content, selection, style);
state = EditorState.push(state, content, "change-inline-style");
}
return state;
}
export function splitBlockPreservingData(state) {
let content = state.getCurrentContent();
const selection = state.getSelection();
content = Modifier.splitBlock(content, selection);
const blockData = content.blockMap.get(content.selectionBefore.getStartKey()).getData();
const blockKey = content.selectionAfter.getStartKey();
const blockMap = content.blockMap.update(blockKey, (block) => {
return block.set("data", blockData);
});
content = content.set("blockMap", blockMap);
return EditorState.push(state, content, "split-block");
}
export function addBlurSelectionEntity(state) {
let content = state.getCurrentContent(state);
const selection = state.getSelection();
content = content.createEntity("PENPOT_SELECTION", "MUTABLE");
const entityKey = content.getLastCreatedEntityKey();
content = Modifier.applyEntity(content, selection, entityKey);
return EditorState.push(state, content, "apply-entity");
}
export function removeBlurSelectionEntity(state) {
const selectionAll = getSelectAllSelection(state);
const selection = state.getSelection();
let content = state.getCurrentContent();
content = Modifier.applyEntity(content, selectionAll, null);
state = EditorState.push(state, content, "apply-entity");
state = EditorState.forceSelection(state, selection);
return state;
}
export function getCurrentBlock(state) {
const content = state.getCurrentContent();
const selection = state.getSelection();
const startKey = selection.getStartKey();
return content.getBlockForKey(startKey);
}
export function getCurrentEntityKey(state) {
const block = getCurrentBlock(state);
const selection = state.getSelection();
const startOffset = selection.getStartOffset();
return block.getEntityAt(startOffset);
}
export function removeInlineStylePrefix(contentState, selectionState, stylePrefix) {
const startKey = selectionState.getStartKey();
const startOffset = selectionState.getStartOffset();
const endKey = selectionState.getEndKey();
const endOffset = selectionState.getEndOffset();
return modifySelectedBlocks(contentState, selectionState, (block, blockKey) => {
let sliceStart;
let sliceEnd;
if (startKey === endKey) {
sliceStart = startOffset;
sliceEnd = endOffset;
} else {
sliceStart = blockKey === startKey ? startOffset : 0;
sliceEnd = blockKey === endKey ? endOffset : block.getLength();
}
let chars = block.getCharacterList();
let current;
while (sliceStart < sliceEnd) {
current = chars.get(sliceStart);
current = current.set("style", current.getStyle().filter((s) => !s.startsWith(stylePrefix)))
chars = chars.set(sliceStart, CharacterMetadata.create(current));
sliceStart++;
}
return block.set('characterList', chars);
});
}