Commit 5d2f0619 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '224-markdown-editor-pasting-files-and-images' into 'master'

Resolve "Markdown editor: pasting files and images"

Closes #224

See merge request !179
parents 28d583aa be3b6c5d
Pipeline #4642 passed with stages
in 5 minutes and 9 seconds
......@@ -14,50 +14,77 @@ import Input from 'material-ui/Input';
class MarkdownField extends OriginalMarkdownField {
// ref to input textarea
private textarea: any;
private noReactUpdates: boolean = false;
public constructor(props: any) {
super(props);
this.setRef = this.setRef.bind(this);
this.dataDropped = this.dataDropped.bind(this);
this.dataPasted = this.dataPasted.bind(this);
this.stopReactUpdate = this.stopReactUpdate.bind(this);
this.resumeReactUpdate = this.resumeReactUpdate.bind(this);
}
// private dataDropped = (e) => {
// e.preventDefault();
// console.log('Dropped', e.dataTransfer);
// const { dataTransfer: { items, files }, target } = e;
// const { uploadMarkdownAttachment } = this.props;
//
// console.log('Dropped', e.dataTransfer, items, target);
//
// const list = items.filter((i) => i.kind === 'file').map((i) => i.getAsFile()) || files;
// for (const f of list) {
// console.log('Uploading file', f);
// uploadMarkdownAttachment(f)
// .then((r) => {
// // JSON.stringify(r));
// if (r.contentType.startsWith('image/')) {
// insertAtCaret(target, `![${f.name}](${REPOSITORY_DOWNLOADFILE_URL}/${r.uuid})`);
// } else {
// insertAtCaret(target, `[${f.name}](${REPOSITORY_DOWNLOADFILE_URL}/${r.uuid})`);
// }
// });
// }
// }
private dataPasted = (e) => {
const { clipboardData: { types, files, items }, target } = e;
private stopReactUpdate = (e) => {
console.log('Stopping React updates');
this.noReactUpdates = true;
}
private resumeReactUpdate = (e) => {
console.log('Resuming React updates');
this.noReactUpdates = false;
}
private addFile = (file) => {
const { uploadMarkdownAttachment } = this.props;
const textarea = this.textarea;
// console.log('Uploading file', file);
return uploadMarkdownAttachment(file)
.then((r) => {
// JSON.stringify(r));
console.log('Uploaded', r);
if (r.contentType.startsWith('image/')) {
insertAtCaret(textarea, `![${file.name}](${REPOSITORY_DOWNLOADFILE_URL}/${r.uuid})`);
} else {
insertAtCaret(textarea, `[${file.name}](${REPOSITORY_DOWNLOADFILE_URL}/${r.uuid})`);
}
return r;
});
}
private dataDropped = async (e) => {
const { input } = this.props;
e.preventDefault();
// console.log('Dropped', e.dataTransfer);
const { dataTransfer: { items, files }, target } = e;
// console.log('Dropped', e.dataTransfer, items, target);
const list = [ ...items ].filter((i) => i.kind === 'file').map((i) => i.getAsFile()) || files;
const p = [];
for (const f of list) {
const result = await this.addFile(f);
p.push(result);
// console.log(result);
}
Promise.all(p).then(() => {
// console.log(this.textarea.value);
input.onChange(this.textarea.value);
this.resumeReactUpdate();
});
}
console.log('Pasted', e.clipboardData, types, files, items);
private dataPasted = async (e) => {
const { clipboardData: { types, files, items }, target } = e;
// console.log('Pasted', e.clipboardData, types, files, items);
if (files.length) {
e.preventDefault();
console.log('Uploading files', files);
for (const f of files) {
uploadMarkdownAttachment(f).then((r) => {
// JSON.stringify(r));
if (r.contentType.startsWith('image/')) {
insertAtCaret(target, `![${f.name}](${REPOSITORY_DOWNLOADFILE_URL}/${r.uuid})`);
} else {
insertAtCaret(target, `[${f.name}](${REPOSITORY_DOWNLOADFILE_URL}/${r.uuid})`);
}
});
await this.addFile(f);
}
} else {
// NOOP
......@@ -65,25 +92,62 @@ class MarkdownField extends OriginalMarkdownField {
// console.log(e.clipboardData.getData('text/plain'));
}
// Bind event listeners
private setRef = (textarea): void => {
this.textarea = textarea;
if (textarea) {
this.textarea.addEventListener('drop', this.dataDropped, false);
this.textarea.addEventListener('dragenter', this.stopReactUpdate, false);
this.textarea.addEventListener('dragexit', this.resumeReactUpdate, false);
this.textarea.addEventListener('paste', this.dataPasted, false);
}
}
// Unbind event listeners
public componentWillUnmount() {
if (this.textarea) {
this.textarea.removeEventListener('drop', this.dataDropped, false);
this.textarea.removeEventListener('dragenter', this.stopReactUpdate, false);
this.textarea.removeEventListener('dragexit', this.resumeReactUpdate, false);
this.textarea.removeEventListener('paste', this.dataPasted, false);
}
}
// We need to use this while dragging so that component is not rerendered
public shouldComponentUpdate(nextProps, nextState) {
// console.log(`shouldComponentUpdate ${! this.noReactUpdates}`); // , this.props, nextProps, this.state, nextState);
return ! this.noReactUpdates;
}
public render() {
const {basicMarkdown, input, label, required, meta, meta: {touched, error}, ...custom} = this.props;
const {classes, basicMarkdown, input, label, required, meta, meta: {touched, error}, ...custom} = this.props;
const basic: boolean = basicMarkdown === undefined || null ? false : basicMarkdown;
if (basic) {
return (
<FormControl fullWidth required={ required } meta={ meta } label={ label }>
<Input error={ touched && error } { ...input } { ...custom } />
<h6>
<span>Basic markdown supported: * **</span>
</h6>
</FormControl>
);
}
return (
<div>
{ (basic || !this.state.previewMode) ?
<FormControl fullWidth required={ required } meta={ meta } label={ label }>
<Input error={ touched && error } onPaste={ this.dataPasted } multiline={ !basic } { ...input } { ...custom } />
<h6>
{ ! basic && <a onClick={ this.onChangePreviewMode }>Preview Markdown</a> }
<span> { basic ? 'Basic markdown supported: * **' : 'Full markdown supported' }</span>
</h6>
</FormControl>
:
<FormControl fullWidth required={ required } meta={ meta } label={ label }>
<Markdown style={ { marginTop: '2rem' } } basic={ basic } source={ input.value } />
<h6><a onClick={ this.onChangePreviewMode }>Edit Markdown</a></h6>
</FormControl>
{ ! this.state.previewMode ?
<FormControl fullWidth required={ required } meta={ meta } label={ label }>
<Input error={ touched && error } inputRef={ this.setRef } multiline { ...input } { ...custom } />
<h6><a onClick={ this.onChangePreviewMode }>Preview Markdown</a> <span> Full markdown supported</span></h6>
</FormControl>
:
<FormControl fullWidth required={ required } meta={ meta } label={ label }>
<div style={ { paddingTop: '1.5rem' } }>
<Markdown source={ input.value } />
</div>
<h6><a onClick={ this.onChangePreviewMode }>Edit Markdown</a></h6>
</FormControl>
}
</div>
);
......
......@@ -473,6 +473,20 @@ table.genesys-table {
p {
font-size: 1.1rem;
}
img {
max-width: 100%;
}
a[href^='/proxy/uploads/'] {
&:before {
margin-right: 4px;
font-family: 'FontAwesome';
font-size: inherit;
text-rendering: auto;
content: '\f0c6';
}
}
}
// Markdown basic: the inlined version
......
......@@ -44,6 +44,6 @@
"no-implicit-dependencies": false,
"variable-name": [true, "ban-keywords", "allow-leading-underscore"],
"no-consecutive-blank-lines": [true, 3],
"no-unnecessary-semicolons": true
"semicolon": [true, "always" ]
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment