fix: Add indicator of starred status when viewing a document (#1785)
* fix: Add indicator of starred status when viewing a document closes #461 * fix: Account for shared document
This commit is contained in:
@ -1,10 +1,10 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import { StarredIcon, PlusIcon } from "outline-icons";
|
||||
import { PlusIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 Badge from "components/Badge";
|
||||
import Button from "components/Button";
|
||||
@ -12,6 +12,7 @@ import DocumentMeta from "components/DocumentMeta";
|
||||
import EventBoundary from "components/EventBoundary";
|
||||
import Flex from "components/Flex";
|
||||
import Highlight from "components/Highlight";
|
||||
import StarButton, { AnimatedStar } from "components/Star";
|
||||
import Tooltip from "components/Tooltip";
|
||||
import useCurrentUser from "hooks/useCurrentUser";
|
||||
import DocumentMenu from "menus/DocumentMenu";
|
||||
@ -52,24 +53,6 @@ function DocumentListItem(props: Props) {
|
||||
context,
|
||||
} = 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(
|
||||
(ev: SyntheticEvent<>) => {
|
||||
ev.preventDefault();
|
||||
@ -90,7 +73,8 @@ function DocumentListItem(props: Props) {
|
||||
|
||||
return (
|
||||
<DocumentLink
|
||||
menuOpen={menuOpen}
|
||||
$isStarred={document.isStarred}
|
||||
$menuOpen={menuOpen}
|
||||
to={{
|
||||
pathname: document.url,
|
||||
state: { title: document.titleWithDefault },
|
||||
@ -103,11 +87,7 @@ function DocumentListItem(props: Props) {
|
||||
)}
|
||||
{!document.isDraft && !document.isArchived && !document.isTemplate && (
|
||||
<Actions>
|
||||
{document.isStarred ? (
|
||||
<StyledStar onClick={handleUnstar} solid />
|
||||
) : (
|
||||
<StyledStar onClick={handleStar} />
|
||||
)}
|
||||
<StarButton document={document} />
|
||||
</Actions>
|
||||
)}
|
||||
{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)`
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
@ -195,6 +160,10 @@ const DocumentLink = styled(Link)`
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
${AnimatedStar} {
|
||||
opacity: ${(props) => (props.$isStarred ? "1 !important" : 0)};
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
@ -204,7 +173,7 @@ const DocumentLink = styled(Link)`
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
${StyledStar} {
|
||||
${AnimatedStar} {
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
@ -214,7 +183,7 @@ const DocumentLink = styled(Link)`
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.menuOpen &&
|
||||
props.$menuOpen &&
|
||||
css`
|
||||
background: ${(props) => props.theme.listItemHoverBackground};
|
||||
|
||||
@ -222,7 +191,7 @@ const DocumentLink = styled(Link)`
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
${StyledStar} {
|
||||
${AnimatedStar} {
|
||||
opacity: 0.5;
|
||||
}
|
||||
`}
|
||||
|
@ -3,8 +3,8 @@ import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Button = styled.button`
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: ${(props) => props.size}px;
|
||||
height: ${(props) => props.size}px;
|
||||
background: none;
|
||||
border-radius: 4px;
|
||||
line-height: 0;
|
||||
@ -14,6 +14,6 @@ const Button = styled.button`
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
export default React.forwardRef<any, typeof Button>((props, ref) => (
|
||||
<Button {...props} ref={ref} />
|
||||
));
|
||||
export default React.forwardRef<any, typeof Button>(
|
||||
({ 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 Flex from "components/Flex";
|
||||
import HoverPreview from "components/HoverPreview";
|
||||
import Star, { AnimatedStar } from "components/Star";
|
||||
import { isMetaKey } from "utils/keyboard";
|
||||
import { documentHistoryUrl } from "utils/routeHelpers";
|
||||
|
||||
@ -98,23 +99,35 @@ class DocumentEditor extends React.Component<Props> {
|
||||
readOnly,
|
||||
innerRef,
|
||||
} = this.props;
|
||||
|
||||
const { emoji } = parseTitle(title);
|
||||
const startsWithEmojiAndSpace = !!(emoji && title.startsWith(`${emoji} `));
|
||||
const normalizedTitle =
|
||||
!title && readOnly ? document.titleWithDefault : title;
|
||||
|
||||
return (
|
||||
<Flex auto column>
|
||||
<Title
|
||||
type="text"
|
||||
onChange={onChangeTitle}
|
||||
onKeyDown={this.handleTitleKeyDown}
|
||||
placeholder={document.placeholder}
|
||||
value={!title && readOnly ? document.titleWithDefault : title}
|
||||
style={startsWithEmojiAndSpace ? { marginLeft: "-1.2em" } : undefined}
|
||||
readOnly={readOnly}
|
||||
disabled={readOnly}
|
||||
autoFocus={!title}
|
||||
maxLength={MAX_TITLE_LENGTH}
|
||||
/>
|
||||
{readOnly ? (
|
||||
<Title
|
||||
as="div"
|
||||
$startsWithEmojiAndSpace={startsWithEmojiAndSpace}
|
||||
$isStarred={document.isStarred}
|
||||
>
|
||||
<span>{normalizedTitle}</span>{" "}
|
||||
{!isShare && <StarButton document={document} size={32} />}
|
||||
</Title>
|
||||
) : (
|
||||
<Title
|
||||
type="text"
|
||||
onChange={onChangeTitle}
|
||||
onKeyDown={this.handleTitleKeyDown}
|
||||
placeholder={document.placeholder}
|
||||
value={normalizedTitle}
|
||||
$startsWithEmojiAndSpace={startsWithEmojiAndSpace}
|
||||
autoFocus={!title}
|
||||
maxLength={MAX_TITLE_LENGTH}
|
||||
/>
|
||||
)}
|
||||
<DocumentMetaWithViews
|
||||
isDraft={isDraft}
|
||||
document={document}
|
||||
@ -142,11 +155,17 @@ class DocumentEditor extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
const StarButton = styled(Star)`
|
||||
position: relative;
|
||||
top: 4px;
|
||||
`;
|
||||
|
||||
const Title = styled(Textarea)`
|
||||
z-index: 1;
|
||||
line-height: 1.25;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
margin-left: ${(props) => (props.$startsWithEmojiAndSpace ? "-1.2em" : 0)};
|
||||
background: ${(props) => props.theme.background};
|
||||
transition: ${(props) => props.theme.backgroundTransition};
|
||||
color: ${(props) => props.theme.text};
|
||||
@ -162,6 +181,20 @@ const Title = styled(Textarea)`
|
||||
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;
|
||||
|
@ -5,6 +5,8 @@ export const metaDisplay = isMac ? "⌘" : "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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user