chore: Move to Typescript (#2783)
This PR moves the entire project to Typescript. Due to the ~1000 ignores this will lead to a messy codebase for a while, but the churn is worth it – all of those ignore comments are places that were never type-safe previously. closes #1282
This commit is contained in:
@@ -1,27 +1,28 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import { ArchiveIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useDrop } from "react-dnd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import { archivePath } from "utils/routeHelpers";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import { archivePath } from "~/utils/routeHelpers";
|
||||
import SidebarLink, { DragObject } from "./SidebarLink";
|
||||
|
||||
function ArchiveLink({ documents }) {
|
||||
const { policies } = useStores();
|
||||
function ArchiveLink() {
|
||||
const { policies, documents } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const { showToast } = useToasts();
|
||||
|
||||
const [{ isDocumentDropping }, dropToArchiveDocument] = useDrop({
|
||||
accept: "document",
|
||||
drop: async (item, monitor) => {
|
||||
drop: async (item: DragObject) => {
|
||||
const document = documents.get(item.id);
|
||||
await document.archive();
|
||||
showToast(t("Document archived"), { type: "success" });
|
||||
await document?.archive();
|
||||
showToast(t("Document archived"), {
|
||||
type: "success",
|
||||
});
|
||||
},
|
||||
canDrop: (item, monitor) => policies.abilities(item.id).archive,
|
||||
canDrop: (item) => policies.abilities(item.id).archive,
|
||||
collect: (monitor) => ({
|
||||
isDocumentDropping: monitor.isOver(),
|
||||
}),
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import fractionalIndex from "fractional-index";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
@@ -6,30 +5,31 @@ import { useDrop, useDrag } from "react-dnd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation, useHistory } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import Collection from "models/Collection";
|
||||
import Document from "models/Document";
|
||||
import DocumentReparent from "scenes/DocumentReparent";
|
||||
import CollectionIcon from "components/CollectionIcon";
|
||||
import Modal from "components/Modal";
|
||||
import Collection from "~/models/Collection";
|
||||
import Document from "~/models/Document";
|
||||
import DocumentReparent from "~/scenes/DocumentReparent";
|
||||
import CollectionIcon from "~/components/CollectionIcon";
|
||||
import Modal from "~/components/Modal";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import CollectionMenu from "~/menus/CollectionMenu";
|
||||
import CollectionSortMenu from "~/menus/CollectionSortMenu";
|
||||
import { NavigationNode } from "~/types";
|
||||
import DocumentLink from "./DocumentLink";
|
||||
import DropCursor from "./DropCursor";
|
||||
import DropToImport from "./DropToImport";
|
||||
import EditableTitle from "./EditableTitle";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
import useBoolean from "hooks/useBoolean";
|
||||
import useStores from "hooks/useStores";
|
||||
import CollectionMenu from "menus/CollectionMenu";
|
||||
import CollectionSortMenu from "menus/CollectionSortMenu";
|
||||
import SidebarLink, { DragObject } from "./SidebarLink";
|
||||
|
||||
type Props = {|
|
||||
collection: Collection,
|
||||
canUpdate: boolean,
|
||||
activeDocument: ?Document,
|
||||
prefetchDocument: (id: string) => Promise<void>,
|
||||
belowCollection: Collection | void,
|
||||
isDraggingAnyCollection: boolean,
|
||||
onChangeDragging: (dragging: boolean) => void,
|
||||
|};
|
||||
type Props = {
|
||||
collection: Collection;
|
||||
canUpdate: boolean;
|
||||
activeDocument: Document | null | undefined;
|
||||
prefetchDocument: (id: string) => Promise<any>;
|
||||
belowCollection: Collection | void;
|
||||
isDraggingAnyCollection: boolean;
|
||||
onChangeDragging: (dragging: boolean) => void;
|
||||
};
|
||||
|
||||
function CollectionLink({
|
||||
collection,
|
||||
@@ -49,18 +49,21 @@ function CollectionLink({
|
||||
handlePermissionOpen,
|
||||
handlePermissionClose,
|
||||
] = useBoolean();
|
||||
const itemRef = React.useRef();
|
||||
const itemRef = React.useRef<
|
||||
NavigationNode & { depth: number; active: boolean; collectionId: string }
|
||||
>();
|
||||
|
||||
const handleTitleChange = React.useCallback(
|
||||
async (name: string) => {
|
||||
await collection.save({ name });
|
||||
await collection.save({
|
||||
name,
|
||||
});
|
||||
history.push(collection.url);
|
||||
},
|
||||
[collection, history]
|
||||
);
|
||||
|
||||
const { ui, documents, policies, collections } = useStores();
|
||||
|
||||
const [expanded, setExpanded] = React.useState(
|
||||
collection.id === ui.activeCollectionId
|
||||
);
|
||||
@@ -71,13 +74,13 @@ function CollectionLink({
|
||||
if (search === "?starred") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDraggingAnyCollection) {
|
||||
setExpanded(false);
|
||||
} else {
|
||||
setExpanded(collection.id === ui.activeCollectionId);
|
||||
}
|
||||
}, [isDraggingAnyCollection, collection.id, ui.activeCollectionId, search]);
|
||||
|
||||
const manualSort = collection.sort.field === "index";
|
||||
const can = policies.abilities(collection.id);
|
||||
const belowCollectionIndex = belowCollection ? belowCollection.index : null;
|
||||
@@ -85,7 +88,7 @@ function CollectionLink({
|
||||
// Drop to re-parent
|
||||
const [{ isOver, canDrop }, drop] = useDrop({
|
||||
accept: "document",
|
||||
drop: (item, monitor) => {
|
||||
drop: (item: DragObject, monitor) => {
|
||||
const { id, collectionId } = item;
|
||||
if (monitor.didDrop()) return;
|
||||
if (!collection) return;
|
||||
@@ -103,11 +106,13 @@ function CollectionLink({
|
||||
documents.move(id, collection.id);
|
||||
}
|
||||
},
|
||||
canDrop: (item, monitor) => {
|
||||
canDrop: () => {
|
||||
return policies.abilities(collection.id).update;
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isOver: !!monitor.isOver({ shallow: true }),
|
||||
isOver: !!monitor.isOver({
|
||||
shallow: true,
|
||||
}),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
});
|
||||
@@ -115,7 +120,7 @@ function CollectionLink({
|
||||
// Drop to reorder
|
||||
const [{ isOverReorder }, dropToReorder] = useDrop({
|
||||
accept: "document",
|
||||
drop: async (item, monitor) => {
|
||||
drop: async (item: DragObject) => {
|
||||
if (!collection) return;
|
||||
documents.move(item.id, collection.id, undefined, 0);
|
||||
},
|
||||
@@ -127,13 +132,13 @@ function CollectionLink({
|
||||
// Drop to reorder Collection
|
||||
const [{ isCollectionDropping }, dropToReorderCollection] = useDrop({
|
||||
accept: "collection",
|
||||
drop: async (item, monitor) => {
|
||||
drop: async (item: DragObject) => {
|
||||
collections.move(
|
||||
item.id,
|
||||
fractionalIndex(collection.index, belowCollectionIndex)
|
||||
);
|
||||
},
|
||||
canDrop: (item, monitor) => {
|
||||
canDrop: (item) => {
|
||||
return (
|
||||
collection.id !== item.id &&
|
||||
(!belowCollection || item.id !== belowCollection.id)
|
||||
@@ -156,17 +161,22 @@ function CollectionLink({
|
||||
collect: (monitor) => ({
|
||||
isCollectionDragging: monitor.isDragging(),
|
||||
}),
|
||||
canDrag: (monitor) => {
|
||||
canDrag: () => {
|
||||
return can.move;
|
||||
},
|
||||
end: (monitor) => {
|
||||
end: () => {
|
||||
onChangeDragging(false);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={drop} style={{ position: "relative" }}>
|
||||
<div
|
||||
ref={drop}
|
||||
style={{
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Draggable
|
||||
key={collection.id}
|
||||
ref={dragToReorderCollection}
|
||||
@@ -238,18 +248,20 @@ function CollectionLink({
|
||||
onRequestClose={handlePermissionClose}
|
||||
isOpen={permissionOpen}
|
||||
>
|
||||
<DocumentReparent
|
||||
item={itemRef.current}
|
||||
collection={collection}
|
||||
onSubmit={handlePermissionClose}
|
||||
onCancel={handlePermissionClose}
|
||||
/>
|
||||
{itemRef.current && (
|
||||
<DocumentReparent
|
||||
item={itemRef.current}
|
||||
collection={collection}
|
||||
onSubmit={handlePermissionClose}
|
||||
onCancel={handlePermissionClose}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const Draggable = styled("div")`
|
||||
const Draggable = styled("div")<{ $isDragging: boolean; $isMoving: boolean }>`
|
||||
opacity: ${(props) => (props.$isDragging || props.$isMoving ? 0.5 : 1)};
|
||||
pointer-events: ${(props) => (props.$isMoving ? "none" : "auto")};
|
||||
`;
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import fractionalIndex from "fractional-index";
|
||||
import { observer } from "mobx-react";
|
||||
import { CollapsedIcon } from "outline-icons";
|
||||
@@ -6,24 +5,25 @@ import * as React from "react";
|
||||
import { useDrop } from "react-dnd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import Fade from "components/Fade";
|
||||
import Flex from "components/Flex";
|
||||
import useStores from "../../../hooks/useStores";
|
||||
import Collection from "~/models/Collection";
|
||||
import Fade from "~/components/Fade";
|
||||
import Flex from "~/components/Flex";
|
||||
import { createCollection } from "~/actions/definitions/collections";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import CollectionLink from "./CollectionLink";
|
||||
import DropCursor from "./DropCursor";
|
||||
import PlaceholderCollections from "./PlaceholderCollections";
|
||||
import SidebarAction from "./SidebarAction";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
import { createCollection } from "actions/definitions/collections";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import SidebarLink, { DragObject } from "./SidebarLink";
|
||||
|
||||
function Collections() {
|
||||
const [isFetching, setFetching] = React.useState(false);
|
||||
const [fetchError, setFetchError] = React.useState();
|
||||
const { ui, policies, documents, collections } = useStores();
|
||||
const { policies, documents, collections } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const [expanded, setExpanded] = React.useState(true);
|
||||
const isPreloaded: boolean = !!collections.orderedData.length;
|
||||
const isPreloaded = !!collections.orderedData.length;
|
||||
const { t } = useTranslation();
|
||||
const orderedCollections = collections.orderedData;
|
||||
const [isDraggingAnyCollection, setIsDraggingAnyCollection] = React.useState(
|
||||
@@ -35,7 +35,9 @@ function Collections() {
|
||||
if (!collections.isLoaded && !isFetching && !fetchError) {
|
||||
try {
|
||||
setFetching(true);
|
||||
await collections.fetchPage({ limit: 100 });
|
||||
await collections.fetchPage({
|
||||
limit: 100,
|
||||
});
|
||||
} catch (error) {
|
||||
showToast(
|
||||
t("Collections could not be loaded, please reload the app"),
|
||||
@@ -49,18 +51,19 @@ function Collections() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
load();
|
||||
}, [collections, isFetching, showToast, fetchError, t]);
|
||||
|
||||
const [{ isCollectionDropping }, dropToReorderCollection] = useDrop({
|
||||
accept: "collection",
|
||||
drop: async (item, monitor) => {
|
||||
drop: async (item: DragObject) => {
|
||||
collections.move(
|
||||
item.id,
|
||||
fractionalIndex(null, orderedCollections[0].index)
|
||||
);
|
||||
},
|
||||
canDrop: (item, monitor) => {
|
||||
canDrop: (item) => {
|
||||
return item.id !== orderedCollections[0].id;
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
@@ -75,14 +78,13 @@ function Collections() {
|
||||
innerRef={dropToReorderCollection}
|
||||
from="collections"
|
||||
/>
|
||||
{orderedCollections.map((collection, index) => (
|
||||
{orderedCollections.map((collection: Collection, index: number) => (
|
||||
<CollectionLink
|
||||
key={collection.id}
|
||||
collection={collection}
|
||||
activeDocument={documents.active}
|
||||
prefetchDocument={documents.prefetchDocument}
|
||||
canUpdate={policies.abilities(collection.id).update}
|
||||
ui={ui}
|
||||
isDraggingAnyCollection={isDraggingAnyCollection}
|
||||
onChangeDragging={setIsDraggingAnyCollection}
|
||||
belowCollection={orderedCollections[index + 1]}
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { CollapsedIcon } from "outline-icons";
|
||||
import styled from "styled-components";
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useDrag, useDrop } from "react-dnd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { MAX_TITLE_LENGTH } from "shared/constants";
|
||||
import Collection from "models/Collection";
|
||||
import Document from "models/Document";
|
||||
import Fade from "components/Fade";
|
||||
import { MAX_TITLE_LENGTH } from "@shared/constants";
|
||||
import Collection from "~/models/Collection";
|
||||
import Document from "~/models/Document";
|
||||
import Fade from "~/components/Fade";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import DocumentMenu from "~/menus/DocumentMenu";
|
||||
import { NavigationNode } from "~/types";
|
||||
import Disclosure from "./Disclosure";
|
||||
import DropCursor from "./DropCursor";
|
||||
import DropToImport from "./DropToImport";
|
||||
import EditableTitle from "./EditableTitle";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
import useBoolean from "hooks/useBoolean";
|
||||
import useStores from "hooks/useStores";
|
||||
import DocumentMenu from "menus/DocumentMenu";
|
||||
import { type NavigationNode } from "types";
|
||||
import SidebarLink, { DragObject } from "./SidebarLink";
|
||||
|
||||
type Props = {|
|
||||
node: NavigationNode,
|
||||
canUpdate: boolean,
|
||||
collection?: Collection,
|
||||
activeDocument: ?Document,
|
||||
prefetchDocument: (documentId: string) => Promise<void>,
|
||||
depth: number,
|
||||
index: number,
|
||||
parentId?: string,
|
||||
|};
|
||||
type Props = {
|
||||
node: NavigationNode;
|
||||
canUpdate: boolean;
|
||||
collection?: Collection;
|
||||
activeDocument: Document | null | undefined;
|
||||
prefetchDocument: (documentId: string) => Promise<any>;
|
||||
depth: number;
|
||||
index: number;
|
||||
parentId?: string;
|
||||
};
|
||||
|
||||
function DocumentLink(
|
||||
{
|
||||
@@ -40,14 +39,12 @@ function DocumentLink(
|
||||
index,
|
||||
parentId,
|
||||
}: Props,
|
||||
ref
|
||||
ref: React.RefObject<HTMLAnchorElement>
|
||||
) {
|
||||
const { documents, policies } = useStores();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isActiveDocument = activeDocument && activeDocument.id === node.id;
|
||||
const hasChildDocuments = !!node.children.length;
|
||||
|
||||
const document = documents.get(node.id);
|
||||
const { fetchChildDocuments } = documents;
|
||||
|
||||
@@ -75,7 +72,6 @@ function DocumentLink(
|
||||
isActiveDocument)
|
||||
);
|
||||
}, [hasChildDocuments, activeDocument, isActiveDocument, node, collection]);
|
||||
|
||||
const [expanded, setExpanded] = React.useState(showChildren);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -93,7 +89,7 @@ function DocumentLink(
|
||||
}, [expanded, hasChildDocuments]);
|
||||
|
||||
const handleDisclosureClick = React.useCallback(
|
||||
(ev: SyntheticEvent<>) => {
|
||||
(ev: React.SyntheticEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setExpanded(!expanded);
|
||||
@@ -101,27 +97,26 @@ function DocumentLink(
|
||||
[expanded]
|
||||
);
|
||||
|
||||
const handleMouseEnter = React.useCallback(
|
||||
(ev: SyntheticEvent<>) => {
|
||||
prefetchDocument(node.id);
|
||||
},
|
||||
[prefetchDocument, node]
|
||||
);
|
||||
const handleMouseEnter = React.useCallback(() => {
|
||||
prefetchDocument(node.id);
|
||||
}, [prefetchDocument, node]);
|
||||
|
||||
const handleTitleChange = React.useCallback(
|
||||
async (title: string) => {
|
||||
if (!document) return;
|
||||
|
||||
await documents.update({
|
||||
id: document.id,
|
||||
lastRevision: document.revision,
|
||||
text: document.text,
|
||||
title,
|
||||
});
|
||||
await documents.update(
|
||||
{
|
||||
id: document.id,
|
||||
text: document.text,
|
||||
title,
|
||||
},
|
||||
{
|
||||
lastRevision: document.revision,
|
||||
}
|
||||
);
|
||||
},
|
||||
[documents, document]
|
||||
);
|
||||
|
||||
const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean();
|
||||
const isMoving = documents.movingDocumentId === node.id;
|
||||
const manualSort = collection?.sort.field === "index";
|
||||
@@ -138,7 +133,7 @@ function DocumentLink(
|
||||
collect: (monitor) => ({
|
||||
isDragging: !!monitor.isDragging(),
|
||||
}),
|
||||
canDrag: (monitor) => {
|
||||
canDrag: () => {
|
||||
return (
|
||||
policies.abilities(node.id).move ||
|
||||
policies.abilities(node.id).archive ||
|
||||
@@ -147,50 +142,56 @@ function DocumentLink(
|
||||
},
|
||||
});
|
||||
|
||||
const hoverExpanding = React.useRef(null);
|
||||
const hoverExpanding = React.useRef<ReturnType<typeof setTimeout>>();
|
||||
|
||||
// We set a timeout when the user first starts hovering over the document link,
|
||||
// to trigger expansion of children. Clear this timeout when they stop hovering.
|
||||
const resetHoverExpanding = React.useCallback(() => {
|
||||
if (hoverExpanding.current) {
|
||||
clearTimeout(hoverExpanding.current);
|
||||
hoverExpanding.current = null;
|
||||
hoverExpanding.current = undefined;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Drop to re-parent
|
||||
const [{ isOverReparent, canDropToReparent }, dropToReparent] = useDrop({
|
||||
accept: "document",
|
||||
drop: (item, monitor) => {
|
||||
drop: (item: DragObject, monitor) => {
|
||||
if (monitor.didDrop()) return;
|
||||
if (!collection) return;
|
||||
documents.move(item.id, collection.id, node.id);
|
||||
},
|
||||
|
||||
canDrop: (item, monitor) =>
|
||||
pathToNode && !pathToNode.includes(monitor.getItem().id),
|
||||
|
||||
canDrop: (_item, monitor) =>
|
||||
!!pathToNode && !pathToNode.includes(monitor.getItem<DragObject>().id),
|
||||
hover: (item, monitor) => {
|
||||
// Enables expansion of document children when hovering over the document
|
||||
// for more than half a second.
|
||||
if (
|
||||
hasChildDocuments &&
|
||||
monitor.canDrop() &&
|
||||
monitor.isOver({ shallow: true })
|
||||
monitor.isOver({
|
||||
shallow: true,
|
||||
})
|
||||
) {
|
||||
if (!hoverExpanding.current) {
|
||||
hoverExpanding.current = setTimeout(() => {
|
||||
hoverExpanding.current = null;
|
||||
if (monitor.isOver({ shallow: true })) {
|
||||
hoverExpanding.current = undefined;
|
||||
|
||||
if (
|
||||
monitor.isOver({
|
||||
shallow: true,
|
||||
})
|
||||
) {
|
||||
setExpanded(true);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
collect: (monitor) => ({
|
||||
isOverReparent: !!monitor.isOver({ shallow: true }),
|
||||
isOverReparent: !!monitor.isOver({
|
||||
shallow: true,
|
||||
}),
|
||||
canDropToReparent: monitor.canDrop(),
|
||||
}),
|
||||
});
|
||||
@@ -198,7 +199,7 @@ function DocumentLink(
|
||||
// Drop to reorder
|
||||
const [{ isOverReorder }, dropToReorder] = useDrop({
|
||||
accept: "document",
|
||||
drop: (item, monitor) => {
|
||||
drop: (item: DragObject) => {
|
||||
if (!collection) return;
|
||||
if (item.id === node.id) return;
|
||||
|
||||
@@ -229,7 +230,9 @@ function DocumentLink(
|
||||
onMouseEnter={handleMouseEnter}
|
||||
to={{
|
||||
pathname: node.url,
|
||||
state: { title: node.title },
|
||||
state: {
|
||||
title: node.title,
|
||||
},
|
||||
}}
|
||||
label={
|
||||
<>
|
||||
@@ -247,6 +250,7 @@ function DocumentLink(
|
||||
/>
|
||||
</>
|
||||
}
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'match' implicitly has an 'any' type.
|
||||
isActive={(match, location) =>
|
||||
match && location.search !== "?starred"
|
||||
}
|
||||
@@ -300,7 +304,7 @@ const Relative = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const Draggable = styled.div`
|
||||
const Draggable = styled.div<{ $isDragging?: boolean; $isMoving?: boolean }>`
|
||||
opacity: ${(props) => (props.$isDragging || props.$isMoving ? 0.5 : 1)};
|
||||
pointer-events: ${(props) => (props.$isMoving ? "none" : "all")};
|
||||
`;
|
||||
@@ -1,24 +1,20 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled, { withTheme } from "styled-components";
|
||||
import { type Theme } from "types";
|
||||
import styled from "styled-components";
|
||||
|
||||
function DropCursor({
|
||||
isActiveDrop,
|
||||
innerRef,
|
||||
theme,
|
||||
from,
|
||||
}: {
|
||||
isActiveDrop: boolean,
|
||||
innerRef: React.Ref<any>,
|
||||
theme: Theme,
|
||||
from: string,
|
||||
isActiveDrop: boolean;
|
||||
innerRef: React.Ref<HTMLDivElement>;
|
||||
from?: string;
|
||||
}) {
|
||||
return <Cursor isOver={isActiveDrop} ref={innerRef} from={from} />;
|
||||
}
|
||||
|
||||
// transparent hover zone with a thin visible band vertically centered
|
||||
const Cursor = styled("div")`
|
||||
const Cursor = styled.div<{ isOver?: boolean; from?: string }>`
|
||||
opacity: ${(props) => (props.isOver ? 1 : 0)};
|
||||
transition: opacity 150ms;
|
||||
|
||||
@@ -41,4 +37,4 @@ const Cursor = styled("div")`
|
||||
}
|
||||
`;
|
||||
|
||||
export default withTheme(DropCursor);
|
||||
export default DropCursor;
|
||||
@@ -1,20 +1,21 @@
|
||||
// @flow
|
||||
import invariant from "invariant";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import Dropzone from "react-dropzone";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled, { css } from "styled-components";
|
||||
import LoadingIndicator from "components/LoadingIndicator";
|
||||
import useImportDocument from "hooks/useImportDocument";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import LoadingIndicator from "~/components/LoadingIndicator";
|
||||
import useImportDocument from "~/hooks/useImportDocument";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
children: React.Node,
|
||||
collectionId: string,
|
||||
documentId?: string,
|
||||
disabled: boolean,
|
||||
|};
|
||||
type Props = {
|
||||
children: JSX.Element;
|
||||
collectionId?: string;
|
||||
documentId?: string;
|
||||
disabled?: boolean;
|
||||
activeClassName?: string;
|
||||
};
|
||||
|
||||
function DropToImport({ disabled, children, collectionId, documentId }: Props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -24,13 +25,16 @@ function DropToImport({ disabled, children, collectionId, documentId }: Props) {
|
||||
collectionId,
|
||||
documentId
|
||||
);
|
||||
const targetId = collectionId || documentId;
|
||||
invariant(targetId, "Must provide either collectionId or documentId");
|
||||
|
||||
const can = policies.abilities(collectionId);
|
||||
|
||||
const can = policies.abilities(targetId);
|
||||
const handleRejection = React.useCallback(() => {
|
||||
showToast(
|
||||
t("Document not supported – try Markdown, Plain text, HTML, or Word"),
|
||||
{ type: "error" }
|
||||
{
|
||||
type: "error",
|
||||
}
|
||||
);
|
||||
}, [t, showToast]);
|
||||
|
||||
@@ -46,17 +50,11 @@ function DropToImport({ disabled, children, collectionId, documentId }: Props) {
|
||||
noClick
|
||||
multiple
|
||||
>
|
||||
{({
|
||||
getRootProps,
|
||||
getInputProps,
|
||||
isDragActive,
|
||||
isDragAccept,
|
||||
isDragReject,
|
||||
}) => (
|
||||
{({ getRootProps, getInputProps, isDragActive }) => (
|
||||
<DropzoneContainer
|
||||
{...getRootProps()}
|
||||
$isDragActive={isDragActive}
|
||||
tabIndex="-1"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
{isImporting && <LoadingIndicator />}
|
||||
@@ -67,7 +65,7 @@ function DropToImport({ disabled, children, collectionId, documentId }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const DropzoneContainer = styled.div`
|
||||
const DropzoneContainer = styled.div<{ $isDragActive: boolean }>`
|
||||
border-radius: 4px;
|
||||
|
||||
${({ $isDragActive, theme }) =>
|
||||
@@ -1,14 +1,13 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
onSubmit: (title: string) => Promise<void>,
|
||||
title: string,
|
||||
canUpdate: boolean,
|
||||
maxLength?: number,
|
||||
|};
|
||||
type Props = {
|
||||
onSubmit: (title: string) => Promise<void>;
|
||||
title: string;
|
||||
canUpdate: boolean;
|
||||
maxLength?: number;
|
||||
};
|
||||
|
||||
function EditableTitle({ title, onSubmit, canUpdate, ...rest }: Props) {
|
||||
const [isEditing, setIsEditing] = React.useState(false);
|
||||
@@ -43,10 +42,9 @@ function EditableTitle({ title, onSubmit, canUpdate, ...rest }: Props) {
|
||||
const handleSave = React.useCallback(
|
||||
async (ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
setIsEditing(false);
|
||||
|
||||
const trimmedValue = value.trim();
|
||||
|
||||
if (trimmedValue === originalValue || trimmedValue.length === 0) {
|
||||
setValue(originalValue);
|
||||
return;
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
import Flex from "components/Flex";
|
||||
import Flex from "~/components/Flex";
|
||||
|
||||
const Header = styled(Flex)`
|
||||
font-size: 11px;
|
||||
@@ -1,44 +1,43 @@
|
||||
// @flow
|
||||
// ref: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/NavLink.js
|
||||
|
||||
// This file is pulled almost 100% from react-router with the addition of one
|
||||
// thing, automatic scroll to the active link. It's worth the copy paste because
|
||||
// it avoids recalculating the link match again.
|
||||
import { createLocation } from "history";
|
||||
import { Location, createLocation } from "history";
|
||||
import * as React from "react";
|
||||
import {
|
||||
__RouterContext as RouterContext,
|
||||
matchPath,
|
||||
type Location,
|
||||
} from "react-router";
|
||||
import { __RouterContext as RouterContext, matchPath } from "react-router";
|
||||
import { Link } from "react-router-dom";
|
||||
import scrollIntoView from "smooth-scroll-into-view-if-needed";
|
||||
|
||||
const resolveToLocation = (to, currentLocation) =>
|
||||
typeof to === "function" ? to(currentLocation) : to;
|
||||
const resolveToLocation = (
|
||||
to: string | Record<string, any>,
|
||||
currentLocation: Location
|
||||
) => (typeof to === "function" ? to(currentLocation) : to);
|
||||
|
||||
const normalizeToLocation = (to, currentLocation) => {
|
||||
const normalizeToLocation = (
|
||||
to: string | Record<string, any>,
|
||||
currentLocation: Location
|
||||
) => {
|
||||
return typeof to === "string"
|
||||
? createLocation(to, null, null, currentLocation)
|
||||
? createLocation(to, null, undefined, currentLocation)
|
||||
: to;
|
||||
};
|
||||
|
||||
const joinClassnames = (...classnames) => {
|
||||
const joinClassnames = (...classnames: (string | undefined)[]) => {
|
||||
return classnames.filter((i) => i).join(" ");
|
||||
};
|
||||
|
||||
export type Props = {|
|
||||
activeClassName?: String,
|
||||
activeStyle?: Object,
|
||||
className?: string,
|
||||
scrollIntoViewIfNeeded?: boolean,
|
||||
exact?: boolean,
|
||||
isActive?: any,
|
||||
location?: Location,
|
||||
strict?: boolean,
|
||||
style?: Object,
|
||||
to: string,
|
||||
|};
|
||||
export type Props = React.HTMLAttributes<HTMLAnchorElement> & {
|
||||
activeClassName?: string;
|
||||
activeStyle?: React.CSSProperties;
|
||||
className?: string;
|
||||
scrollIntoViewIfNeeded?: boolean;
|
||||
exact?: boolean;
|
||||
isActive?: any;
|
||||
location?: Location;
|
||||
strict?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
to: string | Record<string, any>;
|
||||
};
|
||||
|
||||
/**
|
||||
* A <Link> wrapper that knows if it's "active" or not.
|
||||
@@ -57,7 +56,7 @@ const NavLink = ({
|
||||
to,
|
||||
...rest
|
||||
}: Props) => {
|
||||
const linkRef = React.useRef();
|
||||
const linkRef = React.useRef(null);
|
||||
const context = React.useContext(RouterContext);
|
||||
const currentLocation = locationProp || context.location;
|
||||
const toLocation = normalizeToLocation(
|
||||
@@ -65,9 +64,9 @@ const NavLink = ({
|
||||
currentLocation
|
||||
);
|
||||
const { pathname: path } = toLocation;
|
||||
|
||||
// Regex taken from: https://github.com/pillarjs/path-to-regexp/blob/master/index.js#L202
|
||||
const escapedPath = path && path.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
|
||||
|
||||
const match = escapedPath
|
||||
? matchPath(currentLocation.pathname, {
|
||||
path: escapedPath,
|
||||
@@ -78,7 +77,6 @@ const NavLink = ({
|
||||
const isActive = !!(isActiveProp
|
||||
? isActiveProp(match, currentLocation)
|
||||
: match);
|
||||
|
||||
const className = isActive
|
||||
? joinClassnames(classNameProp, activeClassName)
|
||||
: classNameProp;
|
||||
@@ -88,13 +86,13 @@ const NavLink = ({
|
||||
if (isActive && linkRef.current && scrollIntoViewIfNeeded !== false) {
|
||||
scrollIntoView(linkRef.current, {
|
||||
scrollMode: "if-needed",
|
||||
behavior: "instant",
|
||||
behavior: "auto",
|
||||
});
|
||||
}
|
||||
}, [linkRef, scrollIntoViewIfNeeded, isActive]);
|
||||
|
||||
const props = {
|
||||
"aria-current": (isActive && ariaCurrent) || null,
|
||||
"aria-current": (isActive && ariaCurrent) || undefined,
|
||||
className,
|
||||
style,
|
||||
to: toLocation,
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import PlaceholderText from "components/PlaceholderText";
|
||||
import PlaceholderText from "~/components/PlaceholderText";
|
||||
|
||||
function PlaceholderCollections() {
|
||||
return (
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const ResizeBorder = styled.div`
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
import Flex from "components/Flex";
|
||||
import Flex from "~/components/Flex";
|
||||
|
||||
const Section = styled(Flex)`
|
||||
position: relative;
|
||||
@@ -1,23 +1,22 @@
|
||||
// @flow
|
||||
import invariant from "invariant";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation } from "react-router";
|
||||
import { actionToMenuItem } from "~/actions";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { Action } from "~/types";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
import { actionToMenuItem } from "actions";
|
||||
import useStores from "hooks/useStores";
|
||||
import type { Action } from "types";
|
||||
|
||||
type Props = {|
|
||||
action: Action,
|
||||
|};
|
||||
type Props = {
|
||||
action: Action;
|
||||
depth?: number;
|
||||
};
|
||||
|
||||
function SidebarAction({ action, ...rest }: Props) {
|
||||
const stores = useStores();
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
|
||||
const context = {
|
||||
isContextMenu: false,
|
||||
isCommandBar: false,
|
||||
@@ -27,9 +26,8 @@ function SidebarAction({ action, ...rest }: Props) {
|
||||
stores,
|
||||
t,
|
||||
};
|
||||
|
||||
const menuItem = actionToMenuItem(action, context);
|
||||
invariant(menuItem.onClick, "passed action must have perform");
|
||||
invariant(menuItem.type === "button", "passed action must be a button");
|
||||
|
||||
return (
|
||||
<SidebarLink
|
||||
@@ -1,28 +1,32 @@
|
||||
// @flow
|
||||
import { transparentize } from "polished";
|
||||
import * as React from "react";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import EventBoundary from "components/EventBoundary";
|
||||
import NavLink, { type Props as NavLinkProps } from "./NavLink";
|
||||
import EventBoundary from "~/components/EventBoundary";
|
||||
import { NavigationNode } from "~/types";
|
||||
import NavLink, { Props as NavLinkProps } from "./NavLink";
|
||||
|
||||
type Props = {|
|
||||
...NavLinkProps,
|
||||
to?: string | Object,
|
||||
href?: string | Object,
|
||||
innerRef?: (?HTMLElement) => void,
|
||||
onClick?: (SyntheticEvent<>) => mixed,
|
||||
onMouseEnter?: (SyntheticEvent<>) => void,
|
||||
children?: React.Node,
|
||||
icon?: React.Node,
|
||||
label?: React.Node,
|
||||
menu?: React.Node,
|
||||
showActions?: boolean,
|
||||
active?: boolean,
|
||||
isActiveDrop?: boolean,
|
||||
depth?: number,
|
||||
scrollIntoViewIfNeeded?: boolean,
|
||||
|};
|
||||
export type DragObject = NavigationNode & {
|
||||
depth: number;
|
||||
active: boolean;
|
||||
collectionId: string;
|
||||
};
|
||||
|
||||
type Props = Omit<NavLinkProps, "to"> & {
|
||||
to?: string | Record<string, any>;
|
||||
href?: string | Record<string, any>;
|
||||
innerRef?: (arg0: HTMLElement | null | undefined) => void;
|
||||
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
|
||||
onMouseEnter?: React.MouseEventHandler<HTMLAnchorElement>;
|
||||
icon?: React.ReactNode;
|
||||
label?: React.ReactNode;
|
||||
menu?: React.ReactNode;
|
||||
showActions?: boolean;
|
||||
active?: boolean;
|
||||
isActiveDrop?: boolean;
|
||||
depth?: number;
|
||||
scrollIntoViewIfNeeded?: boolean;
|
||||
};
|
||||
|
||||
const activeDropStyle = {
|
||||
fontWeight: 600,
|
||||
@@ -31,7 +35,6 @@ const activeDropStyle = {
|
||||
function SidebarLink(
|
||||
{
|
||||
icon,
|
||||
children,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
to,
|
||||
@@ -44,10 +47,9 @@ function SidebarLink(
|
||||
href,
|
||||
depth,
|
||||
className,
|
||||
scrollIntoViewIfNeeded,
|
||||
...rest
|
||||
}: Props,
|
||||
ref
|
||||
ref: React.RefObject<HTMLAnchorElement>
|
||||
) {
|
||||
const theme = useTheme();
|
||||
const style = React.useMemo(
|
||||
@@ -71,11 +73,11 @@ function SidebarLink(
|
||||
<>
|
||||
<Link
|
||||
$isActiveDrop={isActiveDrop}
|
||||
scrollIntoViewIfNeeded={scrollIntoViewIfNeeded}
|
||||
activeStyle={isActiveDrop ? activeDropStyle : activeStyle}
|
||||
style={active ? activeStyle : style}
|
||||
onClick={onClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
// @ts-expect-error exact does not exist on div
|
||||
exact={exact !== false}
|
||||
to={to}
|
||||
as={to ? undefined : href ? "a" : "div"}
|
||||
@@ -101,7 +103,7 @@ const IconWrapper = styled.span`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const Actions = styled(EventBoundary)`
|
||||
const Actions = styled(EventBoundary)<{ showActions?: boolean }>`
|
||||
display: ${(props) => (props.showActions ? "inline-flex" : "none")};
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
@@ -124,7 +126,7 @@ const Actions = styled(EventBoundary)`
|
||||
}
|
||||
`;
|
||||
|
||||
const Link = styled(NavLink)`
|
||||
const Link = styled(NavLink)<{ $isActiveDrop?: boolean }>`
|
||||
display: flex;
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
@@ -182,4 +184,4 @@ const Label = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.forwardRef<Props, HTMLAnchorElement>(SidebarLink);
|
||||
export default React.forwardRef<HTMLAnchorElement, Props>(SidebarLink);
|
||||
@@ -1,17 +1,16 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import { CollapsedIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import Flex from "components/Flex";
|
||||
import Flex from "~/components/Flex";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import PlaceholderCollections from "./PlaceholderCollections";
|
||||
import Section from "./Section";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
import StarredLink from "./StarredLink";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
const STARRED_PAGINATION_LIMIT = 10;
|
||||
const STARRED = "STARRED";
|
||||
@@ -63,6 +62,7 @@ function Starred() {
|
||||
|
||||
useEffect(() => {
|
||||
setOffset(starred.length);
|
||||
|
||||
if (starred.length <= STARRED_PAGINATION_LIMIT) {
|
||||
setShow("Nothing");
|
||||
} else if (starred.length >= upperBound) {
|
||||
@@ -78,17 +78,14 @@ function Starred() {
|
||||
}
|
||||
}, [fetchResults, offset]);
|
||||
|
||||
const handleShowMore = React.useCallback(
|
||||
async (ev) => {
|
||||
setUpperBound(
|
||||
(previousUpperBound) => previousUpperBound + STARRED_PAGINATION_LIMIT
|
||||
);
|
||||
await fetchResults();
|
||||
},
|
||||
[fetchResults]
|
||||
);
|
||||
const handleShowMore = React.useCallback(async () => {
|
||||
setUpperBound(
|
||||
(previousUpperBound) => previousUpperBound + STARRED_PAGINATION_LIMIT
|
||||
);
|
||||
await fetchResults();
|
||||
}, [fetchResults]);
|
||||
|
||||
const handleShowLess = React.useCallback((ev) => {
|
||||
const handleShowLess = React.useCallback(() => {
|
||||
setUpperBound(STARRED_PAGINATION_LIMIT);
|
||||
setShow("More");
|
||||
}, []);
|
||||
@@ -103,12 +100,13 @@ function Starred() {
|
||||
} catch (_) {
|
||||
// no-op Safari private mode
|
||||
}
|
||||
|
||||
setExpanded((prev) => !prev);
|
||||
},
|
||||
[expanded]
|
||||
);
|
||||
|
||||
const content = starred.slice(0, upperBound).map((document, index) => {
|
||||
const content = starred.slice(0, upperBound).map((document) => {
|
||||
return (
|
||||
<StarredLink
|
||||
key={document.id}
|
||||
@@ -116,7 +114,6 @@ function Starred() {
|
||||
collectionId={document.collectionId}
|
||||
to={document.url}
|
||||
title={document.title}
|
||||
url={document.url}
|
||||
depth={2}
|
||||
/>
|
||||
);
|
||||
@@ -151,7 +148,7 @@ function Starred() {
|
||||
depth={2}
|
||||
/>
|
||||
)}
|
||||
{(isFetching || fetchError) && (
|
||||
{(isFetching || fetchError) && !starred.length && (
|
||||
<Flex column>
|
||||
<PlaceholderCollections />
|
||||
</Flex>
|
||||
@@ -1,25 +1,24 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { MAX_TITLE_LENGTH } from "shared/constants";
|
||||
import Fade from "components/Fade";
|
||||
import useStores from "../../../hooks/useStores";
|
||||
import { MAX_TITLE_LENGTH } from "@shared/constants";
|
||||
import Fade from "~/components/Fade";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import DocumentMenu from "~/menus/DocumentMenu";
|
||||
import Disclosure from "./Disclosure";
|
||||
import EditableTitle from "./EditableTitle";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
import useBoolean from "hooks/useBoolean";
|
||||
import DocumentMenu from "menus/DocumentMenu";
|
||||
|
||||
type Props = {|
|
||||
depth: number,
|
||||
title: string,
|
||||
to: string,
|
||||
documentId: string,
|
||||
collectionId: string,
|
||||
|};
|
||||
type Props = {
|
||||
depth: number;
|
||||
title: string;
|
||||
to: string;
|
||||
documentId: string;
|
||||
collectionId: string;
|
||||
};
|
||||
|
||||
function StarredLink({ depth, title, to, documentId, collectionId }: Props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -29,11 +28,9 @@ function StarredLink({ depth, title, to, documentId, collectionId }: Props) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean();
|
||||
const canUpdate = policies.abilities(documentId).update;
|
||||
|
||||
const childDocuments = collection
|
||||
? collection.getDocumentChildren(documentId)
|
||||
: [];
|
||||
|
||||
const hasChildDocuments = childDocuments.length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -42,25 +39,32 @@ function StarredLink({ depth, title, to, documentId, collectionId }: Props) {
|
||||
await documents.fetch(documentId);
|
||||
}
|
||||
}
|
||||
|
||||
load();
|
||||
}, [collection, collectionId, collections, document, documentId, documents]);
|
||||
|
||||
const handleDisclosureClick = React.useCallback((ev: SyntheticEvent<>) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setExpanded((prevExpanded) => !prevExpanded);
|
||||
}, []);
|
||||
const handleDisclosureClick = React.useCallback(
|
||||
(ev: React.MouseEvent<HTMLDivElement>) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setExpanded((prevExpanded) => !prevExpanded);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleTitleChange = React.useCallback(
|
||||
async (title: string) => {
|
||||
if (!document) return;
|
||||
|
||||
await documents.update({
|
||||
id: document.id,
|
||||
lastRevision: document.revision,
|
||||
text: document.text,
|
||||
title,
|
||||
});
|
||||
await documents.update(
|
||||
{
|
||||
id: document.id,
|
||||
text: document.text,
|
||||
title,
|
||||
},
|
||||
{
|
||||
lastRevision: document.revision,
|
||||
}
|
||||
);
|
||||
},
|
||||
[documents, document]
|
||||
);
|
||||
@@ -71,6 +75,7 @@ function StarredLink({ depth, title, to, documentId, collectionId }: Props) {
|
||||
<SidebarLink
|
||||
depth={depth}
|
||||
to={`${to}?starred`}
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'match' implicitly has an 'any' type.
|
||||
isActive={(match, location) =>
|
||||
match && location.search === "?starred"
|
||||
}
|
||||
@@ -1,23 +1,22 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import { ExpandedIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Flex from "components/Flex";
|
||||
import TeamLogo from "components/TeamLogo";
|
||||
import Flex from "~/components/Flex";
|
||||
import TeamLogo from "~/components/TeamLogo";
|
||||
|
||||
type Props = {|
|
||||
teamName: string,
|
||||
subheading: React.Node,
|
||||
showDisclosure?: boolean,
|
||||
onClick: (event: SyntheticEvent<>) => void,
|
||||
logoUrl: string,
|
||||
|};
|
||||
type Props = {
|
||||
teamName: string;
|
||||
subheading: React.ReactNode;
|
||||
showDisclosure?: boolean;
|
||||
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||
logoUrl: string;
|
||||
};
|
||||
|
||||
const TeamButton = React.forwardRef<Props, any>(
|
||||
const TeamButton = React.forwardRef<HTMLButtonElement, Props>(
|
||||
({ showDisclosure, teamName, subheading, logoUrl, ...rest }: Props, ref) => (
|
||||
<Wrapper>
|
||||
<Header justify="flex-start" align="center" ref={ref} {...rest}>
|
||||
<Header ref={ref} {...rest}>
|
||||
<TeamLogo
|
||||
alt={`${teamName} logo`}
|
||||
src={logoUrl}
|
||||
@@ -25,7 +24,7 @@ const TeamButton = React.forwardRef<Props, any>(
|
||||
height={38}
|
||||
/>
|
||||
<Flex align="flex-start" column>
|
||||
<TeamName showDisclosure>
|
||||
<TeamName>
|
||||
{teamName} {showDisclosure && <Disclosure color="currentColor" />}
|
||||
</TeamName>
|
||||
<Subheading>{subheading}</Subheading>
|
||||
@@ -1,20 +1,18 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import Arrow from "components/Arrow";
|
||||
import Arrow from "~/components/Arrow";
|
||||
|
||||
type Props = {
|
||||
direction: "left" | "right",
|
||||
style?: Object,
|
||||
onClick?: () => any,
|
||||
direction: "left" | "right";
|
||||
style?: React.CSSProperties;
|
||||
onClick?: () => any;
|
||||
};
|
||||
|
||||
const Toggle = React.forwardRef<Props, HTMLButtonElement>(
|
||||
const Toggle = React.forwardRef<HTMLButtonElement, Props>(
|
||||
({ direction = "left", onClick, style }: Props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Positioner style={style}>
|
||||
<ToggleButton
|
||||
@@ -30,7 +28,7 @@ const Toggle = React.forwardRef<Props, HTMLButtonElement>(
|
||||
}
|
||||
);
|
||||
|
||||
export const ToggleButton = styled.button`
|
||||
export const ToggleButton = styled.button<{ $direction?: "left" | "right" }>`
|
||||
opacity: 0;
|
||||
background: none;
|
||||
transition: opacity 100ms ease-in-out;
|
||||
@@ -1,30 +1,31 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import { TrashIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
import { useDrop } from "react-dnd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DocumentDelete from "scenes/DocumentDelete";
|
||||
import Modal from "components/Modal";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
import useStores from "hooks/useStores";
|
||||
import { trashPath } from "utils/routeHelpers";
|
||||
import Document from "~/models/Document";
|
||||
import DocumentDelete from "~/scenes/DocumentDelete";
|
||||
import Modal from "~/components/Modal";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { trashPath } from "~/utils/routeHelpers";
|
||||
import SidebarLink, { DragObject } from "./SidebarLink";
|
||||
|
||||
function TrashLink({ documents }) {
|
||||
const { policies } = useStores();
|
||||
function TrashLink() {
|
||||
const { policies, documents } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const [document, setDocument] = useState();
|
||||
const [document, setDocument] = useState<Document>();
|
||||
|
||||
const [{ isDocumentDropping }, dropToTrashDocument] = useDrop({
|
||||
accept: "document",
|
||||
drop: (item, monitor) => {
|
||||
drop: (item: DragObject) => {
|
||||
const doc = documents.get(item.id);
|
||||
|
||||
// without setTimeout it was not working in firefox v89.0.2-ubuntu
|
||||
// on dropping mouseup is considered as clicking outside the modal, and it immediately closes
|
||||
setTimeout(() => setDocument(doc), 1);
|
||||
setTimeout(() => doc && setDocument(doc), 1);
|
||||
},
|
||||
canDrop: (item, monitor) => policies.abilities(item.id).delete,
|
||||
canDrop: (item) => policies.abilities(item.id).delete,
|
||||
collect: (monitor) => ({
|
||||
isDocumentDropping: monitor.isOver(),
|
||||
}),
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Badge from "components/Badge";
|
||||
import Badge from "~/components/Badge";
|
||||
import { version } from "../../../../package.json";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
|
||||
@@ -15,6 +14,7 @@ export default function Version() {
|
||||
"https://api.github.com/repos/outline/outline/releases"
|
||||
);
|
||||
const releases = await res.json();
|
||||
|
||||
for (const release of releases) {
|
||||
if (release.tag_name === `v${version}`) {
|
||||
return setReleasesBehind(out);
|
||||
@@ -23,6 +23,7 @@ export default function Version() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadReleases();
|
||||
}, []);
|
||||
|
||||
Reference in New Issue
Block a user