mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e7ea165b4 | |||
| c68d55f49b | |||
| 7e349c9db1 | |||
| 13b067fb3f | |||
| 41c346d105 | |||
| 4788ab3bd6 | |||
| 5f00b4f744 | |||
| fd600ced09 | |||
| 0047384d70 | |||
| 8bff566c30 | |||
| fce90df3aa | |||
| 28ae1af2a3 | |||
| 9f0534d544 | |||
| 4edfab20fe | |||
| c38e045df2 | |||
| b7bfc4bb1a | |||
| a71ad43c31 | |||
| 199fa5844e | |||
| b466f1c8bb | |||
| 503e4e1f71 | |||
| 2bc52be2cf | |||
| 3ba730943c | |||
| 6828718cf0 | |||
| 9749a53558 | |||
| f4e4992508 | |||
| cf2f0b1b5c | |||
| 4a4ea0e531 | |||
| 8830773acb | |||
| f5d2c7890a | |||
| 434812dbe3 | |||
| ed5671209a | |||
| c32cec7bff |
+1
-1
@@ -30,7 +30,7 @@ REDIS_URL=redis://localhost:6379
|
||||
|
||||
# URL should point to the fully qualified, publicly accessible URL. If using a
|
||||
# proxy the port in URL and PORT may be different.
|
||||
URL=http://localhost:3000
|
||||
URL=https://app.outline.dev:3000
|
||||
PORT=3000
|
||||
|
||||
# See [documentation](docs/SERVICES.md) on running a separate collaboration
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"eslint-plugin-import",
|
||||
"eslint-plugin-node",
|
||||
"eslint-plugin-react",
|
||||
"import"
|
||||
"eslint-plugin-lodash"
|
||||
],
|
||||
"rules": {
|
||||
"eqeqeq": 2,
|
||||
@@ -55,6 +55,7 @@
|
||||
],
|
||||
"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,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
up:
|
||||
docker-compose up -d redis postgres s3
|
||||
yarn install-local-ssl
|
||||
yarn install --pure-lockfile
|
||||
yarn dev:watch
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
BrowserIcon,
|
||||
} from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { isMac } from "@shared/utils/browser";
|
||||
import {
|
||||
developersUrl,
|
||||
changelogUrl,
|
||||
@@ -26,7 +27,6 @@ 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";
|
||||
import flattenDeep from "lodash/flattenDeep";
|
||||
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";
|
||||
import escape from "lodash/escape";
|
||||
import * as React from "react";
|
||||
import { IntegrationService } from "@shared/types";
|
||||
import env from "~/env";
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { sortBy, filter, uniq, isEqual } from "lodash";
|
||||
import filter from "lodash/filter";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import uniq from "lodash/uniq";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -7,6 +7,7 @@ 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 = {
|
||||
@@ -17,16 +18,20 @@ 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,15 +14,48 @@ 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={
|
||||
<Centered>
|
||||
<strong>{t("Server connection lost")}</strong>
|
||||
<br />
|
||||
{t("Edits you make will sync once you’re online")}
|
||||
</Centered>
|
||||
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>
|
||||
)
|
||||
}
|
||||
placement="bottom"
|
||||
>
|
||||
|
||||
@@ -151,7 +151,7 @@ const ContextMenu: React.FC<Props> = ({
|
||||
ref={backgroundRef}
|
||||
hiddenScrollbars
|
||||
style={
|
||||
topAnchor
|
||||
topAnchor && !isMobile
|
||||
? {
|
||||
maxHeight,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import FuzzySearch from "fuzzy-search";
|
||||
import { includes, difference, concat, filter, map, fill } from "lodash";
|
||||
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 { observer } from "mobx-react";
|
||||
import { StarredIcon, DocumentIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sortBy } from "lodash";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { deburr, difference, sortBy } from "lodash";
|
||||
import deburr from "lodash/deburr";
|
||||
import difference from "lodash/difference";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import { observer } from "mobx-react";
|
||||
import { DOMParser as ProsemirrorDOMParser } from "prosemirror-model";
|
||||
import { TextSelection } from "prosemirror-state";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { throttle } from "lodash";
|
||||
import throttle from "lodash/throttle";
|
||||
import { observer } from "mobx-react";
|
||||
import { MenuIcon } from "outline-icons";
|
||||
import { transparentize } from "polished";
|
||||
@@ -6,6 +6,7 @@ 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";
|
||||
@@ -14,7 +15,6 @@ 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";
|
||||
import escapeRegExp from "lodash/escapeRegExp";
|
||||
import * as React from "react";
|
||||
import replace from "string-replace-to-array";
|
||||
import styled from "styled-components";
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function InputSelectPermission(
|
||||
const handleChange = React.useCallback(
|
||||
(value) => {
|
||||
if (value === "no_access") {
|
||||
value = "";
|
||||
value = null;
|
||||
}
|
||||
|
||||
onChange?.(value);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { find } from "lodash";
|
||||
import find from "lodash/find";
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { times } from "lodash";
|
||||
import times from "lodash/times";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Fade from "~/components/Fade";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isEqual } from "lodash";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import { observable, action } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { debounce } from "lodash";
|
||||
import debounce from "lodash/debounce";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { groupBy } from "lodash";
|
||||
import groupBy from "lodash/groupBy";
|
||||
import { observer } from "mobx-react";
|
||||
import { BackIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -292,7 +292,7 @@ const Label = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-height: 4.8em;
|
||||
line-height: 1.6;
|
||||
line-height: 24px;
|
||||
|
||||
* {
|
||||
unicode-bidi: plaintext;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isEqual } from "lodash";
|
||||
import isEqual from "lodash/isEqual";
|
||||
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 : "initial")};
|
||||
text-align: ${(props) => (props.dir ? props.dir : "inherit")};
|
||||
color: ${(props) =>
|
||||
props.type === "secondary"
|
||||
? props.theme.textSecondary
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import invariant from "invariant";
|
||||
import { find } from "lodash";
|
||||
import find from "lodash/find";
|
||||
import { action, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { some } from "lodash";
|
||||
import some from "lodash/some";
|
||||
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 = getImageMenuItems(state, dictionary);
|
||||
items = readOnly ? [] : 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";
|
||||
import capitalize from "lodash/capitalize";
|
||||
import * as React from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import { VisuallyHidden } from "reakit/VisuallyHidden";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useRegisterActions } from "kbar";
|
||||
import { flattenDeep } from "lodash";
|
||||
import flattenDeep from "lodash/flattenDeep";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { actionToKBar } from "~/actions";
|
||||
import { Action } from "~/types";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { find } from "lodash";
|
||||
import find from "lodash/find";
|
||||
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";
|
||||
import throttle from "lodash/throttle";
|
||||
import * as React from "react";
|
||||
import { Minute } from "@shared/utils/time";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { noop } from "lodash";
|
||||
import noop from "lodash/noop";
|
||||
import React from "react";
|
||||
|
||||
type MenuContextType = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { throttle } from "lodash";
|
||||
import throttle from "lodash/throttle";
|
||||
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";
|
||||
import throttle from "lodash/throttle";
|
||||
import { useState, useEffect } from "react";
|
||||
import { supportsPassiveListener } from "~/utils/browser";
|
||||
import { supportsPassiveListener } from "@shared/utils/browser";
|
||||
|
||||
const getPosition = () => ({
|
||||
x: window.pageXOffset,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { pick } from "lodash";
|
||||
import pick from "lodash/pick";
|
||||
import { set, observable } from "mobx";
|
||||
import Logger from "~/utils/Logger";
|
||||
import { getFieldsForModel } from "./decorators/Field";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { trim } from "lodash";
|
||||
import trim from "lodash/trim";
|
||||
import { action, computed, observable, reaction, runInAction } from "mobx";
|
||||
import {
|
||||
CollectionPermission,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { addDays, differenceInDays } from "date-fns";
|
||||
import { floor } from "lodash";
|
||||
import floor from "lodash/floor";
|
||||
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";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { intersection } from "lodash";
|
||||
import intersection from "lodash/intersection";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
@@ -56,6 +56,7 @@ 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";
|
||||
import debounce from "lodash/debounce";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { throttle } from "lodash";
|
||||
import throttle from "lodash/throttle";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { debounce } from "lodash";
|
||||
import debounce from "lodash/debounce";
|
||||
import { action, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { AllSelection } from "prosemirror-state";
|
||||
|
||||
@@ -138,7 +138,7 @@ function DocumentHeader({
|
||||
to={documentEditPath(document)}
|
||||
neutral
|
||||
>
|
||||
{t("Edit")}
|
||||
{isMobile ? null : t("Edit")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Action>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { HocuspocusProvider, WebSocketStatus } from "@hocuspocus/provider";
|
||||
import { throttle } from "lodash";
|
||||
import throttle from "lodash/throttle";
|
||||
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";
|
||||
@@ -16,7 +17,6 @@ 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,13 +135,10 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
|
||||
});
|
||||
|
||||
provider.on("close", (ev: MessageEvent) => {
|
||||
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."
|
||||
)
|
||||
);
|
||||
if ("code" in ev.event) {
|
||||
provider.shouldConnect =
|
||||
ev.event.code !== 1009 && ev.event.code !== 4401;
|
||||
ui.setMultiplayerStatus("disconnected", ev.event.code);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -164,9 +161,11 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
|
||||
);
|
||||
}
|
||||
|
||||
provider.on("status", (ev: ConnectionStatusEvent) =>
|
||||
ui.setMultiplayerStatus(ev.status)
|
||||
);
|
||||
provider.on("status", (ev: ConnectionStatusEvent) => {
|
||||
if (ui.multiplayerStatus !== ev.status) {
|
||||
ui.setMultiplayerStatus(ev.status, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
setRemoteProvider(provider);
|
||||
|
||||
@@ -177,7 +176,7 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
|
||||
provider?.destroy();
|
||||
void localProvider?.destroy();
|
||||
setRemoteProvider(null);
|
||||
ui.setMultiplayerStatus(undefined);
|
||||
ui.setMultiplayerStatus(undefined, undefined);
|
||||
};
|
||||
}, [
|
||||
history,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import invariant from "invariant";
|
||||
import { debounce, isEmpty } from "lodash";
|
||||
import debounce from "lodash/debounce";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
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";
|
||||
import flatten from "lodash/flatten";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { flatten } from "lodash";
|
||||
import flatten from "lodash/flatten";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { debounce } from "lodash";
|
||||
import debounce from "lodash/debounce";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { find } from "lodash";
|
||||
import find from "lodash/find";
|
||||
import { observer } from "mobx-react";
|
||||
import { BackIcon, EmailIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isEqual } from "lodash";
|
||||
import isEqual from "lodash/isEqual";
|
||||
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";
|
||||
import pickBy from "lodash/pickBy";
|
||||
import { observer } from "mobx-react";
|
||||
import { TeamIcon } from "outline-icons";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { find } from "lodash";
|
||||
import find from "lodash/find";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sortBy } from "lodash";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import { observer } from "mobx-react";
|
||||
import { PlusIcon, UserIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { debounce } from "lodash";
|
||||
import debounce from "lodash/debounce";
|
||||
import { observer } from "mobx-react";
|
||||
import {
|
||||
AcademicCapIcon,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { debounce } from "lodash";
|
||||
import debounce from "lodash/debounce";
|
||||
import { observer } from "mobx-react";
|
||||
import { CheckboxIcon, EmailIcon, PadlockIcon } from "outline-icons";
|
||||
import { useState } from "react";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { find } from "lodash";
|
||||
import find from "lodash/find";
|
||||
import { observer } from "mobx-react";
|
||||
import { BuildingBlocksIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
@@ -16,6 +16,7 @@ import SettingRow from "./components/SettingRow";
|
||||
|
||||
type FormData = {
|
||||
drawIoUrl: string;
|
||||
gristUrl: string;
|
||||
};
|
||||
|
||||
function SelfHosted() {
|
||||
@@ -23,11 +24,16 @@ function SelfHosted() {
|
||||
const { t } = useTranslation();
|
||||
const { showToast } = useToasts();
|
||||
|
||||
const integration = find(integrations.orderedData, {
|
||||
const integrationDiagrams = 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,
|
||||
@@ -36,7 +42,8 @@ function SelfHosted() {
|
||||
} = useForm<FormData>({
|
||||
mode: "all",
|
||||
defaultValues: {
|
||||
drawIoUrl: integration?.settings.url,
|
||||
drawIoUrl: integrationDiagrams?.settings.url,
|
||||
gristUrl: integrationGrist?.settings.url,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -47,15 +54,18 @@ function SelfHosted() {
|
||||
}, [integrations]);
|
||||
|
||||
React.useEffect(() => {
|
||||
reset({ drawIoUrl: integration?.settings.url });
|
||||
}, [integration, reset]);
|
||||
reset({
|
||||
drawIoUrl: integrationDiagrams?.settings.url,
|
||||
gristUrl: integrationGrist?.settings.url,
|
||||
});
|
||||
}, [integrationDiagrams, integrationGrist, reset]);
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
async (data: FormData) => {
|
||||
try {
|
||||
if (data.drawIoUrl) {
|
||||
await integrations.save({
|
||||
id: integration?.id,
|
||||
id: integrationDiagrams?.id,
|
||||
type: IntegrationType.Embed,
|
||||
service: IntegrationService.Diagrams,
|
||||
settings: {
|
||||
@@ -63,7 +73,20 @@ function SelfHosted() {
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await integration?.delete();
|
||||
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();
|
||||
}
|
||||
|
||||
showToast(t("Settings saved"), {
|
||||
@@ -75,7 +98,7 @@ function SelfHosted() {
|
||||
});
|
||||
}
|
||||
},
|
||||
[integrations, integration, t, showToast]
|
||||
[integrations, integrationDiagrams, integrationGrist, t, showToast]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -98,6 +121,19 @@ 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";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import { observer } from "mobx-react";
|
||||
import { LinkIcon, WarningIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { compact } from "lodash";
|
||||
import compact from "lodash/compact";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import invariant from "invariant";
|
||||
import { lowerFirst, orderBy } from "lodash";
|
||||
import lowerFirst from "lodash/lowerFirst";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import { observable, action, computed, runInAction } from "mobx";
|
||||
import { Class } from "utility-types";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import invariant from "invariant";
|
||||
import { concat, find, last, sortBy } from "lodash";
|
||||
import concat from "lodash/concat";
|
||||
import find from "lodash/find";
|
||||
import last from "lodash/last";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import { computed, action } from "mobx";
|
||||
import {
|
||||
CollectionPermission,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import invariant from "invariant";
|
||||
import { filter, orderBy } from "lodash";
|
||||
import filter from "lodash/filter";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import { action, runInAction, computed } from "mobx";
|
||||
import Comment from "~/models/Comment";
|
||||
import Document from "~/models/Document";
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import invariant from "invariant";
|
||||
import { find, orderBy, filter, compact, omitBy } from "lodash";
|
||||
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 { observable, action, computed, runInAction } from "mobx";
|
||||
import { DateFilter, NavigationNode, PublicTeam } from "@shared/types";
|
||||
import { subtractDate } from "@shared/utils/date";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { sortBy, filter } from "lodash";
|
||||
import filter from "lodash/filter";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import { computed } from "mobx";
|
||||
import Event from "~/models/Event";
|
||||
import BaseStore, { RPCAction } from "./BaseStore";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { orderBy } from "lodash";
|
||||
import orderBy from "lodash/orderBy";
|
||||
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";
|
||||
import filter from "lodash/filter";
|
||||
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";
|
||||
import filter from "lodash/filter";
|
||||
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";
|
||||
import filter from "lodash/filter";
|
||||
import { computed } from "mobx";
|
||||
import { IntegrationService } from "@shared/types";
|
||||
import naturalSort from "@shared/utils/naturalSort";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import invariant from "invariant";
|
||||
import { orderBy, sortBy } from "lodash";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import sortBy from "lodash/sortBy";
|
||||
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";
|
||||
import filter from "lodash/filter";
|
||||
import { action, runInAction } from "mobx";
|
||||
import BaseStore, { RPCAction } from "~/stores/BaseStore";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uniqBy } from "lodash";
|
||||
import uniqBy from "lodash/uniqBy";
|
||||
import { computed } from "mobx";
|
||||
import SearchQuery from "~/models/SearchQuery";
|
||||
import BaseStore, { RPCAction } from "./BaseStore";
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import invariant from "invariant";
|
||||
import { sortBy, filter, find, isUndefined } from "lodash";
|
||||
import filter from "lodash/filter";
|
||||
import find from "lodash/find";
|
||||
import isUndefined from "lodash/isUndefined";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import { action, computed } from "mobx";
|
||||
import Share from "~/models/Share";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { orderBy } from "lodash";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import { observable, action, computed } from "mobx";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Toast, ToastOptions } from "~/types";
|
||||
|
||||
@@ -69,6 +69,9 @@ class UiStore {
|
||||
@observable
|
||||
multiplayerStatus: ConnectionStatus;
|
||||
|
||||
@observable
|
||||
multiplayerErrorCode?: number;
|
||||
|
||||
constructor() {
|
||||
// Rehydrate
|
||||
const data: Partial<UiStore> = Storage.get(UI_STORE) || {};
|
||||
@@ -133,8 +136,12 @@ class UiStore {
|
||||
};
|
||||
|
||||
@action
|
||||
setMultiplayerStatus = (status: ConnectionStatus): void => {
|
||||
setMultiplayerStatus = (
|
||||
status: ConnectionStatus,
|
||||
errorCode?: number
|
||||
): void => {
|
||||
this.multiplayerStatus = status;
|
||||
this.multiplayerErrorCode = errorCode;
|
||||
};
|
||||
|
||||
@action
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import invariant from "invariant";
|
||||
import { filter, orderBy } from "lodash";
|
||||
import filter from "lodash/filter";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import { observable, computed, action, runInAction } from "mobx";
|
||||
import { Role } from "@shared/types";
|
||||
import User from "~/models/User";
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { reduce, filter, find, orderBy } from "lodash";
|
||||
import filter from "lodash/filter";
|
||||
import find from "lodash/find";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import reduce from "lodash/reduce";
|
||||
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";
|
||||
import trim from "lodash/trim";
|
||||
import queryString from "query-string";
|
||||
import EDITOR_VERSION from "@shared/editor/version";
|
||||
import stores from "~/stores";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isMac, isWindows } from "./browser";
|
||||
import { isMac, isWindows } from "@shared/utils/browser";
|
||||
|
||||
export default class Desktop {
|
||||
/**
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import {
|
||||
format as formatDate,
|
||||
} from "date-fns";
|
||||
import { TFunction } from "i18next";
|
||||
import { startCase } from "lodash";
|
||||
import startCase from "lodash/startCase";
|
||||
import {
|
||||
getCurrentDateAsString,
|
||||
getCurrentDateTimeAsString,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isMac } from "~/utils/browser";
|
||||
import { isMac } from "@shared/utils/browser";
|
||||
|
||||
export const altDisplay = isMac() ? "⌥" : "Alt";
|
||||
|
||||
|
||||
+10
-9
@@ -16,6 +16,7 @@
|
||||
"lint": "eslint app server shared plugins",
|
||||
"prepare": "husky install",
|
||||
"postinstall": "yarn patch-package",
|
||||
"install-local-ssl": "node ./server/scripts/install-local-ssl.js",
|
||||
"heroku-postbuild": "yarn build && yarn db:migrate",
|
||||
"db:create-migration": "sequelize migration:create",
|
||||
"db:create": "sequelize db:create",
|
||||
@@ -93,7 +94,7 @@
|
||||
"date-fns": "^2.30.0",
|
||||
"dd-trace": "^3.32.1",
|
||||
"dotenv": "^4.0.0",
|
||||
"email-providers": "^1.13.1",
|
||||
"email-providers": "^1.14.0",
|
||||
"emoji-regex": "^10.2.1",
|
||||
"es6-error": "^4.1.1",
|
||||
"fetch-retry": "^5.0.5",
|
||||
@@ -140,7 +141,7 @@
|
||||
"mobx-utils": "^4.0.1",
|
||||
"natural-sort": "^1.0.0",
|
||||
"node-fetch": "2.6.12",
|
||||
"nodemailer": "^6.9.1",
|
||||
"nodemailer": "^6.9.4",
|
||||
"outline-icons": "^2.3.0",
|
||||
"oy-vey": "^0.12.0",
|
||||
"passport": "^0.6.0",
|
||||
@@ -185,7 +186,7 @@
|
||||
"react-table": "^7.8.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.17",
|
||||
"react-waypoint": "^10.3.0",
|
||||
"react-window": "^1.8.7",
|
||||
"react-window": "^1.8.9",
|
||||
"reakit": "^1.3.11",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"refractor": "^3.6.0",
|
||||
@@ -215,10 +216,10 @@
|
||||
"utility-types": "^3.10.0",
|
||||
"uuid": "^8.3.2",
|
||||
"validator": "13.9.0",
|
||||
"vite": "^4.1.5",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-pwa": "^0.14.4",
|
||||
"winston": "^3.10.0",
|
||||
"ws": "^7.5.3",
|
||||
"ws": "^7.5.9",
|
||||
"y-indexeddb": "^9.0.11",
|
||||
"y-protocols": "^1.0.5",
|
||||
"yjs": "^13.6.1",
|
||||
@@ -228,7 +229,6 @@
|
||||
"@babel/cli": "^7.21.5",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@getoutline/jest-runner-serial": "^2.0.0",
|
||||
"@optimize-lodash/rollup-plugin": "4.0.3",
|
||||
"@relative-ci/agent": "^4.1.3",
|
||||
"@types/addressparser": "^1.0.1",
|
||||
"@types/body-scroll-lock": "^3.1.0",
|
||||
@@ -264,7 +264,7 @@
|
||||
"@types/natural-sort": "^0.0.21",
|
||||
"@types/node": "18.0.6",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/nodemailer": "^6.4.7",
|
||||
"@types/nodemailer": "^6.4.9",
|
||||
"@types/passport-oauth2": "^1.4.11",
|
||||
"@types/quoted-printable": "^1.0.0",
|
||||
"@types/randomstring": "^1.1.8",
|
||||
@@ -290,7 +290,7 @@
|
||||
"@types/turndown": "^5.0.1",
|
||||
"@types/utf8": "^3.0.1",
|
||||
"@types/validator": "^13.7.17",
|
||||
"@typescript-eslint/eslint-plugin": "^5.61.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^29.6.1",
|
||||
@@ -307,6 +307,7 @@
|
||||
"eslint-plugin-es": "^4.1.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.1.0",
|
||||
"eslint-plugin-lodash": "^7.4.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.20.0",
|
||||
@@ -339,5 +340,5 @@
|
||||
"qs": "6.9.7",
|
||||
"rollup": "^3.14.0"
|
||||
},
|
||||
"version": "0.70.2"
|
||||
"version": "0.71.0"
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ describe("email", () => {
|
||||
email: user.email,
|
||||
},
|
||||
headers: {
|
||||
host: "example.localoutline.com",
|
||||
host: "example.outline.dev",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
@@ -71,7 +71,7 @@ describe("email", () => {
|
||||
});
|
||||
|
||||
it("should not send email when user is on another subdomain but respond with success", async () => {
|
||||
env.URL = sharedEnv.URL = "http://localoutline.com";
|
||||
env.URL = sharedEnv.URL = "https://app.outline.dev";
|
||||
env.SUBDOMAINS_ENABLED = sharedEnv.SUBDOMAINS_ENABLED = true;
|
||||
env.DEPLOYMENT = "hosted";
|
||||
|
||||
@@ -85,7 +85,7 @@ describe("email", () => {
|
||||
email: user.email,
|
||||
},
|
||||
headers: {
|
||||
host: "example.localoutline.com",
|
||||
host: "example.outline.dev",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -109,7 +109,7 @@ describe("email", () => {
|
||||
email: user.email,
|
||||
},
|
||||
headers: {
|
||||
host: "example.localoutline.com",
|
||||
host: "example.outline.dev",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
@@ -129,7 +129,7 @@ describe("email", () => {
|
||||
email: "user@example.com",
|
||||
},
|
||||
headers: {
|
||||
host: "example.localoutline.com",
|
||||
host: "example.outline.dev",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
@@ -141,7 +141,7 @@ describe("email", () => {
|
||||
describe("with multiple users matching email", () => {
|
||||
it("should default to current subdomain with SSO", async () => {
|
||||
const spy = jest.spyOn(SigninEmail.prototype, "schedule");
|
||||
env.URL = sharedEnv.URL = "http://localoutline.com";
|
||||
env.URL = sharedEnv.URL = "https://app.outline.dev";
|
||||
env.SUBDOMAINS_ENABLED = sharedEnv.SUBDOMAINS_ENABLED = true;
|
||||
const email = "sso-user@example.org";
|
||||
const team = await buildTeam({
|
||||
@@ -159,7 +159,7 @@ describe("email", () => {
|
||||
email,
|
||||
},
|
||||
headers: {
|
||||
host: "example.localoutline.com",
|
||||
host: "example.outline.dev",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
@@ -171,7 +171,7 @@ describe("email", () => {
|
||||
|
||||
it("should default to current subdomain with guest email", async () => {
|
||||
const spy = jest.spyOn(SigninEmail.prototype, "schedule");
|
||||
env.URL = sharedEnv.URL = "http://localoutline.com";
|
||||
env.URL = sharedEnv.URL = "https://app.outline.dev";
|
||||
env.SUBDOMAINS_ENABLED = sharedEnv.SUBDOMAINS_ENABLED = true;
|
||||
const email = "guest-user@example.org";
|
||||
const team = await buildTeam({
|
||||
@@ -189,7 +189,7 @@ describe("email", () => {
|
||||
email,
|
||||
},
|
||||
headers: {
|
||||
host: "example.localoutline.com",
|
||||
host: "example.outline.dev",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import passport from "@outlinewiki/koa-passport";
|
||||
import type { Context } from "koa";
|
||||
import Router from "koa-router";
|
||||
import { capitalize } from "lodash";
|
||||
import capitalize from "lodash/capitalize";
|
||||
import { Profile } from "passport";
|
||||
import { Strategy as GoogleStrategy } from "passport-google-oauth2";
|
||||
import { slugifyDomain } from "@shared/utils/domains";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import passport from "@outlinewiki/koa-passport";
|
||||
import type { Context } from "koa";
|
||||
import Router from "koa-router";
|
||||
import { get } from "lodash";
|
||||
import get from "lodash/get";
|
||||
import { Strategy } from "passport-oauth2";
|
||||
import { slugifyDomain } from "@shared/utils/domains";
|
||||
import accountProvisioner from "@server/commands/accountProvisioner";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { find } from "lodash";
|
||||
import find from "lodash/find";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uniq } from "lodash";
|
||||
import uniq from "lodash/uniq";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { t } from "i18next";
|
||||
import Router from "koa-router";
|
||||
import { escapeRegExp } from "lodash";
|
||||
import escapeRegExp from "lodash/escapeRegExp";
|
||||
import { Op } from "sequelize";
|
||||
import { IntegrationService } from "@shared/types";
|
||||
import env from "@server/env";
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { isEqual, filter, includes } from "lodash";
|
||||
import filter from "lodash/filter";
|
||||
import includes from "lodash/includes";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import randomstring from "randomstring";
|
||||
import * as React from "react";
|
||||
import { useEffect } from "react";
|
||||
@@ -269,9 +271,7 @@ function WebhookSubscriptionForm({ handleSubmit, webhookSubscription }: Props) {
|
||||
needs to function.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<EventCheckbox label={t("All events")} value="*" />
|
||||
|
||||
<FieldSet disabled={isAllEventSelected}>
|
||||
<GroupGrid isMobile={isMobile}>
|
||||
{Object.entries(WEBHOOK_EVENTS)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Router from "koa-router";
|
||||
import { compact, isEmpty } from "lodash";
|
||||
import compact from "lodash/compact";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import { ValidationError } from "@server/errors";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import { WebhookSubscription, Event } from "@server/models";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import fetch from "fetch-with-proxy";
|
||||
import { FetchError } from "node-fetch";
|
||||
import fetch, { FetchError } from "node-fetch";
|
||||
import { useAgent } from "request-filtering-agent";
|
||||
import { Op } from "sequelize";
|
||||
import WebhookDisabledEmail from "@server/emails/templates/WebhookDisabledEmail";
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
@@ -0,0 +1,4 @@
|
||||
export const TooManyConnections = {
|
||||
code: 4503,
|
||||
reason: "Too Many Connections",
|
||||
};
|
||||
@@ -6,28 +6,36 @@ import {
|
||||
import env from "@server/env";
|
||||
import Logger from "@server/logging/Logger";
|
||||
import { trace } from "@server/logging/tracing";
|
||||
import { TooManyConnections } from "./CloseEvents";
|
||||
|
||||
@trace()
|
||||
export class ConnectionLimitExtension implements Extension {
|
||||
/**
|
||||
* Map of documentId -> connection count
|
||||
*/
|
||||
connectionsByDocument: Map<string, number> = new Map();
|
||||
connectionsByDocument: Map<string, Set<string>> = new Map();
|
||||
|
||||
/**
|
||||
* onDisconnect hook
|
||||
* @param data The disconnect payload
|
||||
*/
|
||||
onDisconnect(data: onDisconnectPayload) {
|
||||
const { documentName } = data;
|
||||
const { documentName, socketId } = data;
|
||||
|
||||
const currConnections = this.connectionsByDocument.get(documentName) || 0;
|
||||
const newConnections = currConnections - 1;
|
||||
this.connectionsByDocument.set(documentName, newConnections);
|
||||
const connections = this.connectionsByDocument.get(documentName);
|
||||
if (connections) {
|
||||
connections.delete(socketId);
|
||||
|
||||
if (connections.size === 0) {
|
||||
this.connectionsByDocument.delete(documentName);
|
||||
} else {
|
||||
this.connectionsByDocument.set(documentName, connections);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug(
|
||||
"multiplayer",
|
||||
`${newConnections} connections to "${documentName}"`
|
||||
`${connections?.size} connections to "${documentName}"`
|
||||
);
|
||||
|
||||
return Promise.resolve();
|
||||
@@ -40,23 +48,24 @@ export class ConnectionLimitExtension implements Extension {
|
||||
onConnect(data: onConnectPayload) {
|
||||
const { documentName } = data;
|
||||
|
||||
const currConnections = this.connectionsByDocument.get(documentName) || 0;
|
||||
if (currConnections >= env.COLLABORATION_MAX_CLIENTS_PER_DOCUMENT) {
|
||||
const connections =
|
||||
this.connectionsByDocument.get(documentName) || new Set();
|
||||
if (connections?.size >= env.COLLABORATION_MAX_CLIENTS_PER_DOCUMENT) {
|
||||
Logger.info(
|
||||
"multiplayer",
|
||||
`Rejected connection to "${documentName}" because it has reached the maximum number of connections`
|
||||
);
|
||||
|
||||
// Rejecting the promise will cause the connection to be dropped.
|
||||
return Promise.reject();
|
||||
return Promise.reject(TooManyConnections);
|
||||
}
|
||||
|
||||
const newConnections = currConnections + 1;
|
||||
this.connectionsByDocument.set(documentName, newConnections);
|
||||
connections.add(data.socketId);
|
||||
this.connectionsByDocument.set(documentName, connections);
|
||||
|
||||
Logger.debug(
|
||||
"multiplayer",
|
||||
`${newConnections} connections to "${documentName}"`
|
||||
`${connections.size} connections to "${documentName}"`
|
||||
);
|
||||
|
||||
return Promise.resolve();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { yDocToProsemirrorJSON } from "@getoutline/y-prosemirror";
|
||||
import { uniq } from "lodash";
|
||||
import uniq from "lodash/uniq";
|
||||
import { Node } from "prosemirror-model";
|
||||
import * as Y from "yjs";
|
||||
import { sequelize } from "@server/database/sequelize";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import path from "path";
|
||||
import emojiRegex from "emoji-regex";
|
||||
import { truncate } from "lodash";
|
||||
import escapeRegExp from "lodash/escapeRegExp";
|
||||
import truncate from "lodash/truncate";
|
||||
import mammoth from "mammoth";
|
||||
import quotedPrintable from "quoted-printable";
|
||||
import { Transaction } from "sequelize";
|
||||
@@ -221,7 +222,10 @@ async function documentImporter({
|
||||
ip,
|
||||
transaction,
|
||||
});
|
||||
text = text.replace(uri, attachment.redirectUrl);
|
||||
text = text.replace(
|
||||
new RegExp(escapeRegExp(uri), "g"),
|
||||
attachment.redirectUrl
|
||||
);
|
||||
}
|
||||
|
||||
// It's better to truncate particularly long titles than fail the import
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uniq } from "lodash";
|
||||
import uniq from "lodash/uniq";
|
||||
import { QueryTypes } from "sequelize";
|
||||
import { sequelize } from "@server/database/sequelize";
|
||||
import Logger from "@server/logging/Logger";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isUndefined } from "lodash";
|
||||
import isUndefined from "lodash/isUndefined";
|
||||
import { Transaction } from "sequelize";
|
||||
import { Event, Notification } from "@server/models";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { has } from "lodash";
|
||||
import has from "lodash/has";
|
||||
import { Transaction } from "sequelize";
|
||||
import { TeamPreference } from "@shared/types";
|
||||
import env from "@server/env";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uniqBy } from "lodash";
|
||||
import uniqBy from "lodash/uniqBy";
|
||||
import { Role } from "@shared/types";
|
||||
import InviteEmail from "@server/emails/templates/InviteEmail";
|
||||
import env from "@server/env";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user