Paste/drop images directly to markdown editor

Ticket: 1135

- Changed `FileDrop` to only cover the upper 20% of the page, otherwise it will clash with markdown image drop.
This commit is contained in:
Thomas Zarebczan 2022-03-31 15:55:00 -04:00 committed by Thomas Zarebczan
parent e765a74eb0
commit 8d4a05157d
9 changed files with 73 additions and 11 deletions

View file

@ -63,6 +63,7 @@
"express": "^4.17.1",
"humanize-duration": "^3.27.0",
"if-env": "^1.0.4",
"inline-attachment": "^2.0.3",
"match-sorter": "^6.3.0",
"parse-duration": "^1.0.0",
"player.js": "^0.1.0",

View file

@ -2229,5 +2229,6 @@
"Still Valid Until": "Still Valid Until",
"Active channel": "Active channel",
"This account has livestreaming disabled, please reach out to hello@odysee.com for assistance.": "This account has livestreaming disabled, please reach out to hello@odysee.com for assistance.",
"Attach images by pasting or drag-and-drop.": "Attach images by pasting or drag-and-drop.",
"--end--": "--end--"
}

View file

@ -1,6 +1,9 @@
// @flow
import 'easymde/dist/easymde.min.css';
import 'inline-attachment/src/inline-attachment';
import 'inline-attachment/src/codemirror-4.inline-attachment';
import { IMG_CDN_PUBLISH_URL, JSON_RESPONSE_KEYS, UPLOAD_CONFIG } from 'constants/cdn_urls';
import { FF_MAX_CHARS_DEFAULT } from 'constants/form-field';
import { openEditorMenu, stopContextMenu } from 'util/context-menu';
import { lazyImport } from 'util/lazyImport';
@ -179,8 +182,8 @@ export class FormField extends React.PureComponent<Props, State> {
// SimpleMDE max char check
editor.codemirror.on('beforeChange', (instance, changes) => {
if (textAreaMaxLength && changes.update) {
var str = changes.text.join('\n');
var delta = str.length - (instance.indexFromPos(changes.to) - instance.indexFromPos(changes.from));
let str = changes.text.join('\n');
let delta = str.length - (instance.indexFromPos(changes.to) - instance.indexFromPos(changes.from));
if (delta <= 0) return;
@ -227,6 +230,16 @@ export class FormField extends React.PureComponent<Props, State> {
}
} catch (e) {} // Do nothing (revert to original behavior)
});
// Add ability to upload pasted/dragged image (https://github.com/sparksuite/simplemde-markdown-editor/issues/328#issuecomment-227075500)
window.inlineAttachment.editors.codemirror4.attach(editor.codemirror, {
uploadUrl: IMG_CDN_PUBLISH_URL,
uploadFieldName: UPLOAD_CONFIG.BLOB_KEY,
extraParams: { [UPLOAD_CONFIG.ACTION_KEY]: UPLOAD_CONFIG.ACTION_VAL },
filenameTag: '{filename}',
urlText: '![image]({filename})',
jsonFieldName: JSON_RESPONSE_KEYS.UPLOADED_URL,
});
};
return (
@ -250,6 +263,17 @@ export class FormField extends React.PureComponent<Props, State> {
options={{
spellChecker: true,
hideIcons: ['heading', 'image', 'fullscreen', 'side-by-side'],
status: [
{
className: 'editor-statusbar__upload-hint',
defaultValue: (el) => {
el.innerHTML = __('Attach images by pasting or drag-and-drop.');
},
},
'lines',
'words',
'cursor',
],
previewRender(plainText) {
const preview = <MarkdownPreview content={plainText} noDataStore />;
return ReactDOMServer.renderToString(preview);

View file

@ -115,8 +115,12 @@ function FileDrop(props: Props) {
// Handle file drop...
React.useEffect(() => {
if (dropData && !files.length && (!modal || modal.id !== MODALS.FILE_SELECTION)) {
getTree(dropData)
const DROP_AREA_HEIGHT_PCT = 0.2; // @see: css[.file-drop -> height]
const windowHeight = window.innerHeight || document.documentElement?.clientHeight || 768;
const dropAreaBottom = windowHeight * DROP_AREA_HEIGHT_PCT;
if (dropData && dropData.y <= dropAreaBottom && !files.length && (!modal || modal.id !== MODALS.FILE_SELECTION)) {
getTree(dropData.dataTransfer)
.then((entries) => {
if (entries && entries.length) {
setFiles(entries);

View file

@ -1,2 +1,20 @@
export const IMG_CDN_PUBLISH_URL = 'https://thumbs.odycdn.com/upload.php';
export const IMG_CDN_STATUS_URL = 'https://thumbs.odycdn.com/status.php';
export const JSON_RESPONSE_KEYS = Object.freeze({
STATUS: 'type',
UPLOADED_URL: 'message',
TIMESTAMP: 'timestamp',
// --- Sample response ---
// {
// "type": "success",
// "message": "https://thumbs.odycdn.com/90c08452a2e2ebb347c8c95f749a84c5.png",
// "timestamp": 1648731440
// }
});
export const UPLOAD_CONFIG = Object.freeze({
BLOB_KEY: 'file-input',
ACTION_KEY: 'upload',
ACTION_VAL: 'Upload',
});

View file

@ -23,7 +23,7 @@ const DRAG_STATE = {
};
// Returns simple detection for global drag-drop
export default function useFetched() {
export default function useDragDrop() {
const [drag, setDrag] = React.useState(false);
const [dropData, setDropData] = React.useState(null);
@ -32,7 +32,7 @@ export default function useFetched() {
let draggingElement = false;
// Handle file drop
const handleDropEvent = event => {
const handleDropEvent = (event) => {
// Ignore non file types ( html elements / text )
if (!draggingElement) {
event.stopPropagation();
@ -41,7 +41,7 @@ export default function useFetched() {
const files = event.dataTransfer.files;
// Check for files
if (files.length > 0) {
setDropData(event.dataTransfer);
setDropData(event);
}
}
// Reset state ( hide drop zone )
@ -50,12 +50,12 @@ export default function useFetched() {
};
// Drag event for non files type ( html elements / text )
const handleDragElementEvent = event => {
const handleDragElementEvent = (event) => {
draggingElement = DRAG_STATE[event.type];
};
// Drag events
const handleDragEvent = event => {
const handleDragEvent = (event) => {
event.stopPropagation();
event.preventDefault();
// Prevent multiple drop areas
@ -71,7 +71,7 @@ export default function useFetched() {
};
// Register / Unregister listeners
const handleEventListeners = event => {
const handleEventListeners = (event) => {
const action = `${event}EventListener`;
// Handle drop event
document[action]('drop', handleDropEvent);
@ -82,6 +82,7 @@ export default function useFetched() {
document[action](DRAG_TYPES.END, handleDragElementEvent);
document[action](DRAG_TYPES.START, handleDragElementEvent);
};
// On component mounted:
// Register event listeners
handleEventListeners(LISTENER.ADD);

View file

@ -4,7 +4,7 @@
position: fixed;
background: var(--color-background-overlay);
opacity: 0;
height: 100%;
height: 20%;
width: 100%;
overflow: hidden;
pointer-events: none;

View file

@ -52,6 +52,14 @@
}
}
.editor-statusbar {
.editor-statusbar__upload-hint {
display: block;
float: left;
margin-left: 0;
}
}
.form-field--SimpleMDE {
margin-top: var(--spacing-l);

View file

@ -9234,6 +9234,11 @@ ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==
inline-attachment@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inline-attachment/-/inline-attachment-2.0.3.tgz#5ee32374583fabd3b7206df2e20f251ba20c4306"
integrity sha1-XuMjdFg/q9O3IG3y4g8lG6IMQwY=
inline-style-parser@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"