1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-24 08:12:24 +02:00

Desktop: WYSIWYG: Handle drag and drop of notes and files

This commit is contained in:
Laurent Cozic 2020-05-10 16:28:22 +01:00
parent 6ca41ddf80
commit 9f9f760ede
5 changed files with 72 additions and 53 deletions

View File

@ -13,7 +13,6 @@ interface PlainEditorProps {
onChange(event: OnChangeEvent): void,
onWillChange(event:any): void,
markupToHtml: Function,
attachResources: Function,
disabled: boolean,
}

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { useState, useEffect, useCallback, useRef, forwardRef, useImperativeHandle } from 'react';
import { ScrollOptions, ScrollOptionTypes, EditorCommand, NoteBodyEditorProps } from '../../utils/types';
import { resourcesStatus } from '../../utils/resourceHandling';
import { resourcesStatus, commandAttachFileToBody } from '../../utils/resourceHandling';
import useScroll from './utils/useScroll';
import { menuItems, ContextMenuOptions, ContextMenuItemType } from '../../utils/contextMenu';
const { MarkupToHtml } = require('lib/joplin-renderer');
@ -125,6 +125,7 @@ function styles_(props:NoteBodyEditorProps) {
padding: 20,
paddingTop: 50,
textAlign: 'center',
width: '100%',
},
rootStyle: {
position: 'relative',
@ -142,13 +143,14 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
const [editor, setEditor] = useState(null);
const [scriptLoaded, setScriptLoaded] = useState(false);
const [editorReady, setEditorReady] = useState(false);
const attachResources = useRef(null);
attachResources.current = props.attachResources;
const [draggingStarted, setDraggingStarted] = useState(false);
const props_onMessage = useRef(null);
props_onMessage.current = props.onMessage;
const props_onDrop = useRef(null);
props_onDrop.current = props.onDrop;
const contextMenuActionOptions = useRef<ContextMenuOptions>(null);
const markupToHtml = useRef(null);
@ -173,6 +175,17 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
}, 10);
};
const insertResourcesIntoContent = useCallback(async (filePaths:string[] = null, options:any = null) => {
const resourceMd = await commandAttachFileToBody('', filePaths, options);
const result = await props.markupToHtml(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, resourceMd, markupRenderOptions({ bodyOnly: true }));
editor.insertContent(result.html);
// editor.fire('joplinChange');
// dispatchDidUpdate(editor);
}, [props.markupToHtml, editor]);
const insertResourcesIntoContentRef = useRef(null);
insertResourcesIntoContentRef.current = insertResourcesIntoContent;
const onEditorContentClick = useCallback((event:any) => {
const nodeName = event.target ? event.target.nodeName : '';
@ -239,6 +252,15 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
editor.insertContent(result.html);
} else if (cmd.name === 'focus') {
editor.focus();
} else if (cmd.name === 'dropItems') {
if (cmd.value.type === 'notes') {
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, cmd.value.markdownTags.join('\n'), markupRenderOptions({ bodyOnly: true }));
editor.insertContent(result.html);
} else if (cmd.value.type === 'files') {
insertResourcesIntoContentRef.current(cmd.value.paths, { createFileURL: !!cmd.value.createFileURL });
} else {
reg.logger().warn('AceEditor: unsupported drop item: ', cmd);
}
} else {
commandProcessed = false;
}
@ -540,18 +562,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
tooltip: _('Attach file'),
icon: 'paperclip',
onAction: async function() {
const resources = await attachResources.current();
if (!resources.length) return;
const html = [];
for (const resource of resources) {
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, resource.markdownTag, markupRenderOptions({ bodyOnly: true }));
html.push(result.html);
}
editor.insertContent(html.join('\n'));
editor.fire('joplinChange');
dispatchDidUpdate(editor);
insertResourcesIntoContentRef.current();
},
});
@ -633,6 +644,11 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
if (editable) openEditDialog(editable);
});
// This is triggered when an external file is dropped on the editor
editor.on('drop', (event:any) => {
props_onDrop.current(event);
});
editor.on('ObjectResized', function(event:any) {
if (event.target.nodeName === 'IMG') {
editor.fire('joplinChange');
@ -709,13 +725,12 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
let cancelled = false;
const loadContent = async () => {
if (lastOnChangeEventContent.current === props.content) return;
const result = await props.markupToHtml(props.contentMarkupLanguage, props.content, markupRenderOptions({ resourceInfos: props.resourceInfos }));
if (cancelled) return;
lastOnChangeEventContent.current = props.content;
editor.setContent(result.html);
if (lastOnChangeEventContent.current !== props.content) {
const result = await props.markupToHtml(props.contentMarkupLanguage, props.content, markupRenderOptions({ resourceInfos: props.resourceInfos }));
if (cancelled) return;
lastOnChangeEventContent.current = props.content;
editor.setContent(result.html);
}
await loadDocumentAssets(editor, await props.allAssets(props.contentMarkupLanguage));
@ -749,6 +764,34 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
};
}, [editor, onEditorContentClick]);
// This is to handle dropping notes on the editor. In this case, we add an
// overlay over the editor, which makes it a valid drop target. This in
// turn makes NoteEditor get the drop event and dispatch it.
useEffect(() => {
if (!editor) return () => {};
function onDragStart() {
setDraggingStarted(true);
}
function onDrop() {
setDraggingStarted(false);
}
function onDragEnd() {
setDraggingStarted(false);
}
document.addEventListener('dragstart', onDragStart);
document.addEventListener('drop', onDrop);
document.addEventListener('dragend', onDragEnd);
return () => {
document.removeEventListener('dragstart', onDragStart);
document.removeEventListener('drop', onDrop);
document.removeEventListener('dragend', onDragEnd);
};
}, [editor]);
// -----------------------------------------------------------------------------------------
// Handle onChange event
// -----------------------------------------------------------------------------------------
@ -905,13 +948,14 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
// as it is quite complex and probably rarely used.
function renderDisabledOverlay() {
const status = resourcesStatus(props.resourceInfos);
if (status === 'ready') return null;
if (status === 'ready' && !draggingStarted) return null;
const message = _('Please wait for all attachments to be downloaded and decrypted. You may also switch to %s to edit the note.', _('Code View'));
const message = draggingStarted ? _('Drop notes or files here') : _('Please wait for all attachments to be downloaded and decrypted. You may also switch to %s to edit the note.', _('Code View'));
const statusComp = draggingStarted ? null : <p style={theme.textStyleMinor}>{`Status: ${status}`}</p>;
return (
<div style={styles.disabledOverlay}>
<p style={theme.textStyle}>{message}</p>
<p style={theme.textStyleMinor}>{`Status: ${status}`}</p>
{statusComp}
</div>
);
}

View File

@ -16,7 +16,6 @@ import useMarkupToHtml from './utils/useMarkupToHtml';
import useFormNote, { OnLoadEvent } from './utils/useFormNote';
import styles_ from './styles';
import { NoteEditorProps, FormNote, ScrollOptions, ScrollOptionTypes, OnChangeEvent, NoteBodyEditorProps } from './utils/types';
import { attachResources } from './utils/resourceHandling';
const { themeStyle } = require('../../theme.js');
const NoteSearchBar = require('../NoteSearchBar.min.js');
@ -408,7 +407,6 @@ function NoteEditor(props: NoteEditorProps) {
htmlToMarkdown: htmlToMarkdown,
markupToHtml: markupToHtml,
allAssets: allAssets,
attachResources: attachResources,
disabled: false,
theme: props.theme,
dispatch: props.dispatch,
@ -418,6 +416,7 @@ function NoteEditor(props: NoteEditorProps) {
visiblePanes: props.noteVisiblePanes || ['editor', 'viewer'],
keyboardMode: Setting.value('editor.keyboardMode'),
locale: Setting.value('locale'),
onDrop: onDrop,
};
let editor = null;

View File

@ -49,29 +49,6 @@ export async function attachedResources(noteBody: string): Promise<any> {
return output;
}
export async function attachResources() {
const filePaths = bridge().showOpenDialog({
properties: ['openFile', 'createDirectory', 'multiSelections'],
});
if (!filePaths || !filePaths.length) return [];
const output = [];
for (const filePath of filePaths) {
try {
const resource = await shim.createResourceFromPath(filePath);
output.push({
item: resource,
markdownTag: Resource.markdownTag(resource),
});
} catch (error) {
bridge().showErrorMessageBox(error.message);
}
}
return output;
}
export async function commandAttachFileToBody(body:string, filePaths:string[] = null, options:any = null) {
options = {
createFileURL: false,

View File

@ -41,7 +41,6 @@ export interface NoteBodyEditorProps {
markupToHtml: Function;
htmlToMarkdown: Function;
allAssets: Function;
attachResources: Function;
disabled: boolean;
dispatch: Function;
noteToolbar: any;
@ -50,6 +49,7 @@ export interface NoteBodyEditorProps {
keyboardMode: string,
resourceInfos: ResourceInfos,
locale: string,
onDrop: Function,
}
export interface FormNote {