Merge branch 'master' of github.com:jorilallo/atlas into tests
This commit is contained in:
@@ -19,6 +19,7 @@ class DropToImport extends Component {
|
||||
activeClassName?: string,
|
||||
rejectClassName?: string,
|
||||
documents: DocumentsStore,
|
||||
disabled: boolean,
|
||||
history: Object,
|
||||
};
|
||||
state = {
|
||||
@@ -83,9 +84,12 @@ class DropToImport extends Component {
|
||||
'history',
|
||||
'documentId',
|
||||
'collectionId',
|
||||
'documents'
|
||||
'documents',
|
||||
'disabled'
|
||||
);
|
||||
|
||||
if (this.props.disabled) return this.props.children;
|
||||
|
||||
return (
|
||||
<Dropzone
|
||||
accept="text/markdown, text/plain"
|
||||
|
||||
@@ -3,8 +3,11 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Editor, Plain } from 'slate';
|
||||
import keydown from 'react-keydown';
|
||||
import classnames from 'classnames/bind';
|
||||
import type { Document, State, Editor as EditorType } from './types';
|
||||
import getDataTransferFiles from 'utils/getDataTransferFiles';
|
||||
import uploadFile from 'utils/uploadFile';
|
||||
import Flex from 'components/Flex';
|
||||
import ClickablePadding from './components/ClickablePadding';
|
||||
import Toolbar from './components/Toolbar';
|
||||
@@ -82,22 +85,73 @@ type KeyData = {
|
||||
this.props.onChange(Markdown.serialize(state));
|
||||
};
|
||||
|
||||
handleDrop = async (ev: SyntheticEvent) => {
|
||||
// check if this event was already handled by the Editor
|
||||
if (ev.isDefaultPrevented()) return;
|
||||
|
||||
// otherwise we'll handle this
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
const files = getDataTransferFiles(ev);
|
||||
for (const file of files) {
|
||||
await this.insertFile(file);
|
||||
}
|
||||
};
|
||||
|
||||
insertFile = async (file: Object) => {
|
||||
this.props.onImageUploadStart();
|
||||
const asset = await uploadFile(file);
|
||||
const state = this.editor.getState();
|
||||
const transform = state.transform();
|
||||
transform.collapseToEndOf(state.document);
|
||||
transform.insertBlock({
|
||||
type: 'image',
|
||||
isVoid: true,
|
||||
data: { src: asset.url, alt: file.name },
|
||||
});
|
||||
this.props.onImageUploadStop();
|
||||
this.setState({ state: transform.apply() });
|
||||
};
|
||||
|
||||
cancelEvent = (ev: SyntheticEvent) => {
|
||||
ev.preventDefault();
|
||||
};
|
||||
|
||||
// Handling of keyboard shortcuts outside of editor focus
|
||||
@keydown('meta+s')
|
||||
onSave(ev: SyntheticKeyboardEvent) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.props.onSave();
|
||||
}
|
||||
|
||||
@keydown('meta+enter')
|
||||
onSaveAndExit(ev: SyntheticKeyboardEvent) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.props.onSave({ redirect: false });
|
||||
}
|
||||
|
||||
@keydown('esc')
|
||||
onCancel() {
|
||||
this.props.onCancel();
|
||||
}
|
||||
|
||||
// Handling of keyboard shortcuts within editor focus
|
||||
onKeyDown = (ev: SyntheticKeyboardEvent, data: KeyData, state: State) => {
|
||||
if (!data.isMeta) return;
|
||||
|
||||
switch (data.key) {
|
||||
case 's':
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.props.onSave();
|
||||
return state;
|
||||
this.onSave(ev);
|
||||
break;
|
||||
case 'enter':
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.props.onSave({ redirect: false });
|
||||
return state;
|
||||
this.onSaveAndExit(ev);
|
||||
break;
|
||||
case 'escape':
|
||||
return this.props.onCancel();
|
||||
this.onCancel();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
};
|
||||
@@ -120,31 +174,40 @@ type KeyData = {
|
||||
|
||||
render = () => {
|
||||
return (
|
||||
<Container auto column>
|
||||
<HeaderContainer
|
||||
onClick={this.focusAtStart}
|
||||
readOnly={this.props.readOnly}
|
||||
>
|
||||
{this.props.heading}
|
||||
</HeaderContainer>
|
||||
<Toolbar state={this.state.state} onChange={this.onChange} />
|
||||
<Editor
|
||||
key={this.props.starred}
|
||||
ref={ref => (this.editor = ref)}
|
||||
placeholder="Start with a title..."
|
||||
className={cx(styles.editor, { readOnly: this.props.readOnly })}
|
||||
schema={this.schema}
|
||||
plugins={this.plugins}
|
||||
state={this.state.state}
|
||||
onChange={this.onChange}
|
||||
onDocumentChange={this.onDocumentChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onSave={this.props.onSave}
|
||||
readOnly={this.props.readOnly}
|
||||
/>
|
||||
{!this.props.readOnly &&
|
||||
<ClickablePadding onClick={this.focusAtEnd} grow />}
|
||||
</Container>
|
||||
<Flex
|
||||
onDrop={this.handleDrop}
|
||||
onDragOver={this.cancelEvent}
|
||||
onDragEnter={this.cancelEvent}
|
||||
align="flex-start"
|
||||
justify="center"
|
||||
auto
|
||||
>
|
||||
<MaxWidth column auto>
|
||||
<HeaderContainer
|
||||
onClick={this.focusAtStart}
|
||||
readOnly={this.props.readOnly}
|
||||
>
|
||||
{this.props.heading}
|
||||
</HeaderContainer>
|
||||
<Toolbar state={this.state.state} onChange={this.onChange} />
|
||||
<Editor
|
||||
key={this.props.starred}
|
||||
ref={ref => (this.editor = ref)}
|
||||
placeholder="Start with a title..."
|
||||
className={cx(styles.editor, { readOnly: this.props.readOnly })}
|
||||
schema={this.schema}
|
||||
plugins={this.plugins}
|
||||
state={this.state.state}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onChange={this.onChange}
|
||||
onDocumentChange={this.onDocumentChange}
|
||||
onSave={this.props.onSave}
|
||||
readOnly={this.props.readOnly}
|
||||
/>
|
||||
{!this.props.readOnly &&
|
||||
<ClickablePadding onClick={this.focusAtEnd} grow />}
|
||||
</MaxWidth>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -153,7 +216,8 @@ MarkdownEditor.childContextTypes = {
|
||||
starred: PropTypes.bool,
|
||||
};
|
||||
|
||||
const Container = styled(Flex)`
|
||||
const MaxWidth = styled(Flex)`
|
||||
max-width: 50em;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
.editor {
|
||||
color: #1b2631;
|
||||
font-weight: 400;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
width: 100%;
|
||||
color: #1b2631;
|
||||
|
||||
h1,
|
||||
h2,
|
||||
|
||||
@@ -26,8 +26,8 @@ const createPlugins = ({ onImageUploadStart, onImageUploadStop }: Options) => {
|
||||
DropOrPasteImages({
|
||||
extensions: ['png', 'jpg', 'gif'],
|
||||
applyTransform: async (transform, file) => {
|
||||
onImageUploadStart();
|
||||
try {
|
||||
onImageUploadStart();
|
||||
const asset = await uploadFile(file);
|
||||
const alt = file.name;
|
||||
const src = asset.url;
|
||||
@@ -39,6 +39,7 @@ const createPlugins = ({ onImageUploadStart, onImageUploadStop }: Options) => {
|
||||
});
|
||||
} catch (err) {
|
||||
// TODO: Show a failure alert
|
||||
console.error(err);
|
||||
} finally {
|
||||
onImageUploadStop();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import _ from 'lodash';
|
||||
import keydown from 'react-keydown';
|
||||
import Flex from 'components/Flex';
|
||||
import { color, layout } from 'styles/constants';
|
||||
import { documentEditUrl, homeUrl, searchUrl } from 'utils/routeHelpers';
|
||||
|
||||
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
|
||||
import { LoadingIndicatorBar } from 'components/LoadingIndicator';
|
||||
@@ -51,15 +52,21 @@ type Props = {
|
||||
@observable modal = null;
|
||||
|
||||
@keydown(['/', 't'])
|
||||
search() {
|
||||
if (this.props.auth.authenticated)
|
||||
_.defer(() => this.props.history.push('/search'));
|
||||
goToSearch(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.props.history.push(searchUrl());
|
||||
}
|
||||
|
||||
@keydown('d')
|
||||
dashboard() {
|
||||
if (this.props.auth.authenticated)
|
||||
_.defer(() => this.props.history.push('/'));
|
||||
goToDashboard() {
|
||||
this.props.history.push(homeUrl());
|
||||
}
|
||||
|
||||
@keydown('e')
|
||||
goToEdit() {
|
||||
if (!this.props.ui.activeDocument) return;
|
||||
this.props.history.push(documentEditUrl(this.props.ui.activeDocument));
|
||||
}
|
||||
|
||||
handleLogout = () => {
|
||||
@@ -67,9 +74,9 @@ type Props = {
|
||||
};
|
||||
|
||||
@keydown('shift+/')
|
||||
handleOpenKeyboardShortcuts = () => {
|
||||
handleOpenKeyboardShortcuts() {
|
||||
this.modal = 'keyboard-shortcuts';
|
||||
};
|
||||
}
|
||||
|
||||
handleCreateCollection = () => {
|
||||
this.modal = 'create-collection';
|
||||
|
||||
@@ -124,13 +124,13 @@ type Props = {
|
||||
);
|
||||
}
|
||||
|
||||
onImageUploadStart() {
|
||||
onImageUploadStart = () => {
|
||||
this.setState({ isLoading: true });
|
||||
}
|
||||
};
|
||||
|
||||
onImageUploadStop() {
|
||||
onImageUploadStop = () => {
|
||||
this.setState({ isLoading: false });
|
||||
}
|
||||
};
|
||||
|
||||
onChange = text => {
|
||||
if (!this.document) return;
|
||||
@@ -168,7 +168,7 @@ type Props = {
|
||||
|
||||
render() {
|
||||
const isNew = this.props.newDocument;
|
||||
const isEditing = this.props.match.params.edit || isNew;
|
||||
const isEditing = !!this.props.match.params.edit || isNew;
|
||||
const isFetching = !this.document;
|
||||
const titleText = get(this.document, 'title', 'Loading');
|
||||
|
||||
@@ -192,28 +192,27 @@ type Props = {
|
||||
onDragEnter={this.onStartDragging}
|
||||
onDragLeave={this.onStopDragging}
|
||||
onDrop={this.onStopDragging}
|
||||
disabled={isEditing}
|
||||
>
|
||||
<Flex justify="center" auto>
|
||||
<Prompt
|
||||
when={this.document.hasPendingChanges}
|
||||
message={DISCARD_CHANGES}
|
||||
/>
|
||||
<DocumentContainer>
|
||||
<Editor
|
||||
key={this.document.id}
|
||||
text={this.document.text}
|
||||
onImageUploadStart={this.onImageUploadStart}
|
||||
onImageUploadStop={this.onImageUploadStop}
|
||||
onChange={this.onChange}
|
||||
onSave={this.onSave}
|
||||
onCancel={this.onCancel}
|
||||
onStar={this.document.star}
|
||||
onUnstar={this.document.unstar}
|
||||
starred={this.document.starred}
|
||||
heading={this.renderHeading(!!isEditing)}
|
||||
readOnly={!isEditing}
|
||||
/>
|
||||
</DocumentContainer>
|
||||
<Editor
|
||||
key={this.document.id}
|
||||
text={this.document.text}
|
||||
onImageUploadStart={this.onImageUploadStart}
|
||||
onImageUploadStop={this.onImageUploadStop}
|
||||
onChange={this.onChange}
|
||||
onSave={this.onSave}
|
||||
onCancel={this.onCancel}
|
||||
onStar={this.document.star}
|
||||
onUnstar={this.document.unstar}
|
||||
starred={this.document.starred}
|
||||
heading={this.renderHeading(!!isEditing)}
|
||||
readOnly={!isEditing}
|
||||
/>
|
||||
<Meta align="center" justify="flex-end" readOnly={!isEditing}>
|
||||
<Flex align="center">
|
||||
<HeaderAction>
|
||||
@@ -270,14 +269,6 @@ const LoadingState = styled(PreviewLoading)`
|
||||
margin: 80px 20px;
|
||||
`;
|
||||
|
||||
const DocumentContainer = styled.div`
|
||||
font-weight: 400;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
padding: 0 3em;
|
||||
width: 50em;
|
||||
`;
|
||||
|
||||
const StyledDropToImport = styled(DropToImport)`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- `Cmd+Enter` - Save and exit document editor
|
||||
- `Cmd+s` - Save document and continue editing
|
||||
- `Cmd+S` - Save document and continue editing
|
||||
- `Cmd+Esc` - Cancel edit
|
||||
- `/` or `t` - Jump to search
|
||||
- `d` - Jump to dashboard
|
||||
|
||||
18
frontend/utils/getDataTransferFiles.js
Normal file
18
frontend/utils/getDataTransferFiles.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// @flow
|
||||
export default function getDataTransferFiles(event: SyntheticEvent) {
|
||||
let dataTransferItemsList = [];
|
||||
if (event.dataTransfer) {
|
||||
const dt = event.dataTransfer;
|
||||
if (dt.files && dt.files.length) {
|
||||
dataTransferItemsList = dt.files;
|
||||
} else if (dt.items && dt.items.length) {
|
||||
// During the drag even the dataTransfer.files is null
|
||||
// but Chrome implements some drag store, which is accesible via dataTransfer.items
|
||||
dataTransferItemsList = dt.items;
|
||||
}
|
||||
} else if (event.target && event.target.files) {
|
||||
dataTransferItemsList = event.target.files;
|
||||
}
|
||||
// Convert from DataTransferItemsList to the native Array
|
||||
return Array.prototype.slice.call(dataTransferItemsList);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
import Document from 'models/Document';
|
||||
|
||||
export function homeUrl(): string {
|
||||
return '/dashboard';
|
||||
@@ -12,7 +13,15 @@ export function newCollectionUrl(): string {
|
||||
return '/collections/new';
|
||||
}
|
||||
|
||||
export function searchUrl(query: string): string {
|
||||
export function documentUrl(doc: Document): string {
|
||||
return doc.url;
|
||||
}
|
||||
|
||||
export function documentEditUrl(doc: Document): string {
|
||||
return `${doc.url}/edit`;
|
||||
}
|
||||
|
||||
export function searchUrl(query?: string): string {
|
||||
if (query) return `/search/${query}`;
|
||||
return `/search`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user