<commit_before>import Webiny from 'Webiny';
const Ui = Webiny.Ui.Components;

class HtmlEditor extends Webiny.Ui.FormComponent {
    constructor(props) {
        super(props);


        this.toolbarOptions = [
            ['bold', 'italic', 'underline', 'strike'],        // toggled buttons
            ['blockquote', 'code-block', 'link', 'image'],

            [{'list': 'ordered'}, {'list': 'bullet'}],
            [{'indent': '-1'}, {'indent': '+1'}],          // outdent/indent

            [{'size': ['small', false, 'large', 'huge']}],  // custom dropdown
            [{'header': [1, 2, 3, 4, 5, 6, false]}],

            [{'color': []}, {'background': []}],          // dropdown with defaults from theme
            [{'font': []}],
            [{'align': []}],

            ['clean']                                         // remove formatting button
        ];

        this.editor = null;
        this.delay = null;
        this.api = new Webiny.Api.Endpoint(props.imageApi);
        this.uploader = new Ui.Files.FileUploader(this.api);

        _.merge(this.state, {
            cropImage: null,
            uploadPercentage: null,
            value: props.valueLink.value
        });

        this.bindMethods('getTextareaElement,getEditor,getCropper,onCropperHidden,uploadImage,fileChanged,applyValue,changed,renderError');
    }

    componentDidMount() {
        super.componentDidMount();

        this.editor = new Quill(this.getTextareaElement(), {
            modules: {
                toolbar: this.toolbarOptions
            },
            theme: 'snow',
            bounds: document.body
        });

        const toolbar = this.editor.getModule('toolbar');
        toolbar.addHandler('image', () => {
            this.refs.reader.getFiles();
        });

        this.editor.on('text-change', () => {
            this.setState({value: this.editor.root.innerHTML}, this.changed);
        });

        this.editor.pasteHTML(this.props.valueLink.value);

        window.qe = this.editor;
    }

    componentWillReceiveProps(props) {
        super.componentWillReceiveProps(props);
        if (!this.delay && props.valueLink.value != this.editor.root.innerHTML) {
            this.editor.pasteHTML(props.valueLink.value);
        }
    }

    shouldComponentUpdate(nextProps, nextState) {
        const oldState = _.pick(this.state, ['cropImage', 'uploadPercentage', 'error']);
        const newState = _.pick(nextState, ['cropImage', 'uploadPercentage', 'error']);
        return !_.isEqual(oldState, newState);
    }

    componentDidUpdate() {
        super.componentDidUpdate();
        this.editor.pasteHTML(this.state.value);
    }

    componentWillUnmount() {
        delete this.editor;
    }

    applyValue(value) {
        clearTimeout(this.delay);
        this.delay = null;
        this.props.valueLink.requestChange(value);
    }

    changed() {
        clearTimeout(this.delay);
        this.delay = null;
        this.delay = setTimeout(() => this.applyValue(this.state.value), 300);
    }

    fileChanged(file, error) {
        if (error) {
            this.setState({error});
            return;
        }

        this.setState({error: null});

        if (this.props.cropper) {
            this.setState({cropImage: file});
        } else {
            this.uploadImage(file);
        }
    }

    uploadImage(data) {
        this.uploader.upload(data, (percentage) => {
            this.setState({uploadPercentage: percentage});
        }, file => {
            this.editor.insertEmbed(this.editor.getSelection(true).index, 'image', file.src);
            this.setState({uploadPercentage: null});
        });
    }

    getCropper(children = null) {
        const cropper = this.props.cropper;

        if (!cropper) {
            return null;
        }

        if (cropper.inline) {
            return (
                <Ui.Files.InlineFileCropper
                    title={cropper.title}
                    action={cropper.action}
                    onHidden={this.onCropperHidden}
                    onCrop={this.uploadImage}
                    config={cropper.config}
                    image={this.state.cropImage}>
                    {children}
                </Ui.Files.InlineFileCropper>
            );
        }

        return (
            <Ui.Files.ModalFileCropper
                title={cropper.title}
                action={cropper.action}
                onHidden={this.onCropperHidden}
                onCrop={this.uploadImage}
                config={cropper.config}
                image={this.state.cropImage}>
                {children}
            </Ui.Files.ModalFileCropper>
        );
    }

    onCropperHidden() {
        this.setState({cropImage: null});
    }

    getEditor() {
        return this.editor;
    }

    getTextareaElement() {
        return ReactDOM.findDOMNode(this).querySelector('.editor');
    }

    renderError() {
        let error = null;
        if (this.state.error) {
            error = (
                <Ui.Alert type="error">{this.state.error.message}</Ui.Alert>
            );
        }
        return error;
    }
}

HtmlEditor.defaultProps = {
    imageApi: '/entities/core/images',
    accept: ['image/jpg', 'image/jpeg', 'image/gif', 'image/png'],
    sizeLimit: 2485760,
    label: null,
    description: null,
    info: null,
    tooltip: null,
    cropper: {
        title: 'Crop your image',
        action: 'Insert image',
        config: {
            closeOnClick: false,
            autoCropArea: 0.7,
            guides: false,
            strict: true,
            mouseWheelZoom: true,
            touchDragZoom: false
        }
    },
    renderer() {
        let uploader = null;
        if (this.state.uploadPercentage != null) {
            uploader = (<commit_msg>Moved toolbar options to props.<commit_after>import Webiny from 'Webiny';
const Ui = Webiny.Ui.Components;

class HtmlEditor extends Webiny.Ui.FormComponent {
    constructor(props) {
        super(props);


        this.editor = null;
        this.delay = null;
        this.api = new Webiny.Api.Endpoint(props.imageApi);
        this.uploader = new Ui.Files.FileUploader(this.api);

        _.merge(this.state, {
            cropImage: null,
            uploadPercentage: null,
            value: props.valueLink.value
        });

        this.bindMethods('getTextareaElement,getEditor,getCropper,onCropperHidden,uploadImage,fileChanged,applyValue,changed,renderError');
    }

    componentDidMount() {
        super.componentDidMount();

        this.editor = new Quill(this.getTextareaElement(), {
            modules: {
                toolbar: this.props.toolbar
            },
            theme: 'snow',
            bounds: document.body
        });

        const toolbar = this.editor.getModule('toolbar');
        toolbar.addHandler('image', () => {
            this.refs.reader.getFiles();
        });

        this.editor.on('text-change', () => {
            this.setState({value: this.editor.root.innerHTML}, this.changed);
        });

        this.editor.pasteHTML(this.props.valueLink.value);
    }

    componentWillReceiveProps(props) {
        super.componentWillReceiveProps(props);
        if (!this.delay && props.valueLink.value != this.editor.root.innerHTML) {
            this.editor.pasteHTML(props.valueLink.value);
        }
    }

    shouldComponentUpdate(nextProps, nextState) {
        const oldState = _.pick(this.state, ['cropImage', 'uploadPercentage', 'error']);
        const newState = _.pick(nextState, ['cropImage', 'uploadPercentage', 'error']);
        return !_.isEqual(oldState, newState);
    }

    componentDidUpdate() {
        super.componentDidUpdate();
        this.editor.pasteHTML(this.state.value);
    }

    componentWillUnmount() {
        delete this.editor;
    }

    applyValue(value) {
        clearTimeout(this.delay);
        this.delay = null;
        this.props.valueLink.requestChange(value);
    }

    changed() {
        clearTimeout(this.delay);
        this.delay = null;
        this.delay = setTimeout(() => this.applyValue(this.state.value), 300);
    }

    fileChanged(file, error) {
        if (error) {
            this.setState({error});
            return;
        }

        this.setState({error: null});

        if (this.props.cropper) {
            this.setState({cropImage: file});
        } else {
            this.uploadImage(file);
        }
    }

    uploadImage(data) {
        this.uploader.upload(data, (percentage) => {
            this.setState({uploadPercentage: percentage});
        }, file => {
            this.editor.insertEmbed(this.editor.getSelection(true).index, 'image', file.src);
            this.setState({uploadPercentage: null});
        });
    }

    getCropper(children = null) {
        const cropper = this.props.cropper;

        if (!cropper) {
            return null;
        }

        if (cropper.inline) {
            return (
                <Ui.Files.InlineFileCropper
                    title={cropper.title}
                    action={cropper.action}
                    onHidden={this.onCropperHidden}
                    onCrop={this.uploadImage}
                    config={cropper.config}
                    image={this.state.cropImage}>
                    {children}
                </Ui.Files.InlineFileCropper>
            );
        }

        return (
            <Ui.Files.ModalFileCropper
                title={cropper.title}
                action={cropper.action}
                onHidden={this.onCropperHidden}
                onCrop={this.uploadImage}
                config={cropper.config}
                image={this.state.cropImage}>
                {children}
            </Ui.Files.ModalFileCropper>
        );
    }

    onCropperHidden() {
        this.setState({cropImage: null});
    }

    getEditor() {
        return this.editor;
    }

    getTextareaElement() {
        return ReactDOM.findDOMNode(this).querySelector('.editor');
    }

    renderError() {
        let error = null;
        if (this.state.error) {
            error = (
                <Ui.Alert type="error">{this.state.error.message}</Ui.Alert>
            );
        }
        return error;
    }
}

HtmlEditor.defaultProps = {
    imageApi: '/entities/core/images',
    accept: ['image/jpg', 'image/jpeg', 'image/gif', 'image/png'],
    sizeLimit: 2485760,
    label: null,
    description: null,
    info: null,
    tooltip: null,
    toolbar: [
        ['bold', 'italic', 'underline', 'strike'],
        ['blockquote', 'code-block', 'link', 'image'],
        [{'list': 'ordered'}, {'list': 'bullet'}],
        [{'indent': '-1'}, {'indent': '+1'}],
        [{'size': ['small', false, 'large', 'huge']}],
        [{'header': [1, 2, 3, 4, 5, 6, false]}],
        [{'color': []}, {'background': []}],
        [{'font': []}],
        [{'align': []}],
        ['clean']
    ],
    cropper: {
        title: 'Crop your image',
        action: 'Insert image',
        config: {
            closeOnClick: false,
            autoCropArea: 0.7,
            guides: false,
            strict: true,
            mouseWheelZoom: true,
            touchDragZoom: false
        }
    },
    renderer() {
        let uploader = null;
        if (this.state.uploadPercentage != null) {
            uploader = (
{'fraction_non_alphanumeric': 0.1399576420755383, 'fraction_numerical': 0.0035298270384751147, 'mean_word_length': 1.7715822939594033, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2481843', 'n_tokens_mistral': 3337, 'n_tokens_neox': 3178, 'n_words': 527}