Merge branch 'master' of github.com:jorilallo/atlas into tests

This commit is contained in:
Tom Moor
2017-07-15 15:14:08 -07:00
9 changed files with 174 additions and 77 deletions

View File

@@ -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"

View File

@@ -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%;
`;

View File

@@ -1,6 +1,9 @@
.editor {
color: #1b2631;
font-weight: 400;
font-size: 1em;
line-height: 1.5em;
width: 100%;
color: #1b2631;
h1,
h2,

View File

@@ -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();
}

View File

@@ -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';

View File

@@ -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;

View File

@@ -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

View 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);
}

View File

@@ -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`;
}