Compare commits

..

2 Commits

Author SHA1 Message Date
Tom Moor 8e9beac59f test 2023-08-08 23:12:41 -04:00
Tom Moor a0f7c76405 Add support for SSL in development 2023-08-08 22:46:31 -04:00
319 changed files with 1448 additions and 2920 deletions
+1 -2
View File
@@ -21,7 +21,7 @@
"eslint-plugin-import",
"eslint-plugin-node",
"eslint-plugin-react",
"eslint-plugin-lodash"
"import"
],
"rules": {
"eqeqeq": 2,
@@ -55,7 +55,6 @@
],
"padding-line-between-statements": ["error", { "blankLine": "always", "prev": "*", "next": "export" }],
"lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }],
"lodash/import-scope": ["warn", "method"],
"import/no-named-as-default": "off",
"import/no-named-as-default-member": "off",
"import/newline-after-import": 2,
-4
View File
@@ -96,10 +96,6 @@ Or to run migrations on test database:
yarn sequelize db:migrate --env test
```
# Activity
![Alt](https://repobeats.axiom.co/api/embed/ff2e4e6918afff1acf9deb72d1ba6b071d586178.svg "Repobeats analytics image")
## License
Outline is [BSL 1.1 licensed](LICENSE).
+1 -1
View File
@@ -14,7 +14,6 @@ import {
BrowserIcon,
} from "outline-icons";
import * as React from "react";
import { isMac } from "@shared/utils/browser";
import {
developersUrl,
changelogUrl,
@@ -27,6 +26,7 @@ import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
import { createAction } from "~/actions";
import { NavigationSection, RecentSearchesSection } from "~/actions/sections";
import Desktop from "~/utils/Desktop";
import { isMac } from "~/utils/browser";
import history from "~/utils/history";
import isCloudHosted from "~/utils/isCloudHosted";
import {
+1 -1
View File
@@ -1,4 +1,4 @@
import flattenDeep from "lodash/flattenDeep";
import { flattenDeep } from "lodash";
import * as React from "react";
import { Optional } from "utility-types";
import { v4 as uuidv4 } from "uuid";
+1 -1
View File
@@ -1,6 +1,6 @@
/* eslint-disable prefer-rest-params */
/* global ga */
import escape from "lodash/escape";
import { escape } from "lodash";
import * as React from "react";
import { IntegrationService } from "@shared/types";
import env from "~/env";
+1 -4
View File
@@ -1,7 +1,4 @@
import filter from "lodash/filter";
import isEqual from "lodash/isEqual";
import sortBy from "lodash/sortBy";
import uniq from "lodash/uniq";
import { sortBy, filter, uniq, isEqual } from "lodash";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
+2 -7
View File
@@ -7,7 +7,6 @@ import ConfirmationDialog from "~/components/ConfirmationDialog";
import Text from "~/components/Text";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { homePath } from "~/utils/routeHelpers";
type Props = {
@@ -18,20 +17,16 @@ type Props = {
function CollectionDeleteDialog({ collection, onSubmit }: Props) {
const team = useCurrentTeam();
const { ui } = useStores();
const { showToast } = useToasts();
const history = useHistory();
const { t } = useTranslation();
const handleSubmit = async () => {
const redirect = collection.id === ui.activeCollectionId;
await collection.delete();
onSubmit();
if (redirect) {
history.push(homePath());
}
await collection.delete();
onSubmit();
showToast(t("Collection deleted"), { type: "success" });
};
return (
+5 -38
View File
@@ -14,48 +14,15 @@ function ConnectionStatus() {
const theme = useTheme();
const { t } = useTranslation();
const codeToMessage = {
1009: {
title: t("Document is too large"),
body: t(
"This document has reached the maximum size and can no longer be edited"
),
},
4401: {
title: t("Authentication failed"),
body: t("Please try logging out and back in again"),
},
4403: {
title: t("Authorization failed"),
body: t("You may have lost access to this document, try reloading"),
},
4503: {
title: t("Too many users connected to document"),
body: t("Your edits will sync once other users leave the document"),
},
};
const message = ui.multiplayerErrorCode
? codeToMessage[ui.multiplayerErrorCode]
: undefined;
return ui.multiplayerStatus === "connecting" ||
ui.multiplayerStatus === "disconnected" ? (
<Tooltip
tooltip={
message ? (
<Centered>
<strong>{message.title}</strong>
<br />
{message.body}
</Centered>
) : (
<Centered>
<strong>{t("Server connection lost")}</strong>
<br />
{t("Edits you make will sync once youre online")}
</Centered>
)
<Centered>
<strong>{t("Server connection lost")}</strong>
<br />
{t("Edits you make will sync once youre online")}
</Centered>
}
placement="bottom"
>
+1 -1
View File
@@ -151,7 +151,7 @@ const ContextMenu: React.FC<Props> = ({
ref={backgroundRef}
hiddenScrollbars
style={
topAnchor && !isMobile
topAnchor
? {
maxHeight,
}
+1 -6
View File
@@ -1,10 +1,5 @@
import FuzzySearch from "fuzzy-search";
import concat from "lodash/concat";
import difference from "lodash/difference";
import fill from "lodash/fill";
import filter from "lodash/filter";
import includes from "lodash/includes";
import map from "lodash/map";
import { includes, difference, concat, filter, map, fill } from "lodash";
import { observer } from "mobx-react";
import { StarredIcon, DocumentIcon } from "outline-icons";
import * as React from "react";
+1 -1
View File
@@ -1,4 +1,4 @@
import sortBy from "lodash/sortBy";
import { sortBy } from "lodash";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
+2 -6
View File
@@ -1,6 +1,4 @@
import deburr from "lodash/deburr";
import difference from "lodash/difference";
import sortBy from "lodash/sortBy";
import { deburr, difference, sortBy } from "lodash";
import { observer } from "mobx-react";
import { DOMParser as ProsemirrorDOMParser } from "prosemirror-model";
import { TextSelection } from "prosemirror-state";
@@ -48,7 +46,6 @@ export type Props = Optional<
> & {
shareId?: string | undefined;
embedsDisabled?: boolean;
previewsDisabled?: boolean;
onHeadingsChange?: (headings: Heading[]) => void;
onSynced?: () => Promise<void>;
onPublish?: (event: React.MouseEvent) => any;
@@ -63,7 +60,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
onHeadingsChange,
onCreateCommentMark,
onDeleteCommentMark,
previewsDisabled,
} = props;
const userLocale = useUserLocale();
const locale = dateLocale(userLocale);
@@ -341,7 +337,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
userPreferences={preferences}
dictionary={dictionary}
{...props}
onHoverLink={previewsDisabled ? undefined : handleLinkActive}
onHoverLink={handleLinkActive}
onClickLink={handleClickLink}
onSearchLink={handleSearchLink}
onChange={handleChange}
+2 -2
View File
@@ -1,4 +1,4 @@
import throttle from "lodash/throttle";
import { throttle } from "lodash";
import { observer } from "mobx-react";
import { MenuIcon } from "outline-icons";
import { transparentize } from "polished";
@@ -6,7 +6,6 @@ import * as React from "react";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { depths, s } from "@shared/styles";
import { supportsPassiveListener } from "@shared/utils/browser";
import Button from "~/components/Button";
import Fade from "~/components/Fade";
import Flex from "~/components/Flex";
@@ -15,6 +14,7 @@ import useMobile from "~/hooks/useMobile";
import useStores from "~/hooks/useStores";
import { draggableOnDesktop, fadeOnDesktopBackgrounded } from "~/styles";
import Desktop from "~/utils/Desktop";
import { supportsPassiveListener } from "~/utils/browser";
type Props = {
left?: React.ReactNode;
+1 -1
View File
@@ -1,4 +1,4 @@
import escapeRegExp from "lodash/escapeRegExp";
import { escapeRegExp } from "lodash";
import * as React from "react";
import replace from "string-replace-to-array";
import styled from "styled-components";
+1 -1
View File
@@ -4,7 +4,7 @@ import styled, { css } from "styled-components";
import { s } from "@shared/styles";
import Text from "~/components/Text";
export const CARD_MARGIN = 10;
export const CARD_MARGIN = 16;
const NUMBER_OF_LINES = 10;
+27 -59
View File
@@ -2,7 +2,7 @@ import { m } from "framer-motion";
import * as React from "react";
import { Portal } from "react-portal";
import styled from "styled-components";
import { depths } from "@shared/styles";
import { depths, s } from "@shared/styles";
import { UnfurlType } from "@shared/types";
import LoadingIndicator from "~/components/LoadingIndicator";
import useEventListener from "~/hooks/useEventListener";
@@ -27,14 +27,6 @@ type Props = {
onClose: () => void;
};
enum Direction {
UP,
DOWN,
}
const POINTER_HEIGHT = 22;
const POINTER_WIDTH = 22;
function HoverPreviewInternal({ element, onClose }: Props) {
const url = element.href || element.dataset.url;
const [isVisible, setVisible] = React.useState(false);
@@ -44,46 +36,31 @@ function HoverPreviewInternal({ element, onClose }: Props) {
const stores = useStores();
const [cardLeft, setCardLeft] = React.useState(0);
const [cardTop, setCardTop] = React.useState(0);
const [pointerLeft, setPointerLeft] = React.useState(0);
const [pointerTop, setPointerTop] = React.useState(0);
const [pointerDir, setPointerDir] = React.useState(Direction.UP);
const [pointerOffset, setPointerOffset] = React.useState(0);
React.useLayoutEffect(() => {
if (isVisible && cardRef.current) {
const elem = element.getBoundingClientRect();
const card = cardRef.current.getBoundingClientRect();
let cTop = elem.bottom + window.scrollY + CARD_MARGIN;
let pTop = -POINTER_HEIGHT;
let pDir = Direction.UP;
if (cTop + card.height > window.innerHeight + window.scrollY) {
// shift card upwards if it goes out of screen
const bottom = elem.top + window.scrollY;
cTop = bottom - card.height;
// shift a little further to leave some margin between card and element boundary
cTop -= CARD_MARGIN;
// pointer should be shifted downwards to align with card's bottom
pTop = card.height;
pDir = Direction.DOWN;
}
setCardTop(cTop);
setPointerTop(pTop);
setPointerDir(pDir);
const top = elem.bottom + window.scrollY;
setCardTop(top);
let cLeft = elem.left;
let pLeft = elem.width / 2;
if (cLeft + card.width > window.innerWidth) {
let left = elem.left;
let pointerOffset = elem.width / 2;
if (left + card.width > window.innerWidth) {
// shift card leftwards by the amount it went out of screen
let shiftBy = cLeft + card.width - window.innerWidth;
// shift a little further to leave some margin between card and window boundary
let shiftBy = left + card.width - window.innerWidth;
// shift a littler further to leave some margin between card and window boundary
shiftBy += CARD_MARGIN;
cLeft -= shiftBy;
left -= shiftBy;
// shift pointer rightwards by same amount so as to position it back correctly
pLeft += shiftBy;
pointerOffset += shiftBy;
}
setCardLeft(cLeft);
setPointerLeft(pLeft);
setCardLeft(left);
setPointerOffset(pointerOffset);
}
}, [isVisible, element]);
@@ -216,11 +193,7 @@ function HoverPreviewInternal({ element, onClose }: Props) {
description={data.description}
/>
)}
<Pointer
top={pointerTop}
left={pointerLeft}
direction={pointerDir}
/>
<Pointer offset={pointerOffset} />
</Animate>
) : null}
</Position>
@@ -244,6 +217,7 @@ const Animate = styled(m.div)`
`;
const Position = styled.div<{ fixed?: boolean; top?: number; left?: number }>`
margin-top: 10px;
position: ${({ fixed }) => (fixed ? "fixed" : "absolute")};
z-index: ${depths.hoverPreview};
display: flex;
@@ -253,11 +227,11 @@ const Position = styled.div<{ fixed?: boolean; top?: number; left?: number }>`
${({ left }) => (left !== undefined ? `left: ${left}px` : "")};
`;
const Pointer = styled.div<{ top: number; left: number; direction: Direction }>`
top: ${(props) => props.top}px;
left: ${(props) => props.left}px;
width: ${POINTER_WIDTH}px;
height: ${POINTER_HEIGHT}px;
const Pointer = styled.div<{ offset: number }>`
top: -22px;
left: ${(props) => props.offset}px;
width: 22px;
height: 22px;
position: absolute;
transform: translateX(-50%);
pointer-events: none;
@@ -267,26 +241,20 @@ const Pointer = styled.div<{ top: number; left: number; direction: Direction }>`
content: "";
display: inline-block;
position: absolute;
${({ direction }) => (direction === Direction.UP ? "bottom: 0" : "top: 0")};
${({ direction }) => (direction === Direction.UP ? "right: 0" : "left: 0")};
bottom: 0;
right: 0;
}
&:before {
border: 8px solid transparent;
${({ direction, theme }) =>
direction === Direction.UP
? `border-bottom-color: ${theme.menuBorder || "rgba(0, 0, 0, 0.1)"}`
: `border-top-color: ${theme.menuBorder || "rgba(0, 0, 0, 0.1)"}`};
${({ direction }) =>
direction === Direction.UP ? "right: -1px" : "left: -1px"};
border-bottom-color: ${(props) =>
props.theme.menuBorder || "rgba(0, 0, 0, 0.1)"};
right: -1px;
}
&:after {
border: 7px solid transparent;
${({ direction, theme }) =>
direction === Direction.UP
? `border-bottom-color: ${theme.menuBackground}`
: `border-top-color: ${theme.menuBackground}`};
border-bottom-color: ${s("menuBackground")};
}
`;
@@ -26,9 +26,9 @@ const HoverPreviewLink = React.forwardRef(function _HoverPreviewLink(
) {
return (
<Preview as="a" href={url} target="_blank" rel="noopener noreferrer">
<Flex column ref={ref}>
<Flex column>
{thumbnailUrl ? <Thumbnail src={thumbnailUrl} alt={""} /> : null}
<Card>
<Card ref={ref}>
<CardContent>
<Flex column>
<Title>{title}</Title>
+1 -1
View File
@@ -18,7 +18,7 @@ export default function InputSelectPermission(
const handleChange = React.useCallback(
(value) => {
if (value === "no_access") {
value = null;
value = "";
}
onChange?.(value);
+1 -1
View File
@@ -1,4 +1,4 @@
import find from "lodash/find";
import { find } from "lodash";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import styled from "styled-components";
+1 -1
View File
@@ -1,4 +1,4 @@
import times from "lodash/times";
import { times } from "lodash";
import * as React from "react";
import styled from "styled-components";
import Fade from "~/components/Fade";
@@ -64,7 +64,6 @@ function NotificationListItem({ notification, onNavigate }: Props) {
{notification.comment && (
<StyledCommentEditor
defaultValue={toJS(notification.comment.data)}
previewsDisabled
/>
)}
</Flex>
+1 -1
View File
@@ -1,4 +1,4 @@
import isEqual from "lodash/isEqual";
import { isEqual } from "lodash";
import { observable, action } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
+1 -1
View File
@@ -1,4 +1,4 @@
import debounce from "lodash/debounce";
import { debounce } from "lodash";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
+3 -5
View File
@@ -26,9 +26,7 @@ import Sidebar from "./Sidebar";
import ArchiveLink from "./components/ArchiveLink";
import Collections from "./components/Collections";
import DragPlaceholder from "./components/DragPlaceholder";
import FullWidthButton, {
FullWidthButtonProps,
} from "./components/FullWidthButton";
import HeaderButton, { HeaderButtonProps } from "./components/HeaderButton";
import HistoryNavigation from "./components/HistoryNavigation";
import Section from "./components/Section";
import SidebarAction from "./components/SidebarAction";
@@ -67,8 +65,8 @@ function AppSidebar() {
<DragPlaceholder />
<OrganizationMenu>
{(props: FullWidthButtonProps) => (
<FullWidthButton
{(props: HeaderButtonProps) => (
<HeaderButton
{...props}
title={team.name}
image={
+3 -3
View File
@@ -1,4 +1,4 @@
import groupBy from "lodash/groupBy";
import { groupBy } from "lodash";
import { observer } from "mobx-react";
import { BackIcon } from "outline-icons";
import * as React from "react";
@@ -11,8 +11,8 @@ import useSettingsConfig from "~/hooks/useSettingsConfig";
import Desktop from "~/utils/Desktop";
import isCloudHosted from "~/utils/isCloudHosted";
import Sidebar from "./Sidebar";
import FullWidthButton from "./components/FullWidthButton";
import Header from "./components/Header";
import HeaderButton from "./components/HeaderButton";
import HistoryNavigation from "./components/HistoryNavigation";
import Section from "./components/Section";
import SidebarLink from "./components/SidebarLink";
@@ -31,7 +31,7 @@ function SettingsSidebar() {
return (
<Sidebar>
<HistoryNavigation />
<FullWidthButton
<HeaderButton
title={t("Return to App")}
image={<StyledBackIcon />}
onClick={returnToApp}
+2 -2
View File
@@ -11,7 +11,7 @@ import { homePath, sharedDocumentPath } from "~/utils/routeHelpers";
import { useTeamContext } from "../TeamContext";
import TeamLogo from "../TeamLogo";
import Sidebar from "./Sidebar";
import FullWidthButton from "./components/FullWidthButton";
import HeaderButton from "./components/HeaderButton";
import Section from "./components/Section";
import DocumentLink from "./components/SharedDocumentLink";
@@ -28,7 +28,7 @@ function SharedSidebar({ rootNode, shareId }: Props) {
return (
<Sidebar>
{team && (
<FullWidthButton
<HeaderButton
title={team.name}
image={<TeamLogo model={team} size={32} alt={t("Logo")} />}
onClick={() =>
+9 -14
View File
@@ -11,15 +11,13 @@ import useMenuContext from "~/hooks/useMenuContext";
import usePrevious from "~/hooks/usePrevious";
import useStores from "~/hooks/useStores";
import AccountMenu from "~/menus/AccountMenu";
import { fadeOnDesktopBackgrounded } from "~/styles";
import { draggableOnDesktop, fadeOnDesktopBackgrounded } from "~/styles";
import { fadeIn } from "~/styles/animations";
import Desktop from "~/utils/Desktop";
import Avatar from "../Avatar";
import NotificationIcon from "../Notifications/NotificationIcon";
import NotificationsPopover from "../Notifications/NotificationsPopover";
import FullWidthButton, {
FullWidthButtonProps,
} from "./components/FullWidthButton";
import HeaderButton, { HeaderButtonProps } from "./components/HeaderButton";
import ResizeBorder from "./components/ResizeBorder";
import Toggle, { ToggleButton, Positioner } from "./components/Toggle";
@@ -177,12 +175,11 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
{user && (
<AccountMenu>
{(props: FullWidthButtonProps) => (
<FullWidthButton
{(props: HeaderButtonProps) => (
<HeaderButton
{...props}
showMoreMenu
title={user.name}
position="bottom"
image={
<StyledAvatar
alt={user.name}
@@ -193,15 +190,11 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
}
>
<NotificationsPopover>
{(rest: FullWidthButtonProps) => (
<FullWidthButton
{...rest}
position="bottom"
image={<NotificationIcon />}
/>
{(rest: HeaderButtonProps) => (
<HeaderButton {...rest} image={<NotificationIcon />} />
)}
</NotificationsPopover>
</FullWidthButton>
</HeaderButton>
)}
</AccountMenu>
)}
@@ -266,6 +259,8 @@ const Container = styled(Flex)<ContainerProps>`
z-index: ${depths.sidebar};
max-width: 80%;
min-width: 280px;
padding-top: ${Desktop.hasInsetTitlebar() ? 36 : 0}px;
${draggableOnDesktop()}
${fadeOnDesktopBackgrounded()}
${Positioner} {
@@ -3,11 +3,9 @@ import * as React from "react";
import styled from "styled-components";
import { s } from "@shared/styles";
import Flex from "~/components/Flex";
import { draggableOnDesktop, undraggableOnDesktop } from "~/styles";
import Desktop from "~/utils/Desktop";
import { undraggableOnDesktop } from "~/styles";
export type FullWidthButtonProps = React.ComponentProps<typeof Button> & {
position: "top" | "bottom";
export type HeaderButtonProps = React.ComponentProps<typeof Button> & {
title: React.ReactNode;
image: React.ReactNode;
minHeight?: number;
@@ -18,53 +16,40 @@ export type FullWidthButtonProps = React.ComponentProps<typeof Button> & {
children?: React.ReactNode;
};
const FullWidthButton = React.forwardRef<
HTMLButtonElement,
FullWidthButtonProps
>(function _FullWidthButton(
{
position = "top",
showDisclosure,
showMoreMenu,
image,
title,
minHeight = 0,
children,
...rest
}: FullWidthButtonProps,
ref
) {
return (
<Container
justify="space-between"
align="center"
shrink={false}
$position={position}
>
<Button
{...rest}
minHeight={minHeight}
as="button"
ref={ref}
role="button"
>
<Title gap={8} align="center">
{image}
{title}
</Title>
{showDisclosure && <ExpandedIcon />}
{showMoreMenu && <MoreIcon />}
</Button>
{children}
</Container>
);
});
const Container = styled(Flex)<{ $position: "top" | "bottom" }>`
padding-top: ${(props) =>
props.$position === "top" && Desktop.hasInsetTitlebar() ? 36 : 0}px;
${draggableOnDesktop()}
`;
const HeaderButton = React.forwardRef<HTMLButtonElement, HeaderButtonProps>(
function _HeaderButton(
{
showDisclosure,
showMoreMenu,
image,
title,
minHeight = 0,
children,
...rest
}: HeaderButtonProps,
ref
) {
return (
<Flex justify="space-between" align="center" shrink={false}>
<Button
{...rest}
minHeight={minHeight}
as="button"
ref={ref}
role="button"
>
<Title gap={8} align="center">
{image}
{title}
</Title>
{showDisclosure && <ExpandedIcon />}
{showMoreMenu && <MoreIcon />}
</Button>
{children}
</Flex>
);
}
);
const Title = styled(Flex)`
color: ${s("text")};
@@ -114,4 +99,4 @@ const Button = styled(Flex)<{ minHeight: number }>`
}
`;
export default FullWidthButton;
export default HeaderButton;
@@ -3,12 +3,12 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { s } from "@shared/styles";
import { isMac } from "@shared/utils/browser";
import Flex from "~/components/Flex";
import NudeButton from "~/components/NudeButton";
import Tooltip from "~/components/Tooltip";
import useKeyDown from "~/hooks/useKeyDown";
import Desktop from "~/utils/Desktop";
import { isMac } from "~/utils/browser";
function HistoryNavigation(props: React.ComponentProps<typeof Flex>) {
const { t } = useTranslation();
@@ -1,4 +1,3 @@
import includes from "lodash/includes";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
@@ -7,15 +6,14 @@ import Collection from "~/models/Collection";
import Document from "~/models/Document";
import useStores from "~/hooks/useStores";
import { sharedDocumentPath } from "~/utils/routeHelpers";
import { descendants } from "~/utils/tree";
import Disclosure from "./Disclosure";
import SidebarLink from "./SidebarLink";
type Props = {
node: NavigationNode;
collection?: Collection;
activeDocumentId?: string;
activeDocument?: Document;
activeDocumentId: string | undefined;
activeDocument: Document | undefined;
isDraft?: boolean;
depth: number;
index: number;
@@ -43,19 +41,10 @@ function DocumentLink(
const hasChildDocuments =
!!node.children.length || activeDocument?.parentDocumentId === node.id;
const document = documents.get(node.id);
const showChildren = React.useMemo(
() =>
!!(
hasChildDocuments &&
((activeDocumentId &&
includes(
descendants(node).map((n) => n.id),
activeDocumentId
)) ||
isActiveDocument ||
depth <= 1)
),
[hasChildDocuments, activeDocumentId, isActiveDocument, depth, node]
() => !!hasChildDocuments,
[hasChildDocuments]
);
const [expanded, setExpanded] = React.useState(showChildren);
@@ -66,6 +55,12 @@ function DocumentLink(
}
}, [showChildren]);
React.useEffect(() => {
if (isActiveDocument) {
setExpanded(true);
}
}, [isActiveDocument]);
const handleDisclosureClick = React.useCallback(
(ev: React.SyntheticEvent) => {
ev.preventDefault();
@@ -292,7 +292,7 @@ const Label = styled.div`
position: relative;
width: 100%;
max-height: 4.8em;
line-height: 24px;
line-height: 1.6;
* {
unicode-bidi: plaintext;
+1 -1
View File
@@ -1,4 +1,4 @@
import isEqual from "lodash/isEqual";
import { isEqual } from "lodash";
import { observer } from "mobx-react";
import { CollapsedIcon } from "outline-icons";
import * as React from "react";
+1 -1
View File
@@ -14,7 +14,7 @@ type Props = {
*/
const Text = styled.p<Props>`
margin-top: 0;
text-align: ${(props) => (props.dir ? props.dir : "inherit")};
text-align: ${(props) => (props.dir ? props.dir : "initial")};
color: ${(props) =>
props.type === "secondary"
? props.theme.textSecondary
+1 -1
View File
@@ -1,5 +1,5 @@
import invariant from "invariant";
import find from "lodash/find";
import { find } from "lodash";
import { action, observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
+1 -3
View File
@@ -9,7 +9,6 @@ import { Portal } from "~/components/Portal";
import useComponentSize from "~/hooks/useComponentSize";
import useEventListener from "~/hooks/useEventListener";
import useMediaQuery from "~/hooks/useMediaQuery";
import useMobile from "~/hooks/useMobile";
import useViewportHeight from "~/hooks/useViewportHeight";
import Logger from "~/utils/Logger";
import { useEditor } from "./EditorContext";
@@ -41,7 +40,6 @@ function usePosition({
const { selection } = view.state;
const { width: menuWidth, height: menuHeight } = useComponentSize(menuRef);
const viewportHeight = useViewportHeight();
const isMobile = useMobile();
const isTouchDevice = useMediaQuery("(hover: none) and (pointer: coarse)");
if (!active || !menuWidth || !menuHeight || !menuRef.current) {
@@ -50,7 +48,7 @@ function usePosition({
// If we're on a mobile device then stick the floating toolbar to the bottom
// of the screen above the virtual keyboard.
if (isTouchDevice && isMobile && viewportHeight) {
if (isTouchDevice && viewportHeight) {
return {
left: 0,
right: 0,
+2 -2
View File
@@ -1,4 +1,4 @@
import some from "lodash/some";
import { some } from "lodash";
import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
import * as React from "react";
import createAndInsertLink from "@shared/editor/commands/createAndInsertLink";
@@ -231,7 +231,7 @@ export default function SelectionToolbar(props: Props) {
} else if (rowIndex !== undefined) {
items = getTableRowMenuItems(state, rowIndex, dictionary);
} else if (isImageSelection) {
items = readOnly ? [] : getImageMenuItems(state, dictionary);
items = getImageMenuItems(state, dictionary);
} else if (isDividerSelection) {
items = getDividerMenuItems(state, dictionary);
} else if (readOnly) {
+1 -1
View File
@@ -1,5 +1,5 @@
import commandScore from "command-score";
import capitalize from "lodash/capitalize";
import { capitalize } from "lodash";
import * as React from "react";
import { Trans } from "react-i18next";
import { VisuallyHidden } from "reakit/VisuallyHidden";
-6
View File
@@ -8,10 +8,4 @@ declare global {
const env = window.env;
if (!env) {
throw new Error(
"Config could not be be parsed. \nSee: https://docs.getoutline.com/s/hosting/doc/troubleshooting-HXckrzCqDJ#h-config-could-not-be-parsed"
);
}
export default env;
+1 -1
View File
@@ -1,5 +1,5 @@
import { useRegisterActions } from "kbar";
import flattenDeep from "lodash/flattenDeep";
import { flattenDeep } from "lodash";
import { useLocation } from "react-router-dom";
import { actionToKBar } from "~/actions";
import { Action } from "~/types";
+1 -1
View File
@@ -1,4 +1,4 @@
import find from "lodash/find";
import { find } from "lodash";
import * as React from "react";
import embeds, { EmbedDescriptor } from "@shared/editor/embeds";
import { IntegrationType } from "@shared/types";
+1 -1
View File
@@ -1,4 +1,4 @@
import throttle from "lodash/throttle";
import { throttle } from "lodash";
import * as React from "react";
import { Minute } from "@shared/utils/time";
+1 -1
View File
@@ -1,4 +1,4 @@
import noop from "lodash/noop";
import { noop } from "lodash";
import React from "react";
type MenuContextType = {
+1 -1
View File
@@ -1,4 +1,4 @@
import throttle from "lodash/throttle";
import { throttle } from "lodash";
import * as React from "react";
import useEventListener from "./useEventListener";
import useIsMounted from "./useIsMounted";
+2 -2
View File
@@ -1,8 +1,8 @@
// Based on https://github.com/rehooks/window-scroll-position which is no longer
// maintained.
import throttle from "lodash/throttle";
import { throttle } from "lodash";
import { useState, useEffect } from "react";
import { supportsPassiveListener } from "@shared/utils/browser";
import { supportsPassiveListener } from "~/utils/browser";
const getPosition = () => ({
x: window.pageXOffset,
+1 -1
View File
@@ -1,4 +1,4 @@
import pick from "lodash/pick";
import { pick } from "lodash";
import { set, observable } from "mobx";
import Logger from "~/utils/Logger";
import { getFieldsForModel } from "./decorators/Field";
+1 -1
View File
@@ -1,4 +1,4 @@
import trim from "lodash/trim";
import { trim } from "lodash";
import { action, computed, observable, reaction, runInAction } from "mobx";
import {
CollectionPermission,
+1 -1
View File
@@ -1,5 +1,5 @@
import { addDays, differenceInDays } from "date-fns";
import floor from "lodash/floor";
import { floor } from "lodash";
import { action, autorun, computed, observable, set } from "mobx";
import { ExportContentType } from "@shared/types";
import type { NavigationNode } from "@shared/types";
+1 -1
View File
@@ -1,4 +1,4 @@
import sortBy from "lodash/sortBy";
import { sortBy } from "lodash";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
+1 -2
View File
@@ -1,4 +1,4 @@
import intersection from "lodash/intersection";
import { intersection } from "lodash";
import { observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
@@ -56,7 +56,6 @@ class CollectionNew extends React.Component<Props> {
icon: this.icon,
color: this.color,
permission: this.permission,
documents: [],
},
this.props.collections
);
@@ -1,4 +1,4 @@
import debounce from "lodash/debounce";
import { debounce } from "lodash";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
@@ -1,4 +1,4 @@
import throttle from "lodash/throttle";
import { throttle } from "lodash";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
+1 -1
View File
@@ -1,4 +1,4 @@
import debounce from "lodash/debounce";
import { debounce } from "lodash";
import { action, observable } from "mobx";
import { observer } from "mobx-react";
import { AllSelection } from "prosemirror-state";
+57 -63
View File
@@ -104,6 +104,7 @@ function DocumentHeader({
const { isDeleted, isTemplate } = document;
const can = usePolicy(document?.id);
const canToggleEmbeds = team?.documentEmbeds;
const canEdit = can.update && !isEditing;
const toc = (
<Tooltip
tooltip={ui.tocVisible ? t("Hide contents") : t("Show contents")}
@@ -137,7 +138,7 @@ function DocumentHeader({
to={documentEditPath(document)}
neutral
>
{isMobile ? null : t("Edit")}
{t("Edit")}
</Button>
</Tooltip>
</Action>
@@ -184,7 +185,7 @@ function DocumentHeader({
actions={
<>
{appearanceAction}
{can.update && !isEditing ? editAction : <div />}
{canEdit ? editAction : <div />}
</>
}
/>
@@ -249,56 +250,45 @@ function DocumentHeader({
disabled={savingIsDisabled}
neutral={isDraft}
>
{isDraft ? t("Save draft") : t("Done editing")}
{isDraft ? t("Save Draft") : t("Done Editing")}
</Button>
</Tooltip>
</Action>
</>
)}
{can.update &&
!isEditing &&
!team?.seamlessEditing &&
!isRevision &&
editAction}
{can.update &&
can.createChildDocument &&
!isRevision &&
!isMobile && (
<Action>
<NewChildDocumentMenu
document={document}
label={(props) => (
<Tooltip
tooltip={t("New document")}
shortcut="n"
delay={500}
placement="bottom"
>
<Button icon={<PlusIcon />} {...props} neutral>
{t("New doc")}
</Button>
</Tooltip>
)}
/>
</Action>
)}
{can.update &&
!isEditing &&
isTemplate &&
!isDraft &&
!isRevision && (
<Action>
<Button
icon={<PlusIcon />}
as={Link}
to={newDocumentPath(document.collectionId, {
templateId: document.id,
})}
>
{t("New from template")}
</Button>
</Action>
)}
{canEdit && !team?.seamlessEditing && !isRevision && editAction}
{canEdit && can.createChildDocument && !isRevision && !isMobile && (
<Action>
<NewChildDocumentMenu
document={document}
label={(props) => (
<Tooltip
tooltip={t("New document")}
shortcut="n"
delay={500}
placement="bottom"
>
<Button icon={<PlusIcon />} {...props} neutral>
{t("New doc")}
</Button>
</Tooltip>
)}
/>
</Action>
)}
{canEdit && isTemplate && !isDraft && !isRevision && (
<Action>
<Button
icon={<PlusIcon />}
as={Link}
to={newDocumentPath(document.collectionId, {
templateId: document.id,
})}
>
{t("New from template")}
</Button>
</Action>
)}
{revision && revision.createdAt !== document.updatedAt && (
<Action>
<Tooltip
@@ -328,23 +318,27 @@ function DocumentHeader({
{document.collectionId ? t("Publish") : `${t("Publish")}`}
</Button>
</Action>
{!isDeleted && <Separator />}
<Action>
<DocumentMenu
document={document}
isRevision={isRevision}
label={(props) => (
<Button
icon={<MoreIcon />}
{...props}
borderOnHover
neutral
{!isEditing && (
<>
{!isDeleted && <Separator />}
<Action>
<DocumentMenu
document={document}
isRevision={isRevision}
label={(props) => (
<Button
icon={<MoreIcon />}
{...props}
borderOnHover
neutral
/>
)}
showToggleEmbeds={canToggleEmbeds}
showDisplayOptions
/>
)}
showToggleEmbeds={canToggleEmbeds}
showDisplayOptions
/>
</Action>
</Action>
</>
)}
</>
}
/>
@@ -1,31 +1,36 @@
import { observer } from "mobx-react";
import { KeyboardIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
import Guide from "~/components/Guide";
import NudeButton from "~/components/NudeButton";
import Tooltip from "~/components/Tooltip";
import useStores from "~/hooks/useStores";
import useBoolean from "~/hooks/useBoolean";
function KeyboardShortcutsButton() {
const { t } = useTranslation();
const { dialogs } = useStores();
const handleOpenKeyboardShortcuts = () => {
dialogs.openGuide({
title: t("Keyboard shortcuts"),
content: <KeyboardShortcuts />,
});
};
const [
keyboardShortcutsOpen,
handleOpenKeyboardShortcuts,
handleCloseKeyboardShortcuts,
] = useBoolean();
return (
<Tooltip tooltip={t("Keyboard shortcuts")} shortcut="?" delay={500}>
<Button onClick={handleOpenKeyboardShortcuts}>
<KeyboardIcon />
</Button>
</Tooltip>
<>
<Guide
isOpen={keyboardShortcutsOpen}
onRequestClose={handleCloseKeyboardShortcuts}
title={t("Keyboard shortcuts")}
>
<KeyboardShortcuts />
</Guide>
<Tooltip tooltip={t("Keyboard shortcuts")} shortcut="?" delay={500}>
<Button onClick={handleOpenKeyboardShortcuts}>
<KeyboardIcon />
</Button>
</Tooltip>
</>
);
}
@@ -44,4 +49,4 @@ const Button = styled(NudeButton)`
}
`;
export default observer(KeyboardShortcutsButton);
export default KeyboardShortcutsButton;
@@ -1,12 +1,11 @@
import { HocuspocusProvider, WebSocketStatus } from "@hocuspocus/provider";
import throttle from "lodash/throttle";
import { throttle } from "lodash";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { IndexeddbPersistence } from "y-indexeddb";
import * as Y from "yjs";
import MultiplayerExtension from "@shared/editor/extensions/Multiplayer";
import { supportsPassiveListener } from "@shared/utils/browser";
import Editor, { Props as EditorProps } from "~/components/Editor";
import env from "~/env";
import useCurrentUser from "~/hooks/useCurrentUser";
@@ -17,6 +16,7 @@ import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { AwarenessChangeEvent } from "~/types";
import Logger from "~/utils/Logger";
import { supportsPassiveListener } from "~/utils/browser";
import { homePath } from "~/utils/routeHelpers";
type Props = EditorProps & {
@@ -135,10 +135,13 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
});
provider.on("close", (ev: MessageEvent) => {
if ("code" in ev.event) {
provider.shouldConnect =
ev.event.code !== 1009 && ev.event.code !== 4401;
ui.setMultiplayerStatus("disconnected", ev.event.code);
if ("code" in ev.event && ev.event.code === 1009) {
provider.shouldConnect = false;
showToast(
t(
"Sorry, this document is too large - edits will no longer be persisted."
)
);
}
});
@@ -161,11 +164,9 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
);
}
provider.on("status", (ev: ConnectionStatusEvent) => {
if (ui.multiplayerStatus !== ev.status) {
ui.setMultiplayerStatus(ev.status, undefined);
}
});
provider.on("status", (ev: ConnectionStatusEvent) =>
ui.setMultiplayerStatus(ev.status)
);
setRemoteProvider(provider);
@@ -176,7 +177,7 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
provider?.destroy();
void localProvider?.destroy();
setRemoteProvider(null);
ui.setMultiplayerStatus(undefined, undefined);
ui.setMultiplayerStatus(undefined);
};
}, [
history,
@@ -1,6 +1,5 @@
import invariant from "invariant";
import debounce from "lodash/debounce";
import isEmpty from "lodash/isEmpty";
import { debounce, isEmpty } from "lodash";
import { observer } from "mobx-react";
import { ExpandedIcon, GlobeIcon, PadlockIcon } from "outline-icons";
import * as React from "react";
+1 -1
View File
@@ -1,4 +1,4 @@
import flatten from "lodash/flatten";
import { flatten } from "lodash";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
+1 -1
View File
@@ -1,4 +1,4 @@
import flatten from "lodash/flatten";
import { flatten } from "lodash";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
+1 -1
View File
@@ -1,4 +1,4 @@
import debounce from "lodash/debounce";
import { debounce } from "lodash";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
+2 -2
View File
@@ -32,7 +32,7 @@ function Home() {
void pins.fetchPage();
}, [pins]);
const can = usePolicy(team);
const canManageTeam = usePolicy(team).manage;
return (
<Scene
@@ -49,7 +49,7 @@ function Home() {
>
{!ui.languagePromptDismissed && <LanguagePrompt />}
<Heading>{t("Home")}</Heading>
<PinnedDocuments pins={pins.home} canUpdate={can.update} />
<PinnedDocuments pins={pins.home} canUpdate={canManageTeam} />
<Documents>
<Tabs>
<Tab to="/home" exact>
+1 -2
View File
@@ -2,10 +2,10 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { s } from "@shared/styles";
import { isMac } from "@shared/utils/browser";
import Flex from "~/components/Flex";
import InputSearch from "~/components/InputSearch";
import Key from "~/components/Key";
import { isMac } from "~/utils/browser";
import { metaDisplay, altDisplay } from "~/utils/keyboard";
function KeyboardShortcuts() {
@@ -442,7 +442,6 @@ const List = styled.dl`
overflow: hidden;
padding: 0;
margin: 0;
user-select: none;
`;
const Keys = styled.dt`
+1 -7
View File
@@ -37,13 +37,7 @@ export default function Notices() {
Please use a Google Workspaces account instead.
</Trans>
)}
{notice === "pending-deletion" && (
<Trans>
The workspace associated with your user is scheduled for deletion and
cannot at accessed at this time.
</Trans>
)}
{notice === "maximum-reached" && (
{notice === "maximum-teams" && (
<Trans>
The workspace you authenticated with is not authorized on this
installation. Try another?
+1 -1
View File
@@ -1,4 +1,4 @@
import find from "lodash/find";
import { find } from "lodash";
import { observer } from "mobx-react";
import { BackIcon, EmailIcon } from "outline-icons";
import * as React from "react";
+1 -1
View File
@@ -1,4 +1,4 @@
import isEqual from "lodash/isEqual";
import { isEqual } from "lodash";
import { observable, action } from "mobx";
import { observer } from "mobx-react";
import queryString from "query-string";
+2 -37
View File
@@ -1,5 +1,5 @@
import { isHexColor } from "class-validator";
import pickBy from "lodash/pickBy";
import { pickBy } from "lodash";
import { observer } from "mobx-react";
import { TeamIcon } from "outline-icons";
import { useRef, useState } from "react";
@@ -20,22 +20,18 @@ import Switch from "~/components/Switch";
import Text from "~/components/Text";
import env from "~/env";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import isCloudHosted from "~/utils/isCloudHosted";
import TeamDelete from "../TeamDelete";
import ImageInput from "./components/ImageInput";
import SettingRow from "./components/SettingRow";
function Details() {
const { auth, dialogs, ui } = useStores();
const { auth, ui } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const team = useCurrentTeam();
const theme = useTheme();
const can = usePolicy(team);
const form = useRef<HTMLFormElement>(null);
const [accent, setAccent] = useState<null | undefined | string>(
team.preferences?.customTheme?.accent
@@ -129,14 +125,6 @@ function Details() {
[showToast, t]
);
const showDeleteWorkspace = () => {
dialogs.openModal({
title: t("Delete workspace"),
content: <TeamDelete onSubmit={dialogs.closeAllModals} />,
isCentered: true,
});
};
const onSelectCollection = React.useCallback(async (value: string) => {
const defaultCollectionId = value === "home" ? null : value;
setDefaultCollectionId(defaultCollectionId);
@@ -234,7 +222,6 @@ function Details() {
</SettingRow>
{team.avatarUrl && (
<SettingRow
border={false}
name={TeamPreference.PublicBranding}
label={t("Public branding")}
description={t(
@@ -300,28 +287,6 @@ function Details() {
<Button type="submit" disabled={auth.isSaving || !isValid}>
{auth.isSaving ? `${t("Saving")}` : t("Save")}
</Button>
{can.delete && (
<>
<p>&nbsp;</p>
<Heading as="h2">{t("Danger")}</Heading>
<SettingRow
name="delete"
border={false}
label={t("Delete workspace")}
description={t(
"You can delete this entire workspace including collections, documents, and users."
)}
>
<span>
<Button onClick={showDeleteWorkspace} neutral>
{t("Delete workspace")}
</Button>
</span>
</SettingRow>
</>
)}
</form>
</Scene>
</ThemeProvider>
+1 -1
View File
@@ -1,4 +1,4 @@
import find from "lodash/find";
import { find } from "lodash";
import { observer } from "mobx-react";
import * as React from "react";
import { useForm } from "react-hook-form";
+2 -2
View File
@@ -1,4 +1,4 @@
import sortBy from "lodash/sortBy";
import { sortBy } from "lodash";
import { observer } from "mobx-react";
import { PlusIcon, UserIcon } from "outline-icons";
import * as React from "react";
@@ -184,7 +184,7 @@ function Members() {
</Flex>
<PeopleTable
data={data}
canManage={can.update}
canManage={can.manage}
isLoading={isLoading}
page={page}
pageSize={limit}
+1 -1
View File
@@ -1,4 +1,4 @@
import debounce from "lodash/debounce";
import { debounce } from "lodash";
import { observer } from "mobx-react";
import {
AcademicCapIcon,
+2 -2
View File
@@ -47,7 +47,6 @@ function Preferences() {
dialogs.openModal({
title: t("Delete account"),
content: <UserDelete />,
isCentered: true,
});
};
@@ -132,7 +131,8 @@ function Preferences() {
/>
</SettingRow>
<Heading as="h2">{t("Danger")}</Heading>
<p>&nbsp;</p>
<SettingRow
name="delete"
label={t("Delete account")}
+1 -1
View File
@@ -1,4 +1,4 @@
import debounce from "lodash/debounce";
import { debounce } from "lodash";
import { observer } from "mobx-react";
import { CheckboxIcon, EmailIcon, PadlockIcon } from "outline-icons";
import { useState } from "react";
+8 -44
View File
@@ -1,4 +1,4 @@
import find from "lodash/find";
import { find } from "lodash";
import { observer } from "mobx-react";
import { BuildingBlocksIcon } from "outline-icons";
import * as React from "react";
@@ -16,7 +16,6 @@ import SettingRow from "./components/SettingRow";
type FormData = {
drawIoUrl: string;
gristUrl: string;
};
function SelfHosted() {
@@ -24,16 +23,11 @@ function SelfHosted() {
const { t } = useTranslation();
const { showToast } = useToasts();
const integrationDiagrams = find(integrations.orderedData, {
const integration = find(integrations.orderedData, {
type: IntegrationType.Embed,
service: IntegrationService.Diagrams,
}) as Integration<IntegrationType.Embed> | undefined;
const integrationGrist = find(integrations.orderedData, {
type: IntegrationType.Embed,
service: IntegrationService.Grist,
}) as Integration<IntegrationType.Embed> | undefined;
const {
register,
reset,
@@ -42,8 +36,7 @@ function SelfHosted() {
} = useForm<FormData>({
mode: "all",
defaultValues: {
drawIoUrl: integrationDiagrams?.settings.url,
gristUrl: integrationGrist?.settings.url,
drawIoUrl: integration?.settings.url,
},
});
@@ -54,18 +47,15 @@ function SelfHosted() {
}, [integrations]);
React.useEffect(() => {
reset({
drawIoUrl: integrationDiagrams?.settings.url,
gristUrl: integrationGrist?.settings.url,
});
}, [integrationDiagrams, integrationGrist, reset]);
reset({ drawIoUrl: integration?.settings.url });
}, [integration, reset]);
const handleSubmit = React.useCallback(
async (data: FormData) => {
try {
if (data.drawIoUrl) {
await integrations.save({
id: integrationDiagrams?.id,
id: integration?.id,
type: IntegrationType.Embed,
service: IntegrationService.Diagrams,
settings: {
@@ -73,20 +63,7 @@ function SelfHosted() {
},
});
} else {
await integrationDiagrams?.delete();
}
if (data.gristUrl) {
await integrations.save({
id: integrationGrist?.id,
type: IntegrationType.Embed,
service: IntegrationService.Grist,
settings: {
url: data.gristUrl,
},
});
} else {
await integrationGrist?.delete();
await integration?.delete();
}
showToast(t("Settings saved"), {
@@ -98,7 +75,7 @@ function SelfHosted() {
});
}
},
[integrations, integrationDiagrams, integrationGrist, t, showToast]
[integrations, integration, t, showToast]
);
return (
@@ -121,19 +98,6 @@ function SelfHosted() {
/>
</SettingRow>
<SettingRow
label={t("Grist deployment")}
name="gristUrl"
description={t("Add your self-hosted grist installation URL here.")}
border={false}
>
<Input
placeholder="https://docs.getgrist.com/"
pattern="https?://.*"
{...register("gristUrl")}
/>
</SettingRow>
<Button type="submit" disabled={formState.isSubmitting}>
{formState.isSubmitting ? `${t("Saving")}` : t("Save")}
</Button>
+3 -3
View File
@@ -1,4 +1,4 @@
import sortBy from "lodash/sortBy";
import { sortBy } from "lodash";
import { observer } from "mobx-react";
import { LinkIcon, WarningIcon } from "outline-icons";
import * as React from "react";
@@ -70,7 +70,7 @@ function Shares() {
<Scene title={t("Shared Links")} icon={<LinkIcon />}>
<Heading>{t("Shared Links")}</Heading>
{can.update && !canShareDocuments && (
{can.manage && !canShareDocuments && (
<>
<Notice icon={<WarningIcon />}>
{t("Sharing is currently disabled.")}{" "}
@@ -95,7 +95,7 @@ function Shares() {
<SharesTable
data={data}
canManage={can.update}
canManage={can.manage}
isLoading={isLoading}
page={page}
pageSize={limit}
@@ -1,4 +1,4 @@
import compact from "lodash/compact";
import { compact } from "lodash";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
-122
View File
@@ -1,122 +0,0 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useForm } from "react-hook-form";
import { useTranslation, Trans } from "react-i18next";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Input from "~/components/Input";
import Text from "~/components/Text";
import env from "~/env";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type FormData = {
code: string;
};
type Props = {
onSubmit: () => void;
};
function TeamDelete({ onSubmit }: Props) {
const [isWaitingCode, setWaitingCode] = React.useState(false);
const { auth } = useStores();
const { showToast } = useToasts();
const team = useCurrentTeam();
const { t } = useTranslation();
const {
register,
handleSubmit: formHandleSubmit,
formState,
} = useForm<FormData>();
const handleRequestDelete = React.useCallback(
async (ev: React.SyntheticEvent) => {
ev.preventDefault();
try {
await auth.requestDeleteTeam();
setWaitingCode(true);
} catch (error) {
showToast(error.message, {
type: "error",
});
}
},
[auth, showToast]
);
const handleSubmit = React.useCallback(
async (data: FormData) => {
try {
await auth.deleteTeam(data);
await auth.logout();
onSubmit();
} catch (error) {
showToast(error.message, {
type: "error",
});
}
},
[auth, onSubmit, showToast]
);
const inputProps = register("code", {
required: true,
});
const appName = env.APP_NAME;
const workspaceName = team.name;
return (
<Flex column>
<form onSubmit={formHandleSubmit(handleSubmit)}>
{isWaitingCode ? (
<>
<Text type="secondary">
<Trans>
A confirmation code has been sent to your email address, please
enter the code below to permanantly destroy this workspace.
</Trans>
</Text>
<Input
placeholder={t("Confirmation code")}
autoComplete="off"
autoFocus
maxLength={8}
required
{...inputProps}
/>
</>
) : (
<>
<Text type="secondary">
<Trans>
Deleting the <strong>{{ workspaceName }}</strong> workspace will
destroy all collections, documents, users, and associated data.
You will be immediately logged out of {{ appName }}.
</Trans>
</Text>
</>
)}
{env.EMAIL_ENABLED && !isWaitingCode ? (
<Button type="submit" onClick={handleRequestDelete} neutral>
{t("Continue")}
</Button>
) : (
<Button
type="submit"
disabled={formState.isSubmitting || !formState.isValid}
danger
>
{formState.isSubmitting
? `${t("Deleting")}`
: t("Delete workspace")}
</Button>
)}
</form>
</Flex>
);
}
export default observer(TeamDelete);
+14 -22
View File
@@ -5,7 +5,6 @@ import User from "~/models/User";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Input from "~/components/Input";
import Notice from "~/components/Notice";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
@@ -47,14 +46,17 @@ function TeamNew({ user }: Props) {
return (
<>
<form onSubmit={handleSubmit}>
<Notice>
<Trans>
Please note that workspaces are completely separate. They can have a
different domain, settings, users, and billing.
</Trans>
</Notice>
<p />
<Text type="secondary">
<Trans
defaults="Your are creating a new workspace using your current account — <em>{{email}}</em>"
values={{
email: user.email,
}}
components={{
em: <strong />,
}}
/>
</Text>
<Flex>
<Input
@@ -67,21 +69,11 @@ function TeamNew({ user }: Props) {
flex
/>
</Flex>
<Text type="secondary">
<Trans
defaults="Your are creating a new workspace using your current account — <em>{{email}}</em>"
values={{
email: user.email,
}}
components={{
em: <strong />,
}}
/>
.{" "}
<Trans>
To create a workspace under another email please sign up from the
homepage
When your new workspace is created, you will be the admin, meaning
you will have the highest level of permissions and the ability to
invite others.
</Trans>
</Text>
+12 -8
View File
@@ -30,7 +30,7 @@ function UserDelete() {
ev.preventDefault();
try {
await auth.requestDeleteUser();
await auth.requestDelete();
setWaitingCode(true);
} catch (error) {
showToast(error.message, {
@@ -71,8 +71,16 @@ function UserDelete() {
enter the code below to permanantly destroy your account.
</Trans>
</Text>
<Text type="secondary">
<Trans
defaults="<em>Note:</em> Signing back in will cause a new account to be automatically reprovisioned."
components={{
em: <strong />,
}}
/>
</Text>
<Input
placeholder={t("Confirmation code")}
placeholder="CODE"
autoComplete="off"
autoFocus
maxLength={8}
@@ -97,14 +105,10 @@ function UserDelete() {
{t("Continue")}
</Button>
) : (
<Button
type="submit"
disabled={formState.isSubmitting || !formState.isValid}
danger
>
<Button type="submit" disabled={formState.isSubmitting} danger>
{formState.isSubmitting
? `${t("Deleting")}`
: t("Delete my account")}
: t("Delete My Account")}
</Button>
)}
</form>
+3 -16
View File
@@ -241,14 +241,13 @@ export default class AuthStore {
}
};
requestDeleteUser = () => client.post(`/users.requestDelete`);
requestDeleteTeam = () => client.post(`/teams.requestDelete`);
@action
requestDelete = () => client.post(`/users.requestDelete`);
@action
deleteUser = async (data: { code: string }) => {
await client.post(`/users.delete`, data);
runInAction("AuthStore#deleteUser", () => {
runInAction("AuthStore#updateUser", () => {
this.user = null;
this.team = null;
this.collaborationToken = null;
@@ -259,18 +258,6 @@ export default class AuthStore {
});
};
@action
deleteTeam = async (data: { code: string }) => {
await client.post(`/teams.delete`, data);
runInAction("AuthStore#deleteTeam", () => {
this.user = null;
this.availableTeams = this.availableTeams?.filter(
(team) => team.id !== this.team?.id
);
this.policies = [];
});
};
@action
updateUser = async (params: {
name?: string;
+1 -2
View File
@@ -1,6 +1,5 @@
import invariant from "invariant";
import lowerFirst from "lodash/lowerFirst";
import orderBy from "lodash/orderBy";
import { lowerFirst, orderBy } from "lodash";
import { observable, action, computed, runInAction } from "mobx";
import { Class } from "utility-types";
import RootStore from "~/stores/RootStore";
+1 -4
View File
@@ -1,8 +1,5 @@
import invariant from "invariant";
import concat from "lodash/concat";
import find from "lodash/find";
import last from "lodash/last";
import sortBy from "lodash/sortBy";
import { concat, find, last, sortBy } from "lodash";
import { computed, action } from "mobx";
import {
CollectionPermission,
+1 -2
View File
@@ -1,6 +1,5 @@
import invariant from "invariant";
import filter from "lodash/filter";
import orderBy from "lodash/orderBy";
import { filter, orderBy } from "lodash";
import { action, runInAction, computed } from "mobx";
import Comment from "~/models/Comment";
import Document from "~/models/Document";
+1 -5
View File
@@ -1,9 +1,5 @@
import invariant from "invariant";
import compact from "lodash/compact";
import filter from "lodash/filter";
import find from "lodash/find";
import omitBy from "lodash/omitBy";
import orderBy from "lodash/orderBy";
import { find, orderBy, filter, compact, omitBy } from "lodash";
import { observable, action, computed, runInAction } from "mobx";
import { DateFilter, NavigationNode, PublicTeam } from "@shared/types";
import { subtractDate } from "@shared/utils/date";
+1 -2
View File
@@ -1,5 +1,4 @@
import filter from "lodash/filter";
import sortBy from "lodash/sortBy";
import { sortBy, filter } from "lodash";
import { computed } from "mobx";
import Event from "~/models/Event";
import BaseStore, { RPCAction } from "./BaseStore";
+1 -1
View File
@@ -1,4 +1,4 @@
import orderBy from "lodash/orderBy";
import { orderBy } from "lodash";
import { computed } from "mobx";
import { FileOperationType } from "@shared/types";
import FileOperation from "~/models/FileOperation";
+1 -1
View File
@@ -1,5 +1,5 @@
import invariant from "invariant";
import filter from "lodash/filter";
import { filter } from "lodash";
import { action, runInAction } from "mobx";
import GroupMembership from "~/models/GroupMembership";
import { PaginationParams } from "~/types";
+1 -1
View File
@@ -1,5 +1,5 @@
import invariant from "invariant";
import filter from "lodash/filter";
import { filter } from "lodash";
import { action, runInAction, computed } from "mobx";
import naturalSort from "@shared/utils/naturalSort";
import Group from "~/models/Group";
+1 -1
View File
@@ -1,4 +1,4 @@
import filter from "lodash/filter";
import { filter } from "lodash";
import { computed } from "mobx";
import { IntegrationService } from "@shared/types";
import naturalSort from "@shared/utils/naturalSort";
+1 -2
View File
@@ -1,6 +1,5 @@
import invariant from "invariant";
import orderBy from "lodash/orderBy";
import sortBy from "lodash/sortBy";
import { orderBy, sortBy } from "lodash";
import { action, computed, runInAction } from "mobx";
import Notification from "~/models/Notification";
import { PaginationParams } from "~/types";
+1 -1
View File
@@ -1,5 +1,5 @@
import invariant from "invariant";
import filter from "lodash/filter";
import { filter } from "lodash";
import { action, runInAction } from "mobx";
import BaseStore, { RPCAction } from "~/stores/BaseStore";
import RootStore from "~/stores/RootStore";
+1 -1
View File
@@ -1,4 +1,4 @@
import uniqBy from "lodash/uniqBy";
import { uniqBy } from "lodash";
import { computed } from "mobx";
import SearchQuery from "~/models/SearchQuery";
import BaseStore, { RPCAction } from "./BaseStore";
+1 -4
View File
@@ -1,8 +1,5 @@
import invariant from "invariant";
import filter from "lodash/filter";
import find from "lodash/find";
import isUndefined from "lodash/isUndefined";
import sortBy from "lodash/sortBy";
import { sortBy, filter, find, isUndefined } from "lodash";
import { action, computed } from "mobx";
import Share from "~/models/Share";
import { client } from "~/utils/ApiClient";
+1 -1
View File
@@ -1,4 +1,4 @@
import orderBy from "lodash/orderBy";
import { orderBy } from "lodash";
import { observable, action, computed } from "mobx";
import { v4 as uuidv4 } from "uuid";
import { Toast, ToastOptions } from "~/types";
+1 -8
View File
@@ -69,9 +69,6 @@ class UiStore {
@observable
multiplayerStatus: ConnectionStatus;
@observable
multiplayerErrorCode?: number;
constructor() {
// Rehydrate
const data: Partial<UiStore> = Storage.get(UI_STORE) || {};
@@ -136,12 +133,8 @@ class UiStore {
};
@action
setMultiplayerStatus = (
status: ConnectionStatus,
errorCode?: number
): void => {
setMultiplayerStatus = (status: ConnectionStatus): void => {
this.multiplayerStatus = status;
this.multiplayerErrorCode = errorCode;
};
@action
+1 -2
View File
@@ -1,6 +1,5 @@
import invariant from "invariant";
import filter from "lodash/filter";
import orderBy from "lodash/orderBy";
import { filter, orderBy } from "lodash";
import { observable, computed, action, runInAction } from "mobx";
import { Role } from "@shared/types";
import User from "~/models/User";
+1 -4
View File
@@ -1,7 +1,4 @@
import filter from "lodash/filter";
import find from "lodash/find";
import orderBy from "lodash/orderBy";
import reduce from "lodash/reduce";
import { reduce, filter, find, orderBy } from "lodash";
import View from "~/models/View";
import BaseStore, { RPCAction } from "./BaseStore";
import RootStore from "./RootStore";
+1 -1
View File
@@ -1,5 +1,5 @@
import { isTouchDevice } from "@shared/utils/browser";
import Desktop from "~/utils/Desktop";
import { isTouchDevice } from "~/utils/browser";
/**
* Returns "hover" on a non-touch device and "active" on a touch device. To
+1 -1
View File
@@ -1,5 +1,5 @@
import retry from "fetch-retry";
import trim from "lodash/trim";
import { trim } from "lodash";
import queryString from "query-string";
import EDITOR_VERSION from "@shared/editor/version";
import stores from "~/stores";
+1 -1
View File
@@ -1,4 +1,4 @@
import { isMac, isWindows } from "@shared/utils/browser";
import { isMac, isWindows } from "./browser";
export default class Desktop {
/**
@@ -1,10 +1,8 @@
const SSR = typeof window === "undefined";
/**
* Returns true if the client is a touch device.
*/
export function isTouchDevice(): boolean {
if (SSR) {
if (typeof window === "undefined") {
return false;
}
return window.matchMedia?.("(hover: none) and (pointer: coarse)")?.matches;
@@ -14,9 +12,6 @@ export function isTouchDevice(): boolean {
* Returns true if the client is running on a Mac.
*/
export function isMac(): boolean {
if (SSR) {
return false;
}
return window.navigator.platform === "MacIntel";
}
@@ -24,9 +19,6 @@ export function isMac(): boolean {
* Returns true if the client is running on Windows.
*/
export function isWindows(): boolean {
if (SSR) {
return false;
}
return window.navigator.platform === "Win32";
}
+1 -1
View File
@@ -7,7 +7,7 @@ import {
format as formatDate,
} from "date-fns";
import { TFunction } from "i18next";
import startCase from "lodash/startCase";
import { startCase } from "lodash";
import {
getCurrentDateAsString,
getCurrentDateTimeAsString,
+1 -1
View File
@@ -1,4 +1,4 @@
import { isMac } from "@shared/utils/browser";
import { isMac } from "~/utils/browser";
export const altDisplay = isMac() ? "⌥" : "Alt";

Some files were not shown because too many files have changed in this diff Show More