From 919bca676981e1c2d38f298d3edc5c68603f2722 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 18 Nov 2018 01:13:28 -0800 Subject: [PATCH] Collections got descriptions now --- app/components/InputRich.js | 38 ++++++++++++++++++++++++ app/models/Collection.js | 15 +--------- app/scenes/Collection.js | 8 +++++ app/scenes/CollectionEdit.js | 27 ++++++++++++++--- app/scenes/Document/components/Editor.js | 32 ++------------------ app/scenes/Document/schema.js | 35 ++++++++++++++++++++++ server/api/collections.js | 3 +- 7 files changed, 110 insertions(+), 48 deletions(-) create mode 100644 app/components/InputRich.js create mode 100644 app/scenes/Document/schema.js diff --git a/app/components/InputRich.js b/app/components/InputRich.js new file mode 100644 index 000000000..d2b08843c --- /dev/null +++ b/app/components/InputRich.js @@ -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 ( + + {label} + + + + + ); +} + +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; + } +`; diff --git a/app/models/Collection.js b/app/models/Collection.js index 625bb87ba..9a005e56a 100644 --- a/app/models/Collection.js +++ b/app/models/Collection.js @@ -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); } diff --git a/app/scenes/Collection.js b/app/scenes/Collection.js index 99cd8e2da..cc6720efe 100644 --- a/app/scenes/Collection.js +++ b/app/scenes/Collection.js @@ -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 { />{' '} {this.collection.name} + {this.collection.description && ( + + )} {hasPinnedDocuments && ( diff --git a/app/scenes/CollectionEdit.js b/app/scenes/CollectionEdit.js index 6ae01f237..c68720982 100644 --- a/app/scenes/CollectionEdit.js +++ b/app/scenes/CollectionEdit.js @@ -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 { @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 { 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 {
- 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. { required autoFocus /> + { - 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 { editor: *; diff --git a/app/scenes/Document/schema.js b/app/scenes/Document/schema.js new file mode 100644 index 000000000..64a3be4d3 --- /dev/null +++ b/app/scenes/Document/schema.js @@ -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; diff --git a/server/api/collections.js b/server/api/collections.js index a15942674..9812f0700 100644 --- a/server/api/collections.js +++ b/server/api/collections.js @@ -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();