Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use editor3 internal clipboard to copy entities from open editors #2509

Merged
merged 11 commits into from
Sep 6, 2018
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
],
"main": "scripts/index.js",
"dependencies": {
"@types/draft-js": "0.10.24",
"@types/lodash": "4.14.116",
"@types/react": "16.4.9",
"angular": "1.6.9",
Expand All @@ -43,8 +44,6 @@
"angular-resource": "1.6.9",
"angular-route": "1.6.9",
"angular-vs-repeat": "1.1.7",
"ts-loader": "3.5.0",
"typescript": "3.0.1",
"bootstrap": "3.3.7",
"classnames": "2.2.5",
"css-loader": "0.28.10",
Expand Down Expand Up @@ -101,6 +100,8 @@
"shortid": "2.2.8",
"style-loader": "0.20.2",
"superdesk-ui-framework": "^1.1.0",
"ts-loader": "3.5.0",
"typescript": "3.0.1",
"webpack": "3.11.0",
"webpack-dev-server": "2.11.1"
},
Expand All @@ -118,6 +119,7 @@
"karma-ng-html2js-preprocessor": "^1.0.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.13",
"patch-package": "^5.1.1",
"protractor": "^5.1.2",
"react-addons-test-utils": "^15.6.0",
"react-test-renderer": "^16.2.0",
Expand All @@ -133,6 +135,7 @@
"lint": "tsc --noEmit --version && eslint --ext .js --ext .jsx --ext .tsx scripts spec tasks *.js",
"start-test-server": "cd test-server && python3 -m venv env && . env/bin/activate && pip install -Ur requirements.txt && honcho start",
"protractor": "protractor protractor.conf.js",
"webdriver-manager": "webdriver-manager"
"webdriver-manager": "webdriver-manager",
"postinstall": "patch-package"
}
}
19 changes: 19 additions & 0 deletions patches/@types/draft-js+0.10.24.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
patch-package
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ioanpocol I added a new dependency: patch-package. With it we can modify any package under node_modules and make a patch like this one to fix a bug without waiting for a PR to be merged. This will patch the module after an npm install automatically

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

--- a/node_modules/@types/draft-js/index.d.ts
+++ b/node_modules/@types/draft-js/index.d.ts
@@ -950,6 +950,7 @@ import Editor = Draft.Component.Base.DraftEditor;
import EditorProps = Draft.Component.Base.DraftEditorProps;
import EditorBlock = Draft.Component.Components.DraftEditorBlock;
import EditorState = Draft.Model.ImmutableData.EditorState;
+import EditorChangeType = Draft.Model.ImmutableData.EditorChangeType;

import CompositeDecorator = Draft.Model.Decorators.CompositeDraftDecorator;
import Entity = Draft.Model.Entity.DraftEntity;
@@ -999,6 +1000,7 @@ export {
EditorProps,
EditorBlock,
EditorState,
+ EditorChangeType,

CompositeDecorator,
Entity,
10 changes: 10 additions & 0 deletions scripts/core/editor3/components/Editor3Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const VALID_MEDIA_TYPES = [
'Files',
];

export const EDITOR_GLOBAL_REFS = 'editor3-refs';

/**
* Get valid media type from event dataTransfer types
*
Expand Down Expand Up @@ -323,6 +325,12 @@ export class Editor3Component extends React.Component<any, any> {

componentDidMount() {
$(this.div).on('dragover', this.onDragOver);

if (!window[EDITOR_GLOBAL_REFS]) {
window[EDITOR_GLOBAL_REFS] = {};
}

window[EDITOR_GLOBAL_REFS][this.editorKey] = this.editor;
}

handleRefs(editor) {
Expand All @@ -334,6 +342,8 @@ export class Editor3Component extends React.Component<any, any> {

componentWillUnmount() {
$(this.div).off();

delete window[EDITOR_GLOBAL_REFS][this.editorKey];
}

componentDidUpdate() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import {EditorState, ContentState, Modifier, genKey, CharacterMetadata, ContentBlock} from 'draft-js';
import {List, OrderedSet} from 'immutable';
import {EditorState, ContentState, Modifier, genKey, CharacterMetadata, ContentBlock, DraftHandleValue} from 'draft-js';
import {List, OrderedMap} from 'immutable';
import {getContentStateFromHtml} from '../html/from-html';
import * as Suggestions from '../helpers/suggestions';
import {sanitizeContent, inlineStyles} from '../helpers/inlineStyles';
import {getAllCustomDataFromEditor, setAllCustomDataForEditor} from '../helpers/editor3CustomData';
import {getCurrentAuthor} from '../helpers/author';
import {htmlComesFromDraftjsEditor} from '../helpers/htmlComesFromDraftjsEditor';
import {EDITOR_GLOBAL_REFS} from 'core/editor3/components/Editor3Component';

function removeMediaFromHtml(htmlString) {
function removeMediaFromHtml(htmlString) : string {
const element = document.createElement('div');

element.innerHTML = htmlString;
Expand All @@ -19,8 +20,26 @@ function removeMediaFromHtml(htmlString) {
return element.innerHTML;
}

const HANDLED = 'handled';
const NOT_HANDLED = 'not-handled';
function pasteContentFromOpenEditor(
html: string, editorState: EditorState, onChange: Function, editorFormat: Array<string>) : DraftHandleValue {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's alright for now, but try avoiding the use of Function type, since it's very generic. You can define

interface IEditor3props {
    onChange(editorState: EditorState): void;
}

and then access onChange like a object property, so you have the whole function signature, but don't need to typed it entirely.

function functionWhichTakesOnChangeAsAnArgument(onChange: IEditor3props['onChange']) {
    // ...
}

for (const editorKey in window[EDITOR_GLOBAL_REFS]) {
if (html.includes(editorKey)) {
const editor = window[EDITOR_GLOBAL_REFS][editorKey];
const internalClipboard = editor.getClipboard();

if (internalClipboard) {
const blocksArray = [];

internalClipboard.forEach((b) => blocksArray.push(b));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't you passing internalClipboard to createFromBlockArray and instead are making a shallow copy?

const contentState = ContentState.createFromBlockArray(blocksArray);

return insertContentInState(editorState, contentState, onChange, editorFormat);
}
}
}

return 'not-handled';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with DraftHandleValue?

}

/**
* @ngdoc method
Expand All @@ -31,46 +50,51 @@ const NOT_HANDLED = 'not-handled';
* @description Handles pasting into the editor, in cases where the content contains
* atomic blocks that need special handling in editor3.
*/
export function handlePastedText(text, _html) {
export function handlePastedText(text: string, _html: string) : DraftHandleValue {
const author = getCurrentAuthor();
let html = _html;

if (typeof html === 'string') {
html = removeMediaFromHtml(html);
}

const {editorState, suggestingMode, onPasteFromSuggestingMode} = this.props;
const {editorState, suggestingMode, onPasteFromSuggestingMode, onChange, editorFormat} = this.props;

if (!html && !text) {
return HANDLED;
return 'handled';
}

if (suggestingMode) {
if (!Suggestions.allowEditSuggestionOnLeft(editorState, author)
&& !Suggestions.allowEditSuggestionOnRight(editorState, author)) {
return HANDLED;
return 'handled';
}

const content = html ? getContentStateFromHtml(html) : ContentState.createFromText(text);

onPasteFromSuggestingMode(content);
return HANDLED;
return 'handled';
}

if (pasteContentFromOpenEditor(html, editorState, onChange, editorFormat) === 'handled') {
return 'handled';
}


if (htmlComesFromDraftjsEditor(html)) {
return NOT_HANDLED;
return 'not-handled';
}

return processPastedHtml(this.props, html || text);
return processPastedHtml(html || text, editorState, onChange, editorFormat);
}

// Checks if there are atomic blocks in the paste content. If there are, we need to set
// the 'atomic' block type using the Modifier tool and add these entities to the
// contentState.
function processPastedHtml(props, html) {
const {onChange, editorState, editorFormat} = props;
let pastedContent = getContentStateFromHtml(html);
const blockMap = pastedContent.getBlockMap();
function insertContentInState(
editorState: EditorState,
pastedContent: ContentState,
onChange: Function,
editorFormat: Array<string>) : DraftHandleValue {
let _pastedContent = pastedContent;
const blockMap = _pastedContent.getBlockMap();
const hasAtomicBlocks = blockMap.some((block) => block.getType() === 'atomic');
const acceptedInlineStyles =
Object.keys(inlineStyles)
Expand All @@ -86,7 +110,7 @@ function processPastedHtml(props, html) {
selection = contentState.getSelectionAfter();
}

pastedContent = sanitizeContent(EditorState.createWithContent(pastedContent), acceptedInlineStyles)
_pastedContent = sanitizeContent(EditorState.createWithContent(_pastedContent), acceptedInlineStyles)
.getCurrentContent();

blockMap.forEach((block) => {
Expand All @@ -95,7 +119,7 @@ function processPastedHtml(props, html) {
}

const entityKey = block.getEntityAt(0);
const entity = pastedContent.getEntity(entityKey);
const entity = _pastedContent.getEntity(entityKey);

contentState = contentState.addEntity(entity);

Expand All @@ -109,9 +133,11 @@ function processPastedHtml(props, html) {
contentState = Modifier.setBlockType(contentState, selection, 'atomic');
}

const newBlockMap = OrderedMap<string, ContentBlock>(blocks.map((b) => ([b.getKey(), b])));

let nextEditorState = EditorState.push(
editorState,
Modifier.replaceWithFragment(contentState, selection, OrderedSet(blocks)),
Modifier.replaceWithFragment(contentState, selection, newBlockMap),
'insert-fragment'
);

Expand All @@ -133,7 +159,22 @@ function processPastedHtml(props, html) {

onChange(nextEditorState);

return HANDLED;
return 'handled';
}

// Checks if there are atomic blocks in the paste content. If there are, we need to set
// the 'atomic' block type using the Modifier tool and add these entities to the
// contentState.
function processPastedHtml(
html: string, editorState: EditorState, onChange: Function, editorFormat: Array<string>) : DraftHandleValue {
let pastedContent = getContentStateFromHtml(html);

return insertContentInState(
editorState,
pastedContent,
onChange,
editorFormat
);
}

// Returns an empty block.
Expand Down
8 changes: 5 additions & 3 deletions scripts/core/editor3/components/tests/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function stateWithLink() {

return RichUtils.toggleLink(
EditorState.createWithContent(contentState),
linkSelection,
linkSelection as SelectionState,
entityKey
);
}
Expand Down Expand Up @@ -174,8 +174,10 @@ export function cursorAtPosition(editorState, pos, n = 0) {
.getFirstBlock()
.getKey();

return EditorState.forceSelection(editorState, SelectionState.createEmpty(blockKey).merge({
const selection = SelectionState.createEmpty(blockKey).merge({
anchorOffset: pos,
focusOffset: pos + n,
}));
}) as SelectionState;

return EditorState.forceSelection(editorState, selection);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {getDraftSelectionForEntireContent} from './getDraftSelectionForEntireCon
import {resizeDraftSelection} from './resizeDraftSelection';
import {clearInlineStyles} from './clearInlineStyles';
import {changeSuggestionsTypes, blockStylesDescription, paragraphSuggestionTypes} from '../highlightsConfig';
import {has} from 'lodash';

export const paragraphSeparator = '¶';

Expand Down Expand Up @@ -266,7 +267,8 @@ export function canAddHighlight(editorState, highlightType) {
* @param {Boolean} firstFound - if true return first style found otherwise return all styles found
* @description the highlight style from the new possition specified by offset.
*/
export function getHighlightStyleAtOffset(editorState, types, selection, offset, fromEnd = false, firstFound = true) {
export function getHighlightStyleAtOffset(
editorState, types, selection, offset, fromEnd = false, firstFound = true) : string | Array<string> {
const {block, newOffset} = getBlockAndOffset(editorState, selection, offset, fromEnd);

if (block == null) {
Expand Down Expand Up @@ -354,7 +356,7 @@ export function getHighlightAuthor(editorState, style) {
* @description the highlight associated data from the new possition specified by offset.
*/
export function getHighlightDataAtOffset(editorState, types, selection, offset, fromEnd = false) {
const style = getHighlightStyleAtOffset(editorState, types, selection, offset, fromEnd);
const style = getHighlightStyleAtOffset(editorState, types, selection, offset, fromEnd) as string;

if (style == null) {
return null;
Expand Down Expand Up @@ -420,7 +422,7 @@ export function addHighlight(editorState, type, data, single = false) {

let newIndex = 0;

if (highlightsState.lastHighlightIds && _.has(highlightsState.lastHighlightIds, type)) {
if (highlightsState.lastHighlightIds && has(highlightsState.lastHighlightIds, type)) {
newIndex = highlightsState.lastHighlightIds[type] + 1;
}
const styleName = type + '-' + newIndex;
Expand Down
2 changes: 1 addition & 1 deletion scripts/core/editor3/html/from-html/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ class HTMLParser {
* @param {string} html
* @param {Object} associations (optional)
*/
export function getContentStateFromHtml(html, associations) {
export function getContentStateFromHtml(html, associations = null) {
return new HTMLParser(html, associations).contentState();
}

Expand Down
16 changes: 7 additions & 9 deletions scripts/core/editor3/reducers/find-replace.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// eslint complains about imported types not being used
// eslint-disable-next-line no-unused-vars
import {EditorChangeType} from 'draft-js';
import {SelectionState, Modifier, EditorState} from 'draft-js';

const findReplace = (state = {}, action) => {
Expand Down Expand Up @@ -208,19 +211,14 @@ export const clearHighlights = (c, es = null) => {


/**
* @name createSelection
* @param {string} key Block key
* @param {number} anchor Anchor offset
* @param {number} focus Focus offset
* @returns {SelectionState}
* @description Creates a new selection state, based on the given block key, having the specified
* Creates a new selection state, based on the given block key, having the specified
* anchor and offset.
*/
const createSelection = (key, start, end) =>
const createSelection = (key: string, start: number, end: number) : SelectionState =>
SelectionState.createEmpty(key).merge({
anchorOffset: start,
focusOffset: end,
});
}) as SelectionState;

/**
* @name forEachMatch
Expand Down Expand Up @@ -269,7 +267,7 @@ const getRegExp = ({pattern, caseSensitive}) =>
* @description Silently pushes the new content state into the given editor state, without
* affecting the undo/redo stack.
*/
export const quietPush = (editorState, content, changeType = 'insert-characters') => {
export const quietPush = (editorState, content, changeType: EditorChangeType = 'insert-characters') => {
let newState;

newState = EditorState.set(editorState, {allowUndo: false});
Expand Down
Loading