Move star out of editor into documentpreview (#174)
* Move star out of editor into documentpreview
* 💚
This commit is contained in:
@ -1,10 +1,12 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Document from 'models/Document';
|
import Document from 'models/Document';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { color } from 'styles/constants';
|
import { color } from 'styles/constants';
|
||||||
import PublishingInfo from 'components/PublishingInfo';
|
import PublishingInfo from 'components/PublishingInfo';
|
||||||
|
import StarIcon from 'components/Icon/StarIcon';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
document: Document,
|
document: Document,
|
||||||
@ -13,10 +15,27 @@ type Props = {
|
|||||||
innerRef?: Function,
|
innerRef?: Function,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const StyledStar = styled(StarIcon)`
|
||||||
|
top: 2px;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 4px;
|
||||||
|
opacity: ${props => (props.solid ? 1 : 0.25)};
|
||||||
|
transition: opacity 100ms ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const DocumentLink = styled(Link)`
|
const DocumentLink = styled(Link)`
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 -16px;
|
margin: 0 -16px;
|
||||||
padding: 16px;
|
padding: 10px 16px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
max-height: 50vh;
|
max-height: 50vh;
|
||||||
@ -37,18 +56,36 @@ const DocumentLink = styled(Link)`
|
|||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
margin-bottom: .25em;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class DocumentPreview extends Component {
|
@observer class DocumentPreview extends Component {
|
||||||
props: Props;
|
props: Props;
|
||||||
|
|
||||||
|
star = (ev: SyntheticEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.props.document.star();
|
||||||
|
};
|
||||||
|
|
||||||
|
unstar = (ev: SyntheticEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.props.document.unstar();
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { document, showCollection, innerRef, ...rest } = this.props;
|
const { document, showCollection, innerRef, ...rest } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocumentLink to={document.url} innerRef={innerRef} {...rest}>
|
<DocumentLink to={document.url} innerRef={innerRef} {...rest}>
|
||||||
<h3>{document.title}</h3>
|
<h3>
|
||||||
|
{document.title}
|
||||||
|
{document.starred
|
||||||
|
? <a onClick={this.unstar}><StyledStar solid /></a>
|
||||||
|
: <a onClick={this.star}><StyledStar /></a>}
|
||||||
|
</h3>
|
||||||
<PublishingInfo
|
<PublishingInfo
|
||||||
document={document}
|
document={document}
|
||||||
collection={showCollection ? document.collection : undefined}
|
collection={showCollection ? document.collection : undefined}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Editor, Plain } from 'slate';
|
import { Editor, Plain } from 'slate';
|
||||||
import keydown from 'react-keydown';
|
import keydown from 'react-keydown';
|
||||||
@ -24,11 +23,8 @@ type Props = {
|
|||||||
onChange: Function,
|
onChange: Function,
|
||||||
onSave: Function,
|
onSave: Function,
|
||||||
onCancel: Function,
|
onCancel: Function,
|
||||||
onStar: Function,
|
|
||||||
onUnstar: Function,
|
|
||||||
onImageUploadStart: Function,
|
onImageUploadStart: Function,
|
||||||
onImageUploadStop: Function,
|
onImageUploadStop: Function,
|
||||||
starred: boolean,
|
|
||||||
emoji: string,
|
emoji: string,
|
||||||
readOnly: boolean,
|
readOnly: boolean,
|
||||||
heading?: ?React.Element<*>,
|
heading?: ?React.Element<*>,
|
||||||
@ -52,10 +48,7 @@ type KeyData = {
|
|||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.schema = createSchema({
|
this.schema = createSchema();
|
||||||
onStar: props.onStar,
|
|
||||||
onUnstar: props.onUnstar,
|
|
||||||
});
|
|
||||||
this.plugins = createPlugins({
|
this.plugins = createPlugins({
|
||||||
onImageUploadStart: props.onImageUploadStart,
|
onImageUploadStart: props.onImageUploadStart,
|
||||||
onImageUploadStop: props.onImageUploadStop,
|
onImageUploadStop: props.onImageUploadStop,
|
||||||
@ -84,10 +77,6 @@ type KeyData = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildContext() {
|
|
||||||
return { starred: this.props.starred };
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange = (state: State) => {
|
onChange = (state: State) => {
|
||||||
this.setState({ state });
|
this.setState({ state });
|
||||||
};
|
};
|
||||||
@ -207,7 +196,6 @@ type KeyData = {
|
|||||||
</HeaderContainer>
|
</HeaderContainer>
|
||||||
<Toolbar state={this.state.state} onChange={this.onChange} />
|
<Toolbar state={this.state.state} onChange={this.onChange} />
|
||||||
<Editor
|
<Editor
|
||||||
key={this.props.starred}
|
|
||||||
ref={ref => (this.editor = ref)}
|
ref={ref => (this.editor = ref)}
|
||||||
placeholder="Start with a title…"
|
placeholder="Start with a title…"
|
||||||
bodyPlaceholder="Insert witty platitude here"
|
bodyPlaceholder="Insert witty platitude here"
|
||||||
@ -232,10 +220,6 @@ type KeyData = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
MarkdownEditor.childContextTypes = {
|
|
||||||
starred: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MaxWidth = styled(Flex)`
|
const MaxWidth = styled(Flex)`
|
||||||
max-width: 50em;
|
max-width: 50em;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { Document } from 'slate';
|
import { Document } from 'slate';
|
||||||
|
import styled from 'styled-components';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import slug from 'slug';
|
import slug from 'slug';
|
||||||
import StarIcon from 'components/Icon/StarIcon';
|
|
||||||
import type { Node, Editor } from '../types';
|
import type { Node, Editor } from '../types';
|
||||||
import styles from '../Editor.scss';
|
import styles from '../Editor.scss';
|
||||||
|
|
||||||
@ -14,46 +12,22 @@ type Props = {
|
|||||||
placeholder?: boolean,
|
placeholder?: boolean,
|
||||||
parent: Node,
|
parent: Node,
|
||||||
node: Node,
|
node: Node,
|
||||||
onStar?: Function,
|
|
||||||
onUnstar?: Function,
|
|
||||||
editor: Editor,
|
editor: Editor,
|
||||||
readOnly: boolean,
|
readOnly: boolean,
|
||||||
component?: string,
|
component?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Context = {
|
|
||||||
starred?: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
|
display: inline;
|
||||||
margin-left: ${props => (props.hasEmoji ? '-1.2em' : 0)}
|
margin-left: ${props => (props.hasEmoji ? '-1.2em' : 0)}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledStar = styled(StarIcon)`
|
function Heading(props: Props) {
|
||||||
top: 3px;
|
|
||||||
position: relative;
|
|
||||||
margin-left: 4px;
|
|
||||||
opacity: ${props => (props.solid ? 1 : 0.25)};
|
|
||||||
transition: opacity 100ms ease-in-out;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function Heading(props: Props, { starred }: Context) {
|
|
||||||
const {
|
const {
|
||||||
parent,
|
parent,
|
||||||
placeholder,
|
placeholder,
|
||||||
node,
|
node,
|
||||||
editor,
|
editor,
|
||||||
onStar,
|
|
||||||
onUnstar,
|
|
||||||
readOnly,
|
readOnly,
|
||||||
children,
|
children,
|
||||||
component = 'h1',
|
component = 'h1',
|
||||||
@ -62,8 +36,7 @@ function Heading(props: Props, { starred }: Context) {
|
|||||||
const firstHeading = parentIsDocument && parent.nodes.first() === node;
|
const firstHeading = parentIsDocument && parent.nodes.first() === node;
|
||||||
const showPlaceholder = placeholder && firstHeading && !node.text;
|
const showPlaceholder = placeholder && firstHeading && !node.text;
|
||||||
const slugish = _.escape(`${component}-${slug(node.text)}`);
|
const slugish = _.escape(`${component}-${slug(node.text)}`);
|
||||||
const showStar = readOnly && !!onStar;
|
const showHash = readOnly && !!slugish;
|
||||||
const showHash = readOnly && !!slugish && !showStar;
|
|
||||||
const Component = component;
|
const Component = component;
|
||||||
const emoji = editor.props.emoji || '';
|
const emoji = editor.props.emoji || '';
|
||||||
const title = node.text.trim();
|
const title = node.text.trim();
|
||||||
@ -79,14 +52,8 @@ function Heading(props: Props, { starred }: Context) {
|
|||||||
</span>}
|
</span>}
|
||||||
{showHash &&
|
{showHash &&
|
||||||
<a name={slugish} className={styles.anchor} href={`#${slugish}`}>#</a>}
|
<a name={slugish} className={styles.anchor} href={`#${slugish}`}>#</a>}
|
||||||
{showStar && starred && <a onClick={onUnstar}><StyledStar solid /></a>}
|
|
||||||
{showStar && !starred && <a onClick={onStar}><StyledStar /></a>}
|
|
||||||
</Component>
|
</Component>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Heading.contextTypes = {
|
|
||||||
starred: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Heading;
|
export default Heading;
|
||||||
|
@ -9,12 +9,7 @@ import Paragraph from './components/Paragraph';
|
|||||||
import type { Props, Node, Transform } from './types';
|
import type { Props, Node, Transform } from './types';
|
||||||
import styles from './Editor.scss';
|
import styles from './Editor.scss';
|
||||||
|
|
||||||
type Options = {
|
const createSchema = () => {
|
||||||
onStar: Function,
|
|
||||||
onUnstar: Function,
|
|
||||||
};
|
|
||||||
|
|
||||||
const createSchema = ({ onStar, onUnstar }: Options) => {
|
|
||||||
return {
|
return {
|
||||||
marks: {
|
marks: {
|
||||||
bold: (props: Props) => <strong>{props.children}</strong>,
|
bold: (props: Props) => <strong>{props.children}</strong>,
|
||||||
@ -44,9 +39,7 @@ const createSchema = ({ onStar, onUnstar }: Options) => {
|
|||||||
image: Image,
|
image: Image,
|
||||||
link: Link,
|
link: Link,
|
||||||
'list-item': ListItem,
|
'list-item': ListItem,
|
||||||
heading1: (props: Props) => (
|
heading1: (props: Props) => <Heading placeholder {...props} />,
|
||||||
<Heading placeholder onStar={onStar} onUnstar={onUnstar} {...props} />
|
|
||||||
),
|
|
||||||
heading2: (props: Props) => <Heading component="h2" {...props} />,
|
heading2: (props: Props) => <Heading component="h2" {...props} />,
|
||||||
heading3: (props: Props) => <Heading component="h3" {...props} />,
|
heading3: (props: Props) => <Heading component="h3" {...props} />,
|
||||||
heading4: (props: Props) => <Heading component="h4" {...props} />,
|
heading4: (props: Props) => <Heading component="h4" {...props} />,
|
||||||
|
@ -26,6 +26,7 @@ const SidebarCollectionList = observer(({ history, collections }: Props) => {
|
|||||||
<Header>Collections</Header>
|
<Header>Collections</Header>
|
||||||
{collections.data.map(collection => (
|
{collections.data.map(collection => (
|
||||||
<DropToImport
|
<DropToImport
|
||||||
|
key={collection.id}
|
||||||
history={history}
|
history={history}
|
||||||
collectionId={collection.id}
|
collectionId={collection.id}
|
||||||
activeStyle={activeStyle}
|
activeStyle={activeStyle}
|
||||||
|
@ -209,9 +209,6 @@ type Props = {
|
|||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
onSave={this.onSave}
|
onSave={this.onSave}
|
||||||
onCancel={this.onCancel}
|
onCancel={this.onCancel}
|
||||||
onStar={document.star}
|
|
||||||
onUnstar={document.unstar}
|
|
||||||
starred={document.starred}
|
|
||||||
heading={this.renderHeading(!!isEditing)}
|
heading={this.renderHeading(!!isEditing)}
|
||||||
readOnly={!isEditing}
|
readOnly={!isEditing}
|
||||||
/>
|
/>
|
||||||
|
@ -40,7 +40,19 @@ router.post('documents.viewed', auth(), pagination(), async ctx => {
|
|||||||
const views = await View.findAll({
|
const views = await View.findAll({
|
||||||
where: { userId: user.id },
|
where: { userId: user.id },
|
||||||
order: [[sort, direction]],
|
order: [[sort, direction]],
|
||||||
include: [{ model: Document, required: true }],
|
include: [
|
||||||
|
{
|
||||||
|
model: Document,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Star,
|
||||||
|
as: 'starred',
|
||||||
|
where: { userId: user.id },
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
offset: ctx.state.pagination.offset,
|
offset: ctx.state.pagination.offset,
|
||||||
limit: ctx.state.pagination.limit,
|
limit: ctx.state.pagination.limit,
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user