Skip to content

Commit

Permalink
remove image drop (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
InsidersByte committed Oct 28, 2017
1 parent 5e09abe commit 2cdc11f
Show file tree
Hide file tree
Showing 4 changed files with 5,475 additions and 227 deletions.
18 changes: 1 addition & 17 deletions README.md
Expand Up @@ -11,7 +11,7 @@

[![NPM](https://nodei.co/npm/@insidersbyte/react-markdown-editor.png?downloads=true&downloadRank=true)](https://nodei.co/npm/@insidersbyte/react-markdown-editor/)

[React](http://facebook.github.io/react) Markdown editor with preview and drag and drop image support (at the moment it always adds the image to the end, regardless of where you drop it), built with [react-markdown-renderer](https://github.com/insidersbyte/react-markdown-renderer).
[React](http://facebook.github.io/react) Markdown editor with preview, built with [react-markdown-renderer](https://github.com/insidersbyte/react-markdown-renderer).

## Demo
http://insidersbyte.github.io/react-markdown-editor
Expand Down Expand Up @@ -41,20 +41,6 @@ class App extends React.Component {
this.updateMarkdown = this.updateMarkdown.bind(this);
}

onImageDrop(file) {
// This is where you would upload your files to whatever storage you are using
// You just need to return a promise with the original filename and the url of the uploaded file

return new Promise((resolve) => {
setTimeout(() => {
resolve({
filename: file.name,
url: 'http://images.freeimages.com/images/previews/b56/hands-2-ok-hand-1241594.jpg',
});
}, 3000);
});
}

updateMarkdown(event) {
this.setState({ markdown: event.target.value });
}
Expand All @@ -64,7 +50,6 @@ class App extends React.Component {
<MarkdownEditor
value={this.state.markdown}
onChange={this.updateMarkdown}
onImageDrop={this.onImageDrop}
/>
);
}
Expand All @@ -77,7 +62,6 @@ ReactDOM.render(<App />, document.getElementById('app'));

* value (*string*) - the raw markdown that will be converted to html (**required**)
* onChange (*function*) - called when a change is made (**required**)
* onImageDrop (*function*) - called per image dropped on the textarea
* options (*object*) - the options for remarkable (see [here](https://github.com/jonschlinkert/remarkable#options)) (default: { })

## Styles
Expand Down
142 changes: 3 additions & 139 deletions __tests__/src/index-test.js
Expand Up @@ -10,159 +10,23 @@ import MarkdownEditor from '../../src';
let markdownEditor;

const value = '';
const validFile = [{ type: 'image/jpeg', name: 'image' }];
const invalidFile = [{ type: 'text/markdown', name: 'markdown' }];
const uploadedImage = { url: 'url', filename: 'image' };

const onChangeMock = jest.genMockFunction();
const onImageDropMock = jest.genMockFunction();

function generateMarkdownEditor(includeOnDropImage = false) {
const markup = includeOnDropImage ?
<MarkdownEditor value={value} onChange={onChangeMock} onImageDrop={onImageDropMock} /> :
<MarkdownEditor value={value} onChange={onChangeMock} />;

return TestUtils.renderIntoDocument(markup);
}

function simulateDrop(files, useDataTransfer = true) {
TestUtils.Simulate.drop(
TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea'),
{ [useDataTransfer ? 'dataTransfer' : 'target']: { files } }
);
function generateMarkdownEditor() {
return TestUtils.renderIntoDocument(<MarkdownEditor value={value} onChange={onChangeMock} />);
}

describe('MarkdownEditor', () => {
beforeEach(() => {
markdownEditor = generateMarkdownEditor();
onChangeMock.mockClear();
onImageDropMock.mockClear();
});

it('renders', () => {
it('renders correctly', () => {
const markdownEditorNode = ReactDOM.findDOMNode(markdownEditor);

expect(markdownEditorNode).toBeDefined();
expect(markdownEditorNode).not.toBe(null);

expect(markdownEditor.state.draggingOver).toBe(false);
});

describe('onDragEnter', () => {
it('sets draggingOver to true', () => {
expect(markdownEditor.state.draggingOver).toBe(false);

TestUtils.Simulate.dragEnter(TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea'));

expect(markdownEditor.state.draggingOver).toBe(true);
});
});

describe('onDragLeave', () => {
it('sets draggingOver to false', () => {
markdownEditor.setState({ draggingOver: true });

TestUtils.Simulate.dragLeave(TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea'));

expect(markdownEditor.state.draggingOver).toBe(false);
});
});

describe('onDrop', () => {
it('sets draggingOver to false', () => {
markdownEditor.setState({ draggingOver: true });

simulateDrop(validFile);

expect(markdownEditor.state.draggingOver).toBe(false);
});

describe('valid images, but no onImageDrop prop passed', () => {
it('does not call onChange', () => {
expect(onChangeMock).not.toBeCalled();

simulateDrop(validFile);

expect(onChangeMock).not.toBeCalled();
});
});

describe('onImageDrop prop passed', () => {
beforeEach(() => {
markdownEditor = generateMarkdownEditor(true);

expect(onChangeMock).not.toBeCalled();
expect(onImageDropMock).not.toBeCalled();
});

describe('invalid files', () => {
afterEach(() => {
expect(onChangeMock).not.toBeCalled();
expect(onImageDropMock).not.toBeCalled();
});

describe('no files passed', () => {
it('does not call onChange or onImageDrop', () => {
simulateDrop([]);
});
});

describe('no images passed', () => {
it('does not call onChange or onImageDrop', () => {
simulateDrop(invalidFile);
});
});
});

describe('invalid response from onImageDrop', () => {
afterEach(() => {
simulateDrop(validFile);

expect(onChangeMock.mock.calls.length).toBe(1);
expect(onChangeMock).toBeCalledWith({ target: { value: '\n![uploading image...]()' } });

expect(onImageDropMock.mock.calls.length).toBe(1);
expect(onImageDropMock).toBeCalledWith(validFile[0]);
});

describe('no filename', () => {
it('does not call onChange twice', () => {
onImageDropMock.mockReturnValueOnce({ url: 'url' });
});
});

describe('no url', () => {
it('does not call onChange twice', () => {
onImageDropMock.mockReturnValueOnce({ filename: 'filename' });
});
});
});

describe('one image passed', () => {
beforeEach(() => {
onImageDropMock.mockReturnValueOnce(uploadedImage);
});

afterEach(() => {
// TODO: This should be two calls but having an issue with promises
expect(onChangeMock.mock.calls.length).toBe(1);
expect(onChangeMock).toBeCalledWith({ target: { value: '\n![uploading image...]()' } });
// expect(onChangeMock).toBeCalledWith({ target: { value: '\n![image](url)' } });

expect(onImageDropMock.mock.calls.length).toBe(1);
expect(onImageDropMock).toBeCalledWith(validFile[0]);
});

it('calls onChange and onImageDrop', () => {
simulateDrop(validFile);
});

describe('one image passed with event.target.files', () => {
it('calls onChange and onImageDrop', () => {
simulateDrop(validFile, false);
});
});
});
});
});
});
72 changes: 1 addition & 71 deletions src/index.js
Expand Up @@ -2,95 +2,25 @@ import React from 'react';
import MarkdownRenderer from 'react-markdown-renderer';
import TextArea from 'react-textarea-autosize';

const imageType = /^image\//;
const placeholderTemplate = (filename) => `![uploading ${filename}...]()`;
const uploadedTemplate = ({ filename, url }) => `![${filename}](${url})`;

export default class MarkdownEditor extends React.Component {
static propTypes = {
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
onImageDrop: React.PropTypes.func,
options: React.PropTypes.object,
};

static defaultProps = {
options: {},
};

state = {
draggingOver: false,
};

onDragEnter = () => {
this.setState({ draggingOver: true });
};

onDragLeave = () => {
this.setState({ draggingOver: false });
};

onImageDrop = (event) => {
event.preventDefault();

this.setState({ draggingOver: false });

if (!this.props.onImageDrop) {
return;
}

const files = event.dataTransfer ? event.dataTransfer.files : event.target.files;
const filesArray = [...files];
const images = filesArray.filter(o => imageType.test(o.type));

if (images.length === 0) {
return;
}

const imageFileNames = images.map(o => placeholderTemplate(o.name));
const imagePlaceholders = imageFileNames.join('\n');

this.props.onChange({
target: {
value: `${this.props.value}\n${imagePlaceholders}`,
},
});

for (const image of images) {
Promise
.resolve(this.props.onImageDrop(image))
.then(({ filename, url }) => {
if (!filename || !url) {
return;
}

const templateString = placeholderTemplate(filename);
const uploadedString = uploadedTemplate({ filename, url });
const value = this.props.value.replace(templateString, uploadedString);

this.props.onChange({
target: {
value,
},
});
});
}
};

render() {
let textAreaClassName = 'markdown-editor__textarea';

if (this.state.draggingOver) {
textAreaClassName += ' markdown-editor__textarea--dragover';
}

return (
<div className="markdown-editor">
<div className="markdown-editor__editor-container">
<h2>Markdown</h2>

<TextArea
className={textAreaClassName}
className="markdown-editor__textarea"
value={this.props.value}
onChange={this.props.onChange}
onDrop={this.onImageDrop}
Expand Down

0 comments on commit 2cdc11f

Please sign in to comment.