Collections got descriptions now
This commit is contained in:
38
app/components/InputRich.js
Normal file
38
app/components/InputRich.js
Normal file
@@ -0,0 +1,38 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import RichMarkdownEditor from 'rich-markdown-editor';
|
||||
import { LabelText, Outline } from 'components/Input';
|
||||
|
||||
type Props = {
|
||||
label: string,
|
||||
minHeight?: number,
|
||||
maxHeight?: number,
|
||||
};
|
||||
|
||||
export default function InputRich({
|
||||
label,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
...rest
|
||||
}: Props) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<LabelText>{label}</LabelText>
|
||||
<StyledOutline maxHeight={maxHeight} minHeight={minHeight}>
|
||||
<RichMarkdownEditor {...rest} />
|
||||
</StyledOutline>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledOutline = styled(Outline)`
|
||||
padding: 8px 12px;
|
||||
min-height: ${({ minHeight }) => (minHeight ? `${minHeight}px` : '0')};
|
||||
max-height: ${({ maxHeight }) => (maxHeight ? `${maxHeight}px` : 'auto')};
|
||||
overflow: scroll;
|
||||
|
||||
> * {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
@@ -12,10 +12,9 @@ import type { NavigationNode } from 'types';
|
||||
class Collection extends BaseModel {
|
||||
isSaving: boolean = false;
|
||||
ui: UiStore;
|
||||
data: Object;
|
||||
|
||||
createdAt: string;
|
||||
description: ?string;
|
||||
description: string;
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
@@ -24,15 +23,6 @@ class Collection extends BaseModel {
|
||||
updatedAt: string;
|
||||
url: string;
|
||||
|
||||
/* Computed */
|
||||
|
||||
@computed
|
||||
get entryUrl(): string {
|
||||
return this.type === 'atlas' && this.documents.length > 0
|
||||
? this.documents[0].url
|
||||
: this.url;
|
||||
}
|
||||
|
||||
@computed
|
||||
get isEmpty(): boolean {
|
||||
return this.documents.length === 0;
|
||||
@@ -66,8 +56,6 @@ class Collection extends BaseModel {
|
||||
travelDocuments(this.documents);
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
|
||||
@action
|
||||
fetch = async () => {
|
||||
try {
|
||||
@@ -138,7 +126,6 @@ class Collection extends BaseModel {
|
||||
|
||||
@action
|
||||
updateData(data: Object = {}) {
|
||||
this.data = data;
|
||||
extendObservable(this, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { observer, inject } from 'mobx-react';
|
||||
import { withRouter, Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { CollectionIcon, NewDocumentIcon, PinIcon } from 'outline-icons';
|
||||
import RichMarkdownEditor from 'rich-markdown-editor';
|
||||
|
||||
import { newDocumentUrl } from 'utils/routeHelpers';
|
||||
import CollectionsStore from 'stores/CollectionsStore';
|
||||
@@ -157,6 +158,13 @@ class CollectionScene extends React.Component<Props> {
|
||||
/>{' '}
|
||||
{this.collection.name}
|
||||
</Heading>
|
||||
{this.collection.description && (
|
||||
<RichMarkdownEditor
|
||||
key={this.collection.description}
|
||||
defaultValue={this.collection.description}
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasPinnedDocuments && (
|
||||
<React.Fragment>
|
||||
|
||||
@@ -3,8 +3,9 @@ import * as React from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { observable } from 'mobx';
|
||||
import { inject, observer } from 'mobx-react';
|
||||
import Button from 'components/Button';
|
||||
import Input from 'components/Input';
|
||||
import InputRich from 'components/InputRich';
|
||||
import Button from 'components/Button';
|
||||
import Flex from 'shared/components/Flex';
|
||||
import HelpText from 'components/HelpText';
|
||||
import ColorPicker from 'components/ColorPicker';
|
||||
@@ -19,18 +20,24 @@ type Props = {
|
||||
@observer
|
||||
class CollectionEdit extends React.Component<Props> {
|
||||
@observable name: string;
|
||||
@observable description: string = '';
|
||||
@observable color: string = '';
|
||||
@observable isSaving: boolean;
|
||||
|
||||
componentWillMount() {
|
||||
this.name = this.props.collection.name;
|
||||
this.description = this.props.collection.description;
|
||||
}
|
||||
|
||||
handleSubmit = async (ev: SyntheticEvent<*>) => {
|
||||
ev.preventDefault();
|
||||
this.isSaving = true;
|
||||
|
||||
this.props.collection.updateData({ name: this.name, color: this.color });
|
||||
this.props.collection.updateData({
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
color: this.color,
|
||||
});
|
||||
const success = await this.props.collection.save();
|
||||
|
||||
if (success) {
|
||||
@@ -40,6 +47,10 @@ class CollectionEdit extends React.Component<Props> {
|
||||
this.isSaving = false;
|
||||
};
|
||||
|
||||
handleDescriptionChange = getValue => {
|
||||
this.description = getValue();
|
||||
};
|
||||
|
||||
handleNameChange = (ev: SyntheticInputEvent<*>) => {
|
||||
this.name = ev.target.value;
|
||||
};
|
||||
@@ -53,8 +64,8 @@ class CollectionEdit extends React.Component<Props> {
|
||||
<Flex column>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<HelpText>
|
||||
You can edit a collection’s name at any time, however doing so might
|
||||
confuse your team mates.
|
||||
You can edit a collection’s details at any time, however doing so
|
||||
might confuse your team mates.
|
||||
</HelpText>
|
||||
<Input
|
||||
type="text"
|
||||
@@ -64,6 +75,14 @@ class CollectionEdit extends React.Component<Props> {
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
<InputRich
|
||||
label="Description"
|
||||
onChange={this.handleDescriptionChange}
|
||||
defaultValue={this.description || ''}
|
||||
placeholder="More details about this collection…"
|
||||
minHeight={68}
|
||||
maxHeight={200}
|
||||
/>
|
||||
<ColorPicker
|
||||
onSelect={this.handleColor}
|
||||
value={this.props.collection.color}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Block, Change, Node, Mark, Text } from 'slate';
|
||||
import RichMarkdownEditor, { Placeholder, schema } from 'rich-markdown-editor';
|
||||
import { Text } from 'slate';
|
||||
import RichMarkdownEditor, { Placeholder } from 'rich-markdown-editor';
|
||||
import ClickablePadding from 'components/ClickablePadding';
|
||||
import schema from '../schema';
|
||||
|
||||
type Props = {
|
||||
titlePlaceholder?: string,
|
||||
@@ -12,33 +13,6 @@ type Props = {
|
||||
readOnly: boolean,
|
||||
};
|
||||
|
||||
// add rules to the schema to ensure the first node is a heading
|
||||
schema.document.nodes.unshift({ types: ['heading1'], min: 1, max: 1 });
|
||||
schema.document.normalize = (
|
||||
change: Change,
|
||||
reason: string,
|
||||
{
|
||||
node,
|
||||
child,
|
||||
mark,
|
||||
index,
|
||||
}: { node: Node, mark?: Mark, child: Node, index: number }
|
||||
) => {
|
||||
switch (reason) {
|
||||
case 'child_type_invalid': {
|
||||
return change.setNodeByKey(
|
||||
child.key,
|
||||
index === 0 ? 'heading1' : 'paragraph'
|
||||
);
|
||||
}
|
||||
case 'child_required': {
|
||||
const block = Block.create(index === 0 ? 'heading1' : 'paragraph');
|
||||
return change.insertNodeByKey(node.key, index, block);
|
||||
}
|
||||
default:
|
||||
}
|
||||
};
|
||||
|
||||
class Editor extends React.Component<Props> {
|
||||
editor: *;
|
||||
|
||||
|
||||
35
app/scenes/Document/schema.js
Normal file
35
app/scenes/Document/schema.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// @flow
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { Block, Change, Node, Mark } from 'slate';
|
||||
import { schema as originalSchema } from 'rich-markdown-editor';
|
||||
|
||||
const schema = cloneDeep(originalSchema);
|
||||
|
||||
// add rules to the schema to ensure the first node is a heading
|
||||
schema.document.nodes.unshift({ types: ['heading1'], min: 1, max: 1 });
|
||||
schema.document.normalize = (
|
||||
change: Change,
|
||||
reason: string,
|
||||
{
|
||||
node,
|
||||
child,
|
||||
mark,
|
||||
index,
|
||||
}: { node: Node, mark?: Mark, child: Node, index: number }
|
||||
) => {
|
||||
switch (reason) {
|
||||
case 'child_type_invalid': {
|
||||
return change.setNodeByKey(
|
||||
child.key,
|
||||
index === 0 ? 'heading1' : 'paragraph'
|
||||
);
|
||||
}
|
||||
case 'child_required': {
|
||||
const block = Block.create(index === 0 ? 'heading1' : 'paragraph');
|
||||
return change.insertNodeByKey(node.key, index, block);
|
||||
}
|
||||
default:
|
||||
}
|
||||
};
|
||||
|
||||
export default schema;
|
||||
@@ -77,7 +77,7 @@ router.post('collections.exportAll', auth(), async ctx => {
|
||||
});
|
||||
|
||||
router.post('collections.update', auth(), async ctx => {
|
||||
const { id, name, color } = ctx.body;
|
||||
const { id, name, description, color } = ctx.body;
|
||||
ctx.assertPresent(name, 'name is required');
|
||||
if (color)
|
||||
ctx.assertHexColor(color, 'Invalid hex value (please use format #FFFFFF)');
|
||||
@@ -86,6 +86,7 @@ router.post('collections.update', auth(), async ctx => {
|
||||
authorize(ctx.state.user, 'update', collection);
|
||||
|
||||
collection.name = name;
|
||||
collection.description = description;
|
||||
collection.color = color;
|
||||
await collection.save();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user