Merge branch 'develop' of github.com:outline/outline into develop
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { StarredIcon, PlusIcon } from "outline-icons";
|
import { PlusIcon } from "outline-icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useHistory } from "react-router-dom";
|
import { Link, useHistory } from "react-router-dom";
|
||||||
import styled, { css, withTheme } from "styled-components";
|
import styled, { css } from "styled-components";
|
||||||
import Document from "models/Document";
|
import Document from "models/Document";
|
||||||
import Badge from "components/Badge";
|
import Badge from "components/Badge";
|
||||||
import Button from "components/Button";
|
import Button from "components/Button";
|
||||||
@@ -12,6 +12,7 @@ import DocumentMeta from "components/DocumentMeta";
|
|||||||
import EventBoundary from "components/EventBoundary";
|
import EventBoundary from "components/EventBoundary";
|
||||||
import Flex from "components/Flex";
|
import Flex from "components/Flex";
|
||||||
import Highlight from "components/Highlight";
|
import Highlight from "components/Highlight";
|
||||||
|
import StarButton, { AnimatedStar } from "components/Star";
|
||||||
import Tooltip from "components/Tooltip";
|
import Tooltip from "components/Tooltip";
|
||||||
import useCurrentUser from "hooks/useCurrentUser";
|
import useCurrentUser from "hooks/useCurrentUser";
|
||||||
import DocumentMenu from "menus/DocumentMenu";
|
import DocumentMenu from "menus/DocumentMenu";
|
||||||
@@ -52,24 +53,6 @@ function DocumentListItem(props: Props) {
|
|||||||
context,
|
context,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const handleStar = React.useCallback(
|
|
||||||
(ev: SyntheticEvent<>) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
document.star();
|
|
||||||
},
|
|
||||||
[document]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleUnstar = React.useCallback(
|
|
||||||
(ev: SyntheticEvent<>) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
document.unstar();
|
|
||||||
},
|
|
||||||
[document]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleNewFromTemplate = React.useCallback(
|
const handleNewFromTemplate = React.useCallback(
|
||||||
(ev: SyntheticEvent<>) => {
|
(ev: SyntheticEvent<>) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
@@ -90,7 +73,8 @@ function DocumentListItem(props: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DocumentLink
|
<DocumentLink
|
||||||
menuOpen={menuOpen}
|
$isStarred={document.isStarred}
|
||||||
|
$menuOpen={menuOpen}
|
||||||
to={{
|
to={{
|
||||||
pathname: document.url,
|
pathname: document.url,
|
||||||
state: { title: document.titleWithDefault },
|
state: { title: document.titleWithDefault },
|
||||||
@@ -103,11 +87,7 @@ function DocumentListItem(props: Props) {
|
|||||||
)}
|
)}
|
||||||
{!document.isDraft && !document.isArchived && !document.isTemplate && (
|
{!document.isDraft && !document.isArchived && !document.isTemplate && (
|
||||||
<Actions>
|
<Actions>
|
||||||
{document.isStarred ? (
|
<StarButton document={document} />
|
||||||
<StyledStar onClick={handleUnstar} solid />
|
|
||||||
) : (
|
|
||||||
<StyledStar onClick={handleStar} />
|
|
||||||
)}
|
|
||||||
</Actions>
|
</Actions>
|
||||||
)}
|
)}
|
||||||
{document.isDraft && showDraft && (
|
{document.isDraft && showDraft && (
|
||||||
@@ -157,21 +137,6 @@ function DocumentListItem(props: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledStar = withTheme(styled(({ solid, theme, ...props }) => (
|
|
||||||
<StarredIcon color={theme.text} {...props} />
|
|
||||||
))`
|
|
||||||
flex-shrink: 0;
|
|
||||||
opacity: ${(props) => (props.solid ? "1 !important" : 0)};
|
|
||||||
transition: all 100ms ease-in-out;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const SecondaryActions = styled(Flex)`
|
const SecondaryActions = styled(Flex)`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -195,6 +160,10 @@ const DocumentLink = styled(Link)`
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${AnimatedStar} {
|
||||||
|
opacity: ${(props) => (props.$isStarred ? "1 !important" : 0)};
|
||||||
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
@@ -204,7 +173,7 @@ const DocumentLink = styled(Link)`
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
${StyledStar} {
|
${AnimatedStar} {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -214,7 +183,7 @@ const DocumentLink = styled(Link)`
|
|||||||
}
|
}
|
||||||
|
|
||||||
${(props) =>
|
${(props) =>
|
||||||
props.menuOpen &&
|
props.$menuOpen &&
|
||||||
css`
|
css`
|
||||||
background: ${(props) => props.theme.listItemHoverBackground};
|
background: ${(props) => props.theme.listItemHoverBackground};
|
||||||
|
|
||||||
@@ -222,7 +191,7 @@ const DocumentLink = styled(Link)`
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
${StyledStar} {
|
${AnimatedStar} {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import * as React from "react";
|
|||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
const Button = styled.button`
|
const Button = styled.button`
|
||||||
width: 24px;
|
width: ${(props) => props.size}px;
|
||||||
height: 24px;
|
height: ${(props) => props.size}px;
|
||||||
background: none;
|
background: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
@@ -14,6 +14,6 @@ const Button = styled.button`
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default React.forwardRef<any, typeof Button>((props, ref) => (
|
export default React.forwardRef<any, typeof Button>(
|
||||||
<Button {...props} ref={ref} />
|
({ size = 24, ...props }, ref) => <Button size={size} {...props} ref={ref} />
|
||||||
));
|
);
|
||||||
|
|||||||
59
app/components/Star.js
Normal file
59
app/components/Star.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// @flow
|
||||||
|
import { StarredIcon } from "outline-icons";
|
||||||
|
import * as React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import Document from "models/Document";
|
||||||
|
import NudeButton from "./NudeButton";
|
||||||
|
|
||||||
|
type Props = {|
|
||||||
|
document: Document,
|
||||||
|
size?: number,
|
||||||
|
|};
|
||||||
|
|
||||||
|
function Star({ size, document, ...rest }: Props) {
|
||||||
|
const handleClick = React.useCallback(
|
||||||
|
(ev: SyntheticEvent<>) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
if (document.isStarred) {
|
||||||
|
document.unstar();
|
||||||
|
} else {
|
||||||
|
document.star();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[document]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={handleClick} size={size} {...rest}>
|
||||||
|
<AnimatedStar
|
||||||
|
solid={document.isStarred}
|
||||||
|
size={size}
|
||||||
|
color="currentColor"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = styled(NudeButton)`
|
||||||
|
color: ${(props) => props.theme.text};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const AnimatedStar = styled(StarredIcon)`
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: all 100ms ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Star;
|
||||||
@@ -12,6 +12,7 @@ import DocumentMetaWithViews from "components/DocumentMetaWithViews";
|
|||||||
import Editor from "components/Editor";
|
import Editor from "components/Editor";
|
||||||
import Flex from "components/Flex";
|
import Flex from "components/Flex";
|
||||||
import HoverPreview from "components/HoverPreview";
|
import HoverPreview from "components/HoverPreview";
|
||||||
|
import Star, { AnimatedStar } from "components/Star";
|
||||||
import { isMetaKey } from "utils/keyboard";
|
import { isMetaKey } from "utils/keyboard";
|
||||||
import { documentHistoryUrl } from "utils/routeHelpers";
|
import { documentHistoryUrl } from "utils/routeHelpers";
|
||||||
|
|
||||||
@@ -98,23 +99,35 @@ class DocumentEditor extends React.Component<Props> {
|
|||||||
readOnly,
|
readOnly,
|
||||||
innerRef,
|
innerRef,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { emoji } = parseTitle(title);
|
const { emoji } = parseTitle(title);
|
||||||
const startsWithEmojiAndSpace = !!(emoji && title.startsWith(`${emoji} `));
|
const startsWithEmojiAndSpace = !!(emoji && title.startsWith(`${emoji} `));
|
||||||
|
const normalizedTitle =
|
||||||
|
!title && readOnly ? document.titleWithDefault : title;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex auto column>
|
<Flex auto column>
|
||||||
<Title
|
{readOnly ? (
|
||||||
type="text"
|
<Title
|
||||||
onChange={onChangeTitle}
|
as="div"
|
||||||
onKeyDown={this.handleTitleKeyDown}
|
$startsWithEmojiAndSpace={startsWithEmojiAndSpace}
|
||||||
placeholder={document.placeholder}
|
$isStarred={document.isStarred}
|
||||||
value={!title && readOnly ? document.titleWithDefault : title}
|
>
|
||||||
style={startsWithEmojiAndSpace ? { marginLeft: "-1.2em" } : undefined}
|
<span>{normalizedTitle}</span>{" "}
|
||||||
readOnly={readOnly}
|
{!isShare && <StarButton document={document} size={32} />}
|
||||||
disabled={readOnly}
|
</Title>
|
||||||
autoFocus={!title}
|
) : (
|
||||||
maxLength={MAX_TITLE_LENGTH}
|
<Title
|
||||||
/>
|
type="text"
|
||||||
|
onChange={onChangeTitle}
|
||||||
|
onKeyDown={this.handleTitleKeyDown}
|
||||||
|
placeholder={document.placeholder}
|
||||||
|
value={normalizedTitle}
|
||||||
|
$startsWithEmojiAndSpace={startsWithEmojiAndSpace}
|
||||||
|
autoFocus={!title}
|
||||||
|
maxLength={MAX_TITLE_LENGTH}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<DocumentMetaWithViews
|
<DocumentMetaWithViews
|
||||||
isDraft={isDraft}
|
isDraft={isDraft}
|
||||||
document={document}
|
document={document}
|
||||||
@@ -142,11 +155,17 @@ class DocumentEditor extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StarButton = styled(Star)`
|
||||||
|
position: relative;
|
||||||
|
top: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
const Title = styled(Textarea)`
|
const Title = styled(Textarea)`
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
|
margin-left: ${(props) => (props.$startsWithEmojiAndSpace ? "-1.2em" : 0)};
|
||||||
background: ${(props) => props.theme.background};
|
background: ${(props) => props.theme.background};
|
||||||
transition: ${(props) => props.theme.backgroundTransition};
|
transition: ${(props) => props.theme.backgroundTransition};
|
||||||
color: ${(props) => props.theme.text};
|
color: ${(props) => props.theme.text};
|
||||||
@@ -162,6 +181,20 @@ const Title = styled(Textarea)`
|
|||||||
color: ${(props) => props.theme.placeholder};
|
color: ${(props) => props.theme.placeholder};
|
||||||
-webkit-text-fill-color: ${(props) => props.theme.placeholder};
|
-webkit-text-fill-color: ${(props) => props.theme.placeholder};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${AnimatedStar} {
|
||||||
|
opacity: ${(props) => (props.$isStarred ? "1 !important" : 0)};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
${AnimatedStar} {
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default DocumentEditor;
|
export default DocumentEditor;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export const metaDisplay = isMac ? "⌘" : "Ctrl";
|
|||||||
|
|
||||||
export const meta = isMac ? "cmd" : "ctrl";
|
export const meta = isMac ? "cmd" : "ctrl";
|
||||||
|
|
||||||
export function isMetaKey(event: KeyboardEvent | MouseEvent) {
|
export function isMetaKey(
|
||||||
|
event: KeyboardEvent | MouseEvent | SyntheticKeyboardEvent<>
|
||||||
|
) {
|
||||||
return isMac ? event.metaKey : event.ctrlKey;
|
return isMac ? event.metaKey : event.ctrlKey;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user