mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e9beac59f | |||
| a0f7c76405 |
@@ -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,
|
||||
|
||||
@@ -96,10 +96,6 @@ Or to run migrations on test database:
|
||||
yarn sequelize db:migrate --env test
|
||||
```
|
||||
|
||||
# Activity
|
||||
|
||||

|
||||
|
||||
## License
|
||||
|
||||
Outline is [BSL 1.1 licensed](LICENSE).
|
||||
|
||||
@@ -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,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,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,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";
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 you’re online")}
|
||||
</Centered>
|
||||
)
|
||||
<Centered>
|
||||
<strong>{t("Server connection lost")}</strong>
|
||||
<br />
|
||||
{t("Edits you make will sync once you’re online")}
|
||||
</Centered>
|
||||
}
|
||||
placement="bottom"
|
||||
>
|
||||
|
||||
@@ -151,7 +151,7 @@ const ContextMenu: React.FC<Props> = ({
|
||||
ref={backgroundRef}
|
||||
hiddenScrollbars
|
||||
style={
|
||||
topAnchor && !isMobile
|
||||
topAnchor
|
||||
? {
|
||||
maxHeight,
|
||||
}
|
||||
|
||||
@@ -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,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,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}
|
||||
|
||||
@@ -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,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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function InputSelectPermission(
|
||||
const handleChange = React.useCallback(
|
||||
(value) => {
|
||||
if (value === "no_access") {
|
||||
value = null;
|
||||
value = "";
|
||||
}
|
||||
|
||||
onChange?.(value);
|
||||
|
||||
@@ -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,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,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,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";
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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={() =>
|
||||
|
||||
@@ -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} {
|
||||
|
||||
+37
-52
@@ -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,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";
|
||||
|
||||
@@ -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,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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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";
|
||||
|
||||
@@ -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,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,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,4 +1,4 @@
|
||||
import throttle from "lodash/throttle";
|
||||
import { throttle } from "lodash";
|
||||
import * as React from "react";
|
||||
import { Minute } from "@shared/utils/time";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import noop from "lodash/noop";
|
||||
import { noop } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
type MenuContextType = {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,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,4 +1,4 @@
|
||||
import trim from "lodash/trim";
|
||||
import { trim } from "lodash";
|
||||
import { action, computed, observable, reaction, runInAction } from "mobx";
|
||||
import {
|
||||
CollectionPermission,
|
||||
|
||||
@@ -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,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,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,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";
|
||||
|
||||
@@ -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,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,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,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
@@ -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>
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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,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,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";
|
||||
|
||||
@@ -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> </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,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";
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
import debounce from "lodash/debounce";
|
||||
import { debounce } from "lodash";
|
||||
import { observer } from "mobx-react";
|
||||
import {
|
||||
AcademicCapIcon,
|
||||
|
||||
@@ -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> </p>
|
||||
|
||||
<SettingRow
|
||||
name="delete"
|
||||
label={t("Delete account")}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
|
||||
@@ -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
@@ -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,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,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,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,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,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,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,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,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,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,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,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,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,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,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";
|
||||
|
||||
@@ -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,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,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
@@ -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,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,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
@@ -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,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
Reference in New Issue
Block a user