Merge pull request #71 from jorilallo/client-side-previews

Document previews rendered on client
This commit is contained in:
Jori Lallo
2017-05-26 12:17:12 -07:00
committed by GitHub
34 changed files with 213 additions and 127 deletions

View File

@@ -2,43 +2,55 @@
import React from 'react';
import { toJS } from 'mobx';
import { Link } from 'react-router-dom';
import styles from './DocumentPreview.scss';
import type { Document } from 'types';
import styled from 'styled-components';
import { color } from 'styles/constants';
import Markdown from 'components/Markdown';
import PublishingInfo from 'components/PublishingInfo';
class Document extends React.Component {
static propTypes = {
document: React.PropTypes.object.isRequired,
};
type Props = {
document: Document,
};
render() {
return (
<div className={styles.container}>
<PublishingInfo
createdAt={this.props.document.createdAt}
createdBy={this.props.document.createdBy}
updatedAt={this.props.document.updatedAt}
updatedBy={this.props.document.updatedBy}
collaborators={toJS(this.props.document.collaborators)}
/>
const Container = styled.div`
width: 100%;
padding: 20px 0;
`;
<Link to={this.props.document.url} className={styles.title}>
<h2>{this.props.document.title}</h2>
</Link>
const DocumentLink = styled(Link)`
display: block;
margin: -16px;
padding: 16px;
border-radius: 8px;
<div
dangerouslySetInnerHTML={{ __html: this.props.document.preview }}
/>
<div>
<Link to={this.props.document.url} className={styles.continueLink}>
Continue reading...
</Link>
</div>
</div>
);
h1 {
margin-top: 0;
}
}
export default Document;
&:hover {
background: ${color.smokeLight};
}
`;
const TruncatedMarkdown = styled(Markdown)`
pointer-events: none;
`;
const DocumentPreview = ({ document }: Props) => {
return (
<Container>
<DocumentLink to={document.url}>
<PublishingInfo
createdAt={document.createdAt}
createdBy={document.createdBy}
updatedAt={document.updatedAt}
updatedBy={document.updatedBy}
collaborators={toJS(document.collaborators)}
/>
<TruncatedMarkdown text={document.text} limit={150} />
</DocumentLink>
</Container>
);
};
export default DocumentPreview;

View File

@@ -1,20 +0,0 @@
@import '~styles/constants.scss';
.container {
width: 100%;
padding: 20px 0;
}
.title {
color: $textColor;
text-decoration: none;
h2 {
font-size: 1.3em;
}
}
.continueLink {
text-decoration: none;
}

View File

@@ -12,7 +12,6 @@
}
.editor {
background: #fff;
color: #1b2631;
height: auto;
width: 100%;

View File

@@ -15,7 +15,10 @@ const onlyInCode = node => node.type === 'code';
const createPlugins = ({
onImageUploadStart,
onImageUploadStop,
}: { onImageUploadStart: Function, onImageUploadStop: Function }) => {
}: {
onImageUploadStart: Function,
onImageUploadStop: Function,
}) => {
return [
PasteLinkify({
type: 'link',

View File

@@ -0,0 +1,77 @@
// @flow
import React from 'react';
import { State, Document, Editor } from 'slate';
import MarkdownSerializer from '../Editor/serializer';
import type { State as StateType } from '../Editor/types';
import schema from '../Editor/schema';
import styles from '../Editor/Editor.scss';
type Props = {
text: string,
className: string,
limit: number,
};
function filterDocumentState({ state, characterLimit, nodeLimit }) {
const { document } = state;
if (document.text.length <= characterLimit) {
return state;
}
let totalCharacters = 0;
let totalNodes = 0;
const nodes = document.nodes.filter(childNode => {
if (childNode.text.length + totalCharacters <= characterLimit) {
totalCharacters += childNode.text.length;
if (totalNodes++ <= nodeLimit) {
return true;
}
}
return false;
});
return State.create({
document: Document.create({
...document,
nodes: nodes,
}),
});
}
class Markdown extends React.Component {
props: Props;
state: {
state: StateType,
};
constructor(props: Props) {
super(props);
const state = MarkdownSerializer.deserialize(props.text);
const options = {
state,
characterLimit: props.limit,
nodeLimit: 5,
};
this.state = {
state: filterDocumentState(options),
};
}
render() {
return (
<span className={this.props.className}>
<Editor
className={styles.editor}
schema={schema}
state={this.state.state}
readOnly
/>
</span>
);
}
}
export default Markdown;

View File

@@ -0,0 +1,3 @@
// @flow
import Markdown from './Markdown';
export default Markdown;

View File

@@ -7,8 +7,8 @@ import { Flex } from 'reflexbox';
import DocumentStore from './DocumentStore';
import Breadcrumbs from './components/Breadcrumbs';
import Editor from './components/Editor';
import Menu from './components/Menu';
import Editor from 'components/Editor';
import Layout, { HeaderAction, SaveAction } from 'components/Layout';
import AtlasPreviewLoading from 'components/AtlasPreviewLoading';
import CenteredContent from 'components/CenteredContent';

View File

@@ -52,6 +52,7 @@ type Props = {
};
render() {
const query = this.props.match.params.query;
const title = <Title content="Search" />;
return (
@@ -80,6 +81,7 @@ type Props = {
searchTerm={this.store.searchTerm}
onKeyDown={this.handleKeyDown}
onChange={this.updateQuery}
value={query}
/>
</Flex>
{this.store.documents.map(document => (

View File

@@ -7,8 +7,8 @@ class SearchField extends Component {
onChange: Function,
};
handleChange = (event: SyntheticEvent) => {
event.currentTarget.value && this.props.onChange(event.currentTarget.value);
handleChange = (ev: SyntheticEvent) => {
this.props.onChange(ev.currentTarget.value ? ev.currentTarget.value : '');
};
render() {

View File

@@ -0,0 +1,46 @@
// @flow
export const size = {
tiny: '2px',
small: '4px',
medium: '8px',
large: '16px',
huge: '24px',
enormous: '32px',
};
export const fontSize = {
small: '14px',
medium: '16px',
large: '18px',
huge: '24px',
};
export const fontWeight = {
ultraLight: 100,
thin: 200,
light: 300,
regular: 400,
medium: 500,
demiBold: 600,
bold: 700,
heavy: 800,
};
export const color = {
/* Brand */
primary: '#73DF7B',
/* Dark Grays */
slate: '#9BA6B2',
slateLight: '#DAE1E9',
slateDark: '#4E5C6E',
/* Light Grays */
smoke: '#F4F7FA',
smokeLight: '#F9FBFC',
/* Misc */
white: '#FFFFFF',
black: '#000000',
};

View File

@@ -9,5 +9,6 @@ export function newCollectionUrl() {
}
export function searchUrl(query: string) {
return `/search/${query}`;
if (query) return `/search/${query}`;
return `/search`;
}

View File

@@ -164,7 +164,7 @@
"slug": "0.9.1",
"string-hash": "^1.1.0",
"style-loader": "0.13.0",
"styled-components": "^1.4.5",
"styled-components": "^2.0.0",
"truncate-html": "https://github.com/jorilallo/truncate-html/tarball/master",
"url-loader": "0.5.7",
"uuid": "2.0.2",
@@ -183,7 +183,6 @@
"koa-webpack-dev-middleware": "1.4.5",
"koa-webpack-hot-middleware": "1.0.3",
"lint-staged": "^3.4.0",
"node-dev": "3.1.0",
"nodemon": "1.11.0",
"prettier": "1.3.1",
"react-addons-test-utils": "^15.3.1",

View File

@@ -1209,7 +1209,7 @@ buffer@^4.3.0, buffer@^4.9.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
buffer@^5.0.2:
buffer@^5.0.3:
version "5.0.6"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.0.6.tgz#2ea669f7eec0b6eda05b08f8b5ff661b28573588"
dependencies:
@@ -1580,10 +1580,6 @@ colormin@^1.0.5:
css-color-names "0.0.4"
has "^1.0.1"
colors@0.5.x:
version "0.5.1"
resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774"
colors@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
@@ -1929,15 +1925,9 @@ csrf@~3.0.0:
tsscmp "1.0.5"
uid-safe "2.1.1"
css-color-list@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/css-color-list/-/css-color-list-0.0.1.tgz#8718e8695ae7a2cc8787be8715f1c008a7f28b15"
dependencies:
css-color-names "0.0.1"
css-color-names@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.1.tgz#5d0548fa256456ede4a9a0c2ac7ab19d3eb1ad81"
css-color-keywords@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
css-color-names@0.0.4:
version "0.0.4"
@@ -1983,13 +1973,13 @@ css-selector-tokenizer@^0.6.0:
fastparse "^1.1.1"
regexpu-core "^1.0.0"
css-to-react-native@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-1.0.6.tgz#728c7e774e56536558a0ecaa990d9507c43a4ac4"
css-to-react-native@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.0.4.tgz#cf4cc407558b3474d4ba8be1a2cd3b6ce713101b"
dependencies:
css-color-list "0.0.1"
css-color-keywords "^1.0.0"
fbjs "^0.8.5"
nearley "^2.7.7"
postcss-value-parser "^3.3.0"
css-what@2.1:
version "2.1.0"
@@ -2251,10 +2241,6 @@ direction@^0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/direction/-/direction-0.1.5.tgz#ce5d797f97e26f8be7beff53f7dc40e1c1a9ec4c"
discontinuous-range@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a"
dns-prefetch-control@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2"
@@ -2993,7 +2979,7 @@ fbjs@^0.8.4:
promise "^7.1.1"
ua-parser-js "^0.7.9"
fbjs@^0.8.5, fbjs@^0.8.7, fbjs@^0.8.9:
fbjs@^0.8.5, fbjs@^0.8.9:
version "0.8.12"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04"
dependencies:
@@ -5882,14 +5868,6 @@ ncname@1.0.x:
dependencies:
xml-char-classes "^1.0.0"
nearley@^2.7.7:
version "2.8.0"
resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.8.0.tgz#7f88b503e3b373503f5c7e5b3b52bf134c86145b"
dependencies:
nomnom "~1.6.2"
railroad-diagrams "^1.0.0"
randexp "^0.4.2"
negotiator@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.5.3.tgz#269d5c476810ec92edbe7b6c2f28316384f9a7e8"
@@ -6098,13 +6076,6 @@ nodemon@1.11.0:
undefsafe "0.0.3"
update-notifier "0.5.0"
nomnom@~1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.6.2.tgz#84a66a260174408fc5b77a18f888eccc44fb6971"
dependencies:
colors "0.5.x"
underscore "~1.4.4"
"nopt@2 || 3", nopt@~3.0.1:
version "3.0.6"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
@@ -6857,7 +6828,7 @@ postcss-unique-selectors@^2.0.2:
postcss "^5.0.4"
uniqs "^2.0.0"
postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.1.3, postcss-value-parser@^3.2.3:
postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.1.3, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15"
@@ -7060,17 +7031,6 @@ querystring@0.2.0, querystring@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
railroad-diagrams@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
randexp@^0.4.2:
version "0.4.5"
resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.5.tgz#ffe3a80c3f666cd71e6b008e477e584c1a32ff3e"
dependencies:
discontinuous-range "1.0.0"
ret "~0.1.10"
random-bytes@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
@@ -7596,10 +7556,6 @@ restore-cursor@^1.0.1:
exit-hook "^1.0.0"
onetime "^1.0.0"
ret@~0.1.10:
version "0.1.14"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.14.tgz#58c636837b12e161f8a380cf081c6a230fd1664e"
retry-as-promised@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-2.0.1.tgz#6edc0074d685d0520aeb180cface45ed42ddbef7"
@@ -8243,18 +8199,24 @@ style-loader@0.13.0:
dependencies:
loader-utils "^0.2.7"
styled-components@^1.4.5:
version "1.4.5"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-1.4.5.tgz#20c52f6355e28c7f20a99c05c6d5108a4858cfba"
styled-components@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.0.0.tgz#0906652b77647e7400ca7e5a6d8d45eba6fa77ec"
dependencies:
buffer "^5.0.2"
css-to-react-native "^1.0.6"
fbjs "^0.8.7"
buffer "^5.0.3"
css-to-react-native "^2.0.3"
fbjs "^0.8.9"
hoist-non-react-statics "^1.2.0"
inline-style-prefixer "^2.0.5"
is-function "^1.0.1"
is-plain-object "^2.0.1"
prop-types "^15.5.4"
supports-color "^3.1.2"
stylis "^2.0.0"
supports-color "^3.2.3"
stylis@^2.0.0:
version "2.0.12"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-2.0.12.tgz#547253055d170f2a7ac2f6d09365d70635f2bec6"
supports-color@^2.0.0:
version "2.0.0"
@@ -8266,6 +8228,12 @@ supports-color@^3.1.0, supports-color@^3.1.2:
dependencies:
has-flag "^1.0.0"
supports-color@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
dependencies:
has-flag "^1.0.0"
svgo@^0.7.0:
version "0.7.1"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.1.tgz#287320fed972cb097e72c2bb1685f96fe08f8034"
@@ -8604,10 +8572,6 @@ underscore@^1.7.0:
version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
underscore@~1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604"
understyle@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/understyle/-/understyle-1.3.0.tgz#df3f9a9be96779d718c3da9598fad1c2f90f24ee"