diff --git a/app/components/Actions/Actions.js b/app/components/Actions/Actions.js
index b1b56ec1d..ae0a7cac1 100644
--- a/app/components/Actions/Actions.js
+++ b/app/components/Actions/Actions.js
@@ -7,6 +7,7 @@ export const Action = styled(Flex)`
justify-content: center;
align-items: center;
padding: 0 0 0 12px;
+ font-size: 15px;
a {
color: ${props => props.theme.text};
@@ -37,8 +38,7 @@ const Actions = styled(Flex)`
${breakpoint('tablet')`
left: auto;
- padding: ${props => props.theme.vpadding} ${props =>
- props.theme.hpadding} 8px 8px;
+ padding: ${props => props.theme.vpadding} ${props => props.theme.hpadding};
`};
`;
diff --git a/app/components/Collaborators/Collaborators.js b/app/components/Collaborators/Collaborators.js
index 463e08c1a..0fbeaf1c2 100644
--- a/app/components/Collaborators/Collaborators.js
+++ b/app/components/Collaborators/Collaborators.js
@@ -31,22 +31,21 @@ const Collaborators = ({ document }: Props) => {
return (
-
- {collaborators.map(user => (
-
+ {collaborators.map(user => (
+ 1 ? user.name : tooltip}
+ placement="bottom"
+ key={user.id}
+ >
+
- ))}
-
+
+ ))}
);
};
-const StyledTooltip = styled(Tooltip)`
- display: flex;
- flex-direction: row-reverse;
-`;
-
const AvatarWrapper = styled.div`
width: 24px;
height: 24px;
diff --git a/app/models/Document.js b/app/models/Document.js
index 0b9c9cfad..65b5c953f 100644
--- a/app/models/Document.js
+++ b/app/models/Document.js
@@ -8,7 +8,7 @@ import UiStore from 'stores/UiStore';
import parseTitle from '../../shared/utils/parseTitle';
import unescape from '../../shared/utils/unescape';
-import type { User } from 'types';
+import type { NavigationNode, User } from 'types';
import BaseModel from './BaseModel';
import Collection from './Collection';
@@ -52,17 +52,11 @@ class Document extends BaseModel {
}
@computed
- get pathToDocument(): Array<{ id: string, title: string }> {
+ get pathToDocument(): NavigationNode[] {
let path;
const traveler = (nodes, previousPath) => {
nodes.forEach(childNode => {
- const newPath = [
- ...previousPath,
- {
- id: childNode.id,
- title: childNode.title,
- },
- ];
+ const newPath = [...previousPath, childNode];
if (childNode.id === this.id) {
path = newPath;
return;
diff --git a/app/scenes/Document/Document.js b/app/scenes/Document/Document.js
index 23ef41cf8..ef5147934 100644
--- a/app/scenes/Document/Document.js
+++ b/app/scenes/Document/Document.js
@@ -21,7 +21,7 @@ import { uploadFile } from 'utils/uploadFile';
import isInternalUrl from 'utils/isInternalUrl';
import Document from 'models/Document';
-import Actions from './components/Actions';
+import Header from './components/Header';
import DocumentMove from './components/DocumentMove';
import UiStore from 'stores/UiStore';
import AuthStore from 'stores/AuthStore';
@@ -248,7 +248,7 @@ class DocumentScene extends React.Component {
};
render() {
- const { location, match } = this.props;
+ const { location, match, ui } = this.props;
const Editor = this.editorComponent;
const isMoving = match.path === matchDocumentMove;
const document = this.document;
@@ -269,7 +269,7 @@ class DocumentScene extends React.Component {
}
return (
-
+
{isMoving && document && }
{titleText && }
{(this.isUploading || this.isSaving) && }
@@ -290,6 +290,7 @@ class DocumentScene extends React.Component {
)}
{
{document &&
!isShare && (
- {
history={this.props.history}
onDiscard={this.onDiscard}
onSave={this.onSave}
+ editMode={ui.editMode}
/>
)}
diff --git a/app/scenes/Document/components/Actions.js b/app/scenes/Document/components/Actions.js
deleted file mode 100644
index dd90aa88a..000000000
--- a/app/scenes/Document/components/Actions.js
+++ /dev/null
@@ -1,130 +0,0 @@
-// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import { NewDocumentIcon } from 'outline-icons';
-import Document from 'models/Document';
-import { documentEditUrl, documentNewUrl } from 'utils/routeHelpers';
-
-import DocumentMenu from 'menus/DocumentMenu';
-import Collaborators from 'components/Collaborators';
-import Actions, { Action, Separator } from 'components/Actions';
-
-type Props = {
- document: Document,
- isDraft: boolean,
- isEditing: boolean,
- isSaving: boolean,
- isPublishing: boolean,
- savingIsDisabled: boolean,
- onDiscard: () => *,
- onSave: ({
- done?: boolean,
- publish?: boolean,
- autosave?: boolean,
- }) => *,
- history: Object,
-};
-
-class DocumentActions extends React.Component {
- handleNewDocument = () => {
- this.props.history.push(documentNewUrl(this.props.document));
- };
-
- handleEdit = () => {
- this.props.history.push(documentEditUrl(this.props.document));
- };
-
- handleSave = () => {
- this.props.onSave({ done: true });
- };
-
- handlePublish = () => {
- this.props.onSave({ done: true, publish: true });
- };
-
- render() {
- const {
- document,
- isEditing,
- isDraft,
- isPublishing,
- isSaving,
- savingIsDisabled,
- } = this.props;
-
- return (
-
- {!isDraft && !isEditing && }
- {isDraft && (
-
-
- {isPublishing ? 'Publishing…' : 'Publish'}
-
-
- )}
- {isEditing && (
-
-
-
- {isSaving && !isPublishing ? 'Saving…' : 'Save'}
-
-
- {isDraft && }
-
- )}
- {!isEditing && (
-
- Edit
-
- )}
- {isEditing && (
-
-
- {document.hasPendingChanges ? 'Discard' : 'Done'}
-
-
- )}
- {!isEditing && (
-
-
-
- )}
- {!isEditing &&
- !isDraft && (
-
-
-
-
-
-
-
-
- )}
-
- );
- }
-}
-
-const Link = styled.a`
- display: flex;
- align-items: center;
- font-weight: ${props => (props.highlight ? 500 : 'inherit')};
- color: ${props =>
- props.highlight ? `${props.theme.primary} !important` : 'inherit'};
- opacity: ${props => (props.disabled ? 0.5 : 1)};
- pointer-events: ${props => (props.disabled ? 'none' : 'auto')};
- cursor: ${props => (props.disabled ? 'default' : 'pointer')};
-`;
-
-export default DocumentActions;
diff --git a/app/scenes/Document/components/Breadcrumb.js b/app/scenes/Document/components/Breadcrumb.js
new file mode 100644
index 000000000..72b3e5b8b
--- /dev/null
+++ b/app/scenes/Document/components/Breadcrumb.js
@@ -0,0 +1,61 @@
+// @flow
+import * as React from 'react';
+import styled from 'styled-components';
+import { Link } from 'react-router-dom';
+import { CollectionIcon, GoToIcon } from 'outline-icons';
+
+import { collectionUrl } from 'utils/routeHelpers';
+import Flex from 'shared/components/Flex';
+import Document from 'models/Document';
+
+type Props = {
+ document: Document,
+};
+
+const Breadcrumb = ({ document }: Props) => {
+ const path = document.pathToDocument.slice(0, -1);
+
+ return (
+
+
+ {' '}
+ {document.collection.name}
+
+ {path.map(n => (
+
+ {n.title}
+
+ ))}
+
+ );
+};
+
+const Wrapper = styled(Flex)`
+ width: 33.3%;
+`;
+
+const Slash = styled(GoToIcon)`
+ opacity: 0.25;
+`;
+
+const Crumb = styled(Link)`
+ color: ${props => props.theme.text};
+ font-size: 15px;
+ height: 24px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+
+ &:hover {
+ text-decoration: underline;
+ }
+`;
+
+const CollectionName = styled(Link)`
+ color: ${props => props.theme.text};
+ font-size: 15px;
+ display: flex;
+ font-weight: 500;
+`;
+
+export default Breadcrumb;
diff --git a/app/scenes/Document/components/Header.js b/app/scenes/Document/components/Header.js
new file mode 100644
index 000000000..0d064d31e
--- /dev/null
+++ b/app/scenes/Document/components/Header.js
@@ -0,0 +1,212 @@
+// @flow
+import * as React from 'react';
+import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
+import { observable } from 'mobx';
+import { observer } from 'mobx-react';
+import styled from 'styled-components';
+import breakpoint from 'styled-components-breakpoint';
+import { NewDocumentIcon } from 'outline-icons';
+import Document from 'models/Document';
+import { documentEditUrl, documentNewUrl } from 'utils/routeHelpers';
+
+import Flex from 'shared/components/Flex';
+import Breadcrumb from './Breadcrumb';
+import DocumentMenu from 'menus/DocumentMenu';
+import Collaborators from 'components/Collaborators';
+import { Action, Separator } from 'components/Actions';
+
+type Props = {
+ document: Document,
+ isDraft: boolean,
+ isEditing: boolean,
+ isSaving: boolean,
+ isPublishing: boolean,
+ savingIsDisabled: boolean,
+ editMode: boolean,
+ onDiscard: () => *,
+ onSave: ({
+ done?: boolean,
+ publish?: boolean,
+ autosave?: boolean,
+ }) => *,
+ history: Object,
+};
+
+@observer
+class Header extends React.Component {
+ @observable isScrolled = false;
+
+ componentWillMount() {
+ this.handleScroll();
+ }
+
+ componentDidMount() {
+ window.addEventListener('scroll', this.handleScroll);
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('scroll', this.handleScroll);
+ }
+
+ handleScroll = () => {
+ this.isScrolled = window.scrollY > 75;
+ };
+
+ handleNewDocument = () => {
+ this.props.history.push(documentNewUrl(this.props.document));
+ };
+
+ handleEdit = () => {
+ this.props.history.push(documentEditUrl(this.props.document));
+ };
+
+ handleSave = () => {
+ this.props.onSave({ done: true });
+ };
+
+ handlePublish = () => {
+ this.props.onSave({ done: true, publish: true });
+ };
+
+ render() {
+ const {
+ document,
+ isEditing,
+ isDraft,
+ isPublishing,
+ isSaving,
+ savingIsDisabled,
+ editMode,
+ } = this.props;
+
+ return (
+
+
+ {document.title}
+
+ {!isDraft && !isEditing && }
+ {isDraft && (
+
+
+ {isPublishing ? 'Publishing…' : 'Publish'}
+
+
+ )}
+ {isEditing && (
+
+
+ {isSaving && !isPublishing && Saving…}
+
+ Done
+
+
+ {isDraft && }
+
+ )}
+ {!isEditing && (
+
+ Edit
+
+ )}
+ {isEditing &&
+ !isSaving &&
+ document.hasPendingChanges && (
+
+ Discard
+
+ )}
+ {!isEditing && (
+
+
+
+ )}
+ {!isEditing &&
+ !isDraft && (
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+ }
+}
+
+const Status = styled.div`
+ color: ${props => props.theme.slate};
+ margin-right: 12px;
+`;
+
+const Wrapper = styled(Flex)`
+ width: 33.3%;
+`;
+
+const Actions = styled(Flex)`
+ position: fixed;
+ top: 0;
+ right: 0;
+ left: ${props => (props.editMode ? '0' : props.theme.sidebarWidth)};
+ background: rgba(255, 255, 255, 0.9);
+ border-bottom: 1px solid
+ ${props => (props.isCompact ? props.theme.smoke : 'transparent')};
+ padding: 12px;
+ transition: all 100ms ease-out;
+ -webkit-backdrop-filter: blur(20px);
+
+ @media print {
+ display: none;
+ }
+
+ ${breakpoint('tablet')`
+ padding: ${props =>
+ props.isCompact ? '12px' : `${props.theme.padding} 0`};
+ `};
+`;
+
+const Title = styled.div`
+ width: 33.3%;
+ font-size: 16px;
+ font-weight: 600;
+ text-align: center;
+ justify-content: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ transition: opacity 100ms ease-in-out;
+ opacity: ${props => (props.isHidden ? '0' : '1')};
+`;
+
+const Link = styled.a`
+ display: flex;
+ align-items: center;
+ font-weight: ${props => (props.highlight ? 500 : 'inherit')};
+ color: ${props =>
+ props.highlight ? `${props.theme.primary} !important` : 'inherit'};
+ opacity: ${props => (props.disabled ? 0.5 : 1)};
+ pointer-events: ${props => (props.disabled ? 'none' : 'auto')};
+ cursor: ${props => (props.disabled ? 'default' : 'pointer')};
+`;
+
+export default Header;
diff --git a/shared/styles/theme.js b/shared/styles/theme.js
index 7c71623ff..e301fea2b 100644
--- a/shared/styles/theme.js
+++ b/shared/styles/theme.js
@@ -27,8 +27,8 @@ const theme = {
black: '#000000',
blackLight: '#2f3336',
- padding: '1.5vw 1.875vw',
- vpadding: '1.5vw',
+ padding: '1.6vw 1.875vw',
+ vpadding: '1.6vw',
hpadding: '1.875vw',
sidebarWidth: '280px',
sidebarMinWidth: '250px',