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
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"main": "scripts/index.js",
"dependencies": {
"@types/angular": "1.6.50",
"@types/draft-js": "0.10.24",
"@types/lodash": "4.14.116",
"@types/react": "16.4.9",
"angular": "1.6.9",
Expand All @@ -44,9 +45,6 @@
"angular-resource": "1.6.9",
"angular-route": "1.6.9",
"angular-vs-repeat": "1.1.7",
"ts-loader": "3.5.0",
"tslint": "5.11.0",
"typescript": "3.0.1",
"bootstrap": "3.3.7",
"classnames": "2.2.5",
"css-loader": "0.28.10",
Expand Down Expand Up @@ -103,6 +101,9 @@
"shortid": "2.2.8",
"style-loader": "0.20.2",
"superdesk-ui-framework": "^1.1.0",
"ts-loader": "3.5.0",
"tslint": "5.11.0",
"typescript": "3.0.1",
"webpack": "3.11.0",
"webpack-dev-server": "2.11.1"
},
Expand All @@ -120,6 +121,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 @@ -135,6 +137,7 @@
"lint": "tsc --noEmit --version && tslint -c tslint.json 'scripts/**/*.ts' && 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: 5 additions & 5 deletions scripts/business-logic/SuperdeskGlobalConfig.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ export interface ISuperdeskGlobalConfig {
savedSearch: {
subscriptions: boolean;
}
}
};
auth: {
google: boolean
}
google: boolean,
};
ingest: {
PROVIDER_DASHBOARD_DEFAULTS: {
show_log_messages: boolean;
Expand All @@ -29,5 +29,5 @@ export interface ISuperdeskGlobalConfig {
hours: number;
minutes: number;
};
}
}
};
}
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: () => void, editorFormat: Array<string>): DraftHandleValue {
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));
const contentState = ContentState.createFromBlockArray(blocksArray);

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

return 'not-handled';
}

/**
* @ngdoc method
Expand All @@ -31,46 +50,50 @@ 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: () => void,
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 +109,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,24 +118,26 @@ function processPastedHtml(props, html) {
}

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

contentState = contentState.addEntity(entity);

blocks = blocks.concat(
atomicBlock(block.getData(), contentState.getLastCreatedEntityKey()),
emptyBlock()
emptyBlock(),
);
});

if (hasAtomicBlocks) {
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)),
'insert-fragment'
Modifier.replaceWithFragment(contentState, selection, newBlockMap),
'insert-fragment',
);

const selectionAfterInsert = nextEditorState.getSelection();
Expand All @@ -126,14 +151,29 @@ function processPastedHtml(props, html) {
nextEditorState = EditorState.push(
editorState,
nextEditorState.getCurrentContent(),
'insert-fragment'
'insert-fragment',
);

nextEditorState = EditorState.forceSelection(nextEditorState, selectionAfterInsert);

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: () => void, editorFormat: Array<string>): DraftHandleValue {
const 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);
}
Loading