* fix: add disclosure and transition * fix: keep collections expanded * fix: tune transition and collapsing conditions * fix: collectionIcon expanded props is no longer driven by expanded state * fix: sync issue * fix: managing state together * fix: remove comment * fix: simplify expanded state * fix: remove extra state * fix: remove animation and retain expanded state * fix: remove isCollectionDropped * fix: don't use ref * review suggestions * fix many functional and design issues * don't render every single document in the sidebar, just ones that the user has seen before * chore: Sidebar refinement (#3154) * stash * wip: More sidebar tweaks * Simplify draft bubble * disclosure refactor * wip wip * lint * tweak menu position * Use document emoji for starred docs where available * feat: Trigger cmd+k from sidebar (#3149) * feat: Trigger cmd+k from sidebar * Add hint when opening command bar from sidebar * fix: Clicking internal links in shared documents sometimes reroutes to Login * fix: Spacing issues on connected slack channels list * Merge * fix: Do not prefetch JS bundles on public share links * fix: Buttons show on collection empty state when user does not have permission to edit * fix: the hover area for the "collections" subheading was being obfuscated by the initial collection drop cursor * fix: top-align disclosures * fix: Disclosure color PR feedback fix: Starred no longer draggable * fix: Overflow on sidebar button * fix: Scrollbar in sidebar when command menu is open * Minor alignment issues, clarify back in settings sidebar * fix: Fade component causes SidebarButton missizing Co-authored-by: Nan Yu <thenanyu@gmail.com> Co-authored-by: Tom Moor <tom.moor@gmail.com> Co-authored-by: Nan Yu <thenanyu@gmail.com>
158 lines
4.2 KiB
TypeScript
158 lines
4.2 KiB
TypeScript
import fractionalIndex from "fractional-index";
|
|
import { observer } from "mobx-react";
|
|
import { StarredIcon } from "outline-icons";
|
|
import * as React from "react";
|
|
import { useEffect, useState } from "react";
|
|
import { useDrag, useDrop } from "react-dnd";
|
|
import styled, { useTheme } from "styled-components";
|
|
import parseTitle from "@shared/utils/parseTitle";
|
|
import Star from "~/models/Star";
|
|
import EmojiIcon from "~/components/EmojiIcon";
|
|
import Fade from "~/components/Fade";
|
|
import useBoolean from "~/hooks/useBoolean";
|
|
import useStores from "~/hooks/useStores";
|
|
import DocumentMenu from "~/menus/DocumentMenu";
|
|
import DropCursor from "./DropCursor";
|
|
import SidebarLink from "./SidebarLink";
|
|
|
|
type Props = {
|
|
star?: Star;
|
|
depth: number;
|
|
title: string;
|
|
to: string;
|
|
documentId: string;
|
|
collectionId: string;
|
|
};
|
|
|
|
function StarredLink({
|
|
depth,
|
|
to,
|
|
documentId,
|
|
title,
|
|
collectionId,
|
|
star,
|
|
}: Props) {
|
|
const theme = useTheme();
|
|
const { collections, documents } = useStores();
|
|
const collection = collections.get(collectionId);
|
|
const document = documents.get(documentId);
|
|
const [expanded, setExpanded] = useState(false);
|
|
const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean();
|
|
const childDocuments = collection
|
|
? collection.getDocumentChildren(documentId)
|
|
: [];
|
|
const hasChildDocuments = childDocuments.length > 0;
|
|
|
|
useEffect(() => {
|
|
async function load() {
|
|
if (!document) {
|
|
await documents.fetch(documentId);
|
|
}
|
|
}
|
|
|
|
load();
|
|
}, [collection, collectionId, collections, document, documentId, documents]);
|
|
|
|
const handleDisclosureClick = React.useCallback(
|
|
(ev: React.MouseEvent<HTMLButtonElement>) => {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
setExpanded((prevExpanded) => !prevExpanded);
|
|
},
|
|
[]
|
|
);
|
|
|
|
// Draggable
|
|
const [{ isDragging }, drag] = useDrag({
|
|
type: "star",
|
|
item: () => star,
|
|
collect: (monitor) => ({
|
|
isDragging: !!monitor.isDragging(),
|
|
}),
|
|
canDrag: () => {
|
|
return depth === 0;
|
|
},
|
|
});
|
|
|
|
// Drop to reorder
|
|
const [{ isOverReorder, isDraggingAny }, dropToReorder] = useDrop({
|
|
accept: "star",
|
|
drop: (item: Star) => {
|
|
const next = star?.next();
|
|
|
|
item?.save({
|
|
index: fractionalIndex(star?.index || null, next?.index || null),
|
|
});
|
|
},
|
|
collect: (monitor) => ({
|
|
isOverReorder: !!monitor.isOver(),
|
|
isDraggingAny: !!monitor.canDrop(),
|
|
}),
|
|
});
|
|
|
|
const { emoji } = parseTitle(title);
|
|
const label = emoji ? title.replace(emoji, "") : title;
|
|
|
|
return (
|
|
<>
|
|
<Draggable key={documentId} ref={drag} $isDragging={isDragging}>
|
|
<SidebarLink
|
|
depth={depth}
|
|
expanded={hasChildDocuments ? expanded : undefined}
|
|
onDisclosureClick={handleDisclosureClick}
|
|
to={`${to}?starred`}
|
|
icon={
|
|
depth === 0 ? (
|
|
emoji ? (
|
|
<EmojiIcon emoji={emoji} />
|
|
) : (
|
|
<StarredIcon color={theme.yellow} />
|
|
)
|
|
) : undefined
|
|
}
|
|
isActive={(match, location) =>
|
|
!!match && location.search === "?starred"
|
|
}
|
|
label={depth === 0 ? label : title}
|
|
exact={false}
|
|
showActions={menuOpen}
|
|
menu={
|
|
document ? (
|
|
<Fade>
|
|
<DocumentMenu
|
|
document={document}
|
|
onOpen={handleMenuOpen}
|
|
onClose={handleMenuClose}
|
|
/>
|
|
</Fade>
|
|
) : undefined
|
|
}
|
|
/>
|
|
{isDraggingAny && (
|
|
<DropCursor isActiveDrop={isOverReorder} innerRef={dropToReorder} />
|
|
)}
|
|
</Draggable>
|
|
{expanded &&
|
|
childDocuments.map((childDocument) => (
|
|
<ObserveredStarredLink
|
|
key={childDocument.id}
|
|
depth={depth === 0 ? 2 : depth + 1}
|
|
title={childDocument.title}
|
|
to={childDocument.url}
|
|
documentId={childDocument.id}
|
|
collectionId={collectionId}
|
|
/>
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
const Draggable = styled.div<{ $isDragging?: boolean }>`
|
|
position: relative;
|
|
opacity: ${(props) => (props.$isDragging ? 0.5 : 1)};
|
|
`;
|
|
|
|
const ObserveredStarredLink = observer(StarredLink);
|
|
|
|
export default ObserveredStarredLink;
|