New login screen (#1331)
* wip * feat: first draft of auth.config * chore: auth methodS * chore: styling * styling, styling, styling * feat: Auth notices * chore: Remove server-rendered pages, move shared/components -> components * lint * cleanup * cleanup * fix: Remove unused component * fix: Ensure env variables in prod too * style tweaks * fix: Entering SSO email into login form fails fix: Tweak language around guest signin
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
export const Action = styled(Flex)`
|
||||
justify-content: center;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Flex from "shared/components/Flex";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = {
|
||||
children: React.Node,
|
||||
type?: "info" | "success" | "warning" | "danger" | "offline",
|
||||
};
|
||||
|
||||
@observer
|
||||
class Alert extends React.Component<Props> {
|
||||
defaultProps = {
|
||||
type: "info",
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Container align="center" justify="center" type={this.props.type}>
|
||||
{this.props.children}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Container = styled(Flex)`
|
||||
height: $headerHeight;
|
||||
color: ${props => props.theme.white};
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
|
||||
background-color: ${({ theme, type }) => theme.color[type]};
|
||||
`;
|
||||
|
||||
export default Alert;
|
||||
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import LoadingIndicator from "components/LoadingIndicator";
|
||||
import { isCustomSubdomain } from "shared/utils/domains";
|
||||
@@ -35,7 +36,7 @@ const Authenticated = observer(({ auth, children }: Props) => {
|
||||
}
|
||||
|
||||
auth.logout(true);
|
||||
return null;
|
||||
return <Redirect to="/" />;
|
||||
});
|
||||
|
||||
export default inject("auth")(Authenticated);
|
||||
|
||||
41
app/components/Branding.js
Normal file
41
app/components/Branding.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import OutlineLogo from "./OutlineLogo";
|
||||
|
||||
type Props = {
|
||||
href?: string,
|
||||
};
|
||||
|
||||
function Branding({ href = process.env.URL }: Props) {
|
||||
return (
|
||||
<Link href={href}>
|
||||
<OutlineLogo size={16} /> Outline
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
const Link = styled.a`
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
border-top-right-radius: 2px;
|
||||
color: ${props => props.theme.text};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
|
||||
svg {
|
||||
fill: ${props => props.theme.text};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: ${props => props.theme.sidebarBackground};
|
||||
}
|
||||
`;
|
||||
|
||||
export default Branding;
|
||||
133
app/components/Breadcrumb.js
Normal file
133
app/components/Breadcrumb.js
Normal file
@@ -0,0 +1,133 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import styled from "styled-components";
|
||||
import { Link } from "react-router-dom";
|
||||
import { PadlockIcon, GoToIcon, MoreIcon } from "outline-icons";
|
||||
|
||||
import Document from "models/Document";
|
||||
import CollectionsStore from "stores/CollectionsStore";
|
||||
import { collectionUrl } from "utils/routeHelpers";
|
||||
import Flex from "components/Flex";
|
||||
import BreadcrumbMenu from "./BreadcrumbMenu";
|
||||
import CollectionIcon from "components/CollectionIcon";
|
||||
|
||||
type Props = {
|
||||
document: Document,
|
||||
collections: CollectionsStore,
|
||||
onlyText: boolean,
|
||||
};
|
||||
|
||||
const Breadcrumb = observer(({ document, collections, onlyText }: Props) => {
|
||||
const collection = collections.get(document.collectionId);
|
||||
if (!collection) return <div />;
|
||||
|
||||
const path = collection.pathToDocument(document).slice(0, -1);
|
||||
|
||||
if (onlyText === true) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{collection.private && (
|
||||
<React.Fragment>
|
||||
<SmallPadlockIcon color="currentColor" size={16} />{" "}
|
||||
</React.Fragment>
|
||||
)}
|
||||
{collection.name}
|
||||
{path.map(n => (
|
||||
<React.Fragment key={n.id}>
|
||||
<SmallSlash />
|
||||
{n.title}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const isNestedDocument = path.length > 1;
|
||||
const lastPath = path.length ? path[path.length - 1] : undefined;
|
||||
const menuPath = isNestedDocument ? path.slice(0, -1) : [];
|
||||
|
||||
return (
|
||||
<Wrapper justify="flex-start" align="center">
|
||||
<CollectionName to={collectionUrl(collection.id)}>
|
||||
<CollectionIcon collection={collection} expanded />{" "}
|
||||
<span>{collection.name}</span>
|
||||
</CollectionName>
|
||||
{isNestedDocument && (
|
||||
<React.Fragment>
|
||||
<Slash /> <BreadcrumbMenu label={<Overflow />} path={menuPath} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
{lastPath && (
|
||||
<React.Fragment>
|
||||
<Slash />{" "}
|
||||
<Crumb to={lastPath.url} title={lastPath.title}>
|
||||
{lastPath.title}
|
||||
</Crumb>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Wrapper>
|
||||
);
|
||||
});
|
||||
|
||||
const Wrapper = styled(Flex)`
|
||||
display: none;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
display: flex;
|
||||
`};
|
||||
`;
|
||||
|
||||
const SmallPadlockIcon = styled(PadlockIcon)`
|
||||
display: inline-block;
|
||||
vertical-align: sub;
|
||||
`;
|
||||
|
||||
const SmallSlash = styled(GoToIcon)`
|
||||
width: 15px;
|
||||
height: 10px;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.25;
|
||||
`;
|
||||
|
||||
export const Slash = styled(GoToIcon)`
|
||||
flex-shrink: 0;
|
||||
fill: ${props => props.theme.divider};
|
||||
`;
|
||||
|
||||
const Overflow = styled(MoreIcon)`
|
||||
flex-shrink: 0;
|
||||
opacity: 0.25;
|
||||
transition: opacity 100ms ease-in-out;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
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)`
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
color: ${props => props.theme.text};
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export default inject("collections")(Breadcrumb);
|
||||
25
app/components/BreadcrumbMenu.js
Normal file
25
app/components/BreadcrumbMenu.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu";
|
||||
|
||||
type Props = {
|
||||
label: React.Node,
|
||||
path: Array<any>,
|
||||
};
|
||||
|
||||
export default class BreadcrumbMenu extends React.Component<Props> {
|
||||
render() {
|
||||
const { path } = this.props;
|
||||
|
||||
return (
|
||||
<DropdownMenu label={this.props.label} position="center">
|
||||
{path.map(item => (
|
||||
<DropdownMenuItem as={Link} to={item.url} key={item.id}>
|
||||
{item.title}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ import { darken, lighten } from "polished";
|
||||
import { ExpandedIcon } from "outline-icons";
|
||||
|
||||
const RealButton = styled.button`
|
||||
display: inline-block;
|
||||
display: ${props => (props.fullwidth ? "block" : "inline-block")};
|
||||
width: ${props => (props.fullwidth ? "100%" : "auto")};
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
@@ -126,6 +127,7 @@ export type Props = {
|
||||
children?: React.Node,
|
||||
innerRef?: React.ElementRef<any>,
|
||||
disclosure?: boolean,
|
||||
fullwidth?: boolean,
|
||||
borderOnHover?: boolean,
|
||||
};
|
||||
|
||||
|
||||
13
app/components/ButtonLarge.js
Normal file
13
app/components/ButtonLarge.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
import Button, { Inner } from "./Button";
|
||||
|
||||
const ButtonLarge = styled(Button)`
|
||||
height: 40px;
|
||||
|
||||
${Inner} {
|
||||
padding: 4px 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default ButtonLarge;
|
||||
@@ -11,7 +11,7 @@ import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore";
|
||||
import DocumentsStore from "stores/DocumentsStore";
|
||||
import RevisionsStore from "stores/RevisionsStore";
|
||||
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import { ListPlaceholder } from "components/LoadingPlaceholder";
|
||||
import Revision from "./components/Revision";
|
||||
import { documentHistoryUrl } from "utils/routeHelpers";
|
||||
|
||||
@@ -5,8 +5,8 @@ import styled, { withTheme } from "styled-components";
|
||||
import format from "date-fns/format";
|
||||
import { MoreIcon } from "outline-icons";
|
||||
|
||||
import Flex from "shared/components/Flex";
|
||||
import Time from "shared/components/Time";
|
||||
import Flex from "components/Flex";
|
||||
import Time from "components/Time";
|
||||
import Avatar from "components/Avatar";
|
||||
import RevisionMenu from "menus/RevisionMenu";
|
||||
import Document from "models/Document";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { StarredIcon } from "outline-icons";
|
||||
import styled, { withTheme } from "styled-components";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import Badge from "components/Badge";
|
||||
import Tooltip from "components/Tooltip";
|
||||
import Highlight from "components/Highlight";
|
||||
|
||||
@@ -7,7 +7,7 @@ import { PortalWithState } from "react-portal";
|
||||
import { MoreIcon } from "outline-icons";
|
||||
import { rgba } from "polished";
|
||||
import styled from "styled-components";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import { fadeAndScaleIn } from "shared/styles/animations";
|
||||
import NudeButton from "components/NudeButton";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import * as React from "react";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import styled, { withTheme } from "styled-components";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import Avatar from "components/Avatar";
|
||||
import User from "models/User";
|
||||
|
||||
|
||||
45
app/components/Flex.js
Normal file
45
app/components/Flex.js
Normal file
@@ -0,0 +1,45 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
type JustifyValues =
|
||||
| "center"
|
||||
| "space-around"
|
||||
| "space-between"
|
||||
| "flex-start"
|
||||
| "flex-end";
|
||||
|
||||
type AlignValues =
|
||||
| "stretch"
|
||||
| "center"
|
||||
| "baseline"
|
||||
| "flex-start"
|
||||
| "flex-end";
|
||||
|
||||
type Props = {
|
||||
column?: ?boolean,
|
||||
shrink?: ?boolean,
|
||||
align?: AlignValues,
|
||||
justify?: JustifyValues,
|
||||
auto?: ?boolean,
|
||||
className?: string,
|
||||
children?: React.Node,
|
||||
};
|
||||
|
||||
const Flex = (props: Props) => {
|
||||
const { children, ...restProps } = props;
|
||||
return <Container {...restProps}>{children}</Container>;
|
||||
};
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex: ${({ auto }) => (auto ? "1 1 auto" : "initial")};
|
||||
flex-direction: ${({ column }) => (column ? "column" : "row")};
|
||||
align-items: ${({ align }) => align};
|
||||
justify-content: ${({ justify }) => justify};
|
||||
flex-shrink: ${({ shrink }) => (shrink ? 1 : "initial")};
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
export default Flex;
|
||||
30
app/components/GithubLogo.js
Normal file
30
app/components/GithubLogo.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
size?: number,
|
||||
fill?: string,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
function GithubLogo({ size = 34, fill = "#FFF", className }: Props) {
|
||||
return (
|
||||
<svg
|
||||
fill={fill}
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 36 36"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
fill="#191717"
|
||||
d="M18,1.4C9,1.4,1.7,8.7,1.7,17.7c0,7.2,4.7,13.3,11.1,15.5 c0.8,0.1,1.1-0.4,1.1-0.8c0-0.4,0-1.4,0-2.8c-4.5,1-5.5-2.2-5.5-2.2c-0.7-1.9-1.8-2.4-1.8-2.4c-1.5-1,0.1-1,0.1-1 c1.6,0.1,2.5,1.7,2.5,1.7c1.5,2.5,3.8,1.8,4.7,1.4c0.1-1.1,0.6-1.8,1-2.2c-3.6-0.4-7.4-1.8-7.4-8.1c0-1.8,0.6-3.2,1.7-4.4 c-0.2-0.4-0.7-2.1,0.2-4.3c0,0,1.4-0.4,4.5,1.7c1.3-0.4,2.7-0.5,4.1-0.5c1.4,0,2.8,0.2,4.1,0.5c3.1-2.1,4.5-1.7,4.5-1.7 c0.9,2.2,0.3,3.9,0.2,4.3c1,1.1,1.7,2.6,1.7,4.4c0,6.3-3.8,7.6-7.4,8c0.6,0.5,1.1,1.5,1.1,3c0,2.2,0,3.9,0,4.5 c0,0.4,0.3,0.9,1.1,0.8c6.5-2.2,11.1-8.3,11.1-15.5C34.3,8.7,27,1.4,18,1.4z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default GithubLogo;
|
||||
27
app/components/GoogleLogo.js
Normal file
27
app/components/GoogleLogo.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
size?: number,
|
||||
fill?: string,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
function GoogleLogo({ size = 34, fill = "#FFF", className }: Props) {
|
||||
return (
|
||||
<svg
|
||||
fill={fill}
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 34 34"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g>
|
||||
<path d="M32.6162791,13.9090909 L16.8837209,13.9090909 L16.8837209,20.4772727 L25.9395349,20.4772727 C25.0953488,24.65 21.5651163,27.0454545 16.8837209,27.0454545 C11.3581395,27.0454545 6.90697674,22.5636364 6.90697674,17 C6.90697674,11.4363636 11.3581395,6.95454545 16.8837209,6.95454545 C19.2627907,6.95454545 21.4116279,7.80454545 23.1,9.19545455 L28.0116279,4.25 C25.0186047,1.62272727 21.1813953,0 16.8837209,0 C7.52093023,0 0,7.57272727 0,17 C0,26.4272727 7.52093023,34 16.8837209,34 C25.3255814,34 33,27.8181818 33,17 C33,15.9954545 32.8465116,14.9136364 32.6162791,13.9090909 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default GoogleLogo;
|
||||
@@ -5,7 +5,7 @@ import { observable } from "mobx";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { MAX_AVATAR_DISPLAY } from "shared/constants";
|
||||
import Modal from "components/Modal";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import Facepile from "components/Facepile";
|
||||
import GroupMembers from "scenes/GroupMembers";
|
||||
import ListItem from "components/List/Item";
|
||||
|
||||
@@ -27,7 +27,7 @@ import styled from "styled-components";
|
||||
import { LabelText } from "components/Input";
|
||||
import { DropdownMenu } from "components/DropdownMenu";
|
||||
import NudeButton from "components/NudeButton";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
export const icons = {
|
||||
collection: {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
|
||||
import { observable } from "mobx";
|
||||
import styled from "styled-components";
|
||||
import VisuallyHidden from "components/VisuallyHidden";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
const RealTextarea = styled.textarea`
|
||||
border: 0;
|
||||
|
||||
13
app/components/InputLarge.js
Normal file
13
app/components/InputLarge.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
import Input from "./Input";
|
||||
|
||||
const InputLarge = styled(Input)`
|
||||
height: 40px;
|
||||
|
||||
input {
|
||||
height: 40px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default InputLarge;
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { observable } from "mobx";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import keydown from "react-keydown";
|
||||
import Analytics from "components/Analytics";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import {
|
||||
homeUrl,
|
||||
searchUrl,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
type Props = {
|
||||
image?: React.Node,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { times } from "lodash";
|
||||
import styled from "styled-components";
|
||||
import Mask from "components/Mask";
|
||||
import Fade from "components/Fade";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
type Props = {
|
||||
count?: number,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { times } from "lodash";
|
||||
import styled from "styled-components";
|
||||
import Mask from "components/Mask";
|
||||
import Fade from "components/Fade";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
type Props = {
|
||||
count?: number,
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Mask from "components/Mask";
|
||||
import Fade from "components/Fade";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
export default function LoadingPlaceholder(props: Object) {
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { pulsate } from "shared/styles/animations";
|
||||
import { randomInteger } from "shared/random";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
type Props = {
|
||||
header?: boolean,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { transparentize } from "polished";
|
||||
import { CloseIcon, BackIcon } from "outline-icons";
|
||||
import NudeButton from "components/NudeButton";
|
||||
import { fadeAndScaleIn } from "shared/styles/animations";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
ReactModal.setAppElement("#root");
|
||||
|
||||
|
||||
12
app/components/Notice.js
Normal file
12
app/components/Notice.js
Normal file
@@ -0,0 +1,12 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const Notice = styled.p`
|
||||
background: ${props => props.theme.sidebarBackground};
|
||||
color: ${props => props.theme.sidebarText};
|
||||
padding: 10px 12px;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export default Notice;
|
||||
24
app/components/NoticeAlert.js
Normal file
24
app/components/NoticeAlert.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import Notice from "components/Notice";
|
||||
|
||||
export default function AlertNotice({ children }: { children: React.Node }) {
|
||||
return (
|
||||
<Notice muted>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ position: "relative", top: "2px" }}
|
||||
>
|
||||
<path
|
||||
d="M15.6676 11.5372L10.0155 1.14735C9.10744 -0.381434 6.89378 -0.383465 5.98447 1.14735L0.332715 11.5372C-0.595598 13.0994 0.528309 15.0776 2.34778 15.0776H13.652C15.47 15.0776 16.5959 13.101 15.6676 11.5372ZM8 13.2026C7.48319 13.2026 7.0625 12.7819 7.0625 12.2651C7.0625 11.7483 7.48319 11.3276 8 11.3276C8.51681 11.3276 8.9375 11.7483 8.9375 12.2651C8.9375 12.7819 8.51681 13.2026 8 13.2026ZM8.9375 9.45257C8.9375 9.96938 8.51681 10.3901 8 10.3901C7.48319 10.3901 7.0625 9.96938 7.0625 9.45257V4.76507C7.0625 4.24826 7.48319 3.82757 8 3.82757C8.51681 3.82757 8.9375 4.24826 8.9375 4.76507V9.45257Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{children}
|
||||
</Notice>
|
||||
);
|
||||
}
|
||||
25
app/components/OutlineLogo.js
Normal file
25
app/components/OutlineLogo.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
size?: number,
|
||||
fill?: string,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
function OutlineLogo({ size = 32, fill = "#333", className }: Props) {
|
||||
return (
|
||||
<svg
|
||||
fill={fill}
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 64 64"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path d="M32,57.6 L32,59.1606101 C32,61.3697491 30.209139,63.1606101 28,63.1606101 C27.3130526,63.1606101 26.6376816,62.9836959 26.038955,62.6469122 L2.03895504,49.1469122 C0.779447116,48.438439 -4.3614532e-15,47.1057033 -7.10542736e-15,45.6606101 L-7.10542736e-15,18.3393899 C-7.28240024e-15,16.8942967 0.779447116,15.561561 2.03895504,14.8530878 L26.038955,1.35308779 C27.9643866,0.270032565 30.4032469,0.952913469 31.4863021,2.87834498 C31.8230858,3.47707155 32,4.15244252 32,4.83938994 L32,6.4 L34.8506085,5.54481746 C36.9665799,4.91002604 39.1965137,6.11075966 39.8313051,8.22673106 C39.9431692,8.59961116 40,8.98682435 40,9.3761226 L40,11 L43.5038611,10.5620174 C45.6959408,10.2880074 47.6951015,11.8429102 47.9691115,14.0349899 C47.9896839,14.1995692 48,14.3652688 48,14.5311289 L48,49.4688711 C48,51.6780101 46.209139,53.4688711 44,53.4688711 C43.8341399,53.4688711 43.6684404,53.458555 43.5038611,53.4379826 L40,53 L40,54.6238774 C40,56.8330164 38.209139,58.6238774 36,58.6238774 C35.6107017,58.6238774 35.2234886,58.5670466 34.8506085,58.4551825 L32,57.6 Z M32,53.4238774 L36,54.6238774 L36,9.3761226 L32,10.5761226 L32,53.4238774 Z M40,15.0311289 L40,48.9688711 L44,49.4688711 L44,14.5311289 L40,15.0311289 Z M5.32907052e-15,44.4688711 L5.32907052e-15,19.5311289 L3.55271368e-15,44.4688711 Z M4,18.3393899 L4,45.6606101 L28,59.1606101 L28,4.83938994 L4,18.3393899 Z M8,21 L12,19 L12,45 L8,43 L8,21 Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default OutlineLogo;
|
||||
@@ -3,7 +3,7 @@ import * as React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import styled from "styled-components";
|
||||
import { GoToIcon } from "outline-icons";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
import Document from "models/Document";
|
||||
import Collection from "models/Collection";
|
||||
|
||||
@@ -3,9 +3,9 @@ import * as React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import styled from "styled-components";
|
||||
import Document from "models/Document";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Time from "shared/components/Time";
|
||||
import Breadcrumb from "shared/components/Breadcrumb";
|
||||
import Flex from "components/Flex";
|
||||
import Time from "components/Time";
|
||||
import Breadcrumb from "components/Breadcrumb";
|
||||
import CollectionsStore from "stores/CollectionsStore";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
PlusIcon,
|
||||
} from "outline-icons";
|
||||
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import Modal from "components/Modal";
|
||||
import Invite from "scenes/Invite";
|
||||
import AccountMenu from "menus/AccountMenu";
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import ZapierIcon from "./icons/Zapier";
|
||||
import SlackIcon from "./icons/Slack";
|
||||
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import Sidebar from "./Sidebar";
|
||||
import Scrollable from "components/Scrollable";
|
||||
import Section from "./components/Section";
|
||||
@@ -54,7 +54,7 @@ class SettingsSidebar extends React.Component<Props> {
|
||||
<HeaderBlock
|
||||
subheading={
|
||||
<ReturnToApp align="center">
|
||||
<BackIcon /> Return to App
|
||||
<BackIcon color="currentColor" /> Return to App
|
||||
</ReturnToApp>
|
||||
}
|
||||
teamName={team.name}
|
||||
|
||||
@@ -7,7 +7,7 @@ import breakpoint from "styled-components-breakpoint";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { CloseIcon, MenuIcon } from "outline-icons";
|
||||
import Fade from "components/Fade";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import UiStore from "stores/UiStore";
|
||||
|
||||
let firstRender = true;
|
||||
|
||||
@@ -11,7 +11,7 @@ import SidebarLink from "./SidebarLink";
|
||||
import DocumentLink from "./DocumentLink";
|
||||
import CollectionIcon from "components/CollectionIcon";
|
||||
import DropToImport from "components/DropToImport";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
type Props = {
|
||||
collection: Collection,
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as React from "react";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { withRouter, type RouterHistory } from "react-router-dom";
|
||||
import keydown from "react-keydown";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import { PlusIcon } from "outline-icons";
|
||||
import { newDocumentUrl } from "utils/routeHelpers";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import DropToImport from "components/DropToImport";
|
||||
import Fade from "components/Fade";
|
||||
import Collection from "models/Collection";
|
||||
import DocumentsStore from "stores/DocumentsStore";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import { type NavigationNode } from "types";
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @flow
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Header = styled(Flex)`
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import * as React from "react";
|
||||
import styled, { withTheme } from "styled-components";
|
||||
import { ExpandedIcon } from "outline-icons";
|
||||
import Flex from "shared/components/Flex";
|
||||
import TeamLogo from "shared/components/TeamLogo";
|
||||
import Flex from "components/Flex";
|
||||
import TeamLogo from "components/TeamLogo";
|
||||
|
||||
type Props = {
|
||||
teamName: string,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
const Section = styled(Flex)`
|
||||
position: relative;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
|
||||
import { withRouter, NavLink } from "react-router-dom";
|
||||
import { CollapsedIcon } from "outline-icons";
|
||||
import styled, { withTheme } from "styled-components";
|
||||
import Flex from "shared/components/Flex";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
type Props = {
|
||||
to?: string | Object,
|
||||
|
||||
42
app/components/SlackLogo.js
Normal file
42
app/components/SlackLogo.js
Normal file
@@ -0,0 +1,42 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
size?: number,
|
||||
fill?: string,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
function SlackLogo({ size = 34, fill = "#FFF", className }: Props) {
|
||||
return (
|
||||
<svg
|
||||
fill={fill}
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 34 34"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g stroke="none" strokeWidth="1" fillRule="evenodd">
|
||||
<g transform="translate(0.000000, 17.822581)">
|
||||
<path d="M7.23870968,3.61935484 C7.23870968,5.56612903 5.6483871,7.15645161 3.7016129,7.15645161 C1.75483871,7.15645161 0.164516129,5.56612903 0.164516129,3.61935484 C0.164516129,1.67258065 1.75483871,0.0822580645 3.7016129,0.0822580645 L7.23870968,0.0822580645 L7.23870968,3.61935484 Z" />
|
||||
<path d="M9.02096774,3.61935484 C9.02096774,1.67258065 10.6112903,0.0822580645 12.5580645,0.0822580645 C14.5048387,0.0822580645 16.0951613,1.67258065 16.0951613,3.61935484 L16.0951613,12.4758065 C16.0951613,14.4225806 14.5048387,16.0129032 12.5580645,16.0129032 C10.6112903,16.0129032 9.02096774,14.4225806 9.02096774,12.4758065 C9.02096774,12.4758065 9.02096774,3.61935484 9.02096774,3.61935484 Z" />
|
||||
</g>
|
||||
<g>
|
||||
<path d="M12.5580645,7.23870968 C10.6112903,7.23870968 9.02096774,5.6483871 9.02096774,3.7016129 C9.02096774,1.75483871 10.6112903,0.164516129 12.5580645,0.164516129 C14.5048387,0.164516129 16.0951613,1.75483871 16.0951613,3.7016129 L16.0951613,7.23870968 L12.5580645,7.23870968 Z" />
|
||||
<path d="M12.5580645,9.02096774 C14.5048387,9.02096774 16.0951613,10.6112903 16.0951613,12.5580645 C16.0951613,14.5048387 14.5048387,16.0951613 12.5580645,16.0951613 L3.7016129,16.0951613 C1.75483871,16.0951613 0.164516129,14.5048387 0.164516129,12.5580645 C0.164516129,10.6112903 1.75483871,9.02096774 3.7016129,9.02096774 C3.7016129,9.02096774 12.5580645,9.02096774 12.5580645,9.02096774 Z" />
|
||||
</g>
|
||||
<g transform="translate(17.822581, 0.000000)">
|
||||
<path d="M8.93870968,12.5580645 C8.93870968,10.6112903 10.5290323,9.02096774 12.4758065,9.02096774 C14.4225806,9.02096774 16.0129032,10.6112903 16.0129032,12.5580645 C16.0129032,14.5048387 14.4225806,16.0951613 12.4758065,16.0951613 L8.93870968,16.0951613 L8.93870968,12.5580645 Z" />
|
||||
<path d="M7.15645161,12.5580645 C7.15645161,14.5048387 5.56612903,16.0951613 3.61935484,16.0951613 C1.67258065,16.0951613 0.0822580645,14.5048387 0.0822580645,12.5580645 L0.0822580645,3.7016129 C0.0822580645,1.75483871 1.67258065,0.164516129 3.61935484,0.164516129 C5.56612903,0.164516129 7.15645161,1.75483871 7.15645161,3.7016129 L7.15645161,12.5580645 Z" />
|
||||
</g>
|
||||
<g transform="translate(17.822581, 17.822581)">
|
||||
<path d="M3.61935484,8.93870968 C5.56612903,8.93870968 7.15645161,10.5290323 7.15645161,12.4758065 C7.15645161,14.4225806 5.56612903,16.0129032 3.61935484,16.0129032 C1.67258065,16.0129032 0.0822580645,14.4225806 0.0822580645,12.4758065 L0.0822580645,8.93870968 L3.61935484,8.93870968 Z" />
|
||||
<path d="M3.61935484,7.15645161 C1.67258065,7.15645161 0.0822580645,5.56612903 0.0822580645,3.61935484 C0.0822580645,1.67258065 1.67258065,0.0822580645 3.61935484,0.0822580645 L12.4758065,0.0822580645 C14.4225806,0.0822580645 16.0129032,1.67258065 16.0129032,3.61935484 C16.0129032,5.56612903 14.4225806,7.15645161 12.4758065,7.15645161 L3.61935484,7.15645161 Z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default SlackLogo;
|
||||
12
app/components/TeamLogo.js
Normal file
12
app/components/TeamLogo.js
Normal file
@@ -0,0 +1,12 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const TeamLogo = styled.img`
|
||||
width: auto;
|
||||
height: 38px;
|
||||
border-radius: 4px;
|
||||
background: ${props => props.theme.background};
|
||||
border: 1px solid ${props => props.theme.divider};
|
||||
`;
|
||||
|
||||
export default TeamLogo;
|
||||
55
app/components/Time.js
Normal file
55
app/components/Time.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import Tooltip from "components/Tooltip";
|
||||
import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
|
||||
import format from "date-fns/format";
|
||||
|
||||
let callbacks = [];
|
||||
|
||||
// This is a shared timer that fires every minute, used for
|
||||
// updating all Time components across the page all at once.
|
||||
setInterval(() => {
|
||||
callbacks.forEach(cb => cb());
|
||||
}, 1000 * 60);
|
||||
|
||||
function eachMinute(fn) {
|
||||
callbacks.push(fn);
|
||||
|
||||
return () => {
|
||||
callbacks = callbacks.filter(cb => cb !== fn);
|
||||
};
|
||||
}
|
||||
|
||||
type Props = {
|
||||
dateTime: string,
|
||||
children?: React.Node,
|
||||
};
|
||||
|
||||
class Time extends React.Component<Props> {
|
||||
removeEachMinuteCallback: () => void;
|
||||
|
||||
componentDidMount() {
|
||||
this.removeEachMinuteCallback = eachMinute(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.removeEachMinuteCallback();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Tooltip
|
||||
tooltip={format(this.props.dateTime, "MMMM Do, YYYY h:mm a")}
|
||||
placement="bottom"
|
||||
>
|
||||
<time dateTime={this.props.dateTime}>
|
||||
{this.props.children || distanceInWordsToNow(this.props.dateTime)}
|
||||
</time>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Time;
|
||||
Reference in New Issue
Block a user