mirror of
https://github.com/strapi/strapi.git
synced 2025-08-13 11:17:42 +00:00
Merge pull request #879 from strapi/improve-wys-upload
Improve file upload in WYSIWYG
This commit is contained in:
commit
5796d96ecc
@ -110,6 +110,8 @@
|
||||
"components.Wysiwyg.ToggleMode.markdown": "Switch to markdown",
|
||||
"components.Wysiwyg.ToggleMode.preview": "Switch to preview",
|
||||
"components.WysiwygBottomControls.charactersIndicators": "characters",
|
||||
"components.WysiwygBottomControls.uploadFiles": "Attach files by dragging & dropping, {browse}, or pasting from the clipboard.",
|
||||
"components.WysiwygBottomControls.uploadFiles.browse": "selecting them",
|
||||
"components.WysiwygBottomControls.fullscreen": "Expand",
|
||||
|
||||
"notification.error": "Ein Fehler ist aufgetreten",
|
||||
|
@ -116,6 +116,8 @@
|
||||
"components.Wysiwyg.ToggleMode.markdown": "Switch to markdown",
|
||||
"components.Wysiwyg.ToggleMode.preview": "Switch to preview",
|
||||
"components.WysiwygBottomControls.charactersIndicators": "characters",
|
||||
"components.WysiwygBottomControls.uploadFiles": "Attach files by dragging & dropping, {browse}, or pasting from the clipboard.",
|
||||
"components.WysiwygBottomControls.uploadFiles.browse": "selecting them",
|
||||
"components.WysiwygBottomControls.fullscreen": "Expand",
|
||||
|
||||
"notification.error": "An error occurred",
|
||||
|
@ -116,6 +116,8 @@
|
||||
"components.Wysiwyg.ToggleMode.markdown": "Retour au markdown",
|
||||
"components.Wysiwyg.ToggleMode.preview": "Voir la preview",
|
||||
"components.WysiwygBottomControls.charactersIndicators": "charactères",
|
||||
"components.WysiwygBottomControls.uploadFiles": "Ajouter des fichiers en les 'droppant', {browse}, ou en les collant depuis le presse-papier",
|
||||
"components.WysiwygBottomControls.uploadFiles.browse": "en les selectionnant",
|
||||
"components.WysiwygBottomControls.fullscreen": "Plein écran",
|
||||
|
||||
"notification.error": "Une erreur est survenue",
|
||||
|
@ -113,6 +113,8 @@
|
||||
"components.Wysiwyg.ToggleMode.markdown": "Switch to markdown",
|
||||
"components.Wysiwyg.ToggleMode.preview": "Switch to preview",
|
||||
"components.WysiwygBottomControls.charactersIndicators": "characters",
|
||||
"components.WysiwygBottomControls.uploadFiles": "Attach files by dragging & dropping, {browse}, or pasting from the clipboard.",
|
||||
"components.WysiwygBottomControls.uploadFiles.browse": "selecting them",
|
||||
"components.WysiwygBottomControls.fullscreen": "Expand",
|
||||
|
||||
"notification.error": "Wystąpił błąd",
|
||||
|
@ -114,6 +114,8 @@
|
||||
"components.Wysiwyg.ToggleMode.markdown": "Switch to markdown",
|
||||
"components.Wysiwyg.ToggleMode.preview": "Switch to preview",
|
||||
"components.WysiwygBottomControls.charactersIndicators": "characters",
|
||||
"components.WysiwygBottomControls.uploadFiles": "Attach files by dragging & dropping, {browse}, or pasting from the clipboard.",
|
||||
"components.WysiwygBottomControls.uploadFiles.browse": "selecting them",
|
||||
"components.WysiwygBottomControls.fullscreen": "Expand",
|
||||
|
||||
"notification.error": "Bir hata oluştu",
|
||||
|
@ -118,6 +118,8 @@
|
||||
"components.Wysiwyg.ToggleMode.markdown": "Switch to markdown",
|
||||
"components.Wysiwyg.ToggleMode.preview": "Switch to preview",
|
||||
"components.WysiwygBottomControls.charactersIndicators": "characters",
|
||||
"components.WysiwygBottomControls.uploadFiles": "Attach files by dragging & dropping, {browse}, or pasting from the clipboard.",
|
||||
"components.WysiwygBottomControls.uploadFiles.browse": "selecting them",
|
||||
"components.WysiwygBottomControls.fullscreen": "Expand",
|
||||
|
||||
"notification.error": "有錯誤發生了",
|
||||
|
@ -56,9 +56,9 @@
|
||||
}
|
||||
|
||||
// NOTE: we might need this later
|
||||
// span {
|
||||
// white-space: pre-line;
|
||||
// }
|
||||
span {
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
|
||||
h1+.editorParagraph{
|
||||
|
@ -369,28 +369,8 @@ class Wysiwyg extends React.Component {
|
||||
return this.setState({ isDraging: false });
|
||||
}
|
||||
|
||||
const { dataTransfer: { files } } = e;
|
||||
const formData = new FormData();
|
||||
formData.append('files', files[0]);
|
||||
const headers = {
|
||||
'X-Forwarded-Host': 'strapi',
|
||||
};
|
||||
|
||||
return request('/upload', { method: 'POST', headers, body: formData }, false, false)
|
||||
.then(response => {
|
||||
const newContentState = this.createNewContentStateFromBlock(
|
||||
createNewBlock(``),
|
||||
);
|
||||
const newEditorState = EditorState.push(this.getEditorState(), newContentState);
|
||||
this.setState({ editorState: newEditorState });
|
||||
this.sendData(newEditorState);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('error', err.response);
|
||||
})
|
||||
.finally(() => {
|
||||
this.setState({ isDraging: false });
|
||||
});
|
||||
const files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
|
||||
return this.uploadFile(files);
|
||||
};
|
||||
|
||||
handleKeyCommand = (command, editorState) => {
|
||||
@ -410,6 +390,8 @@ class Wysiwyg extends React.Component {
|
||||
return false;
|
||||
};
|
||||
|
||||
handlePastedFiles = files => this.uploadFile(files);
|
||||
|
||||
handleReturn = (e, editorState) => {
|
||||
const selection = editorState.getSelection();
|
||||
const currentBlock = editorState.getCurrentContent().getBlockForKey(selection.getStartKey());
|
||||
@ -474,6 +456,37 @@ class Wysiwyg extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
uploadFile = (files) => {
|
||||
const formData = new FormData();
|
||||
formData.append('files', files[0]);
|
||||
const headers = {
|
||||
'X-Forwarded-Host': 'strapi',
|
||||
};
|
||||
|
||||
const newContentState = this.createNewContentStateFromBlock(
|
||||
createNewBlock(`![Uploading ${files[0].name}]()`),
|
||||
);
|
||||
const newEditorState = EditorState.push(this.getEditorState(), newContentState);
|
||||
this.setState({ editorState: newEditorState });
|
||||
|
||||
return request('/upload', { method: 'POST', headers, body: formData }, false, false)
|
||||
.then(response => {
|
||||
const lastBlock = this.getEditorState().getCurrentContent().getLastBlock();
|
||||
const newContentState = this.createNewContentStateFromBlock(
|
||||
createNewBlock(``, 'unstyled', lastBlock.getKey()),
|
||||
);
|
||||
const newEditorState = EditorState.push(this.getEditorState(), newContentState);
|
||||
this.setState({ editorState: EditorState.moveFocusToEnd(newEditorState) });
|
||||
this.sendData(newEditorState);
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({ editorState: EditorState.undo(this.getEditorState()) });
|
||||
})
|
||||
.finally(() => {
|
||||
this.setState({ isDraging: false });
|
||||
});
|
||||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
console.log('err', error);
|
||||
console.log('info', info);
|
||||
@ -549,6 +562,7 @@ class Wysiwyg extends React.Component {
|
||||
blockStyleFn={getBlockStyle}
|
||||
editorState={editorState}
|
||||
handleKeyCommand={this.handleKeyCommand}
|
||||
handlePastedFiles={this.handlePastedFiles}
|
||||
handleReturn={this.handleReturn}
|
||||
keyBindingFn={this.mapKeyToEditorCommand}
|
||||
onBlur={this.handleBlur}
|
||||
@ -563,8 +577,9 @@ class Wysiwyg extends React.Component {
|
||||
)}
|
||||
{!isFullscreen && (
|
||||
<WysiwygBottomControls
|
||||
charactersNumber={this.getCharactersNumber()}
|
||||
isPreviewMode={isPreviewMode}
|
||||
onClick={this.toggleFullScreen}
|
||||
onChange={this.handleDrop}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -12,10 +12,12 @@ import { isEmpty } from 'lodash';
|
||||
|
||||
import WysiwygEditor from 'components/WysiwygEditor';
|
||||
import converter from './converter';
|
||||
import { findLinkEntities, findImageEntities } from './strategies';
|
||||
import { findLinkEntities, findImageEntities, findVideoEntities } from './strategies';
|
||||
|
||||
import Image from './image';
|
||||
import Link from './link';
|
||||
import Video from './video';
|
||||
|
||||
import styles from './componentsStyles.scss';
|
||||
|
||||
function getBlockStyle(block) {
|
||||
@ -61,6 +63,10 @@ class PreviewWysiwyg extends React.Component {
|
||||
strategy: findImageEntities,
|
||||
component: Image,
|
||||
},
|
||||
{
|
||||
strategy: findVideoEntities,
|
||||
component: Video,
|
||||
},
|
||||
]);
|
||||
|
||||
const blocksFromHTML = convertFromHTML(html);
|
||||
|
@ -8,8 +8,19 @@ function findLinkEntities(contentBlock, callback, contentState) {
|
||||
function findImageEntities(contentBlock, callback, contentState) {
|
||||
contentBlock.findEntityRanges(character => {
|
||||
const entityKey = character.getEntity();
|
||||
return entityKey !== null && contentState.getEntity(entityKey).getType() === 'IMAGE';
|
||||
|
||||
return entityKey !== null && contentState.getEntity(entityKey).getType() === 'IMAGE' && !isVideoType(contentState.getEntity(entityKey).getData().src);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
export { findLinkEntities, findImageEntities };
|
||||
function findVideoEntities(contentBlock, cb, contentState) {
|
||||
contentBlock.findEntityRanges(character => {
|
||||
const entityKey = character.getEntity();
|
||||
|
||||
return entityKey !== null && contentState.getEntity(entityKey).getType() === 'IMAGE' && isVideoType(contentState.getEntity(entityKey).getData().src);
|
||||
}, cb);
|
||||
}
|
||||
|
||||
const isVideoType = (fileName) => /\.(mp4|mpg|mpeg|mov|avi)$/i.test(fileName);
|
||||
|
||||
export { findLinkEntities, findImageEntities, findVideoEntities };
|
||||
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
*
|
||||
* Video
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/* eslint-disable jsx-a11y/media-has-caption */
|
||||
const Video = props => {
|
||||
const { height, src, width } = props.contentState.getEntity(props.entityKey).getData();
|
||||
|
||||
return (
|
||||
<video height={height} width={width} style={{ maxWidth: '100%' }} controls>
|
||||
<source src={src} />
|
||||
</video>
|
||||
);
|
||||
};
|
||||
|
||||
Video.propTypes = {
|
||||
contentState: PropTypes.object.isRequired,
|
||||
entityKey: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Video;
|
@ -9,13 +9,31 @@ import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import styles from './styles.scss';
|
||||
/* eslint-disable jsx-a11y/label-has-for */
|
||||
const WysiwygBottomControls = ({ isPreviewMode, onChange, onClick }) => {
|
||||
const browse = (
|
||||
<FormattedMessage id="components.WysiwygBottomControls.uploadFiles.browse">
|
||||
{(message) => <span className={styles.underline}>{message}</span>}
|
||||
</FormattedMessage>
|
||||
);
|
||||
|
||||
const WysiwygBottomControls = ({ charactersNumber, onClick }) => {
|
||||
return (
|
||||
<div className={styles.wysiwygBottomControlsWrapper}>
|
||||
<div>
|
||||
<span>{charactersNumber} </span>
|
||||
<FormattedMessage id="components.WysiwygBottomControls.charactersIndicators" />
|
||||
<label
|
||||
className={styles.dropLabel}
|
||||
onClick={(e) => {
|
||||
if (isPreviewMode) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="components.WysiwygBottomControls.uploadFiles"
|
||||
values={{ browse }}
|
||||
/>
|
||||
<input type="file" onChange={onChange} />
|
||||
</label>
|
||||
</div>
|
||||
<div className={styles.fullScreenWrapper} onClick={onClick}>
|
||||
<FormattedMessage id="components.WysiwygBottomControls.fullscreen" />
|
||||
@ -25,12 +43,14 @@ const WysiwygBottomControls = ({ charactersNumber, onClick }) => {
|
||||
};
|
||||
|
||||
WysiwygBottomControls.defaultProps = {
|
||||
charactersNumber: 0,
|
||||
isPreviewMode: false,
|
||||
onChange: () => {},
|
||||
onClick: () => {},
|
||||
};
|
||||
|
||||
WysiwygBottomControls.propTypes = {
|
||||
charactersNumber: PropTypes.number,
|
||||
isPreviewMode: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
|
@ -25,3 +25,15 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.underline {
|
||||
color: #1C5DE7;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropLabel {
|
||||
> input {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -31,21 +31,27 @@ class Li extends React.Component {
|
||||
|
||||
getUnit = (value) => {
|
||||
let unit;
|
||||
let divider;
|
||||
|
||||
switch (true) {
|
||||
case value > 10000:
|
||||
unit = 'GB';
|
||||
divider = 1000;
|
||||
break;
|
||||
case value < 1:
|
||||
unit = 'B';
|
||||
divider = 1;
|
||||
break;
|
||||
case value > 1000:
|
||||
unit = 'MB';
|
||||
divider = 1000;
|
||||
break;
|
||||
default:
|
||||
unit = 'KB';
|
||||
divider = 1;
|
||||
}
|
||||
|
||||
return unit;
|
||||
return { divider, unit };
|
||||
}
|
||||
|
||||
handleClick = (e) => {
|
||||
@ -111,10 +117,11 @@ class Li extends React.Component {
|
||||
}
|
||||
|
||||
if (value === 'size') {
|
||||
const unit = this.getUnit(item[value]);
|
||||
const { divider, unit } = this.getUnit(item[value]);
|
||||
const size = item[value]/divider;
|
||||
|
||||
return (
|
||||
<div key={key} className={styles.truncate}>{item[value]} {unit}</div>
|
||||
<div key={key} className={styles.truncate}>{Math.round(size * 100) / 100 } {unit}</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user