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

@ -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);
});
}