mirror of
https://github.com/penpot/penpot.git
synced 2025-01-21 22:22:43 -05:00
0cd446421d
related to how package.json is defined and how modules are exported
409 lines
12 KiB
JavaScript
409 lines
12 KiB
JavaScript
/**
|
|
* 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
|
|
*/
|
|
import pkg from "draft-js";
|
|
|
|
export const {
|
|
BlockMapBuilder,
|
|
CharacterMetadata,
|
|
CompositeDecorator,
|
|
EditorState,
|
|
Modifier,
|
|
RichTextEditorUtil,
|
|
SelectionState,
|
|
convertFromRaw,
|
|
convertToRaw
|
|
} = pkg;
|
|
|
|
import DraftPasteProcessor from 'draft-js/lib/DraftPasteProcessor.js';
|
|
import {Map, OrderedSet} from "immutable";
|
|
|
|
function isDefined(v) {
|
|
return v !== undefined && v !== null;
|
|
}
|
|
|
|
function mergeBlockData(block, newData) {
|
|
let data = block.getData();
|
|
|
|
for (let key of Object.keys(newData)) {
|
|
const oldVal = data.get(key);
|
|
if (oldVal === newData[key]) {
|
|
data = data.delete(key);
|
|
} else {
|
|
data = data.set(key, newData[key]);
|
|
}
|
|
}
|
|
|
|
return block.mergeDeep({
|
|
data: data
|
|
});
|
|
}
|
|
|
|
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": strategy, "component": 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()
|
|
});
|
|
}
|
|
|
|
function getCursorInEndPosition(state) {
|
|
const content = state.getCurrentContent();
|
|
const lastBlock = content.getBlockMap().last();
|
|
|
|
return new SelectionState({
|
|
"anchorKey": lastBlock.getKey(),
|
|
"anchorOffset": lastBlock.getLength(),
|
|
"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) => {
|
|
return mergeBlockData(block, attrs);
|
|
});
|
|
|
|
return EditorState.push(state, content, "change-block-data");
|
|
}
|
|
|
|
function addStylesToOverride(styles, other) {
|
|
let result = styles;
|
|
|
|
for (let style of other) {
|
|
const [p, k, v] = style.split("$$$");
|
|
const prefix = [p, k, ""].join("$$$");
|
|
|
|
const curValue = result.find((it) => it.startsWith(prefix))
|
|
if (curValue) {
|
|
result = result.remove(curValue);
|
|
}
|
|
result = result.add(style);
|
|
}
|
|
return result
|
|
}
|
|
|
|
export function applyInlineStyle(state, styles) {
|
|
const userSelection = state.getSelection();
|
|
let selection = userSelection;
|
|
let result = state;
|
|
|
|
if (selection.isCollapsed()) {
|
|
const currentOverride = state.getCurrentInlineStyle() || new OrderedSet();
|
|
const styleOverride = addStylesToOverride(currentOverride, styles)
|
|
return EditorState.setInlineStyleOverride(state, styleOverride);
|
|
}
|
|
|
|
let content = null;
|
|
|
|
for (let style of styles) {
|
|
const [p, k, v] = style.split("$$$");
|
|
const prefix = [p, k, ""].join("$$$");
|
|
|
|
content = result.getCurrentContent();
|
|
content = removeInlineStylePrefix(content, selection, prefix);
|
|
|
|
if (v !== "z:null") {
|
|
content = Modifier.applyInlineStyle(content, selection, style);
|
|
}
|
|
|
|
result = EditorState.push(result, content, "change-inline-style");
|
|
}
|
|
|
|
return EditorState.acceptSelection(result, userSelection);
|
|
}
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
export function cursorToEnd(state) {
|
|
const newSelection = getCursorInEndPosition(state);
|
|
const selection = state.getSelection();
|
|
|
|
let content = state.getCurrentContent();
|
|
content = Modifier.applyEntity(content, newSelection, null);
|
|
|
|
state = EditorState.forceSelection(state, newSelection);
|
|
state = EditorState.push(state, content, "apply-entity");
|
|
|
|
return state;
|
|
}
|
|
|
|
export function isCurrentEmpty(state) {
|
|
const selection = state.getSelection();
|
|
|
|
if (!selection.isCollapsed()) {
|
|
return false;
|
|
}
|
|
|
|
const blockKey = selection.getStartKey();
|
|
const content = state.getCurrentContent();
|
|
|
|
const block = content.getBlockForKey(blockKey);
|
|
|
|
return block.getText() === "";
|
|
}
|
|
|
|
/*
|
|
Returns the block keys between a selection
|
|
*/
|
|
export function getSelectedBlocks(state) {
|
|
const selection = state.getSelection();
|
|
const startKey = selection.getStartKey();
|
|
const endKey = selection.getEndKey();
|
|
const content = state.getCurrentContent();
|
|
const result = [ startKey ];
|
|
|
|
let currentKey = startKey;
|
|
|
|
while (currentKey !== endKey) {
|
|
const currentBlock = content.getBlockAfter(currentKey);
|
|
currentKey = currentBlock.getKey();
|
|
result.push(currentKey);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
export function getBlockContent(state, blockKey) {
|
|
const content = state.getCurrentContent();
|
|
const block = content.getBlockForKey(blockKey);
|
|
return block.getText();
|
|
}
|
|
|
|
export function getBlockData(state, blockKey) {
|
|
const content = state.getCurrentContent();
|
|
const block = content.getBlockForKey(blockKey);
|
|
return block && block.getData().toJS();
|
|
}
|
|
|
|
export function updateBlockData(state, blockKey, data) {
|
|
const userSelection = state.getSelection();
|
|
const inlineStyleOverride = state.getInlineStyleOverride();
|
|
const content = state.getCurrentContent();
|
|
const block = content.getBlockForKey(blockKey);
|
|
const newBlock = mergeBlockData(block, data);
|
|
|
|
const blockData = newBlock.getData();
|
|
|
|
const newContent = Modifier.setBlockData(
|
|
state.getCurrentContent(),
|
|
SelectionState.createEmpty(blockKey),
|
|
blockData
|
|
);
|
|
|
|
let result = EditorState.push(state, newContent, 'change-block-data');
|
|
result = EditorState.acceptSelection(result, userSelection);
|
|
result = EditorState.setInlineStyleOverride(result, inlineStyleOverride);
|
|
return result;
|
|
}
|
|
|
|
export function getSelection(state) {
|
|
return state.getSelection();
|
|
}
|
|
|
|
export function setSelection(state, selection) {
|
|
return EditorState.acceptSelection(state, selection);
|
|
}
|
|
|
|
export function selectBlock(state, blockKey) {
|
|
const block = state.getCurrentContent().getBlockForKey(blockKey);
|
|
const length = block.getText().length;
|
|
const selection = SelectionState.createEmpty(blockKey).merge({
|
|
focusOffset: length
|
|
});
|
|
return EditorState.acceptSelection(state, selection);
|
|
}
|
|
|
|
export function getInlineStyle(state, blockKey, offset) {
|
|
const content = state.getCurrentContent();
|
|
const block = content.getBlockForKey(blockKey);
|
|
return block.getInlineStyleAt(offset).toJS();
|
|
}
|
|
|
|
const NEWLINE_REGEX = /\r\n?|\n/g;
|
|
|
|
function splitTextIntoTextBlocks(text) {
|
|
return text.split(NEWLINE_REGEX);
|
|
}
|
|
|
|
export function insertText(state, text, attrs, inlineStyles) {
|
|
const blocks = splitTextIntoTextBlocks(text);
|
|
|
|
const character = CharacterMetadata.create({style: OrderedSet(inlineStyles)});
|
|
|
|
let blockArray = DraftPasteProcessor.processText(
|
|
blocks,
|
|
character,
|
|
"unstyled",
|
|
);
|
|
|
|
blockArray = blockArray.map((b) => {
|
|
return mergeBlockData(b, attrs);
|
|
});
|
|
|
|
const fragment = BlockMapBuilder.createFromArray(blockArray);
|
|
const content = state.getCurrentContent();
|
|
const selection = state.getSelection();
|
|
|
|
const newContent = Modifier.replaceWithFragment(
|
|
content,
|
|
selection,
|
|
fragment
|
|
);
|
|
|
|
const resultSelection = SelectionState.createEmpty(selection.getStartKey());
|
|
return EditorState.push(state, newContent, 'insert-fragment');
|
|
}
|
|
|
|
export function setInlineStyleOverride(state, inlineStyles) {
|
|
return EditorState.setInlineStyleOverride(state, inlineStyles);
|
|
}
|
|
|
|
export function selectionEquals(selection, other) {
|
|
return selection.getAnchorKey() === other.getAnchorKey() &&
|
|
selection.getAnchorOffset() === other.getAnchorOffset() &&
|
|
selection.getFocusKey() === other.getFocusKey() &&
|
|
selection.getFocusOffset() === other.getFocusOffset() &&
|
|
selection.getIsBackward() === other.getIsBackward();
|
|
}
|