diff --git a/.env.sample b/.env.sample
index 98c24159..640e03fc 100644
--- a/.env.sample
+++ b/.env.sample
@@ -29,6 +29,10 @@ REDIS_URL=redis://localhost:6479
URL=http://localhost:3000
PORT=3000
+# ALPHA – See [documentation](docs/SERVICES.md) on running the alpha version of
+# the collaboration server.
+COLLABORATION_URL=
+
# To support uploading of images for avatars and document attachments an
# s3-compatible storage must be provided. AWS S3 is recommended for redundency
# however if you want to keep all file storage local an alternative such as
diff --git a/.flowconfig b/.flowconfig
index 0a37806b..7e630b56 100644
--- a/.flowconfig
+++ b/.flowconfig
@@ -11,6 +11,11 @@
.*/node_modules/react-side-effect/.*
.*/node_modules/fbjs/.*
.*/node_modules/config-chain/.*
+.*/node_modules/yjs/.*
+.*/node_modules/y-prosemirror/.*
+.*/node_modules/y-protocols/.*
+.*/node_modules/y-indexeddb/.*
+.*/node_modules/lib0/.*
.*/server/scripts/.*
*.test.js
diff --git a/Makefile b/Makefile
index a10b9261..dc597047 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ up:
docker-compose up -d redis postgres s3
yarn install --pure-lockfile
yarn sequelize db:migrate
- yarn dev
+ yarn dev:watch
build:
docker-compose build --pull outline
diff --git a/Procfile b/Procfile
index 0795ef08..0de779fb 100644
--- a/Procfile
+++ b/Procfile
@@ -1,2 +1,2 @@
-web: node ./build/server/index.js --services=web,websockets
-worker: node ./build/server/index.js --services=worker
\ No newline at end of file
+web: yarn start --services=web,websockets
+worker: yarn start --services=worker
diff --git a/README.md b/README.md
index d9b0b8fe..9a9b2112 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,3 @@
-
-
@@ -30,7 +28,6 @@ Outline requires the following dependencies:
- AWS S3 bucket or compatible API for file storage
- Slack or Google developer application for authentication
-
## Self-Hosted Production
### Docker
@@ -41,16 +38,20 @@ For a manual self-hosted production installation these are the recommended steps
1. Download the latest official Docker image, new releases are available around the middle of every month:
`docker pull outlinewiki/outline`
+
1. Using the [.env.sample](.env.sample) as a reference, set the required variables in your production environment. You can export the environment variables directly, or create a `.env` file and pass it to the docker image like so:
`docker run --env-file=.env outlinewiki/outline`
+
1. Setup the database with `yarn db:migrate`. Production assumes an SSL connection to the database by default, if
-Postgres is on the same machine and is not SSL you can migrate with `yarn db:migrate --env=production-ssl-disabled`, for example:
+ Postgres is on the same machine and is not SSL you can migrate with `yarn db:migrate --env=production-ssl-disabled`, for example:
`docker run --rm outlinewiki/outline yarn db:migrate`
+
1. Start the container:
`docker run outlinewiki/outline`
+
1. Visit http://you_server_ip:3000 and you should be able to see Outline page
> Port number can be changed using the `PORT` environment variable
@@ -79,29 +80,27 @@ If you're running Outline by cloning this repository, run the following command
yarn run upgrade
```
-
## Local Development
For contributing features and fixes you can quickly get an environment running using Docker by following these steps:
1. Install these dependencies if you don't already have them
- 1. [Docker for Desktop](https://www.docker.com)
- 1. [Node.js](https://nodejs.org/) (v12 LTS preferred)
- 1. [Yarn](https://yarnpkg.com)
+ 1. [Docker for Desktop](https://www.docker.com)
+ 1. [Node.js](https://nodejs.org/) (v12 LTS preferred)
+ 1. [Yarn](https://yarnpkg.com)
1. Clone this repo
1. Register a Slack app at https://api.slack.com/apps
1. Copy the file `.env.sample` to `.env`
1. Fill out the following fields:
- 1. `SECRET_KEY` (follow instructions in the comments at the top of `.env`)
- 1. `SLACK_KEY` (this is called "Client ID" in Slack admin)
- 1. `SLACK_SECRET` (this is called "Client Secret" in Slack admin)
-1. Configure your Slack app's Oauth & Permissions settings
- 1. Slack recently prevented the use of `http` protocol for localhost. For local development, you can use a tool like [ngrok](https://ngrok.com) or a package like `mkcert`. ([How to use HTTPS for local development](https://web.dev/how-to-use-local-https/))
- 1. Add `https://my_ngrok_address/auth/slack.callback` as an Oauth redirect URL
- 1. Ensure that the bot token scope contains at least `users:read`
+ 1. `SECRET_KEY` (follow instructions in the comments at the top of `.env`)
+ 1. `SLACK_KEY` (this is called "Client ID" in Slack admin)
+ 1. `SLACK_SECRET` (this is called "Client Secret" in Slack admin)
+1. Configure your Slack app's Oauth & Permissions settings
+ 1. Slack recently prevented the use of `http` protocol for localhost. For local development, you can use a tool like [ngrok](https://ngrok.com) or a package like `mkcert`. ([How to use HTTPS for local development](https://web.dev/how-to-use-local-https/))
+ 1. Add `https://my_ngrok_address/auth/slack.callback` as an Oauth redirect URL
+ 1. Ensure that the bot token scope contains at least `users:read`
1. Run `make up`. This will download dependencies, build and launch a development version of Outline
-
# Contributing
Outline is built and maintained by a small team – we'd love your help to fix bugs and add features!
@@ -110,18 +109,16 @@ Before submitting a pull request please let the core team know by creating or co
If you’re looking for ways to get started, here's a list of ways to help us improve Outline:
-* [Translation](TRANSLATION.md) into other languages
-* Issues with [`good first issue`](https://github.com/outline/outline/labels/good%20first%20issue) label
-* Performance improvements, both on server and frontend
-* Developer happiness and documentation
-* Bugs and other issues listed on GitHub
-
+- [Translation](docs/TRANSLATION.md) into other languages
+- Issues with [`good first issue`](https://github.com/outline/outline/labels/good%20first%20issue) label
+- Performance improvements, both on server and frontend
+- Developer happiness and documentation
+- Bugs and other issues listed on GitHub
## Architecture
If you're interested in contributing or learning more about the Outline codebase
-please refer to the [architecture document](ARCHITECTURE.md) first for a high level overview of how the application is put together.
-
+please refer to the [architecture document](docs/ARCHITECTURE.md) first for a high level overview of how the application is put together.
## Debugging
@@ -145,7 +142,7 @@ make test
make watch
```
-Once the test database is created with `make test` you may individually run
+Once the test database is created with `make test` you may individually run
frontend and backend tests directly.
```shell
diff --git a/app/components/ConnectionStatus.js b/app/components/ConnectionStatus.js
new file mode 100644
index 00000000..99fbf05a
--- /dev/null
+++ b/app/components/ConnectionStatus.js
@@ -0,0 +1,59 @@
+// @flow
+import { observer } from "mobx-react";
+import { DisconnectedIcon } from "outline-icons";
+import * as React from "react";
+import { useTranslation } from "react-i18next";
+import styled, { useTheme } from "styled-components";
+import breakpoint from "styled-components-breakpoint";
+import Fade from "components/Fade";
+import NudeButton from "components/NudeButton";
+import Tooltip from "components/Tooltip";
+import useStores from "hooks/useStores";
+
+function ConnectionStatus() {
+ const { ui } = useStores();
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ return ui.multiplayerStatus === "connecting" ||
+ ui.multiplayerStatus === "disconnected" ? (
+
+ {t("Server connection lost")}
+
+ {t("Edits you make will sync once you’re online")}
+
+ }
+ placement="bottom"
+ >
+
+
+
+
+
+
+ ) : null;
+}
+
+const Button = styled(NudeButton)`
+ display: none;
+ position: fixed;
+ bottom: 0;
+ right: 32px;
+ margin: 24px;
+
+ ${breakpoint("tablet")`
+ display: block;
+ `};
+
+ @media print {
+ display: none;
+ }
+`;
+
+const Centered = styled.div`
+ text-align: center;
+`;
+
+export default observer(ConnectionStatus);
diff --git a/app/components/Editor.js b/app/components/Editor.js
index 562f3c80..eb948094 100644
--- a/app/components/Editor.js
+++ b/app/components/Editor.js
@@ -3,12 +3,13 @@ import { lighten } from "polished";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { withRouter, type RouterHistory } from "react-router-dom";
+import { Extension } from "rich-markdown-editor";
import styled, { withTheme } from "styled-components";
+import embeds from "shared/embeds";
import { light } from "shared/theme";
import UiStore from "stores/UiStore";
import ErrorBoundary from "components/ErrorBoundary";
import Tooltip from "components/Tooltip";
-import embeds from "../embeds";
import useMediaQuery from "hooks/useMediaQuery";
import useToasts from "hooks/useToasts";
import { type Theme } from "types";
@@ -30,6 +31,8 @@ export type Props = {|
grow?: boolean,
disableEmbeds?: boolean,
ui?: UiStore,
+ style?: Object,
+ extensions?: Extension[],
shareId?: ?string,
autoFocus?: boolean,
template?: boolean,
@@ -246,6 +249,50 @@ const StyledEditor = styled(RichMarkdownEditor)`
}
}
}
+
+ .ProseMirror {
+ .ProseMirror-yjs-cursor {
+ position: relative;
+ margin-left: -1px;
+ margin-right: -1px;
+ border-left: 1px solid black;
+ border-right: 1px solid black;
+ height: 1em;
+ word-break: normal;
+ &:after {
+ content: "";
+ display: block;
+ position: absolute;
+ left: -8px;
+ right: -8px;
+ top: 0;
+ bottom: 0;
+ }
+ > div {
+ opacity: 0;
+ position: absolute;
+ top: -1.8em;
+ font-size: 13px;
+ background-color: rgb(250, 129, 0);
+ font-style: normal;
+ line-height: normal;
+ user-select: none;
+ white-space: nowrap;
+ color: white;
+ padding: 2px 6px;
+ font-weight: 500;
+ border-radius: 4px;
+ pointer-events: none;
+ left: -1px;
+ }
+ &:hover {
+ > div {
+ opacity: 1;
+ transition: opacity 100ms ease-in-out;
+ }
+ }
+ }
+ }
`;
const EditorTooltip = ({ children, ...props }) => (
diff --git a/app/components/Header.js b/app/components/Header.js
index 828fb431..5fcb6747 100644
--- a/app/components/Header.js
+++ b/app/components/Header.js
@@ -36,7 +36,7 @@ function Header({ breadcrumb, title, actions }: Props) {
}, []);
return (
-
+
{breadcrumb ? {breadcrumb} : null}
{isScrolled ? (
@@ -95,7 +95,7 @@ const Wrapper = styled(Flex)`
}
${breakpoint("tablet")`
- padding: ${(props) => (props.isCompact ? "12px" : `24px 24px 0`)};
+ padding: 16px 16px 0;
justify-content: "center";
`};
`;
diff --git a/app/components/PageTitle.js b/app/components/PageTitle.js
index 435d1e5d..b33a0f1c 100644
--- a/app/components/PageTitle.js
+++ b/app/components/PageTitle.js
@@ -2,8 +2,8 @@
import { observer } from "mobx-react";
import * as React from "react";
import { Helmet } from "react-helmet";
+import { cdnPath } from "../../shared/utils/urls";
import useStores from "hooks/useStores";
-import { cdnPath } from "utils/urls";
type Props = {|
title: string,
diff --git a/app/components/PlaceholderDocument.js b/app/components/PlaceholderDocument.js
index d3f93d07..a99b76d5 100644
--- a/app/components/PlaceholderDocument.js
+++ b/app/components/PlaceholderDocument.js
@@ -6,18 +6,45 @@ import Fade from "components/Fade";
import Flex from "components/Flex";
import PlaceholderText from "components/PlaceholderText";
-export default function PlaceholderDocument(props: Object) {
+export default function PlaceholderDocument({
+ includeTitle,
+ delay,
+}: {
+ includeTitle?: boolean,
+ delay?: number,
+}) {
+ const content = (
+ <>
+
+
+
+ >
+ );
+
+ if (includeTitle === false) {
+ return (
+
+
+
+ {content}
+
+
+
+ );
+ }
+
return (
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ {content}
+
+
);
diff --git a/app/components/Sidebar/components/TeamButton.js b/app/components/Sidebar/components/TeamButton.js
index 04738a47..96418763 100644
--- a/app/components/Sidebar/components/TeamButton.js
+++ b/app/components/Sidebar/components/TeamButton.js
@@ -1,4 +1,5 @@
// @flow
+import { observer } from "mobx-react";
import { ExpandedIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
@@ -84,4 +85,4 @@ const Header = styled.button`
}
`;
-export default TeamButton;
+export default observer(TeamButton);
diff --git a/app/hooks/useCurrentToken.js b/app/hooks/useCurrentToken.js
new file mode 100644
index 00000000..bdcac380
--- /dev/null
+++ b/app/hooks/useCurrentToken.js
@@ -0,0 +1,9 @@
+// @flow
+import invariant from "invariant";
+import useStores from "./useStores";
+
+export default function useCurrentToken() {
+ const { auth } = useStores();
+ invariant(auth.token, "token is required");
+ return auth.token;
+}
diff --git a/app/menus/AccountMenu.js b/app/menus/AccountMenu.js
index 59f59e42..a6dd053c 100644
--- a/app/menus/AccountMenu.js
+++ b/app/menus/AccountMenu.js
@@ -1,6 +1,6 @@
// @flow
import { observer } from "mobx-react";
-import { MoonIcon, SunIcon } from "outline-icons";
+import { MoonIcon, SunIcon, TrashIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { MenuButton, useMenuState } from "reakit/Menu";
@@ -16,11 +16,14 @@ import KeyboardShortcuts from "scenes/KeyboardShortcuts";
import ContextMenu from "components/ContextMenu";
import Template from "components/ContextMenu/Template";
import Guide from "components/Guide";
+import env from "env";
import useBoolean from "hooks/useBoolean";
import useCurrentTeam from "hooks/useCurrentTeam";
import usePrevious from "hooks/usePrevious";
import useSessions from "hooks/useSessions";
import useStores from "hooks/useStores";
+import useToasts from "hooks/useToasts";
+import { deleteAllDatabases } from "utils/developer";
type Props = {|
children: (props: any) => React.Node,
@@ -33,11 +36,13 @@ function AccountMenu(props: Props) {
placement: "bottom-start",
modal: true,
});
+ const { showToast } = useToasts();
const { auth, ui } = useStores();
const { theme, resolvedTheme } = ui;
const team = useCurrentTeam();
const previousTheme = usePrevious(theme);
const { t } = useTranslation();
+ const [includeAlt, setIncludeAlt] = React.useState(false);
const [
keyboardShortcutsOpen,
handleKeyboardShortcutsOpen,
@@ -50,6 +55,16 @@ function AccountMenu(props: Props) {
}
}, [menu, theme, previousTheme]);
+ const handleDeleteAllDatabases = React.useCallback(async () => {
+ await deleteAllDatabases();
+ showToast("IndexedDB cache deleted");
+ menu.hide();
+ }, [showToast, menu]);
+
+ const handleOpenMenu = React.useCallback((event) => {
+ setIncludeAlt(event.altKey);
+ }, []);
+
const items = React.useMemo(() => {
const otherSessions = sessions.filter(
(session) => session.teamId !== team.id && session.url !== team.url
@@ -83,6 +98,20 @@ function AccountMenu(props: Props) {
title: t("Report a bug"),
href: githubIssuesUrl(),
},
+ ...(includeAlt || env.ENVIRONMENT === "development"
+ ? [
+ {
+ title: t("Development"),
+ items: [
+ {
+ title: "Delete IndexedDB cache",
+ icon: ,
+ onClick: handleDeleteAllDatabases,
+ },
+ ],
+ },
+ ]
+ : []),
{
title: t("Appearance"),
icon: resolvedTheme === "light" ? : ,
@@ -130,7 +159,9 @@ function AccountMenu(props: Props) {
team.url,
sessions,
handleKeyboardShortcutsOpen,
+ handleDeleteAllDatabases,
resolvedTheme,
+ includeAlt,
theme,
t,
ui,
@@ -145,7 +176,9 @@ function AccountMenu(props: Props) {
>
- {props.children}
+
+ {props.children}
+
diff --git a/app/menus/RevisionMenu.js b/app/menus/RevisionMenu.js
index 5e680321..ea4c47f4 100644
--- a/app/menus/RevisionMenu.js
+++ b/app/menus/RevisionMenu.js
@@ -12,6 +12,7 @@ import OverflowMenuButton from "components/ContextMenu/OverflowMenuButton";
import Separator from "components/ContextMenu/Separator";
import CopyToClipboard from "components/CopyToClipboard";
import MenuIconWrapper from "components/MenuIconWrapper";
+import useCurrentTeam from "hooks/useCurrentTeam";
import useToasts from "hooks/useToasts";
import { documentHistoryUrl } from "utils/routeHelpers";
@@ -23,6 +24,7 @@ type Props = {|
function RevisionMenu({ document, revisionId, className }: Props) {
const { showToast } = useToasts();
+ const team = useCurrentTeam();
const menu = useMenuState({ modal: true });
const { t } = useTranslation();
const history = useHistory();
@@ -55,7 +57,11 @@ function RevisionMenu({ document, revisionId, className }: Props) {
{...menu}
/>
-
+
diff --git a/app/menus/TemplatesMenu.js b/app/menus/TemplatesMenu.js
index 311530fc..a3f2cf92 100644
--- a/app/menus/TemplatesMenu.js
+++ b/app/menus/TemplatesMenu.js
@@ -14,9 +14,10 @@ import useStores from "hooks/useStores";
type Props = {|
document: Document,
+ onSelectTemplate: (template: Document) => void,
|};
-function TemplatesMenu({ document }: Props) {
+function TemplatesMenu({ onSelectTemplate, document }: Props) {
const menu = useMenuState({ modal: true });
const { documents } = useStores();
const { t } = useTranslation();
@@ -36,7 +37,7 @@ function TemplatesMenu({ document }: Props) {
const renderTemplate = (template) => (
document.updateFromTemplate(template)}
+ onClick={() => onSelectTemplate(template)}
icon={ }
{...menu}
>
diff --git a/app/models/Document.js b/app/models/Document.js
index 93286dca..262c24cf 100644
--- a/app/models/Document.js
+++ b/app/models/Document.js
@@ -10,17 +10,16 @@ import BaseModel from "models/BaseModel";
import User from "models/User";
import View from "./View";
-type SaveOptions = {
+type SaveOptions = {|
publish?: boolean,
done?: boolean,
autosave?: boolean,
lastRevision?: number,
-};
+|};
export default class Document extends BaseModel {
@observable isSaving: boolean = false;
@observable embedsDisabled: boolean = false;
- @observable injectTemplate: boolean = false;
@observable lastViewedAt: ?string;
store: DocumentsStore;
@@ -254,15 +253,28 @@ export default class Document extends BaseModel {
};
@action
- updateFromTemplate = async (template: Document) => {
- this.templateId = template.id;
- this.title = template.title;
- this.text = template.text;
- this.injectTemplate = true;
+ update = async (options: {| ...SaveOptions, title: string |}) => {
+ if (this.isSaving) return this;
+ this.isSaving = true;
+
+ try {
+ if (options.lastRevision) {
+ return await this.store.update({
+ id: this.id,
+ title: this.title,
+ lastRevision: options.lastRevision,
+ ...options,
+ });
+ }
+
+ throw new Error("Attempting to update without a lastRevision");
+ } finally {
+ this.isSaving = false;
+ }
};
@action
- save = async (options: SaveOptions = {}) => {
+ save = async (options: ?SaveOptions) => {
if (this.isSaving) return this;
const isCreating = !this.id;
@@ -275,22 +287,22 @@ export default class Document extends BaseModel {
collectionId: this.collectionId,
title: this.title,
text: this.text,
- publish: options.publish,
- done: options.done,
- autosave: options.autosave,
+ publish: options?.publish,
+ done: options?.done,
+ autosave: options?.autosave,
});
}
- if (options.lastRevision) {
+ if (options?.lastRevision) {
return await this.store.update({
id: this.id,
title: this.title,
text: this.text,
templateId: this.templateId,
- lastRevision: options.lastRevision,
- publish: options.publish,
- done: options.done,
- autosave: options.autosave,
+ lastRevision: options?.lastRevision,
+ publish: options?.publish,
+ done: options?.done,
+ autosave: options?.autosave,
});
}
diff --git a/app/models/Team.js b/app/models/Team.js
index fbe18bf9..b0865b8e 100644
--- a/app/models/Team.js
+++ b/app/models/Team.js
@@ -7,6 +7,7 @@ class Team extends BaseModel {
name: string;
avatarUrl: string;
sharing: boolean;
+ collaborativeEditing: boolean;
documentEmbeds: boolean;
guestSignin: boolean;
subdomain: ?string;
diff --git a/app/models/User.js b/app/models/User.js
index 7fc03c5b..86c9a6c9 100644
--- a/app/models/User.js
+++ b/app/models/User.js
@@ -8,6 +8,7 @@ class User extends BaseModel {
id: string;
name: string;
email: string;
+ color: string;
isAdmin: boolean;
isViewer: boolean;
lastActiveAt: string;
diff --git a/app/multiplayer/MultiplayerExtension.js b/app/multiplayer/MultiplayerExtension.js
new file mode 100644
index 00000000..66a7f0ab
--- /dev/null
+++ b/app/multiplayer/MultiplayerExtension.js
@@ -0,0 +1,57 @@
+// @flow
+import { keymap } from "prosemirror-keymap";
+import { Extension } from "rich-markdown-editor";
+import {
+ ySyncPlugin,
+ yCursorPlugin,
+ yUndoPlugin,
+ undo,
+ redo,
+} from "y-prosemirror";
+import * as Y from "yjs";
+
+export default class MultiplayerExtension extends Extension {
+ get name() {
+ return "multiplayer";
+ }
+
+ get plugins() {
+ const { user, provider, document: doc } = this.options;
+ const type = doc.get("default", Y.XmlFragment);
+
+ const assignUser = (tr) => {
+ const clientIds = Array.from(doc.store.clients.keys());
+
+ if (
+ tr.local &&
+ tr.changed.size > 0 &&
+ !clientIds.includes(doc.clientID)
+ ) {
+ const permanentUserData = new Y.PermanentUserData(doc);
+ permanentUserData.setUserMapping(doc, doc.clientID, user.id);
+ doc.off("afterTransaction", assignUser);
+ }
+ };
+
+ // only once we have authenticated successfully do we initalize awareness.
+ // we could send this earlier, but getting authenticated faster is more important
+ provider.on("authenticated", () => {
+ provider.awareness.setLocalStateField("user", user);
+ });
+
+ // only once an actual change has been made do we add the userId <> clientId
+ // mapping, this avoids stored mappings for clients that never made a change
+ doc.on("afterTransaction", assignUser);
+
+ return [
+ ySyncPlugin(type),
+ yCursorPlugin(provider.awareness),
+ yUndoPlugin(),
+ keymap({
+ "Mod-z": undo,
+ "Mod-y": redo,
+ "Mod-Shift-z": redo,
+ }),
+ ];
+ }
+}
diff --git a/app/routes/authenticated.js b/app/routes/authenticated.js
index 7b9dd1bb..f6a141a1 100644
--- a/app/routes/authenticated.js
+++ b/app/routes/authenticated.js
@@ -21,10 +21,8 @@ import { matchDocumentSlug as slug } from "utils/routeHelpers";
const SettingsRoutes = React.lazy(() =>
import(/* webpackChunkName: "settings" */ "./settings")
);
-const KeyedDocument = React.lazy(() =>
- import(
- /* webpackChunkName: "keyed-document" */ "scenes/Document/KeyedDocument"
- )
+const Document = React.lazy(() =>
+ import(/* webpackChunkName: "document" */ "scenes/Document")
);
const NotFound = () => ;
const RedirectDocument = ({ match }: { match: Match }) => (
@@ -64,10 +62,10 @@ export default function AuthenticatedRoutes() {
-
-
+
+
diff --git a/app/routes/index.js b/app/routes/index.js
index f3d93a35..5ef41d7d 100644
--- a/app/routes/index.js
+++ b/app/routes/index.js
@@ -12,10 +12,8 @@ const Authenticated = React.lazy(() =>
const AuthenticatedRoutes = React.lazy(() =>
import(/* webpackChunkName: "authenticated-routes" */ "./authenticated")
);
-const KeyedDocument = React.lazy(() =>
- import(
- /* webpackChunkName: "keyed-document" */ "scenes/Document/KeyedDocument"
- )
+const SharedDocument = React.lazy(() =>
+ import(/* webpackChunkName: "shared-document" */ "scenes/Document/Shared")
);
const Login = React.lazy(() =>
import(/* webpackChunkName: "login" */ "scenes/Login")
@@ -37,11 +35,11 @@ export default function Routes() {
-
+
diff --git a/app/scenes/Document/KeyedDocument.js b/app/scenes/Document/KeyedDocument.js
deleted file mode 100644
index aefd1a78..00000000
--- a/app/scenes/Document/KeyedDocument.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// @flow
-import { inject } from "mobx-react";
-import * as React from "react";
-import DataLoader from "./components/DataLoader";
-
-class KeyedDocument extends React.Component<*> {
- componentWillUnmount() {
- this.props.ui.clearActiveDocument();
- }
-
- render() {
- const { documentSlug, revisionId } = this.props.match.params;
-
- // the urlId portion of the url does not include the slugified title
- // we only want to force a re-mount of the document component when the
- // document changes, not when the title does so only this portion is used
- // for the key.
- const urlParts = documentSlug ? documentSlug.split("-") : [];
- const urlId = urlParts.length ? urlParts[urlParts.length - 1] : undefined;
-
- return ;
- }
-}
-
-export default inject("ui")(KeyedDocument);
diff --git a/app/scenes/Document/Shared.js b/app/scenes/Document/Shared.js
new file mode 100644
index 00000000..ae9be691
--- /dev/null
+++ b/app/scenes/Document/Shared.js
@@ -0,0 +1,64 @@
+// @flow
+import * as React from "react";
+import { type Match } from "react-router-dom";
+import { useTheme } from "styled-components";
+import Error404 from "scenes/Error404";
+import ErrorOffline from "scenes/ErrorOffline";
+import useStores from "../../hooks/useStores";
+import Document from "./components/Document";
+import Loading from "./components/Loading";
+import { type LocationWithState } from "types";
+import { OfflineError } from "utils/errors";
+
+const EMPTY_OBJECT = {};
+
+type Props = {|
+ match: Match,
+ location: LocationWithState,
+|};
+
+export default function SharedDocumentScene(props: Props) {
+ const theme = useTheme();
+ const [response, setResponse] = React.useState();
+ const [error, setError] = React.useState();
+ const { documents } = useStores();
+ const { shareId, documentSlug } = props.match.params;
+
+ // ensure the wider page color always matches the theme
+ React.useEffect(() => {
+ window.document.body.style.background = theme.background;
+ }, [theme]);
+
+ React.useEffect(() => {
+ async function fetchData() {
+ try {
+ const response = await documents.fetch(documentSlug, {
+ shareId,
+ });
+ setResponse(response);
+ } catch (err) {
+ setError(err);
+ }
+ }
+ fetchData();
+ }, [documents, documentSlug, shareId]);
+
+ if (error) {
+ return error instanceof OfflineError ? : ;
+ }
+
+ if (!response) {
+ return ;
+ }
+
+ return (
+
+ );
+}
diff --git a/app/scenes/Document/components/DataLoader.js b/app/scenes/Document/components/DataLoader.js
index df8fc3b5..522c3661 100644
--- a/app/scenes/Document/components/DataLoader.js
+++ b/app/scenes/Document/components/DataLoader.js
@@ -18,16 +18,15 @@ import Document from "models/Document";
import Revision from "models/Revision";
import Error404 from "scenes/Error404";
import ErrorOffline from "scenes/ErrorOffline";
-import DocumentComponent from "./Document";
import HideSidebar from "./HideSidebar";
import Loading from "./Loading";
-import SocketPresence from "./SocketPresence";
import { type LocationWithState, type NavigationNode } from "types";
import { NotFoundError, OfflineError } from "utils/errors";
import { matchDocumentEdit, updateDocumentUrl } from "utils/routeHelpers";
import { isInternalUrl } from "utils/urls";
type Props = {|
match: Match,
+ auth: AuthStore,
location: LocationWithState,
shares: SharesStore,
documents: DocumentsStore,
@@ -36,6 +35,7 @@ type Props = {|
auth: AuthStore,
ui: UiStore,
history: RouterHistory,
+ children: (any) => React.Node,
|};
const sharedTreeCache = {};
@@ -223,7 +223,7 @@ class DataLoader extends React.Component {
};
render() {
- const { location, policies, ui } = this.props;
+ const { location, policies, auth, ui } = this.props;
if (this.error) {
return this.error instanceof OfflineError ? (
@@ -233,10 +233,11 @@ class DataLoader extends React.Component {
);
}
+ const team = auth.team;
const document = this.document;
const revision = this.revision;
- if (!document) {
+ if (!document || !team) {
return (
<>
@@ -247,20 +248,28 @@ class DataLoader extends React.Component {
const abilities = policies.abilities(document.id);
+ // We do not want to remount the document when changing from view->edit
+ // on the multiplayer flag as the doc is guaranteed to be upto date.
+ const key = team.collaborativeEditing
+ ? ""
+ : this.isEditing
+ ? "editing"
+ : "read-only";
+
return (
-
+
{this.isEditing && }
-
-
+ {this.props.children({
+ document,
+ revision,
+ abilities,
+ isEditing: this.isEditing,
+ readOnly: !this.isEditing || !abilities.update || document.isArchived,
+ onSearchLink: this.onSearchLink,
+ onCreateLink: this.onCreateLink,
+ sharedTree: this.sharedTree,
+ })}
+
);
}
}
diff --git a/app/scenes/Document/components/Document.js b/app/scenes/Document/components/Document.js
index d825da7a..13628d2f 100644
--- a/app/scenes/Document/components/Document.js
+++ b/app/scenes/Document/components/Document.js
@@ -1,8 +1,9 @@
// @flow
import { debounce } from "lodash";
-import { observable } from "mobx";
+import { action, observable } from "mobx";
import { observer, inject } from "mobx-react";
import { InputIcon } from "outline-icons";
+import { AllSelection } from "prosemirror-state";
import * as React from "react";
import { type TFunction, Trans, withTranslation } from "react-i18next";
import keydown from "react-keydown";
@@ -18,6 +19,7 @@ import Document from "models/Document";
import Revision from "models/Revision";
import DocumentMove from "scenes/DocumentMove";
import Branding from "components/Branding";
+import ConnectionStatus from "components/ConnectionStatus";
import ErrorBoundary from "components/ErrorBoundary";
import Flex from "components/Flex";
import LoadingIndicator from "components/LoadingIndicator";
@@ -113,15 +115,31 @@ class DocumentScene extends React.Component {
);
}
}
-
- if (document.injectTemplate) {
- document.injectTemplate = false;
- this.title = document.title;
- this.isDirty = true;
- this.updateIsDirty();
- }
}
+ onSelectTemplate = (template: Document) => {
+ this.title = template.title;
+ this.isDirty = true;
+
+ const editorRef = this.editor.current;
+ if (!editorRef) {
+ return;
+ }
+
+ const { view, parser } = editorRef;
+ view.dispatch(
+ view.state.tr
+ .setSelection(new AllSelection(view.state.doc))
+ .replaceSelectionWith(parser.parse(template.text))
+ );
+
+ this.props.document.templateId = template.id;
+ this.props.document.title = template.title;
+ this.props.document.text = template.text;
+
+ this.updateIsDirty();
+ };
+
@keydown("m")
goToMove(ev) {
if (!this.props.readOnly) return;
@@ -197,7 +215,7 @@ class DocumentScene extends React.Component {
autosave?: boolean,
} = {}
) => {
- const { document } = this.props;
+ const { document, auth } = this.props;
// prevent saves when we are already saving
if (document.isSaving) return;
@@ -219,18 +237,29 @@ class DocumentScene extends React.Component {
document.title = title;
document.text = text;
+ document.tasks = getTasks(document.text);
let isNew = !document.id;
this.isSaving = true;
this.isPublishing = !!options.publish;
- document.tasks = getTasks(document.text);
-
try {
- const savedDocument = await document.save({
- ...options,
- lastRevision: this.lastRevision,
- });
+ let savedDocument = document;
+ if (auth.team?.collaborativeEditing) {
+ // update does not send "text" field to the API, this is a workaround
+ // while the multiplayer editor is toggleable. Once it's finalized
+ // this can be cleaned up to single code path
+ savedDocument = await document.update({
+ ...options,
+ lastRevision: this.lastRevision,
+ });
+ } else {
+ savedDocument = await document.save({
+ ...options,
+ lastRevision: this.lastRevision,
+ });
+ }
+
this.isDirty = false;
this.lastRevision = savedDocument.revision;
@@ -275,8 +304,21 @@ class DocumentScene extends React.Component {
};
onChange = (getEditorText) => {
+ const { document, auth } = this.props;
+
this.getEditorText = getEditorText;
+ // If the multiplayer editor is enabled then we still want to keep the local
+ // text value in sync as it is used as a cache.
+ if (auth.team?.collaborativeEditing) {
+ action(() => {
+ document.text = this.getEditorText();
+ document.tasks = getTasks(document.text);
+ })();
+
+ return;
+ }
+
// document change while read only is presumed to be a checkbox edit,
// in that case we don't delay in saving for a better user experience.
if (this.props.readOnly) {
@@ -314,7 +356,6 @@ class DocumentScene extends React.Component {
const isShare = !!shareId;
const value = revision ? revision.text : document.text;
- const injectTemplate = document.injectTemplate;
const disableEmbeds =
(team && team.documentEmbeds === false) || document.embedsDisabled;
@@ -323,6 +364,12 @@ class DocumentScene extends React.Component {
: [];
const showContents = ui.tocVisible && readOnly;
+ const collaborativeEditing =
+ team?.collaborativeEditing &&
+ !document.isArchived &&
+ !document.isDeleted &&
+ !revision;
+
return (
{
auto
>
(
{
{!readOnly && (
<>
{
savingIsDisabled={document.isSaving || this.isEmpty}
sharedTree={this.props.sharedTree}
goBack={this.goBack}
+ onSelectTemplate={this.onSelectTemplate}
onSave={this.onSave}
headings={headings}
/>
@@ -443,11 +495,12 @@ class DocumentScene extends React.Component {
{showContents && }
{
{isShare && !isCustomDomain() && (
)}
- {!isShare && }
+ {!isShare && (
+ <>
+
+
+ >
+ )}
);
}
diff --git a/app/scenes/Document/components/Editor.js b/app/scenes/Document/components/Editor.js
index df442c7e..9da6b2b3 100644
--- a/app/scenes/Document/components/Editor.js
+++ b/app/scenes/Document/components/Editor.js
@@ -17,6 +17,7 @@ import Editor, { type Props as EditorProps } from "components/Editor";
import Flex from "components/Flex";
import HoverPreview from "components/HoverPreview";
import Star, { AnimatedStar } from "components/Star";
+import MultiplayerEditor from "./MultiplayerEditor";
import { isModKey } from "utils/keyboard";
import { documentHistoryUrl } from "utils/routeHelpers";
@@ -27,6 +28,7 @@ type Props = {|
document: Document,
isDraft: boolean,
shareId: ?string,
+ multiplayer?: boolean,
onSave: ({ done?: boolean, autosave?: boolean, publish?: boolean }) => any,
innerRef: { current: any },
children: React.Node,
@@ -107,10 +109,12 @@ class DocumentEditor extends React.Component {
innerRef,
children,
policies,
+ multiplayer,
t,
...rest
} = this.props;
+ const EditorComponent = multiplayer ? MultiplayerEditor : Editor;
const can = policies.abilities(document.id);
const { emoji } = parseTitle(title);
const startsWithEmojiAndSpace = !!(emoji && title.startsWith(`${emoji} `));
@@ -162,7 +166,7 @@ class DocumentEditor extends React.Component {
}
/>
)}
- void,
onDiscard: () => void,
onSave: ({
done?: boolean,
@@ -61,6 +62,7 @@ function DocumentHeader({
savingIsDisabled,
publishingIsDisabled,
sharedTree,
+ onSelectTemplate,
onSave,
headings,
}: Props) {
@@ -167,7 +169,10 @@ function DocumentHeader({
/>
{isEditing && !isTemplate && isNew && (
-
+
)}
{!isEditing && (!isMobile || !isTemplate) && (
diff --git a/app/scenes/Document/components/KeyboardShortcutsButton.js b/app/scenes/Document/components/KeyboardShortcutsButton.js
index fe3bcb11..1fd92380 100644
--- a/app/scenes/Document/components/KeyboardShortcutsButton.js
+++ b/app/scenes/Document/components/KeyboardShortcutsButton.js
@@ -27,12 +27,7 @@ function KeyboardShortcutsButton() {
>
-
+
diff --git a/app/scenes/Document/components/MultiplayerEditor.js b/app/scenes/Document/components/MultiplayerEditor.js
new file mode 100644
index 00000000..94fc414f
--- /dev/null
+++ b/app/scenes/Document/components/MultiplayerEditor.js
@@ -0,0 +1,144 @@
+// @flow
+import { HocuspocusProvider } from "@hocuspocus/provider";
+import * as React from "react";
+import { useTranslation } from "react-i18next";
+import { useHistory } from "react-router";
+import { IndexeddbPersistence } from "y-indexeddb";
+import * as Y from "yjs";
+import Editor, { type Props as EditorProps } from "components/Editor";
+import PlaceholderDocument from "components/PlaceholderDocument";
+import env from "env";
+import useCurrentToken from "hooks/useCurrentToken";
+import useCurrentUser from "hooks/useCurrentUser";
+import useStores from "hooks/useStores";
+import useToasts from "hooks/useToasts";
+import useUnmount from "hooks/useUnmount";
+import MultiplayerExtension from "multiplayer/MultiplayerExtension";
+import { homeUrl } from "utils/routeHelpers";
+
+type Props = {|
+ ...EditorProps,
+ id: string,
+|};
+
+function MultiplayerEditor(props: Props, ref: any) {
+ const documentId = props.id;
+ const history = useHistory();
+ const { t } = useTranslation();
+ const currentUser = useCurrentUser();
+ const { presence, ui } = useStores();
+ const token = useCurrentToken();
+ const [localProvider, setLocalProvider] = React.useState();
+ const [remoteProvider, setRemoteProvider] = React.useState();
+ const [isLocalSynced, setLocalSynced] = React.useState(false);
+ const [isRemoteSynced, setRemoteSynced] = React.useState(false);
+ const [ydoc] = React.useState(() => new Y.Doc());
+ const { showToast } = useToasts();
+
+ // Provider initialization must be within useLayoutEffect rather than useState
+ // or useMemo as both of these are ran twice in React StrictMode resulting in
+ // an orphaned websocket connection.
+ // see: https://github.com/facebook/react/issues/20090#issuecomment-715926549
+ React.useLayoutEffect(() => {
+ const debug = env.ENVIRONMENT === "development";
+ const name = `document.${documentId}`;
+
+ const localProvider = new IndexeddbPersistence(name, ydoc);
+ const provider = new HocuspocusProvider({
+ url: `${env.COLLABORATION_URL}/collaboration`,
+ debug,
+ name,
+ document: ydoc,
+ token,
+ maxReconnectTimeout: 10000,
+ });
+
+ provider.on("authenticationFailed", () => {
+ showToast(
+ t(
+ "Sorry, it looks like you don’t have permission to access the document"
+ )
+ );
+
+ history.replace(homeUrl());
+ });
+
+ provider.on("awarenessChange", ({ states }) => {
+ states.forEach(({ user }) => {
+ if (user) {
+ // could know if the user is editing here using `state.cursor` but it
+ // feels distracting in the UI, once multiplayer is on for everyone we
+ // can stop diffentiating
+ presence.touch(documentId, user.id, false);
+ }
+ });
+ });
+
+ localProvider.on("synced", () => setLocalSynced(true));
+ provider.on("synced", () => setRemoteSynced(true));
+
+ if (debug) {
+ provider.on("status", (ev) => console.log("status", ev.status));
+ provider.on("message", (ev) => console.log("incoming", ev.message));
+ provider.on("outgoingMessage", (ev) =>
+ console.log("outgoing", ev.message)
+ );
+ localProvider.on("synced", (ev) => console.log("local synced"));
+ }
+
+ provider.on("status", (ev) => ui.setMultiplayerStatus(ev.status));
+
+ setRemoteProvider(provider);
+ setLocalProvider(localProvider);
+ }, [history, showToast, t, documentId, ui, presence, token, ydoc]);
+
+ const user = React.useMemo(() => {
+ return {
+ id: currentUser.id,
+ name: currentUser.name,
+ color: currentUser.color,
+ };
+ }, [currentUser.id, currentUser.color, currentUser.name]);
+
+ const extensions = React.useMemo(() => {
+ if (!remoteProvider) {
+ return [];
+ }
+
+ return [
+ new MultiplayerExtension({
+ user,
+ provider: remoteProvider,
+ document: ydoc,
+ }),
+ ];
+ }, [remoteProvider, user, ydoc]);
+
+ useUnmount(() => {
+ remoteProvider?.destroy();
+ localProvider?.destroy();
+ ui.setMultiplayerStatus(undefined);
+ });
+
+ if (!extensions.length) {
+ return null;
+ }
+
+ if (isLocalSynced && !isRemoteSynced && !ydoc.get("default")._start) {
+ return ;
+ }
+
+ return (
+
+ );
+}
+
+export default React.forwardRef(
+ MultiplayerEditor
+);
diff --git a/app/scenes/Document/index.js b/app/scenes/Document/index.js
index 7af896b2..75edaaf4 100644
--- a/app/scenes/Document/index.js
+++ b/app/scenes/Document/index.js
@@ -1,3 +1,61 @@
// @flow
+import * as React from "react";
+import { type Match } from "react-router-dom";
import DataLoader from "./components/DataLoader";
-export default DataLoader;
+import Document from "./components/Document";
+import SocketPresence from "./components/SocketPresence";
+import useCurrentTeam from "hooks/useCurrentTeam";
+import useCurrentUser from "hooks/useCurrentUser";
+import useStores from "hooks/useStores";
+import { type LocationWithState } from "types";
+
+type Props = {|
+ location: LocationWithState,
+ match: Match,
+|};
+
+export default function DocumentScene(props: Props) {
+ const { ui } = useStores();
+ const team = useCurrentTeam();
+ const user = useCurrentUser();
+
+ React.useEffect(() => {
+ return () => ui.clearActiveDocument();
+ }, [ui]);
+
+ const { documentSlug, revisionId } = props.match.params;
+
+ // the urlId portion of the url does not include the slugified title
+ // we only want to force a re-mount of the document component when the
+ // document changes, not when the title does so only this portion is used
+ // for the key.
+ const urlParts = documentSlug ? documentSlug.split("-") : [];
+ const urlId = urlParts.length ? urlParts[urlParts.length - 1] : undefined;
+ const key = [urlId, revisionId].join("/");
+ const isMultiplayer = team.collaborativeEditing;
+
+ return (
+
+ {({ document, isEditing, ...rest }) => {
+ const isActive =
+ !document.isArchived && !document.isDeleted && !revisionId;
+
+ // TODO: Remove once multiplayer is 100% rollout, SocketPresence will
+ // no longer be required
+ if (isActive && !isMultiplayer) {
+ return (
+
+
+
+ );
+ }
+
+ return ;
+ }}
+
+ );
+}
diff --git a/app/stores/DocumentsStore.js b/app/stores/DocumentsStore.js
index a0aed5af..374fad76 100644
--- a/app/stores/DocumentsStore.js
+++ b/app/stores/DocumentsStore.js
@@ -603,7 +603,7 @@ export default class DocumentsStore extends BaseStore {
async update(params: {
id: string,
title: string,
- text: string,
+ text?: string,
lastRevision: number,
}) {
const document = await super.update(params);
diff --git a/app/stores/UiStore.js b/app/stores/UiStore.js
index 1a0280f5..d44ac883 100644
--- a/app/stores/UiStore.js
+++ b/app/stores/UiStore.js
@@ -6,6 +6,8 @@ import Document from "models/Document";
const UI_STORE = "UI_STORE";
+type Status = "connecting" | "connected" | "disconnected" | void;
+
class UiStore {
// has the user seen the prompt to change the UI language and actioned it
@observable languagePromptDismissed: boolean;
@@ -24,6 +26,7 @@ class UiStore {
@observable sidebarWidth: number;
@observable sidebarCollapsed: boolean = false;
@observable sidebarIsResizing: boolean = false;
+ @observable multiplayerStatus: Status;
constructor() {
// Rehydrate
@@ -93,6 +96,11 @@ class UiStore {
}
};
+ @action
+ setMultiplayerStatus = (status: Status): void => {
+ this.multiplayerStatus = status;
+ };
+
@action
setSidebarResizing = (sidebarIsResizing: boolean): void => {
this.sidebarIsResizing = sidebarIsResizing;
diff --git a/app/utils/developer.js b/app/utils/developer.js
new file mode 100644
index 00000000..537e9fff
--- /dev/null
+++ b/app/utils/developer.js
@@ -0,0 +1,11 @@
+// @flow
+
+// A function to delete all IndexedDB databases
+export async function deleteAllDatabases() {
+ const databases = await window.indexedDB.databases();
+ for (const database of databases) {
+ if (database.name) {
+ await window.indexedDB.deleteDatabase(database.name);
+ }
+ }
+}
diff --git a/app/utils/urls.js b/app/utils/urls.js
index 6f38a6e7..7e19f3df 100644
--- a/app/utils/urls.js
+++ b/app/utils/urls.js
@@ -1,6 +1,5 @@
// @flow
import { parseDomain } from "../../shared/utils/domains";
-import env from "env";
export function isInternalUrl(href: string) {
if (href[0] === "/") return true;
@@ -21,14 +20,6 @@ export function isInternalUrl(href: string) {
return false;
}
-export function cdnPath(path: string): string {
- return `${env.CDN_URL}${path}`;
-}
-
-export function imagePath(path: string): string {
- return cdnPath(`/images/${path}`);
-}
-
export function decodeURIComponentSafe(text: string) {
return text
? decodeURIComponent(text.replace(/%(?![0-9][0-9a-fA-F]+)/g, "%25"))
diff --git a/ARCHITECTURE.md b/docs/ARCHITECTURE.md
similarity index 100%
rename from ARCHITECTURE.md
rename to docs/ARCHITECTURE.md
diff --git a/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md
similarity index 100%
rename from CODE_OF_CONDUCT.md
rename to docs/CODE_OF_CONDUCT.md
diff --git a/SECURITY.md b/docs/SECURITY.md
similarity index 100%
rename from SECURITY.md
rename to docs/SECURITY.md
diff --git a/docs/SERVICES.md b/docs/SERVICES.md
new file mode 100644
index 00000000..67522d07
--- /dev/null
+++ b/docs/SERVICES.md
@@ -0,0 +1,45 @@
+# Backend Services
+
+Outline's backend is split into several distinct [services](../server/services)
+that combined form the application. When running the official Docker container
+it will run all of the production services by default.
+
+You can choose which services to run through either a comma separated CLI flag,
+`--services`, or the `SERVICES` environment variable. For example:
+
+```bash
+yarn start --services=web,worker
+```
+
+## Admin
+
+Currently this service is only used in development to view and debug the queues.
+It is hosted at `/admin`.
+
+## Web
+
+The web server hosts the Application and API, as such this is the main service
+and must be run by at least one process.
+
+## Websockets
+
+The websocket server is used to communicate with the frontend, it can be ran on
+the same box as the web server or separately.
+
+## Worker
+
+At least one worker process is required to process the [queues](../server/queues).
+
+## Collaboration
+
+The service is in alpha and as such is not started by default. It must run
+separately to the `websockets` service, and will not start in the same process.
+The `COLLABORATION_URL` must be set to the publicly accessible URL when running
+the service. For example, if the app is hosted at `https://docs.example.com` you
+may use something like: `COLLABORATION_URL=wss://docs-collaboration.example.com`.
+
+Start the service with:
+
+```bash
+yarn start --services=collaboration
+```
diff --git a/TRANSLATION.md b/docs/TRANSLATION.md
similarity index 100%
rename from TRANSLATION.md
rename to docs/TRANSLATION.md
diff --git a/flow-typed/npm/lib0_vx.x.x.js b/flow-typed/npm/lib0_vx.x.x.js
new file mode 100644
index 00000000..6c73c4e7
--- /dev/null
+++ b/flow-typed/npm/lib0_vx.x.x.js
@@ -0,0 +1,377 @@
+// flow-typed signature: 97da878aea98698d6c06f8a696bb62af
+// flow-typed version: <>/lib0_v0.2.34/flow_v0.104.0
+
+/**
+ * This is an autogenerated libdef stub for:
+ *
+ * 'lib0'
+ *
+ * Fill this stub out by replacing all the `any` types.
+ *
+ * Once filled out, we encourage you to share your work with the
+ * community by sending a pull request to:
+ * https://github.com/flowtype/flow-typed
+ */
+
+// @flow
+declare module "lib0" {
+ declare module.exports: any;
+}
+
+/**
+ * We include stubs for each file inside this npm package in case you need to
+ * require those files directly. Feel free to delete any files that aren't
+ * needed.
+ */
+declare module "lib0/array" {
+ declare module.exports: any;
+}
+
+declare module "lib0/bin/gendocs" {
+ declare module.exports: any;
+}
+
+declare module "lib0/binary" {
+ declare module.exports: any;
+}
+
+declare module "lib0/broadcastchannel" {
+ declare module.exports: any;
+}
+
+declare module "lib0/buffer" {
+ declare module.exports: any;
+}
+
+declare module "lib0/component" {
+ declare module.exports: any;
+}
+
+declare module "lib0/conditions" {
+ declare module.exports: any;
+}
+
+declare module "lib0/decoding" {
+ declare module.exports: any;
+}
+
+declare module "lib0/diff" {
+ declare module.exports: any;
+}
+
+declare module "lib0/dist/test" {
+ declare module.exports: any;
+}
+
+declare module "lib0/dom" {
+ declare module.exports: any;
+}
+
+declare module "lib0/encoding" {
+ declare module.exports: any;
+}
+
+declare module "lib0/environment" {
+ declare module.exports: any;
+}
+
+declare module "lib0/error" {
+ declare module.exports: any;
+}
+
+declare module "lib0/eventloop" {
+ declare module.exports: any;
+}
+
+declare module "lib0/function" {
+ declare module.exports: any;
+}
+
+declare module "lib0/indexeddb" {
+ declare module.exports: any;
+}
+
+declare module "lib0/isomorphic" {
+ declare module.exports: any;
+}
+
+declare module "lib0/iterator" {
+ declare module.exports: any;
+}
+
+declare module "lib0/json" {
+ declare module.exports: any;
+}
+
+declare module "lib0/logging" {
+ declare module.exports: any;
+}
+
+declare module "lib0/map" {
+ declare module.exports: any;
+}
+
+declare module "lib0/math" {
+ declare module.exports: any;
+}
+
+declare module "lib0/metric" {
+ declare module.exports: any;
+}
+
+declare module "lib0/mutex" {
+ declare module.exports: any;
+}
+
+declare module "lib0/number" {
+ declare module.exports: any;
+}
+
+declare module "lib0/object" {
+ declare module.exports: any;
+}
+
+declare module "lib0/observable" {
+ declare module.exports: any;
+}
+
+declare module "lib0/pair" {
+ declare module.exports: any;
+}
+
+declare module "lib0/prng" {
+ declare module.exports: any;
+}
+
+declare module "lib0/prng/Mt19937" {
+ declare module.exports: any;
+}
+
+declare module "lib0/prng/Xoroshiro128plus" {
+ declare module.exports: any;
+}
+
+declare module "lib0/prng/Xorshift32" {
+ declare module.exports: any;
+}
+
+declare module "lib0/promise" {
+ declare module.exports: any;
+}
+
+declare module "lib0/queue" {
+ declare module.exports: any;
+}
+
+declare module "lib0/random" {
+ declare module.exports: any;
+}
+
+declare module "lib0/set" {
+ declare module.exports: any;
+}
+
+declare module "lib0/sort" {
+ declare module.exports: any;
+}
+
+declare module "lib0/statistics" {
+ declare module.exports: any;
+}
+
+declare module "lib0/storage" {
+ declare module.exports: any;
+}
+
+declare module "lib0/string" {
+ declare module.exports: any;
+}
+
+declare module "lib0/symbol" {
+ declare module.exports: any;
+}
+
+declare module "lib0/test" {
+ declare module.exports: any;
+}
+
+declare module "lib0/testing" {
+ declare module.exports: any;
+}
+
+declare module "lib0/time" {
+ declare module.exports: any;
+}
+
+declare module "lib0/tree" {
+ declare module.exports: any;
+}
+
+declare module "lib0/url" {
+ declare module.exports: any;
+}
+
+declare module "lib0/websocket" {
+ declare module.exports: any;
+}
+
+// Filename aliases
+declare module "lib0/array.js" {
+ declare module.exports: $Exports<"lib0/array">;
+}
+declare module "lib0/bin/gendocs.js" {
+ declare module.exports: $Exports<"lib0/bin/gendocs">;
+}
+declare module "lib0/binary.js" {
+ declare module.exports: $Exports<"lib0/binary">;
+}
+declare module "lib0/broadcastchannel.js" {
+ declare module.exports: $Exports<"lib0/broadcastchannel">;
+}
+declare module "lib0/buffer.js" {
+ declare module.exports: $Exports<"lib0/buffer">;
+}
+declare module "lib0/component.js" {
+ declare module.exports: $Exports<"lib0/component">;
+}
+declare module "lib0/conditions.js" {
+ declare module.exports: $Exports<"lib0/conditions">;
+}
+declare module "lib0/decoding.js" {
+ declare module.exports: $Exports<"lib0/decoding">;
+}
+declare module "lib0/dist/decoding.cjs" {
+ declare module.exports: $Exports<"lib0/decoding">;
+}
+declare module "lib0/diff.js" {
+ declare module.exports: $Exports<"lib0/diff">;
+}
+declare module "lib0/dist/test.js" {
+ declare module.exports: $Exports<"lib0/dist/test">;
+}
+declare module "lib0/dom.js" {
+ declare module.exports: $Exports<"lib0/dom">;
+}
+declare module "lib0/encoding.js" {
+ declare module.exports: $Exports<"lib0/encoding">;
+}
+declare module "lib0/dist/encoding.cjs" {
+ declare module.exports: $Exports<"lib0/encoding">;
+}
+declare module "lib0/environment.js" {
+ declare module.exports: $Exports<"lib0/environment">;
+}
+declare module "lib0/error.js" {
+ declare module.exports: $Exports<"lib0/error">;
+}
+declare module "lib0/eventloop.js" {
+ declare module.exports: $Exports<"lib0/eventloop">;
+}
+declare module "lib0/function.js" {
+ declare module.exports: $Exports<"lib0/function">;
+}
+declare module "lib0/index" {
+ declare module.exports: $Exports<"lib0">;
+}
+declare module "lib0/index.js" {
+ declare module.exports: $Exports<"lib0">;
+}
+declare module "lib0/indexeddb.js" {
+ declare module.exports: $Exports<"lib0/indexeddb">;
+}
+declare module "lib0/isomorphic.js" {
+ declare module.exports: $Exports<"lib0/isomorphic">;
+}
+declare module "lib0/iterator.js" {
+ declare module.exports: $Exports<"lib0/iterator">;
+}
+declare module "lib0/json.js" {
+ declare module.exports: $Exports<"lib0/json">;
+}
+declare module "lib0/logging.js" {
+ declare module.exports: $Exports<"lib0/logging">;
+}
+declare module "lib0/map.js" {
+ declare module.exports: $Exports<"lib0/map">;
+}
+declare module "lib0/math.js" {
+ declare module.exports: $Exports<"lib0/math">;
+}
+declare module "lib0/metric.js" {
+ declare module.exports: $Exports<"lib0/metric">;
+}
+declare module "lib0/mutex.js" {
+ declare module.exports: $Exports<"lib0/mutex">;
+}
+declare module "lib0/dist/mutex.cjs" {
+ declare module.exports: $Exports<"lib0/mutex">;
+}
+declare module "lib0/number.js" {
+ declare module.exports: $Exports<"lib0/number">;
+}
+declare module "lib0/object.js" {
+ declare module.exports: $Exports<"lib0/object">;
+}
+declare module "lib0/observable.js" {
+ declare module.exports: $Exports<"lib0/observable">;
+}
+declare module "lib0/pair.js" {
+ declare module.exports: $Exports<"lib0/pair">;
+}
+declare module "lib0/prng.js" {
+ declare module.exports: $Exports<"lib0/prng">;
+}
+declare module "lib0/prng/Mt19937.js" {
+ declare module.exports: $Exports<"lib0/prng/Mt19937">;
+}
+declare module "lib0/prng/Xoroshiro128plus.js" {
+ declare module.exports: $Exports<"lib0/prng/Xoroshiro128plus">;
+}
+declare module "lib0/prng/Xorshift32.js" {
+ declare module.exports: $Exports<"lib0/prng/Xorshift32">;
+}
+declare module "lib0/promise.js" {
+ declare module.exports: $Exports<"lib0/promise">;
+}
+declare module "lib0/queue.js" {
+ declare module.exports: $Exports<"lib0/queue">;
+}
+declare module "lib0/random.js" {
+ declare module.exports: $Exports<"lib0/random">;
+}
+declare module "lib0/set.js" {
+ declare module.exports: $Exports<"lib0/set">;
+}
+declare module "lib0/sort.js" {
+ declare module.exports: $Exports<"lib0/sort">;
+}
+declare module "lib0/statistics.js" {
+ declare module.exports: $Exports<"lib0/statistics">;
+}
+declare module "lib0/storage.js" {
+ declare module.exports: $Exports<"lib0/storage">;
+}
+declare module "lib0/string.js" {
+ declare module.exports: $Exports<"lib0/string">;
+}
+declare module "lib0/symbol.js" {
+ declare module.exports: $Exports<"lib0/symbol">;
+}
+declare module "lib0/test.js" {
+ declare module.exports: $Exports<"lib0/test">;
+}
+declare module "lib0/testing.js" {
+ declare module.exports: $Exports<"lib0/testing">;
+}
+declare module "lib0/time.js" {
+ declare module.exports: $Exports<"lib0/time">;
+}
+declare module "lib0/tree.js" {
+ declare module.exports: $Exports<"lib0/tree">;
+}
+declare module "lib0/url.js" {
+ declare module.exports: $Exports<"lib0/url">;
+}
+declare module "lib0/websocket.js" {
+ declare module.exports: $Exports<"lib0/websocket">;
+}
diff --git a/flow-typed/npm/lib0_vx.x.x.js~4256e7ec (flow) b/flow-typed/npm/lib0_vx.x.x.js~4256e7ec (flow)
new file mode 100644
index 00000000..6c73c4e7
--- /dev/null
+++ b/flow-typed/npm/lib0_vx.x.x.js~4256e7ec (flow)
@@ -0,0 +1,377 @@
+// flow-typed signature: 97da878aea98698d6c06f8a696bb62af
+// flow-typed version: <>/lib0_v0.2.34/flow_v0.104.0
+
+/**
+ * This is an autogenerated libdef stub for:
+ *
+ * 'lib0'
+ *
+ * Fill this stub out by replacing all the `any` types.
+ *
+ * Once filled out, we encourage you to share your work with the
+ * community by sending a pull request to:
+ * https://github.com/flowtype/flow-typed
+ */
+
+// @flow
+declare module "lib0" {
+ declare module.exports: any;
+}
+
+/**
+ * We include stubs for each file inside this npm package in case you need to
+ * require those files directly. Feel free to delete any files that aren't
+ * needed.
+ */
+declare module "lib0/array" {
+ declare module.exports: any;
+}
+
+declare module "lib0/bin/gendocs" {
+ declare module.exports: any;
+}
+
+declare module "lib0/binary" {
+ declare module.exports: any;
+}
+
+declare module "lib0/broadcastchannel" {
+ declare module.exports: any;
+}
+
+declare module "lib0/buffer" {
+ declare module.exports: any;
+}
+
+declare module "lib0/component" {
+ declare module.exports: any;
+}
+
+declare module "lib0/conditions" {
+ declare module.exports: any;
+}
+
+declare module "lib0/decoding" {
+ declare module.exports: any;
+}
+
+declare module "lib0/diff" {
+ declare module.exports: any;
+}
+
+declare module "lib0/dist/test" {
+ declare module.exports: any;
+}
+
+declare module "lib0/dom" {
+ declare module.exports: any;
+}
+
+declare module "lib0/encoding" {
+ declare module.exports: any;
+}
+
+declare module "lib0/environment" {
+ declare module.exports: any;
+}
+
+declare module "lib0/error" {
+ declare module.exports: any;
+}
+
+declare module "lib0/eventloop" {
+ declare module.exports: any;
+}
+
+declare module "lib0/function" {
+ declare module.exports: any;
+}
+
+declare module "lib0/indexeddb" {
+ declare module.exports: any;
+}
+
+declare module "lib0/isomorphic" {
+ declare module.exports: any;
+}
+
+declare module "lib0/iterator" {
+ declare module.exports: any;
+}
+
+declare module "lib0/json" {
+ declare module.exports: any;
+}
+
+declare module "lib0/logging" {
+ declare module.exports: any;
+}
+
+declare module "lib0/map" {
+ declare module.exports: any;
+}
+
+declare module "lib0/math" {
+ declare module.exports: any;
+}
+
+declare module "lib0/metric" {
+ declare module.exports: any;
+}
+
+declare module "lib0/mutex" {
+ declare module.exports: any;
+}
+
+declare module "lib0/number" {
+ declare module.exports: any;
+}
+
+declare module "lib0/object" {
+ declare module.exports: any;
+}
+
+declare module "lib0/observable" {
+ declare module.exports: any;
+}
+
+declare module "lib0/pair" {
+ declare module.exports: any;
+}
+
+declare module "lib0/prng" {
+ declare module.exports: any;
+}
+
+declare module "lib0/prng/Mt19937" {
+ declare module.exports: any;
+}
+
+declare module "lib0/prng/Xoroshiro128plus" {
+ declare module.exports: any;
+}
+
+declare module "lib0/prng/Xorshift32" {
+ declare module.exports: any;
+}
+
+declare module "lib0/promise" {
+ declare module.exports: any;
+}
+
+declare module "lib0/queue" {
+ declare module.exports: any;
+}
+
+declare module "lib0/random" {
+ declare module.exports: any;
+}
+
+declare module "lib0/set" {
+ declare module.exports: any;
+}
+
+declare module "lib0/sort" {
+ declare module.exports: any;
+}
+
+declare module "lib0/statistics" {
+ declare module.exports: any;
+}
+
+declare module "lib0/storage" {
+ declare module.exports: any;
+}
+
+declare module "lib0/string" {
+ declare module.exports: any;
+}
+
+declare module "lib0/symbol" {
+ declare module.exports: any;
+}
+
+declare module "lib0/test" {
+ declare module.exports: any;
+}
+
+declare module "lib0/testing" {
+ declare module.exports: any;
+}
+
+declare module "lib0/time" {
+ declare module.exports: any;
+}
+
+declare module "lib0/tree" {
+ declare module.exports: any;
+}
+
+declare module "lib0/url" {
+ declare module.exports: any;
+}
+
+declare module "lib0/websocket" {
+ declare module.exports: any;
+}
+
+// Filename aliases
+declare module "lib0/array.js" {
+ declare module.exports: $Exports<"lib0/array">;
+}
+declare module "lib0/bin/gendocs.js" {
+ declare module.exports: $Exports<"lib0/bin/gendocs">;
+}
+declare module "lib0/binary.js" {
+ declare module.exports: $Exports<"lib0/binary">;
+}
+declare module "lib0/broadcastchannel.js" {
+ declare module.exports: $Exports<"lib0/broadcastchannel">;
+}
+declare module "lib0/buffer.js" {
+ declare module.exports: $Exports<"lib0/buffer">;
+}
+declare module "lib0/component.js" {
+ declare module.exports: $Exports<"lib0/component">;
+}
+declare module "lib0/conditions.js" {
+ declare module.exports: $Exports<"lib0/conditions">;
+}
+declare module "lib0/decoding.js" {
+ declare module.exports: $Exports<"lib0/decoding">;
+}
+declare module "lib0/dist/decoding.cjs" {
+ declare module.exports: $Exports<"lib0/decoding">;
+}
+declare module "lib0/diff.js" {
+ declare module.exports: $Exports<"lib0/diff">;
+}
+declare module "lib0/dist/test.js" {
+ declare module.exports: $Exports<"lib0/dist/test">;
+}
+declare module "lib0/dom.js" {
+ declare module.exports: $Exports<"lib0/dom">;
+}
+declare module "lib0/encoding.js" {
+ declare module.exports: $Exports<"lib0/encoding">;
+}
+declare module "lib0/dist/encoding.cjs" {
+ declare module.exports: $Exports<"lib0/encoding">;
+}
+declare module "lib0/environment.js" {
+ declare module.exports: $Exports<"lib0/environment">;
+}
+declare module "lib0/error.js" {
+ declare module.exports: $Exports<"lib0/error">;
+}
+declare module "lib0/eventloop.js" {
+ declare module.exports: $Exports<"lib0/eventloop">;
+}
+declare module "lib0/function.js" {
+ declare module.exports: $Exports<"lib0/function">;
+}
+declare module "lib0/index" {
+ declare module.exports: $Exports<"lib0">;
+}
+declare module "lib0/index.js" {
+ declare module.exports: $Exports<"lib0">;
+}
+declare module "lib0/indexeddb.js" {
+ declare module.exports: $Exports<"lib0/indexeddb">;
+}
+declare module "lib0/isomorphic.js" {
+ declare module.exports: $Exports<"lib0/isomorphic">;
+}
+declare module "lib0/iterator.js" {
+ declare module.exports: $Exports<"lib0/iterator">;
+}
+declare module "lib0/json.js" {
+ declare module.exports: $Exports<"lib0/json">;
+}
+declare module "lib0/logging.js" {
+ declare module.exports: $Exports<"lib0/logging">;
+}
+declare module "lib0/map.js" {
+ declare module.exports: $Exports<"lib0/map">;
+}
+declare module "lib0/math.js" {
+ declare module.exports: $Exports<"lib0/math">;
+}
+declare module "lib0/metric.js" {
+ declare module.exports: $Exports<"lib0/metric">;
+}
+declare module "lib0/mutex.js" {
+ declare module.exports: $Exports<"lib0/mutex">;
+}
+declare module "lib0/dist/mutex.cjs" {
+ declare module.exports: $Exports<"lib0/mutex">;
+}
+declare module "lib0/number.js" {
+ declare module.exports: $Exports<"lib0/number">;
+}
+declare module "lib0/object.js" {
+ declare module.exports: $Exports<"lib0/object">;
+}
+declare module "lib0/observable.js" {
+ declare module.exports: $Exports<"lib0/observable">;
+}
+declare module "lib0/pair.js" {
+ declare module.exports: $Exports<"lib0/pair">;
+}
+declare module "lib0/prng.js" {
+ declare module.exports: $Exports<"lib0/prng">;
+}
+declare module "lib0/prng/Mt19937.js" {
+ declare module.exports: $Exports<"lib0/prng/Mt19937">;
+}
+declare module "lib0/prng/Xoroshiro128plus.js" {
+ declare module.exports: $Exports<"lib0/prng/Xoroshiro128plus">;
+}
+declare module "lib0/prng/Xorshift32.js" {
+ declare module.exports: $Exports<"lib0/prng/Xorshift32">;
+}
+declare module "lib0/promise.js" {
+ declare module.exports: $Exports<"lib0/promise">;
+}
+declare module "lib0/queue.js" {
+ declare module.exports: $Exports<"lib0/queue">;
+}
+declare module "lib0/random.js" {
+ declare module.exports: $Exports<"lib0/random">;
+}
+declare module "lib0/set.js" {
+ declare module.exports: $Exports<"lib0/set">;
+}
+declare module "lib0/sort.js" {
+ declare module.exports: $Exports<"lib0/sort">;
+}
+declare module "lib0/statistics.js" {
+ declare module.exports: $Exports<"lib0/statistics">;
+}
+declare module "lib0/storage.js" {
+ declare module.exports: $Exports<"lib0/storage">;
+}
+declare module "lib0/string.js" {
+ declare module.exports: $Exports<"lib0/string">;
+}
+declare module "lib0/symbol.js" {
+ declare module.exports: $Exports<"lib0/symbol">;
+}
+declare module "lib0/test.js" {
+ declare module.exports: $Exports<"lib0/test">;
+}
+declare module "lib0/testing.js" {
+ declare module.exports: $Exports<"lib0/testing">;
+}
+declare module "lib0/time.js" {
+ declare module.exports: $Exports<"lib0/time">;
+}
+declare module "lib0/tree.js" {
+ declare module.exports: $Exports<"lib0/tree">;
+}
+declare module "lib0/url.js" {
+ declare module.exports: $Exports<"lib0/url">;
+}
+declare module "lib0/websocket.js" {
+ declare module.exports: $Exports<"lib0/websocket">;
+}
diff --git a/flow-typed/npm/y-indexeddb_vx.x.x.js b/flow-typed/npm/y-indexeddb_vx.x.x.js
new file mode 100644
index 00000000..03dd378c
--- /dev/null
+++ b/flow-typed/npm/y-indexeddb_vx.x.x.js
@@ -0,0 +1,39 @@
+// flow-typed signature: 71e55e30d387153cf804d226f95c0ad8
+// flow-typed version: <>/y-indexeddb_v^9.0.5/flow_v0.104.0
+
+/**
+ * This is an autogenerated libdef stub for:
+ *
+ * 'y-indexeddb'
+ *
+ * Fill this stub out by replacing all the `any` types.
+ *
+ * Once filled out, we encourage you to share your work with the
+ * community by sending a pull request to:
+ * https://github.com/flowtype/flow-typed
+ */
+
+declare module 'y-indexeddb' {
+ declare module.exports: any;
+}
+
+/**
+ * We include stubs for each file inside this npm package in case you need to
+ * require those files directly. Feel free to delete any files that aren't
+ * needed.
+ */
+declare module 'y-indexeddb/dist/test' {
+ declare module.exports: any;
+}
+
+declare module 'y-indexeddb/src/y-indexeddb' {
+ declare module.exports: any;
+}
+
+// Filename aliases
+declare module 'y-indexeddb/dist/test.js' {
+ declare module.exports: $Exports<'y-indexeddb/dist/test'>;
+}
+declare module 'y-indexeddb/src/y-indexeddb.js' {
+ declare module.exports: $Exports<'y-indexeddb/src/y-indexeddb'>;
+}
diff --git a/flow-typed/npm/y-prosemirror_vx.x.x.js b/flow-typed/npm/y-prosemirror_vx.x.x.js
new file mode 100644
index 00000000..d299fd87
--- /dev/null
+++ b/flow-typed/npm/y-prosemirror_vx.x.x.js
@@ -0,0 +1,67 @@
+// flow-typed signature: 2db53ec5dbb577a4e27bc465cd4670f3
+// flow-typed version: <>/y-prosemirror_v^0.3.7/flow_v0.104.0
+
+/**
+ * This is an autogenerated libdef stub for:
+ *
+ * 'y-prosemirror'
+ *
+ * Fill this stub out by replacing all the `any` types.
+ *
+ * Once filled out, we encourage you to share your work with the
+ * community by sending a pull request to:
+ * https://github.com/flowtype/flow-typed
+ */
+
+declare module 'y-prosemirror' {
+ declare module.exports: any;
+}
+
+/**
+ * We include stubs for each file inside this npm package in case you need to
+ * require those files directly. Feel free to delete any files that aren't
+ * needed.
+ */
+declare module 'y-prosemirror/dist/test' {
+ declare module.exports: any;
+}
+
+declare module 'y-prosemirror/src/lib' {
+ declare module.exports: any;
+}
+
+declare module 'y-prosemirror/src/plugins/cursor-plugin' {
+ declare module.exports: any;
+}
+
+declare module 'y-prosemirror/src/plugins/sync-plugin' {
+ declare module.exports: any;
+}
+
+declare module 'y-prosemirror/src/plugins/undo-plugin' {
+ declare module.exports: any;
+}
+
+declare module 'y-prosemirror/src/y-prosemirror' {
+ declare module.exports: any;
+}
+
+// Filename aliases
+declare module 'y-prosemirror/dist/test.js' {
+ declare module.exports: $Exports<'y-prosemirror/dist/test'>;
+}
+declare module 'y-prosemirror/src/lib.js' {
+ declare module.exports: $Exports<'y-prosemirror/src/lib'>;
+}
+declare module 'y-prosemirror/src/plugins/cursor-plugin.js' {
+ declare module.exports: $Exports<'y-prosemirror/src/plugins/cursor-plugin'>;
+}
+declare module 'y-prosemirror/src/plugins/sync-plugin.js' {
+ declare module.exports: $Exports<'y-prosemirror/src/plugins/sync-plugin'>;
+}
+declare module 'y-prosemirror/src/plugins/undo-plugin.js' {
+ declare module.exports: $Exports<'y-prosemirror/src/plugins/undo-plugin'>;
+}
+declare module 'y-prosemirror/src/y-prosemirror.js' {
+ declare module.exports: $Exports<'y-prosemirror/src/y-prosemirror'>;
+}
diff --git a/flow-typed/npm/y-protocols_vx.x.x.js b/flow-typed/npm/y-protocols_vx.x.x.js
new file mode 100644
index 00000000..8ef44753
--- /dev/null
+++ b/flow-typed/npm/y-protocols_vx.x.x.js
@@ -0,0 +1,67 @@
+// flow-typed signature: 3ef5e4dd42591ff15af5f507abd6aa97
+// flow-typed version: <>/y-protocols_v^1.0.1/flow_v0.104.0
+
+/**
+ * This is an autogenerated libdef stub for:
+ *
+ * 'y-protocols'
+ *
+ * Fill this stub out by replacing all the `any` types.
+ *
+ * Once filled out, we encourage you to share your work with the
+ * community by sending a pull request to:
+ * https://github.com/flowtype/flow-typed
+ */
+
+// @flow
+declare module "y-protocols" {
+ declare module.exports: any;
+}
+
+/**
+ * We include stubs for each file inside this npm package in case you need to
+ * require those files directly. Feel free to delete any files that aren't
+ * needed.
+ */
+declare module "y-protocols/auth" {
+ declare module.exports: any;
+}
+
+declare module "y-protocols/awareness" {
+ declare module.exports: any;
+}
+
+declare module "y-protocols/awareness.test" {
+ declare module.exports: any;
+}
+
+declare module "y-protocols/dist/test" {
+ declare module.exports: any;
+}
+
+declare module "y-protocols/sync" {
+ declare module.exports: any;
+}
+
+// Filename aliases
+declare module "y-protocols/auth.js" {
+ declare module.exports: $Exports<"y-protocols/auth">;
+}
+declare module "y-protocols/awareness.js" {
+ declare module.exports: $Exports<"y-protocols/awareness">;
+}
+declare module "y-protocols/dist/awareness.cjs" {
+ declare module.exports: $Exports<"y-protocols/awareness">;
+}
+declare module "y-protocols/awareness.test.js" {
+ declare module.exports: $Exports<"y-protocols/awareness.test">;
+}
+declare module "y-protocols/dist/test.js" {
+ declare module.exports: $Exports<"y-protocols/dist/test">;
+}
+declare module "y-protocols/sync.js" {
+ declare module.exports: $Exports<"y-protocols/sync">;
+}
+declare module "y-protocols/dist/sync.cjs" {
+ declare module.exports: $Exports<"y-protocols/sync">;
+}
diff --git a/flow-typed/npm/yjs_vx.x.x.js b/flow-typed/npm/yjs_vx.x.x.js
new file mode 100644
index 00000000..6f506208
--- /dev/null
+++ b/flow-typed/npm/yjs_vx.x.x.js
@@ -0,0 +1,430 @@
+// flow-typed signature: ec89eac307897bef104c76ce1dd14a4d
+// flow-typed version: <>/yjs_v^13.4.1/flow_v0.104.0
+
+/**
+ * This is an autogenerated libdef stub for:
+ *
+ * 'yjs'
+ *
+ * Fill this stub out by replacing all the `any` types.
+ *
+ * Once filled out, we encourage you to share your work with the
+ * community by sending a pull request to:
+ * https://github.com/flowtype/flow-typed
+ */
+
+declare module 'yjs' {
+ declare module.exports: any;
+}
+
+/**
+ * We include stubs for each file inside this npm package in case you need to
+ * require those files directly. Feel free to delete any files that aren't
+ * needed.
+ */
+declare module 'yjs/dist/tests' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/docs/scripts/jquery.min' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/docs/scripts/linenumber' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/docs/scripts/prettify/lang-css' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/docs/scripts/prettify/prettify' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/docs/scripts/tui-doc' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/internals' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/structs/AbstractStruct' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/structs/ContentAny' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/structs/ContentBinary' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/structs/ContentDeleted' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/structs/ContentDoc' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/structs/ContentEmbed' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/structs/ContentFormat' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/structs/ContentJSON' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/structs/ContentString' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/structs/ContentType' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/structs/GC' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/structs/Item' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/types/AbstractType' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/types/YArray' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/types/YMap' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/types/YText' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/types/YXmlElement' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/types/YXmlEvent' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/types/YXmlFragment' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/types/YXmlHook' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/types/YXmlText' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/AbstractConnector' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/DeleteSet' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/Doc' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/encoding' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/EventHandler' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/ID' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/isParentOf' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/logging' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/PermanentUserData' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/RelativePosition' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/Snapshot' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/StructStore' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/Transaction' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/UndoManager' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/UpdateDecoder' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/UpdateEncoder' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/src/utils/YEvent' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/tests/compatibility.tests' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/tests/doc.tests' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/tests/encoding.tests' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/tests' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/tests/snapshot.tests' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/tests/testHelper' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/tests/undo-redo.tests' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/tests/y-array.tests' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/tests/y-map.tests' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/tests/y-text.tests' {
+ declare module.exports: any;
+}
+
+declare module 'yjs/tests/y-xml.tests' {
+ declare module.exports: any;
+}
+
+// Filename aliases
+declare module 'yjs/dist/tests.js' {
+ declare module.exports: $Exports<'yjs/dist/tests'>;
+}
+declare module 'yjs/docs/scripts/jquery.min.js' {
+ declare module.exports: $Exports<'yjs/docs/scripts/jquery.min'>;
+}
+declare module 'yjs/docs/scripts/linenumber.js' {
+ declare module.exports: $Exports<'yjs/docs/scripts/linenumber'>;
+}
+declare module 'yjs/docs/scripts/prettify/lang-css.js' {
+ declare module.exports: $Exports<'yjs/docs/scripts/prettify/lang-css'>;
+}
+declare module 'yjs/docs/scripts/prettify/prettify.js' {
+ declare module.exports: $Exports<'yjs/docs/scripts/prettify/prettify'>;
+}
+declare module 'yjs/docs/scripts/tui-doc.js' {
+ declare module.exports: $Exports<'yjs/docs/scripts/tui-doc'>;
+}
+declare module 'yjs/src/index' {
+ declare module.exports: $Exports<'yjs/src'>;
+}
+declare module 'yjs/src/index.js' {
+ declare module.exports: $Exports<'yjs/src'>;
+}
+declare module 'yjs/src/internals.js' {
+ declare module.exports: $Exports<'yjs/src/internals'>;
+}
+declare module 'yjs/src/structs/AbstractStruct.js' {
+ declare module.exports: $Exports<'yjs/src/structs/AbstractStruct'>;
+}
+declare module 'yjs/src/structs/ContentAny.js' {
+ declare module.exports: $Exports<'yjs/src/structs/ContentAny'>;
+}
+declare module 'yjs/src/structs/ContentBinary.js' {
+ declare module.exports: $Exports<'yjs/src/structs/ContentBinary'>;
+}
+declare module 'yjs/src/structs/ContentDeleted.js' {
+ declare module.exports: $Exports<'yjs/src/structs/ContentDeleted'>;
+}
+declare module 'yjs/src/structs/ContentDoc.js' {
+ declare module.exports: $Exports<'yjs/src/structs/ContentDoc'>;
+}
+declare module 'yjs/src/structs/ContentEmbed.js' {
+ declare module.exports: $Exports<'yjs/src/structs/ContentEmbed'>;
+}
+declare module 'yjs/src/structs/ContentFormat.js' {
+ declare module.exports: $Exports<'yjs/src/structs/ContentFormat'>;
+}
+declare module 'yjs/src/structs/ContentJSON.js' {
+ declare module.exports: $Exports<'yjs/src/structs/ContentJSON'>;
+}
+declare module 'yjs/src/structs/ContentString.js' {
+ declare module.exports: $Exports<'yjs/src/structs/ContentString'>;
+}
+declare module 'yjs/src/structs/ContentType.js' {
+ declare module.exports: $Exports<'yjs/src/structs/ContentType'>;
+}
+declare module 'yjs/src/structs/GC.js' {
+ declare module.exports: $Exports<'yjs/src/structs/GC'>;
+}
+declare module 'yjs/src/structs/Item.js' {
+ declare module.exports: $Exports<'yjs/src/structs/Item'>;
+}
+declare module 'yjs/src/types/AbstractType.js' {
+ declare module.exports: $Exports<'yjs/src/types/AbstractType'>;
+}
+declare module 'yjs/src/types/YArray.js' {
+ declare module.exports: $Exports<'yjs/src/types/YArray'>;
+}
+declare module 'yjs/src/types/YMap.js' {
+ declare module.exports: $Exports<'yjs/src/types/YMap'>;
+}
+declare module 'yjs/src/types/YText.js' {
+ declare module.exports: $Exports<'yjs/src/types/YText'>;
+}
+declare module 'yjs/src/types/YXmlElement.js' {
+ declare module.exports: $Exports<'yjs/src/types/YXmlElement'>;
+}
+declare module 'yjs/src/types/YXmlEvent.js' {
+ declare module.exports: $Exports<'yjs/src/types/YXmlEvent'>;
+}
+declare module 'yjs/src/types/YXmlFragment.js' {
+ declare module.exports: $Exports<'yjs/src/types/YXmlFragment'>;
+}
+declare module 'yjs/src/types/YXmlHook.js' {
+ declare module.exports: $Exports<'yjs/src/types/YXmlHook'>;
+}
+declare module 'yjs/src/types/YXmlText.js' {
+ declare module.exports: $Exports<'yjs/src/types/YXmlText'>;
+}
+declare module 'yjs/src/utils/AbstractConnector.js' {
+ declare module.exports: $Exports<'yjs/src/utils/AbstractConnector'>;
+}
+declare module 'yjs/src/utils/DeleteSet.js' {
+ declare module.exports: $Exports<'yjs/src/utils/DeleteSet'>;
+}
+declare module 'yjs/src/utils/Doc.js' {
+ declare module.exports: $Exports<'yjs/src/utils/Doc'>;
+}
+declare module 'yjs/src/utils/encoding.js' {
+ declare module.exports: $Exports<'yjs/src/utils/encoding'>;
+}
+declare module 'yjs/src/utils/EventHandler.js' {
+ declare module.exports: $Exports<'yjs/src/utils/EventHandler'>;
+}
+declare module 'yjs/src/utils/ID.js' {
+ declare module.exports: $Exports<'yjs/src/utils/ID'>;
+}
+declare module 'yjs/src/utils/isParentOf.js' {
+ declare module.exports: $Exports<'yjs/src/utils/isParentOf'>;
+}
+declare module 'yjs/src/utils/logging.js' {
+ declare module.exports: $Exports<'yjs/src/utils/logging'>;
+}
+declare module 'yjs/src/utils/PermanentUserData.js' {
+ declare module.exports: $Exports<'yjs/src/utils/PermanentUserData'>;
+}
+declare module 'yjs/src/utils/RelativePosition.js' {
+ declare module.exports: $Exports<'yjs/src/utils/RelativePosition'>;
+}
+declare module 'yjs/src/utils/Snapshot.js' {
+ declare module.exports: $Exports<'yjs/src/utils/Snapshot'>;
+}
+declare module 'yjs/src/utils/StructStore.js' {
+ declare module.exports: $Exports<'yjs/src/utils/StructStore'>;
+}
+declare module 'yjs/src/utils/Transaction.js' {
+ declare module.exports: $Exports<'yjs/src/utils/Transaction'>;
+}
+declare module 'yjs/src/utils/UndoManager.js' {
+ declare module.exports: $Exports<'yjs/src/utils/UndoManager'>;
+}
+declare module 'yjs/src/utils/UpdateDecoder.js' {
+ declare module.exports: $Exports<'yjs/src/utils/UpdateDecoder'>;
+}
+declare module 'yjs/src/utils/UpdateEncoder.js' {
+ declare module.exports: $Exports<'yjs/src/utils/UpdateEncoder'>;
+}
+declare module 'yjs/src/utils/YEvent.js' {
+ declare module.exports: $Exports<'yjs/src/utils/YEvent'>;
+}
+declare module 'yjs/tests/compatibility.tests.js' {
+ declare module.exports: $Exports<'yjs/tests/compatibility.tests'>;
+}
+declare module 'yjs/tests/doc.tests.js' {
+ declare module.exports: $Exports<'yjs/tests/doc.tests'>;
+}
+declare module 'yjs/tests/encoding.tests.js' {
+ declare module.exports: $Exports<'yjs/tests/encoding.tests'>;
+}
+declare module 'yjs/tests/index' {
+ declare module.exports: $Exports<'yjs/tests'>;
+}
+declare module 'yjs/tests/index.js' {
+ declare module.exports: $Exports<'yjs/tests'>;
+}
+declare module 'yjs/tests/snapshot.tests.js' {
+ declare module.exports: $Exports<'yjs/tests/snapshot.tests'>;
+}
+declare module 'yjs/tests/testHelper.js' {
+ declare module.exports: $Exports<'yjs/tests/testHelper'>;
+}
+declare module 'yjs/tests/undo-redo.tests.js' {
+ declare module.exports: $Exports<'yjs/tests/undo-redo.tests'>;
+}
+declare module 'yjs/tests/y-array.tests.js' {
+ declare module.exports: $Exports<'yjs/tests/y-array.tests'>;
+}
+declare module 'yjs/tests/y-map.tests.js' {
+ declare module.exports: $Exports<'yjs/tests/y-map.tests'>;
+}
+declare module 'yjs/tests/y-text.tests.js' {
+ declare module.exports: $Exports<'yjs/tests/y-text.tests'>;
+}
+declare module 'yjs/tests/y-xml.tests.js' {
+ declare module.exports: $Exports<'yjs/tests/y-xml.tests'>;
+}
diff --git a/package.json b/package.json
index 7b6d60c4..0260c0ab 100644
--- a/package.json
+++ b/package.json
@@ -6,11 +6,12 @@
"scripts": {
"clean": "rimraf build",
"build:i18n": "i18next --silent 'app/**/*.js' 'server/**/*.js' && mkdir -p ./build/shared/i18n && cp -R ./shared/i18n/locales ./build/shared/i18n",
- "build:server": "babel -d ./build/server ./server && babel -d ./build/shared ./shared && cp package.json ./build && ln -sf \"$(pwd)/webpack.config.dev.js\" ./build",
+ "build:server": "babel -d ./build/server ./server && babel -d ./build/shared ./shared && cp ./server/collaboration/Procfile ./build/server/collaboration/Procfile && cp package.json ./build && ln -sf \"$(pwd)/webpack.config.dev.js\" ./build",
"build:webpack": "webpack --config webpack.config.prod.js",
"build": "yarn clean && yarn build:webpack && yarn build:i18n && yarn build:server",
"start": "node ./build/server/index.js",
- "dev": "nodemon --exec \"yarn build:server && yarn build:i18n && node build/server/index.js\" -e js --ignore build/ --ignore app/ --ignore flow-typed/",
+ "dev": "yarn concurrently --kill-others -n server,multiplayer \"node --inspect=0.0.0.0 build/server/index.js --services=websockets,admin,web,worker\" \"node build/server/index.js --services=collaboration --port=4000\"",
+ "dev:watch": "nodemon --exec \"yarn build:server && yarn build:i18n && yarn dev\" -e js --ignore build/ --ignore app/ --ignore flow-typed/",
"lint": "eslint app server shared",
"deploy": "git push heroku master",
"prepare": "yarn yarn-deduplicate yarn.lock",
@@ -45,6 +46,11 @@
"@babel/preset-env": "^7.11.0",
"@babel/preset-flow": "^7.10.4",
"@babel/preset-react": "^7.10.4",
+ "@bull-board/api": "^3.5.0",
+ "@bull-board/koa": "^3.5.0",
+ "@hocuspocus/extension-logger": "^1.0.0-alpha.43",
+ "@hocuspocus/provider": "^1.0.0-alpha.13",
+ "@hocuspocus/server": "^1.0.0-alpha.68",
"@outlinewiki/koa-passport": "^4.1.4",
"@outlinewiki/passport-azure-ad-oauth2": "^0.1.0",
"@sentry/node": "^6.3.1",
@@ -58,7 +64,7 @@
"babel-plugin-styled-components": "^1.11.1",
"babel-plugin-transform-class-properties": "^6.24.1",
"boundless-arrow-key-navigation": "^1.0.4",
- "bull": "^3.5.2",
+ "bull": "^3.29.0",
"cancan": "3.1.0",
"chalk": "^4.1.0",
"compressorjs": "^1.0.7",
@@ -96,6 +102,7 @@
"koa-body": "^4.2.0",
"koa-compress": "2.0.0",
"koa-convert": "1.2.0",
+ "koa-easy-ws": "^1.3.0",
"koa-helmet": "5.2.0",
"koa-jwt": "^3.6.0",
"koa-logger": "^3.2.1",
@@ -111,7 +118,7 @@
"mobx-react": "^6.3.1",
"natural-sort": "^1.0.0",
"nodemailer": "^6.4.16",
- "outline-icons": "^1.30.0",
+ "outline-icons": "^1.31.0",
"oy-vey": "^0.10.0",
"passport": "^0.4.1",
"passport-google-oauth2": "^0.2.0",
@@ -144,7 +151,7 @@
"react-window": "^1.8.6",
"reakit": "^1.3.8",
"regenerator-runtime": "^0.13.7",
- "rich-markdown-editor": "^11.17.4",
+ "rich-markdown-editor": "^11.17.5",
"semver": "^7.3.2",
"sequelize": "^6.3.4",
"sequelize-cli": "^6.2.0",
@@ -167,7 +174,10 @@
"turndown": "^7.1.1",
"utf8": "^3.0.0",
"uuid": "^8.3.2",
- "validator": "5.2.0"
+ "validator": "5.2.0",
+ "y-indexeddb": "^9.0.6",
+ "y-prosemirror": "^1.0.9",
+ "yjs": "^13.5.12"
},
"devDependencies": {
"@babel/cli": "^7.10.5",
@@ -177,6 +187,7 @@
"babel-jest": "^26.2.2",
"babel-loader": "^8.1.0",
"babel-plugin-transform-inline-environment-variables": "^0.4.3",
+ "concurrently": "^6.2.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.6",
"eslint": "^7.6.0",
diff --git a/server/collaboration/Procfile b/server/collaboration/Procfile
new file mode 100644
index 00000000..913cf469
--- /dev/null
+++ b/server/collaboration/Procfile
@@ -0,0 +1 @@
+web: yarn start --services=collaboration
diff --git a/server/collaboration/authentication.js b/server/collaboration/authentication.js
new file mode 100644
index 00000000..b334971b
--- /dev/null
+++ b/server/collaboration/authentication.js
@@ -0,0 +1,46 @@
+// @flow
+import { AuthenticationError } from "../errors";
+import { Document } from "../models";
+import policy from "../policies";
+import { getUserForJWT } from "../utils/jwt";
+
+const { can } = policy;
+
+export default class Authentication {
+ async onAuthenticate({
+ connection,
+ token,
+ documentName,
+ }: {
+ connection: { readOnly: boolean },
+ token: string,
+ documentName: string,
+ }) {
+ // allows for different entity types to use this multiplayer provider later
+ const [, documentId] = documentName.split(".");
+
+ if (!token) {
+ throw new AuthenticationError("Authentication required");
+ }
+
+ const user = await getUserForJWT(token);
+ if (user.isSuspended) {
+ throw new AuthenticationError("Account suspended");
+ }
+
+ const document = await Document.findByPk(documentId, { userId: user.id });
+ if (!can(user, "read", document)) {
+ throw new AuthenticationError("Authorization required");
+ }
+
+ // set document to read only for the current user, thus changes will not be
+ // accepted and synced to other clients
+ if (!can(user, "update", document)) {
+ connection.readOnly = true;
+ }
+
+ return {
+ user,
+ };
+ }
+}
diff --git a/server/collaboration/persistence.js b/server/collaboration/persistence.js
new file mode 100644
index 00000000..3d361f37
--- /dev/null
+++ b/server/collaboration/persistence.js
@@ -0,0 +1,71 @@
+// @flow
+import debug from "debug";
+import { debounce } from "lodash";
+import * as Y from "yjs";
+import documentUpdater from "../commands/documentUpdater";
+import { Document, User } from "../models";
+import markdownToYDoc from "./utils/markdownToYDoc";
+
+const log = debug("server");
+const DELAY = 3000;
+
+export default class Persistence {
+ async onCreateDocument({
+ documentName,
+ ...data
+ }: {
+ documentName: string,
+ document: Y.Doc,
+ }) {
+ const [, documentId] = documentName.split(".");
+ const fieldName = "default";
+
+ // Check if the given field already exists in the given y-doc. This is import
+ // so we don't import a document fresh if it exists already.
+ if (!data.document.isEmpty(fieldName)) {
+ return;
+ }
+
+ const document = await Document.findByPk(documentId);
+
+ if (document.state) {
+ const ydoc = new Y.Doc();
+ log(`Document ${documentId} is already in state`);
+ Y.applyUpdate(ydoc, document.state);
+ return ydoc;
+ }
+
+ log(`Document ${documentId} is not in state, creating state from markdown`);
+ const ydoc = markdownToYDoc(document.text, fieldName);
+ const state = Y.encodeStateAsUpdate(ydoc);
+
+ await document.update({ state: Buffer.from(state) }, { hooks: false });
+ return ydoc;
+ }
+
+ onChange = debounce(
+ async ({
+ document,
+ context,
+ documentName,
+ }: {
+ document: Y.Doc,
+ context: { user: User },
+ documentName: string,
+ }) => {
+ const [, documentId] = documentName.split(".");
+
+ log(`persisting ${documentId}`);
+
+ await documentUpdater({
+ documentId,
+ ydoc: document,
+ userId: context.user.id,
+ });
+ },
+ DELAY,
+ {
+ maxWait: DELAY * 3,
+ }
+ );
+}
diff --git a/server/collaboration/utils/markdownToYDoc.js b/server/collaboration/utils/markdownToYDoc.js
new file mode 100644
index 00000000..ab9605cc
--- /dev/null
+++ b/server/collaboration/utils/markdownToYDoc.js
@@ -0,0 +1,42 @@
+// @flow
+import { Node, Fragment } from "prosemirror-model";
+import { parser, schema } from "rich-markdown-editor";
+import { prosemirrorToYDoc } from "y-prosemirror";
+import * as Y from "yjs";
+import embeds from "../../../shared/embeds";
+
+export default function markdownToYDoc(
+ markdown: string,
+ fieldName?: string = "default"
+): Y.Doc {
+ let node = parser.parse(markdown);
+
+ // in rich-markdown-editor embeds were created at runtime by converting links
+ // into embeds where they match. Because we're converting to a CRDT structure
+ // on the server we need to mimic this behavior.
+ function urlsToEmbeds(node: Node): Node {
+ if (node.type.name === "paragraph") {
+ for (const textNode of node.content.content) {
+ for (const embed of embeds) {
+ if (textNode.text && embed.matcher(textNode.text)) {
+ return schema.nodes.embed.createAndFill({
+ href: textNode.text,
+ });
+ }
+ }
+ }
+ }
+
+ if (node.content) {
+ const contentAsArray =
+ node.content instanceof Fragment ? node.content.content : node.content;
+ node.content = Fragment.fromArray(contentAsArray.map(urlsToEmbeds));
+ }
+
+ return node;
+ }
+
+ node = urlsToEmbeds(node);
+
+ return prosemirrorToYDoc(node, fieldName);
+}
diff --git a/server/commands/documentUpdater.js b/server/commands/documentUpdater.js
new file mode 100644
index 00000000..f38bc0c5
--- /dev/null
+++ b/server/commands/documentUpdater.js
@@ -0,0 +1,69 @@
+// @flow
+import { uniq } from "lodash";
+import { Node } from "prosemirror-model";
+import { schema, serializer } from "rich-markdown-editor";
+import { yDocToProsemirrorJSON } from "y-prosemirror";
+import * as Y from "yjs";
+import { Document, Event } from "../models";
+
+export default async function documentUpdater({
+ documentId,
+ ydoc,
+ userId,
+ done,
+}: {
+ documentId: string,
+ ydoc: Y.Doc,
+ userId: string,
+ done?: boolean,
+}) {
+ const document = await Document.findByPk(documentId);
+ const state = Y.encodeStateAsUpdate(ydoc);
+ const node = Node.fromJSON(schema, yDocToProsemirrorJSON(ydoc, "default"));
+ const text = serializer.serialize(node);
+
+ const isUnchanged = document.text === text;
+ const hasMultiplayerState = !!document.state;
+
+ if (isUnchanged && hasMultiplayerState) {
+ return;
+ }
+
+ // extract collaborators from doc user data
+ const pud = new Y.PermanentUserData(ydoc);
+ const pudIds = Array.from(pud.clients.values());
+ const existingIds = document.collaboratorIds;
+ const collaboratorIds = uniq([...pudIds, ...existingIds]);
+
+ await Document.scope("withUnpublished").update(
+ {
+ text,
+ state: Buffer.from(state),
+ updatedAt: isUnchanged ? document.updatedAt : new Date(),
+ lastModifiedById: isUnchanged ? document.lastModifiedById : userId,
+ collaboratorIds,
+ },
+ {
+ hooks: false,
+ where: {
+ id: documentId,
+ },
+ }
+ );
+
+ if (isUnchanged) {
+ return;
+ }
+
+ await Event.add({
+ name: "documents.update",
+ documentId: document.id,
+ collectionId: document.collectionId,
+ teamId: document.teamId,
+ actorId: userId,
+ data: {
+ multiplayer: true,
+ title: document.title,
+ },
+ });
+}
diff --git a/server/index.js b/server/index.js
index f7a5b3eb..eeffabdc 100644
--- a/server/index.js
+++ b/server/index.js
@@ -8,30 +8,31 @@ import Koa from "koa";
import compress from "koa-compress";
import helmet from "koa-helmet";
import logger from "koa-logger";
+import onerror from "koa-onerror";
import Router from "koa-router";
import { uniq } from "lodash";
import stoppable from "stoppable";
import throng from "throng";
-import "./sentry";
import services from "./services";
+import { getArg } from "./utils/args";
+import { requestErrorHandler } from "./utils/sentry";
import { checkEnv, checkMigrations } from "./utils/startup";
import { checkUpdates } from "./utils/updates";
checkEnv();
checkMigrations();
+// If a --port flag is passed then it takes priority over the env variable
+const normalizedPortFlag = getArg("port", "p");
+
// If a services flag is passed it takes priority over the enviroment variable
// for example: --services=web,worker
-const normalizedServiceFlag = process.argv
- .slice(2)
- .filter((arg) => arg.startsWith("--services="))
- .map((arg) => arg.split("=")[1])
- .join(",");
+const normalizedServiceFlag = getArg("services");
// The default is to run all services to make development and OSS installations
// easier to deal with. Separate services are only needed at scale.
const serviceNames = uniq(
- (normalizedServiceFlag || env.SERVICES || "web,websockets,worker")
+ (normalizedServiceFlag || env.SERVICES || "websockets,worker,web")
.split(",")
.map((service) => service.trim())
);
@@ -48,10 +49,23 @@ async function start(id, disconnect) {
app.use(compress());
app.use(helmet());
+ // catch errors in one place, automatically set status and response headers
+ onerror(app);
+ app.on("error", requestErrorHandler);
+
// install health check endpoint for all services
router.get("/_health", (ctx) => (ctx.body = "OK"));
app.use(router.routes());
+ if (
+ serviceNames.includes("websockets") &&
+ serviceNames.includes("collaboration")
+ ) {
+ throw new Error(
+ "Cannot run websockets and collaboration services in the same process"
+ );
+ }
+
// loop through requestsed services at startup
for (const name of serviceNames) {
if (!Object.keys(services).includes(name)) {
@@ -72,7 +86,7 @@ async function start(id, disconnect) {
console.log(`\n> Listening on http://localhost:${address.port}\n`);
});
- server.listen(env.PORT || "3000");
+ server.listen(normalizedPortFlag || env.PORT || "3000");
process.once("SIGTERM", shutdown);
process.once("SIGINT", shutdown);
@@ -86,8 +100,13 @@ async function start(id, disconnect) {
throng({
worker: start,
- // The number of workers to run, defaults to the number of CPU's available
- count: process.env.WEB_CONCURRENCY || undefined,
+ // The number of processes to run, defaults to the number of CPU's available
+ // for the web service, and 1 for collaboration during the beta period.
+ count: serviceNames.includes("web")
+ ? process.env.WEB_CONCURRENCY || undefined
+ : serviceNames.includes("collaboration")
+ ? 1
+ : undefined,
});
if (env.ENABLE_UPDATES !== "false" && process.env.NODE_ENV === "production") {
diff --git a/server/migrations/20210730044248-create-realtime.js b/server/migrations/20210730044248-create-realtime.js
new file mode 100644
index 00000000..6750914d
--- /dev/null
+++ b/server/migrations/20210730044248-create-realtime.js
@@ -0,0 +1,17 @@
+"use strict";
+
+module.exports = {
+ up: async (queryInterface, Sequelize) => {
+ await queryInterface.addColumn("documents", "state", {
+ type: Sequelize.BLOB,
+ });
+ await queryInterface.addColumn("teams", "collaborativeEditing", {
+ type: Sequelize.BOOLEAN,
+ });
+ },
+
+ down: async (queryInterface, Sequelize) => {
+ await queryInterface.removeColumn("documents", "state");
+ await queryInterface.removeColumn("teams", "collaborativeEditing");
+ },
+};
diff --git a/server/models/Document.js b/server/models/Document.js
index 52b9f9f3..06fe8fcd 100644
--- a/server/models/Document.js
+++ b/server/models/Document.js
@@ -81,6 +81,7 @@ const Document = sequelize.define(
template: DataTypes.BOOLEAN,
editorVersion: DataTypes.STRING,
text: DataTypes.TEXT,
+ state: DataTypes.BLOB,
isWelcome: { type: DataTypes.BOOLEAN, defaultValue: false },
revisionCount: { type: DataTypes.INTEGER, defaultValue: 0 },
archivedAt: DataTypes.DATE,
diff --git a/server/models/Team.js b/server/models/Team.js
index 6fcfb191..20f229e3 100644
--- a/server/models/Team.js
+++ b/server/models/Team.js
@@ -69,6 +69,11 @@ const Team = sequelize.define(
allowNull: false,
defaultValue: true,
},
+ collaborativeEditing: {
+ type: DataTypes.BOOLEAN,
+ allowNull: false,
+ defaultValue: false,
+ },
},
{
paranoid: true,
diff --git a/server/models/User.js b/server/models/User.js
index af987c60..9d592072 100644
--- a/server/models/User.js
+++ b/server/models/User.js
@@ -7,6 +7,7 @@ import { languages } from "../../shared/i18n";
import { ValidationError } from "../errors";
import { DataTypes, sequelize, encryptedFields, Op } from "../sequelize";
import { DEFAULT_AVATAR_HOST } from "../utils/avatars";
+import { palette } from "../utils/color";
import { publicS3Endpoint, uploadToS3FromUrl } from "../utils/s3";
import {
UserAuthentication,
@@ -74,6 +75,11 @@ const User = sequelize.define(
.digest("hex");
return `${DEFAULT_AVATAR_HOST}/avatar/${hash}/${initial}.png`;
},
+ color() {
+ const idAsHex = crypto.createHash("md5").update(this.id).digest("hex");
+ const idAsNumber = parseInt(idAsHex, 16);
+ return palette[idAsNumber % palette.length];
+ },
},
}
);
diff --git a/server/presenters/__snapshots__/user.test.js.snap b/server/presenters/__snapshots__/user.test.js.snap
index 238edb8f..f080b50c 100644
--- a/server/presenters/__snapshots__/user.test.js.snap
+++ b/server/presenters/__snapshots__/user.test.js.snap
@@ -3,6 +3,7 @@
exports[`presents a user 1`] = `
Object {
"avatarUrl": undefined,
+ "color": undefined,
"createdAt": undefined,
"id": "123",
"isAdmin": undefined,
@@ -16,6 +17,7 @@ Object {
exports[`presents a user without slack data 1`] = `
Object {
"avatarUrl": undefined,
+ "color": undefined,
"createdAt": undefined,
"id": "123",
"isAdmin": undefined,
diff --git a/server/presenters/env.js b/server/presenters/env.js
index e5b42ea6..3b361f6b 100644
--- a/server/presenters/env.js
+++ b/server/presenters/env.js
@@ -4,8 +4,11 @@
// do not add anything here that should be a secret or password
export default function present(env: Object): Object {
return {
- URL: env.URL,
- CDN_URL: env.CDN_URL || "",
+ URL: env.URL.replace(/\/$/, ""),
+ CDN_URL: (env.CDN_URL || "").replace(/\/$/, ""),
+ COLLABORATION_URL: (env.COLLABORATION_URL || "")
+ .replace(/\/$/, "")
+ .replace(/^http/, "ws"),
DEPLOYMENT: env.DEPLOYMENT,
ENVIRONMENT: env.NODE_ENV,
SENTRY_DSN: env.SENTRY_DSN,
diff --git a/server/presenters/team.js b/server/presenters/team.js
index 58eb5b14..3f91c1e3 100644
--- a/server/presenters/team.js
+++ b/server/presenters/team.js
@@ -1,4 +1,5 @@
// @flow
+import env from "../env";
import { Team } from "../models";
export default function present(team: Team) {
@@ -7,6 +8,7 @@ export default function present(team: Team) {
name: team.name,
avatarUrl: team.logoUrl,
sharing: team.sharing,
+ collaborativeEditing: team.collaborativeEditing && env.COLLABORATION_URL,
documentEmbeds: team.documentEmbeds,
guestSignin: team.guestSignin,
subdomain: team.subdomain,
diff --git a/server/presenters/user.js b/server/presenters/user.js
index eec34e63..3c5022f2 100644
--- a/server/presenters/user.js
+++ b/server/presenters/user.js
@@ -10,6 +10,7 @@ type UserPresentation = {
name: string,
avatarUrl: ?string,
email?: string,
+ color: string,
isAdmin: boolean,
isSuspended: boolean,
isViewer: boolean,
@@ -21,6 +22,7 @@ export default (user: User, options: Options = {}): ?UserPresentation => {
userData.id = user.id;
userData.createdAt = user.createdAt;
userData.name = user.name;
+ userData.color = user.color;
userData.isAdmin = user.isAdmin;
userData.isViewer = user.isViewer;
userData.isSuspended = user.isSuspended;
diff --git a/server/queues/processors/backlinks.js b/server/queues/processors/backlinks.js
index 2d6ac4f0..f33f2256 100644
--- a/server/queues/processors/backlinks.js
+++ b/server/queues/processors/backlinks.js
@@ -1,5 +1,5 @@
// @flow
-import { Document, Backlink } from "../../models";
+import { Document, Backlink, Team } from "../../models";
import { Op } from "../../sequelize";
import type { DocumentEvent, RevisionEvent } from "../../types";
import parseDocumentIds from "../../utils/parseDocumentIds";
@@ -78,13 +78,19 @@ export default class BacklinksProcessor {
break;
}
case "documents.title_change": {
- const document = await Document.findByPk(event.documentId);
- if (!document) return;
-
// might as well check
const { title, previousTitle } = event.data;
if (!previousTitle || title === previousTitle) break;
+ const document = await Document.findByPk(event.documentId);
+ if (!document) return;
+
+ // TODO: Handle re-writing of titles into CRDT
+ const team = await Team.findByPk(document.teamId);
+ if (team?.collaborativeEditing) {
+ break;
+ }
+
// update any link titles in documents that lead to this one
const backlinks = await Backlink.findAll({
where: {
diff --git a/server/redis.js b/server/redis.js
index 1d207617..42b10a9f 100644
--- a/server/redis.js
+++ b/server/redis.js
@@ -9,11 +9,12 @@ const options = {
},
// support Heroku Redis, see:
// https://devcenter.heroku.com/articles/heroku-redis#ioredis-module
- tls: process.env.REDIS_URL.startsWith("rediss://")
- ? {
- rejectUnauthorized: false,
- }
- : undefined,
+ tls:
+ process.env.REDIS_URL && process.env.REDIS_URL.startsWith("rediss://")
+ ? {
+ rejectUnauthorized: false,
+ }
+ : undefined,
};
const client = new Redis(process.env.REDIS_URL, options);
diff --git a/server/routes/api/__snapshots__/users.test.js.snap b/server/routes/api/__snapshots__/users.test.js.snap
index 626a5eab..060fd066 100644
--- a/server/routes/api/__snapshots__/users.test.js.snap
+++ b/server/routes/api/__snapshots__/users.test.js.snap
@@ -4,6 +4,7 @@ exports[`#users.activate should activate a suspended user 1`] = `
Object {
"data": Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png",
+ "color": "#e600e0",
"createdAt": "2018-01-02T00:00:00.000Z",
"email": "user1@example.com",
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
@@ -56,6 +57,7 @@ exports[`#users.demote should demote an admin 1`] = `
Object {
"data": Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png",
+ "color": "#e600e0",
"createdAt": "2018-01-02T00:00:00.000Z",
"email": "user1@example.com",
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
@@ -90,6 +92,7 @@ exports[`#users.demote should demote an admin to member 1`] = `
Object {
"data": Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png",
+ "color": "#e600e0",
"createdAt": "2018-01-02T00:00:00.000Z",
"email": "user1@example.com",
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
@@ -124,6 +127,7 @@ exports[`#users.demote should demote an admin to viewer 1`] = `
Object {
"data": Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png",
+ "color": "#e600e0",
"createdAt": "2018-01-02T00:00:00.000Z",
"email": "user1@example.com",
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
@@ -176,6 +180,7 @@ exports[`#users.promote should promote a new admin 1`] = `
Object {
"data": Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png",
+ "color": "#e600e0",
"createdAt": "2018-01-02T00:00:00.000Z",
"email": "user1@example.com",
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
@@ -237,6 +242,7 @@ exports[`#users.suspend should suspend an user 1`] = `
Object {
"data": Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png",
+ "color": "#e600e0",
"createdAt": "2018-01-02T00:00:00.000Z",
"email": "user1@example.com",
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
diff --git a/server/routes/api/hooks.js b/server/routes/api/hooks.js
index 0c36cb16..3d9448b1 100644
--- a/server/routes/api/hooks.js
+++ b/server/routes/api/hooks.js
@@ -13,7 +13,7 @@ import {
IntegrationAuthentication,
} from "../../models";
import { presentSlackAttachment } from "../../presenters";
-import * as Slack from "../../slack";
+import * as Slack from "../../utils/slack";
const router = new Router();
// triggered by a user posting a getoutline.com link in Slack
diff --git a/server/routes/api/hooks.test.js b/server/routes/api/hooks.test.js
index 82108977..b2df3fed 100644
--- a/server/routes/api/hooks.test.js
+++ b/server/routes/api/hooks.test.js
@@ -2,9 +2,9 @@
import TestServer from "fetch-test-server";
import { IntegrationAuthentication, SearchQuery } from "../../models";
import webService from "../../services/web";
-import * as Slack from "../../slack";
import { buildDocument, buildIntegration } from "../../test/factories";
import { flushdb, seed } from "../../test/support";
+import * as Slack from "../../utils/slack";
const app = webService();
const server = new TestServer(app.callback());
@@ -12,7 +12,7 @@ const server = new TestServer(app.callback());
beforeEach(() => flushdb());
afterAll(() => server.close());
-jest.mock("../../slack", () => ({
+jest.mock("../../utils/slack", () => ({
post: jest.fn(),
}));
diff --git a/server/routes/auth/providers/slack.js b/server/routes/auth/providers/slack.js
index ede4e2e7..4cd0e52e 100644
--- a/server/routes/auth/providers/slack.js
+++ b/server/routes/auth/providers/slack.js
@@ -12,8 +12,8 @@ import {
Integration,
Team,
} from "../../../models";
-import * as Slack from "../../../slack";
import { StateStore } from "../../../utils/passport";
+import * as Slack from "../../../utils/slack";
const router = new Router();
const providerName = "slack";
diff --git a/server/sentry.js b/server/sentry.js
deleted file mode 100644
index b2bc8283..00000000
--- a/server/sentry.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// @flow
-import * as Sentry from "@sentry/node";
-import env from "./env";
-
-if (env.SENTRY_DSN) {
- Sentry.init({
- dsn: env.SENTRY_DSN,
- environment: env.ENVIRONMENT,
- release: env.RELEASE,
- maxBreadcrumbs: 0,
- ignoreErrors: [
- // emitted by Koa when bots attempt to snoop on paths such as wp-admin
- // or the user client submits a bad request. These are expected in normal
- // running of the application and don't need to be reported.
- "BadRequestError",
- "UnauthorizedError",
- ],
- });
-}
-
-export default Sentry;
diff --git a/server/services/admin.js b/server/services/admin.js
new file mode 100644
index 00000000..90470285
--- /dev/null
+++ b/server/services/admin.js
@@ -0,0 +1,28 @@
+// @flow
+import http from "http";
+import { createBullBoard } from "@bull-board/api";
+import { BullAdapter } from "@bull-board/api/bullAdapter";
+import { KoaAdapter } from "@bull-board/koa";
+import Koa from "koa";
+import {
+ emailsQueue,
+ globalEventQueue,
+ processorEventQueue,
+ websocketsQueue,
+} from "../queues";
+
+export default function init(app: Koa, server?: http.Server) {
+ const serverAdapter = new KoaAdapter();
+ createBullBoard({
+ queues: [
+ new BullAdapter(globalEventQueue),
+ new BullAdapter(processorEventQueue),
+ new BullAdapter(emailsQueue),
+ new BullAdapter(websocketsQueue),
+ ],
+ serverAdapter,
+ });
+
+ serverAdapter.setBasePath("/admin");
+ app.use(serverAdapter.registerPlugin());
+}
diff --git a/server/services/collaboration.js b/server/services/collaboration.js
new file mode 100644
index 00000000..ff2886d2
--- /dev/null
+++ b/server/services/collaboration.js
@@ -0,0 +1,37 @@
+// @flow
+import http from "http";
+import { Logger } from "@hocuspocus/extension-logger";
+import { Server } from "@hocuspocus/server";
+import Koa from "koa";
+import websocket from "koa-easy-ws";
+import Router from "koa-router";
+import AuthenticationExtension from "../collaboration/authentication";
+import PersistenceExtension from "../collaboration/persistence";
+
+export default function init(app: Koa, server: http.Server) {
+ const router = new Router();
+
+ const hocuspocus = Server.configure({
+ extensions: [
+ new AuthenticationExtension(),
+ new PersistenceExtension(),
+ new Logger(),
+ ],
+ });
+
+ // Websockets for collaborative editing
+ router.get("/collaboration/:documentName", async (ctx) => {
+ let { documentName } = ctx.params;
+
+ if (ctx.ws) {
+ const ws = await ctx.ws();
+ hocuspocus.handleConnection(ws, ctx.request, documentName);
+ }
+
+ ctx.response.status = 101;
+ });
+
+ app.use(websocket());
+ app.use(router.routes());
+ app.use(router.allowedMethods());
+}
diff --git a/server/services/index.js b/server/services/index.js
index ab4383ca..4af106b0 100644
--- a/server/services/index.js
+++ b/server/services/index.js
@@ -1,6 +1,8 @@
// @flow
+import admin from "./admin";
+import collaboration from "./collaboration";
import web from "./web";
import websockets from "./websockets";
import worker from "./worker";
-export default { web, websockets, worker };
+export default { websockets, collaboration, admin, web, worker };
diff --git a/server/services/web.js b/server/services/web.js
index a2df0eb5..ce6b7544 100644
--- a/server/services/web.js
+++ b/server/services/web.js
@@ -7,14 +7,12 @@ import {
referrerPolicy,
} from "koa-helmet";
import mount from "koa-mount";
-import onerror from "koa-onerror";
import enforceHttps from "koa-sslify";
import emails from "../emails";
import env from "../env";
import routes from "../routes";
import api from "../routes/api";
import auth from "../routes/auth";
-import Sentry from "../sentry";
const isProduction = env.NODE_ENV === "production";
const isTest = env.NODE_ENV === "test";
@@ -101,44 +99,6 @@ export default function init(app: Koa = new Koa(), server?: http.Server): Koa {
app.use(mount("/emails", emails));
}
- // catch errors in one place, automatically set status and response headers
- onerror(app);
-
- app.on("error", (error, ctx) => {
- // we don't need to report every time a request stops to the bug tracker
- if (error.code === "EPIPE" || error.code === "ECONNRESET") {
- console.warn("Connection error", { error });
- return;
- }
-
- if (process.env.SENTRY_DSN) {
- Sentry.withScope(function (scope) {
- const requestId = ctx.headers["x-request-id"];
- if (requestId) {
- scope.setTag("request_id", requestId);
- }
-
- const authType = ctx.state ? ctx.state.authType : undefined;
- if (authType) {
- scope.setTag("auth_type", authType);
- }
-
- const userId =
- ctx.state && ctx.state.user ? ctx.state.user.id : undefined;
- if (userId) {
- scope.setUser({ id: userId });
- }
-
- scope.addEventProcessor(function (event) {
- return Sentry.Handlers.parseRequest(event, ctx.request);
- });
- Sentry.captureException(error);
- });
- } else {
- console.error(error);
- }
- });
-
app.use(mount("/auth", auth));
app.use(mount("/api", api));
diff --git a/server/services/websockets.js b/server/services/websockets.js
index 46547a8a..0179f43a 100644
--- a/server/services/websockets.js
+++ b/server/services/websockets.js
@@ -10,14 +10,14 @@ import policy from "../policies";
import { websocketsQueue } from "../queues";
import WebsocketsProcessor from "../queues/processors/websockets";
import { client, subscriber } from "../redis";
-import Sentry from "../sentry";
import { getUserForJWT } from "../utils/jwt";
import * as metrics from "../utils/metrics";
+import Sentry from "../utils/sentry";
const { can } = policy;
-const websockets = new WebsocketsProcessor();
export default function init(app: Koa, server: http.Server) {
+ // Websockets for events and non-collaborative documents
const io = IO(server, {
path: "/realtime",
serveClient: false,
@@ -226,6 +226,9 @@ export default function init(app: Koa, server: http.Server) {
},
});
+ // Handle events from event queue that should be sent to the clients down ws
+ const websockets = new WebsocketsProcessor();
+
websocketsQueue.process(async function websocketEventsProcessor(job) {
const event = job.data;
websockets.on(event, io).catch((error) => {
diff --git a/server/services/worker.js b/server/services/worker.js
index ba4da6f7..2c86da3c 100644
--- a/server/services/worker.js
+++ b/server/services/worker.js
@@ -16,7 +16,7 @@ import Imports from "../queues/processors/imports";
import Notifications from "../queues/processors/notifications";
import Revisions from "../queues/processors/revisions";
import Slack from "../queues/processors/slack";
-import Sentry from "../sentry";
+import Sentry from "../utils/sentry";
const log = debug("queue");
diff --git a/server/utils/args.js b/server/utils/args.js
new file mode 100644
index 00000000..d1f89d5c
--- /dev/null
+++ b/server/utils/args.js
@@ -0,0 +1,21 @@
+// @flow
+
+/**
+ * Get the value of a command line argument
+ *
+ * @param {string} name The name of the argument
+ * @param {string} shortName The optioanl short name
+ *
+ * @returns {string} The value of the argument.
+ */
+export function getArg(name: string, shortName?: string) {
+ return process.argv
+ .slice(2)
+ .filter(
+ (arg) =>
+ arg.startsWith(`--${name}=`) ||
+ (shortName && arg.startsWith(`-${shortName}=`))
+ )
+ .map((arg) => arg.split("=")[1])
+ .join(",");
+}
diff --git a/server/utils/color.js b/server/utils/color.js
new file mode 100644
index 00000000..02b2c09a
--- /dev/null
+++ b/server/utils/color.js
@@ -0,0 +1,20 @@
+// @flow
+import { darken } from "polished";
+import theme from "../../shared/theme";
+
+export const palette = [
+ theme.brand.red,
+ theme.brand.blue,
+ theme.brand.purple,
+ theme.brand.pink,
+ theme.brand.marine,
+ theme.brand.green,
+ theme.brand.yellow,
+ darken(0.2, theme.brand.red),
+ darken(0.2, theme.brand.blue),
+ darken(0.2, theme.brand.purple),
+ darken(0.2, theme.brand.pink),
+ darken(0.2, theme.brand.marine),
+ darken(0.2, theme.brand.green),
+ darken(0.2, theme.brand.yellow),
+];
diff --git a/server/utils/queue.js b/server/utils/queue.js
index 54e079bd..8cfb826b 100644
--- a/server/utils/queue.js
+++ b/server/utils/queue.js
@@ -3,8 +3,8 @@ import Queue from "bull";
import Redis from "ioredis";
import { snakeCase } from "lodash";
import { client, subscriber } from "../redis";
-import Sentry from "../sentry";
import * as metrics from "../utils/metrics";
+import Sentry from "./sentry";
export function createQueue(name: string) {
const prefix = `queue.${snakeCase(name)}`;
diff --git a/server/utils/sentry.js b/server/utils/sentry.js
new file mode 100644
index 00000000..662eb219
--- /dev/null
+++ b/server/utils/sentry.js
@@ -0,0 +1,57 @@
+// @flow
+import * as Sentry from "@sentry/node";
+import env from "../env";
+import type { ContextWithState } from "../types";
+
+if (env.SENTRY_DSN) {
+ Sentry.init({
+ dsn: env.SENTRY_DSN,
+ environment: env.ENVIRONMENT,
+ release: env.RELEASE,
+ maxBreadcrumbs: 0,
+ ignoreErrors: [
+ // emitted by Koa when bots attempt to snoop on paths such as wp-admin
+ // or the user client submits a bad request. These are expected in normal
+ // running of the application and don't need to be reported.
+ "BadRequestError",
+ "UnauthorizedError",
+ ],
+ });
+}
+
+export function requestErrorHandler(error: any, ctx: ContextWithState) {
+ // we don't need to report every time a request stops to the bug tracker
+ if (error.code === "EPIPE" || error.code === "ECONNRESET") {
+ console.warn("Connection error", { error });
+ return;
+ }
+
+ if (process.env.SENTRY_DSN) {
+ Sentry.withScope(function (scope) {
+ const requestId = ctx.headers["x-request-id"];
+ if (requestId) {
+ scope.setTag("request_id", requestId);
+ }
+
+ const authType = ctx.state ? ctx.state.authType : undefined;
+ if (authType) {
+ scope.setTag("auth_type", authType);
+ }
+
+ const userId =
+ ctx.state && ctx.state.user ? ctx.state.user.id : undefined;
+ if (userId) {
+ scope.setUser({ id: userId });
+ }
+
+ scope.addEventProcessor(function (event) {
+ return Sentry.Handlers.parseRequest(event, ctx.request);
+ });
+ Sentry.captureException(error);
+ });
+ } else {
+ console.error(error);
+ }
+}
+
+export default Sentry;
diff --git a/server/slack.js b/server/utils/slack.js
similarity index 96%
rename from server/slack.js
rename to server/utils/slack.js
index 9045108f..ed78b507 100644
--- a/server/slack.js
+++ b/server/utils/slack.js
@@ -1,7 +1,7 @@
// @flow
import querystring from "querystring";
import fetch from "fetch-with-proxy";
-import { InvalidRequestError } from "./errors";
+import { InvalidRequestError } from "../errors";
const SLACK_API_URL = "https://slack.com/api";
diff --git a/app/embeds/Abstract.js b/shared/embeds/Abstract.js
similarity index 100%
rename from app/embeds/Abstract.js
rename to shared/embeds/Abstract.js
diff --git a/app/embeds/Abstract.test.js b/shared/embeds/Abstract.test.js
similarity index 100%
rename from app/embeds/Abstract.test.js
rename to shared/embeds/Abstract.test.js
diff --git a/app/embeds/Airtable.js b/shared/embeds/Airtable.js
similarity index 100%
rename from app/embeds/Airtable.js
rename to shared/embeds/Airtable.js
diff --git a/app/embeds/Airtable.test.js b/shared/embeds/Airtable.test.js
similarity index 100%
rename from app/embeds/Airtable.test.js
rename to shared/embeds/Airtable.test.js
diff --git a/app/embeds/Cawemo.js b/shared/embeds/Cawemo.js
similarity index 100%
rename from app/embeds/Cawemo.js
rename to shared/embeds/Cawemo.js
diff --git a/app/embeds/Cawemo.test.js b/shared/embeds/Cawemo.test.js
similarity index 100%
rename from app/embeds/Cawemo.test.js
rename to shared/embeds/Cawemo.test.js
diff --git a/app/embeds/ClickUp.js b/shared/embeds/ClickUp.js
similarity index 100%
rename from app/embeds/ClickUp.js
rename to shared/embeds/ClickUp.js
diff --git a/app/embeds/ClickUp.test.js b/shared/embeds/ClickUp.test.js
similarity index 100%
rename from app/embeds/ClickUp.test.js
rename to shared/embeds/ClickUp.test.js
diff --git a/app/embeds/Codepen.js b/shared/embeds/Codepen.js
similarity index 100%
rename from app/embeds/Codepen.js
rename to shared/embeds/Codepen.js
diff --git a/app/embeds/Codepen.test.js b/shared/embeds/Codepen.test.js
similarity index 100%
rename from app/embeds/Codepen.test.js
rename to shared/embeds/Codepen.test.js
diff --git a/app/embeds/Descript.js b/shared/embeds/Descript.js
similarity index 100%
rename from app/embeds/Descript.js
rename to shared/embeds/Descript.js
diff --git a/app/embeds/Diagrams.js b/shared/embeds/Diagrams.js
similarity index 96%
rename from app/embeds/Diagrams.js
rename to shared/embeds/Diagrams.js
index 0669ed78..eca9c54f 100644
--- a/app/embeds/Diagrams.js
+++ b/shared/embeds/Diagrams.js
@@ -1,7 +1,7 @@
// @flow
import * as React from "react";
-import Image from "components/Image";
import Frame from "./components/Frame";
+import Image from "./components/Image";
const URL_REGEX = new RegExp("^https://viewer.diagrams.net/.*(title=\\w+)?");
diff --git a/app/embeds/Diagrams.test.js b/shared/embeds/Diagrams.test.js
similarity index 100%
rename from app/embeds/Diagrams.test.js
rename to shared/embeds/Diagrams.test.js
diff --git a/app/embeds/Figma.js b/shared/embeds/Figma.js
similarity index 100%
rename from app/embeds/Figma.js
rename to shared/embeds/Figma.js
diff --git a/app/embeds/Figma.test.js b/shared/embeds/Figma.test.js
similarity index 100%
rename from app/embeds/Figma.test.js
rename to shared/embeds/Figma.test.js
diff --git a/app/embeds/Framer.js b/shared/embeds/Framer.js
similarity index 100%
rename from app/embeds/Framer.js
rename to shared/embeds/Framer.js
diff --git a/app/embeds/Framer.test.js b/shared/embeds/Framer.test.js
similarity index 100%
rename from app/embeds/Framer.test.js
rename to shared/embeds/Framer.test.js
diff --git a/app/embeds/Gist.js b/shared/embeds/Gist.js
similarity index 100%
rename from app/embeds/Gist.js
rename to shared/embeds/Gist.js
diff --git a/app/embeds/Gist.test.js b/shared/embeds/Gist.test.js
similarity index 100%
rename from app/embeds/Gist.test.js
rename to shared/embeds/Gist.test.js
diff --git a/app/embeds/GoogleCalendar.js b/shared/embeds/GoogleCalendar.js
similarity index 100%
rename from app/embeds/GoogleCalendar.js
rename to shared/embeds/GoogleCalendar.js
diff --git a/app/embeds/GoogleCalendar.test.js b/shared/embeds/GoogleCalendar.test.js
similarity index 100%
rename from app/embeds/GoogleCalendar.test.js
rename to shared/embeds/GoogleCalendar.test.js
diff --git a/app/embeds/GoogleDataStudio.js b/shared/embeds/GoogleDataStudio.js
similarity index 95%
rename from app/embeds/GoogleDataStudio.js
rename to shared/embeds/GoogleDataStudio.js
index 24c00b6b..2ca4dd29 100644
--- a/app/embeds/GoogleDataStudio.js
+++ b/shared/embeds/GoogleDataStudio.js
@@ -1,7 +1,7 @@
// @flow
import * as React from "react";
-import Image from "components/Image";
import Frame from "./components/Frame";
+import Image from "./components/Image";
const URL_REGEX = new RegExp(
"^https?://datastudio.google.com/(embed|u/0)/reporting/(.*)/page/(.*)(/edit)?$"
diff --git a/app/embeds/GoogleDataStudio.test.js b/shared/embeds/GoogleDataStudio.test.js
similarity index 100%
rename from app/embeds/GoogleDataStudio.test.js
rename to shared/embeds/GoogleDataStudio.test.js
diff --git a/app/embeds/GoogleDocs.js b/shared/embeds/GoogleDocs.js
similarity index 94%
rename from app/embeds/GoogleDocs.js
rename to shared/embeds/GoogleDocs.js
index fd5a4263..a185b531 100644
--- a/app/embeds/GoogleDocs.js
+++ b/shared/embeds/GoogleDocs.js
@@ -1,7 +1,7 @@
// @flow
import * as React from "react";
-import Image from "components/Image";
import Frame from "./components/Frame";
+import Image from "./components/Image";
const URL_REGEX = new RegExp("^https?://docs.google.com/document/(.*)$");
diff --git a/app/embeds/GoogleDocs.test.js b/shared/embeds/GoogleDocs.test.js
similarity index 100%
rename from app/embeds/GoogleDocs.test.js
rename to shared/embeds/GoogleDocs.test.js
diff --git a/app/embeds/GoogleDrawings.js b/shared/embeds/GoogleDrawings.js
similarity index 95%
rename from app/embeds/GoogleDrawings.js
rename to shared/embeds/GoogleDrawings.js
index 6be5db51..79053976 100644
--- a/app/embeds/GoogleDrawings.js
+++ b/shared/embeds/GoogleDrawings.js
@@ -1,7 +1,7 @@
// @flow
import * as React from "react";
-import Image from "components/Image";
import Frame from "./components/Frame";
+import Image from "./components/Image";
const URL_REGEX = new RegExp(
"^https://docs.google.com/drawings/d/(.*)/(edit|preview)(.*)$"
diff --git a/app/embeds/GoogleDrawings.test.js b/shared/embeds/GoogleDrawings.test.js
similarity index 100%
rename from app/embeds/GoogleDrawings.test.js
rename to shared/embeds/GoogleDrawings.test.js
diff --git a/app/embeds/GoogleDrive.js b/shared/embeds/GoogleDrive.js
similarity index 94%
rename from app/embeds/GoogleDrive.js
rename to shared/embeds/GoogleDrive.js
index 12bb07a2..e75eb33b 100644
--- a/app/embeds/GoogleDrive.js
+++ b/shared/embeds/GoogleDrive.js
@@ -1,7 +1,7 @@
// @flow
import * as React from "react";
-import Image from "components/Image";
import Frame from "./components/Frame";
+import Image from "./components/Image";
const URL_REGEX = new RegExp("^https?://drive.google.com/file/d/(.*)$");
diff --git a/app/embeds/GoogleDrive.test.js b/shared/embeds/GoogleDrive.test.js
similarity index 100%
rename from app/embeds/GoogleDrive.test.js
rename to shared/embeds/GoogleDrive.test.js
diff --git a/app/embeds/GoogleSheets.js b/shared/embeds/GoogleSheets.js
similarity index 95%
rename from app/embeds/GoogleSheets.js
rename to shared/embeds/GoogleSheets.js
index 70a3d53e..070964eb 100644
--- a/app/embeds/GoogleSheets.js
+++ b/shared/embeds/GoogleSheets.js
@@ -1,7 +1,7 @@
// @flow
import * as React from "react";
-import Image from "components/Image";
import Frame from "./components/Frame";
+import Image from "./components/Image";
const URL_REGEX = new RegExp("^https?://docs.google.com/spreadsheets/d/(.*)$");
diff --git a/app/embeds/GoogleSheets.test.js b/shared/embeds/GoogleSheets.test.js
similarity index 100%
rename from app/embeds/GoogleSheets.test.js
rename to shared/embeds/GoogleSheets.test.js
diff --git a/app/embeds/GoogleSlides.js b/shared/embeds/GoogleSlides.js
similarity index 95%
rename from app/embeds/GoogleSlides.js
rename to shared/embeds/GoogleSlides.js
index cc961a4f..05a3da6a 100644
--- a/app/embeds/GoogleSlides.js
+++ b/shared/embeds/GoogleSlides.js
@@ -1,7 +1,7 @@
// @flow
import * as React from "react";
-import Image from "components/Image";
import Frame from "./components/Frame";
+import Image from "./components/Image";
const URL_REGEX = new RegExp("^https?://docs.google.com/presentation/d/(.*)$");
diff --git a/app/embeds/GoogleSlides.test.js b/shared/embeds/GoogleSlides.test.js
similarity index 100%
rename from app/embeds/GoogleSlides.test.js
rename to shared/embeds/GoogleSlides.test.js
diff --git a/app/embeds/InVision.js b/shared/embeds/InVision.js
similarity index 100%
rename from app/embeds/InVision.js
rename to shared/embeds/InVision.js
diff --git a/app/embeds/InVision.test.js b/shared/embeds/InVision.test.js
similarity index 100%
rename from app/embeds/InVision.test.js
rename to shared/embeds/InVision.test.js
diff --git a/app/embeds/Loom.js b/shared/embeds/Loom.js
similarity index 100%
rename from app/embeds/Loom.js
rename to shared/embeds/Loom.js
diff --git a/app/embeds/Loom.test.js b/shared/embeds/Loom.test.js
similarity index 100%
rename from app/embeds/Loom.test.js
rename to shared/embeds/Loom.test.js
diff --git a/app/embeds/Lucidchart.js b/shared/embeds/Lucidchart.js
similarity index 100%
rename from app/embeds/Lucidchart.js
rename to shared/embeds/Lucidchart.js
diff --git a/app/embeds/Lucidchart.test.js b/shared/embeds/Lucidchart.test.js
similarity index 100%
rename from app/embeds/Lucidchart.test.js
rename to shared/embeds/Lucidchart.test.js
diff --git a/app/embeds/Marvel.js b/shared/embeds/Marvel.js
similarity index 100%
rename from app/embeds/Marvel.js
rename to shared/embeds/Marvel.js
diff --git a/app/embeds/Marvel.test.js b/shared/embeds/Marvel.test.js
similarity index 100%
rename from app/embeds/Marvel.test.js
rename to shared/embeds/Marvel.test.js
diff --git a/app/embeds/Mindmeister.js b/shared/embeds/Mindmeister.js
similarity index 100%
rename from app/embeds/Mindmeister.js
rename to shared/embeds/Mindmeister.js
diff --git a/app/embeds/Mindmeister.test.js b/shared/embeds/Mindmeister.test.js
similarity index 100%
rename from app/embeds/Mindmeister.test.js
rename to shared/embeds/Mindmeister.test.js
diff --git a/app/embeds/Miro.js b/shared/embeds/Miro.js
similarity index 100%
rename from app/embeds/Miro.js
rename to shared/embeds/Miro.js
diff --git a/app/embeds/Miro.test.js b/shared/embeds/Miro.test.js
similarity index 100%
rename from app/embeds/Miro.test.js
rename to shared/embeds/Miro.test.js
diff --git a/app/embeds/ModeAnalytics.js b/shared/embeds/ModeAnalytics.js
similarity index 100%
rename from app/embeds/ModeAnalytics.js
rename to shared/embeds/ModeAnalytics.js
diff --git a/app/embeds/ModeAnalytics.test.js b/shared/embeds/ModeAnalytics.test.js
similarity index 100%
rename from app/embeds/ModeAnalytics.test.js
rename to shared/embeds/ModeAnalytics.test.js
diff --git a/app/embeds/Prezi.js b/shared/embeds/Prezi.js
similarity index 100%
rename from app/embeds/Prezi.js
rename to shared/embeds/Prezi.js
diff --git a/app/embeds/Prezi.test.js b/shared/embeds/Prezi.test.js
similarity index 100%
rename from app/embeds/Prezi.test.js
rename to shared/embeds/Prezi.test.js
diff --git a/app/embeds/Spotify.js b/shared/embeds/Spotify.js
similarity index 100%
rename from app/embeds/Spotify.js
rename to shared/embeds/Spotify.js
diff --git a/app/embeds/Spotify.test.js b/shared/embeds/Spotify.test.js
similarity index 100%
rename from app/embeds/Spotify.test.js
rename to shared/embeds/Spotify.test.js
diff --git a/app/embeds/Trello.js b/shared/embeds/Trello.js
similarity index 100%
rename from app/embeds/Trello.js
rename to shared/embeds/Trello.js
diff --git a/app/embeds/Typeform.js b/shared/embeds/Typeform.js
similarity index 100%
rename from app/embeds/Typeform.js
rename to shared/embeds/Typeform.js
diff --git a/app/embeds/Typeform.test.js b/shared/embeds/Typeform.test.js
similarity index 100%
rename from app/embeds/Typeform.test.js
rename to shared/embeds/Typeform.test.js
diff --git a/app/embeds/Vimeo.js b/shared/embeds/Vimeo.js
similarity index 100%
rename from app/embeds/Vimeo.js
rename to shared/embeds/Vimeo.js
diff --git a/app/embeds/Vimeo.test.js b/shared/embeds/Vimeo.test.js
similarity index 100%
rename from app/embeds/Vimeo.test.js
rename to shared/embeds/Vimeo.test.js
diff --git a/app/embeds/YouTube.js b/shared/embeds/YouTube.js
similarity index 100%
rename from app/embeds/YouTube.js
rename to shared/embeds/YouTube.js
diff --git a/app/embeds/YouTube.test.js b/shared/embeds/YouTube.test.js
similarity index 100%
rename from app/embeds/YouTube.test.js
rename to shared/embeds/YouTube.test.js
diff --git a/app/embeds/components/Frame.js b/shared/embeds/components/Frame.js
similarity index 98%
rename from app/embeds/components/Frame.js
rename to shared/embeds/components/Frame.js
index 2cd79624..0cc1e5d0 100644
--- a/app/embeds/components/Frame.js
+++ b/shared/embeds/components/Frame.js
@@ -4,7 +4,6 @@ import { observer } from "mobx-react";
import { OpenIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
-import Flex from "components/Flex";
// This wrapper allows us to pass non-standard HTML attributes through to the DOM element
// https://www.styled-components.com/docs/basics#passed-props
@@ -130,7 +129,9 @@ const Title = styled.span`
padding-left: 4px;
`;
-const Bar = styled(Flex)`
+const Bar = styled.div`
+ display: flex;
+ align-items: center;
border-top: 1px solid ${(props) => props.theme.embedBorder};
background: ${(props) => props.theme.secondaryBackground};
color: ${(props) => props.theme.textSecondary};
diff --git a/app/components/Image.js b/shared/embeds/components/Image.js
similarity index 85%
rename from app/components/Image.js
rename to shared/embeds/components/Image.js
index a8bf88a8..8fc67ab8 100644
--- a/app/components/Image.js
+++ b/shared/embeds/components/Image.js
@@ -1,6 +1,6 @@
// @flow
import * as React from "react";
-import { cdnPath } from "utils/urls";
+import { cdnPath } from "../../utils/urls";
type Props = {|
alt: string,
diff --git a/app/embeds/index.js b/shared/embeds/index.js
similarity index 99%
rename from app/embeds/index.js
rename to shared/embeds/index.js
index 2ee824d5..3f53c865 100644
--- a/app/embeds/index.js
+++ b/shared/embeds/index.js
@@ -1,7 +1,6 @@
// @flow
import * as React from "react";
import styled from "styled-components";
-import Image from "components/Image";
import Abstract from "./Abstract";
import Airtable from "./Airtable";
import Cawemo from "./Cawemo";
@@ -32,6 +31,7 @@ import Trello from "./Trello";
import Typeform from "./Typeform";
import Vimeo from "./Vimeo";
import YouTube from "./YouTube";
+import Image from "./components/Image";
function matcher(Component) {
return (url: string) => {
diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json
index 63bd46b8..d7395722 100644
--- a/shared/i18n/locales/en_US/translation.json
+++ b/shared/i18n/locales/en_US/translation.json
@@ -8,6 +8,8 @@
"Add a description": "Add a description",
"Collapse": "Collapse",
"Expand": "Expand",
+ "Server connection lost": "Server connection lost",
+ "Edits you make will sync once you’re online": "Edits you make will sync once you’re online",
"Submenu": "Submenu",
"Trash": "Trash",
"Archive": "Archive",
@@ -178,6 +180,7 @@
"Changelog": "Changelog",
"Send us feedback": "Send us feedback",
"Report a bug": "Report a bug",
+ "Development": "Development",
"Appearance": "Appearance",
"System": "System",
"Light": "Light",
@@ -350,6 +353,7 @@
"New from template": "New from template",
"Publish": "Publish",
"Publishing": "Publishing",
+ "Sorry, it looks like you don’t have permission to access the document": "Sorry, it looks like you don’t have permission to access the document",
"Nested documents": "Nested documents",
"Anyone with the link <1>1>can view this document": "Anyone with the link <1>1>can view this document",
"Share": "Share",
diff --git a/shared/theme.js b/shared/theme.js
index 0e355085..c0b9f7b6 100644
--- a/shared/theme.js
+++ b/shared/theme.js
@@ -40,6 +40,7 @@ const colors = {
blue: "#3633FF",
marine: "#2BC2FF",
green: "#42DED1",
+ yellow: "#F5BE31",
},
};
diff --git a/shared/utils/domains.js b/shared/utils/domains.js
index 25eb8679..496dae05 100644
--- a/shared/utils/domains.js
+++ b/shared/utils/domains.js
@@ -102,6 +102,7 @@ export const RESERVED_SUBDOMAINS = [
"mail",
"marketing",
"mobile",
+ "multiplayer",
"new",
"news",
"newsletter",
@@ -111,6 +112,7 @@ export const RESERVED_SUBDOMAINS = [
"ns4",
"password",
"profile",
+ "realtime",
"sandbox",
"script",
"scripts",
diff --git a/shared/utils/urls.js b/shared/utils/urls.js
new file mode 100644
index 00000000..c7de2298
--- /dev/null
+++ b/shared/utils/urls.js
@@ -0,0 +1,6 @@
+// @flow
+const env = typeof window !== "undefined" ? window.env : process.env;
+
+export function cdnPath(path: string): string {
+ return `${env.CDN_URL}${path}`;
+}
diff --git a/yarn.lock b/yarn.lock
index 0da83c2d..fb6e1b1d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -978,10 +978,10 @@
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.5.tgz#665450911c6031af38f81db530f387ec04cd9a98"
- integrity sha512-121rumjddw9c3NCQ55KGkyE1h/nzWhU/owjhw0l4mQrkzz4x9SGS1X8gFLraHwX7td3Yo4QTL+qj0NcIzN87BA==
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.3.1", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+ version "7.15.3"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b"
+ integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==
dependencies:
regenerator-runtime "^0.13.4"
@@ -1022,6 +1022,36 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
+"@bull-board/api@3.5.0", "@bull-board/api@^3.5.0":
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.5.0.tgz#9a678a3fc89663bf7cb2cade7c99f3ab7deeff71"
+ integrity sha512-IeNH3UMvxxfxmT5zimUfFps4093tIaWO5L7Mpxb2OdFvbcP/Xd8Nm+f1w/dpKj+aLd2acQbPD4OCra35GLqgOQ==
+ dependencies:
+ redis-info "^3.0.8"
+
+"@bull-board/koa@^3.5.0":
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.5.0.tgz#7e0683525483c61f08e9ca96e75eec5497eb2316"
+ integrity sha512-0JGUtWtV5I1jVZqW7JYLiCMyPImGyv6B5xOGxT2pyWQHQAfe7NJBdNBl8CsJjU4FwV70LWlLg2rL+kpzNAJdJQ==
+ dependencies:
+ "@bull-board/api" "3.5.0"
+ "@bull-board/ui" "3.5.0"
+ ejs "^3.1.6"
+ koa "^2.13.1"
+ koa-mount "^4.0.0"
+ koa-router "^10.0.0"
+ koa-static "^5.0.0"
+ koa-views "^7.0.1"
+
+"@bull-board/ui@3.5.0":
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.5.0.tgz#7dec7ea4987b65a06abce91952ed8f923eb58d20"
+ integrity sha512-D7ScfrleLuTsXs19yteqT7EmesEfBgercXl7hSA1LFdevesAwW/u+qdupBBom/Dit9ejTYafaOzzxFDPvszWxw==
+ dependencies:
+ "@bull-board/api" "3.5.0"
+ "@radix-ui/react-alert-dialog" "^0.0.19"
+ "@radix-ui/react-dropdown-menu" "^0.0.22"
+
"@bundle-stats/utils@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@bundle-stats/utils/-/utils-3.0.0.tgz#8578b957bbad3a71b6137ca6541f2ee81f1f176b"
@@ -1116,6 +1146,36 @@
dependencies:
"@hapi/hoek" "^8.3.0"
+"@hocuspocus/extension-logger@^1.0.0-alpha.43":
+ version "1.0.0-alpha.43"
+ resolved "https://registry.yarnpkg.com/@hocuspocus/extension-logger/-/extension-logger-1.0.0-alpha.43.tgz#66224ba5bf634ba1a1b6be6156d5d55d515efba6"
+ integrity sha512-PdIG7egACqNpRzWR/vX5BRVlvTRjOsT1MwK1h+6T/ICI8aP6WOsJ5ruqPwJGN0vLbffZK+IH9mfxALHa6NHVlA==
+ dependencies:
+ "@hocuspocus/server" "^1.0.0-alpha.68"
+
+"@hocuspocus/provider@^1.0.0-alpha.13":
+ version "1.0.0-alpha.13"
+ resolved "https://registry.yarnpkg.com/@hocuspocus/provider/-/provider-1.0.0-alpha.13.tgz#40c65168d1bed35bef0196df40512326825bfa51"
+ integrity sha512-/L6GmV7bEEwd8VIiodOynQjQUaEHUT4hQIkzNMtsICUkiu3OQs35ZrC973BxSp0t7df6KOo3kSULBpp/TW8wGg==
+ dependencies:
+ lib0 "^0.2.42"
+ y-protocols "^1.0.5"
+ yjs "^13.5.8"
+
+"@hocuspocus/server@^1.0.0-alpha.68":
+ version "1.0.0-alpha.68"
+ resolved "https://registry.yarnpkg.com/@hocuspocus/server/-/server-1.0.0-alpha.68.tgz#19a061718f0cdebab83fe0847386a556e7753e5c"
+ integrity sha512-phuuW8KFHmcufhatvIysq3iiXYH1Utd8+dWW0YNM90BA79A6rmx32DcQT3fton/wjcqU8rzoeZQomemghfSxBQ==
+ dependencies:
+ "@types/async-lock" "^1.1.2"
+ "@types/uuid" "^8.3.0"
+ "@types/ws" "^7.4.0"
+ async-lock "^1.2.8"
+ lib0 "^0.2.41"
+ uuid "^8.3.2"
+ ws "^7.4.3"
+ yjs "^13.5.0"
+
"@icons/material@^0.2.4":
version "0.2.4"
resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
@@ -1800,6 +1860,306 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
+"@radix-ui/popper@0.0.10":
+ version "0.0.10"
+ resolved "https://registry.yarnpkg.com/@radix-ui/popper/-/popper-0.0.10.tgz#9f707d9cec8762423f81acaf8e650e40a554cb73"
+ integrity sha512-YFKuPqQPKscreQid7NuB4it3PMzSwGg03vgrud6sVliHkI43QNAOHyrHyMNo015jg6QK5GVDn+7J2W5uygqSGA==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ csstype "^3.0.4"
+
+"@radix-ui/primitive@0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-0.0.5.tgz#8464fb4db04401bde72d36e27e05714080668d40"
+ integrity sha512-VeL6A5LpKYRJhDDj5tCTnzP3zm+FnvybsAkgBHQ4LUPPBnqRdWLoyKpZhlwFze/z22QHINaTIcE9Z/fTcrUR1g==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
+"@radix-ui/react-alert-dialog@^0.0.19":
+ version "0.0.19"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-0.0.19.tgz#5b69bfe063cdb13f49630ad2705e71228505d147"
+ integrity sha512-SJRUT2s0/WLCvCEbfuKL5EM6QNXjZQkX9ZgkwKvgRNYu5zYEmCmlCUWDJbPIX1Y7w/a6tuEm24f3Uywd8VcBxw==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "0.0.5"
+ "@radix-ui/react-compose-refs" "0.0.5"
+ "@radix-ui/react-context" "0.0.5"
+ "@radix-ui/react-dialog" "0.0.19"
+ "@radix-ui/react-polymorphic" "0.0.12"
+ "@radix-ui/react-primitive" "0.0.14"
+ "@radix-ui/react-slot" "0.0.12"
+
+"@radix-ui/react-arrow@0.0.14":
+ version "0.0.14"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.0.14.tgz#70b2c66efbf3cde0c9dd0895417e39f6cdf31805"
+ integrity sha512-ZWfQM3hTCXN6ub2dMUmMv2ttEB/1zuBlOZdeWFeCCJHwZ+yy7iPzWF6ixaNygBa6t451PImq/dC7sqOnDJCfqQ==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-polymorphic" "0.0.12"
+ "@radix-ui/react-primitive" "0.0.14"
+
+"@radix-ui/react-collection@0.0.14":
+ version "0.0.14"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.0.14.tgz#c51c790100407e8eb38be2b67538a5ff7c52915e"
+ integrity sha512-IM3ttcjArKWItPY7vaCHyqWFSGK+k4Lb0V7RC8vCgB0LXEep5YQ1d30o4TNAqiRX3+Mz7T0zg+gDCwK86visdg==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-compose-refs" "0.0.5"
+ "@radix-ui/react-slot" "0.0.12"
+
+"@radix-ui/react-compose-refs@0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.0.5.tgz#0f71f0de1dec341f30cebd420b6bc3d12a3037dd"
+ integrity sha512-O9mH9X/2EwuAEEoZXrU4alcrRbAhhZHGpIJ5bOH6rmRcokhaoWRBY1tOEe2lgHdb/bkKrY+viLi4Zq8Ju6/09Q==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
+"@radix-ui/react-context@0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-0.0.5.tgz#7c15f46795d7765dabfaf6f9c53791ad28c521c2"
+ integrity sha512-bwrzAc0qc2EPepSTLBT4+93uCiI9wP78VSmPg2K+k71O/vpx7dPs0VqrewwCBNCHT54NIwaRr2hEsm2uqYi02A==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
+"@radix-ui/react-dialog@0.0.19":
+ version "0.0.19"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.0.19.tgz#5a76fa380142a7a97c15c585ab071f63fba5297d"
+ integrity sha512-7FbWaj/C/TDpfJ+VJ4wNAQIjENDNfwAqNvAfeb+TEtBjgjmsfRDgA1AMenlA5N1QuRtAokRMTHUs3ukW49oQ+g==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "0.0.5"
+ "@radix-ui/react-compose-refs" "0.0.5"
+ "@radix-ui/react-context" "0.0.5"
+ "@radix-ui/react-dismissable-layer" "0.0.14"
+ "@radix-ui/react-focus-guards" "0.0.7"
+ "@radix-ui/react-focus-scope" "0.0.14"
+ "@radix-ui/react-id" "0.0.6"
+ "@radix-ui/react-polymorphic" "0.0.12"
+ "@radix-ui/react-portal" "0.0.14"
+ "@radix-ui/react-presence" "0.0.14"
+ "@radix-ui/react-primitive" "0.0.14"
+ "@radix-ui/react-slot" "0.0.12"
+ "@radix-ui/react-use-controllable-state" "0.0.6"
+ aria-hidden "^1.1.1"
+ react-remove-scroll "^2.4.0"
+
+"@radix-ui/react-dismissable-layer@0.0.14":
+ version "0.0.14"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.0.14.tgz#9d8a3415a2830688070c6596dec18b43c33df7b2"
+ integrity sha512-0pmRuGYYvWlEaED1igGFLjic0+hD0OqvsnrZaN3n1nDOkoCd7H5CA2geaShSrlBF5riI2Dr9jIZPGLbDRhs4DA==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "0.0.5"
+ "@radix-ui/react-polymorphic" "0.0.12"
+ "@radix-ui/react-primitive" "0.0.14"
+ "@radix-ui/react-use-body-pointer-events" "0.0.6"
+ "@radix-ui/react-use-callback-ref" "0.0.5"
+ "@radix-ui/react-use-escape-keydown" "0.0.6"
+
+"@radix-ui/react-dropdown-menu@^0.0.22":
+ version "0.0.22"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-0.0.22.tgz#914dbbb12d31b4379df697f1262150b3f50916ae"
+ integrity sha512-/2EnuNKPZjM+MZOsL7X+EOZGBXbJsWH6Rkrrk7etoajZzng48nINVm67aJQERue44LSO8zRJa+XbxS9/g9SvLg==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "0.0.5"
+ "@radix-ui/react-compose-refs" "0.0.5"
+ "@radix-ui/react-context" "0.0.5"
+ "@radix-ui/react-id" "0.0.6"
+ "@radix-ui/react-menu" "0.0.21"
+ "@radix-ui/react-polymorphic" "0.0.12"
+ "@radix-ui/react-primitive" "0.0.14"
+ "@radix-ui/react-use-controllable-state" "0.0.6"
+
+"@radix-ui/react-focus-guards@0.0.7":
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-0.0.7.tgz#285ed081c877587acd4ee7e6d8260bdf9044e922"
+ integrity sha512-enAsmrUunptHVzPLTuZqwTP/X3WFBnyJ/jP9W+0g+bRvI3o7V1kxNc+T2Rp1eRTFBW+lUNnt08qkugPytyTRog==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
+"@radix-ui/react-focus-scope@0.0.14":
+ version "0.0.14"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-0.0.14.tgz#778e2a3ea607621d82e0139616d7ea6d517d9533"
+ integrity sha512-D3v6Tw8vzpIBNd2I32Q2G4LCiXMIlmc6Pl2VV9CZjSatDOjkV/ckGbhkQyQ7QxnD/0CmiSxNo5hTeGRmZDjwmA==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-compose-refs" "0.0.5"
+ "@radix-ui/react-polymorphic" "0.0.12"
+ "@radix-ui/react-primitive" "0.0.14"
+ "@radix-ui/react-use-callback-ref" "0.0.5"
+
+"@radix-ui/react-id@0.0.6":
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.0.6.tgz#c4b27d11861805e91ac296e7758ab47e3947b65c"
+ integrity sha512-PzmraF34fYggsYvTIZVJ5S68WMp3aKUN3HkSmGnz4zn9zpRjkAbbg7Xn3ueQI3FQsLWKgyUfnpsmWFDndpcqYg==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
+"@radix-ui/react-menu@0.0.21":
+ version "0.0.21"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-0.0.21.tgz#a0a2402e1bd11537af5a43f82a7a6f113abf00c6"
+ integrity sha512-zUhG4pFcHhvsCTHTqGfrury9FQTSbvkclxVIfZ0I6pkCA5wEXJNMmkztCQe1QZT6lZlTuQRPybbbLrt8j3DVAA==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "0.0.5"
+ "@radix-ui/react-collection" "0.0.14"
+ "@radix-ui/react-compose-refs" "0.0.5"
+ "@radix-ui/react-context" "0.0.5"
+ "@radix-ui/react-dismissable-layer" "0.0.14"
+ "@radix-ui/react-focus-guards" "0.0.7"
+ "@radix-ui/react-focus-scope" "0.0.14"
+ "@radix-ui/react-id" "0.0.6"
+ "@radix-ui/react-polymorphic" "0.0.12"
+ "@radix-ui/react-popper" "0.0.17"
+ "@radix-ui/react-portal" "0.0.14"
+ "@radix-ui/react-presence" "0.0.14"
+ "@radix-ui/react-primitive" "0.0.14"
+ "@radix-ui/react-roving-focus" "0.0.15"
+ "@radix-ui/react-slot" "0.0.12"
+ "@radix-ui/react-use-callback-ref" "0.0.5"
+ "@radix-ui/react-use-direction" "0.0.1"
+ aria-hidden "^1.1.1"
+ react-remove-scroll "^2.4.0"
+
+"@radix-ui/react-polymorphic@0.0.12":
+ version "0.0.12"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.12.tgz#bf4ae516669b68e059549538104d97322f7c876b"
+ integrity sha512-/GYNMicBnGzjD1d2fCAuzql1VeFrp8mqM3xfzT1kxhnV85TKdURO45jBfMgqo17XNXoNhWIAProUsCO4qFAAIg==
+
+"@radix-ui/react-popper@0.0.17":
+ version "0.0.17"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.0.17.tgz#a73486e19a628cb3fecaf3fb6eecf6e2cab9d0be"
+ integrity sha512-2Lk5AjlCcN9B6fQwQ+y1Zb93f1LfyCK6u0oOAUsPtwbnYEHCdC6wuCFBOZ7AonpjHbrHbY6QDrLGcC43TY6ihw==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/popper" "0.0.10"
+ "@radix-ui/react-arrow" "0.0.14"
+ "@radix-ui/react-compose-refs" "0.0.5"
+ "@radix-ui/react-context" "0.0.5"
+ "@radix-ui/react-polymorphic" "0.0.12"
+ "@radix-ui/react-primitive" "0.0.14"
+ "@radix-ui/react-use-rect" "0.0.7"
+ "@radix-ui/react-use-size" "0.0.6"
+ "@radix-ui/rect" "0.0.5"
+
+"@radix-ui/react-portal@0.0.14":
+ version "0.0.14"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.0.14.tgz#31513d8777cf5e50a3a30ebc9deb34821e890e9e"
+ integrity sha512-Wi9arVwVenonjZIX6znCBYaasua03Tb+UtrBZShepJkLGtbGxDlzExijiGIaIRNetl46Oc2pw0F6Y6HffDnUww==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-polymorphic" "0.0.12"
+ "@radix-ui/react-primitive" "0.0.14"
+ "@radix-ui/react-use-layout-effect" "0.0.5"
+
+"@radix-ui/react-presence@0.0.14":
+ version "0.0.14"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-0.0.14.tgz#6a86058bbbf46234dd8840dacd620b3ac5797025"
+ integrity sha512-ufof9B76DHXV0sC8H7Lswh2AepdJFG8qEtF32JWrbA9N1bl2Jnf9px76KsagyC0MA8crGEZO5A96wizGuSgGWQ==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-compose-refs" "0.0.5"
+
+"@radix-ui/react-primitive@0.0.14":
+ version "0.0.14"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.0.14.tgz#752a967cb05d4c5643634fe20274e7dc905d1cce"
+ integrity sha512-FYOWGCrxFpLdB534aWTwMK4Pjg8cxFb+745qWhPfI+cYi+aYUddJQD3ilRHHXxCBD72ve7/PufqeB7Y/QlKqgg==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-polymorphic" "0.0.12"
+
+"@radix-ui/react-roving-focus@0.0.15":
+ version "0.0.15"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-0.0.15.tgz#9bc238ba705fbeed74ef04ebdbb77b35e5faca06"
+ integrity sha512-KtbWAqqxJRSxVKM+dhOc1rrq6eNWLqjmp7LVj3eKhw62nyV/z5G1lzzT54Zr5GD7+SH/QDyDvBFmPzRomXf1bQ==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "0.0.5"
+ "@radix-ui/react-collection" "0.0.14"
+ "@radix-ui/react-compose-refs" "0.0.5"
+ "@radix-ui/react-context" "0.0.5"
+ "@radix-ui/react-id" "0.0.6"
+ "@radix-ui/react-polymorphic" "0.0.12"
+ "@radix-ui/react-primitive" "0.0.14"
+ "@radix-ui/react-use-callback-ref" "0.0.5"
+ "@radix-ui/react-use-controllable-state" "0.0.6"
+
+"@radix-ui/react-slot@0.0.12":
+ version "0.0.12"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-0.0.12.tgz#c4d8a75fffca561aeeca2ed9603384d86757f60a"
+ integrity sha512-Em8P/xYyh3O/32IhrmARJNH+J/XCAVnw6h2zGu6oeReliIX7ktU67pMSeyyIZiU2hNXzaXYB/xDdixizQe/DGA==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-compose-refs" "0.0.5"
+
+"@radix-ui/react-use-body-pointer-events@0.0.6":
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.0.6.tgz#30b21301880417e7dbb345871ff5a83f2abe0d8d"
+ integrity sha512-ouYb7u1+K9TsiEcNs3HceNUBUgB2PV41EyD5O6y6ZPMxl1lW/QAy5dfyfJMRyaRWQ6kxwmGoKlCSb4uPTruzuQ==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-use-layout-effect" "0.0.5"
+
+"@radix-ui/react-use-callback-ref@0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-0.0.5.tgz#fa8db050229cda573dfeeae213d74ef06f6130db"
+ integrity sha512-z1AI221vmq9f3vsDyrCsGLCatKagbM1YeCGdRMhMsUBzFFCaJ+Axyoe/ndVqW8wwsraGWr1zYVAhIEdlC0GvPg==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
+"@radix-ui/react-use-controllable-state@0.0.6":
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-0.0.6.tgz#c4b16bc911a25889333388a684a04df937e5fec7"
+ integrity sha512-fBk4hUSKc4N7X/NAaifWYfKKfNuOB9xvj0MBQQYS5oOTNRgg4y8/Ax3jZ0adsplXDm7ix75sxqWm0nrvUoAjcw==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-use-callback-ref" "0.0.5"
+
+"@radix-ui/react-use-direction@0.0.1":
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-direction/-/react-use-direction-0.0.1.tgz#9ac72eb6d9902ed505c8a34048981d94f9433e14"
+ integrity sha512-sU+tkP09uEI1m+YJAR1ZAZLVFY1h/JD+jLSSQt5Wo3b9SYrJA889i2hH1P3DNRyWbbbisweiEQdK3MWILhFCig==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
+"@radix-ui/react-use-escape-keydown@0.0.6":
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-0.0.6.tgz#1ad1c81b99961b7dbe376ef54151ebc8bef627a0"
+ integrity sha512-MJpVj21BYwWllmp2xbXPqpKPssJ1WWrZi+Qx7PY5hVcBhQr5Jo6yKwIX677pH5Yql95ENTTT5LW3q+LVFYIISw==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-use-callback-ref" "0.0.5"
+
+"@radix-ui/react-use-layout-effect@0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.0.5.tgz#cbbd059090edc765749da00d9f562a9abd43cbac"
+ integrity sha512-bNPW2JNOr/p2hXr0hfKKqrEy5deNSRF17sw3l9Z7qlEnvIbBtQ7iwY/wrxIz5P7XFyYGoXodIUDH5G8PEucE3A==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
+"@radix-ui/react-use-rect@0.0.7":
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-0.0.7.tgz#e3a55fa7183ef436042198787bf38f8c9befcc14"
+ integrity sha512-OmaeFTgyiGNAchaxzDu+kFLz4Ly8RUcT5nwfoz4Nddd86I8Zdq93iNFnOpVLoVYqBnFEmvR6zexHXNFATrMbbQ==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/rect" "0.0.5"
+
+"@radix-ui/react-use-size@0.0.6":
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-0.0.6.tgz#998eaf6e8871b868f81f3b7faac06c3e896c37a0"
+ integrity sha512-kP4RIb2I5oHQzwzXJ21Hu8htNqf+sdaRzywxQpbj+hmqeUhpvIkhoq+ShNWV9wE/3c1T7gPnka8/nKYsKaKdCg==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
+"@radix-ui/rect@0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-0.0.5.tgz#6000d8d800288114af4bbc5863e6b58755d7d978"
+ integrity sha512-gXw171KbjyttA7K1DRIvPguLmKsg8raitB67MIcsdZwcquy+a1O2w3xY21NIKEqGhJwqJkECPUmMJDXgMNYuAg==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
"@react-dnd/asap@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-4.0.0.tgz#b300eeed83e9801f51bd66b0337c9a6f04548651"
@@ -2012,6 +2372,18 @@
resolved "https://registry.yarnpkg.com/@tommoor/remove-markdown/-/remove-markdown-0.3.2.tgz#5288ddd0e26b6b173e76ebb31c94653b0dcff45d"
integrity sha512-awcc9hfLZqyyZHOGzAHbnjgZJpQGS1W1oZZ5GXOTTnbKVdKQ4OWYbrRWPUvXI2YAKJazrcS8rxPh67PX3rpGkQ==
+"@types/accepts@*":
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575"
+ integrity sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==
+ dependencies:
+ "@types/node" "*"
+
+"@types/async-lock@^1.1.2":
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.3.tgz#0d86017cf87abbcb941c55360e533d37a3f23b3d"
+ integrity sha512-UpeDcjGKsYEQMeqEbfESm8OWJI305I7b9KE4ji3aBjoKWyN5CTdn8izcA1FM1DVDne30R5fNEnIy89vZw5LXJQ==
+
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
version "7.1.12"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d"
@@ -2045,6 +2417,14 @@
dependencies:
"@babel/types" "^7.3.0"
+"@types/body-parser@*":
+ version "1.19.1"
+ resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.1.tgz#0c0174c42a7d017b818303d4b5d969cb0b75929c"
+ integrity sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==
+ dependencies:
+ "@types/connect" "*"
+ "@types/node" "*"
+
"@types/cacheable-request@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976"
@@ -2055,6 +2435,28 @@
"@types/node" "*"
"@types/responselike" "*"
+"@types/connect@*":
+ version "3.4.35"
+ resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
+ integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==
+ dependencies:
+ "@types/node" "*"
+
+"@types/content-disposition@*":
+ version "0.5.4"
+ resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.4.tgz#de48cf01c79c9f1560bcfd8ae43217ab028657f8"
+ integrity sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==
+
+"@types/cookies@*":
+ version "0.7.7"
+ resolved "https://registry.yarnpkg.com/@types/cookies/-/cookies-0.7.7.tgz#7a92453d1d16389c05a5301eef566f34946cfd81"
+ integrity sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==
+ dependencies:
+ "@types/connect" "*"
+ "@types/express" "*"
+ "@types/keygrip" "*"
+ "@types/node" "*"
+
"@types/estree@0.0.39":
version "0.0.39"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
@@ -2065,6 +2467,25 @@
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
+"@types/express-serve-static-core@^4.17.18":
+ version "4.17.24"
+ resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz#ea41f93bf7e0d59cd5a76665068ed6aab6815c07"
+ integrity sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==
+ dependencies:
+ "@types/node" "*"
+ "@types/qs" "*"
+ "@types/range-parser" "*"
+
+"@types/express@*":
+ version "4.17.13"
+ resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034"
+ integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==
+ dependencies:
+ "@types/body-parser" "*"
+ "@types/express-serve-static-core" "^4.17.18"
+ "@types/qs" "*"
+ "@types/serve-static" "*"
+
"@types/formidable@^1.0.31":
version "1.0.31"
resolved "https://registry.yarnpkg.com/@types/formidable/-/formidable-1.0.31.tgz#274f9dc2d0a1a9ce1feef48c24ca0859e7ec947b"
@@ -2087,11 +2508,21 @@
dependencies:
"@types/unist" "*"
+"@types/http-assert@*":
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.2.tgz#a7fb59a7ca366e141789a084555a633801b9af3b"
+ integrity sha512-Ddzuzv/bB2prZnJKlS1sEYhaeT50wfJjhcTTTQLjEsEZJlk3XB4Xohieyq+P4VXIzg7lrQ1Spd/PfRnBpQsdqA==
+
"@types/http-cache-semantics@*":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a"
integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==
+"@types/http-errors@*":
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-1.8.1.tgz#e81ad28a60bee0328c6d2384e029aec626f1ae67"
+ integrity sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q==
+
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@@ -2121,6 +2552,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
+"@types/keygrip@*":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
+ integrity sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==
+
"@types/keyv@*", "@types/keyv@^3.1.1":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7"
@@ -2128,11 +2564,37 @@
dependencies:
"@types/node" "*"
+"@types/koa-compose@*":
+ version "3.2.5"
+ resolved "https://registry.yarnpkg.com/@types/koa-compose/-/koa-compose-3.2.5.tgz#85eb2e80ac50be95f37ccf8c407c09bbe3468e9d"
+ integrity sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==
+ dependencies:
+ "@types/koa" "*"
+
+"@types/koa@*", "@types/koa@^2.13.1":
+ version "2.13.4"
+ resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.4.tgz#10620b3f24a8027ef5cbae88b393d1b31205726b"
+ integrity sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==
+ dependencies:
+ "@types/accepts" "*"
+ "@types/content-disposition" "*"
+ "@types/cookies" "*"
+ "@types/http-assert" "*"
+ "@types/http-errors" "*"
+ "@types/keygrip" "*"
+ "@types/koa-compose" "*"
+ "@types/node" "*"
+
"@types/long@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
+"@types/mime@^1":
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
+ integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
+
"@types/node@*", "@types/node@>= 8":
version "14.14.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.7.tgz#8ea1e8f8eae2430cf440564b98c6dfce1ec5945d"
@@ -2163,6 +2625,16 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.5.tgz#b6ab3bba29e16b821d84e09ecfaded462b816b00"
integrity sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==
+"@types/qs@*":
+ version "6.9.7"
+ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
+ integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
+
+"@types/range-parser@*":
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
+ integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
+
"@types/resolve@1.17.1":
version "1.17.1"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@@ -2177,6 +2649,14 @@
dependencies:
"@types/node" "*"
+"@types/serve-static@*":
+ version "1.13.10"
+ resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9"
+ integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==
+ dependencies:
+ "@types/mime" "^1"
+ "@types/node" "*"
+
"@types/stack-utils@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
@@ -2187,6 +2667,18 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
+"@types/uuid@^8.3.0":
+ version "8.3.1"
+ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f"
+ integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==
+
+"@types/ws@^7.4.0":
+ version "7.4.7"
+ resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702"
+ integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==
+ dependencies:
+ "@types/node" "*"
+
"@types/yargs-parser@*":
version "15.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
@@ -2601,6 +3093,13 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+aria-hidden@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.1.3.tgz#bb48de18dc84787a3c6eee113709c473c64ec254"
+ integrity sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==
+ dependencies:
+ tslib "^1.0.0"
+
aria-query@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
@@ -2739,6 +3238,16 @@ async-each@^1.0.1:
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
+async-lock@^1.2.8:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.3.0.tgz#0fba111bea8b9693020857eba4f9adca173df3e5"
+ integrity sha512-8A7SkiisnEgME2zEedtDYPxUPzdv3x//E7n5IFktPAtMYSEAV7eNJF0rMwrVyUFj6d/8rgajLantbjcNRQYXIg==
+
+async@0.9.x:
+ version "0.9.2"
+ resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
+ integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=
+
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -3458,16 +3967,16 @@ builtin-status-codes@^3.0.0:
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
-bull@^3.5.2:
- version "3.18.1"
- resolved "https://registry.yarnpkg.com/bull/-/bull-3.18.1.tgz#49eb8fd9844a3dc0a12a851b132e508890763a31"
- integrity sha512-g3gHFZ0qMo0wpecoNmd2W+F1Gj48l6phVCTdsQPKxDk1bB7kzD0nY5FAFnBFiaWxNmh5lb5X9TMB64uNXFKFDg==
+bull@^3.29.0:
+ version "3.29.1"
+ resolved "https://registry.yarnpkg.com/bull/-/bull-3.29.1.tgz#7c5d7c557ebbf856892dee576ca5a19b46bb983e"
+ integrity sha512-ASNnorakKCV4hmgHABfn8Ir+gy24a4gaGnXH/0bm1Msq+djOnNfM5XW7Igzsa5iTjpboWXhY9dHFVjiWRKsSGw==
dependencies:
cron-parser "^2.13.0"
debuglog "^1.0.0"
get-port "^5.1.1"
- ioredis "^4.14.1"
- lodash "^4.17.19"
+ ioredis "^4.27.0"
+ lodash "^4.17.21"
p-timeout "^3.2.0"
promise.prototype.finally "^3.1.2"
semver "^7.3.2"
@@ -3881,6 +4390,15 @@ cliui@^6.0.0:
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
+cliui@^7.0.2:
+ version "7.0.4"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
+ integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.0"
+ wrap-ansi "^7.0.0"
+
clone-buffer@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
@@ -4103,6 +4621,30 @@ concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@~1.6.0:
readable-stream "^2.2.2"
typedarray "^0.0.6"
+concurrently@^6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-6.2.1.tgz#d880fc1d77559084732fa514092a3d5109a0d5bf"
+ integrity sha512-emgwhH+ezkuYKSHZQ+AkgEpoUZZlbpPVYCVv7YZx0r+T7fny1H03r2nYRebpi2DudHR4n1Rgbo2YTxKOxVJ4+g==
+ dependencies:
+ chalk "^4.1.0"
+ date-fns "^2.16.1"
+ lodash "^4.17.21"
+ read-pkg "^5.2.0"
+ rxjs "^6.6.3"
+ spawn-command "^0.0.2-1"
+ supports-color "^8.1.0"
+ tree-kill "^1.2.2"
+ yargs "^16.2.0"
+
+condense-newlines@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/condense-newlines/-/condense-newlines-0.2.1.tgz#3de985553139475d32502c83b02f60684d24c55f"
+ integrity sha1-PemFVTE5R10yUCyDsC9gaE0kxV8=
+ dependencies:
+ extend-shallow "^2.0.1"
+ is-whitespace "^0.3.0"
+ kind-of "^3.0.2"
+
config-chain@^1.1.12:
version "1.1.12"
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
@@ -4133,6 +4675,13 @@ console-browserify@^1.1.0:
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==
+consolidate@^0.16.0:
+ version "0.16.0"
+ resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.16.0.tgz#a11864768930f2f19431660a65906668f5fbdc16"
+ integrity sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==
+ dependencies:
+ bluebird "^3.7.2"
+
"consolidated-events@^1.1.0 || ^2.0.0":
version "2.0.2"
resolved "https://registry.yarnpkg.com/consolidated-events/-/consolidated-events-2.0.2.tgz#da8d8f8c2b232831413d9e190dc11669c79f4a91"
@@ -4437,6 +4986,11 @@ cssstyle@^2.2.0:
dependencies:
cssom "~0.3.6"
+csstype@^3.0.4:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
+ integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
+
cyclist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
@@ -4489,6 +5043,11 @@ date-fns@2.22.1:
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.1.tgz#1e5af959831ebb1d82992bf67b765052d8f0efc4"
integrity sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==
+date-fns@^2.16.1:
+ version "2.23.0"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.23.0.tgz#4e886c941659af0cf7b30fafdd1eaa37e88788a9"
+ integrity sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA==
+
dd-trace@^0.32.2:
version "0.32.2"
resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-0.32.2.tgz#c5844f75f3c15bf88f4992b266def062a254102a"
@@ -4660,12 +5219,12 @@ denque@^1.1.0, denque@^1.5.0:
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de"
integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==
-depd@2.0.0, depd@~2.0.0:
+depd@2.0.0, depd@^2.0.0, depd@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
-depd@^1.1.2, depd@~1.1.2:
+depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
@@ -4698,6 +5257,11 @@ detect-newline@^3.0.0:
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
+detect-node-es@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
+ integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
+
dicer@0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f"
@@ -4973,6 +5537,13 @@ ejs@^2.6.1:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba"
integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==
+ejs@^3.1.6:
+ version "3.1.6"
+ resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a"
+ integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==
+ dependencies:
+ jake "^10.6.1"
+
electron-to-chromium@^1.3.723:
version "1.3.736"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.736.tgz#f632d900a1f788dab22fec9c62ec5c9c8f0c4052"
@@ -5179,9 +5750,9 @@ eol@^0.9.1:
integrity sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==
errno@^0.1.3, errno@~0.1.7:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
- integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
+ integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==
dependencies:
prr "~1.0.1"
@@ -5808,6 +6379,13 @@ file-uri-to-path@1.0.0:
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
+filelist@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b"
+ integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==
+ dependencies:
+ minimatch "^3.0.4"
+
fill-range@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
@@ -6174,7 +6752,7 @@ gensync@^1.0.0-beta.1:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
-get-caller-file@^2.0.1:
+get-caller-file@^2.0.1, get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
@@ -6188,6 +6766,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1:
has "^1.0.3"
has-symbols "^1.0.1"
+get-nonce@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
+ integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==
+
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
@@ -6198,6 +6781,13 @@ get-package-type@^0.1.0:
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
+get-paths@0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/get-paths/-/get-paths-0.0.7.tgz#15331086752077cf130166ccd233a1cdbeefcf38"
+ integrity sha512-0wdJt7C1XKQxuCgouqd+ZvLJ56FQixKoki9MrFaO4EriqzXOiH9gbukaDE1ou08S8Ns3/yDzoBAISNPqj6e6tA==
+ dependencies:
+ pify "^4.0.1"
+
get-port@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
@@ -6784,7 +7374,7 @@ http-errors@1.7.3, http-errors@~1.7.2:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
-http-errors@^1.3.1, http-errors@^1.6.1, http-errors@^1.6.3:
+http-errors@^1.3.1, http-errors@^1.6.1, http-errors@^1.6.3, http-errors@^1.7.3:
version "1.8.0"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507"
integrity sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==
@@ -7034,23 +7624,24 @@ interpret@^1.0.0, interpret@^1.4.0:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
-invariant@^2.2.1, invariant@^2.2.2:
+invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
dependencies:
loose-envify "^1.0.0"
-ioredis@^4.14.1, ioredis@^4.24.3:
- version "4.24.3"
- resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.24.3.tgz#ba14c3621f751727f1b6c41c55ab26a7794018d5"
- integrity sha512-ANE2YT2fCa+KE0EUmx8VMZuJ+LaTNVXhjyAUDAxom9nqJGAXzCbNBMcujrUSJbz6xc2ZMaMxGB5y10cfYo/0IA==
+ioredis@^4.24.3, ioredis@^4.27.0:
+ version "4.27.8"
+ resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.27.8.tgz#822c2d1ac44067a8f7b92fb673070fc9d661c56e"
+ integrity sha512-AcMEevap2wKxNcYEybZ/Qp+MR2HbNNUwGjG4sVCC3cAJ/zR9HXKAkolXOuR6YcOGPf7DHx9mWb/JKtAGujyPow==
dependencies:
cluster-key-slot "^1.1.0"
debug "^4.3.1"
denque "^1.1.0"
lodash.defaults "^4.2.0"
lodash.flatten "^4.4.0"
+ lodash.isarguments "^3.1.0"
p-map "^2.1.0"
redis-commands "1.7.0"
redis-errors "^1.2.0"
@@ -7447,6 +8038,11 @@ is-valid-glob@^1.0.0:
resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa"
integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=
+is-whitespace@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/is-whitespace/-/is-whitespace-0.3.0.tgz#1639ecb1be036aec69a54cbb401cfbed7114ab7f"
+ integrity sha1-Fjnssb4DauxppUy7QBz77XEUq38=
+
is-windows@^1.0.1, is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@@ -7504,6 +8100,11 @@ isomorphic-fetch@^3.0.0:
node-fetch "^2.6.1"
whatwg-fetch "^3.4.1"
+isomorphic.js@^0.2.4:
+ version "0.2.4"
+ resolved "https://registry.yarnpkg.com/isomorphic.js/-/isomorphic.js-0.2.4.tgz#24ca374163ae54a7ce3b86ce63b701b91aa84969"
+ integrity sha512-Y4NjZceAwaPXctwsHgNsmfuPxR8lJ3f8X7QTAkhltrX4oGIv+eTlgHLXn4tWysC9zGTi929gapnPp+8F8cg7nA==
+
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -7550,6 +8151,16 @@ istanbul-reports@^3.0.2:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
+jake@^10.6.1:
+ version "10.8.2"
+ resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b"
+ integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==
+ dependencies:
+ async "0.9.x"
+ chalk "^2.4.2"
+ filelist "^1.0.1"
+ minimatch "^3.0.4"
+
java-properties@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/java-properties/-/java-properties-1.0.2.tgz#ccd1fa73907438a5b5c38982269d0e771fe78211"
@@ -7953,15 +8564,14 @@ jpeg-js@0.4.2:
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.2.tgz#8b345b1ae4abde64c2da2fe67ea216a114ac279d"
integrity sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw==
-js-beautify@^1.8.8:
- version "1.13.0"
- resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.13.0.tgz#a056d5d3acfd4918549aae3ab039f9f3c51eebb2"
- integrity sha512-/Tbp1OVzZjbwzwJQFIlYLm9eWQ+3aYbBXLSaqb1mEJzhcQAfrqMMQYtjb6io+U6KpD0ID4F+Id3/xcjH3l/sqA==
+js-beautify@^1.6.12, js-beautify@^1.8.8:
+ version "1.14.0"
+ resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.0.tgz#2ce790c555d53ce1e3d7363227acf5dc69024c2d"
+ integrity sha512-yuck9KirNSCAwyNJbqW+BxJqJ0NLJ4PwBUzQQACl5O3qHMBXVkXb/rD0ilh/Lat/tn88zSZ+CAHOlk0DsY7GuQ==
dependencies:
config-chain "^1.1.12"
editorconfig "^0.15.3"
glob "^7.1.3"
- mkdirp "^1.0.4"
nopt "^5.0.0"
js-search@^1.4.2:
@@ -8278,6 +8888,14 @@ koa-convert@1.2.0, koa-convert@^1.2.0:
co "^4.6.0"
koa-compose "^3.0.0"
+koa-easy-ws@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/koa-easy-ws/-/koa-easy-ws-1.3.0.tgz#dbf8eeb126ca11eed4c4e3af7904a47c25be2347"
+ integrity sha512-06lHAwm25HBdplTpwHiZqKcl39vS4KiVzRPneUExCHVvQ9TPHmFlUFO/gjQrUdLj3+kvqQpqdlIPNb91wn6EwA==
+ dependencies:
+ debug "^4.1.1"
+ ws "^7.3.1"
+
koa-helmet@5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/koa-helmet/-/koa-helmet-5.2.0.tgz#6529f64dd4539261a9bb0a56e201e4976f0200f0"
@@ -8317,6 +8935,14 @@ koa-mount@^3.0.0:
debug "^2.6.1"
koa-compose "^3.2.1"
+koa-mount@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/koa-mount/-/koa-mount-4.0.0.tgz#e0265e58198e1a14ef889514c607254ff386329c"
+ integrity sha512-rm71jaA/P+6HeCpoRhmCv8KVBIi0tfGuO/dMKicbQnQW/YJntJ6MnnspkodoA4QstMVEZArsCphmd0bJEtoMjQ==
+ dependencies:
+ debug "^4.0.1"
+ koa-compose "^4.1.0"
+
koa-onerror@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/koa-onerror/-/koa-onerror-4.1.0.tgz#7949c7651941e67b11813bf1fad03c2d34470b1c"
@@ -8336,6 +8962,17 @@ koa-router@7.0.1:
methods "^1.0.1"
path-to-regexp "^1.1.1"
+koa-router@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/koa-router/-/koa-router-10.0.0.tgz#7bc76a031085731e61fc92c1683687b2f44de6a4"
+ integrity sha512-gAE5J1gBQTvfR8rMMtMUkE26+1MbO3DGpGmvfmM2pR9Z7w2VIb2Ecqeal98yVO7+4ltffby7gWOzpCmdNOQe0w==
+ dependencies:
+ debug "^4.1.1"
+ http-errors "^1.7.3"
+ koa-compose "^4.1.0"
+ methods "^1.1.2"
+ path-to-regexp "^6.1.0"
+
koa-send@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/koa-send/-/koa-send-4.1.3.tgz#0822207bbf5253a414c8f1765ebc29fa41353cb6"
@@ -8346,6 +8983,15 @@ koa-send@^4.1.3:
mz "^2.6.0"
resolve-path "^1.4.0"
+koa-send@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/koa-send/-/koa-send-5.0.1.tgz#39dceebfafb395d0d60beaffba3a70b4f543fe79"
+ integrity sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==
+ dependencies:
+ debug "^4.1.1"
+ http-errors "^1.7.3"
+ resolve-path "^1.4.0"
+
koa-sendfile@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/koa-sendfile/-/koa-sendfile-2.0.0.tgz#446e2a35ba7074ed03afda2c667c2359406e1082"
@@ -8368,11 +9014,33 @@ koa-static@^4.0.1:
debug "^3.1.0"
koa-send "^4.1.3"
+koa-static@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/koa-static/-/koa-static-5.0.0.tgz#5e92fc96b537ad5219f425319c95b64772776943"
+ integrity sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==
+ dependencies:
+ debug "^3.1.0"
+ koa-send "^5.0.0"
+
koa-unless@1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/koa-unless/-/koa-unless-1.0.7.tgz#b9df375e2b4da3043918d48622520c2c0b79f032"
integrity sha1-ud83XitNowQ5GNSGIlIMLAt58DI=
+koa-views@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/koa-views/-/koa-views-7.0.1.tgz#0c8f8e65d5cd2e08249430cb83dc361e49a17a5a"
+ integrity sha512-yS8751DXHXXDbdl/oUZd0PsgnxR0MLiguu77Eqrgu6yawE9Hi99wNKiVENb0Kfgsmvq/8px7YCI+USgxaTB1LA==
+ dependencies:
+ "@types/koa" "^2.13.1"
+ consolidate "^0.16.0"
+ debug "^4.1.0"
+ get-paths "0.0.7"
+ koa-send "^5.0.0"
+ mz "^2.4.0"
+ pretty "^2.0.0"
+ resolve-path "^1.4.0"
+
koa-webpack-dev-middleware@^1.4.5:
version "1.4.6"
resolved "https://registry.yarnpkg.com/koa-webpack-dev-middleware/-/koa-webpack-dev-middleware-1.4.6.tgz#6ec20d3648c3c80b5edb0b721a6838f66a1fc47a"
@@ -8387,10 +9055,10 @@ koa-webpack-hot-middleware@^1.0.3:
dependencies:
webpack-hot-middleware "2.x"
-koa@^2.10.0:
- version "2.13.0"
- resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.0.tgz#25217e05efd3358a7e5ddec00f0a380c9b71b501"
- integrity sha512-i/XJVOfPw7npbMv67+bOeXr3gPqOAw6uh5wFyNs3QvJ47tUx3M3V9rIE0//WytY42MKz4l/MXKyGkQ2LQTfLUQ==
+koa@^2.10.0, koa@^2.13.1:
+ version "2.13.1"
+ resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.1.tgz#6275172875b27bcfe1d454356a5b6b9f5a9b1051"
+ integrity sha512-Lb2Dloc72auj5vK4X4qqL7B5jyDPQaZucc9sR/71byg7ryoD1NCaCm63CShk9ID9quQvDEi1bGR/iGjCG7As3w==
dependencies:
accepts "^1.3.5"
cache-content-type "^1.0.0"
@@ -8399,7 +9067,7 @@ koa@^2.10.0:
cookies "~0.8.0"
debug "~3.1.0"
delegates "^1.0.0"
- depd "^1.1.2"
+ depd "^2.0.0"
destroy "^1.0.4"
encodeurl "^1.0.2"
escape-html "^1.0.3"
@@ -8475,6 +9143,13 @@ levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
+lib0@^0.2.34, lib0@^0.2.35, lib0@^0.2.41, lib0@^0.2.42:
+ version "0.2.42"
+ resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.42.tgz#6d8bf1fb8205dec37a953c521c5ee403fd8769b0"
+ integrity sha512-8BNM4MiokEKzMvSxTOC3gnCBisJH+jL67CnSnqzHv3jli3pUvGC8wz+0DQ2YvGr4wVQdb2R2uNNPw9LEpVvJ4Q==
+ dependencies:
+ isomorphic.js "^0.2.4"
+
lie@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
@@ -8737,6 +9412,11 @@ lodash.includes@^4.3.0:
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
+lodash.isarguments@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
+ integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=
+
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
@@ -9344,7 +10024,7 @@ multer@^1.4.2:
type-is "^1.6.4"
xtend "^4.0.0"
-mz@2, mz@^2.6.0:
+mz@2, mz@^2.4.0, mz@^2.6.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
@@ -9814,10 +10494,10 @@ os-name@^3.1.0:
macos-release "^2.2.0"
windows-release "^3.1.0"
-outline-icons@^1.26.1, outline-icons@^1.30.0:
- version "1.30.0"
- resolved "https://registry.yarnpkg.com/outline-icons/-/outline-icons-1.30.0.tgz#e6bb779d43fc89df4b7bda8dfe886c28c72e494d"
- integrity sha512-JLL0hiBB7cKBrpQ7ejvLORUVYNpx2ULngoWJHL8vpsMwMha+ChF7z1MmJQ1dD6/yDX3dgIzeMfMASD8weVRloQ==
+outline-icons@^1.26.1, outline-icons@^1.31.0:
+ version "1.31.0"
+ resolved "https://registry.yarnpkg.com/outline-icons/-/outline-icons-1.31.0.tgz#c373667442300afdb1a9fd6059180833c6123fcb"
+ integrity sha512-pe2vSI1mR4bJaWpmTitniyNpu4oq8dVObOweh/H6EON+12w2QDRtJZ6A6Bkc2vjlb2ZRYcZasisJI9VBUuTnHQ==
oy-vey@^0.10.0:
version "0.10.0"
@@ -10206,6 +10886,11 @@ path-to-regexp@^1.1.1, path-to-regexp@^1.7.0:
dependencies:
isarray "0.0.1"
+path-to-regexp@^6.1.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38"
+ integrity sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==
+
path-type@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
@@ -10476,6 +11161,15 @@ pretty-format@^26.6.2:
ansi-styles "^4.0.0"
react-is "^17.0.1"
+pretty@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/pretty/-/pretty-2.0.0.tgz#adbc7960b7bbfe289a557dc5f737619a220d06a5"
+ integrity sha1-rbx5YLe7/iiaVX3F9zdhmiINBqU=
+ dependencies:
+ condense-newlines "^0.2.1"
+ extend-shallow "^2.0.1"
+ js-beautify "^1.6.12"
+
prismjs@~1.23.0:
version "1.23.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.23.0.tgz#d3b3967f7d72440690497652a9d40ff046067f33"
@@ -11021,6 +11715,25 @@ react-refresh@^0.9.0:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf"
integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==
+react-remove-scroll-bar@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz#d4d545a7df024f75d67e151499a6ab5ac97c8cdd"
+ integrity sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg==
+ dependencies:
+ react-style-singleton "^2.1.0"
+ tslib "^1.0.0"
+
+react-remove-scroll@^2.4.0:
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.4.3.tgz#83d19b02503b04bd8141ed6e0b9e6691a2e935a6"
+ integrity sha512-lGWYXfV6jykJwbFpsuPdexKKzp96f3RbvGapDSIdcyGvHb7/eqyn46C7/6h+rUzYar1j5mdU+XECITHXCKBk9Q==
+ dependencies:
+ react-remove-scroll-bar "^2.1.0"
+ react-style-singleton "^2.1.0"
+ tslib "^1.0.0"
+ use-callback-ref "^1.2.3"
+ use-sidecar "^1.0.1"
+
react-router-dom@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
@@ -11055,6 +11768,15 @@ react-side-effect@^2.1.0:
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3"
integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ==
+react-style-singleton@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.1.1.tgz#ce7f90b67618be2b6b94902a30aaea152ce52e66"
+ integrity sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA==
+ dependencies:
+ get-nonce "^1.0.0"
+ invariant "^2.2.4"
+ tslib "^1.0.0"
+
react-table@^7.7.0:
version "7.7.0"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.7.0.tgz#e2ce14d7fe3a559f7444e9ecfe8231ea8373f912"
@@ -11239,6 +11961,13 @@ redis-errors@^1.0.0, redis-errors@^1.2.0:
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=
+redis-info@^3.0.8:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/redis-info/-/redis-info-3.0.8.tgz#27e696778c100d716324fa68b92cccf390487e02"
+ integrity sha512-L7yPuGzRq+gu+ZYl/aO0TDgc4nNcMpDTaTN4P3bBi8ZENp1fk8gvtZQpidrYL5uAJYMIcMN81fgUz28qUpTeVA==
+ dependencies:
+ lodash "^4.17.11"
+
redis-parser@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
@@ -11589,10 +12318,10 @@ retry-as-promised@^3.2.0:
dependencies:
any-promise "^1.3.0"
-rich-markdown-editor@^11.17.4:
- version "11.17.4"
- resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.17.4.tgz#a3ce79ad74291a792526a488f183a3aa047603ce"
- integrity sha512-T9i9jGeq8aOyEl/3u+K169/kCpaOYh+kVwRDFiZfR13DVQxGKrK/MQ9VXjX6yxTBTEDIGy2RSuYxUUjCnJ25RA==
+rich-markdown-editor@^11.17.5:
+ version "11.17.5"
+ resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.17.5.tgz#90041c4b2b6fed64dc7402aac6dec378cc4a7c22"
+ integrity sha512-NhKmQyOqV0FYK4BMjIdjWLC2J0NGEibebWVyJgJE43fLIFm4LOg5ql4QszsOFLX79CVVBPAysHCFYns+XJ+qGw==
dependencies:
copy-to-clipboard "^3.0.8"
lodash "^4.17.11"
@@ -11717,6 +12446,13 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
+rxjs@^6.6.3:
+ version "6.6.7"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
+ integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
+ dependencies:
+ tslib "^1.9.0"
+
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@@ -12318,6 +13054,11 @@ space-separated-tokens@^1.0.0:
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899"
integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==
+spawn-command@^0.0.2-1:
+ version "0.0.2-1"
+ resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
+ integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=
+
spdx-correct@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
@@ -12733,6 +13474,13 @@ supports-color@^7.0.0, supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
+supports-color@^8.1.0:
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+ dependencies:
+ has-flag "^4.0.0"
+
supports-hyperlinks@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz#f663df252af5f37c5d49bbd7eeefa9e0b9e59e47"
@@ -13141,6 +13889,11 @@ tr46@^2.0.2:
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=
+tree-kill@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
+ integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
+
tsconfig-paths@^3.9.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b"
@@ -13151,7 +13904,7 @@ tsconfig-paths@^3.9.0:
minimist "^1.2.0"
strip-bom "^3.0.0"
-tslib@^1.9.0, tslib@^1.9.3:
+tslib@^1.0.0, tslib@^1.9.0, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@@ -13557,6 +14310,19 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
+use-callback-ref@^1.2.3:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5"
+ integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==
+
+use-sidecar@^1.0.1:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b"
+ integrity sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA==
+ dependencies:
+ detect-node-es "^1.1.0"
+ tslib "^1.9.3"
+
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
@@ -14226,6 +14992,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -14257,7 +15032,12 @@ write@1.0.3:
dependencies:
mkdirp "^0.5.1"
-ws@^7.2.3, ws@~7.4.2:
+ws@^7.2.3, ws@^7.3.1, ws@^7.4.3:
+ version "7.5.3"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74"
+ integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==
+
+ws@~7.4.2:
version "7.4.6"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
@@ -14338,11 +15118,37 @@ xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
+y-indexeddb@^9.0.6:
+ version "9.0.6"
+ resolved "https://registry.yarnpkg.com/y-indexeddb/-/y-indexeddb-9.0.6.tgz#49aecac11bc229571fb134e0ec0717c0330b731f"
+ integrity sha512-8mdCYdzZDWS2lGiB9Reaz67ZqvnV6EXH/F7L+TmBC+3mWjIBrPw4UcI79nOhEOh+y9lHXzNpSda4YJ06M13F1A==
+ dependencies:
+ lib0 "^0.2.35"
+
+y-prosemirror@^1.0.9:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/y-prosemirror/-/y-prosemirror-1.0.9.tgz#c0b5bf4e2c6620093ba0658c2aca52055346a683"
+ integrity sha512-OM12aPx04lwiIy1IOBidb6ONAof2KFxQE/Gww26SEsMQuA2dibrJkjaMwXwY1KnYY7yOpwbIFRdwecdNXLU9yQ==
+ dependencies:
+ lib0 "^0.2.34"
+
+y-protocols@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/y-protocols/-/y-protocols-1.0.5.tgz#91d574250060b29fcac8f8eb5e276fbad594245e"
+ integrity sha512-Wil92b7cGk712lRHDqS4T90IczF6RkcvCwAD0A2OPg+adKmOe+nOiT/N2hvpQIWS3zfjmtL4CPaH5sIW1Hkm/A==
+ dependencies:
+ lib0 "^0.2.42"
+
y18n@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
@@ -14387,6 +15193,11 @@ yargs-parser@^18.1.2:
camelcase "^5.0.0"
decamelize "^1.2.0"
+yargs-parser@^20.2.2:
+ version "20.2.9"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
+ integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
+
yargs@^13.1.0, yargs@^13.3.2:
version "13.3.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
@@ -14420,6 +15231,19 @@ yargs@^15.1.0, yargs@^15.4.1:
y18n "^4.0.0"
yargs-parser "^18.1.2"
+yargs@^16.2.0:
+ version "16.2.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
+ integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
+ dependencies:
+ cliui "^7.0.2"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.0"
+ y18n "^5.0.5"
+ yargs-parser "^20.2.2"
+
yarn-deduplicate@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-3.1.0.tgz#3018d93e95f855f236a215b591fe8bc4bcabba3e"
@@ -14434,6 +15258,13 @@ yeast@0.1.2:
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
+yjs@^13.5.0, yjs@^13.5.12, yjs@^13.5.8:
+ version "13.5.12"
+ resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.5.12.tgz#7a0cf3119fb368c07243825e989a55de164b3f9c"
+ integrity sha512-/buy1kh8Ls+t733Lgov9hiNxCsjHSCymTuZNahj2hsPNoGbvnSdDmCz9Z4F19Yr1eUAAXQLJF3q7fiBcvPC6Qg==
+ dependencies:
+ lib0 "^0.2.41"
+
ylru@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.2.1.tgz#f576b63341547989c1de7ba288760923b27fe84f"