superdesk/superdesk-client-core

View on GitHub
scripts/core/editor3/components/BaseUnstyledComponent.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import React from 'react';

import {
    getValidMediaType,
    canDropMedia,
    EVENT_TYPES_TRIGGER_DROP_ZONE,
    IPropsEditor3Component,
} from './Editor3Component';
import {moveBlock, dragDrop, embed} from '../actions/editor3';
import {getEmbedObject} from './embeds/EmbedInput';
import {htmlComesFromDraftjsEditor} from 'core/editor3/helpers/htmlComesFromDraftjsEditor';
import {htmlIsPlainTextDragged} from 'core/editor3/helpers/htmlIsPlainTextDragged';
import {EDITOR_BLOCK_TYPE, MIME_TYPE_SUPERDESK_TEXT_ITEM} from '../constants';
import {RICH_FORMATTING_OPTION} from 'superdesk-api';
import {notify} from 'core/notify/notify';
import {articleEmbedsConfigured} from './article-embed/can-add-article-embed';
import {gettext} from 'core/utils';

export function isEditorBlockEvent(event) {
    return event.originalEvent.dataTransfer.types.indexOf(EDITOR_BLOCK_TYPE) > -1;
}

export function getEditorBlock(event) {
    return event.originalEvent.dataTransfer.getData(EDITOR_BLOCK_TYPE);
}

function embedShouldBeCreated(html, editorProps): boolean {
    if (!editorProps.editorFormat.includes('embed')) {
        return false;
    }

    const comingFromDraftJS = htmlComesFromDraftjsEditor(html);
    const shouldEmbedBeCreated = comingFromDraftJS ? false : !htmlIsPlainTextDragged(html);

    return shouldEmbedBeCreated;
}

function isHtmlTextAndShouldCreateEmbed(event, mediaType, editorProps): boolean {
    if (mediaType !== 'text/html') {
        return false;
    }

    const html = event.originalEvent.dataTransfer.getData(mediaType);

    return embedShouldBeCreated(html, editorProps);
}

export function dragEventShouldShowDropZone(event, editorProps: IPropsEditor3Component) {
    if (event.dataTransfer.types.includes(MIME_TYPE_SUPERDESK_TEXT_ITEM)) {
        return articleEmbedsConfigured(editorProps);
    }

    const mediaFormattingOption: RICH_FORMATTING_OPTION = 'media';
    const intersection = EVENT_TYPES_TRIGGER_DROP_ZONE.filter((type) => event.dataTransfer.types.includes(type));

    return editorProps.editorFormat.includes(mediaFormattingOption) && intersection.length > 0;
}

interface IProps {
    dispatch(action: any);
    editorProps: any;
    className?: string;
}

interface IState {
    over: boolean;
}

class BaseUnstyledComponent extends React.Component<IProps, IState> {
    static propTypes: any;
    static defaultProps: any;

    getDropBlockKey: any;
    dropInsertionMode: any;
    leaveTimeout: any;
    div: any;

    constructor(props) {
        super(props);
        this.onDrop = this.onDrop.bind(this);
        this.onDragOver = this.onDragOver.bind(this);
        this.onDragLeave = this.onDragLeave.bind(this);
        this.state = {over: false};
    }

    onDrop(event) {
        this.setState({over: false});

        let handled = false;
        const block = getEditorBlock(event);

        const {dataTransfer} = event.originalEvent;

        if (dataTransfer.types.includes(MIME_TYPE_SUPERDESK_TEXT_ITEM)) {
            if (articleEmbedsConfigured(this.props.editorProps)) {
                this.props.dispatch(dragDrop(
                    dataTransfer,
                    MIME_TYPE_SUPERDESK_TEXT_ITEM,
                    this.getDropBlockKey(),
                    this.props.editorProps.canAddArticleEmbed,
                ));
            } else {
                notify.error(gettext('Embedding articles is not configured for this content profile field'));
            }

            handled = true;
        } else if (typeof block === 'string' && block.length > 0) {
            // existing media item dropped to another place
            this.props.dispatch(moveBlock(block, this.getDropBlockKey(), this.dropInsertionMode));
            handled = true;
        } else {
            const mediaType = getValidMediaType(event.originalEvent);
            const blockKey = this.getDropBlockKey();
            const link = event.originalEvent.dataTransfer.getData('URL');

            if (canDropMedia(event, this.props.editorProps)
                && (mediaType === 'Files' || mediaType.includes('application/superdesk'))) {
                this.props.dispatch(dragDrop(dataTransfer, mediaType, blockKey));
                handled = true;
            } else if (
                typeof link === 'string'
                && link.startsWith('http')
                && this.props.editorProps.editorFormat.includes('embed')
            ) {
                getEmbedObject(link)
                    .then((oEmbed) => {
                        this.props.dispatch(embed(oEmbed, blockKey));
                    })
                    .catch((err) => {
                        notify.error(err.description ?? gettext('This link is not embeddable.'));
                    });

                // Condition was handled regardless of the getEmbedObject result
                handled = true;
            } else if (isHtmlTextAndShouldCreateEmbed(event, mediaType, this.props.editorProps)) {
                this.props.dispatch(embed(event.originalEvent.dataTransfer.getData(mediaType), blockKey));
                handled = true;
            } else {
                console.warn('unsupported media type on drop', mediaType);
            }
        }

        if (handled) {
            event.preventDefault();
            event.stopPropagation();
        }
    }

    onDragOver(event) {
        if (!dragEventShouldShowDropZone(event.originalEvent, this.props.editorProps)) {
            return;
        }

        if (this.leaveTimeout) {
            clearTimeout(this.leaveTimeout);
            this.leaveTimeout = null;
        }

        event.preventDefault();
        event.stopPropagation();
        this.setState({over: true});
    }

    onDragLeave(event) {
        if (!dragEventShouldShowDropZone(event.originalEvent, this.props.editorProps)) {
            return;
        }

        event.stopPropagation();
        if (this.state.over && !this.leaveTimeout) {
            this.leaveTimeout = setTimeout(() => {
                this.setState({over: false});
                this.leaveTimeout = null;
            }, 50); // avoid placeholder flickering
        }
    }

    componentDidMount() {
        $(this.div).on('drop', this.onDrop);
        $(this.div).on('dragleave', this.onDragLeave);
        $(this.div).on('dragover dragenter', this.onDragOver);
    }

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

export default BaseUnstyledComponent;