Implemented drag and drop file uploads
This commit is contained in:
@@ -54,6 +54,7 @@
|
|||||||
"normalize.css": "^3.0.3",
|
"normalize.css": "^3.0.3",
|
||||||
"react": "^0.14.7",
|
"react": "^0.14.7",
|
||||||
"react-codemirror": "^0.2.5",
|
"react-codemirror": "^0.2.5",
|
||||||
|
"react-dropzone": "^3.3.2",
|
||||||
"react-medium-editor": "^1.6.2",
|
"react-medium-editor": "^1.6.2",
|
||||||
"react-redux": "^4.4.0",
|
"react-redux": "^4.4.0",
|
||||||
"react-router": "^2.0.0",
|
"react-router": "^2.0.0",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import keyMirror from 'fbjs/lib/keyMirror';
|
|||||||
export const UPDATE_TEXT = 'UPDATE_TEXT';
|
export const UPDATE_TEXT = 'UPDATE_TEXT';
|
||||||
export const TOGGLE_EDITORS = 'TOGGLE_EDITORS';
|
export const TOGGLE_EDITORS = 'TOGGLE_EDITORS';
|
||||||
export const ADD_REVISION = 'ADD_REVISION';
|
export const ADD_REVISION = 'ADD_REVISION';
|
||||||
|
export const REPLACE_TEXT= 'REPLACE_TEXT';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Other Constants
|
* Other Constants
|
||||||
@@ -32,3 +33,7 @@ export function toggleEditors(toggledEditor) {
|
|||||||
export function addRevision(createdAt) {
|
export function addRevision(createdAt) {
|
||||||
return { type: ADD_REVISION, createdAt };
|
return { type: ADD_REVISION, createdAt };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function replaceText(originalText, replacedText) {
|
||||||
|
return { type: REPLACE_TEXT, originalText, replacedText };
|
||||||
|
}
|
||||||
@@ -3,14 +3,22 @@ import Codemirror from 'react-codemirror';
|
|||||||
import 'codemirror/mode/gfm/gfm';
|
import 'codemirror/mode/gfm/gfm';
|
||||||
import 'codemirror/mode/javascript/javascript';
|
import 'codemirror/mode/javascript/javascript';
|
||||||
import 'codemirror/addon/edit/continuelist';
|
import 'codemirror/addon/edit/continuelist';
|
||||||
|
import Dropzone from 'react-dropzone';
|
||||||
|
|
||||||
import styles from './MarkdownEditor.scss';
|
import styles from './MarkdownEditor.scss';
|
||||||
import './codemirror.css';
|
import './codemirror.css';
|
||||||
|
|
||||||
|
import { client } from '../../Utils/ApiClient';
|
||||||
|
|
||||||
class MarkdownAtlas extends React.Component {
|
class MarkdownAtlas extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
text: React.PropTypes.string,
|
text: React.PropTypes.string,
|
||||||
onChange: React.PropTypes.func,
|
onChange: React.PropTypes.func,
|
||||||
|
replaceText: React.PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
getEditorInstance = () => {
|
||||||
|
return this.refs.editor.getCodeMirror();
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (newText) => {
|
onChange = (newText) => {
|
||||||
@@ -19,6 +27,63 @@ class MarkdownAtlas extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
console.log(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDropAccepted = (files) => {
|
||||||
|
const file = files[0];
|
||||||
|
const editor = this.getEditorInstance();
|
||||||
|
|
||||||
|
const cursorPosition = editor.getCursor();
|
||||||
|
const insertOnNewLine = cursorPosition.ch !== 0;
|
||||||
|
let newCursorPositionLine;
|
||||||
|
|
||||||
|
// Lets set up the upload text
|
||||||
|
const pendingUploadTag = ``;
|
||||||
|
if (insertOnNewLine) {
|
||||||
|
editor.replaceSelection('\n' + pendingUploadTag + '\n');
|
||||||
|
newCursorPositionLine = cursorPosition.line + 3;
|
||||||
|
} else {
|
||||||
|
editor.replaceSelection(pendingUploadTag + '\n');
|
||||||
|
newCursorPositionLine = cursorPosition.line + 2;
|
||||||
|
}
|
||||||
|
editor.setCursor(newCursorPositionLine, 0);
|
||||||
|
|
||||||
|
client.post('/v0/user/s3', {
|
||||||
|
kind: file.type,
|
||||||
|
size: file.size,
|
||||||
|
filename: file.name,
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
// Upload using FormData API
|
||||||
|
let formData = new FormData();
|
||||||
|
|
||||||
|
for (let key in data.form) {
|
||||||
|
formData.append(key, data.form[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.blob) {
|
||||||
|
formData.append('file', file.file);
|
||||||
|
} else {
|
||||||
|
formData.append('file', file);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(data.upload_url, {
|
||||||
|
method: 'post',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(s3Response => {
|
||||||
|
this.props.replaceText(pendingUploadTag, ``);
|
||||||
|
editor.setCursor(newCursorPositionLine, 0);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.props.replaceText(pendingUploadTag, '');
|
||||||
|
editor.setCursor(newCursorPositionLine, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
// https://github.com/jbt/markdown-editor/blob/master/index.html
|
// https://github.com/jbt/markdown-editor/blob/master/index.html
|
||||||
const options = {
|
const options = {
|
||||||
@@ -39,12 +104,21 @@ class MarkdownAtlas extends React.Component {
|
|||||||
// - Emojify
|
// - Emojify
|
||||||
// -
|
// -
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<div>
|
||||||
|
<Dropzone
|
||||||
|
onDropAccepted={this.onDropAccepted}
|
||||||
|
disableClick={true}
|
||||||
|
multiple={false}
|
||||||
|
accept={'image/*'}
|
||||||
|
className={styles.container}
|
||||||
|
>
|
||||||
<Codemirror
|
<Codemirror
|
||||||
value={this.props.text}
|
value={this.props.text}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
options={options}
|
options={options}
|
||||||
|
ref="editor"
|
||||||
/>
|
/>
|
||||||
|
</Dropzone>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
@import url(https://fonts.googleapis.com/css?family=Cousine:400,700,700italic,400italic);
|
@import url(https://fonts.googleapis.com/css?family=Cousine:400,700,700italic,400italic);
|
||||||
|
|
||||||
|
/* Custom styling */
|
||||||
.cm-s-atlas.CodeMirror {
|
.cm-s-atlas.CodeMirror {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
color: #202020;
|
color: #202020;
|
||||||
@@ -21,6 +22,11 @@
|
|||||||
background: #90CAF9;
|
background: #90CAF9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Disable ondrag cursor for file uploads */
|
||||||
|
.cm-s-atlas div.CodeMirror-dragcursors {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.cm-s-atlas .CodeMirror-line::selection,
|
.cm-s-atlas .CodeMirror-line::selection,
|
||||||
.cm-s-atlas .CodeMirror-line > span::selection,
|
.cm-s-atlas .CodeMirror-line > span::selection,
|
||||||
.cm-s-atlas .CodeMirror-line > span > span::selection {
|
.cm-s-atlas .CodeMirror-line > span > span::selection {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const keys = keyMirror({
|
|||||||
// Constant values
|
// Constant values
|
||||||
const constants = {
|
const constants = {
|
||||||
API_USER_AGENT: `${name}/${version}`,
|
API_USER_AGENT: `${name}/${version}`,
|
||||||
API_BASE_URL: 'http://localhost:3000/api',
|
API_BASE_URL: 'http://localhost:8000/api',
|
||||||
LOGIN_PATH: '/login',
|
LOGIN_PATH: '/login',
|
||||||
LOGIN_SUCCESS_PATH: '/dashboard',
|
LOGIN_SUCCESS_PATH: '/dashboard',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
TOGGLE_EDITORS,
|
TOGGLE_EDITORS,
|
||||||
TOGGLE_HISTORY_SIDEBAR,
|
TOGGLE_HISTORY_SIDEBAR,
|
||||||
ADD_REVISION,
|
ADD_REVISION,
|
||||||
|
REPLACE_TEXT,
|
||||||
ActiveEditors,
|
ActiveEditors,
|
||||||
} from '../Actions';
|
} from '../Actions';
|
||||||
|
|
||||||
@@ -89,6 +90,14 @@ const text = (state = textDefaultState, action) => {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case REPLACE_TEXT: {
|
||||||
|
const newText = state.text.replace(action.originalText, action.replacedText);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
text: newText,
|
||||||
|
};
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ import MarkdownEditor from '../../Components/MarkdownEditor';
|
|||||||
import TextEditor from '../../Components/TextEditor';
|
import TextEditor from '../../Components/TextEditor';
|
||||||
|
|
||||||
import { toMarkdown } from '../../Utils/Markdown';
|
import { toMarkdown } from '../../Utils/Markdown';
|
||||||
import { updateText } from '../../Actions';
|
import {
|
||||||
|
updateText,
|
||||||
|
replaceText,
|
||||||
|
} from '../../Actions';
|
||||||
|
|
||||||
|
import Constants from '../../Constants';
|
||||||
|
|
||||||
import styles from './Dashboard.scss';
|
import styles from './Dashboard.scss';
|
||||||
|
|
||||||
@@ -14,28 +19,26 @@ class Dashboard extends Component {
|
|||||||
editMarkdown: React.PropTypes.func.isRequired,
|
editMarkdown: React.PropTypes.func.isRequired,
|
||||||
editText: React.PropTypes.func.isRequired,
|
editText: React.PropTypes.func.isRequired,
|
||||||
text: React.PropTypes.string,
|
text: React.PropTypes.string,
|
||||||
|
replaceText: React.PropTypes.func.isRequired,
|
||||||
activeEditors: React.PropTypes.arrayOf(React.PropTypes.string),
|
activeEditors: React.PropTypes.arrayOf(React.PropTypes.string),
|
||||||
showHistorySidebar: React.PropTypes.bool.isRequired,
|
showHistorySidebar: React.PropTypes.bool.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
// componentDidMount = () => {
|
|
||||||
// client.get('/user')
|
|
||||||
// .then(data => {
|
|
||||||
// this.setState({ user: data });
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const activeEditors = this.props.activeEditors;
|
const activeEditors = this.props.activeEditors;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<div className={styles.container}>
|
||||||
{
|
{
|
||||||
activeEditors.includes('MARKDOWN') ? (
|
activeEditors.includes('MARKDOWN') ? (
|
||||||
<div className={ `${activeEditors.length > 1 ?
|
<div className={ `${activeEditors.length > 1 ?
|
||||||
styles.panel : styles.fullscreen} ${styles.markdown}`}
|
styles.panel : styles.fullscreen} ${styles.markdown}`}
|
||||||
>
|
>
|
||||||
<MarkdownEditor onChange={this.props.editMarkdown} text={this.props.text} />
|
<MarkdownEditor
|
||||||
|
onChange={this.props.editMarkdown}
|
||||||
|
text={this.props.text}
|
||||||
|
replaceText={this.props.replaceText}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
@@ -72,6 +75,9 @@ const mapDispatchToProps = (dispatch) => {
|
|||||||
const text = toMarkdown(html);
|
const text = toMarkdown(html);
|
||||||
dispatch(updateText(text, 'text'));
|
dispatch(updateText(text, 'text'));
|
||||||
},
|
},
|
||||||
|
replaceText: (originalText, replacedText) => {
|
||||||
|
dispatch(replaceText(originalText, replacedText));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user