markuapad 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.babelrc +3 -0
- data/.gitignore +30 -0
- data/.jshintrc +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +30 -0
- data/Rakefile +1 -0
- data/app/assets/javascripts/markuapad.js +72 -0
- data/app/assets/stylesheets/markuapad.css +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/bower.json +30 -0
- data/build/app.css +1 -0
- data/build/app.js +72 -0
- data/build/browser-polyfill.min.js +3 -0
- data/build/dist.css +1 -0
- data/build/dist.js +72 -0
- data/build/index.css +1 -0
- data/build/index.html +30 -0
- data/build/index.js +1 -0
- data/build/web.css +1 -0
- data/build/web.js +72 -0
- data/lib/markuapad.rb +5 -0
- data/lib/markuapad/engine.rb +4 -0
- data/lib/markuapad/version.rb +3 -0
- data/markuapad.gemspec +32 -0
- data/package.json +55 -0
- data/prepare_gem.sh +3 -0
- data/src/dist.js +7 -0
- data/src/file_accessor.js +77 -0
- data/src/index.js +180 -0
- data/src/jsx/editor.jsx +122 -0
- data/src/jsx/file_browser.jsx +252 -0
- data/src/jsx/file_browser_list_item.jsx +86 -0
- data/src/jsx/image_modal.jsx +49 -0
- data/src/jsx/live_preview.jsx +36 -0
- data/src/jsx/main.jsx +145 -0
- data/src/jsx/preview.jsx +32 -0
- data/src/jsx/toolbar.jsx +24 -0
- data/src/markuapad.js +39 -0
- data/src/styles/_editor.scss +27 -0
- data/src/styles/_extendables.scss +115 -0
- data/src/styles/_file_browser.scss +160 -0
- data/src/styles/_grid-settings.scss +13 -0
- data/src/styles/_layout.scss +18 -0
- data/src/styles/_modal.scss +145 -0
- data/src/styles/_preview.scss +65 -0
- data/src/styles/_toolbar.scss +99 -0
- data/src/styles/_variables.scss +25 -0
- data/src/styles/_workspace.scss +31 -0
- data/src/styles/app.scss +52 -0
- data/src/styles/index.scss +41 -0
- data/src/util.js +43 -0
- data/src/web.js +4 -0
- data/test/editor.es6 +6 -0
- data/webpack.config.js +48 -0
- metadata +129 -0
data/src/jsx/editor.jsx
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ace from "brace";
|
|
3
|
+
import javascript from "brace/mode/markdown";
|
|
4
|
+
import monokai from "brace/theme/xcode";
|
|
5
|
+
import emacsKeys from "brace/keybinding/emacs"
|
|
6
|
+
import FileAccessor from "../file_accessor";
|
|
7
|
+
import _ from "underscore";
|
|
8
|
+
|
|
9
|
+
class Editor extends React.Component {
|
|
10
|
+
constructor(props) {
|
|
11
|
+
super(props);
|
|
12
|
+
this.onCurrentFileLoaded = this.onCurrentFileLoaded.bind(this);
|
|
13
|
+
|
|
14
|
+
// Make the editor changed function not spammy
|
|
15
|
+
this.onEditorChanged = _.debounce(this.onEditorChanged.bind(this), 100);
|
|
16
|
+
this.onCursorChanged = this.onCursorChanged.bind(this)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Setup all the editor options when we mount.
|
|
20
|
+
componentDidMount() {
|
|
21
|
+
this.setupEditor();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// If we get a file change from outside our domain, switch to it.
|
|
25
|
+
// TODO: maybe show a merge warning or something?
|
|
26
|
+
componentDidUpdate(lastProps, lastState) {
|
|
27
|
+
if (lastProps.currentFile !== this.props.currentFile) {
|
|
28
|
+
this.setupEditor();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setupEditor() {
|
|
33
|
+
if (!this.props.currentFile) return;
|
|
34
|
+
|
|
35
|
+
// Setup the ace editor
|
|
36
|
+
this.editor = ace.edit(this.refs.editor.getDOMNode());
|
|
37
|
+
this.editor.getSession().setMode('ace/mode/markdown');
|
|
38
|
+
this.editor.setTheme('ace/theme/xcode');
|
|
39
|
+
this.editor.setKeyboardHandler('ace/keyboard/emacs');
|
|
40
|
+
this.editor.renderer.setShowGutter(false);
|
|
41
|
+
this.editor.renderer.setPadding(20);
|
|
42
|
+
this.editor.renderer.setScrollMargin(20);
|
|
43
|
+
this.editor.setShowPrintMargin(false);
|
|
44
|
+
this.editor.setFontSize(14);
|
|
45
|
+
this.editor.$blockScrolling = Infinity;
|
|
46
|
+
this.editor.session.setUseWrapMode(true);
|
|
47
|
+
this.editor.setHighlightActiveLine(false);
|
|
48
|
+
|
|
49
|
+
// When the editor changes, alert us
|
|
50
|
+
this.editor.on("change", this.onEditorChanged)
|
|
51
|
+
this.editor.getSession().selection.on("changeCursor", this.onCursorChanged)
|
|
52
|
+
|
|
53
|
+
// Get the current file to put in the ace editor
|
|
54
|
+
FileAccessor.get(this.props.currentFile.filename, this.onCurrentFileLoaded, this.props.currentFile.type);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// When someone changes the cursor, we need to tell the file that we have a changed cursor
|
|
58
|
+
onCursorChanged() {
|
|
59
|
+
let count = 0,
|
|
60
|
+
position = this.editor.getCursorPosition(),
|
|
61
|
+
i = 0,
|
|
62
|
+
src = this.editor.getValue();
|
|
63
|
+
|
|
64
|
+
while(count < position.row && (i = src.indexOf('\n', i) + 1)) { count++; }
|
|
65
|
+
FileAccessor.setCursor(this.props.currentFile.filename, i);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// When the editor value changes, then we have to set our current state,
|
|
69
|
+
// then update the file through the data store.
|
|
70
|
+
onEditorChanged() {
|
|
71
|
+
let value = this.editor.getValue();
|
|
72
|
+
|
|
73
|
+
if (value === this.state.currentFileValue)
|
|
74
|
+
return
|
|
75
|
+
else {
|
|
76
|
+
this.setState({ currentFileValue: value })
|
|
77
|
+
FileAccessor.save(this.props.currentFile.filename, this.props.currentFile.type, value, this.props.onBookContentChanged);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// After we load a file, set the value of the editor to it
|
|
82
|
+
onCurrentFileLoaded(error, contents) {
|
|
83
|
+
if (error)
|
|
84
|
+
return console.error(error)
|
|
85
|
+
else {
|
|
86
|
+
// Focus the editor
|
|
87
|
+
this.editor.focus;
|
|
88
|
+
|
|
89
|
+
// Set the value of it
|
|
90
|
+
this.setState({ currentFileValue: contents }, () => this.editor.setValue(contents, -1));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
renderEditor() {
|
|
95
|
+
return (
|
|
96
|
+
<section ref="editor" className="editor"></section>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
renderNoFileHelp() {
|
|
101
|
+
return (
|
|
102
|
+
<section className="editor nofile">
|
|
103
|
+
<h3>No file selected.</h3>
|
|
104
|
+
<p>Click one in the list on the left to start editing</p>
|
|
105
|
+
</section>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
render() {
|
|
110
|
+
return (
|
|
111
|
+
this.props.currentFile ? this.renderEditor() : this.renderNoFileHelp()
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
Editor.propTypes = {
|
|
117
|
+
currentFile: React.PropTypes.object,
|
|
118
|
+
inLiveMode: React.PropTypes.bool.isRequired,
|
|
119
|
+
onBookContentChanged: React.PropTypes.func.isRequired
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export default Editor;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import _ from "underscore";
|
|
3
|
+
import FileBrowserListItem from "./file_browser_list_item";
|
|
4
|
+
import FileAccessor from "../file_accessor";
|
|
5
|
+
import HTML5Backend from 'react-dnd/modules/backends/HTML5';
|
|
6
|
+
import { DragDropContext } from 'react-dnd';
|
|
7
|
+
|
|
8
|
+
import _string from "underscore.string";
|
|
9
|
+
_.string = _string;
|
|
10
|
+
|
|
11
|
+
class FileBrowser extends React.Component {
|
|
12
|
+
constructor(props) {
|
|
13
|
+
super(props);
|
|
14
|
+
|
|
15
|
+
this.state = {
|
|
16
|
+
files: [],
|
|
17
|
+
closed: false,
|
|
18
|
+
fileMode: 'manuscript',
|
|
19
|
+
busy: false
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Autobind
|
|
23
|
+
this.toggleClose = this.toggleClose.bind(this);
|
|
24
|
+
this.newFile = this.newFile.bind(this);
|
|
25
|
+
this.createFile = this.createFile.bind(this);
|
|
26
|
+
this.createImageFile = this.createImageFile.bind(this);
|
|
27
|
+
this.listManuscript = this.listManuscript.bind(this);
|
|
28
|
+
this.onDeleteFile = this.onDeleteFile.bind(this);
|
|
29
|
+
this.onChangeMode = this.onChangeMode.bind(this);
|
|
30
|
+
this.onFileDeleted = this.onFileDeleted.bind(this);
|
|
31
|
+
this.listCode = this.listCode.bind(this);
|
|
32
|
+
this.listManuscript = this.listManuscript.bind(this);
|
|
33
|
+
this.listImage = this.listImage.bind(this);
|
|
34
|
+
this.moveFile = this.moveFile.bind(this);
|
|
35
|
+
this.saveManuscript = this.saveManuscript.bind(this);
|
|
36
|
+
|
|
37
|
+
// Hook up events
|
|
38
|
+
FileAccessor.onDelete(this.onFileDeleted);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
componentDidMount() {
|
|
42
|
+
this.listManuscript();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
componentDidUpdate(lastProps, lastState) {
|
|
46
|
+
if (lastState.fileMode && lastState.fileMode !== this.state.fileMode) {
|
|
47
|
+
this[`list${_.string.capitalize(this.state.fileMode)}`]()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// We need to rechoose an appropriate file
|
|
52
|
+
onFileDeleted() {
|
|
53
|
+
this.props.onChangeFile(this.state.files[0])
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// List what files we have
|
|
57
|
+
listManuscript() {
|
|
58
|
+
this.busy(true);
|
|
59
|
+
FileAccessor.listFiles((error, files) => {
|
|
60
|
+
this.busy(false);
|
|
61
|
+
if (error)
|
|
62
|
+
console.error(error);
|
|
63
|
+
else {
|
|
64
|
+
this.setState({ files: files });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// List what files we have
|
|
70
|
+
listImage() {
|
|
71
|
+
this.busy(true);
|
|
72
|
+
FileAccessor.listImages((error, files) => {
|
|
73
|
+
this.busy(false);
|
|
74
|
+
if (error)
|
|
75
|
+
console.error(error);
|
|
76
|
+
else {
|
|
77
|
+
this.setState({ files: files });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// List what files we have
|
|
83
|
+
listCode() {
|
|
84
|
+
this.busy(true);
|
|
85
|
+
FileAccessor.listCode((error, files) => {
|
|
86
|
+
this.busy(false);
|
|
87
|
+
if (error)
|
|
88
|
+
console.error(error);
|
|
89
|
+
else {
|
|
90
|
+
this.setState({ files: files });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Set busy (loading) state
|
|
96
|
+
busy(busyState) {
|
|
97
|
+
this.setState({ busy: busyState });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// No longer in the loading state
|
|
101
|
+
free() {
|
|
102
|
+
this.setState({ busy: false });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Close the browser
|
|
106
|
+
toggleClose(e) {
|
|
107
|
+
this.setState({ closed: !this.state.closed });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
onChangeMode(fileMode) {
|
|
111
|
+
this.setState({ fileMode: fileMode });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Create a new image file
|
|
115
|
+
createImageFile(e) {
|
|
116
|
+
let fileNode = this.refs.imageFile.getDOMNode()
|
|
117
|
+
|
|
118
|
+
FileAccessor.newImage(fileNode, () => {
|
|
119
|
+
fileNode.value = '';
|
|
120
|
+
this.setState({ creatingFile: false });
|
|
121
|
+
this[`list${_.string.capitalize(this.state.fileMode)}`]()
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
e.stopPropagation();
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Actually create a new file
|
|
130
|
+
createFile(e) {
|
|
131
|
+
let fileNode = this.refs.filename.getDOMNode()
|
|
132
|
+
FileAccessor.new(fileNode.value, this.state.fileMode, '', (error, file) => {
|
|
133
|
+
fileNode.value = '';
|
|
134
|
+
this.setState({ creatingFile: false });
|
|
135
|
+
this[`list${_.string.capitalize(this.state.fileMode)}`]()
|
|
136
|
+
|
|
137
|
+
// Change to the new file
|
|
138
|
+
this.props.onChangeFile(file)
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
e.stopPropagation();
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Open the new file form
|
|
146
|
+
newFile(e) {
|
|
147
|
+
this.setState({ creatingFile: !this.state.creatingFile })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Delete a file
|
|
151
|
+
onDeleteFile(file) {
|
|
152
|
+
FileAccessor.delete(file.filename, this.state.fileMode, this[`list${_.string.capitalize(this.state.fileMode)}`]);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Save the manuscript, usually to preserve the order of the files since
|
|
156
|
+
// The manuscript files order in the ui is a mapping to book.txt
|
|
157
|
+
saveManuscript() {
|
|
158
|
+
if (this.state.fileMode !== "manuscript") return;
|
|
159
|
+
FileAccessor.saveManuscript(this.state.files);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Reorder a file in the list
|
|
163
|
+
moveFile(draggedItem, targetItem) {
|
|
164
|
+
let files = _.clone(this.state.files)
|
|
165
|
+
|
|
166
|
+
let draggedItemIndex = files.indexOf(draggedItem)
|
|
167
|
+
let targetItemIndex = files.indexOf(targetItem)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
files.splice(draggedItemIndex, 1);
|
|
171
|
+
files.splice(targetItemIndex, 0, draggedItem);
|
|
172
|
+
this.setState({ files: files });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
renderFileCreator() {
|
|
176
|
+
if (!this.state.creatingFile) return;
|
|
177
|
+
|
|
178
|
+
if (this.state.fileMode === "image") {
|
|
179
|
+
let imageForm = (
|
|
180
|
+
<form onSubmit={this.createImageFile} className="file-name">
|
|
181
|
+
<input type="file" ref="imageFile" />
|
|
182
|
+
<button>Submit</button>
|
|
183
|
+
</form>
|
|
184
|
+
);
|
|
185
|
+
let noUploadHelp = (<p>Sorry, image uploads are not supported</p>);
|
|
186
|
+
return FileAccessor.supportsImageUploads() ? imageForm : noUploadHelp;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
return (
|
|
190
|
+
<form onSubmit={this.createFile} className="file-name">
|
|
191
|
+
<input type="text" ref="filename" placeholder="Type a filename..."></input>
|
|
192
|
+
<button>Create</button>
|
|
193
|
+
</form>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
renderFilesList() {
|
|
199
|
+
if (this.state.busy)
|
|
200
|
+
return <i className="busy-indicator fa fa-spin fa-2x fa-circle-o-notch"></i>;
|
|
201
|
+
else {
|
|
202
|
+
return (
|
|
203
|
+
<ul className="files-list">
|
|
204
|
+
{
|
|
205
|
+
_.map(this.state.files, (file, i) => {
|
|
206
|
+
return (
|
|
207
|
+
<FileBrowserListItem
|
|
208
|
+
saveManuscript={this.saveManuscript}
|
|
209
|
+
moveFile={this.moveFile}
|
|
210
|
+
key={i}
|
|
211
|
+
fileMode={this.state.fileMode}
|
|
212
|
+
onDeleteFile={this.onDeleteFile}
|
|
213
|
+
onChangeFile={this.props.onChangeFile}
|
|
214
|
+
onPreviewImage={this.props.onPreviewImage}
|
|
215
|
+
isCurrent={this.props.currentFile === file}
|
|
216
|
+
file={file} />
|
|
217
|
+
)
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
</ul>
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
render() {
|
|
226
|
+
let clazz = `file-browser ${this.state.closed ? "closed" : "open"}`
|
|
227
|
+
let newFileClassName = `new-file${ this.state.creatingFile ? " active" : ""}`;
|
|
228
|
+
return (
|
|
229
|
+
<section className={clazz}>
|
|
230
|
+
<ul className="file-types-list">
|
|
231
|
+
<li><a onClick={_.partial(this.onChangeMode, 'manuscript')}><h4 className={`title${this.state.fileMode === 'manuscript' ? ' selected' : ''}`}>files</h4></a></li>
|
|
232
|
+
<li><a onClick={_.partial(this.onChangeMode, 'image')}><h4 className={`title${this.state.fileMode === 'image' ? ' selected' : ''}`}>images</h4></a></li>
|
|
233
|
+
<li><a onClick={_.partial(this.onChangeMode, 'code')}><h4 className={`title${this.state.fileMode === 'code' ? ' selected' : ''}`}>code</h4></a></li>
|
|
234
|
+
</ul>
|
|
235
|
+
<div className={newFileClassName} onClick={this.newFile}>
|
|
236
|
+
<i className="fa fa-plus"> </i>
|
|
237
|
+
</div>
|
|
238
|
+
{ this.renderFileCreator() }
|
|
239
|
+
{ this.renderFilesList() }
|
|
240
|
+
<button className="close-button" onClick={this.toggleClose}><span></span></button>
|
|
241
|
+
</section>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
FileBrowser.propTypes = {
|
|
247
|
+
projectRoot: React.PropTypes.string.isRequired,
|
|
248
|
+
onChangeFile: React.PropTypes.func.isRequired,
|
|
249
|
+
currentFile: React.PropTypes.string.isRequired
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
export default DragDropContext(HTML5Backend)(FileBrowser);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DragSource, DropTarget } from 'react-dnd';
|
|
3
|
+
|
|
4
|
+
const fileSource = {
|
|
5
|
+
beginDrag(props) {
|
|
6
|
+
// Return the data describing the dragged item
|
|
7
|
+
return props.file
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const fileTarget = {
|
|
12
|
+
hover(props, monitor) {
|
|
13
|
+
const draggedFile = monitor.getItem()
|
|
14
|
+
|
|
15
|
+
if (draggedFile !== props.file)
|
|
16
|
+
props.moveFile(draggedFile, props.file)
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
drop(props, monitor) {
|
|
20
|
+
props.saveManuscript()
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@DropTarget('file', fileTarget, connect => ({
|
|
25
|
+
connectDropTarget: connect.dropTarget()
|
|
26
|
+
}))
|
|
27
|
+
@DragSource('file', fileSource, (connect, monitor) => ({
|
|
28
|
+
connectDragSource: connect.dragSource(),
|
|
29
|
+
isDragging: monitor.isDragging()
|
|
30
|
+
}))
|
|
31
|
+
export default class FileBrowserListItem {
|
|
32
|
+
constructor(props) {
|
|
33
|
+
this.onDelete = this.onDelete.bind(this);
|
|
34
|
+
this.onChange = this.onChange.bind(this);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
onDelete(e) {
|
|
38
|
+
if (confirm("Are you sure you want to delete this file?"))
|
|
39
|
+
this.props.onDeleteFile(this.props.file)
|
|
40
|
+
|
|
41
|
+
e.stopPropagation();
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
onChange(e) {
|
|
46
|
+
if (this.props.file.type === "image")
|
|
47
|
+
this.props.onPreviewImage(this.props.file)
|
|
48
|
+
else
|
|
49
|
+
this.props.onChangeFile(this.props.file)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getFileIconClass() {
|
|
53
|
+
switch (this.props.fileMode) {
|
|
54
|
+
case 'manuscript':
|
|
55
|
+
return 'fa-file-o';
|
|
56
|
+
case 'images':
|
|
57
|
+
return 'fa-file-image';
|
|
58
|
+
case 'code':
|
|
59
|
+
return 'fa-code';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
render() {
|
|
64
|
+
let clazz = `files-list-item${this.props.isCurrent ? ' current' : ''}${this.props.file.parent ? ' child' : ''}${this.props.isDragging ? ' dragging' : ''}`
|
|
65
|
+
|
|
66
|
+
return this.props.connectDragSource(this.props.connectDropTarget(
|
|
67
|
+
<li className={clazz} onClick={this.onChange}>
|
|
68
|
+
<a>
|
|
69
|
+
<i className={`fa ${this.getFileIconClass()}`}></i> { this.props.file.filename }
|
|
70
|
+
</a>
|
|
71
|
+
<button onClick={this.onDelete}><i className="fa fa-times"></i></button>
|
|
72
|
+
</li>
|
|
73
|
+
));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
FileBrowserListItem.propTypes = {
|
|
78
|
+
file: React.PropTypes.object.isRequired,
|
|
79
|
+
fileMode: React.PropTypes.string.isRequired,
|
|
80
|
+
moveFile: React.PropTypes.func.isRequired,
|
|
81
|
+
saveManuscript: React.PropTypes.func.isRequired,
|
|
82
|
+
isCurrent: React.PropTypes.bool.isRequired,
|
|
83
|
+
onDeleteFile: React.PropTypes.func.isRequired,
|
|
84
|
+
onChangeFile: React.PropTypes.func.isRequired,
|
|
85
|
+
onPreviewImage: React.PropTypes.func.isRequired
|
|
86
|
+
}
|