feat: Show drafts in sidebar when viewing (#2820)
This commit is contained in:
@@ -1,11 +1,5 @@
|
||||
import { observer } from "mobx-react";
|
||||
import {
|
||||
ArchiveIcon,
|
||||
EditIcon,
|
||||
GoToIcon,
|
||||
ShapesIcon,
|
||||
TrashIcon,
|
||||
} from "outline-icons";
|
||||
import { ArchiveIcon, GoToIcon, ShapesIcon, TrashIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
@@ -43,15 +37,6 @@ function useCategory(document: Document): MenuInternalLink | null {
|
||||
};
|
||||
}
|
||||
|
||||
if (document.isDraft) {
|
||||
return {
|
||||
type: "route",
|
||||
icon: <EditIcon color="currentColor" />,
|
||||
title: t("Drafts"),
|
||||
to: "/drafts",
|
||||
};
|
||||
}
|
||||
|
||||
if (document.isTemplate) {
|
||||
return {
|
||||
type: "route",
|
||||
@@ -90,7 +75,7 @@ const DocumentBreadcrumb = ({ document, children, onlyText }: Props) => {
|
||||
|
||||
const path = React.useMemo(
|
||||
() => collection?.pathToDocument?.(document.id).slice(0, -1) || [],
|
||||
[collection, document.id]
|
||||
[collection, document]
|
||||
);
|
||||
|
||||
const items = React.useMemo(() => {
|
||||
|
||||
@@ -25,7 +25,7 @@ type Props = {
|
||||
document: Document;
|
||||
highlight?: string | undefined;
|
||||
context?: string | undefined;
|
||||
showNestedDocuments?: boolean;
|
||||
showParentDocuments?: boolean;
|
||||
showCollection?: boolean;
|
||||
showPublished?: boolean;
|
||||
showPin?: boolean;
|
||||
@@ -52,7 +52,7 @@ function DocumentListItem(
|
||||
|
||||
const {
|
||||
document,
|
||||
showNestedDocuments,
|
||||
showParentDocuments,
|
||||
showCollection,
|
||||
showPublished,
|
||||
showPin,
|
||||
@@ -89,7 +89,7 @@ function DocumentListItem(
|
||||
highlight={highlight}
|
||||
dir={document.dir}
|
||||
/>
|
||||
{document.isNew && document.createdBy.id !== currentUser.id && (
|
||||
{document.isBadgedNew && document.createdBy.id !== currentUser.id && (
|
||||
<Badge yellow>{t("New")}</Badge>
|
||||
)}
|
||||
{canStar && (
|
||||
@@ -122,7 +122,7 @@ function DocumentListItem(
|
||||
document={document}
|
||||
showCollection={showCollection}
|
||||
showPublished={showPublished}
|
||||
showNestedDocuments={showNestedDocuments}
|
||||
showParentDocuments={showParentDocuments}
|
||||
showLastViewed
|
||||
/>
|
||||
</Content>
|
||||
|
||||
@@ -34,7 +34,7 @@ type Props = {
|
||||
showCollection?: boolean;
|
||||
showPublished?: boolean;
|
||||
showLastViewed?: boolean;
|
||||
showNestedDocuments?: boolean;
|
||||
showParentDocuments?: boolean;
|
||||
document: Document;
|
||||
children?: React.ReactNode;
|
||||
to?: string;
|
||||
@@ -44,7 +44,7 @@ function DocumentMeta({
|
||||
showPublished,
|
||||
showCollection,
|
||||
showLastViewed,
|
||||
showNestedDocuments,
|
||||
showParentDocuments,
|
||||
document,
|
||||
children,
|
||||
to,
|
||||
@@ -152,7 +152,7 @@ function DocumentMeta({
|
||||
</strong>
|
||||
</span>
|
||||
)}
|
||||
{showNestedDocuments && nestedDocumentsCount > 0 && (
|
||||
{showParentDocuments && nestedDocumentsCount > 0 && (
|
||||
<span>
|
||||
• {nestedDocumentsCount}{" "}
|
||||
{t("nested document", {
|
||||
|
||||
@@ -47,7 +47,7 @@ export type Props = {
|
||||
readOnlyWriteCheckboxes?: boolean;
|
||||
onBlur?: () => void;
|
||||
onFocus?: () => void;
|
||||
onPublish?: (event: React.SyntheticEvent) => any;
|
||||
onPublish?: (event: React.MouseEvent) => any;
|
||||
onSave?: (arg0: {
|
||||
done?: boolean;
|
||||
autosave?: boolean;
|
||||
|
||||
@@ -9,7 +9,7 @@ type Props = {
|
||||
options?: Record<string, any>;
|
||||
heading?: React.ReactNode;
|
||||
empty?: React.ReactNode;
|
||||
showNestedDocuments?: boolean;
|
||||
showParentDocuments?: boolean;
|
||||
showCollection?: boolean;
|
||||
showPublished?: boolean;
|
||||
showPin?: boolean;
|
||||
|
||||
@@ -101,13 +101,6 @@ function MainSidebar() {
|
||||
<Bubble count={documents.totalDrafts} />
|
||||
</Drafts>
|
||||
}
|
||||
active={
|
||||
documents.active
|
||||
? !documents.active.publishedAt &&
|
||||
!documents.active.isDeleted &&
|
||||
!documents.active.isTemplate
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useDrop, useDrag } from "react-dnd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation, useHistory } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import { sortNavigationNodes } from "@shared/utils/collections";
|
||||
import Collection from "~/models/Collection";
|
||||
import Document from "~/models/Document";
|
||||
import DocumentReparent from "~/scenes/DocumentReparent";
|
||||
@@ -25,7 +26,7 @@ type Props = {
|
||||
collection: Collection;
|
||||
canUpdate: boolean;
|
||||
activeDocument: Document | null | undefined;
|
||||
prefetchDocument: (id: string) => Promise<any>;
|
||||
prefetchDocument: (id: string) => Promise<Document | void>;
|
||||
belowCollection: Collection | void;
|
||||
};
|
||||
|
||||
@@ -152,6 +153,31 @@ function CollectionLink({
|
||||
},
|
||||
});
|
||||
|
||||
const collectionDocuments = React.useMemo(() => {
|
||||
if (
|
||||
activeDocument?.isActive &&
|
||||
activeDocument?.isDraft &&
|
||||
activeDocument?.collectionId === collection.id &&
|
||||
!activeDocument?.parentDocumentId
|
||||
) {
|
||||
return sortNavigationNodes(
|
||||
[activeDocument.asNavigationNode, ...collection.documents],
|
||||
collection.sort
|
||||
);
|
||||
}
|
||||
|
||||
return collection.documents;
|
||||
}, [
|
||||
activeDocument?.isActive,
|
||||
activeDocument?.isDraft,
|
||||
activeDocument?.collectionId,
|
||||
activeDocument?.parentDocumentId,
|
||||
activeDocument?.asNavigationNode,
|
||||
collection.documents,
|
||||
collection.id,
|
||||
collection.sort,
|
||||
]);
|
||||
|
||||
const isDraggingAnyCollection =
|
||||
isDraggingAnotherCollection || isCollectionDragging;
|
||||
|
||||
@@ -229,9 +255,8 @@ function CollectionLink({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{expanded &&
|
||||
collection.documents.map((node, index) => (
|
||||
collectionDocuments.map((node, index) => (
|
||||
<DocumentLink
|
||||
key={node.id}
|
||||
node={node}
|
||||
@@ -239,6 +264,7 @@ function CollectionLink({
|
||||
activeDocument={activeDocument}
|
||||
prefetchDocument={prefetchDocument}
|
||||
canUpdate={canUpdate}
|
||||
isDraft={node.isDraft}
|
||||
depth={2}
|
||||
index={index}
|
||||
/>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useDrag, useDrop } from "react-dnd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { MAX_TITLE_LENGTH } from "@shared/constants";
|
||||
import { sortNavigationNodes } from "@shared/utils/collections";
|
||||
import Collection from "~/models/Collection";
|
||||
import Document from "~/models/Document";
|
||||
import Fade from "~/components/Fade";
|
||||
@@ -22,7 +23,8 @@ type Props = {
|
||||
canUpdate: boolean;
|
||||
collection?: Collection;
|
||||
activeDocument: Document | null | undefined;
|
||||
prefetchDocument: (documentId: string) => Promise<any>;
|
||||
prefetchDocument: (documentId: string) => Promise<Document | void>;
|
||||
isDraft?: boolean;
|
||||
depth: number;
|
||||
index: number;
|
||||
parentId?: string;
|
||||
@@ -35,6 +37,7 @@ function DocumentLink(
|
||||
collection,
|
||||
activeDocument,
|
||||
prefetchDocument,
|
||||
isDraft,
|
||||
depth,
|
||||
index,
|
||||
parentId,
|
||||
@@ -135,9 +138,10 @@ function DocumentLink(
|
||||
}),
|
||||
canDrag: () => {
|
||||
return (
|
||||
policies.abilities(node.id).move ||
|
||||
policies.abilities(node.id).archive ||
|
||||
policies.abilities(node.id).delete
|
||||
!isDraft &&
|
||||
(policies.abilities(node.id).move ||
|
||||
policies.abilities(node.id).archive ||
|
||||
policies.abilities(node.id).delete)
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -216,6 +220,33 @@ function DocumentLink(
|
||||
}),
|
||||
});
|
||||
|
||||
const nodeChildren = React.useMemo(() => {
|
||||
if (
|
||||
collection &&
|
||||
activeDocument?.isDraft &&
|
||||
activeDocument?.isActive &&
|
||||
activeDocument?.parentDocumentId === node.id
|
||||
) {
|
||||
return sortNavigationNodes(
|
||||
[activeDocument?.asNavigationNode, ...node.children],
|
||||
collection.sort
|
||||
);
|
||||
}
|
||||
|
||||
return node.children;
|
||||
}, [
|
||||
activeDocument?.isActive,
|
||||
activeDocument?.isDraft,
|
||||
activeDocument?.parentDocumentId,
|
||||
activeDocument?.asNavigationNode,
|
||||
collection,
|
||||
node,
|
||||
]);
|
||||
|
||||
const title =
|
||||
(activeDocument?.id === node.id ? activeDocument.title : node.title) ||
|
||||
t("Untitled");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Relative onDragLeave={resetHoverExpanding}>
|
||||
@@ -244,7 +275,7 @@ function DocumentLink(
|
||||
/>
|
||||
)}
|
||||
<EditableTitle
|
||||
title={node.title || t("Untitled")}
|
||||
title={title}
|
||||
onSubmit={handleTitleChange}
|
||||
canUpdate={canUpdate}
|
||||
maxLength={MAX_TITLE_LENGTH}
|
||||
@@ -259,6 +290,7 @@ function DocumentLink(
|
||||
exact={false}
|
||||
showActions={menuOpen}
|
||||
scrollIntoViewIfNeeded={!document?.isStarred}
|
||||
isDraft={isDraft}
|
||||
ref={ref}
|
||||
menu={
|
||||
document && !isMoving ? (
|
||||
@@ -279,23 +311,22 @@ function DocumentLink(
|
||||
<DropCursor isActiveDrop={isOverReorder} innerRef={dropToReorder} />
|
||||
)}
|
||||
</Relative>
|
||||
{expanded && !isDragging && (
|
||||
<>
|
||||
{node.children.map((childNode, index) => (
|
||||
<ObservedDocumentLink
|
||||
key={childNode.id}
|
||||
collection={collection}
|
||||
node={childNode}
|
||||
activeDocument={activeDocument}
|
||||
prefetchDocument={prefetchDocument}
|
||||
depth={depth + 1}
|
||||
canUpdate={canUpdate}
|
||||
index={index}
|
||||
parentId={node.id}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{expanded &&
|
||||
!isDragging &&
|
||||
nodeChildren.map((childNode, index) => (
|
||||
<ObservedDocumentLink
|
||||
key={childNode.id}
|
||||
collection={collection}
|
||||
node={childNode}
|
||||
activeDocument={activeDocument}
|
||||
prefetchDocument={prefetchDocument}
|
||||
isDraft={childNode.isDraft}
|
||||
depth={depth + 1}
|
||||
canUpdate={canUpdate}
|
||||
index={index}
|
||||
parentId={node.id}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { transparentize } from "polished";
|
||||
import * as React from "react";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import styled, { useTheme, css } from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import EventBoundary from "~/components/EventBoundary";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
@@ -25,6 +25,7 @@ type Props = Omit<NavLinkProps, "to"> & {
|
||||
showActions?: boolean;
|
||||
active?: boolean;
|
||||
isActiveDrop?: boolean;
|
||||
isDraft?: boolean;
|
||||
depth?: number;
|
||||
scrollIntoViewIfNeeded?: boolean;
|
||||
};
|
||||
@@ -42,6 +43,7 @@ function SidebarLink(
|
||||
label,
|
||||
active,
|
||||
isActiveDrop,
|
||||
isDraft,
|
||||
menu,
|
||||
showActions,
|
||||
exact,
|
||||
@@ -74,6 +76,7 @@ function SidebarLink(
|
||||
<>
|
||||
<Link
|
||||
$isActiveDrop={isActiveDrop}
|
||||
$isDraft={isDraft}
|
||||
activeStyle={isActiveDrop ? activeDropStyle : activeStyle}
|
||||
style={active ? activeStyle : style}
|
||||
onClick={onClick}
|
||||
@@ -127,7 +130,7 @@ const Actions = styled(EventBoundary)<{ showActions?: boolean }>`
|
||||
}
|
||||
`;
|
||||
|
||||
const Link = styled(NavLink)<{ $isActiveDrop?: boolean }>`
|
||||
const Link = styled(NavLink)<{ $isActiveDrop?: boolean; $isDraft?: boolean }>`
|
||||
display: flex;
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
@@ -143,6 +146,13 @@ const Link = styled(NavLink)<{ $isActiveDrop?: boolean }>`
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
${(props) =>
|
||||
props.$isDraft &&
|
||||
css`
|
||||
padding: 4px 14px;
|
||||
border: 1px dashed ${props.theme.sidebarDraftBorder};
|
||||
`}
|
||||
|
||||
svg {
|
||||
${(props) => (props.$isActiveDrop ? `fill: ${props.theme.white};` : "")}
|
||||
transition: fill 50ms;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { StarredIcon, UnstarredIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import Document from "~/models/Document";
|
||||
import NudeButton from "./NudeButton";
|
||||
|
||||
@@ -12,6 +12,7 @@ type Props = {
|
||||
|
||||
function Star({ size, document, ...rest }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const handleClick = React.useCallback(
|
||||
(ev: React.MouseEvent<HTMLButtonElement>) => {
|
||||
@@ -32,25 +33,25 @@ function Star({ size, document, ...rest }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
<NudeButton
|
||||
onClick={handleClick}
|
||||
size={size}
|
||||
aria-label={document.isStarred ? t("Unstar") : t("Star")}
|
||||
{...rest}
|
||||
>
|
||||
{document.isStarred ? (
|
||||
<AnimatedStar size={size} color="currentColor" />
|
||||
<AnimatedStar size={size} color={theme.textSecondary} />
|
||||
) : (
|
||||
<AnimatedStar size={size} color="currentColor" as={UnstarredIcon} />
|
||||
<AnimatedStar
|
||||
size={size}
|
||||
color={theme.textTertiary}
|
||||
as={UnstarredIcon}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</NudeButton>
|
||||
);
|
||||
}
|
||||
|
||||
const Button = styled(NudeButton)`
|
||||
color: ${(props) => props.theme.text};
|
||||
`;
|
||||
|
||||
export const AnimatedStar = styled(StarredIcon)`
|
||||
flex-shrink: 0;
|
||||
transition: all 100ms ease-in-out;
|
||||
|
||||
Reference in New Issue
Block a user