diff --git a/.eslintrc b/.eslintrc
index ed273945..65facbe7 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -6,7 +6,10 @@
"plugin:import/warnings",
"plugin:flowtype/recommended"
],
- "plugins": ["prettier", "flowtype"],
+ "plugins": [
+ "prettier",
+ "flowtype"
+ ],
"rules": {
"eqeqeq": 2,
"no-unused-vars": 2,
@@ -18,14 +21,19 @@
"annotationStyle": "line"
}
],
- "flowtype/space-after-type-colon": [2, "always"],
- "flowtype/space-before-type-colon": [2, "never"],
+ "flowtype/space-after-type-colon": [
+ 2,
+ "always"
+ ],
+ "flowtype/space-before-type-colon": [
+ 2,
+ "never"
+ ],
"prettier/prettier": [
"error",
{
"printWidth": 80,
- "trailingComma": "es5",
- "singleQuote": true
+ "trailingComma": "es5"
}
]
},
@@ -34,11 +42,14 @@
"createClass": "createReactClass",
"pragma": "React",
"version": "detect",
- "flowVersion": "0.86"
+ "flowVersion": "0.86"
},
"import/resolver": {
"node": {
- "paths": ["app", "."]
+ "paths": [
+ "app",
+ "."
+ ]
}
},
"flowtype": {
@@ -57,4 +68,4 @@
"afterAll": true,
"Sentry": true
}
-}
+}
\ No newline at end of file
diff --git a/app/components/Actions.js b/app/components/Actions.js
index 370b99a2..920f3899 100644
--- a/app/components/Actions.js
+++ b/app/components/Actions.js
@@ -1,7 +1,7 @@
// @flow
-import styled from 'styled-components';
-import breakpoint from 'styled-components-breakpoint';
-import Flex from 'shared/components/Flex';
+import styled from "styled-components";
+import breakpoint from "styled-components-breakpoint";
+import Flex from "shared/components/Flex";
export const Action = styled(Flex)`
justify-content: center;
@@ -40,7 +40,7 @@ const Actions = styled(Flex)`
display: none;
}
- ${breakpoint('tablet')`
+ ${breakpoint("tablet")`
left: auto;
padding: 24px;
`};
diff --git a/app/components/Alert.js b/app/components/Alert.js
index fc5f4c91..629a3c22 100644
--- a/app/components/Alert.js
+++ b/app/components/Alert.js
@@ -1,18 +1,18 @@
// @flow
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import Flex from 'shared/components/Flex';
-import styled from 'styled-components';
+import * as React from "react";
+import { observer } from "mobx-react";
+import Flex from "shared/components/Flex";
+import styled from "styled-components";
type Props = {
children: React.Node,
- type?: 'info' | 'success' | 'warning' | 'danger' | 'offline',
+ type?: "info" | "success" | "warning" | "danger" | "offline",
};
@observer
class Alert extends React.Component {
defaultProps = {
- type: 'info',
+ type: "info",
};
render() {
diff --git a/app/components/Analytics.js b/app/components/Analytics.js
index ebcbb7aa..284c203b 100644
--- a/app/components/Analytics.js
+++ b/app/components/Analytics.js
@@ -1,6 +1,6 @@
// @flow
/* global ga */
-import * as React from 'react';
+import * as React from "react";
type Props = {
children?: React.Node,
@@ -20,12 +20,12 @@ export default class Analytics extends React.Component {
// $FlowIssue
ga.l = +new Date();
- ga('create', process.env.GOOGLE_ANALYTICS_ID, 'auto');
- ga('set', { dimension1: 'true' });
- ga('send', 'pageview');
+ ga("create", process.env.GOOGLE_ANALYTICS_ID, "auto");
+ ga("set", { dimension1: "true" });
+ ga("send", "pageview");
- const script = document.createElement('script');
- script.src = 'https://www.google-analytics.com/analytics.js';
+ const script = document.createElement("script");
+ script.src = "https://www.google-analytics.com/analytics.js";
script.async = true;
if (document.body) {
diff --git a/app/components/Authenticated.js b/app/components/Authenticated.js
index 14f53c3e..a81a8200 100644
--- a/app/components/Authenticated.js
+++ b/app/components/Authenticated.js
@@ -1,9 +1,9 @@
// @flow
-import * as React from 'react';
-import { observer, inject } from 'mobx-react';
-import AuthStore from 'stores/AuthStore';
-import LoadingIndicator from 'components/LoadingIndicator';
-import { isCustomSubdomain } from 'shared/utils/domains';
+import * as React from "react";
+import { observer, inject } from "mobx-react";
+import AuthStore from "stores/AuthStore";
+import LoadingIndicator from "components/LoadingIndicator";
+import { isCustomSubdomain } from "shared/utils/domains";
type Props = {
auth: AuthStore,
@@ -38,4 +38,4 @@ const Authenticated = observer(({ auth, children }: Props) => {
return null;
});
-export default inject('auth')(Authenticated);
+export default inject("auth")(Authenticated);
diff --git a/app/components/Avatar/Avatar.js b/app/components/Avatar/Avatar.js
index e5970971..1066a34a 100644
--- a/app/components/Avatar/Avatar.js
+++ b/app/components/Avatar/Avatar.js
@@ -1,9 +1,9 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import { observable } from 'mobx';
-import { observer } from 'mobx-react';
-import placeholder from './placeholder.png';
+import * as React from "react";
+import styled from "styled-components";
+import { observable } from "mobx";
+import { observer } from "mobx-react";
+import placeholder from "./placeholder.png";
type Props = {
src: string,
diff --git a/app/components/Avatar/AvatarWithPresence.js b/app/components/Avatar/AvatarWithPresence.js
index 3518fbcf..1d2f6f5c 100644
--- a/app/components/Avatar/AvatarWithPresence.js
+++ b/app/components/Avatar/AvatarWithPresence.js
@@ -1,14 +1,14 @@
// @flow
-import * as React from 'react';
-import { observable } from 'mobx';
-import { observer } from 'mobx-react';
-import styled from 'styled-components';
-import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
-import Avatar from 'components/Avatar';
-import Tooltip from 'components/Tooltip';
-import User from 'models/User';
-import UserProfile from 'scenes/UserProfile';
-import { EditIcon } from 'outline-icons';
+import * as React from "react";
+import { observable } from "mobx";
+import { observer } from "mobx-react";
+import styled from "styled-components";
+import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
+import Avatar from "components/Avatar";
+import Tooltip from "components/Tooltip";
+import User from "models/User";
+import UserProfile from "scenes/UserProfile";
+import { EditIcon } from "outline-icons";
type Props = {
user: User,
@@ -44,10 +44,10 @@ class AvatarWithPresence extends React.Component {
- {user.name} {isCurrentUser && '(You)'}
+ {user.name} {isCurrentUser && "(You)"}
{isPresent
- ? isEditing ? 'currently editing' : 'currently viewing'
+ ? isEditing ? "currently editing" : "currently viewing"
: `viewed ${distanceInWordsToNow(new Date(lastViewedAt))} ago`}
}
diff --git a/app/components/Avatar/index.js b/app/components/Avatar/index.js
index cd77b1bb..0106b24d 100644
--- a/app/components/Avatar/index.js
+++ b/app/components/Avatar/index.js
@@ -1,6 +1,6 @@
// @flow
-import Avatar from './Avatar';
-import AvatarWithPresence from './AvatarWithPresence';
+import Avatar from "./Avatar";
+import AvatarWithPresence from "./AvatarWithPresence";
export { AvatarWithPresence };
export default Avatar;
diff --git a/app/components/Badge.js b/app/components/Badge.js
index ba310e3a..d79082e4 100644
--- a/app/components/Badge.js
+++ b/app/components/Badge.js
@@ -1,5 +1,5 @@
// @flow
-import styled from 'styled-components';
+import styled from "styled-components";
const Badge = styled.span`
margin-left: 10px;
diff --git a/app/components/Button.js b/app/components/Button.js
index 07a28cd2..45a7e128 100644
--- a/app/components/Button.js
+++ b/app/components/Button.js
@@ -1,8 +1,8 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import { darken, lighten } from 'polished';
-import { ExpandedIcon } from 'outline-icons';
+import * as React from "react";
+import styled from "styled-components";
+import { darken, lighten } from "polished";
+import { ExpandedIcon } from "outline-icons";
const RealButton = styled.button`
display: inline-block;
@@ -54,10 +54,10 @@ const RealButton = styled.button`
background: ${props.theme.buttonNeutralBackground};
color: ${props.theme.buttonNeutralText};
box-shadow: ${
- props.borderOnHover ? 'none' : 'rgba(0, 0, 0, 0.07) 0px 1px 2px'
+ props.borderOnHover ? "none" : "rgba(0, 0, 0, 0.07) 0px 1px 2px"
};
border: 1px solid ${
- props.borderOnHover ? 'transparent' : props.theme.buttonNeutralBorder
+ props.borderOnHover ? "transparent" : props.theme.buttonNeutralBorder
};
svg {
@@ -102,7 +102,7 @@ const Label = styled.span`
white-space: nowrap;
text-overflow: ellipsis;
- ${props => props.hasIcon && 'padding-left: 4px;'};
+ ${props => props.hasIcon && "padding-left: 4px;"};
`;
export const Inner = styled.span`
@@ -113,8 +113,8 @@ export const Inner = styled.span`
justify-content: center;
align-items: center;
- ${props => props.hasIcon && props.hasText && 'padding-left: 4px;'};
- ${props => props.hasIcon && !props.hasText && 'padding: 0 4px;'};
+ ${props => props.hasIcon && props.hasText && "padding-left: 4px;"};
+ ${props => props.hasIcon && !props.hasText && "padding: 0 4px;"};
`;
export type Props = {
@@ -130,7 +130,7 @@ export type Props = {
};
function Button({
- type = 'text',
+ type = "text",
icon,
children,
value,
diff --git a/app/components/CenteredContent.js b/app/components/CenteredContent.js
index 2c5f9c45..aa79a6f7 100644
--- a/app/components/CenteredContent.js
+++ b/app/components/CenteredContent.js
@@ -1,7 +1,7 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import breakpoint from 'styled-components-breakpoint';
+import * as React from "react";
+import styled from "styled-components";
+import breakpoint from "styled-components-breakpoint";
type Props = {
children?: React.Node,
@@ -11,7 +11,7 @@ const Container = styled.div`
width: 100%;
padding: 60px 20px;
- ${breakpoint('tablet')`
+ ${breakpoint("tablet")`
padding: 60px;
`};
`;
diff --git a/app/components/Checkbox.js b/app/components/Checkbox.js
index f6420cff..8acbaabc 100644
--- a/app/components/Checkbox.js
+++ b/app/components/Checkbox.js
@@ -1,8 +1,8 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import HelpText from 'components/HelpText';
-import VisuallyHidden from 'components/VisuallyHidden';
+import * as React from "react";
+import styled from "styled-components";
+import HelpText from "components/HelpText";
+import VisuallyHidden from "components/VisuallyHidden";
export type Props = {
checked?: boolean,
@@ -15,13 +15,13 @@ export type Props = {
const LabelText = styled.span`
font-weight: 500;
- margin-left: ${props => (props.small ? '6px' : '10px')};
- ${props => (props.small ? `color: ${props.theme.textSecondary}` : '')};
+ margin-left: ${props => (props.small ? "6px" : "10px")};
+ ${props => (props.small ? `color: ${props.theme.textSecondary}` : "")};
`;
const Wrapper = styled.div`
padding-bottom: 8px;
- ${props => (props.small ? 'font-size: 14px' : '')};
+ ${props => (props.small ? "font-size: 14px" : "")};
`;
const Label = styled.label`
diff --git a/app/components/ClickablePadding.js b/app/components/ClickablePadding.js
index 30ac2cf3..04ed7b51 100644
--- a/app/components/ClickablePadding.js
+++ b/app/components/ClickablePadding.js
@@ -1,9 +1,9 @@
// @flow
-import styled from 'styled-components';
+import styled from "styled-components";
const ClickablePadding = styled.div`
min-height: 10em;
- cursor: ${({ onClick }) => (onClick ? 'text' : 'default')};
+ cursor: ${({ onClick }) => (onClick ? "text" : "default")};
${({ grow }) => grow && `flex-grow: 100;`};
`;
diff --git a/app/components/Collaborators.js b/app/components/Collaborators.js
index ce1b6e64..ea673d01 100644
--- a/app/components/Collaborators.js
+++ b/app/components/Collaborators.js
@@ -1,14 +1,14 @@
// @flow
-import * as React from 'react';
-import { observer, inject } from 'mobx-react';
-import { sortBy, keyBy } from 'lodash';
-import { MAX_AVATAR_DISPLAY } from 'shared/constants';
+import * as React from "react";
+import { observer, inject } from "mobx-react";
+import { sortBy, keyBy } from "lodash";
+import { MAX_AVATAR_DISPLAY } from "shared/constants";
-import { AvatarWithPresence } from 'components/Avatar';
-import Facepile from 'components/Facepile';
-import Document from 'models/Document';
-import ViewsStore from 'stores/ViewsStore';
-import DocumentPresenceStore from 'stores/DocumentPresenceStore';
+import { AvatarWithPresence } from "components/Avatar";
+import Facepile from "components/Facepile";
+import Document from "models/Document";
+import ViewsStore from "stores/ViewsStore";
+import DocumentPresenceStore from "stores/DocumentPresenceStore";
type Props = {
views: ViewsStore,
@@ -73,4 +73,4 @@ class Collaborators extends React.Component {
}
}
-export default inject('views', 'presence')(Collaborators);
+export default inject("views", "presence")(Collaborators);
diff --git a/app/components/CollectionIcon.js b/app/components/CollectionIcon.js
index a5b1c348..c28b4977 100644
--- a/app/components/CollectionIcon.js
+++ b/app/components/CollectionIcon.js
@@ -1,11 +1,11 @@
// @flow
-import * as React from 'react';
-import { inject, observer } from 'mobx-react';
-import { getLuminance } from 'polished';
-import { PrivateCollectionIcon, CollectionIcon } from 'outline-icons';
-import Collection from 'models/Collection';
-import { icons } from 'components/IconPicker';
-import UiStore from 'stores/UiStore';
+import * as React from "react";
+import { inject, observer } from "mobx-react";
+import { getLuminance } from "polished";
+import { PrivateCollectionIcon, CollectionIcon } from "outline-icons";
+import Collection from "models/Collection";
+import { icons } from "components/IconPicker";
+import UiStore from "stores/UiStore";
type Props = {
collection: Collection,
@@ -18,18 +18,18 @@ function ResolvedCollectionIcon({ collection, expanded, size, ui }: Props) {
// If the chosen icon color is very dark then we invert it in dark mode
// otherwise it will be impossible to see against the dark background.
const color =
- ui.resolvedTheme === 'dark'
+ ui.resolvedTheme === "dark"
? getLuminance(collection.color) > 0.12
? collection.color
- : 'currentColor'
+ : "currentColor"
: collection.color;
- if (collection.icon && collection.icon !== 'collection') {
+ if (collection.icon && collection.icon !== "collection") {
try {
const Component = icons[collection.icon].component;
return ;
} catch (error) {
- console.warn('Failed to render custom icon ' + collection.icon);
+ console.warn("Failed to render custom icon " + collection.icon);
}
}
@@ -42,4 +42,4 @@ function ResolvedCollectionIcon({ collection, expanded, size, ui }: Props) {
return ;
}
-export default inject('ui')(observer(ResolvedCollectionIcon));
+export default inject("ui")(observer(ResolvedCollectionIcon));
diff --git a/app/components/CopyToClipboard.js b/app/components/CopyToClipboard.js
index 88ba33f5..d6bb9784 100644
--- a/app/components/CopyToClipboard.js
+++ b/app/components/CopyToClipboard.js
@@ -1,6 +1,6 @@
// @flow
-import * as React from 'react';
-import copy from 'copy-to-clipboard';
+import * as React from "react";
+import copy from "copy-to-clipboard";
type Props = {
text: string,
@@ -19,7 +19,7 @@ class CopyToClipboard extends React.PureComponent {
if (onCopy) onCopy();
- if (elem && elem.props && typeof elem.props.onClick === 'function') {
+ if (elem && elem.props && typeof elem.props.onClick === "function") {
elem.props.onClick(ev);
}
};
diff --git a/app/components/DocumentHistory/DocumentHistory.js b/app/components/DocumentHistory/DocumentHistory.js
index 0ab51f2d..47f2cfc7 100644
--- a/app/components/DocumentHistory/DocumentHistory.js
+++ b/app/components/DocumentHistory/DocumentHistory.js
@@ -1,20 +1,20 @@
// @flow
-import * as React from 'react';
-import { observable, action } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import type { RouterHistory } from 'react-router-dom';
-import styled from 'styled-components';
-import { Waypoint } from 'react-waypoint';
-import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
+import * as React from "react";
+import { observable, action } from "mobx";
+import { observer, inject } from "mobx-react";
+import type { RouterHistory } from "react-router-dom";
+import styled from "styled-components";
+import { Waypoint } from "react-waypoint";
+import ArrowKeyNavigation from "boundless-arrow-key-navigation";
-import { DEFAULT_PAGINATION_LIMIT } from 'stores/BaseStore';
-import DocumentsStore from 'stores/DocumentsStore';
-import RevisionsStore from 'stores/RevisionsStore';
+import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore";
+import DocumentsStore from "stores/DocumentsStore";
+import RevisionsStore from "stores/RevisionsStore";
-import Flex from 'shared/components/Flex';
-import { ListPlaceholder } from 'components/LoadingPlaceholder';
-import Revision from './components/Revision';
-import { documentHistoryUrl } from 'utils/routeHelpers';
+import Flex from "shared/components/Flex";
+import { ListPlaceholder } from "components/LoadingPlaceholder";
+import Revision from "./components/Revision";
+import { documentHistoryUrl } from "utils/routeHelpers";
type Props = {
match: Object,
@@ -146,4 +146,4 @@ const Sidebar = styled(Flex)`
z-index: 1;
`;
-export default inject('documents', 'revisions')(DocumentHistory);
+export default inject("documents", "revisions")(DocumentHistory);
diff --git a/app/components/DocumentHistory/components/Revision.js b/app/components/DocumentHistory/components/Revision.js
index ee277c58..30ae6c5a 100644
--- a/app/components/DocumentHistory/components/Revision.js
+++ b/app/components/DocumentHistory/components/Revision.js
@@ -1,18 +1,18 @@
// @flow
-import * as React from 'react';
-import { NavLink } from 'react-router-dom';
-import styled, { withTheme } from 'styled-components';
-import format from 'date-fns/format';
-import { MoreIcon } from 'outline-icons';
+import * as React from "react";
+import { NavLink } from "react-router-dom";
+import styled, { withTheme } from "styled-components";
+import format from "date-fns/format";
+import { MoreIcon } from "outline-icons";
-import Flex from 'shared/components/Flex';
-import Time from 'shared/components/Time';
-import Avatar from 'components/Avatar';
-import RevisionMenu from 'menus/RevisionMenu';
-import Document from 'models/Document';
-import Revision from 'models/Revision';
+import Flex from "shared/components/Flex";
+import Time from "shared/components/Time";
+import Avatar from "components/Avatar";
+import RevisionMenu from "menus/RevisionMenu";
+import Document from "models/Document";
+import Revision from "models/Revision";
-import { documentHistoryUrl } from 'utils/routeHelpers';
+import { documentHistoryUrl } from "utils/routeHelpers";
type Props = {
theme: Object,
@@ -32,12 +32,12 @@ class RevisionListItem extends React.Component {
activeStyle={{ background: theme.primary, color: theme.white }}
>
- {' '}
+ {" "}
{revision.createdBy.name}
{showMenu && (
diff --git a/app/components/DocumentHistory/index.js b/app/components/DocumentHistory/index.js
index e533e31a..7d566709 100644
--- a/app/components/DocumentHistory/index.js
+++ b/app/components/DocumentHistory/index.js
@@ -1,3 +1,3 @@
// @flow
-import DocumentHistory from './DocumentHistory';
+import DocumentHistory from "./DocumentHistory";
export default DocumentHistory;
diff --git a/app/components/DocumentList.js b/app/components/DocumentList.js
index 98f9f41d..d12e730b 100644
--- a/app/components/DocumentList.js
+++ b/app/components/DocumentList.js
@@ -1,8 +1,8 @@
// @flow
-import * as React from 'react';
-import Document from 'models/Document';
-import DocumentPreview from 'components/DocumentPreview';
-import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
+import * as React from "react";
+import Document from "models/Document";
+import DocumentPreview from "components/DocumentPreview";
+import ArrowKeyNavigation from "boundless-arrow-key-navigation";
type Props = {
documents: Document[],
diff --git a/app/components/DocumentPreview/DocumentPreview.js b/app/components/DocumentPreview/DocumentPreview.js
index 740d26af..48321d48 100644
--- a/app/components/DocumentPreview/DocumentPreview.js
+++ b/app/components/DocumentPreview/DocumentPreview.js
@@ -1,16 +1,16 @@
// @flow
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { Link } from 'react-router-dom';
-import { StarredIcon } from 'outline-icons';
-import styled, { withTheme } from 'styled-components';
-import Flex from 'shared/components/Flex';
-import Badge from 'components/Badge';
-import Tooltip from 'components/Tooltip';
-import Highlight from 'components/Highlight';
-import PublishingInfo from 'components/PublishingInfo';
-import DocumentMenu from 'menus/DocumentMenu';
-import Document from 'models/Document';
+import * as React from "react";
+import { observer } from "mobx-react";
+import { Link } from "react-router-dom";
+import { StarredIcon } from "outline-icons";
+import styled, { withTheme } from "styled-components";
+import Flex from "shared/components/Flex";
+import Badge from "components/Badge";
+import Tooltip from "components/Tooltip";
+import Highlight from "components/Highlight";
+import PublishingInfo from "components/PublishingInfo";
+import DocumentMenu from "menus/DocumentMenu";
+import Document from "models/Document";
type Props = {
document: Document,
@@ -26,7 +26,7 @@ const StyledStar = withTheme(styled(({ solid, theme, ...props }) => (
))`
flex-shrink: 0;
- opacity: ${props => (props.solid ? '1 !important' : 0)};
+ opacity: ${props => (props.solid ? "1 !important" : 0)};
transition: all 100ms ease-in-out;
&:hover {
@@ -82,8 +82,8 @@ const Heading = styled.h3`
margin-bottom: 0.25em;
overflow: hidden;
white-space: nowrap;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
- Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
+ Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
`;
const Actions = styled(Flex)`
@@ -124,7 +124,7 @@ class DocumentPreview extends React.Component {
replaceResultMarks = (tag: string) => {
// don't use SEARCH_RESULT_REGEX here as it causes
// an infinite loop to trigger a regex inside it's own callback
- return tag.replace(/]*>(.*?)<\/b>/gi, '$1');
+ return tag.replace(/]*>(.*?)<\/b>/gi, "$1");
};
render() {
@@ -152,7 +152,7 @@ class DocumentPreview extends React.Component {
{...rest}
>
-
+
{!document.isDraft &&
!document.isArchived && (
diff --git a/app/components/DocumentPreview/index.js b/app/components/DocumentPreview/index.js
index a5a2b1af..5b0f0d9d 100644
--- a/app/components/DocumentPreview/index.js
+++ b/app/components/DocumentPreview/index.js
@@ -1,3 +1,3 @@
// @flow
-import DocumentPreview from './DocumentPreview';
+import DocumentPreview from "./DocumentPreview";
export default DocumentPreview;
diff --git a/app/components/DropToImport.js b/app/components/DropToImport.js
index 6dc8fd01..7276e87b 100644
--- a/app/components/DropToImport.js
+++ b/app/components/DropToImport.js
@@ -1,14 +1,14 @@
// @flow
-import * as React from 'react';
-import { observable } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import { withRouter, type RouterHistory } from 'react-router-dom';
-import { createGlobalStyle } from 'styled-components';
-import invariant from 'invariant';
-import importFile from 'utils/importFile';
-import Dropzone from 'react-dropzone';
-import DocumentsStore from 'stores/DocumentsStore';
-import LoadingIndicator from 'components/LoadingIndicator';
+import * as React from "react";
+import { observable } from "mobx";
+import { observer, inject } from "mobx-react";
+import { withRouter, type RouterHistory } from "react-router-dom";
+import { createGlobalStyle } from "styled-components";
+import invariant from "invariant";
+import importFile from "utils/importFile";
+import Dropzone from "react-dropzone";
+import DocumentsStore from "stores/DocumentsStore";
+import LoadingIndicator from "components/LoadingIndicator";
const EMPTY_OBJECT = {};
let importingLock = false;
@@ -56,7 +56,7 @@ class DropToImport extends React.Component {
if (documentId && !collectionId) {
const document = await this.props.documents.fetch(documentId);
- invariant(document, 'Document not available');
+ invariant(document, "Document not available");
collectionId = document.collectionId;
}
@@ -110,4 +110,4 @@ class DropToImport extends React.Component {
}
}
-export default inject('documents')(withRouter(DropToImport));
+export default inject("documents")(withRouter(DropToImport));
diff --git a/app/components/DropdownMenu/DropdownMenu.js b/app/components/DropdownMenu/DropdownMenu.js
index d4bab28b..8b8a8f6d 100644
--- a/app/components/DropdownMenu/DropdownMenu.js
+++ b/app/components/DropdownMenu/DropdownMenu.js
@@ -1,14 +1,14 @@
// @flow
-import * as React from 'react';
-import invariant from 'invariant';
-import { observable } from 'mobx';
-import { observer } from 'mobx-react';
-import { PortalWithState } from 'react-portal';
-import { MoreIcon } from 'outline-icons';
-import styled from 'styled-components';
-import Flex from 'shared/components/Flex';
-import { fadeAndScaleIn } from 'shared/styles/animations';
-import NudeButton from 'components/NudeButton';
+import * as React from "react";
+import invariant from "invariant";
+import { observable } from "mobx";
+import { observer } from "mobx-react";
+import { PortalWithState } from "react-portal";
+import { MoreIcon } from "outline-icons";
+import styled from "styled-components";
+import Flex from "shared/components/Flex";
+import { fadeAndScaleIn } from "shared/styles/animations";
+import NudeButton from "components/NudeButton";
let previousClosePortal;
let counter = 0;
@@ -25,7 +25,7 @@ type Props = {
className?: string,
hover?: boolean,
style?: Object,
- position?: 'left' | 'right' | 'center',
+ position?: "left" | "right" | "center",
};
@observer
@@ -37,7 +37,7 @@ class DropdownMenu extends React.Component {
@observable bottom: ?number;
@observable right: ?number;
@observable left: ?number;
- @observable position: 'left' | 'right' | 'center';
+ @observable position: "left" | "right" | "center";
@observable fixed: ?boolean;
@observable bodyRect: ClientRect;
@observable labelRect: ClientRect;
@@ -51,21 +51,21 @@ class DropdownMenu extends React.Component {
return (ev: SyntheticMouseEvent) => {
ev.preventDefault();
const currentTarget = ev.currentTarget;
- invariant(document.body, 'why you not here');
+ invariant(document.body, "why you not here");
if (currentTarget instanceof HTMLDivElement) {
this.bodyRect = document.body.getBoundingClientRect();
this.labelRect = currentTarget.getBoundingClientRect();
this.top = this.labelRect.bottom - this.bodyRect.top;
this.bottom = undefined;
- this.position = this.props.position || 'left';
+ this.position = this.props.position || "left";
if (currentTarget.parentElement) {
const triggerParentStyle = getComputedStyle(
currentTarget.parentElement
);
- if (triggerParentStyle.position === 'static') {
+ if (triggerParentStyle.position === "static") {
this.fixed = true;
this.top = this.labelRect.bottom;
}
@@ -84,10 +84,10 @@ class DropdownMenu extends React.Component {
};
initPosition() {
- if (this.position === 'left') {
+ if (this.position === "left") {
this.right =
this.bodyRect.width - this.labelRect.left - this.labelRect.width;
- } else if (this.position === 'center') {
+ } else if (this.position === "center") {
this.left = this.labelRect.left + this.labelRect.width / 2;
} else {
this.left = this.labelRect.left;
@@ -95,7 +95,7 @@ class DropdownMenu extends React.Component {
}
onOpen = () => {
- if (typeof this.props.onOpen === 'function') {
+ if (typeof this.props.onOpen === "function") {
this.props.onOpen();
}
this.fitOnTheScreen();
@@ -114,18 +114,18 @@ class DropdownMenu extends React.Component {
this.bottom = undefined;
}
- if (this.position === 'left' || this.position === 'right') {
+ if (this.position === "left" || this.position === "right") {
const totalWidth =
- Math.sign(this.position === 'left' ? -1 : 1) * el.offsetLeft +
+ Math.sign(this.position === "left" ? -1 : 1) * el.offsetLeft +
el.scrollWidth;
const isVisible = totalWidth < window.innerWidth;
if (!isVisible) {
- if (this.position === 'right') {
- this.position = 'left';
+ if (this.position === "right") {
+ this.position = "left";
this.left = undefined;
- } else if (this.position === 'left') {
- this.position = 'right';
+ } else if (this.position === "left") {
+ this.position = "right";
this.right = undefined;
}
}
@@ -177,7 +177,7 @@ class DropdownMenu extends React.Component {
@@ -201,7 +201,7 @@ class DropdownMenu extends React.Component {
hover ? this.closeAfterTimeout(closePortal) : undefined
}
onClick={
- typeof children === 'function'
+ typeof children === "function"
? undefined
: ev => {
ev.stopPropagation();
@@ -213,7 +213,7 @@ class DropdownMenu extends React.Component {
aria-labelledby={`${this.id}button`}
role="menu"
>
- {typeof children === 'function'
+ {typeof children === "function"
? children({ closePortal })
: children}
@@ -228,35 +228,35 @@ class DropdownMenu extends React.Component {
}
const Label = styled(Flex).attrs({
- justify: 'center',
- align: 'center',
+ justify: "center",
+ align: "center",
})`
z-index: 1000;
cursor: pointer;
`;
const Position = styled.div`
- position: ${({ fixed }) => (fixed ? 'fixed' : 'absolute')};
+ position: ${({ fixed }) => (fixed ? "fixed" : "absolute")};
display: flex;
- ${({ left }) => (left !== undefined ? `left: ${left}px` : '')};
- ${({ right }) => (right !== undefined ? `right: ${right}px` : '')};
- ${({ top }) => (top !== undefined ? `top: ${top}px` : '')};
- ${({ bottom }) => (bottom !== undefined ? `bottom: ${bottom}px` : '')};
+ ${({ left }) => (left !== undefined ? `left: ${left}px` : "")};
+ ${({ right }) => (right !== undefined ? `right: ${right}px` : "")};
+ ${({ top }) => (top !== undefined ? `top: ${top}px` : "")};
+ ${({ bottom }) => (bottom !== undefined ? `bottom: ${bottom}px` : "")};
max-height: 75%;
z-index: 1000;
transform: ${props =>
- props.position === 'center' ? 'translateX(-50%)' : 'initial'};
+ props.position === "center" ? "translateX(-50%)" : "initial"};
pointer-events: none;
`;
const Menu = styled.div`
animation: ${fadeAndScaleIn} 200ms ease;
- transform-origin: ${props => (props.left !== undefined ? '25%' : '75%')} 0;
+ transform-origin: ${props => (props.left !== undefined ? "25%" : "75%")} 0;
background: ${props => props.theme.menuBackground};
${props =>
props.theme.menuBorder
? `border: 1px solid ${props.theme.menuBorder}`
- : ''};
+ : ""};
border-radius: 2px;
padding: 0.5em 0;
min-width: 180px;
diff --git a/app/components/DropdownMenu/DropdownMenuItem.js b/app/components/DropdownMenu/DropdownMenuItem.js
index e3ef4220..0526e34a 100644
--- a/app/components/DropdownMenu/DropdownMenuItem.js
+++ b/app/components/DropdownMenu/DropdownMenuItem.js
@@ -1,7 +1,7 @@
// @flow
-import * as React from 'react';
-import { CheckmarkIcon } from 'outline-icons';
-import styled from 'styled-components';
+import * as React from "react";
+import { CheckmarkIcon } from "outline-icons";
+import styled from "styled-components";
type Props = {
onClick?: (SyntheticEvent<>) => void | Promise,
@@ -28,7 +28,7 @@ const DropdownMenuItem = ({
{selected !== undefined && (
)}
@@ -57,12 +57,12 @@ const MenuItem = styled.a`
}
svg {
- opacity: ${props => (props.disabled ? '.5' : 1)};
+ opacity: ${props => (props.disabled ? ".5" : 1)};
}
${props =>
props.disabled
- ? 'pointer-events: none;'
+ ? "pointer-events: none;"
: `
&:hover {
diff --git a/app/components/DropdownMenu/index.js b/app/components/DropdownMenu/index.js
index f6b44681..b1fa231a 100644
--- a/app/components/DropdownMenu/index.js
+++ b/app/components/DropdownMenu/index.js
@@ -1,3 +1,3 @@
// @flow
-export { default as DropdownMenu } from './DropdownMenu';
-export { default as DropdownMenuItem } from './DropdownMenuItem';
+export { default as DropdownMenu } from "./DropdownMenu";
+export { default as DropdownMenuItem } from "./DropdownMenuItem";
diff --git a/app/components/Editor/Editor.js b/app/components/Editor/Editor.js
index 61591f9a..9e3e80c6 100644
--- a/app/components/Editor/Editor.js
+++ b/app/components/Editor/Editor.js
@@ -1,16 +1,16 @@
// @flow
-import * as React from 'react';
-import { withRouter, type RouterHistory } from 'react-router-dom';
-import { observable } from 'mobx';
-import { observer } from 'mobx-react';
-import { lighten } from 'polished';
-import styled, { withTheme } from 'styled-components';
-import RichMarkdownEditor from 'rich-markdown-editor';
-import { uploadFile } from 'utils/uploadFile';
-import isInternalUrl from 'utils/isInternalUrl';
-import Tooltip from 'components/Tooltip';
-import UiStore from 'stores/UiStore';
-import embeds from '../../embeds';
+import * as React from "react";
+import { withRouter, type RouterHistory } from "react-router-dom";
+import { observable } from "mobx";
+import { observer } from "mobx-react";
+import { lighten } from "polished";
+import styled, { withTheme } from "styled-components";
+import RichMarkdownEditor from "rich-markdown-editor";
+import { uploadFile } from "utils/uploadFile";
+import isInternalUrl from "utils/isInternalUrl";
+import Tooltip from "components/Tooltip";
+import UiStore from "stores/UiStore";
+import embeds from "../../embeds";
const EMPTY_ARRAY = [];
@@ -36,7 +36,7 @@ class Editor extends React.Component {
onClickLink = (href: string) => {
// on page hash
- if (href[0] === '#') {
+ if (href[0] === "#") {
window.location.href = href;
return;
}
@@ -46,7 +46,7 @@ class Editor extends React.Component {
let navigateTo = href;
// probably absolute
- if (href[0] !== '/') {
+ if (href[0] !== "/") {
try {
const url = new URL(href);
navigateTo = url.pathname + url.hash;
@@ -57,7 +57,7 @@ class Editor extends React.Component {
this.props.history.push(navigateTo);
} else {
- window.open(href, '_blank');
+ window.open(href, "_blank");
}
};
diff --git a/app/components/Editor/index.js b/app/components/Editor/index.js
index 223d4abd..cef858be 100644
--- a/app/components/Editor/index.js
+++ b/app/components/Editor/index.js
@@ -1,3 +1,3 @@
// @flow
-import Editor from './Editor';
+import Editor from "./Editor";
export default Editor;
diff --git a/app/components/Empty.js b/app/components/Empty.js
index 1c990992..4f78753a 100644
--- a/app/components/Empty.js
+++ b/app/components/Empty.js
@@ -1,6 +1,6 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
+import * as React from "react";
+import styled from "styled-components";
type Props = {
children: React.Node,
diff --git a/app/components/ErrorBoundary.js b/app/components/ErrorBoundary.js
index c28c5369..eb1b6062 100644
--- a/app/components/ErrorBoundary.js
+++ b/app/components/ErrorBoundary.js
@@ -1,13 +1,13 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import { observer } from 'mobx-react';
-import { observable } from 'mobx';
-import HelpText from 'components/HelpText';
-import Button from 'components/Button';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import { githubIssuesUrl } from '../../shared/utils/routeHelpers';
+import * as React from "react";
+import styled from "styled-components";
+import { observer } from "mobx-react";
+import { observable } from "mobx";
+import HelpText from "components/HelpText";
+import Button from "components/Button";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import { githubIssuesUrl } from "../../shared/utils/routeHelpers";
type Props = {
children: React.Node,
@@ -49,12 +49,12 @@ class ErrorBoundary extends React.Component {
Something Unexpected Happened
Sorry, an unrecoverable error occurred{isReported &&
- ' – our engineers have been notified'}. Please try reloading the
+ " – our engineers have been notified"}. Please try reloading the
page, it may have been a temporary glitch.
{this.showDetails &&
{this.error.toString()}
}
- {' '}
+ {" "}
{this.showDetails ? (
@@ -128,4 +128,4 @@ class CollectionEdit extends React.Component {
}
}
-export default inject('ui')(CollectionEdit);
+export default inject("ui")(CollectionEdit);
diff --git a/app/scenes/CollectionExport.js b/app/scenes/CollectionExport.js
index c539cb7d..1ef04c32 100644
--- a/app/scenes/CollectionExport.js
+++ b/app/scenes/CollectionExport.js
@@ -1,13 +1,13 @@
// @flow
-import * as React from 'react';
-import { observable } from 'mobx';
-import { inject, observer } from 'mobx-react';
-import Button from 'components/Button';
-import Flex from 'shared/components/Flex';
-import HelpText from 'components/HelpText';
-import Collection from 'models/Collection';
-import AuthStore from 'stores/AuthStore';
-import UiStore from 'stores/UiStore';
+import * as React from "react";
+import { observable } from "mobx";
+import { inject, observer } from "mobx-react";
+import Button from "components/Button";
+import Flex from "shared/components/Flex";
+import HelpText from "components/HelpText";
+import Collection from "models/Collection";
+import AuthStore from "stores/AuthStore";
+import UiStore from "stores/UiStore";
type Props = {
collection: Collection,
@@ -42,7 +42,7 @@ class CollectionExport extends React.Component {
with files in Markdown format.
@@ -50,4 +50,4 @@ class CollectionExport extends React.Component {
}
}
-export default inject('ui', 'auth')(CollectionExport);
+export default inject("ui", "auth")(CollectionExport);
diff --git a/app/scenes/CollectionMembers/AddGroupsToCollection.js b/app/scenes/CollectionMembers/AddGroupsToCollection.js
index b805bad2..30d55811 100644
--- a/app/scenes/CollectionMembers/AddGroupsToCollection.js
+++ b/app/scenes/CollectionMembers/AddGroupsToCollection.js
@@ -1,23 +1,23 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import { inject, observer } from 'mobx-react';
-import { observable } from 'mobx';
-import { debounce } from 'lodash';
-import Button from 'components/Button';
-import Flex from 'shared/components/Flex';
-import HelpText from 'components/HelpText';
-import Input from 'components/Input';
-import Modal from 'components/Modal';
-import Empty from 'components/Empty';
-import PaginatedList from 'components/PaginatedList';
-import GroupNew from 'scenes/GroupNew';
-import Collection from 'models/Collection';
-import UiStore from 'stores/UiStore';
-import AuthStore from 'stores/AuthStore';
-import GroupsStore from 'stores/GroupsStore';
-import CollectionGroupMembershipsStore from 'stores/CollectionGroupMembershipsStore';
-import GroupListItem from 'components/GroupListItem';
+import * as React from "react";
+import styled from "styled-components";
+import { inject, observer } from "mobx-react";
+import { observable } from "mobx";
+import { debounce } from "lodash";
+import Button from "components/Button";
+import Flex from "shared/components/Flex";
+import HelpText from "components/HelpText";
+import Input from "components/Input";
+import Modal from "components/Modal";
+import Empty from "components/Empty";
+import PaginatedList from "components/PaginatedList";
+import GroupNew from "scenes/GroupNew";
+import Collection from "models/Collection";
+import UiStore from "stores/UiStore";
+import AuthStore from "stores/AuthStore";
+import GroupsStore from "stores/GroupsStore";
+import CollectionGroupMembershipsStore from "stores/CollectionGroupMembershipsStore";
+import GroupListItem from "components/GroupListItem";
type Props = {
ui: UiStore,
@@ -31,7 +31,7 @@ type Props = {
@observer
class AddGroupsToCollection extends React.Component {
@observable newGroupModalOpen: boolean = false;
- @observable query: string = '';
+ @observable query: string = "";
handleNewGroupModalOpen = () => {
this.newGroupModalOpen = true;
@@ -57,11 +57,11 @@ class AddGroupsToCollection extends React.Component {
this.props.collectionGroupMemberships.create({
collectionId: this.props.collection.id,
groupId: group.id,
- permission: 'read_write',
+ permission: "read_write",
});
this.props.ui.showToast(`${group.name} was added to the collection`);
} catch (err) {
- this.props.ui.showToast('Could not add user');
+ this.props.ui.showToast("Could not add user");
console.error(err);
}
};
@@ -74,7 +74,7 @@ class AddGroupsToCollection extends React.Component {
return (
- Can’t find the group you’re looking for?{' '}
+ Can’t find the group you’re looking for?{" "}
Create a group
.
@@ -130,6 +130,6 @@ const ButtonWrap = styled.div`
margin-left: 6px;
`;
-export default inject('auth', 'groups', 'collectionGroupMemberships', 'ui')(
+export default inject("auth", "groups", "collectionGroupMemberships", "ui")(
AddGroupsToCollection
);
diff --git a/app/scenes/CollectionMembers/AddPeopleToCollection.js b/app/scenes/CollectionMembers/AddPeopleToCollection.js
index 00b69bf3..c53499fd 100644
--- a/app/scenes/CollectionMembers/AddPeopleToCollection.js
+++ b/app/scenes/CollectionMembers/AddPeopleToCollection.js
@@ -1,21 +1,21 @@
// @flow
-import * as React from 'react';
-import { inject, observer } from 'mobx-react';
-import { observable } from 'mobx';
-import { debounce } from 'lodash';
-import Flex from 'shared/components/Flex';
-import HelpText from 'components/HelpText';
-import Input from 'components/Input';
-import Modal from 'components/Modal';
-import Empty from 'components/Empty';
-import PaginatedList from 'components/PaginatedList';
-import Invite from 'scenes/Invite';
-import Collection from 'models/Collection';
-import UiStore from 'stores/UiStore';
-import AuthStore from 'stores/AuthStore';
-import UsersStore from 'stores/UsersStore';
-import MembershipsStore from 'stores/MembershipsStore';
-import MemberListItem from './components/MemberListItem';
+import * as React from "react";
+import { inject, observer } from "mobx-react";
+import { observable } from "mobx";
+import { debounce } from "lodash";
+import Flex from "shared/components/Flex";
+import HelpText from "components/HelpText";
+import Input from "components/Input";
+import Modal from "components/Modal";
+import Empty from "components/Empty";
+import PaginatedList from "components/PaginatedList";
+import Invite from "scenes/Invite";
+import Collection from "models/Collection";
+import UiStore from "stores/UiStore";
+import AuthStore from "stores/AuthStore";
+import UsersStore from "stores/UsersStore";
+import MembershipsStore from "stores/MembershipsStore";
+import MemberListItem from "./components/MemberListItem";
type Props = {
ui: UiStore,
@@ -29,7 +29,7 @@ type Props = {
@observer
class AddPeopleToCollection extends React.Component {
@observable inviteModalOpen: boolean = false;
- @observable query: string = '';
+ @observable query: string = "";
handleInviteModalOpen = () => {
this.inviteModalOpen = true;
@@ -55,11 +55,11 @@ class AddPeopleToCollection extends React.Component {
this.props.memberships.create({
collectionId: this.props.collection.id,
userId: user.id,
- permission: 'read_write',
+ permission: "read_write",
});
this.props.ui.showToast(`${user.name} was added to the collection`);
} catch (err) {
- this.props.ui.showToast('Could not add user');
+ this.props.ui.showToast("Could not add user");
}
};
@@ -71,7 +71,7 @@ class AddPeopleToCollection extends React.Component {
return (
- Need to add someone who’s not yet on the team yet?{' '}
+ Need to add someone who’s not yet on the team yet?{" "}
Invite people to {team.name}
.
@@ -118,6 +118,6 @@ class AddPeopleToCollection extends React.Component {
}
}
-export default inject('auth', 'users', 'memberships', 'ui')(
+export default inject("auth", "users", "memberships", "ui")(
AddPeopleToCollection
);
diff --git a/app/scenes/CollectionMembers/CollectionMembers.js b/app/scenes/CollectionMembers/CollectionMembers.js
index 2e56703f..2ca673a2 100644
--- a/app/scenes/CollectionMembers/CollectionMembers.js
+++ b/app/scenes/CollectionMembers/CollectionMembers.js
@@ -1,27 +1,27 @@
// @flow
-import * as React from 'react';
-import { observable } from 'mobx';
-import styled from 'styled-components';
-import { inject, observer } from 'mobx-react';
-import { PlusIcon } from 'outline-icons';
-import Flex from 'shared/components/Flex';
-import HelpText from 'components/HelpText';
-import Subheading from 'components/Subheading';
-import Button from 'components/Button';
-import Empty from 'components/Empty';
-import PaginatedList from 'components/PaginatedList';
-import Modal from 'components/Modal';
-import CollectionGroupMemberListItem from './components/CollectionGroupMemberListItem';
-import Collection from 'models/Collection';
-import UiStore from 'stores/UiStore';
-import AuthStore from 'stores/AuthStore';
-import MembershipsStore from 'stores/MembershipsStore';
-import CollectionGroupMembershipsStore from 'stores/CollectionGroupMembershipsStore';
-import UsersStore from 'stores/UsersStore';
-import MemberListItem from './components/MemberListItem';
-import AddPeopleToCollection from './AddPeopleToCollection';
-import AddGroupsToCollection from './AddGroupsToCollection';
-import GroupsStore from 'stores/GroupsStore';
+import * as React from "react";
+import { observable } from "mobx";
+import styled from "styled-components";
+import { inject, observer } from "mobx-react";
+import { PlusIcon } from "outline-icons";
+import Flex from "shared/components/Flex";
+import HelpText from "components/HelpText";
+import Subheading from "components/Subheading";
+import Button from "components/Button";
+import Empty from "components/Empty";
+import PaginatedList from "components/PaginatedList";
+import Modal from "components/Modal";
+import CollectionGroupMemberListItem from "./components/CollectionGroupMemberListItem";
+import Collection from "models/Collection";
+import UiStore from "stores/UiStore";
+import AuthStore from "stores/AuthStore";
+import MembershipsStore from "stores/MembershipsStore";
+import CollectionGroupMembershipsStore from "stores/CollectionGroupMembershipsStore";
+import UsersStore from "stores/UsersStore";
+import MemberListItem from "./components/MemberListItem";
+import AddPeopleToCollection from "./AddPeopleToCollection";
+import AddGroupsToCollection from "./AddGroupsToCollection";
+import GroupsStore from "stores/GroupsStore";
type Props = {
ui: UiStore,
@@ -63,7 +63,7 @@ class CollectionMembers extends React.Component {
});
this.props.ui.showToast(`${user.name} was removed from the collection`);
} catch (err) {
- this.props.ui.showToast('Could not remove user');
+ this.props.ui.showToast("Could not remove user");
}
};
@@ -76,7 +76,7 @@ class CollectionMembers extends React.Component {
});
this.props.ui.showToast(`${user.name} permissions were updated`);
} catch (err) {
- this.props.ui.showToast('Could not update user');
+ this.props.ui.showToast("Could not update user");
}
};
@@ -88,7 +88,7 @@ class CollectionMembers extends React.Component {
});
this.props.ui.showToast(`${group.name} was removed from the collection`);
} catch (err) {
- this.props.ui.showToast('Could not remove group');
+ this.props.ui.showToast("Could not remove group");
}
};
@@ -101,7 +101,7 @@ class CollectionMembers extends React.Component {
});
this.props.ui.showToast(`${group.name} permissions were updated`);
} catch (err) {
- this.props.ui.showToast('Could not update user');
+ this.props.ui.showToast("Could not update user");
}
};
@@ -120,7 +120,7 @@ class CollectionMembers extends React.Component {
const key = memberships.orderedData
.map(m => m.permission)
.concat(collection.private)
- .join('-');
+ .join("-");
return (
@@ -128,9 +128,9 @@ class CollectionMembers extends React.Component {
Choose which groups and team members have access to view and edit
- documents in the private {collection.name}{' '}
+ documents in the private {collection.name}{" "}
collection. You can make this collection visible to the entire
- team by{' '}
+ team by{" "}
changing its visibility
.
@@ -150,7 +150,7 @@ class CollectionMembers extends React.Component {
The {collection.name} collection is accessible by
everyone on the team. If you want to limit who can view the
- collection,{' '}
+ collection,{" "}
make it private
.
@@ -250,10 +250,10 @@ const GroupsWrap = styled.div`
`;
export default inject(
- 'auth',
- 'users',
- 'memberships',
- 'collectionGroupMemberships',
- 'groups',
- 'ui'
+ "auth",
+ "users",
+ "memberships",
+ "collectionGroupMemberships",
+ "groups",
+ "ui"
)(CollectionMembers);
diff --git a/app/scenes/CollectionMembers/components/CollectionGroupMemberListItem.js b/app/scenes/CollectionMembers/components/CollectionGroupMemberListItem.js
index d57e5d0d..8a111bb5 100644
--- a/app/scenes/CollectionMembers/components/CollectionGroupMemberListItem.js
+++ b/app/scenes/CollectionMembers/components/CollectionGroupMemberListItem.js
@@ -1,15 +1,15 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import InputSelect from 'components/InputSelect';
-import GroupListItem from 'components/GroupListItem';
-import Group from 'models/Group';
-import CollectionGroupMembership from 'models/CollectionGroupMembership';
-import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
+import * as React from "react";
+import styled from "styled-components";
+import InputSelect from "components/InputSelect";
+import GroupListItem from "components/GroupListItem";
+import Group from "models/Group";
+import CollectionGroupMembership from "models/CollectionGroupMembership";
+import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu";
const PERMISSIONS = [
- { label: 'Read only', value: 'read' },
- { label: 'Read & Edit', value: 'read_write' },
+ { label: "Read only", value: "read" },
+ { label: "Read & Edit", value: "read_write" },
];
type Props = {
group: Group,
diff --git a/app/scenes/CollectionMembers/components/MemberListItem.js b/app/scenes/CollectionMembers/components/MemberListItem.js
index 08f71d1a..26815839 100644
--- a/app/scenes/CollectionMembers/components/MemberListItem.js
+++ b/app/scenes/CollectionMembers/components/MemberListItem.js
@@ -1,20 +1,20 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import Avatar from 'components/Avatar';
-import Flex from 'shared/components/Flex';
-import Time from 'shared/components/Time';
-import Badge from 'components/Badge';
-import Button from 'components/Button';
-import InputSelect from 'components/InputSelect';
-import ListItem from 'components/List/Item';
-import User from 'models/User';
-import Membership from 'models/Membership';
-import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
+import * as React from "react";
+import styled from "styled-components";
+import Avatar from "components/Avatar";
+import Flex from "shared/components/Flex";
+import Time from "shared/components/Time";
+import Badge from "components/Badge";
+import Button from "components/Button";
+import InputSelect from "components/InputSelect";
+import ListItem from "components/List/Item";
+import User from "models/User";
+import Membership from "models/Membership";
+import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu";
const PERMISSIONS = [
- { label: 'Read only', value: 'read' },
- { label: 'Read & Edit', value: 'read_write' },
+ { label: "Read only", value: "read" },
+ { label: "Read & Edit", value: "read_write" },
];
type Props = {
user: User,
@@ -43,7 +43,7 @@ const MemberListItem = ({
Active ago
) : (
- 'Never signed in'
+ "Never signed in"
)}
{!user.lastActiveAt && Invited}
{user.isAdmin && Admin}
diff --git a/app/scenes/CollectionMembers/components/UserListItem.js b/app/scenes/CollectionMembers/components/UserListItem.js
index 60fa848d..19663088 100644
--- a/app/scenes/CollectionMembers/components/UserListItem.js
+++ b/app/scenes/CollectionMembers/components/UserListItem.js
@@ -1,12 +1,12 @@
// @flow
-import * as React from 'react';
-import { PlusIcon } from 'outline-icons';
-import Time from 'shared/components/Time';
-import Avatar from 'components/Avatar';
-import Button from 'components/Button';
-import Badge from 'components/Badge';
-import ListItem from 'components/List/Item';
-import User from 'models/User';
+import * as React from "react";
+import { PlusIcon } from "outline-icons";
+import Time from "shared/components/Time";
+import Avatar from "components/Avatar";
+import Button from "components/Button";
+import Badge from "components/Badge";
+import ListItem from "components/List/Item";
+import User from "models/User";
type Props = {
user: User,
@@ -26,7 +26,7 @@ const UserListItem = ({ user, onAdd, canEdit }: Props) => {
Active ago
) : (
- 'Never signed in'
+ "Never signed in"
)}
{!user.lastActiveAt && Invited}
{user.isAdmin && Admin}
diff --git a/app/scenes/CollectionMembers/index.js b/app/scenes/CollectionMembers/index.js
index d58df3c7..e1607f75 100644
--- a/app/scenes/CollectionMembers/index.js
+++ b/app/scenes/CollectionMembers/index.js
@@ -1,3 +1,3 @@
// @flow
-import CollectionMembers from './CollectionMembers';
+import CollectionMembers from "./CollectionMembers";
export default CollectionMembers;
diff --git a/app/scenes/CollectionNew.js b/app/scenes/CollectionNew.js
index 9573d0ea..ed628595 100644
--- a/app/scenes/CollectionNew.js
+++ b/app/scenes/CollectionNew.js
@@ -1,20 +1,20 @@
// @flow
-import * as React from 'react';
-import { withRouter, type RouterHistory } from 'react-router-dom';
-import { observable } from 'mobx';
-import { inject, observer } from 'mobx-react';
-import { intersection } from 'lodash';
-import Button from 'components/Button';
-import Switch from 'components/Switch';
-import Input from 'components/Input';
-import InputRich from 'components/InputRich';
-import IconPicker, { icons } from 'components/IconPicker';
-import HelpText from 'components/HelpText';
-import Flex from 'shared/components/Flex';
+import * as React from "react";
+import { withRouter, type RouterHistory } from "react-router-dom";
+import { observable } from "mobx";
+import { inject, observer } from "mobx-react";
+import { intersection } from "lodash";
+import Button from "components/Button";
+import Switch from "components/Switch";
+import Input from "components/Input";
+import InputRich from "components/InputRich";
+import IconPicker, { icons } from "components/IconPicker";
+import HelpText from "components/HelpText";
+import Flex from "shared/components/Flex";
-import Collection from 'models/Collection';
-import CollectionsStore from 'stores/CollectionsStore';
-import UiStore from 'stores/UiStore';
+import Collection from "models/Collection";
+import CollectionsStore from "stores/CollectionsStore";
+import UiStore from "stores/UiStore";
type Props = {
history: RouterHistory,
@@ -25,10 +25,10 @@ type Props = {
@observer
class CollectionNew extends React.Component {
- @observable name: string = '';
- @observable description: string = '';
- @observable icon: string = '';
- @observable color: string = '#4E5C6E';
+ @observable name: string = "";
+ @observable description: string = "";
+ @observable icon: string = "";
+ @observable color: string = "#4E5C6E";
@observable private: boolean = false;
@observable isSaving: boolean;
hasOpenedIconPicker: boolean = false;
@@ -67,8 +67,8 @@ class CollectionNew extends React.Component {
const keys = Object.keys(icons);
for (const key of keys) {
const icon = icons[key];
- const keywords = icon.keywords.split(' ');
- const namewords = this.name.toLowerCase().split(' ');
+ const keywords = icon.keywords.split(" ");
+ const namewords = this.name.toLowerCase().split(" ");
const matches = intersection(namewords, keywords);
if (matches.length > 0) {
@@ -77,7 +77,7 @@ class CollectionNew extends React.Component {
}
}
- this.icon = 'collection';
+ this.icon = "collection";
}
};
@@ -127,7 +127,7 @@ class CollectionNew extends React.Component {
{
);
}
}
-export default inject('collections', 'ui')(withRouter(CollectionNew));
+export default inject("collections", "ui")(withRouter(CollectionNew));
diff --git a/app/scenes/Dashboard.js b/app/scenes/Dashboard.js
index 556a991c..c798b983 100644
--- a/app/scenes/Dashboard.js
+++ b/app/scenes/Dashboard.js
@@ -1,18 +1,18 @@
// @flow
-import * as React from 'react';
-import { Switch, Route } from 'react-router-dom';
-import { observer, inject } from 'mobx-react';
+import * as React from "react";
+import { Switch, Route } from "react-router-dom";
+import { observer, inject } from "mobx-react";
-import DocumentsStore from 'stores/DocumentsStore';
-import AuthStore from 'stores/AuthStore';
-import NewDocumentMenu from 'menus/NewDocumentMenu';
-import Actions, { Action } from 'components/Actions';
-import InputSearch from 'components/InputSearch';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import Tabs from 'components/Tabs';
-import Tab from 'components/Tab';
-import PaginatedDocumentList from '../components/PaginatedDocumentList';
+import DocumentsStore from "stores/DocumentsStore";
+import AuthStore from "stores/AuthStore";
+import NewDocumentMenu from "menus/NewDocumentMenu";
+import Actions, { Action } from "components/Actions";
+import InputSearch from "components/InputSearch";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import Tabs from "components/Tabs";
+import Tab from "components/Tab";
+import PaginatedDocumentList from "../components/PaginatedDocumentList";
type Props = {
documents: DocumentsStore,
@@ -78,4 +78,4 @@ class Dashboard extends React.Component {
}
}
-export default inject('documents', 'auth')(Dashboard);
+export default inject("documents", "auth")(Dashboard);
diff --git a/app/scenes/Document/KeyedDocument.js b/app/scenes/Document/KeyedDocument.js
index edb2d9f8..bdcf554a 100644
--- a/app/scenes/Document/KeyedDocument.js
+++ b/app/scenes/Document/KeyedDocument.js
@@ -1,7 +1,7 @@
// @flow
-import * as React from 'react';
-import { inject } from 'mobx-react';
-import DataLoader from './components/DataLoader';
+import * as React from "react";
+import { inject } from "mobx-react";
+import DataLoader from "./components/DataLoader";
class KeyedDocument extends React.Component<*> {
componentWillUnmount() {
@@ -15,11 +15,11 @@ class KeyedDocument extends React.Component<*> {
// 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 urlParts = documentSlug ? documentSlug.split("-") : [];
const urlId = urlParts.length ? urlParts[urlParts.length - 1] : undefined;
- return ;
+ return ;
}
}
-export default inject('ui')(KeyedDocument);
+export default inject("ui")(KeyedDocument);
diff --git a/app/scenes/Document/components/Container.js b/app/scenes/Document/components/Container.js
index b60ee3fc..a248aa0d 100644
--- a/app/scenes/Document/components/Container.js
+++ b/app/scenes/Document/components/Container.js
@@ -1,10 +1,10 @@
// @flow
-import styled from 'styled-components';
-import Flex from 'shared/components/Flex';
+import styled from "styled-components";
+import Flex from "shared/components/Flex";
const Container = styled(Flex)`
position: relative;
- margin-top: ${props => (props.isShare ? '50px' : '0')};
+ margin-top: ${props => (props.isShare ? "50px" : "0")};
`;
export default Container;
diff --git a/app/scenes/Document/components/Contents.js b/app/scenes/Document/components/Contents.js
index 03de590b..55df7e7c 100644
--- a/app/scenes/Document/components/Contents.js
+++ b/app/scenes/Document/components/Contents.js
@@ -1,10 +1,10 @@
// @flow
-import * as React from 'react';
-import { darken } from 'polished';
-import breakpoint from 'styled-components-breakpoint';
-import useWindowScrollPosition from '@rehooks/window-scroll-position';
-import HelpText from 'components/HelpText';
-import styled from 'styled-components';
+import * as React from "react";
+import { darken } from "polished";
+import breakpoint from "styled-components-breakpoint";
+import useWindowScrollPosition from "@rehooks/window-scroll-position";
+import HelpText from "components/HelpText";
+import styled from "styled-components";
const HEADING_OFFSET = 20;
@@ -72,7 +72,7 @@ export default function Contents({ headings }: Props) {
);
}
-const Wrapper = styled('div')`
+const Wrapper = styled("div")`
display: none;
position: sticky;
top: 80px;
@@ -82,16 +82,16 @@ const Wrapper = styled('div')`
margin-right: 2em;
min-height: 40px;
- ${breakpoint('desktopLarge')`
+ ${breakpoint("desktopLarge")`
margin-left: -16em;
`};
- ${breakpoint('tablet')`
+ ${breakpoint("tablet")`
display: block;
`};
`;
-const Heading = styled('h3')`
+const Heading = styled("h3")`
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
@@ -107,16 +107,16 @@ const Empty = styled(HelpText)`
font-size: 14px;
`;
-const ListItem = styled('li')`
+const ListItem = styled("li")`
margin-left: ${props => (props.level - 1) * 10}px;
margin-bottom: 8px;
padding-right: 2em;
line-height: 1.3;
border-right: 3px solid
- ${props => (props.active ? props.theme.textSecondary : 'transparent')};
+ ${props => (props.active ? props.theme.textSecondary : "transparent")};
`;
-const Link = styled('a')`
+const Link = styled("a")`
color: ${props => props.theme.text};
font-size: 14px;
@@ -125,7 +125,7 @@ const Link = styled('a')`
}
`;
-const List = styled('ol')`
+const List = styled("ol")`
min-width: 14em;
width: 14em;
padding: 0;
diff --git a/app/scenes/Document/components/DataLoader.js b/app/scenes/Document/components/DataLoader.js
index f9025a60..5cb93ce4 100644
--- a/app/scenes/Document/components/DataLoader.js
+++ b/app/scenes/Document/components/DataLoader.js
@@ -1,24 +1,24 @@
// @flow
-import * as React from 'react';
-import invariant from 'invariant';
-import { withRouter } from 'react-router-dom';
-import type { Location, RouterHistory } from 'react-router-dom';
-import { observable } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import { matchDocumentEdit, updateDocumentUrl } from 'utils/routeHelpers';
-import DocumentComponent from './Document';
-import Revision from 'models/Revision';
-import Document from 'models/Document';
-import SocketPresence from './SocketPresence';
-import Loading from './Loading';
-import HideSidebar from './HideSidebar';
-import Error404 from 'scenes/Error404';
-import ErrorOffline from 'scenes/ErrorOffline';
-import DocumentsStore from 'stores/DocumentsStore';
-import PoliciesStore from 'stores/PoliciesStore';
-import RevisionsStore from 'stores/RevisionsStore';
-import UiStore from 'stores/UiStore';
-import { OfflineError } from 'utils/errors';
+import * as React from "react";
+import invariant from "invariant";
+import { withRouter } from "react-router-dom";
+import type { Location, RouterHistory } from "react-router-dom";
+import { observable } from "mobx";
+import { observer, inject } from "mobx-react";
+import { matchDocumentEdit, updateDocumentUrl } from "utils/routeHelpers";
+import DocumentComponent from "./Document";
+import Revision from "models/Revision";
+import Document from "models/Document";
+import SocketPresence from "./SocketPresence";
+import Loading from "./Loading";
+import HideSidebar from "./HideSidebar";
+import Error404 from "scenes/Error404";
+import ErrorOffline from "scenes/ErrorOffline";
+import DocumentsStore from "stores/DocumentsStore";
+import PoliciesStore from "stores/PoliciesStore";
+import RevisionsStore from "stores/RevisionsStore";
+import UiStore from "stores/UiStore";
+import { OfflineError } from "utils/errors";
type Props = {|
match: Object,
@@ -84,13 +84,13 @@ class DataLoader extends React.Component {
onCreateLink = async (title: string) => {
const document = this.document;
- invariant(document, 'document must be loaded to create link');
+ invariant(document, "document must be loaded to create link");
const newDocument = await this.props.documents.create({
collectionId: document.collectionId,
parentDocumentId: document.parentDocumentId,
title,
- text: '',
+ text: "",
});
return newDocument.url;
@@ -166,7 +166,7 @@ class DataLoader extends React.Component {
}
const abilities = policies.abilities(document.id);
- const key = this.isEditing ? 'editing' : 'read-only';
+ const key = this.isEditing ? "editing" : "read-only";
return (
@@ -187,5 +187,5 @@ class DataLoader extends React.Component {
}
export default withRouter(
- inject('ui', 'auth', 'documents', 'revisions', 'policies')(DataLoader)
+ inject("ui", "auth", "documents", "revisions", "policies")(DataLoader)
);
diff --git a/app/scenes/Document/components/Document.js b/app/scenes/Document/components/Document.js
index 84828ac2..7fc5509c 100644
--- a/app/scenes/Document/components/Document.js
+++ b/app/scenes/Document/components/Document.js
@@ -1,42 +1,42 @@
// @flow
-import * as React from 'react';
-import { debounce } from 'lodash';
-import styled, { withTheme } from 'styled-components';
-import breakpoint from 'styled-components-breakpoint';
-import { observable } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import { Prompt, Route, withRouter } from 'react-router-dom';
-import type { Location, RouterHistory } from 'react-router-dom';
-import keydown from 'react-keydown';
-import Flex from 'shared/components/Flex';
+import * as React from "react";
+import { debounce } from "lodash";
+import styled, { withTheme } from "styled-components";
+import breakpoint from "styled-components-breakpoint";
+import { observable } from "mobx";
+import { observer, inject } from "mobx-react";
+import { Prompt, Route, withRouter } from "react-router-dom";
+import type { Location, RouterHistory } from "react-router-dom";
+import keydown from "react-keydown";
+import Flex from "shared/components/Flex";
import {
collectionUrl,
documentMoveUrl,
documentHistoryUrl,
documentEditUrl,
documentUrl,
-} from 'utils/routeHelpers';
-import { emojiToUrl } from 'utils/emoji';
+} from "utils/routeHelpers";
+import { emojiToUrl } from "utils/emoji";
-import Header from './Header';
-import DocumentMove from './DocumentMove';
-import KeyboardShortcutsButton from './KeyboardShortcutsButton';
-import References from './References';
-import Loading from './Loading';
-import Container from './Container';
-import Contents from './Contents';
-import MarkAsViewed from './MarkAsViewed';
-import ErrorBoundary from 'components/ErrorBoundary';
-import LoadingIndicator from 'components/LoadingIndicator';
-import PageTitle from 'components/PageTitle';
-import Branding from 'shared/components/Branding';
-import Notice from 'shared/components/Notice';
-import Time from 'shared/components/Time';
+import Header from "./Header";
+import DocumentMove from "./DocumentMove";
+import KeyboardShortcutsButton from "./KeyboardShortcutsButton";
+import References from "./References";
+import Loading from "./Loading";
+import Container from "./Container";
+import Contents from "./Contents";
+import MarkAsViewed from "./MarkAsViewed";
+import ErrorBoundary from "components/ErrorBoundary";
+import LoadingIndicator from "components/LoadingIndicator";
+import PageTitle from "components/PageTitle";
+import Branding from "shared/components/Branding";
+import Notice from "shared/components/Notice";
+import Time from "shared/components/Time";
-import UiStore from 'stores/UiStore';
-import AuthStore from 'stores/AuthStore';
-import Document from 'models/Document';
-import Revision from 'models/Revision';
+import UiStore from "stores/UiStore";
+import AuthStore from "stores/AuthStore";
+import Document from "models/Document";
+import Revision from "models/Revision";
let EditorImport;
const AUTOSAVE_DELAY = 3000;
@@ -104,7 +104,7 @@ class DocumentScene extends React.Component {
{
timeout: 30 * 1000,
action: {
- text: 'Reload',
+ text: "Reload",
onClick: () => {
window.location.href = documentUrl(document);
},
@@ -123,7 +123,7 @@ class DocumentScene extends React.Component {
window.document.body.style.background = this.props.theme.background;
}
- @keydown('m')
+ @keydown("m")
goToMove(ev) {
if (!this.props.readOnly) return;
@@ -135,7 +135,7 @@ class DocumentScene extends React.Component {
}
}
- @keydown('e')
+ @keydown("e")
goToEdit(ev) {
if (!this.props.readOnly) return;
@@ -147,7 +147,7 @@ class DocumentScene extends React.Component {
}
}
- @keydown('esc')
+ @keydown("esc")
goBack(ev) {
if (this.props.readOnly) return;
@@ -155,7 +155,7 @@ class DocumentScene extends React.Component {
this.props.history.goBack();
}
- @keydown('h')
+ @keydown("h")
goToHistory(ev) {
if (!this.props.readOnly) return;
@@ -169,7 +169,7 @@ class DocumentScene extends React.Component {
}
}
- @keydown('meta+shift+p')
+ @keydown("meta+shift+p")
onPublish(ev) {
ev.preventDefault();
const { document } = this.props;
@@ -177,7 +177,7 @@ class DocumentScene extends React.Component {
this.onSave({ publish: true, done: true });
}
- @keydown('meta+ctrl+h')
+ @keydown("meta+ctrl+h")
onToggleTableOfContents(ev) {
if (!this.props.readOnly) return;
@@ -195,7 +195,7 @@ class DocumentScene extends React.Component {
if (this.editorComponent) return;
try {
- const EditorImport = await import('./Editor');
+ const EditorImport = await import("./Editor");
this.editorComponent = EditorImport.default;
} catch (err) {
console.error(err);
@@ -227,7 +227,7 @@ class DocumentScene extends React.Component {
const title = this.title;
// prevent save before anything has been written (single hash is empty doc)
- if (text.trim() === '' && title.trim === '') return;
+ if (text.trim() === "" && title.trim === "") return;
// prevent autosave if nothing has changed
if (
@@ -278,7 +278,7 @@ class DocumentScene extends React.Component {
const bodyChanged = editorText !== document.text.trim();
// a single hash is a doc with just an empty title
- this.isEmpty = (!editorText || editorText === '#') && !this.title;
+ this.isEmpty = (!editorText || editorText === "#") && !this.title;
this.isDirty = bodyChanged || titleChanged;
};
@@ -351,7 +351,7 @@ class DocumentScene extends React.Component {
)}
/>
{(this.isUploading || this.isSaving) && }
@@ -394,18 +394,18 @@ class DocumentScene extends React.Component {
{document.archivedAt &&
!document.deletedAt && (
- Archived by {document.updatedBy.name}{' '}
+ Archived by {document.updatedBy.name}{" "}
ago
)}
{document.deletedAt && (
- Deleted by {document.updatedBy.name}{' '}
+ Deleted by {document.updatedBy.name}{" "}
ago
{document.permanentlyDeletedAt && (
- This document will be permanently deleted in{' '}
+ This document will be permanently deleted in{" "}
unless
restored.
@@ -427,7 +427,7 @@ class DocumentScene extends React.Component {
}
}}
isDraft={document.isDraft}
- key={disableEmbeds ? 'embeds-disabled' : 'embeds-enabled'}
+ key={disableEmbeds ? "embeds-disabled" : "embeds-enabled"}
title={revision ? revision.title : this.title}
document={document}
value={readOnly ? value : undefined}
@@ -470,7 +470,7 @@ const Background = styled(Container)`
transition: ${props => props.theme.backgroundTransition};
`;
-const ReferencesWrapper = styled('div')`
+const ReferencesWrapper = styled("div")`
margin-top: ${props => (props.isOnlyTitle ? -45 : 16)}px;
@media print {
@@ -485,17 +485,17 @@ const MaxWidth = styled(Flex)`
max-width: 100vw;
width: 100%;
- ${breakpoint('tablet')`
+ ${breakpoint("tablet")`
padding: 0 24px;
margin: 4px auto 12px;
- max-width: calc(48px + ${props => (props.tocVisible ? '64em' : '46em')});
+ max-width: calc(48px + ${props => (props.tocVisible ? "64em" : "46em")});
`};
- ${breakpoint('desktopLarge')`
+ ${breakpoint("desktopLarge")`
max-width: calc(48px + 46em);
`};
`;
export default withRouter(
- inject('ui', 'auth', 'policies', 'revisions')(withTheme(DocumentScene))
+ inject("ui", "auth", "policies", "revisions")(withTheme(DocumentScene))
);
diff --git a/app/scenes/Document/components/DocumentMeta.js b/app/scenes/Document/components/DocumentMeta.js
index 0070b7d2..44542bee 100644
--- a/app/scenes/Document/components/DocumentMeta.js
+++ b/app/scenes/Document/components/DocumentMeta.js
@@ -1,10 +1,10 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import { inject } from 'mobx-react';
-import ViewsStore from 'stores/ViewsStore';
-import Document from 'models/Document';
-import PublishingInfo from 'components/PublishingInfo';
+import * as React from "react";
+import styled from "styled-components";
+import { inject } from "mobx-react";
+import ViewsStore from "stores/ViewsStore";
+import Document from "models/Document";
+import PublishingInfo from "components/PublishingInfo";
type Props = {|
views: ViewsStore,
@@ -19,8 +19,8 @@ function DocumentMeta({ views, isDraft, document }: Props) {
{totalViews && !isDraft ? (
- · Viewed{' '}
- {totalViews === 1 ? 'once' : `${totalViews} times`}
+ · Viewed{" "}
+ {totalViews === 1 ? "once" : `${totalViews} times`}
) : null}
@@ -36,4 +36,4 @@ const Meta = styled(PublishingInfo)`
}
`;
-export default inject('views')(DocumentMeta);
+export default inject("views")(DocumentMeta);
diff --git a/app/scenes/Document/components/DocumentMove.js b/app/scenes/Document/components/DocumentMove.js
index 2d11bdee..22df2a86 100644
--- a/app/scenes/Document/components/DocumentMove.js
+++ b/app/scenes/Document/components/DocumentMove.js
@@ -1,23 +1,23 @@
// @flow
-import * as React from 'react';
-import ReactDOM from 'react-dom';
-import { observable, computed } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import { Search } from 'js-search';
-import { last } from 'lodash';
-import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
-import styled from 'styled-components';
+import * as React from "react";
+import ReactDOM from "react-dom";
+import { observable, computed } from "mobx";
+import { observer, inject } from "mobx-react";
+import { Search } from "js-search";
+import { last } from "lodash";
+import ArrowKeyNavigation from "boundless-arrow-key-navigation";
+import styled from "styled-components";
-import Modal from 'components/Modal';
-import Input from 'components/Input';
-import Labeled from 'components/Labeled';
-import PathToDocument from 'components/PathToDocument';
-import Flex from 'shared/components/Flex';
+import Modal from "components/Modal";
+import Input from "components/Input";
+import Labeled from "components/Labeled";
+import PathToDocument from "components/PathToDocument";
+import Flex from "shared/components/Flex";
-import Document from 'models/Document';
-import DocumentsStore from 'stores/DocumentsStore';
-import UiStore from 'stores/UiStore';
-import CollectionsStore, { type DocumentPath } from 'stores/CollectionsStore';
+import Document from "models/Document";
+import DocumentsStore from "stores/DocumentsStore";
+import UiStore from "stores/UiStore";
+import CollectionsStore, { type DocumentPath } from "stores/CollectionsStore";
const MAX_RESULTS = 8;
@@ -39,8 +39,8 @@ class DocumentMove extends React.Component {
get searchIndex() {
const { collections } = this.props;
const paths = collections.pathsToDocuments;
- const index = new Search('id');
- index.addIndex('title');
+ const index = new Search("id");
+ index.addIndex("title");
// Build index
const indexeableDocuments = [];
@@ -90,7 +90,7 @@ class DocumentMove extends React.Component {
};
handleSuccess = () => {
- this.props.ui.showToast('Document moved');
+ this.props.ui.showToast("Document moved");
this.props.onRequestClose();
};
@@ -180,4 +180,4 @@ const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
flex: 1;
`;
-export default inject('documents', 'collections', 'ui')(DocumentMove);
+export default inject("documents", "collections", "ui")(DocumentMove);
diff --git a/app/scenes/Document/components/Editor.js b/app/scenes/Document/components/Editor.js
index e54d060a..bb8141f1 100644
--- a/app/scenes/Document/components/Editor.js
+++ b/app/scenes/Document/components/Editor.js
@@ -1,14 +1,14 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import Textarea from 'react-autosize-textarea';
-import { observer } from 'mobx-react';
-import Editor from 'components/Editor';
-import ClickablePadding from 'components/ClickablePadding';
-import Flex from 'shared/components/Flex';
-import parseTitle from 'shared/utils/parseTitle';
-import Document from 'models/Document';
-import DocumentMeta from './DocumentMeta';
+import * as React from "react";
+import styled from "styled-components";
+import Textarea from "react-autosize-textarea";
+import { observer } from "mobx-react";
+import Editor from "components/Editor";
+import ClickablePadding from "components/ClickablePadding";
+import Flex from "shared/components/Flex";
+import parseTitle from "shared/utils/parseTitle";
+import Document from "models/Document";
+import DocumentMeta from "./DocumentMeta";
type Props = {
onChangeTitle: (event: SyntheticInputEvent<>) => void,
@@ -44,7 +44,7 @@ class DocumentEditor extends React.Component {
};
handleTitleKeyDown = (event: SyntheticKeyboardEvent<>) => {
- if (event.key === 'Enter' || event.key === 'Tab') {
+ if (event.key === "Enter" || event.key === "Tab") {
event.preventDefault();
this.focusAtStart();
}
@@ -62,8 +62,8 @@ class DocumentEditor extends React.Component {
onChange={onChangeTitle}
onKeyDown={this.handleTitleKeyDown}
placeholder="Start with a title…"
- value={!title && readOnly ? 'Untitled' : title}
- style={startsWithEmojiAndSpace ? { marginLeft: '-1.2em' } : undefined}
+ value={!title && readOnly ? "Untitled" : title}
+ style={startsWithEmojiAndSpace ? { marginLeft: "-1.2em" } : undefined}
readOnly={readOnly}
autoFocus={!title}
maxLength={100}
diff --git a/app/scenes/Document/components/Header.js b/app/scenes/Document/components/Header.js
index 030142a6..d53a7631 100644
--- a/app/scenes/Document/components/Header.js
+++ b/app/scenes/Document/components/Header.js
@@ -1,32 +1,32 @@
// @flow
-import * as React from 'react';
-import { throttle } from 'lodash';
-import { observable } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import { Redirect } from 'react-router-dom';
-import styled from 'styled-components';
-import breakpoint from 'styled-components-breakpoint';
-import { TableOfContentsIcon, EditIcon, PlusIcon } from 'outline-icons';
-import { transparentize, darken } from 'polished';
-import Document from 'models/Document';
-import AuthStore from 'stores/AuthStore';
-import { documentEditUrl } from 'utils/routeHelpers';
-import { meta } from 'utils/keyboard';
+import * as React from "react";
+import { throttle } from "lodash";
+import { observable } from "mobx";
+import { observer, inject } from "mobx-react";
+import { Redirect } from "react-router-dom";
+import styled from "styled-components";
+import breakpoint from "styled-components-breakpoint";
+import { TableOfContentsIcon, EditIcon, PlusIcon } from "outline-icons";
+import { transparentize, darken } from "polished";
+import Document from "models/Document";
+import AuthStore from "stores/AuthStore";
+import { documentEditUrl } from "utils/routeHelpers";
+import { meta } from "utils/keyboard";
-import Flex from 'shared/components/Flex';
-import Breadcrumb, { Slash } from 'shared/components/Breadcrumb';
-import DocumentMenu from 'menus/DocumentMenu';
-import NewChildDocumentMenu from 'menus/NewChildDocumentMenu';
-import DocumentShare from 'scenes/DocumentShare';
-import Button from 'components/Button';
-import Tooltip from 'components/Tooltip';
-import Modal from 'components/Modal';
-import Fade from 'components/Fade';
-import Badge from 'components/Badge';
-import Collaborators from 'components/Collaborators';
-import { Action, Separator } from 'components/Actions';
-import PoliciesStore from 'stores/PoliciesStore';
-import UiStore from 'stores/UiStore';
+import Flex from "shared/components/Flex";
+import Breadcrumb, { Slash } from "shared/components/Breadcrumb";
+import DocumentMenu from "menus/DocumentMenu";
+import NewChildDocumentMenu from "menus/NewChildDocumentMenu";
+import DocumentShare from "scenes/DocumentShare";
+import Button from "components/Button";
+import Tooltip from "components/Tooltip";
+import Modal from "components/Modal";
+import Fade from "components/Fade";
+import Badge from "components/Badge";
+import Collaborators from "components/Collaborators";
+import { Action, Separator } from "components/Actions";
+import PoliciesStore from "stores/PoliciesStore";
+import UiStore from "stores/UiStore";
type Props = {
auth: AuthStore,
@@ -55,11 +55,11 @@ class Header extends React.Component {
@observable redirectTo: ?string;
componentDidMount() {
- window.addEventListener('scroll', this.handleScroll);
+ window.addEventListener("scroll", this.handleScroll);
}
componentWillUnmount() {
- window.removeEventListener('scroll', this.handleScroll);
+ window.removeEventListener("scroll", this.handleScroll);
}
updateIsScrolled = () => {
@@ -95,7 +95,7 @@ class Header extends React.Component {
handleClickTitle = () => {
window.scrollTo({
top: 0,
- behavior: 'smooth',
+ behavior: "smooth",
});
};
@@ -145,7 +145,7 @@ class Header extends React.Component {
{
neutral={isDraft}
small
>
- {isDraft ? 'Save Draft' : 'Done Editing'}
+ {isDraft ? "Save Draft" : "Done Editing"}
@@ -237,7 +237,7 @@ class Header extends React.Component {
disabled={publishingIsDisabled}
small
>
- {isPublishing ? 'Publishing…' : 'Publish'}
+ {isPublishing ? "Publishing…" : "Publish"}
@@ -307,7 +307,7 @@ const Status = styled.div`
const BreadcrumbAndContents = styled(Flex)`
display: none;
- ${breakpoint('tablet')`
+ ${breakpoint("tablet")`
display: flex;
width: 33.3%;
`};
@@ -318,7 +318,7 @@ const Wrapper = styled(Flex)`
align-self: flex-end;
height: 32px;
- ${breakpoint('tablet')`
+ ${breakpoint("tablet")`
width: 33.3%;
`};
`;
@@ -334,7 +334,7 @@ const Actions = styled(Flex)`
${props =>
props.isCompact
? darken(0.05, props.theme.sidebarBackground)
- : 'transparent'};
+ : "transparent"};
padding: 12px;
transition: all 100ms ease-out;
transform: translate3d(0, 0, 0);
@@ -344,8 +344,8 @@ const Actions = styled(Flex)`
display: none;
}
- ${breakpoint('tablet')`
- padding: ${props => (props.isCompact ? '12px' : `24px 24px 0`)};
+ ${breakpoint("tablet")`
+ padding: ${props => (props.isCompact ? "12px" : `24px 24px 0`)};
`};
`;
@@ -361,10 +361,10 @@ const Title = styled.div`
display: none;
width: 0;
- ${breakpoint('tablet')`
+ ${breakpoint("tablet")`
display: flex;
flex-grow: 1;
`};
`;
-export default inject('auth', 'ui', 'policies')(Header);
+export default inject("auth", "ui", "policies")(Header);
diff --git a/app/scenes/Document/components/HideSidebar.js b/app/scenes/Document/components/HideSidebar.js
index 98a6f118..05869b6b 100644
--- a/app/scenes/Document/components/HideSidebar.js
+++ b/app/scenes/Document/components/HideSidebar.js
@@ -1,6 +1,6 @@
// @flow
-import * as React from 'react';
-import UiStore from 'stores/UiStore';
+import * as React from "react";
+import UiStore from "stores/UiStore";
type Props = {
ui: UiStore,
diff --git a/app/scenes/Document/components/KeyboardShortcutsButton.js b/app/scenes/Document/components/KeyboardShortcutsButton.js
index a8dceea3..0647711b 100644
--- a/app/scenes/Document/components/KeyboardShortcutsButton.js
+++ b/app/scenes/Document/components/KeyboardShortcutsButton.js
@@ -1,14 +1,14 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import breakpoint from 'styled-components-breakpoint';
-import { observable } from 'mobx';
-import { observer } from 'mobx-react';
-import { KeyboardIcon } from 'outline-icons';
-import Modal from 'components/Modal';
-import Tooltip from 'components/Tooltip';
-import NudeButton from 'components/NudeButton';
-import KeyboardShortcuts from 'scenes/KeyboardShortcuts';
+import * as React from "react";
+import styled from "styled-components";
+import breakpoint from "styled-components-breakpoint";
+import { observable } from "mobx";
+import { observer } from "mobx-react";
+import { KeyboardIcon } from "outline-icons";
+import Modal from "components/Modal";
+import Tooltip from "components/Tooltip";
+import NudeButton from "components/NudeButton";
+import KeyboardShortcuts from "scenes/KeyboardShortcuts";
type Props = {};
@@ -56,7 +56,7 @@ const Button = styled(NudeButton)`
right: 0;
margin: 24px;
- ${breakpoint('tablet')`
+ ${breakpoint("tablet")`
display: block;
`};
diff --git a/app/scenes/Document/components/Loading.js b/app/scenes/Document/components/Loading.js
index 10b4e243..a717215c 100644
--- a/app/scenes/Document/components/Loading.js
+++ b/app/scenes/Document/components/Loading.js
@@ -1,10 +1,10 @@
// @flow
-import * as React from 'react';
-import type { Location } from 'react-router-dom';
-import Container from './Container';
-import LoadingPlaceholder from 'components/LoadingPlaceholder';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
+import * as React from "react";
+import type { Location } from "react-router-dom";
+import Container from "./Container";
+import LoadingPlaceholder from "components/LoadingPlaceholder";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
type Props = {|
location: Location,
@@ -13,7 +13,7 @@ type Props = {|
export default function Loading({ location }: Props) {
return (
-
+
diff --git a/app/scenes/Document/components/MarkAsViewed.js b/app/scenes/Document/components/MarkAsViewed.js
index 05c045f6..8a12504b 100644
--- a/app/scenes/Document/components/MarkAsViewed.js
+++ b/app/scenes/Document/components/MarkAsViewed.js
@@ -1,6 +1,6 @@
// @flow
-import * as React from 'react';
-import Document from 'models/Document';
+import * as React from "react";
+import Document from "models/Document";
const MARK_AS_VIEWED_AFTER = 3 * 1000;
diff --git a/app/scenes/Document/components/ReferenceListItem.js b/app/scenes/Document/components/ReferenceListItem.js
index e3913273..148f04bc 100644
--- a/app/scenes/Document/components/ReferenceListItem.js
+++ b/app/scenes/Document/components/ReferenceListItem.js
@@ -1,11 +1,11 @@
// @flow
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { Link } from 'react-router-dom';
-import styled from 'styled-components';
-import PublishingInfo from 'components/PublishingInfo';
-import Document from 'models/Document';
-import type { NavigationNode } from 'types';
+import * as React from "react";
+import { observer } from "mobx-react";
+import { Link } from "react-router-dom";
+import styled from "styled-components";
+import PublishingInfo from "components/PublishingInfo";
+import Document from "models/Document";
+import type { NavigationNode } from "types";
type Props = {
document: Document | NavigationNode,
@@ -39,8 +39,8 @@ const Title = styled.h3`
margin-top: 0;
margin-bottom: 0.25em;
white-space: nowrap;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
- Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
+ Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
`;
@observer
diff --git a/app/scenes/Document/components/References.js b/app/scenes/Document/components/References.js
index 2fcca1b0..3e6f7324 100644
--- a/app/scenes/Document/components/References.js
+++ b/app/scenes/Document/components/References.js
@@ -1,14 +1,14 @@
// @flow
-import * as React from 'react';
-import { observer, inject } from 'mobx-react';
-import { withRouter, type Location } from 'react-router-dom';
-import Fade from 'components/Fade';
-import Tabs from 'components/Tabs';
-import Tab from 'components/Tab';
-import DocumentsStore from 'stores/DocumentsStore';
-import CollectionsStore from 'stores/CollectionsStore';
-import Document from 'models/Document';
-import ReferenceListItem from './ReferenceListItem';
+import * as React from "react";
+import { observer, inject } from "mobx-react";
+import { withRouter, type Location } from "react-router-dom";
+import Fade from "components/Fade";
+import Tabs from "components/Tabs";
+import Tab from "components/Tab";
+import DocumentsStore from "stores/DocumentsStore";
+import CollectionsStore from "stores/CollectionsStore";
+import Document from "models/Document";
+import ReferenceListItem from "./ReferenceListItem";
type Props = {
document: Document,
@@ -34,7 +34,7 @@ class References extends React.Component {
const showBacklinks = !!backlinks.length;
const showChildren = !!children.length;
const isBacklinksTab =
- this.props.location.hash === '#backlinks' || !showChildren;
+ this.props.location.hash === "#backlinks" || !showChildren;
return (
(showBacklinks || showChildren) && (
@@ -80,4 +80,4 @@ class References extends React.Component {
}
}
-export default withRouter(inject('documents', 'collections')(References));
+export default withRouter(inject("documents", "collections")(References));
diff --git a/app/scenes/Document/components/SocketPresence.js b/app/scenes/Document/components/SocketPresence.js
index 2994dd3c..483a55f6 100644
--- a/app/scenes/Document/components/SocketPresence.js
+++ b/app/scenes/Document/components/SocketPresence.js
@@ -1,7 +1,7 @@
// @flow
-import * as React from 'react';
-import { SocketContext } from 'components/SocketProvider';
-import { USER_PRESENCE_INTERVAL } from 'shared/constants';
+import * as React from "react";
+import { SocketContext } from "components/SocketProvider";
+import { USER_PRESENCE_INTERVAL } from "shared/constants";
type Props = {
children?: React.Node,
@@ -33,8 +33,8 @@ export default class SocketPresence extends React.Component {
componentWillUnmount() {
if (this.context) {
- this.context.emit('leave', { documentId: this.props.documentId });
- this.context.off('authenticated', this.emitJoin);
+ this.context.emit("leave", { documentId: this.props.documentId });
+ this.context.off("authenticated", this.emitJoin);
}
clearInterval(this.editingInterval);
@@ -47,7 +47,7 @@ export default class SocketPresence extends React.Component {
if (this.context.authenticated) {
this.emitJoin();
}
- this.context.on('authenticated', () => {
+ this.context.on("authenticated", () => {
this.emitJoin();
});
}
@@ -56,7 +56,7 @@ export default class SocketPresence extends React.Component {
emitJoin = () => {
if (!this.context) return;
- this.context.emit('join', {
+ this.context.emit("join", {
documentId: this.props.documentId,
isEditing: this.props.isEditing,
});
@@ -65,7 +65,7 @@ export default class SocketPresence extends React.Component {
emitPresence = () => {
if (!this.context) return;
- this.context.emit('presence', {
+ this.context.emit("presence", {
documentId: this.props.documentId,
isEditing: this.props.isEditing,
});
diff --git a/app/scenes/Document/index.js b/app/scenes/Document/index.js
index 8b8fbddd..7af896b2 100644
--- a/app/scenes/Document/index.js
+++ b/app/scenes/Document/index.js
@@ -1,3 +1,3 @@
// @flow
-import DataLoader from './components/DataLoader';
+import DataLoader from "./components/DataLoader";
export default DataLoader;
diff --git a/app/scenes/DocumentDelete.js b/app/scenes/DocumentDelete.js
index 451802ad..2ba61e0b 100644
--- a/app/scenes/DocumentDelete.js
+++ b/app/scenes/DocumentDelete.js
@@ -1,15 +1,15 @@
// @flow
-import * as React from 'react';
-import { withRouter, type RouterHistory } from 'react-router-dom';
-import { observable } from 'mobx';
-import { inject, observer } from 'mobx-react';
-import Button from 'components/Button';
-import Flex from 'shared/components/Flex';
-import HelpText from 'components/HelpText';
-import Document from 'models/Document';
-import DocumentsStore from 'stores/DocumentsStore';
-import UiStore from 'stores/UiStore';
-import { collectionUrl } from 'utils/routeHelpers';
+import * as React from "react";
+import { withRouter, type RouterHistory } from "react-router-dom";
+import { observable } from "mobx";
+import { inject, observer } from "mobx-react";
+import Button from "components/Button";
+import Flex from "shared/components/Flex";
+import HelpText from "components/HelpText";
+import Document from "models/Document";
+import DocumentsStore from "stores/DocumentsStore";
+import UiStore from "stores/UiStore";
+import { collectionUrl } from "utils/routeHelpers";
type Props = {
history: RouterHistory,
@@ -49,7 +49,7 @@ class DocumentDelete extends React.Component {
@@ -69,4 +69,4 @@ class DocumentDelete extends React.Component {
}
}
-export default inject('documents', 'ui')(withRouter(DocumentDelete));
+export default inject("documents", "ui")(withRouter(DocumentDelete));
diff --git a/app/scenes/DocumentNew.js b/app/scenes/DocumentNew.js
index 53cfdaf2..1bbe6b08 100644
--- a/app/scenes/DocumentNew.js
+++ b/app/scenes/DocumentNew.js
@@ -1,13 +1,13 @@
// @flow
-import * as React from 'react';
-import { inject } from 'mobx-react';
-import type { RouterHistory, Location } from 'react-router-dom';
-import Flex from 'shared/components/Flex';
-import CenteredContent from 'components/CenteredContent';
-import LoadingPlaceholder from 'components/LoadingPlaceholder';
-import DocumentsStore from 'stores/DocumentsStore';
-import UiStore from 'stores/UiStore';
-import { documentEditUrl } from 'utils/routeHelpers';
+import * as React from "react";
+import { inject } from "mobx-react";
+import type { RouterHistory, Location } from "react-router-dom";
+import Flex from "shared/components/Flex";
+import CenteredContent from "components/CenteredContent";
+import LoadingPlaceholder from "components/LoadingPlaceholder";
+import DocumentsStore from "stores/DocumentsStore";
+import UiStore from "stores/UiStore";
+import { documentEditUrl } from "utils/routeHelpers";
type Props = {
history: RouterHistory,
@@ -23,14 +23,14 @@ class DocumentNew extends React.Component {
const document = await this.props.documents.create({
collectionId: this.props.match.params.id,
parentDocumentId: new URLSearchParams(this.props.location.search).get(
- 'parentDocumentId'
+ "parentDocumentId"
),
- title: '',
- text: '',
+ title: "",
+ text: "",
});
this.props.history.replace(documentEditUrl(document));
} catch (err) {
- this.props.ui.showToast('Couldn’t create the document, try again?');
+ this.props.ui.showToast("Couldn’t create the document, try again?");
this.props.history.goBack();
}
}
@@ -46,4 +46,4 @@ class DocumentNew extends React.Component {
}
}
-export default inject('documents', 'ui')(DocumentNew);
+export default inject("documents", "ui")(DocumentNew);
diff --git a/app/scenes/DocumentShare.js b/app/scenes/DocumentShare.js
index a2d3f30d..a79c753e 100644
--- a/app/scenes/DocumentShare.js
+++ b/app/scenes/DocumentShare.js
@@ -1,13 +1,13 @@
// @flow
-import * as React from 'react';
-import { observable } from 'mobx';
-import { observer } from 'mobx-react';
-import { Link } from 'react-router-dom';
-import Input from 'components/Input';
-import Button from 'components/Button';
-import CopyToClipboard from 'components/CopyToClipboard';
-import HelpText from 'components/HelpText';
-import Document from 'models/Document';
+import * as React from "react";
+import { observable } from "mobx";
+import { observer } from "mobx-react";
+import { Link } from "react-router-dom";
+import Input from "components/Input";
+import Button from "components/Button";
+import CopyToClipboard from "components/CopyToClipboard";
+import HelpText from "components/HelpText";
+import Document from "models/Document";
type Props = {
document: Document,
@@ -40,7 +40,7 @@ class DocumentShare extends React.Component {
The link below allows anyone in the world to access a read-only
version of the document {document.title}. You can
- revoke this link in settings at any time.{' '}
+ revoke this link in settings at any time.{" "}
Manage share links
.
@@ -48,15 +48,15 @@ class DocumentShare extends React.Component {
diff --git a/app/scenes/Drafts.js b/app/scenes/Drafts.js
index 451d0296..7914275c 100644
--- a/app/scenes/Drafts.js
+++ b/app/scenes/Drafts.js
@@ -1,17 +1,17 @@
// @flow
-import * as React from 'react';
-import { observer, inject } from 'mobx-react';
+import * as React from "react";
+import { observer, inject } from "mobx-react";
-import Heading from 'components/Heading';
-import CenteredContent from 'components/CenteredContent';
-import Empty from 'components/Empty';
-import PageTitle from 'components/PageTitle';
-import PaginatedDocumentList from 'components/PaginatedDocumentList';
-import Subheading from 'components/Subheading';
-import InputSearch from 'components/InputSearch';
-import NewDocumentMenu from 'menus/NewDocumentMenu';
-import Actions, { Action } from 'components/Actions';
-import DocumentsStore from 'stores/DocumentsStore';
+import Heading from "components/Heading";
+import CenteredContent from "components/CenteredContent";
+import Empty from "components/Empty";
+import PageTitle from "components/PageTitle";
+import PaginatedDocumentList from "components/PaginatedDocumentList";
+import Subheading from "components/Subheading";
+import InputSearch from "components/InputSearch";
+import NewDocumentMenu from "menus/NewDocumentMenu";
+import Actions, { Action } from "components/Actions";
+import DocumentsStore from "stores/DocumentsStore";
type Props = {
documents: DocumentsStore,
@@ -48,4 +48,4 @@ class Drafts extends React.Component {
}
}
-export default inject('documents')(Drafts);
+export default inject("documents")(Drafts);
diff --git a/app/scenes/Error404.js b/app/scenes/Error404.js
index 6d8e9a30..f5d5f988 100644
--- a/app/scenes/Error404.js
+++ b/app/scenes/Error404.js
@@ -1,8 +1,8 @@
// @flow
-import * as React from 'react';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import Empty from 'components/Empty';
+import * as React from "react";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import Empty from "components/Empty";
const Error404 = () => {
return (
diff --git a/app/scenes/ErrorOffline.js b/app/scenes/ErrorOffline.js
index 57d4c827..f40ac5f4 100644
--- a/app/scenes/ErrorOffline.js
+++ b/app/scenes/ErrorOffline.js
@@ -1,8 +1,8 @@
// @flow
-import * as React from 'react';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import Empty from 'components/Empty';
+import * as React from "react";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import Empty from "components/Empty";
const ErrorOffline = () => {
return (
diff --git a/app/scenes/ErrorSuspended.js b/app/scenes/ErrorSuspended.js
index 3f8041cd..009d9cd1 100644
--- a/app/scenes/ErrorSuspended.js
+++ b/app/scenes/ErrorSuspended.js
@@ -1,10 +1,10 @@
// @flow
-import * as React from 'react';
-import { inject, observer } from 'mobx-react';
+import * as React from "react";
+import { inject, observer } from "mobx-react";
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import AuthStore from 'stores/AuthStore';
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import AuthStore from "stores/AuthStore";
const ErrorSuspended = observer(({ auth }: { auth: AuthStore }) => {
return (
@@ -13,7 +13,7 @@ const ErrorSuspended = observer(({ auth }: { auth: AuthStore }) => {
⚠️
- {' '}
+ {" "}
Your account has been suspended
@@ -26,4 +26,4 @@ const ErrorSuspended = observer(({ auth }: { auth: AuthStore }) => {
);
});
-export default inject('auth')(ErrorSuspended);
+export default inject("auth")(ErrorSuspended);
diff --git a/app/scenes/GroupDelete.js b/app/scenes/GroupDelete.js
index 6377dda8..ff9aa6c9 100644
--- a/app/scenes/GroupDelete.js
+++ b/app/scenes/GroupDelete.js
@@ -1,14 +1,14 @@
// @flow
-import * as React from 'react';
-import { withRouter, type RouterHistory } from 'react-router-dom';
-import { observable } from 'mobx';
-import { inject, observer } from 'mobx-react';
-import { groupSettings } from 'shared/utils/routeHelpers';
-import Button from 'components/Button';
-import Flex from 'shared/components/Flex';
-import HelpText from 'components/HelpText';
-import Group from 'models/Group';
-import UiStore from 'stores/UiStore';
+import * as React from "react";
+import { withRouter, type RouterHistory } from "react-router-dom";
+import { observable } from "mobx";
+import { inject, observer } from "mobx-react";
+import { groupSettings } from "shared/utils/routeHelpers";
+import Button from "components/Button";
+import Flex from "shared/components/Flex";
+import HelpText from "components/HelpText";
+import Group from "models/Group";
+import UiStore from "stores/UiStore";
type Props = {
history: RouterHistory,
@@ -43,12 +43,12 @@ class GroupDelete extends React.Component {
@@ -56,4 +56,4 @@ class GroupDelete extends React.Component {
}
}
-export default inject('ui')(withRouter(GroupDelete));
+export default inject("ui")(withRouter(GroupDelete));
diff --git a/app/scenes/GroupEdit.js b/app/scenes/GroupEdit.js
index 2ddbd2ea..7efb4de1 100644
--- a/app/scenes/GroupEdit.js
+++ b/app/scenes/GroupEdit.js
@@ -1,15 +1,15 @@
// @flow
-import * as React from 'react';
-import { withRouter, type RouterHistory } from 'react-router-dom';
-import { observable } from 'mobx';
-import { inject, observer } from 'mobx-react';
-import Button from 'components/Button';
-import Input from 'components/Input';
-import HelpText from 'components/HelpText';
-import Flex from 'shared/components/Flex';
+import * as React from "react";
+import { withRouter, type RouterHistory } from "react-router-dom";
+import { observable } from "mobx";
+import { inject, observer } from "mobx-react";
+import Button from "components/Button";
+import Input from "components/Input";
+import HelpText from "components/HelpText";
+import Flex from "shared/components/Flex";
-import Group from 'models/Group';
-import UiStore from 'stores/UiStore';
+import Group from "models/Group";
+import UiStore from "stores/UiStore";
type Props = {
history: RouterHistory,
@@ -61,11 +61,11 @@ class GroupEdit extends React.Component {
);
}
}
-export default inject('ui')(withRouter(GroupEdit));
+export default inject("ui")(withRouter(GroupEdit));
diff --git a/app/scenes/GroupMembers/AddPeopleToGroup.js b/app/scenes/GroupMembers/AddPeopleToGroup.js
index 3b77e46e..dc274bba 100644
--- a/app/scenes/GroupMembers/AddPeopleToGroup.js
+++ b/app/scenes/GroupMembers/AddPeopleToGroup.js
@@ -1,21 +1,21 @@
// @flow
-import * as React from 'react';
-import { inject, observer } from 'mobx-react';
-import { observable } from 'mobx';
-import { debounce } from 'lodash';
-import Flex from 'shared/components/Flex';
-import HelpText from 'components/HelpText';
-import Input from 'components/Input';
-import Modal from 'components/Modal';
-import Empty from 'components/Empty';
-import PaginatedList from 'components/PaginatedList';
-import Invite from 'scenes/Invite';
-import Group from 'models/Group';
-import UiStore from 'stores/UiStore';
-import AuthStore from 'stores/AuthStore';
-import UsersStore from 'stores/UsersStore';
-import GroupMembershipsStore from 'stores/GroupMembershipsStore';
-import GroupMemberListItem from './components/GroupMemberListItem';
+import * as React from "react";
+import { inject, observer } from "mobx-react";
+import { observable } from "mobx";
+import { debounce } from "lodash";
+import Flex from "shared/components/Flex";
+import HelpText from "components/HelpText";
+import Input from "components/Input";
+import Modal from "components/Modal";
+import Empty from "components/Empty";
+import PaginatedList from "components/PaginatedList";
+import Invite from "scenes/Invite";
+import Group from "models/Group";
+import UiStore from "stores/UiStore";
+import AuthStore from "stores/AuthStore";
+import UsersStore from "stores/UsersStore";
+import GroupMembershipsStore from "stores/GroupMembershipsStore";
+import GroupMemberListItem from "./components/GroupMemberListItem";
type Props = {
ui: UiStore,
@@ -29,7 +29,7 @@ type Props = {
@observer
class AddPeopleToGroup extends React.Component {
@observable inviteModalOpen: boolean = false;
- @observable query: string = '';
+ @observable query: string = "";
handleInviteModalOpen = () => {
this.inviteModalOpen = true;
@@ -58,7 +58,7 @@ class AddPeopleToGroup extends React.Component {
});
this.props.ui.showToast(`${user.name} was added to the group`);
} catch (err) {
- this.props.ui.showToast('Could not add user');
+ this.props.ui.showToast("Could not add user");
}
};
@@ -71,7 +71,7 @@ class AddPeopleToGroup extends React.Component {
Add team members below to give them access to the group. Need to add
- someone who’s not yet on the team yet?{' '}
+ someone who’s not yet on the team yet?{" "}
Invite them to {team.name}
.
@@ -118,6 +118,6 @@ class AddPeopleToGroup extends React.Component {
}
}
-export default inject('auth', 'users', 'groupMemberships', 'ui')(
+export default inject("auth", "users", "groupMemberships", "ui")(
AddPeopleToGroup
);
diff --git a/app/scenes/GroupMembers/GroupMembers.js b/app/scenes/GroupMembers/GroupMembers.js
index 7ecc6aab..cd8d9147 100644
--- a/app/scenes/GroupMembers/GroupMembers.js
+++ b/app/scenes/GroupMembers/GroupMembers.js
@@ -1,23 +1,23 @@
// @flow
-import * as React from 'react';
-import { observable } from 'mobx';
-import { inject, observer } from 'mobx-react';
-import { PlusIcon } from 'outline-icons';
-import Flex from 'shared/components/Flex';
-import Empty from 'components/Empty';
-import HelpText from 'components/HelpText';
-import Subheading from 'components/Subheading';
-import Button from 'components/Button';
-import PaginatedList from 'components/PaginatedList';
-import Modal from 'components/Modal';
-import Group from 'models/Group';
-import UiStore from 'stores/UiStore';
-import AuthStore from 'stores/AuthStore';
-import GroupMembershipsStore from 'stores/GroupMembershipsStore';
-import UsersStore from 'stores/UsersStore';
-import PoliciesStore from 'stores/PoliciesStore';
-import GroupMemberListItem from './components/GroupMemberListItem';
-import AddPeopleToGroup from './AddPeopleToGroup';
+import * as React from "react";
+import { observable } from "mobx";
+import { inject, observer } from "mobx-react";
+import { PlusIcon } from "outline-icons";
+import Flex from "shared/components/Flex";
+import Empty from "components/Empty";
+import HelpText from "components/HelpText";
+import Subheading from "components/Subheading";
+import Button from "components/Button";
+import PaginatedList from "components/PaginatedList";
+import Modal from "components/Modal";
+import Group from "models/Group";
+import UiStore from "stores/UiStore";
+import AuthStore from "stores/AuthStore";
+import GroupMembershipsStore from "stores/GroupMembershipsStore";
+import UsersStore from "stores/UsersStore";
+import PoliciesStore from "stores/PoliciesStore";
+import GroupMemberListItem from "./components/GroupMemberListItem";
+import AddPeopleToGroup from "./AddPeopleToGroup";
type Props = {
ui: UiStore,
@@ -48,7 +48,7 @@ class GroupMembers extends React.Component {
});
this.props.ui.showToast(`${user.name} was removed from the group`);
} catch (err) {
- this.props.ui.showToast('Could not remove user');
+ this.props.ui.showToast("Could not remove user");
}
};
@@ -64,7 +64,7 @@ class GroupMembers extends React.Component {
{can.update ? (
- Add and remove team members in the {group.name}{' '}
+ Add and remove team members in the {group.name}{" "}
group. Adding people to the group will give them access to any
collections this group has been given access to.
@@ -119,6 +119,6 @@ class GroupMembers extends React.Component {
}
}
-export default inject('auth', 'users', 'policies', 'groupMemberships', 'ui')(
+export default inject("auth", "users", "policies", "groupMemberships", "ui")(
GroupMembers
);
diff --git a/app/scenes/GroupMembers/components/GroupMemberListItem.js b/app/scenes/GroupMembers/components/GroupMemberListItem.js
index 7f5d3d04..623671a3 100644
--- a/app/scenes/GroupMembers/components/GroupMemberListItem.js
+++ b/app/scenes/GroupMembers/components/GroupMemberListItem.js
@@ -1,14 +1,14 @@
// @flow
-import * as React from 'react';
-import Avatar from 'components/Avatar';
-import Flex from 'shared/components/Flex';
-import Time from 'shared/components/Time';
-import Badge from 'components/Badge';
-import Button from 'components/Button';
-import ListItem from 'components/List/Item';
-import User from 'models/User';
-import GroupMembership from 'models/GroupMembership';
-import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
+import * as React from "react";
+import Avatar from "components/Avatar";
+import Flex from "shared/components/Flex";
+import Time from "shared/components/Time";
+import Badge from "components/Badge";
+import Button from "components/Button";
+import ListItem from "components/List/Item";
+import User from "models/User";
+import GroupMembership from "models/GroupMembership";
+import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu";
type Props = {
user: User,
@@ -33,7 +33,7 @@ const GroupMemberListItem = ({
Active ago
) : (
- 'Never signed in'
+ "Never signed in"
)}
{!user.lastActiveAt && Invited}
{user.isAdmin && Admin}
diff --git a/app/scenes/GroupMembers/components/UserListItem.js b/app/scenes/GroupMembers/components/UserListItem.js
index a12bfe5e..cf7202c3 100644
--- a/app/scenes/GroupMembers/components/UserListItem.js
+++ b/app/scenes/GroupMembers/components/UserListItem.js
@@ -1,12 +1,12 @@
// @flow
-import * as React from 'react';
-import { PlusIcon } from 'outline-icons';
-import Time from 'shared/components/Time';
-import Avatar from 'components/Avatar';
-import Button from 'components/Button';
-import Badge from 'components/Badge';
-import ListItem from 'components/List/Item';
-import User from 'models/User';
+import * as React from "react";
+import { PlusIcon } from "outline-icons";
+import Time from "shared/components/Time";
+import Avatar from "components/Avatar";
+import Button from "components/Button";
+import Badge from "components/Badge";
+import ListItem from "components/List/Item";
+import User from "models/User";
type Props = {
user: User,
@@ -26,7 +26,7 @@ const UserListItem = ({ user, onAdd, canEdit }: Props) => {
Active ago
) : (
- 'Never signed in'
+ "Never signed in"
)}
{!user.lastActiveAt && Invited}
{user.isAdmin && Admin}
diff --git a/app/scenes/GroupMembers/index.js b/app/scenes/GroupMembers/index.js
index 4f97042c..53829cf8 100644
--- a/app/scenes/GroupMembers/index.js
+++ b/app/scenes/GroupMembers/index.js
@@ -1,3 +1,3 @@
// @flow
-import GroupMembers from './GroupMembers';
+import GroupMembers from "./GroupMembers";
export default GroupMembers;
diff --git a/app/scenes/GroupNew.js b/app/scenes/GroupNew.js
index 91fb3185..f7f7fefc 100644
--- a/app/scenes/GroupNew.js
+++ b/app/scenes/GroupNew.js
@@ -1,18 +1,18 @@
// @flow
-import * as React from 'react';
-import { withRouter, type RouterHistory } from 'react-router-dom';
-import { observable } from 'mobx';
-import { inject, observer } from 'mobx-react';
-import Button from 'components/Button';
-import Input from 'components/Input';
-import HelpText from 'components/HelpText';
-import Modal from 'components/Modal';
-import GroupMembers from 'scenes/GroupMembers';
-import Flex from 'shared/components/Flex';
+import * as React from "react";
+import { withRouter, type RouterHistory } from "react-router-dom";
+import { observable } from "mobx";
+import { inject, observer } from "mobx-react";
+import Button from "components/Button";
+import Input from "components/Input";
+import HelpText from "components/HelpText";
+import Modal from "components/Modal";
+import GroupMembers from "scenes/GroupMembers";
+import Flex from "shared/components/Flex";
-import Group from 'models/Group';
-import GroupsStore from 'stores/GroupsStore';
-import UiStore from 'stores/UiStore';
+import Group from "models/Group";
+import GroupsStore from "stores/GroupsStore";
+import UiStore from "stores/UiStore";
type Props = {
history: RouterHistory,
@@ -23,7 +23,7 @@ type Props = {
@observer
class GroupNew extends React.Component {
- @observable name: string = '';
+ @observable name: string = "";
@observable isSaving: boolean;
@observable group: Group;
@@ -73,7 +73,7 @@ class GroupNew extends React.Component {
You’ll be able to add people to the group next. {
}
}
-export default inject('groups', 'ui')(withRouter(GroupNew));
+export default inject("groups", "ui")(withRouter(GroupNew));
diff --git a/app/scenes/Home.js b/app/scenes/Home.js
index 2e4fd2f8..1f0b3f7e 100644
--- a/app/scenes/Home.js
+++ b/app/scenes/Home.js
@@ -1,8 +1,8 @@
// @flow
-import * as React from 'react';
-import { observer, inject } from 'mobx-react';
-import { Redirect } from 'react-router-dom';
-import AuthStore from 'stores/AuthStore';
+import * as React from "react";
+import { observer, inject } from "mobx-react";
+import { Redirect } from "react-router-dom";
+import AuthStore from "stores/AuthStore";
type Props = {
auth: AuthStore,
@@ -14,4 +14,4 @@ const Home = observer(({ auth }: Props) => {
return null;
});
-export default inject('auth')(Home);
+export default inject("auth")(Home);
diff --git a/app/scenes/Invite.js b/app/scenes/Invite.js
index 4647692c..e7aaebc4 100644
--- a/app/scenes/Invite.js
+++ b/app/scenes/Invite.js
@@ -1,23 +1,23 @@
// @flow
-import * as React from 'react';
-import { Link, withRouter, type RouterHistory } from 'react-router-dom';
-import { observable, action } from 'mobx';
-import { inject, observer } from 'mobx-react';
-import { CloseIcon } from 'outline-icons';
-import styled from 'styled-components';
-import Flex from 'shared/components/Flex';
-import Button from 'components/Button';
-import Input from 'components/Input';
-import CopyToClipboard from 'components/CopyToClipboard';
-import Checkbox from 'components/Checkbox';
-import HelpText from 'components/HelpText';
-import Tooltip from 'components/Tooltip';
-import NudeButton from 'components/NudeButton';
+import * as React from "react";
+import { Link, withRouter, type RouterHistory } from "react-router-dom";
+import { observable, action } from "mobx";
+import { inject, observer } from "mobx-react";
+import { CloseIcon } from "outline-icons";
+import styled from "styled-components";
+import Flex from "shared/components/Flex";
+import Button from "components/Button";
+import Input from "components/Input";
+import CopyToClipboard from "components/CopyToClipboard";
+import Checkbox from "components/Checkbox";
+import HelpText from "components/HelpText";
+import Tooltip from "components/Tooltip";
+import NudeButton from "components/NudeButton";
-import UiStore from 'stores/UiStore';
-import AuthStore from 'stores/AuthStore';
-import UsersStore from 'stores/UsersStore';
-import PoliciesStore from 'stores/PoliciesStore';
+import UiStore from "stores/UiStore";
+import AuthStore from "stores/AuthStore";
+import UsersStore from "stores/UsersStore";
+import PoliciesStore from "stores/PoliciesStore";
const MAX_INVITES = 20;
@@ -42,9 +42,9 @@ class Invite extends React.Component {
@observable linkCopied: boolean = false;
@observable
invites: InviteRequest[] = [
- { email: '', name: '', guest: false },
- { email: '', name: '', guest: false },
- { email: '', name: '', guest: false },
+ { email: "", name: "", guest: false },
+ { email: "", name: "", guest: false },
+ { email: "", name: "", guest: false },
];
handleSubmit = async (ev: SyntheticEvent<>) => {
@@ -54,7 +54,7 @@ class Invite extends React.Component {
try {
await this.props.users.invite(this.invites);
this.props.onSubmit();
- this.props.ui.showToast('We sent out your invites!');
+ this.props.ui.showToast("We sent out your invites!");
} catch (err) {
this.props.ui.showToast(err.message);
} finally {
@@ -80,7 +80,7 @@ class Invite extends React.Component {
);
}
- this.invites.push({ email: '', name: '', guest: false });
+ this.invites.push({ email: "", name: "", guest: false });
};
@action
@@ -91,14 +91,14 @@ class Invite extends React.Component {
handleCopy = () => {
this.linkCopied = true;
- this.props.ui.showToast('A link was copied to your clipboard');
+ this.props.ui.showToast("A link was copied to your clipboard");
};
render() {
const { team, user } = this.props.auth;
if (!team || !user) return null;
- const predictedDomain = user.email.split('@')[1];
+ const predictedDomain = user.email.split("@")[1];
const can = this.props.policies.abilities(team.id);
return (
@@ -112,10 +112,10 @@ class Invite extends React.Component {
) : (
Invite team members to join your knowledge base. They will need to
- sign in with {team.signinMethods}.{' '}
+ sign in with {team.signinMethods}.{" "}
{can.update && (
- As an admin you can also{' '}
+ As an admin you can also{" "}
enable guest invites.
)}
@@ -128,7 +128,7 @@ class Invite extends React.Component {
@@ -165,7 +165,7 @@ class Invite extends React.Component {
- Guests can sign in with email and do not require{' '}
+ Guests can sign in with email and do not require{" "}
{team.signinMethods} accounts
}
@@ -210,7 +210,7 @@ class Invite extends React.Component {
data-event-category="invite"
data-event-action="sendInvites"
>
- {this.isSaving ? 'Inviting…' : 'Send Invites'}
+ {this.isSaving ? "Inviting…" : "Send Invites"}
@@ -219,7 +219,7 @@ class Invite extends React.Component {
}
}
-const CopyBlock = styled('div')`
+const CopyBlock = styled("div")`
font-size: 14px;
background: ${props => props.theme.secondaryBackground};
padding: 8px 16px 4px;
@@ -232,16 +232,16 @@ const CopyBlock = styled('div')`
}
`;
-const Guest = styled('div')`
+const Guest = styled("div")`
padding-top: 4px;
margin: 0 4px 16px;
align-self: flex-end;
`;
-const Remove = styled('div')`
+const Remove = styled("div")`
margin-top: 6px;
position: absolute;
right: -32px;
`;
-export default inject('auth', 'users', 'policies', 'ui')(withRouter(Invite));
+export default inject("auth", "users", "policies", "ui")(withRouter(Invite));
diff --git a/app/scenes/KeyboardShortcuts.js b/app/scenes/KeyboardShortcuts.js
index 2aff9ab1..86248f41 100644
--- a/app/scenes/KeyboardShortcuts.js
+++ b/app/scenes/KeyboardShortcuts.js
@@ -1,10 +1,10 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import Key from 'components/Key';
-import Flex from 'shared/components/Flex';
-import HelpText from 'components/HelpText';
-import { meta } from 'utils/keyboard';
+import * as React from "react";
+import styled from "styled-components";
+import Key from "components/Key";
+import Flex from "shared/components/Flex";
+import HelpText from "components/HelpText";
+import { meta } from "utils/keyboard";
function KeyboardShortcuts() {
return (
@@ -136,7 +136,7 @@ function KeyboardShortcuts() {
- {'```'}
+ {"```"}
@@ -146,7 +146,7 @@ function KeyboardShortcuts() {
~~strikethrough~~
- {'`code`'}
+ {"`code`"}==highlight==
diff --git a/app/scenes/Search/Search.js b/app/scenes/Search/Search.js
index 3bf3e234..84b73862 100644
--- a/app/scenes/Search/Search.js
+++ b/app/scenes/Search/Search.js
@@ -1,39 +1,39 @@
// @flow
-import * as React from 'react';
-import ReactDOM from 'react-dom';
-import keydown from 'react-keydown';
-import { Waypoint } from 'react-waypoint';
-import { withRouter, Link } from 'react-router-dom';
-import type { Location, RouterHistory } from 'react-router-dom';
-import { PlusIcon } from 'outline-icons';
-import { observable, action } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import { debounce } from 'lodash';
-import queryString from 'query-string';
-import styled from 'styled-components';
-import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
+import * as React from "react";
+import ReactDOM from "react-dom";
+import keydown from "react-keydown";
+import { Waypoint } from "react-waypoint";
+import { withRouter, Link } from "react-router-dom";
+import type { Location, RouterHistory } from "react-router-dom";
+import { PlusIcon } from "outline-icons";
+import { observable, action } from "mobx";
+import { observer, inject } from "mobx-react";
+import { debounce } from "lodash";
+import queryString from "query-string";
+import styled from "styled-components";
+import ArrowKeyNavigation from "boundless-arrow-key-navigation";
-import { DEFAULT_PAGINATION_LIMIT } from 'stores/BaseStore';
-import DocumentsStore from 'stores/DocumentsStore';
-import UsersStore from 'stores/UsersStore';
-import { newDocumentUrl, searchUrl } from 'utils/routeHelpers';
-import { meta } from 'utils/keyboard';
+import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore";
+import DocumentsStore from "stores/DocumentsStore";
+import UsersStore from "stores/UsersStore";
+import { newDocumentUrl, searchUrl } from "utils/routeHelpers";
+import { meta } from "utils/keyboard";
-import Flex from 'shared/components/Flex';
-import Button from 'components/Button';
-import Empty from 'components/Empty';
-import Fade from 'components/Fade';
-import HelpText from 'components/HelpText';
-import CenteredContent from 'components/CenteredContent';
-import LoadingIndicator from 'components/LoadingIndicator';
-import DocumentPreview from 'components/DocumentPreview';
-import NewDocumentMenu from 'menus/NewDocumentMenu';
-import PageTitle from 'components/PageTitle';
-import SearchField from './components/SearchField';
-import StatusFilter from './components/StatusFilter';
-import CollectionFilter from './components/CollectionFilter';
-import UserFilter from './components/UserFilter';
-import DateFilter from './components/DateFilter';
+import Flex from "shared/components/Flex";
+import Button from "components/Button";
+import Empty from "components/Empty";
+import Fade from "components/Fade";
+import HelpText from "components/HelpText";
+import CenteredContent from "components/CenteredContent";
+import LoadingIndicator from "components/LoadingIndicator";
+import DocumentPreview from "components/DocumentPreview";
+import NewDocumentMenu from "menus/NewDocumentMenu";
+import PageTitle from "components/PageTitle";
+import SearchField from "./components/SearchField";
+import StatusFilter from "./components/StatusFilter";
+import CollectionFilter from "./components/CollectionFilter";
+import UserFilter from "./components/UserFilter";
+import DateFilter from "./components/DateFilter";
type Props = {
history: RouterHistory,
@@ -49,7 +49,7 @@ class Search extends React.Component {
firstDocument: ?DocumentPreview;
@observable
- query: string = decodeURIComponent(this.props.match.params.term || '');
+ query: string = decodeURIComponent(this.props.match.params.term || "");
@observable params: URLSearchParams = new URLSearchParams();
@observable offset: number = 0;
@observable allowLoadMore: boolean = true;
@@ -73,7 +73,7 @@ class Search extends React.Component {
}
}
- @keydown('esc')
+ @keydown("esc")
goBack() {
this.props.history.goBack();
}
@@ -107,8 +107,8 @@ class Search extends React.Component {
};
handleTermChange = () => {
- const query = decodeURIComponent(this.props.match.params.term || '');
- this.query = query ? query : '';
+ const query = decodeURIComponent(this.props.match.params.term || "");
+ this.query = query ? query : "";
this.offset = 0;
this.allowLoadMore = true;
@@ -135,21 +135,21 @@ class Search extends React.Component {
};
get includeArchived() {
- return this.params.get('includeArchived') === 'true';
+ return this.params.get("includeArchived") === "true";
}
get collectionId() {
- const id = this.params.get('collectionId');
+ const id = this.params.get("collectionId");
return id ? id : undefined;
}
get userId() {
- const id = this.params.get('userId');
+ const id = this.params.get("userId");
return id ? id : undefined;
}
get dateFilter() {
- const id = this.params.get('dateFilter');
+ const id = this.params.get("dateFilter");
return id ? id : undefined;
}
@@ -164,7 +164,7 @@ class Search extends React.Component {
get title() {
const query = this.query;
- const title = 'Search';
+ const title = "Search";
if (query) return `${query} – ${title}`;
return title;
}
@@ -361,14 +361,14 @@ const Container = styled(CenteredContent)`
const ResultsWrapper = styled(Flex)`
position: absolute;
transition: all 300ms cubic-bezier(0.65, 0.05, 0.36, 1);
- top: ${props => (props.pinToTop ? '0%' : '50%')};
- margin-top: ${props => (props.pinToTop ? '40px' : '-75px')};
+ top: ${props => (props.pinToTop ? "0%" : "50%")};
+ margin-top: ${props => (props.pinToTop ? "40px" : "-75px")};
width: 100%;
`;
const ResultList = styled(Flex)`
margin-bottom: 150px;
- opacity: ${props => (props.visible ? '1' : '0')};
+ opacity: ${props => (props.visible ? "1" : "0")};
transition: all 400ms cubic-bezier(0.65, 0.05, 0.36, 1);
`;
@@ -388,4 +388,4 @@ const Filters = styled(Flex)`
}
`;
-export default withRouter(inject('documents')(Search));
+export default withRouter(inject("documents")(Search));
diff --git a/app/scenes/Search/components/CollectionFilter.js b/app/scenes/Search/components/CollectionFilter.js
index 91905bed..4191452e 100644
--- a/app/scenes/Search/components/CollectionFilter.js
+++ b/app/scenes/Search/components/CollectionFilter.js
@@ -1,12 +1,12 @@
// @flow
-import * as React from 'react';
-import { observer, inject } from 'mobx-react';
-import FilterOptions from './FilterOptions';
-import CollectionsStore from 'stores/CollectionsStore';
+import * as React from "react";
+import { observer, inject } from "mobx-react";
+import FilterOptions from "./FilterOptions";
+import CollectionsStore from "stores/CollectionsStore";
const defaultOption = {
key: undefined,
- label: 'Any collection',
+ label: "Any collection",
};
type Props = {
@@ -36,4 +36,4 @@ class CollectionFilter extends React.Component {
}
}
-export default inject('collections')(CollectionFilter);
+export default inject("collections")(CollectionFilter);
diff --git a/app/scenes/Search/components/DateFilter.js b/app/scenes/Search/components/DateFilter.js
index 605416d0..8fca6047 100644
--- a/app/scenes/Search/components/DateFilter.js
+++ b/app/scenes/Search/components/DateFilter.js
@@ -1,13 +1,13 @@
// @flow
-import * as React from 'react';
-import FilterOptions from './FilterOptions';
+import * as React from "react";
+import FilterOptions from "./FilterOptions";
const options = [
- { key: undefined, label: 'Any time' },
- { key: 'day', label: 'Past day' },
- { key: 'week', label: 'Past week' },
- { key: 'month', label: 'Past month' },
- { key: 'year', label: 'Past year' },
+ { key: undefined, label: "Any time" },
+ { key: "day", label: "Past day" },
+ { key: "week", label: "Past week" },
+ { key: "month", label: "Past month" },
+ { key: "year", label: "Past year" },
];
type Props = {
diff --git a/app/scenes/Search/components/FilterOption.js b/app/scenes/Search/components/FilterOption.js
index 627597d9..f6725b61 100644
--- a/app/scenes/Search/components/FilterOption.js
+++ b/app/scenes/Search/components/FilterOption.js
@@ -1,9 +1,9 @@
// @flow
-import * as React from 'react';
-import { CheckmarkIcon } from 'outline-icons';
-import styled from 'styled-components';
-import HelpText from 'components/HelpText';
-import Flex from 'shared/components/Flex';
+import * as React from "react";
+import { CheckmarkIcon } from "outline-icons";
+import styled from "styled-components";
+import HelpText from "components/HelpText";
+import Flex from "shared/components/Flex";
type Props = {
label: string,
@@ -34,7 +34,7 @@ const Checkmark = styled(CheckmarkIcon)`
fill: ${props => props.theme.text};
`;
-const Anchor = styled('a')`
+const Anchor = styled("a")`
display: flex;
flex-direction: column;
font-size: 15px;
@@ -52,9 +52,9 @@ const Anchor = styled('a')`
}
`;
-const ListItem = styled('li')`
+const ListItem = styled("li")`
list-style: none;
- font-weight: ${props => (props.active ? '600' : 'normal')};
+ font-weight: ${props => (props.active ? "600" : "normal")};
`;
export default FilterOption;
diff --git a/app/scenes/Search/components/FilterOptions.js b/app/scenes/Search/components/FilterOptions.js
index 7346826c..7c45d7ab 100644
--- a/app/scenes/Search/components/FilterOptions.js
+++ b/app/scenes/Search/components/FilterOptions.js
@@ -1,10 +1,10 @@
// @flow
-import * as React from 'react';
-import { find } from 'lodash';
-import styled from 'styled-components';
-import Button, { Inner } from 'components/Button';
-import { DropdownMenu } from 'components/DropdownMenu';
-import FilterOption from './FilterOption';
+import * as React from "react";
+import { find } from "lodash";
+import styled from "styled-components";
+import Button, { Inner } from "components/Button";
+import { DropdownMenu } from "components/DropdownMenu";
+import FilterOption from "./FilterOption";
type Props = {
options: {
@@ -22,11 +22,11 @@ const FilterOptions = ({
options,
activeKey,
defaultLabel,
- selectedPrefix = '',
+ selectedPrefix = "",
onSelect,
}: Props) => {
const selected = find(options, { key: activeKey }) || options[0];
- const selectedLabel = selected ? `${selectedPrefix} ${selected.label}` : '';
+ const selectedLabel = selected ? `${selectedPrefix} ${selected.label}` : "";
return (
@@ -49,7 +49,7 @@ const FilterOptions = ({
);
};
-const Content = styled('div')`
+const Content = styled("div")`
padding: 0 8px;
width: 250px;
@@ -95,7 +95,7 @@ const DropdownButton = styled(SearchFilter)`
margin-right: 8px;
`;
-const List = styled('ol')`
+const List = styled("ol")`
list-style: none;
margin: 0;
padding: 0;
diff --git a/app/scenes/Search/components/SearchField.js b/app/scenes/Search/components/SearchField.js
index 5816dcce..6bed3cfb 100644
--- a/app/scenes/Search/components/SearchField.js
+++ b/app/scenes/Search/components/SearchField.js
@@ -1,8 +1,8 @@
// @flow
-import * as React from 'react';
-import styled, { withTheme } from 'styled-components';
-import { SearchIcon } from 'outline-icons';
-import Flex from 'shared/components/Flex';
+import * as React from "react";
+import styled, { withTheme } from "styled-components";
+import { SearchIcon } from "outline-icons";
+import Flex from "shared/components/Flex";
type Props = {
onChange: string => void,
@@ -16,13 +16,13 @@ class SearchField extends React.Component {
componentDidMount() {
if (this.props && this.input) {
// ensure that focus is placed at end of input
- const len = (this.props.defaultValue || '').length;
+ const len = (this.props.defaultValue || "").length;
this.input.setSelectionRange(len, len);
}
}
handleChange = (ev: SyntheticEvent) => {
- this.props.onChange(ev.currentTarget.value ? ev.currentTarget.value : '');
+ this.props.onChange(ev.currentTarget.value ? ev.currentTarget.value : "");
};
focusInput = (ev: SyntheticEvent<>) => {
diff --git a/app/scenes/Search/components/StatusFilter.js b/app/scenes/Search/components/StatusFilter.js
index 01622454..2a17b550 100644
--- a/app/scenes/Search/components/StatusFilter.js
+++ b/app/scenes/Search/components/StatusFilter.js
@@ -1,17 +1,17 @@
// @flow
-import * as React from 'react';
-import FilterOptions from './FilterOptions';
+import * as React from "react";
+import FilterOptions from "./FilterOptions";
const options = [
{
key: undefined,
- label: 'Active documents',
- note: 'Documents in collections you are able to access',
+ label: "Active documents",
+ note: "Documents in collections you are able to access",
},
{
- key: 'true',
- label: 'All documents',
- note: 'Include documents that are in the archive',
+ key: "true",
+ label: "All documents",
+ note: "Include documents that are in the archive",
},
];
@@ -24,7 +24,7 @@ const StatusFilter = ({ includeArchived, onSelect }: Props) => {
return (
diff --git a/app/scenes/Search/components/UserFilter.js b/app/scenes/Search/components/UserFilter.js
index f3837259..9c293165 100644
--- a/app/scenes/Search/components/UserFilter.js
+++ b/app/scenes/Search/components/UserFilter.js
@@ -1,12 +1,12 @@
// @flow
-import * as React from 'react';
-import { observer, inject } from 'mobx-react';
-import FilterOptions from './FilterOptions';
-import UsersStore from 'stores/UsersStore';
+import * as React from "react";
+import { observer, inject } from "mobx-react";
+import FilterOptions from "./FilterOptions";
+import UsersStore from "stores/UsersStore";
const defaultOption = {
key: undefined,
- label: 'Any author',
+ label: "Any author",
};
type Props = {
@@ -40,4 +40,4 @@ class UserFilter extends React.Component {
}
}
-export default inject('users')(UserFilter);
+export default inject("users")(UserFilter);
diff --git a/app/scenes/Search/index.js b/app/scenes/Search/index.js
index cab60251..1fa6535a 100644
--- a/app/scenes/Search/index.js
+++ b/app/scenes/Search/index.js
@@ -1,3 +1,3 @@
// @flow
-import Search from './Search';
+import Search from "./Search";
export default Search;
diff --git a/app/scenes/Settings/Details.js b/app/scenes/Settings/Details.js
index cdcf0294..120e1e66 100644
--- a/app/scenes/Settings/Details.js
+++ b/app/scenes/Settings/Details.js
@@ -1,18 +1,18 @@
// @flow
-import * as React from 'react';
-import { observable } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import styled from 'styled-components';
+import * as React from "react";
+import { observable } from "mobx";
+import { observer, inject } from "mobx-react";
+import styled from "styled-components";
-import AuthStore from 'stores/AuthStore';
-import UiStore from 'stores/UiStore';
-import ImageUpload from './components/ImageUpload';
-import Input, { LabelText } from 'components/Input';
-import Button from 'components/Button';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import HelpText from 'components/HelpText';
-import Flex from 'shared/components/Flex';
+import AuthStore from "stores/AuthStore";
+import UiStore from "stores/UiStore";
+import ImageUpload from "./components/ImageUpload";
+import Input, { LabelText } from "components/Input";
+import Button from "components/Button";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import HelpText from "components/HelpText";
+import Flex from "shared/components/Flex";
type Props = {
auth: AuthStore,
@@ -49,7 +49,7 @@ class Details extends React.Component {
avatarUrl: this.avatarUrl,
subdomain: this.subdomain,
});
- this.props.ui.showToast('Settings saved');
+ this.props.ui.showToast("Settings saved");
} catch (err) {
this.props.ui.showToast(err.message);
}
@@ -68,7 +68,7 @@ class Details extends React.Component {
};
handleAvatarError = (error: ?string) => {
- this.props.ui.showToast(error || 'Unable to upload new logo');
+ this.props.ui.showToast(error || "Unable to upload new logo");
};
get isValid() {
@@ -120,7 +120,7 @@ class Details extends React.Component {
{
/>
{this.subdomain && (
- Your knowledgebase will be accessible at{' '}
+ Your knowledgebase will be accessible at{" "}
{this.subdomain}.getoutline.com
)}
)}
@@ -183,4 +183,4 @@ const Avatar = styled.img`
${avatarStyles};
`;
-export default inject('auth', 'ui')(Details);
+export default inject("auth", "ui")(Details);
diff --git a/app/scenes/Settings/Events.js b/app/scenes/Settings/Events.js
index 3d7be421..c49c6387 100644
--- a/app/scenes/Settings/Events.js
+++ b/app/scenes/Settings/Events.js
@@ -1,19 +1,19 @@
// @flow
-import * as React from 'react';
-import { observable, action } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import { Waypoint } from 'react-waypoint';
+import * as React from "react";
+import { observable, action } from "mobx";
+import { observer, inject } from "mobx-react";
+import { Waypoint } from "react-waypoint";
-import { DEFAULT_PAGINATION_LIMIT } from 'stores/BaseStore';
-import EventsStore from 'stores/EventsStore';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import HelpText from 'components/HelpText';
-import List from 'components/List';
-import Tabs from 'components/Tabs';
-import Tab from 'components/Tab';
-import { ListPlaceholder } from 'components/LoadingPlaceholder';
-import EventListItem from './components/EventListItem';
+import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore";
+import EventsStore from "stores/EventsStore";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import HelpText from "components/HelpText";
+import List from "components/List";
+import Tabs from "components/Tabs";
+import Tab from "components/Tab";
+import { ListPlaceholder } from "components/LoadingPlaceholder";
+import EventListItem from "./components/EventListItem";
type Props = {
events: EventsStore,
@@ -96,4 +96,4 @@ class Events extends React.Component {
}
}
-export default inject('events')(Events);
+export default inject("events")(Events);
diff --git a/app/scenes/Settings/Export.js b/app/scenes/Settings/Export.js
index 417a32a1..cbda73ab 100644
--- a/app/scenes/Settings/Export.js
+++ b/app/scenes/Settings/Export.js
@@ -1,15 +1,15 @@
// @flow
-import * as React from 'react';
-import { observable } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import AuthStore from 'stores/AuthStore';
-import CollectionsStore from 'stores/CollectionsStore';
-import UiStore from 'stores/UiStore';
+import * as React from "react";
+import { observable } from "mobx";
+import { observer, inject } from "mobx-react";
+import AuthStore from "stores/AuthStore";
+import CollectionsStore from "stores/CollectionsStore";
+import UiStore from "stores/UiStore";
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import HelpText from 'components/HelpText';
-import Button from 'components/Button';
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import HelpText from "components/HelpText";
+import Button from "components/Button";
type Props = {
auth: AuthStore,
@@ -29,7 +29,7 @@ class Export extends React.Component {
try {
await this.props.collections.export();
this.isExporting = true;
- this.props.ui.showToast('Export in progress…');
+ this.props.ui.showToast("Export in progress…");
} finally {
this.isLoading = false;
}
@@ -60,12 +60,12 @@ class Export extends React.Component {
primary
>
{this.isExporting
- ? 'Export Requested'
- : this.isLoading ? 'Requesting Export…' : 'Export All Data'}
+ ? "Export Requested"
+ : this.isLoading ? "Requesting Export…" : "Export All Data"}
);
}
}
-export default inject('auth', 'ui', 'collections')(Export);
+export default inject("auth", "ui", "collections")(Export);
diff --git a/app/scenes/Settings/Groups.js b/app/scenes/Settings/Groups.js
index 9d040b74..2d007705 100644
--- a/app/scenes/Settings/Groups.js
+++ b/app/scenes/Settings/Groups.js
@@ -1,27 +1,27 @@
// @flow
-import * as React from 'react';
-import invariant from 'invariant';
-import { observable } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import { PlusIcon } from 'outline-icons';
+import * as React from "react";
+import invariant from "invariant";
+import { observable } from "mobx";
+import { observer, inject } from "mobx-react";
+import { PlusIcon } from "outline-icons";
-import Empty from 'components/Empty';
-import { ListPlaceholder } from 'components/LoadingPlaceholder';
-import Modal from 'components/Modal';
-import Button from 'components/Button';
-import GroupNew from 'scenes/GroupNew';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import HelpText from 'components/HelpText';
-import GroupListItem from 'components/GroupListItem';
-import List from 'components/List';
-import Tabs from 'components/Tabs';
-import Tab from 'components/Tab';
-import GroupMenu from 'menus/GroupMenu';
+import Empty from "components/Empty";
+import { ListPlaceholder } from "components/LoadingPlaceholder";
+import Modal from "components/Modal";
+import Button from "components/Button";
+import GroupNew from "scenes/GroupNew";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import HelpText from "components/HelpText";
+import GroupListItem from "components/GroupListItem";
+import List from "components/List";
+import Tabs from "components/Tabs";
+import Tab from "components/Tab";
+import GroupMenu from "menus/GroupMenu";
-import AuthStore from 'stores/AuthStore';
-import GroupsStore from 'stores/GroupsStore';
-import PoliciesStore from 'stores/PoliciesStore';
+import AuthStore from "stores/AuthStore";
+import GroupsStore from "stores/GroupsStore";
+import PoliciesStore from "stores/PoliciesStore";
type Props = {
auth: AuthStore,
@@ -51,8 +51,8 @@ class Groups extends React.Component {
const currentUser = auth.user;
const team = auth.team;
- invariant(currentUser, 'User should exist');
- invariant(team, 'Team should exist');
+ invariant(currentUser, "User should exist");
+ invariant(team, "Team should exist");
const showLoading = groups.isFetching && !groups.orderedData.length;
const showEmpty = groups.isLoaded && !groups.orderedData.length;
@@ -111,4 +111,4 @@ class Groups extends React.Component {
}
}
-export default inject('auth', 'groups', 'policies')(Groups);
+export default inject("auth", "groups", "policies")(Groups);
diff --git a/app/scenes/Settings/Notifications.js b/app/scenes/Settings/Notifications.js
index ef765646..94ba1a2c 100644
--- a/app/scenes/Settings/Notifications.js
+++ b/app/scenes/Settings/Notifications.js
@@ -1,19 +1,19 @@
// @flow
-import * as React from 'react';
-import { debounce } from 'lodash';
-import { observer, inject } from 'mobx-react';
-import styled from 'styled-components';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import HelpText from 'components/HelpText';
-import Input from 'components/Input';
-import Subheading from 'components/Subheading';
-import NotificationListItem from './components/NotificationListItem';
-import Notice from 'shared/components/Notice';
+import * as React from "react";
+import { debounce } from "lodash";
+import { observer, inject } from "mobx-react";
+import styled from "styled-components";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import HelpText from "components/HelpText";
+import Input from "components/Input";
+import Subheading from "components/Subheading";
+import NotificationListItem from "./components/NotificationListItem";
+import Notice from "shared/components/Notice";
-import UiStore from 'stores/UiStore';
-import AuthStore from 'stores/AuthStore';
-import NotificationSettingsStore from 'stores/NotificationSettingsStore';
+import UiStore from "stores/UiStore";
+import AuthStore from "stores/AuthStore";
+import NotificationSettingsStore from "stores/NotificationSettingsStore";
type Props = {
ui: UiStore,
@@ -23,33 +23,33 @@ type Props = {
const options = [
{
- event: 'documents.publish',
- title: 'Document published',
- description: 'Receive a notification whenever a new document is published',
+ event: "documents.publish",
+ title: "Document published",
+ description: "Receive a notification whenever a new document is published",
},
{
- event: 'documents.update',
- title: 'Document updated',
- description: 'Receive a notification when a document you created is edited',
+ event: "documents.update",
+ title: "Document updated",
+ description: "Receive a notification when a document you created is edited",
},
{
- event: 'collections.create',
- title: 'Collection created',
- description: 'Receive a notification whenever a new collection is created',
+ event: "collections.create",
+ title: "Collection created",
+ description: "Receive a notification whenever a new collection is created",
},
{
separator: true,
},
{
- event: 'emails.onboarding',
- title: 'Getting started',
+ event: "emails.onboarding",
+ title: "Getting started",
description:
- 'Tips on getting started with Outline`s features and functionality',
+ "Tips on getting started with Outline`s features and functionality",
},
{
- event: 'emails.features',
- title: 'New features',
- description: 'Receive an email when new features of note are added',
+ event: "emails.features",
+ title: "New features",
+ description: "Receive an email when new features of note are added",
},
];
@@ -75,12 +75,12 @@ class Notifications extends React.Component {
};
showSuccessMessage = debounce(() => {
- this.props.ui.showToast('Notifications saved');
+ this.props.ui.showToast("Notifications saved");
}, 500);
render() {
const { notificationSettings, auth } = this.props;
- const showSuccessNotice = window.location.search === '?success';
+ const showSuccessNotice = window.location.search === "?success";
const { user, team } = auth;
if (!team || !user) return null;
@@ -97,8 +97,8 @@ class Notifications extends React.Component {
Manage when and where you receive email notifications from Outline.
- Your email address can be updated in your{' '}
- {team.slackConnected ? 'Slack' : 'Google'} account.
+ Your email address can be updated in your{" "}
+ {team.slackConnected ? "Slack" : "Google"} account.
{
const { filter } = match.params;
const currentUser = auth.user;
const team = auth.team;
- invariant(currentUser, 'User should exist');
- invariant(team, 'Team should exist');
+ invariant(currentUser, "User should exist");
+ invariant(team, "Team should exist");
let users = this.props.users.active;
- if (filter === 'all') {
+ if (filter === "all") {
users = this.props.users.all;
- } else if (filter === 'admins') {
+ } else if (filter === "admins") {
users = this.props.users.admins;
- } else if (filter === 'suspended') {
+ } else if (filter === "suspended") {
users = this.props.users.suspended;
- } else if (filter === 'invited') {
+ } else if (filter === "invited") {
users = this.props.users.invited;
}
@@ -136,4 +136,4 @@ class People extends React.Component {
}
}
-export default inject('auth', 'users', 'policies')(People);
+export default inject("auth", "users", "policies")(People);
diff --git a/app/scenes/Settings/Profile.js b/app/scenes/Settings/Profile.js
index a0b35069..71ad9276 100644
--- a/app/scenes/Settings/Profile.js
+++ b/app/scenes/Settings/Profile.js
@@ -1,18 +1,18 @@
// @flow
-import * as React from 'react';
-import { observable } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import styled from 'styled-components';
+import * as React from "react";
+import { observable } from "mobx";
+import { observer, inject } from "mobx-react";
+import styled from "styled-components";
-import AuthStore from 'stores/AuthStore';
-import UiStore from 'stores/UiStore';
-import ImageUpload from './components/ImageUpload';
-import Input, { LabelText } from 'components/Input';
-import Button from 'components/Button';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import UserDelete from 'scenes/UserDelete';
-import Flex from 'shared/components/Flex';
+import AuthStore from "stores/AuthStore";
+import UiStore from "stores/UiStore";
+import ImageUpload from "./components/ImageUpload";
+import Input, { LabelText } from "components/Input";
+import Button from "components/Button";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import UserDelete from "scenes/UserDelete";
+import Flex from "shared/components/Flex";
type Props = {
auth: AuthStore,
@@ -45,7 +45,7 @@ class Profile extends React.Component {
name: this.name,
avatarUrl: this.avatarUrl,
});
- this.props.ui.showToast('Profile saved');
+ this.props.ui.showToast("Profile saved");
};
handleNameChange = (ev: SyntheticInputEvent<*>) => {
@@ -58,11 +58,11 @@ class Profile extends React.Component {
await this.props.auth.updateUser({
avatarUrl: this.avatarUrl,
});
- this.props.ui.showToast('Profile picture updated');
+ this.props.ui.showToast("Profile picture updated");
};
handleAvatarError = (error: ?string) => {
- this.props.ui.showToast(error || 'Unable to upload new avatar');
+ this.props.ui.showToast(error || "Unable to upload new avatar");
};
toggleDeleteAccount = () => {
@@ -106,7 +106,7 @@ class Profile extends React.Component {
short
/>
@@ -114,7 +114,7 @@ class Profile extends React.Component {
Delete Account
You may delete your account at any time, note that this is
- unrecoverable.{' '}
+ unrecoverable.{" "}
Delete account.
@@ -170,4 +170,4 @@ const Avatar = styled.img`
${avatarStyles};
`;
-export default inject('auth', 'ui')(Profile);
+export default inject("auth", "ui")(Profile);
diff --git a/app/scenes/Settings/Security.js b/app/scenes/Settings/Security.js
index 277e85e2..b4c1bcc1 100644
--- a/app/scenes/Settings/Security.js
+++ b/app/scenes/Settings/Security.js
@@ -1,15 +1,15 @@
// @flow
-import * as React from 'react';
-import { observable } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import { debounce } from 'lodash';
+import * as React from "react";
+import { observable } from "mobx";
+import { observer, inject } from "mobx-react";
+import { debounce } from "lodash";
-import AuthStore from 'stores/AuthStore';
-import UiStore from 'stores/UiStore';
-import Checkbox from 'components/Checkbox';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import HelpText from 'components/HelpText';
+import AuthStore from "stores/AuthStore";
+import UiStore from "stores/UiStore";
+import Checkbox from "components/Checkbox";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import HelpText from "components/HelpText";
type Props = {
auth: AuthStore,
@@ -35,13 +35,13 @@ class Security extends React.Component {
handleChange = async (ev: SyntheticInputEvent<*>) => {
switch (ev.target.name) {
- case 'sharing':
+ case "sharing":
this.sharing = ev.target.checked;
break;
- case 'documentEmbeds':
+ case "documentEmbeds":
this.documentEmbeds = ev.target.checked;
break;
- case 'guestSignin':
+ case "guestSignin":
this.guestSignin = ev.target.checked;
break;
default:
@@ -56,7 +56,7 @@ class Security extends React.Component {
};
showSuccessMessage = debounce(() => {
- this.props.ui.showToast('Settings saved');
+ this.props.ui.showToast("Settings saved");
}, 500);
render() {
@@ -77,7 +77,7 @@ class Security extends React.Component {
checked={this.guestSignin}
onChange={this.handleChange}
note={`When enabled guests can be invited by email address and are able to signin without ${
- team ? team.signinMethods : 'SSO'
+ team ? team.signinMethods : "SSO"
}`}
/>
{
}
}
-export default inject('auth', 'ui')(Security);
+export default inject("auth", "ui")(Security);
diff --git a/app/scenes/Settings/Shares.js b/app/scenes/Settings/Shares.js
index 2dee6c8f..a164dde3 100644
--- a/app/scenes/Settings/Shares.js
+++ b/app/scenes/Settings/Shares.js
@@ -1,17 +1,17 @@
// @flow
-import * as React from 'react';
-import { observer, inject } from 'mobx-react';
-import { Link } from 'react-router-dom';
-import SharesStore from 'stores/SharesStore';
-import AuthStore from 'stores/AuthStore';
+import * as React from "react";
+import { observer, inject } from "mobx-react";
+import { Link } from "react-router-dom";
+import SharesStore from "stores/SharesStore";
+import AuthStore from "stores/AuthStore";
-import ShareListItem from './components/ShareListItem';
-import Empty from 'components/Empty';
-import List from 'components/List';
-import CenteredContent from 'components/CenteredContent';
-import Subheading from 'components/Subheading';
-import PageTitle from 'components/PageTitle';
-import HelpText from 'components/HelpText';
+import ShareListItem from "./components/ShareListItem";
+import Empty from "components/Empty";
+import List from "components/List";
+import CenteredContent from "components/CenteredContent";
+import Subheading from "components/Subheading";
+import PageTitle from "components/PageTitle";
+import HelpText from "components/HelpText";
type Props = {
shares: SharesStore,
@@ -44,8 +44,8 @@ class Shares extends React.Component {
{!canShareDocuments && (
Sharing is currently disabled.
- )}{' '}
- You can turn {canShareDocuments ? 'off' : 'on'} public document
+ )}{" "}
+ You can turn {canShareDocuments ? "off" : "on"} public document
sharing in security settings.
)}
@@ -64,4 +64,4 @@ class Shares extends React.Component {
}
}
-export default inject('shares', 'auth')(Shares);
+export default inject("shares", "auth")(Shares);
diff --git a/app/scenes/Settings/Slack.js b/app/scenes/Settings/Slack.js
index 9f636b43..cdbe05b5 100644
--- a/app/scenes/Settings/Slack.js
+++ b/app/scenes/Settings/Slack.js
@@ -1,19 +1,19 @@
// @flow
-import * as React from 'react';
-import { inject, observer } from 'mobx-react';
-import { find } from 'lodash';
-import styled from 'styled-components';
+import * as React from "react";
+import { inject, observer } from "mobx-react";
+import { find } from "lodash";
+import styled from "styled-components";
-import Button from 'components/Button';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import HelpText from 'components/HelpText';
-import SlackButton from './components/SlackButton';
-import CollectionsStore from 'stores/CollectionsStore';
-import IntegrationsStore from 'stores/IntegrationsStore';
-import AuthStore from 'stores/AuthStore';
-import Notice from 'shared/components/Notice';
-import getQueryVariable from 'shared/utils/getQueryVariable';
+import Button from "components/Button";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import HelpText from "components/HelpText";
+import SlackButton from "./components/SlackButton";
+import CollectionsStore from "stores/CollectionsStore";
+import IntegrationsStore from "stores/IntegrationsStore";
+import AuthStore from "stores/AuthStore";
+import Notice from "shared/components/Notice";
+import getQueryVariable from "shared/utils/getQueryVariable";
type Props = {
collections: CollectionsStore,
@@ -26,39 +26,39 @@ class Slack extends React.Component {
error: ?string;
componentDidMount() {
- this.error = getQueryVariable('error');
+ this.error = getQueryVariable("error");
this.props.collections.fetchPage({ limit: 100 });
this.props.integrations.fetchPage();
}
get commandIntegration() {
return find(this.props.integrations.slackIntegrations, {
- type: 'command',
+ type: "command",
});
}
render() {
const { collections, integrations, auth } = this.props;
- const teamId = auth.team ? auth.team.id : '';
+ const teamId = auth.team ? auth.team.id : "";
return (
Slack
- {this.error === 'access_denied' && (
+ {this.error === "access_denied" && (
Whoops, you need to accept the permissions in Slack to connect
Outline to your team. Try again?
)}
- {this.error === 'unauthenticated' && (
+ {this.error === "unauthenticated" && (
Something went wrong while authenticating your request. Please try
logging in again?
)}
- Preview Outline links your team mates share and use the{' '}
+ Preview Outline links your team mates share and use the{" "}
/outline slash command in Slack to search for documents
in your team’s wiki.
@@ -67,7 +67,7 @@ class Slack extends React.Component {
) : (
@@ -91,7 +91,7 @@ class Slack extends React.Component {
return (
- {collection.name} posting activity to the{' '}
+ {collection.name} posting activity to the{" "}
{integration.settings.channel} Slack
channel
@@ -104,7 +104,7 @@ class Slack extends React.Component {
{collection.name} {
- @observable name: string = '';
+ @observable name: string = "";
componentDidMount() {
this.props.apiKeys.fetchPage({ limit: 100 });
@@ -31,7 +31,7 @@ class Tokens extends React.Component {
handleSubmit = async (ev: SyntheticEvent<>) => {
ev.preventDefault();
await this.props.apiKeys.create({ name: this.name });
- this.name = '';
+ this.name = "";
};
render() {
@@ -45,7 +45,7 @@ class Tokens extends React.Component {
You can create an unlimited amount of personal tokens to authenticate
- with the API. For more details about the API take a look at the{' '}
+ with the API. For more details about the API take a look at the{" "}
developer documentation.
@@ -79,4 +79,4 @@ class Tokens extends React.Component {
}
}
-export default inject('apiKeys')(Tokens);
+export default inject("apiKeys")(Tokens);
diff --git a/app/scenes/Settings/Zapier.js b/app/scenes/Settings/Zapier.js
index ff047bf8..b9011867 100644
--- a/app/scenes/Settings/Zapier.js
+++ b/app/scenes/Settings/Zapier.js
@@ -1,14 +1,14 @@
// @flow
-import * as React from 'react';
-import CenteredContent from 'components/CenteredContent';
-import PageTitle from 'components/PageTitle';
-import HelpText from 'components/HelpText';
-import Button from 'components/Button';
+import * as React from "react";
+import CenteredContent from "components/CenteredContent";
+import PageTitle from "components/PageTitle";
+import HelpText from "components/HelpText";
+import Button from "components/Button";
class Zapier extends React.Component<*> {
goToZapier = () => {
window.open(
- 'https://zapier.com/platform/public-invite/5927/a0b2747dbb017723b55fc54f4f0cdcae/'
+ "https://zapier.com/platform/public-invite/5927/a0b2747dbb017723b55fc54f4f0cdcae/"
);
};
render() {
@@ -17,14 +17,14 @@ class Zapier extends React.Component<*> {
Zapier
- There is now an Outline app on{' '}
+ There is now an Outline app on{" "}
Zapier
- {' '}
+ {" "}
to allow easy integration with hundreds of other business services. It
is currently in early access, to use the integration and hook up to
your wiki simply accept the public invite below. All configuration is
diff --git a/app/scenes/Settings/components/EventListItem.js b/app/scenes/Settings/components/EventListItem.js
index a230e538..08abbffb 100644
--- a/app/scenes/Settings/components/EventListItem.js
+++ b/app/scenes/Settings/components/EventListItem.js
@@ -1,12 +1,12 @@
// @flow
-import * as React from 'react';
-import { Link } from 'react-router-dom';
-import { capitalize } from 'lodash';
-import styled from 'styled-components';
-import Time from 'shared/components/Time';
-import ListItem from 'components/List/Item';
-import Avatar from 'components/Avatar';
-import Event from 'models/Event';
+import * as React from "react";
+import { Link } from "react-router-dom";
+import { capitalize } from "lodash";
+import styled from "styled-components";
+import Time from "shared/components/Time";
+import ListItem from "components/List/Item";
+import Avatar from "components/Avatar";
+import Event from "models/Event";
type Props = {
event: Event,
@@ -14,104 +14,104 @@ type Props = {
const description = event => {
switch (event.name) {
- case 'api_keys.create':
+ case "api_keys.create":
return (
Created the API token {event.data.name}
);
- case 'api_keys.delete':
+ case "api_keys.delete":
return (
Revoked the API token {event.data.name}
);
- case 'teams.create':
- return 'Created the team';
- case 'shares.create':
- case 'shares.revoke':
+ case "teams.create":
+ return "Created the team";
+ case "shares.create":
+ case "shares.revoke":
return (
- {capitalize(event.verbPastTense)} a{' '}
- public link to the{' '}
- {event.data.name}{' '}
+ {capitalize(event.verbPastTense)} a{" "}
+ public link to the{" "}
+ {event.data.name}{" "}
document
);
- case 'users.create':
+ case "users.create":
return (
{event.data.name} created an account
);
- case 'users.invite':
+ case "users.invite":
return (
{capitalize(event.verbPastTense)} {event.data.name} (
- {event.data.email || ''}
+ {event.data.email || ""}
)
);
- case 'users.suspend':
+ case "users.suspend":
return (
Suspended {event.data.name}’s account
);
- case 'users.activate':
+ case "users.activate":
return (
Unsuspended {event.data.name}’s account
);
- case 'users.promote':
+ case "users.promote":
return (
Made {event.data.name} an admin
);
- case 'users.demote':
+ case "users.demote":
return (
Made {event.data.name} a member
);
- case 'users.delete':
- return 'Deleted their account';
- case 'groups.create':
+ case "users.delete":
+ return "Deleted their account";
+ case "groups.create":
return (
Created the group {event.data.name}
);
- case 'groups.update':
+ case "groups.update":
return (
Update the group {event.data.name}
);
- case 'groups.delete':
+ case "groups.delete":
return (
Deleted the group {event.data.name}
);
- case 'collections.add_user':
- case 'collections.add_group':
+ case "collections.add_user":
+ case "collections.add_group":
return (
- Granted {event.data.name} access to a{' '}
-
+ Granted {event.data.name} access to a{" "}
+
collection
);
- case 'collections.remove_user':
- case 'collections.remove_group':
+ case "collections.remove_user":
+ case "collections.remove_group":
return (
- Revoked {event.data.name} access to a{' '}
-
+ Revoked {event.data.name} access to a{" "}
+
collection
@@ -120,7 +120,7 @@ const description = event => {
}
if (event.documentId) {
- if (event.name === 'documents.delete') {
+ if (event.name === "documents.delete") {
return (
Deleted the {event.data.title} document
@@ -129,13 +129,13 @@ const description = event => {
}
return (
- {capitalize(event.verbPastTense)} the{' '}
+ {capitalize(event.verbPastTense)} the{" "}
{event.data.title} document
);
}
if (event.collectionId) {
- if (event.name === 'collections.delete') {
+ if (event.name === "collections.delete") {
return (
Deleted the {event.data.name} collection
@@ -144,10 +144,10 @@ const description = event => {
}
return (
- {capitalize(event.verbPastTense)} the{' '}
-
+ {capitalize(event.verbPastTense)} the{" "}
+
{event.data.name}
- {' '}
+ {" "}
collection
);
@@ -159,7 +159,7 @@ const description = event => {
);
}
- return '';
+ return "";
};
const EventListItem = ({ event }: Props) => {
@@ -170,7 +170,7 @@ const EventListItem = ({ event }: Props) => {
image={}
subtitle={
- {description(event)} ago ·{' '}
+ {description(event)} ago ·{" "}
{event.name}
}
@@ -193,7 +193,7 @@ const EventListItem = ({ event }: Props) => {
);
};
-const IP = styled('span')`
+const IP = styled("span")`
color: ${props => props.theme.textTertiary};
font-size: 12px;
`;
diff --git a/app/scenes/Settings/components/ImageUpload.js b/app/scenes/Settings/components/ImageUpload.js
index 47b1069c..ac64d195 100644
--- a/app/scenes/Settings/components/ImageUpload.js
+++ b/app/scenes/Settings/components/ImageUpload.js
@@ -1,16 +1,16 @@
// @flow
-import * as React from 'react';
-import { observable } from 'mobx';
-import { observer, inject } from 'mobx-react';
-import styled from 'styled-components';
-import Dropzone from 'react-dropzone';
-import LoadingIndicator from 'components/LoadingIndicator';
-import Flex from 'shared/components/Flex';
-import Modal from 'components/Modal';
-import Button from 'components/Button';
-import AvatarEditor from 'react-avatar-editor';
-import { uploadFile, dataUrlToBlob } from 'utils/uploadFile';
-import UiStore from 'stores/UiStore';
+import * as React from "react";
+import { observable } from "mobx";
+import { observer, inject } from "mobx-react";
+import styled from "styled-components";
+import Dropzone from "react-dropzone";
+import LoadingIndicator from "components/LoadingIndicator";
+import Flex from "shared/components/Flex";
+import Modal from "components/Modal";
+import Button from "components/Button";
+import AvatarEditor from "react-avatar-editor";
+import { uploadFile, dataUrlToBlob } from "utils/uploadFile";
+import UiStore from "stores/UiStore";
type Props = {
children?: React.Node,
@@ -30,7 +30,7 @@ class ImageUpload extends React.Component {
avatarEditorRef: AvatarEditor;
static defaultProps = {
- submitText: 'Crop Picture',
+ submitText: "Crop Picture",
borderRadius: 150,
};
@@ -92,7 +92,7 @@ class ImageUpload extends React.Component {
border={25}
borderRadius={this.props.borderRadius}
color={
- ui.theme === 'light' ? [255, 255, 255, 0.6] : [0, 0, 0, 0.6]
+ ui.theme === "light" ? [255, 255, 255, 0.6] : [0, 0, 0, 0.6]
} // RGBA
scale={this.zoom}
rotate={0}
@@ -107,7 +107,7 @@ class ImageUpload extends React.Component {
onChange={this.handleZoom}
/>
- {this.isUploading ? 'Uploading…' : submitText}
+ {this.isUploading ? "Uploading…" : submitText}
@@ -167,4 +167,4 @@ const CropButton = styled(Button)`
width: 300px;
`;
-export default inject('ui')(ImageUpload);
+export default inject("ui")(ImageUpload);
diff --git a/app/scenes/Settings/components/NotificationListItem.js b/app/scenes/Settings/components/NotificationListItem.js
index 60e41fac..bb5ee980 100644
--- a/app/scenes/Settings/components/NotificationListItem.js
+++ b/app/scenes/Settings/components/NotificationListItem.js
@@ -1,7 +1,7 @@
// @flow
-import * as React from 'react';
-import Checkbox from 'components/Checkbox';
-import NotificationSetting from 'models/NotificationSetting';
+import * as React from "react";
+import Checkbox from "components/Checkbox";
+import NotificationSetting from "models/NotificationSetting";
type Props = {
setting?: NotificationSetting,
diff --git a/app/scenes/Settings/components/ShareListItem.js b/app/scenes/Settings/components/ShareListItem.js
index d0b7e271..1f054aec 100644
--- a/app/scenes/Settings/components/ShareListItem.js
+++ b/app/scenes/Settings/components/ShareListItem.js
@@ -1,9 +1,9 @@
// @flow
-import * as React from 'react';
-import ShareMenu from 'menus/ShareMenu';
-import ListItem from 'components/List/Item';
-import Time from 'shared/components/Time';
-import Share from 'models/Share';
+import * as React from "react";
+import ShareMenu from "menus/ShareMenu";
+import ListItem from "components/List/Item";
+import Time from "shared/components/Time";
+import Share from "models/Share";
type Props = {
share: Share,
@@ -16,7 +16,7 @@ const ShareListItem = ({ share }: Props) => {
title={share.documentTitle}
subtitle={
- Shared ago by{' '}
+ Shared ago by{" "}
{share.createdBy.name}
}
diff --git a/app/scenes/Settings/components/SlackButton.js b/app/scenes/Settings/components/SlackButton.js
index 13f72dcc..d471f2a9 100644
--- a/app/scenes/Settings/components/SlackButton.js
+++ b/app/scenes/Settings/components/SlackButton.js
@@ -1,9 +1,9 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import { slackAuth } from 'shared/utils/routeHelpers';
-import SlackLogo from 'shared/components/SlackLogo';
-import Button from 'components/Button';
+import * as React from "react";
+import styled from "styled-components";
+import { slackAuth } from "shared/utils/routeHelpers";
+import SlackLogo from "shared/components/SlackLogo";
+import Button from "components/Button";
type Props = {
scopes?: string[],
diff --git a/app/scenes/Settings/components/TokenListItem.js b/app/scenes/Settings/components/TokenListItem.js
index d89e58b1..2f7df3a8 100644
--- a/app/scenes/Settings/components/TokenListItem.js
+++ b/app/scenes/Settings/components/TokenListItem.js
@@ -1,8 +1,8 @@
// @flow
-import * as React from 'react';
-import Button from 'components/Button';
-import ListItem from 'components/List/Item';
-import ApiKey from 'models/ApiKey';
+import * as React from "react";
+import Button from "components/Button";
+import ListItem from "components/List/Item";
+import ApiKey from "models/ApiKey";
type Props = {
token: ApiKey,
diff --git a/app/scenes/Settings/components/UserListItem.js b/app/scenes/Settings/components/UserListItem.js
index 8069d331..a049e8c8 100644
--- a/app/scenes/Settings/components/UserListItem.js
+++ b/app/scenes/Settings/components/UserListItem.js
@@ -1,15 +1,15 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import { observable } from 'mobx';
-import { observer } from 'mobx-react';
-import UserMenu from 'menus/UserMenu';
-import Avatar from 'components/Avatar';
-import Badge from 'components/Badge';
-import UserProfile from 'scenes/UserProfile';
-import ListItem from 'components/List/Item';
-import Time from 'shared/components/Time';
-import User from 'models/User';
+import * as React from "react";
+import styled from "styled-components";
+import { observable } from "mobx";
+import { observer } from "mobx-react";
+import UserMenu from "menus/UserMenu";
+import Avatar from "components/Avatar";
+import Badge from "components/Badge";
+import UserProfile from "scenes/UserProfile";
+import ListItem from "components/List/Item";
+import Time from "shared/components/Time";
+import User from "models/User";
type Props = {
user: User,
@@ -56,7 +56,7 @@ class UserListItem extends React.Component {
Active ago
) : (
- 'Invited'
+ "Invited"
)}
{user.isAdmin && Admin}
{user.isSuspended && Suspended}
diff --git a/app/scenes/Settings/index.js b/app/scenes/Settings/index.js
index c1e3920e..c8f34287 100644
--- a/app/scenes/Settings/index.js
+++ b/app/scenes/Settings/index.js
@@ -1,3 +1,3 @@
// @flow
-import Profile from './Profile';
+import Profile from "./Profile";
export default Profile;
diff --git a/app/scenes/Starred.js b/app/scenes/Starred.js
index 4aaaa679..8e808353 100644
--- a/app/scenes/Starred.js
+++ b/app/scenes/Starred.js
@@ -1,18 +1,18 @@
// @flow
-import * as React from 'react';
-import { observer, inject } from 'mobx-react';
+import * as React from "react";
+import { observer, inject } from "mobx-react";
-import CenteredContent from 'components/CenteredContent';
-import Empty from 'components/Empty';
-import PageTitle from 'components/PageTitle';
-import Heading from 'components/Heading';
-import PaginatedDocumentList from 'components/PaginatedDocumentList';
-import InputSearch from 'components/InputSearch';
-import Tabs from 'components/Tabs';
-import Tab from 'components/Tab';
-import NewDocumentMenu from 'menus/NewDocumentMenu';
-import Actions, { Action } from 'components/Actions';
-import DocumentsStore from 'stores/DocumentsStore';
+import CenteredContent from "components/CenteredContent";
+import Empty from "components/Empty";
+import PageTitle from "components/PageTitle";
+import Heading from "components/Heading";
+import PaginatedDocumentList from "components/PaginatedDocumentList";
+import InputSearch from "components/InputSearch";
+import Tabs from "components/Tabs";
+import Tab from "components/Tab";
+import NewDocumentMenu from "menus/NewDocumentMenu";
+import Actions, { Action } from "components/Actions";
+import DocumentsStore from "stores/DocumentsStore";
type Props = {
documents: DocumentsStore,
@@ -42,7 +42,7 @@ class Starred extends React.Component {
}
empty={You’ve not starred any documents yet.}
fetch={fetchStarred}
- documents={sort === 'alphabetical' ? starredAlphabetical : starred}
+ documents={sort === "alphabetical" ? starredAlphabetical : starred}
showCollection
/>
@@ -59,4 +59,4 @@ class Starred extends React.Component {
}
}
-export default inject('documents')(Starred);
+export default inject("documents")(Starred);
diff --git a/app/scenes/Trash.js b/app/scenes/Trash.js
index 931b3a5a..31ecd60d 100644
--- a/app/scenes/Trash.js
+++ b/app/scenes/Trash.js
@@ -1,14 +1,14 @@
// @flow
-import * as React from 'react';
-import { observer, inject } from 'mobx-react';
+import * as React from "react";
+import { observer, inject } from "mobx-react";
-import CenteredContent from 'components/CenteredContent';
-import Empty from 'components/Empty';
-import PageTitle from 'components/PageTitle';
-import Heading from 'components/Heading';
-import PaginatedDocumentList from 'components/PaginatedDocumentList';
-import Subheading from 'components/Subheading';
-import DocumentsStore from 'stores/DocumentsStore';
+import CenteredContent from "components/CenteredContent";
+import Empty from "components/Empty";
+import PageTitle from "components/PageTitle";
+import Heading from "components/Heading";
+import PaginatedDocumentList from "components/PaginatedDocumentList";
+import Subheading from "components/Subheading";
+import DocumentsStore from "stores/DocumentsStore";
type Props = {
documents: DocumentsStore,
@@ -35,4 +35,4 @@ class Trash extends React.Component {
}
}
-export default inject('documents')(Trash);
+export default inject("documents")(Trash);
diff --git a/app/scenes/UserDelete.js b/app/scenes/UserDelete.js
index 41ac9e95..7309b81e 100644
--- a/app/scenes/UserDelete.js
+++ b/app/scenes/UserDelete.js
@@ -1,12 +1,12 @@
// @flow
-import * as React from 'react';
-import { observable } from 'mobx';
-import { inject, observer } from 'mobx-react';
-import Button from 'components/Button';
-import Flex from 'shared/components/Flex';
-import HelpText from 'components/HelpText';
-import Modal from 'components/Modal';
-import AuthStore from 'stores/AuthStore';
+import * as React from "react";
+import { observable } from "mobx";
+import { inject, observer } from "mobx-react";
+import Button from "components/Button";
+import Flex from "shared/components/Flex";
+import HelpText from "components/HelpText";
+import Modal from "components/Modal";
+import AuthStore from "stores/AuthStore";
type Props = {
auth: AuthStore,
@@ -47,7 +47,7 @@ class UserDelete extends React.Component {
be automatically reprovisioned.
@@ -56,4 +56,4 @@ class UserDelete extends React.Component {
}
}
-export default inject('auth')(UserDelete);
+export default inject("auth")(UserDelete);
diff --git a/app/scenes/UserProfile.js b/app/scenes/UserProfile.js
index 010bed5c..7ce72313 100644
--- a/app/scenes/UserProfile.js
+++ b/app/scenes/UserProfile.js
@@ -1,22 +1,22 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
-import { inject, observer } from 'mobx-react';
-import { withRouter, type RouterHistory } from 'react-router-dom';
-import { EditIcon } from 'outline-icons';
-import Flex from 'shared/components/Flex';
-import HelpText from 'components/HelpText';
-import Modal from 'components/Modal';
-import Button from 'components/Button';
-import Avatar from 'components/Avatar';
-import Badge from 'components/Badge';
-import PaginatedDocumentList from 'components/PaginatedDocumentList';
-import Subheading from 'components/Subheading';
-import User from 'models/User';
-import DocumentsStore from 'stores/DocumentsStore';
-import AuthStore from 'stores/AuthStore';
-import { settings } from 'shared/utils/routeHelpers';
+import * as React from "react";
+import styled from "styled-components";
+import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
+import { inject, observer } from "mobx-react";
+import { withRouter, type RouterHistory } from "react-router-dom";
+import { EditIcon } from "outline-icons";
+import Flex from "shared/components/Flex";
+import HelpText from "components/HelpText";
+import Modal from "components/Modal";
+import Button from "components/Button";
+import Avatar from "components/Avatar";
+import Badge from "components/Badge";
+import PaginatedDocumentList from "components/PaginatedDocumentList";
+import Subheading from "components/Subheading";
+import User from "models/User";
+import DocumentsStore from "stores/DocumentsStore";
+import AuthStore from "stores/AuthStore";
+import { settings } from "shared/utils/routeHelpers";
type Props = {
user: User,
@@ -46,8 +46,8 @@ class UserProfile extends React.Component {
{isCurrentUser
- ? 'You joined'
- : user.lastActiveAt ? 'Joined' : 'Invited'}{' '}
+ ? "You joined"
+ : user.lastActiveAt ? "Joined" : "Invited"}{" "}
{distanceInWordsToNow(new Date(user.createdAt))} ago.
{user.isAdmin && (
Admin
@@ -96,4 +96,4 @@ const Meta = styled(HelpText)`
margin-top: -12px;
`;
-export default inject('documents', 'auth')(withRouter(UserProfile));
+export default inject("documents", "auth")(withRouter(UserProfile));
diff --git a/app/stores/ApiKeysStore.js b/app/stores/ApiKeysStore.js
index 865eeb13..a9bc4f35 100644
--- a/app/stores/ApiKeysStore.js
+++ b/app/stores/ApiKeysStore.js
@@ -1,10 +1,10 @@
// @flow
-import BaseStore from './BaseStore';
-import RootStore from './RootStore';
-import ApiKey from 'models/ApiKey';
+import BaseStore from "./BaseStore";
+import RootStore from "./RootStore";
+import ApiKey from "models/ApiKey";
export default class ApiKeysStore extends BaseStore {
- actions = ['list', 'create', 'delete'];
+ actions = ["list", "create", "delete"];
constructor(rootStore: RootStore) {
super(rootStore, ApiKey);
diff --git a/app/stores/AuthStore.js b/app/stores/AuthStore.js
index 6586b290..a0e9afdb 100644
--- a/app/stores/AuthStore.js
+++ b/app/stores/AuthStore.js
@@ -1,14 +1,14 @@
// @flow
-import { observable, action, computed, autorun, runInAction } from 'mobx';
-import invariant from 'invariant';
-import { getCookie, setCookie, removeCookie } from 'tiny-cookie';
-import { client } from 'utils/ApiClient';
-import { getCookieDomain } from 'shared/utils/domains';
-import RootStore from 'stores/RootStore';
-import User from 'models/User';
-import Team from 'models/Team';
+import { observable, action, computed, autorun, runInAction } from "mobx";
+import invariant from "invariant";
+import { getCookie, setCookie, removeCookie } from "tiny-cookie";
+import { client } from "utils/ApiClient";
+import { getCookieDomain } from "shared/utils/domains";
+import RootStore from "stores/RootStore";
+import User from "models/User";
+import Team from "models/Team";
-const AUTH_STORE = 'AUTH_STORE';
+const AUTH_STORE = "AUTH_STORE";
export default class AuthStore {
@observable user: ?User;
@@ -23,7 +23,7 @@ export default class AuthStore {
// Rehydrate
let data = {};
try {
- data = JSON.parse(localStorage.getItem(AUTH_STORE) || '{}');
+ data = JSON.parse(localStorage.getItem(AUTH_STORE) || "{}");
} catch (_) {
// no-op Safari private mode
}
@@ -31,7 +31,7 @@ export default class AuthStore {
this.rootStore = rootStore;
this.user = new User(data.user);
this.team = new Team(data.team);
- this.token = getCookie('accessToken');
+ this.token = getCookie("accessToken");
if (this.token) setImmediate(() => this.fetch());
@@ -66,10 +66,10 @@ export default class AuthStore {
@action
fetch = async () => {
try {
- const res = await client.post('/auth.info');
- invariant(res && res.data, 'Auth not available');
+ const res = await client.post("/auth.info");
+ invariant(res && res.data, "Auth not available");
- runInAction('AuthStore#fetch', () => {
+ runInAction("AuthStore#fetch", () => {
this.addPolicies(res.policies);
const { user, team } = res.data;
this.user = new User(user);
@@ -78,20 +78,20 @@ export default class AuthStore {
if (window.Sentry) {
Sentry.configureScope(function(scope) {
scope.setUser({ id: user.id });
- scope.setExtra('team', team.name);
- scope.setExtra('teamId', team.id);
+ scope.setExtra("team", team.name);
+ scope.setExtra("teamId", team.id);
});
}
// If we came from a redirect then send the user immediately there
- const postLoginRedirectPath = getCookie('postLoginRedirectPath');
+ const postLoginRedirectPath = getCookie("postLoginRedirectPath");
if (postLoginRedirectPath) {
- removeCookie('postLoginRedirectPath');
+ removeCookie("postLoginRedirectPath");
window.location.href = postLoginRedirectPath;
}
});
} catch (err) {
- if (err.error === 'user_suspended') {
+ if (err.error === "user_suspended") {
this.isSuspended = true;
this.suspendedContactEmail = err.data.adminEmail;
}
@@ -102,7 +102,7 @@ export default class AuthStore {
deleteUser = async () => {
await client.post(`/users.delete`, { confirmation: true });
- runInAction('AuthStore#updateUser', () => {
+ runInAction("AuthStore#updateUser", () => {
this.user = null;
this.team = null;
this.token = null;
@@ -115,9 +115,9 @@ export default class AuthStore {
try {
const res = await client.post(`/users.update`, params);
- invariant(res && res.data, 'User response not available');
+ invariant(res && res.data, "User response not available");
- runInAction('AuthStore#updateUser', () => {
+ runInAction("AuthStore#updateUser", () => {
this.addPolicies(res.policies);
this.user = res.data;
});
@@ -136,9 +136,9 @@ export default class AuthStore {
try {
const res = await client.post(`/team.update`, params);
- invariant(res && res.data, 'Team response not available');
+ invariant(res && res.data, "Team response not available");
- runInAction('AuthStore#updateTeam', () => {
+ runInAction("AuthStore#updateTeam", () => {
this.addPolicies(res.policies);
this.team = new Team(res.data);
});
@@ -161,19 +161,19 @@ export default class AuthStore {
// if this logout was forced from an authenticated route then
// save the current path so we can go back there once signed in
if (savePath) {
- setCookie('postLoginRedirectPath', window.location.pathname);
+ setCookie("postLoginRedirectPath", window.location.pathname);
}
// remove authentication token itself
- removeCookie('accessToken', { path: '/' });
+ removeCookie("accessToken", { path: "/" });
// remove session record on apex cookie
const team = this.team;
if (team) {
- const sessions = JSON.parse(getCookie('sessions') || '{}');
+ const sessions = JSON.parse(getCookie("sessions") || "{}");
delete sessions[team.id];
- setCookie('sessions', JSON.stringify(sessions), {
+ setCookie("sessions", JSON.stringify(sessions), {
domain: getCookieDomain(window.location.hostname),
});
this.team = null;
diff --git a/app/stores/BaseStore.js b/app/stores/BaseStore.js
index 20345ffc..36dd6c9e 100644
--- a/app/stores/BaseStore.js
+++ b/app/stores/BaseStore.js
@@ -1,13 +1,13 @@
// @flow
-import invariant from 'invariant';
-import { observable, set, action, computed, runInAction } from 'mobx';
-import { orderBy } from 'lodash';
-import { client } from 'utils/ApiClient';
-import RootStore from 'stores/RootStore';
-import BaseModel from '../models/BaseModel';
-import type { PaginationParams } from 'types';
+import invariant from "invariant";
+import { observable, set, action, computed, runInAction } from "mobx";
+import { orderBy } from "lodash";
+import { client } from "utils/ApiClient";
+import RootStore from "stores/RootStore";
+import BaseModel from "../models/BaseModel";
+import type { PaginationParams } from "types";
-type Action = 'list' | 'info' | 'create' | 'update' | 'delete';
+type Action = "list" | "info" | "create" | "update" | "delete";
function modelNameFromClassName(string) {
return string.charAt(0).toLowerCase() + string.slice(1);
@@ -24,7 +24,7 @@ export default class BaseStore {
model: Class;
modelName: string;
rootStore: RootStore;
- actions: Action[] = ['list', 'info', 'create', 'update', 'delete'];
+ actions: Action[] = ["list", "info", "create", "update", "delete"];
constructor(rootStore: RootStore, model: Class) {
this.rootStore = rootStore;
@@ -77,7 +77,7 @@ export default class BaseStore {
@action
async create(params: Object) {
- if (!this.actions.includes('create')) {
+ if (!this.actions.includes("create")) {
throw new Error(`Cannot create ${this.modelName}`);
}
this.isSaving = true;
@@ -85,7 +85,7 @@ export default class BaseStore {
try {
const res = await client.post(`/${this.modelName}s.create`, params);
- invariant(res && res.data, 'Data should be available');
+ invariant(res && res.data, "Data should be available");
this.addPolicies(res.policies);
return this.add(res.data);
@@ -96,7 +96,7 @@ export default class BaseStore {
@action
async update(params: Object): * {
- if (!this.actions.includes('update')) {
+ if (!this.actions.includes("update")) {
throw new Error(`Cannot update ${this.modelName}`);
}
this.isSaving = true;
@@ -104,7 +104,7 @@ export default class BaseStore {
try {
const res = await client.post(`/${this.modelName}s.update`, params);
- invariant(res && res.data, 'Data should be available');
+ invariant(res && res.data, "Data should be available");
this.addPolicies(res.policies);
return this.add(res.data);
@@ -115,7 +115,7 @@ export default class BaseStore {
@action
async delete(item: T, options?: Object = {}) {
- if (!this.actions.includes('delete')) {
+ if (!this.actions.includes("delete")) {
throw new Error(`Cannot delete ${this.modelName}`);
}
this.isSaving = true;
@@ -133,7 +133,7 @@ export default class BaseStore {
@action
async fetch(id: string, options?: Object = {}): Promise<*> {
- if (!this.actions.includes('info')) {
+ if (!this.actions.includes("info")) {
throw new Error(`Cannot fetch ${this.modelName}`);
}
@@ -144,7 +144,7 @@ export default class BaseStore {
try {
const res = await client.post(`/${this.modelName}s.info`, { id });
- invariant(res && res.data, 'Data should be available');
+ invariant(res && res.data, "Data should be available");
this.addPolicies(res.policies);
return this.add(res.data);
@@ -160,7 +160,7 @@ export default class BaseStore {
@action
fetchPage = async (params: ?PaginationParams): Promise<*> => {
- if (!this.actions.includes('list')) {
+ if (!this.actions.includes("list")) {
throw new Error(`Cannot list ${this.modelName}`);
}
this.isFetching = true;
@@ -168,7 +168,7 @@ export default class BaseStore {
try {
const res = await client.post(`/${this.modelName}s.list`, params);
- invariant(res && res.data, 'Data not available');
+ invariant(res && res.data, "Data not available");
runInAction(`list#${this.modelName}`, () => {
this.addPolicies(res.policies);
@@ -183,6 +183,6 @@ export default class BaseStore {
@computed
get orderedData(): T[] {
- return orderBy(Array.from(this.data.values()), 'createdAt', 'desc');
+ return orderBy(Array.from(this.data.values()), "createdAt", "desc");
}
}
diff --git a/app/stores/CollectionGroupMembershipsStore.js b/app/stores/CollectionGroupMembershipsStore.js
index 10b58de8..b55851ed 100644
--- a/app/stores/CollectionGroupMembershipsStore.js
+++ b/app/stores/CollectionGroupMembershipsStore.js
@@ -1,16 +1,16 @@
// @flow
-import invariant from 'invariant';
-import { action, runInAction } from 'mobx';
-import { client } from 'utils/ApiClient';
-import BaseStore from './BaseStore';
-import RootStore from './RootStore';
-import CollectionGroupMembership from 'models/CollectionGroupMembership';
-import type { PaginationParams } from 'types';
+import invariant from "invariant";
+import { action, runInAction } from "mobx";
+import { client } from "utils/ApiClient";
+import BaseStore from "./BaseStore";
+import RootStore from "./RootStore";
+import CollectionGroupMembership from "models/CollectionGroupMembership";
+import type { PaginationParams } from "types";
export default class CollectionGroupMembershipsStore extends BaseStore<
CollectionGroupMembership
> {
- actions = ['create', 'delete'];
+ actions = ["create", "delete"];
constructor(rootStore: RootStore) {
super(rootStore, CollectionGroupMembership);
@@ -23,7 +23,7 @@ export default class CollectionGroupMembershipsStore extends BaseStore<
try {
const res = await client.post(`/collections.group_memberships`, params);
- invariant(res && res.data, 'Data not available');
+ invariant(res && res.data, "Data not available");
runInAction(`CollectionGroupMembershipsStore#fetchPage`, () => {
res.data.groups.forEach(this.rootStore.groups.add);
@@ -46,12 +46,12 @@ export default class CollectionGroupMembershipsStore extends BaseStore<
groupId: string,
permission: string,
}) {
- const res = await client.post('/collections.add_group', {
+ const res = await client.post("/collections.add_group", {
id: collectionId,
groupId,
permission,
});
- invariant(res && res.data, 'Membership data should be available');
+ invariant(res && res.data, "Membership data should be available");
res.data.collectionGroupMemberships.forEach(this.add);
}
@@ -64,7 +64,7 @@ export default class CollectionGroupMembershipsStore extends BaseStore<
collectionId: string,
groupId: string,
}) {
- await client.post('/collections.remove_group', {
+ await client.post("/collections.remove_group", {
id: collectionId,
groupId,
});
diff --git a/app/stores/CollectionsStore.js b/app/stores/CollectionsStore.js
index d08ae3bb..9684047a 100644
--- a/app/stores/CollectionsStore.js
+++ b/app/stores/CollectionsStore.js
@@ -1,19 +1,19 @@
// @flow
-import { computed } from 'mobx';
-import { concat, filter, last } from 'lodash';
-import { client } from 'utils/ApiClient';
+import { computed } from "mobx";
+import { concat, filter, last } from "lodash";
+import { client } from "utils/ApiClient";
-import BaseStore from './BaseStore';
-import RootStore from './RootStore';
-import Collection from 'models/Collection';
-import naturalSort from 'shared/utils/naturalSort';
+import BaseStore from "./BaseStore";
+import RootStore from "./RootStore";
+import Collection from "models/Collection";
+import naturalSort from "shared/utils/naturalSort";
export type DocumentPathItem = {
id: string,
collectionId: string,
title: string,
url: string,
- type: 'collection' | 'document',
+ type: "collection" | "document",
};
export type DocumentPath = DocumentPathItem & {
@@ -35,7 +35,7 @@ export default class CollectionsStore extends BaseStore {
@computed
get orderedData(): Collection[] {
return filter(
- naturalSort(Array.from(this.data.values()), 'name'),
+ naturalSort(Array.from(this.data.values()), "name"),
d => !d.deletedAt
);
}
@@ -59,7 +59,7 @@ export default class CollectionsStore extends BaseStore {
const travelDocuments = (documentList, collectionId, path) =>
documentList.forEach(document => {
const { id, title, url } = document;
- const node = { id, collectionId, title, url, type: 'document' };
+ const node = { id, collectionId, title, url, type: "document" };
results.push(concat(path, node));
travelDocuments(document.children, collectionId, concat(path, [node]));
});
@@ -72,7 +72,7 @@ export default class CollectionsStore extends BaseStore {
collectionId: id,
title: name,
url,
- type: 'collection',
+ type: "collection",
};
results.push([node]);
travelDocuments(collection.documents, id, [node]);
@@ -105,6 +105,6 @@ export default class CollectionsStore extends BaseStore {
}
export = () => {
- return client.post('/collections.export_all');
+ return client.post("/collections.export_all");
};
}
diff --git a/app/stores/DocumentPresenceStore.js b/app/stores/DocumentPresenceStore.js
index e368a8c5..6877a909 100644
--- a/app/stores/DocumentPresenceStore.js
+++ b/app/stores/DocumentPresenceStore.js
@@ -1,6 +1,6 @@
// @flow
-import { observable, action } from 'mobx';
-import { USER_PRESENCE_INTERVAL } from 'shared/constants';
+import { observable, action } from "mobx";
+import { USER_PRESENCE_INTERVAL } from "shared/constants";
type DocumentPresence = Map;
diff --git a/app/stores/DocumentsStore.js b/app/stores/DocumentsStore.js
index 614a840b..b0ea646c 100644
--- a/app/stores/DocumentsStore.js
+++ b/app/stores/DocumentsStore.js
@@ -1,5 +1,5 @@
// @flow
-import { observable, action, computed, runInAction } from 'mobx';
+import { observable, action, computed, runInAction } from "mobx";
import {
without,
map,
@@ -9,16 +9,16 @@ import {
compact,
omitBy,
uniq,
-} from 'lodash';
-import { client } from 'utils/ApiClient';
-import naturalSort from 'shared/utils/naturalSort';
-import invariant from 'invariant';
+} from "lodash";
+import { client } from "utils/ApiClient";
+import naturalSort from "shared/utils/naturalSort";
+import invariant from "invariant";
-import BaseStore from 'stores/BaseStore';
-import RootStore from 'stores/RootStore';
-import Document from 'models/Document';
-import Revision from 'models/Revision';
-import type { FetchOptions, PaginationParams, SearchResult } from 'types';
+import BaseStore from "stores/BaseStore";
+import RootStore from "stores/RootStore";
+import Document from "models/Document";
+import Revision from "models/Revision";
+import type { FetchOptions, PaginationParams, SearchResult } from "types";
export default class DocumentsStore extends BaseStore {
@observable recentlyViewedIds: string[] = [];
@@ -39,21 +39,21 @@ export default class DocumentsStore extends BaseStore {
get recentlyViewed(): Document[] {
return orderBy(
compact(this.recentlyViewedIds.map(id => this.data.get(id))),
- 'updatedAt',
- 'desc'
+ "updatedAt",
+ "desc"
);
}
@computed
get recentlyUpdated(): Document[] {
- return orderBy(this.all, 'updatedAt', 'desc');
+ return orderBy(this.all, "updatedAt", "desc");
}
createdByUser(userId: string): Document[] {
return orderBy(
filter(this.all, d => d.createdBy.id === userId),
- 'updatedAt',
- 'desc'
+ "updatedAt",
+ "desc"
);
}
@@ -77,23 +77,23 @@ export default class DocumentsStore extends BaseStore {
}
leastRecentlyUpdatedInCollection(collectionId: string): Document[] {
- return orderBy(this.inCollection(collectionId), 'updatedAt', 'asc');
+ return orderBy(this.inCollection(collectionId), "updatedAt", "asc");
}
recentlyUpdatedInCollection(collectionId: string): Document[] {
- return orderBy(this.inCollection(collectionId), 'updatedAt', 'desc');
+ return orderBy(this.inCollection(collectionId), "updatedAt", "desc");
}
recentlyPublishedInCollection(collectionId: string): Document[] {
return orderBy(
this.publishedInCollection(collectionId),
- 'publishedAt',
- 'desc'
+ "publishedAt",
+ "desc"
);
}
alphabeticalInCollection(collectionId: string): Document[] {
- return naturalSort(this.inCollection(collectionId), 'title');
+ return naturalSort(this.inCollection(collectionId), "title");
}
searchResults(query: string): SearchResult[] {
@@ -108,7 +108,7 @@ export default class DocumentsStore extends BaseStore {
@computed
get archived(): Document[] {
return filter(
- orderBy(this.orderedData, 'archivedAt', 'desc'),
+ orderBy(this.orderedData, "archivedAt", "desc"),
d => d.archivedAt && !d.deletedAt
);
}
@@ -116,20 +116,20 @@ export default class DocumentsStore extends BaseStore {
@computed
get deleted(): Document[] {
return filter(
- orderBy(this.orderedData, 'deletedAt', 'desc'),
+ orderBy(this.orderedData, "deletedAt", "desc"),
d => d.deletedAt
);
}
@computed
get starredAlphabetical(): Document[] {
- return naturalSort(this.starred, 'title');
+ return naturalSort(this.starred, "title");
}
@computed
get drafts(): Document[] {
return filter(
- orderBy(this.all, 'updatedAt', 'desc'),
+ orderBy(this.all, "updatedAt", "desc"),
doc => !doc.publishedAt
);
}
@@ -146,9 +146,9 @@ export default class DocumentsStore extends BaseStore {
const res = await client.post(`/documents.list`, {
backlinkDocumentId: documentId,
});
- invariant(res && res.data, 'Document list not available');
+ invariant(res && res.data, "Document list not available");
const { data } = res;
- runInAction('DocumentsStore#fetchBacklinks', () => {
+ runInAction("DocumentsStore#fetchBacklinks", () => {
data.forEach(this.add);
this.backlinks.set(documentId, data.map(doc => doc.id));
});
@@ -158,8 +158,8 @@ export default class DocumentsStore extends BaseStore {
const documentIds = this.backlinks.get(documentId) || [];
return orderBy(
compact(documentIds.map(id => this.data.get(id))),
- 'updatedAt',
- 'desc'
+ "updatedAt",
+ "desc"
);
}
@@ -168,24 +168,24 @@ export default class DocumentsStore extends BaseStore {
const res = await client.post(`/documents.list`, {
parentDocumentId: documentId,
});
- invariant(res && res.data, 'Document list not available');
+ invariant(res && res.data, "Document list not available");
const { data } = res;
- runInAction('DocumentsStore#fetchChildDocuments', () => {
+ runInAction("DocumentsStore#fetchChildDocuments", () => {
data.forEach(this.add);
});
};
@action
fetchNamedPage = async (
- request: string = 'list',
+ request: string = "list",
options: ?PaginationParams
): Promise(Document[])> => {
this.isFetching = true;
try {
const res = await client.post(`/documents.${request}`, options);
- invariant(res && res.data, 'Document list not available');
- runInAction('DocumentsStore#fetchNamedPage', () => {
+ invariant(res && res.data, "Document list not available");
+ runInAction("DocumentsStore#fetchNamedPage", () => {
res.data.forEach(this.add);
this.addPolicies(res.policies);
this.isLoaded = true;
@@ -198,24 +198,24 @@ export default class DocumentsStore extends BaseStore {
@action
fetchArchived = async (options: ?PaginationParams): Promise<*> => {
- return this.fetchNamedPage('archived', options);
+ return this.fetchNamedPage("archived", options);
};
@action
fetchDeleted = async (options: ?PaginationParams): Promise<*> => {
- return this.fetchNamedPage('deleted', options);
+ return this.fetchNamedPage("deleted", options);
};
@action
fetchRecentlyUpdated = async (options: ?PaginationParams): Promise<*> => {
- return this.fetchNamedPage('list', options);
+ return this.fetchNamedPage("list", options);
};
@action
fetchAlphabetical = async (options: ?PaginationParams): Promise<*> => {
- return this.fetchNamedPage('list', {
- sort: 'title',
- direction: 'ASC',
+ return this.fetchNamedPage("list", {
+ sort: "title",
+ direction: "ASC",
...options,
});
};
@@ -224,30 +224,30 @@ export default class DocumentsStore extends BaseStore {
fetchLeastRecentlyUpdated = async (
options: ?PaginationParams
): Promise<*> => {
- return this.fetchNamedPage('list', {
- sort: 'updatedAt',
- direction: 'ASC',
+ return this.fetchNamedPage("list", {
+ sort: "updatedAt",
+ direction: "ASC",
...options,
});
};
@action
fetchRecentlyPublished = async (options: ?PaginationParams): Promise<*> => {
- return this.fetchNamedPage('list', {
- sort: 'publishedAt',
- direction: 'DESC',
+ return this.fetchNamedPage("list", {
+ sort: "publishedAt",
+ direction: "DESC",
...options,
});
};
@action
fetchRecentlyViewed = async (options: ?PaginationParams): Promise<*> => {
- const data = await this.fetchNamedPage('viewed', options);
+ const data = await this.fetchNamedPage("viewed", options);
- runInAction('DocumentsStore#fetchRecentlyViewed', () => {
+ runInAction("DocumentsStore#fetchRecentlyViewed", () => {
// $FlowFixMe
this.recentlyViewedIds.replace(
- uniq(this.recentlyViewedIds.concat(map(data, 'id')))
+ uniq(this.recentlyViewedIds.concat(map(data, "id")))
);
});
return data;
@@ -255,22 +255,22 @@ export default class DocumentsStore extends BaseStore {
@action
fetchStarred = (options: ?PaginationParams): Promise<*> => {
- return this.fetchNamedPage('starred', options);
+ return this.fetchNamedPage("starred", options);
};
@action
fetchDrafts = (options: ?PaginationParams): Promise<*> => {
- return this.fetchNamedPage('drafts', options);
+ return this.fetchNamedPage("drafts", options);
};
@action
fetchPinned = (options: ?PaginationParams): Promise<*> => {
- return this.fetchNamedPage('pinned', options);
+ return this.fetchNamedPage("pinned", options);
};
@action
fetchOwned = (options: ?PaginationParams): Promise<*> => {
- return this.fetchNamedPage('list', options);
+ return this.fetchNamedPage("list", options);
};
@action
@@ -280,11 +280,11 @@ export default class DocumentsStore extends BaseStore {
): Promise => {
// $FlowFixMe
const compactedOptions = omitBy(options, o => !o);
- const res = await client.get('/documents.search', {
+ const res = await client.get("/documents.search", {
...compactedOptions,
query,
});
- invariant(res && res.data, 'Search response should be available');
+ invariant(res && res.data, "Search response should be available");
// add the documents and associated policies to the store
res.data.forEach(result => this.add(result.document));
@@ -335,16 +335,16 @@ export default class DocumentsStore extends BaseStore {
return doc;
}
- const res = await client.post('/documents.info', {
+ const res = await client.post("/documents.info", {
id,
shareId: options.shareId,
});
- invariant(res && res.data, 'Document not available');
+ invariant(res && res.data, "Document not available");
this.addPolicies(res.policies);
this.add(res.data);
- runInAction('DocumentsStore#fetch', () => {
+ runInAction("DocumentsStore#fetch", () => {
this.isLoaded = true;
});
@@ -360,12 +360,12 @@ export default class DocumentsStore extends BaseStore {
collectionId: string,
parentDocumentId: ?string
) => {
- const res = await client.post('/documents.move', {
+ const res = await client.post("/documents.move", {
id: document.id,
collectionId,
parentDocumentId,
});
- invariant(res && res.data, 'Data not available');
+ invariant(res && res.data, "Data not available");
res.data.documents.forEach(this.add);
res.data.collections.forEach(this.rootStore.collections.add);
@@ -373,14 +373,14 @@ export default class DocumentsStore extends BaseStore {
@action
duplicate = async (document: Document): * => {
- const res = await client.post('/documents.create', {
+ const res = await client.post("/documents.create", {
publish: !!document.publishedAt,
parentDocumentId: document.parentDocumentId,
collectionId: document.collectionId,
title: `${document.title} (duplicate)`,
text: document.text,
});
- invariant(res && res.data, 'Data should be available');
+ invariant(res && res.data, "Data should be available");
const collection = this.getCollectionForDocument(document);
if (collection) collection.refresh();
@@ -439,11 +439,11 @@ export default class DocumentsStore extends BaseStore {
@action
archive = async (document: Document) => {
- const res = await client.post('/documents.archive', {
+ const res = await client.post("/documents.archive", {
id: document.id,
});
- runInAction('Document#archive', () => {
- invariant(res && res.data, 'Data should be available');
+ runInAction("Document#archive", () => {
+ invariant(res && res.data, "Data should be available");
document.updateFromJson(res.data);
this.addPolicies(res.policies);
});
@@ -454,12 +454,12 @@ export default class DocumentsStore extends BaseStore {
@action
restore = async (document: Document, revision?: Revision) => {
- const res = await client.post('/documents.restore', {
+ const res = await client.post("/documents.restore", {
id: document.id,
revisionId: revision ? revision.id : undefined,
});
- runInAction('Document#restore', () => {
- invariant(res && res.data, 'Data should be available');
+ runInAction("Document#restore", () => {
+ invariant(res && res.data, "Data should be available");
document.updateFromJson(res.data);
this.addPolicies(res.policies);
});
@@ -469,18 +469,18 @@ export default class DocumentsStore extends BaseStore {
};
pin = (document: Document) => {
- return client.post('/documents.pin', { id: document.id });
+ return client.post("/documents.pin", { id: document.id });
};
unpin = (document: Document) => {
- return client.post('/documents.unpin', { id: document.id });
+ return client.post("/documents.unpin", { id: document.id });
};
star = async (document: Document) => {
this.starredIds.set(document.id, true);
try {
- return client.post('/documents.star', { id: document.id });
+ return client.post("/documents.star", { id: document.id });
} catch (err) {
this.starredIds.set(document.id, false);
}
@@ -490,13 +490,13 @@ export default class DocumentsStore extends BaseStore {
this.starredIds.set(document.id, false);
try {
- return client.post('/documents.unstar', { id: document.id });
+ return client.post("/documents.unstar", { id: document.id });
} catch (err) {
this.starredIds.set(document.id, false);
}
};
- getByUrl = (url: string = ''): ?Document => {
+ getByUrl = (url: string = ""): ?Document => {
return find(this.orderedData, doc => url.endsWith(doc.urlId));
};
diff --git a/app/stores/EventsStore.js b/app/stores/EventsStore.js
index 0b967b23..a1ec5606 100644
--- a/app/stores/EventsStore.js
+++ b/app/stores/EventsStore.js
@@ -1,12 +1,12 @@
// @flow
-import { sortBy } from 'lodash';
-import { computed } from 'mobx';
-import BaseStore from './BaseStore';
-import RootStore from './RootStore';
-import Event from 'models/Event';
+import { sortBy } from "lodash";
+import { computed } from "mobx";
+import BaseStore from "./BaseStore";
+import RootStore from "./RootStore";
+import Event from "models/Event";
export default class EventsStore extends BaseStore {
- actions = ['list'];
+ actions = ["list"];
constructor(rootStore: RootStore) {
super(rootStore, Event);
@@ -14,6 +14,6 @@ export default class EventsStore extends BaseStore {
@computed
get orderedData(): Event[] {
- return sortBy(Array.from(this.data.values()), 'createdAt').reverse();
+ return sortBy(Array.from(this.data.values()), "createdAt").reverse();
}
}
diff --git a/app/stores/GroupMembershipsStore.js b/app/stores/GroupMembershipsStore.js
index faa3b2ed..ee3c794d 100644
--- a/app/stores/GroupMembershipsStore.js
+++ b/app/stores/GroupMembershipsStore.js
@@ -1,15 +1,15 @@
// @flow
-import invariant from 'invariant';
-import { action, runInAction } from 'mobx';
-import { filter } from 'lodash';
-import { client } from 'utils/ApiClient';
-import BaseStore from './BaseStore';
-import RootStore from './RootStore';
-import GroupMembership from 'models/GroupMembership';
-import type { PaginationParams } from 'types';
+import invariant from "invariant";
+import { action, runInAction } from "mobx";
+import { filter } from "lodash";
+import { client } from "utils/ApiClient";
+import BaseStore from "./BaseStore";
+import RootStore from "./RootStore";
+import GroupMembership from "models/GroupMembership";
+import type { PaginationParams } from "types";
export default class GroupMembershipsStore extends BaseStore {
- actions = ['create', 'delete'];
+ actions = ["create", "delete"];
constructor(rootStore: RootStore) {
super(rootStore, GroupMembership);
@@ -22,7 +22,7 @@ export default class GroupMembershipsStore extends BaseStore {
try {
const res = await client.post(`/groups.memberships`, params);
- invariant(res && res.data, 'Data not available');
+ invariant(res && res.data, "Data not available");
runInAction(`GroupMembershipsStore#fetchPage`, () => {
res.data.users.forEach(this.rootStore.users.add);
@@ -37,11 +37,11 @@ export default class GroupMembershipsStore extends BaseStore {
@action
async create({ groupId, userId }: { groupId: string, userId: string }) {
- const res = await client.post('/groups.add_user', {
+ const res = await client.post("/groups.add_user", {
id: groupId,
userId,
});
- invariant(res && res.data, 'Group Membership data should be available');
+ invariant(res && res.data, "Group Membership data should be available");
res.data.users.forEach(this.rootStore.users.add);
res.data.groups.forEach(this.rootStore.groups.add);
@@ -50,11 +50,11 @@ export default class GroupMembershipsStore extends BaseStore {
@action
async delete({ groupId, userId }: { groupId: string, userId: string }) {
- const res = await client.post('/groups.remove_user', {
+ const res = await client.post("/groups.remove_user", {
id: groupId,
userId,
});
- invariant(res && res.data, 'Group Membership data should be available');
+ invariant(res && res.data, "Group Membership data should be available");
this.remove(`${userId}-${groupId}`);
diff --git a/app/stores/GroupsStore.js b/app/stores/GroupsStore.js
index 445b336b..7d97c7e6 100644
--- a/app/stores/GroupsStore.js
+++ b/app/stores/GroupsStore.js
@@ -1,13 +1,13 @@
// @flow
-import BaseStore from './BaseStore';
-import RootStore from './RootStore';
-import naturalSort from 'shared/utils/naturalSort';
-import Group from 'models/Group';
-import { client } from 'utils/ApiClient';
-import invariant from 'invariant';
-import { filter } from 'lodash';
-import { action, runInAction, computed } from 'mobx';
-import type { PaginationParams } from 'types';
+import BaseStore from "./BaseStore";
+import RootStore from "./RootStore";
+import naturalSort from "shared/utils/naturalSort";
+import Group from "models/Group";
+import { client } from "utils/ApiClient";
+import invariant from "invariant";
+import { filter } from "lodash";
+import { action, runInAction, computed } from "mobx";
+import type { PaginationParams } from "types";
export default class GroupsStore extends BaseStore {
constructor(rootStore: RootStore) {
@@ -16,7 +16,7 @@ export default class GroupsStore extends BaseStore {
@computed
get orderedData(): Group[] {
- return naturalSort(Array.from(this.data.values()), 'name');
+ return naturalSort(Array.from(this.data.values()), "name");
}
@action
@@ -26,7 +26,7 @@ export default class GroupsStore extends BaseStore {
try {
const res = await client.post(`/groups.list`, params);
- invariant(res && res.data, 'Data not available');
+ invariant(res && res.data, "Data not available");
runInAction(`GroupsStore#fetchPage`, () => {
this.addPolicies(res.policies);
@@ -54,7 +54,7 @@ export default class GroupsStore extends BaseStore {
return queriedGroups(groups, query);
};
- notInCollection = (collectionId: string, query: string = '') => {
+ notInCollection = (collectionId: string, query: string = "") => {
const memberships = filter(
this.rootStore.collectionGroupMemberships.orderedData,
member => member.collectionId === collectionId
diff --git a/app/stores/IntegrationsStore.js b/app/stores/IntegrationsStore.js
index 5a2d4994..56d5068e 100644
--- a/app/stores/IntegrationsStore.js
+++ b/app/stores/IntegrationsStore.js
@@ -1,11 +1,11 @@
// @flow
-import { computed } from 'mobx';
-import { filter } from 'lodash';
+import { computed } from "mobx";
+import { filter } from "lodash";
-import naturalSort from 'shared/utils/naturalSort';
-import BaseStore from 'stores/BaseStore';
-import RootStore from 'stores/RootStore';
-import Integration from 'models/Integration';
+import naturalSort from "shared/utils/naturalSort";
+import BaseStore from "stores/BaseStore";
+import RootStore from "stores/RootStore";
+import Integration from "models/Integration";
class IntegrationsStore extends BaseStore {
constructor(rootStore: RootStore) {
@@ -14,12 +14,12 @@ class IntegrationsStore extends BaseStore {
@computed
get orderedData(): Integration[] {
- return naturalSort(Array.from(this.data.values()), 'name');
+ return naturalSort(Array.from(this.data.values()), "name");
}
@computed
get slackIntegrations(): Integration[] {
- return filter(this.orderedData, { service: 'slack' });
+ return filter(this.orderedData, { service: "slack" });
}
}
diff --git a/app/stores/MembershipsStore.js b/app/stores/MembershipsStore.js
index 3dfeaa82..2cb2b294 100644
--- a/app/stores/MembershipsStore.js
+++ b/app/stores/MembershipsStore.js
@@ -1,14 +1,14 @@
// @flow
-import invariant from 'invariant';
-import { action, runInAction } from 'mobx';
-import { client } from 'utils/ApiClient';
-import BaseStore from './BaseStore';
-import RootStore from './RootStore';
-import Membership from 'models/Membership';
-import type { PaginationParams } from 'types';
+import invariant from "invariant";
+import { action, runInAction } from "mobx";
+import { client } from "utils/ApiClient";
+import BaseStore from "./BaseStore";
+import RootStore from "./RootStore";
+import Membership from "models/Membership";
+import type { PaginationParams } from "types";
export default class MembershipsStore extends BaseStore {
- actions = ['create', 'delete'];
+ actions = ["create", "delete"];
constructor(rootStore: RootStore) {
super(rootStore, Membership);
@@ -21,7 +21,7 @@ export default class MembershipsStore extends BaseStore {
try {
const res = await client.post(`/collections.memberships`, params);
- invariant(res && res.data, 'Data not available');
+ invariant(res && res.data, "Data not available");
runInAction(`/collections.memberships`, () => {
res.data.users.forEach(this.rootStore.users.add);
@@ -44,12 +44,12 @@ export default class MembershipsStore extends BaseStore {
userId: string,
permission: string,
}) {
- const res = await client.post('/collections.add_user', {
+ const res = await client.post("/collections.add_user", {
id: collectionId,
userId,
permission,
});
- invariant(res && res.data, 'Membership data should be available');
+ invariant(res && res.data, "Membership data should be available");
res.data.users.forEach(this.rootStore.users.add);
res.data.memberships.forEach(this.add);
@@ -63,7 +63,7 @@ export default class MembershipsStore extends BaseStore {
collectionId: string,
userId: string,
}) {
- await client.post('/collections.remove_user', {
+ await client.post("/collections.remove_user", {
id: collectionId,
userId,
});
diff --git a/app/stores/NotificationSettingsStore.js b/app/stores/NotificationSettingsStore.js
index cf5fb35d..4640a658 100644
--- a/app/stores/NotificationSettingsStore.js
+++ b/app/stores/NotificationSettingsStore.js
@@ -1,13 +1,13 @@
// @flow
-import { find } from 'lodash';
-import NotificationSetting from 'models/NotificationSetting';
-import BaseStore from './BaseStore';
-import RootStore from './RootStore';
+import { find } from "lodash";
+import NotificationSetting from "models/NotificationSetting";
+import BaseStore from "./BaseStore";
+import RootStore from "./RootStore";
export default class NotificationSettingsStore extends BaseStore<
NotificationSetting
> {
- actions = ['list', 'create', 'delete'];
+ actions = ["list", "create", "delete"];
constructor(rootStore: RootStore) {
super(rootStore, NotificationSetting);
diff --git a/app/stores/PoliciesStore.js b/app/stores/PoliciesStore.js
index 3dc2eee6..787b387c 100644
--- a/app/stores/PoliciesStore.js
+++ b/app/stores/PoliciesStore.js
@@ -1,7 +1,7 @@
// @flow
-import BaseStore from './BaseStore';
-import RootStore from './RootStore';
-import Policy from 'models/Policy';
+import BaseStore from "./BaseStore";
+import RootStore from "./RootStore";
+import Policy from "models/Policy";
export default class PoliciesStore extends BaseStore {
actions = [];
diff --git a/app/stores/RevisionsStore.js b/app/stores/RevisionsStore.js
index 527b93b8..d1c15501 100644
--- a/app/stores/RevisionsStore.js
+++ b/app/stores/RevisionsStore.js
@@ -1,15 +1,15 @@
// @flow
-import { action, runInAction } from 'mobx';
-import { filter } from 'lodash';
-import invariant from 'invariant';
-import { client } from 'utils/ApiClient';
-import BaseStore from 'stores/BaseStore';
-import RootStore from 'stores/RootStore';
-import Revision from 'models/Revision';
-import type { FetchOptions, PaginationParams } from 'types';
+import { action, runInAction } from "mobx";
+import { filter } from "lodash";
+import invariant from "invariant";
+import { client } from "utils/ApiClient";
+import BaseStore from "stores/BaseStore";
+import RootStore from "stores/RootStore";
+import Revision from "models/Revision";
+import type { FetchOptions, PaginationParams } from "types";
export default class RevisionsStore extends BaseStore {
- actions = ['list'];
+ actions = ["list"];
constructor(rootStore: RootStore) {
super(rootStore, Revision);
@@ -22,19 +22,19 @@ export default class RevisionsStore extends BaseStore {
@action
fetch = async (id: string, options?: FetchOptions): Promise => {
this.isFetching = true;
- invariant(id, 'Id is required');
+ invariant(id, "Id is required");
try {
const rev = this.data.get(id);
if (rev) return rev;
- const res = await client.post('/revisions.info', {
+ const res = await client.post("/revisions.info", {
id,
});
- invariant(res && res.data, 'Revision not available');
+ invariant(res && res.data, "Revision not available");
this.add(res.data);
- runInAction('RevisionsStore#fetch', () => {
+ runInAction("RevisionsStore#fetch", () => {
this.isLoaded = true;
});
@@ -49,9 +49,9 @@ export default class RevisionsStore extends BaseStore {
this.isFetching = true;
try {
- const res = await client.post('/revisions.list', options);
- invariant(res && res.data, 'Document revisions not available');
- runInAction('RevisionsStore#fetchPage', () => {
+ const res = await client.post("/revisions.list", options);
+ invariant(res && res.data, "Document revisions not available");
+ runInAction("RevisionsStore#fetchPage", () => {
res.data.forEach(revision => this.add(revision));
this.isLoaded = true;
});
diff --git a/app/stores/RootStore.js b/app/stores/RootStore.js
index be84e78a..b92052bd 100644
--- a/app/stores/RootStore.js
+++ b/app/stores/RootStore.js
@@ -1,22 +1,22 @@
// @flow
-import ApiKeysStore from './ApiKeysStore';
-import AuthStore from './AuthStore';
-import CollectionsStore from './CollectionsStore';
-import DocumentsStore from './DocumentsStore';
-import EventsStore from './EventsStore';
-import GroupsStore from './GroupsStore';
-import GroupMembershipsStore from './GroupMembershipsStore';
-import IntegrationsStore from './IntegrationsStore';
-import MembershipsStore from './MembershipsStore';
-import NotificationSettingsStore from './NotificationSettingsStore';
-import DocumentPresenceStore from './DocumentPresenceStore';
-import PoliciesStore from './PoliciesStore';
-import RevisionsStore from './RevisionsStore';
-import SharesStore from './SharesStore';
-import UiStore from './UiStore';
-import UsersStore from './UsersStore';
-import ViewsStore from './ViewsStore';
-import CollectionGroupMembershipsStore from './CollectionGroupMembershipsStore';
+import ApiKeysStore from "./ApiKeysStore";
+import AuthStore from "./AuthStore";
+import CollectionsStore from "./CollectionsStore";
+import DocumentsStore from "./DocumentsStore";
+import EventsStore from "./EventsStore";
+import GroupsStore from "./GroupsStore";
+import GroupMembershipsStore from "./GroupMembershipsStore";
+import IntegrationsStore from "./IntegrationsStore";
+import MembershipsStore from "./MembershipsStore";
+import NotificationSettingsStore from "./NotificationSettingsStore";
+import DocumentPresenceStore from "./DocumentPresenceStore";
+import PoliciesStore from "./PoliciesStore";
+import RevisionsStore from "./RevisionsStore";
+import SharesStore from "./SharesStore";
+import UiStore from "./UiStore";
+import UsersStore from "./UsersStore";
+import ViewsStore from "./ViewsStore";
+import CollectionGroupMembershipsStore from "./CollectionGroupMembershipsStore";
export default class RootStore {
apiKeys: ApiKeysStore;
diff --git a/app/stores/SharesStore.js b/app/stores/SharesStore.js
index d1b04056..133a392d 100644
--- a/app/stores/SharesStore.js
+++ b/app/stores/SharesStore.js
@@ -1,13 +1,13 @@
// @flow
-import { sortBy } from 'lodash';
-import { action, computed } from 'mobx';
-import { client } from 'utils/ApiClient';
-import BaseStore from './BaseStore';
-import RootStore from './RootStore';
-import Share from 'models/Share';
+import { sortBy } from "lodash";
+import { action, computed } from "mobx";
+import { client } from "utils/ApiClient";
+import BaseStore from "./BaseStore";
+import RootStore from "./RootStore";
+import Share from "models/Share";
export default class SharesStore extends BaseStore {
- actions = ['list', 'create'];
+ actions = ["list", "create"];
constructor(rootStore: RootStore) {
super(rootStore, Share);
@@ -15,12 +15,12 @@ export default class SharesStore extends BaseStore {
@computed
get orderedData(): Share[] {
- return sortBy(Array.from(this.data.values()), 'createdAt').reverse();
+ return sortBy(Array.from(this.data.values()), "createdAt").reverse();
}
@action
revoke = async (share: Share) => {
- await client.post('/shares.revoke', { id: share.id });
+ await client.post("/shares.revoke", { id: share.id });
this.remove(share.id);
};
}
diff --git a/app/stores/UiStore.js b/app/stores/UiStore.js
index f28bda62..07fb96e3 100644
--- a/app/stores/UiStore.js
+++ b/app/stores/UiStore.js
@@ -1,19 +1,19 @@
// @flow
-import { v4 } from 'uuid';
-import { orderBy } from 'lodash';
-import { observable, action, autorun, computed } from 'mobx';
-import Document from 'models/Document';
-import Collection from 'models/Collection';
-import type { Toast } from '../types';
+import { v4 } from "uuid";
+import { orderBy } from "lodash";
+import { observable, action, autorun, computed } from "mobx";
+import Document from "models/Document";
+import Collection from "models/Collection";
+import type { Toast } from "../types";
-const UI_STORE = 'UI_STORE';
+const UI_STORE = "UI_STORE";
class UiStore {
// theme represents the users UI preference (defaults to system)
- @observable theme: 'light' | 'dark' | 'system';
+ @observable theme: "light" | "dark" | "system";
// systemTheme represents the system UI theme (Settings -> General in macOS)
- @observable systemTheme: 'light' | 'dark';
+ @observable systemTheme: "light" | "dark";
@observable activeModalName: ?string;
@observable activeModalProps: ?Object;
@observable activeDocumentId: ?string;
@@ -28,18 +28,18 @@ class UiStore {
// Rehydrate
let data = {};
try {
- data = JSON.parse(localStorage.getItem(UI_STORE) || '{}');
+ data = JSON.parse(localStorage.getItem(UI_STORE) || "{}");
} catch (_) {
// no-op Safari private mode
}
// system theme listeners
const colorSchemeQueryList = window.matchMedia(
- '(prefers-color-scheme: dark)'
+ "(prefers-color-scheme: dark)"
);
const setSystemTheme = event => {
- this.systemTheme = event.matches ? 'dark' : 'light';
+ this.systemTheme = event.matches ? "dark" : "light";
};
setSystemTheme(colorSchemeQueryList);
if (colorSchemeQueryList.addListener) {
@@ -48,7 +48,7 @@ class UiStore {
// persisted keys
this.tocVisible = data.tocVisible;
- this.theme = data.theme || 'system';
+ this.theme = data.theme || "system";
autorun(() => {
try {
@@ -60,11 +60,11 @@ class UiStore {
}
@action
- setTheme = (theme: 'light' | 'dark' | 'system') => {
+ setTheme = (theme: "light" | "dark" | "system") => {
this.theme = theme;
if (window.localStorage) {
- window.localStorage.setItem('theme', this.theme);
+ window.localStorage.setItem("theme", this.theme);
}
};
@@ -149,7 +149,7 @@ class UiStore {
showToast = (
message: string,
options?: {
- type?: 'warning' | 'error' | 'info' | 'success',
+ type?: "warning" | "error" | "info" | "success",
timeout?: number,
action?: {
text: string,
@@ -171,8 +171,8 @@ class UiStore {
};
@computed
- get resolvedTheme(): 'dark' | 'light' {
- if (this.theme === 'system') {
+ get resolvedTheme(): "dark" | "light" {
+ if (this.theme === "system") {
return this.systemTheme;
}
@@ -181,7 +181,7 @@ class UiStore {
@computed
get orderedToasts(): Toast[] {
- return orderBy(Array.from(this.toasts.values()), 'createdAt', 'desc');
+ return orderBy(Array.from(this.toasts.values()), "createdAt", "desc");
}
@computed
diff --git a/app/stores/UsersStore.js b/app/stores/UsersStore.js
index 0ba77f76..c9790088 100644
--- a/app/stores/UsersStore.js
+++ b/app/stores/UsersStore.js
@@ -1,11 +1,11 @@
// @flow
-import { filter, orderBy } from 'lodash';
-import { computed, action, runInAction } from 'mobx';
-import invariant from 'invariant';
-import { client } from 'utils/ApiClient';
-import BaseStore from './BaseStore';
-import RootStore from './RootStore';
-import User from 'models/User';
+import { filter, orderBy } from "lodash";
+import { computed, action, runInAction } from "mobx";
+import invariant from "invariant";
+import { client } from "utils/ApiClient";
+import BaseStore from "./BaseStore";
+import RootStore from "./RootStore";
+import User from "models/User";
export default class UsersStore extends BaseStore {
constructor(rootStore: RootStore) {
@@ -47,40 +47,40 @@ export default class UsersStore extends BaseStore {
@computed
get orderedData(): User[] {
- return orderBy(Array.from(this.data.values()), 'name', 'asc');
+ return orderBy(Array.from(this.data.values()), "name", "asc");
}
@action
promote = (user: User) => {
- return this.actionOnUser('promote', user);
+ return this.actionOnUser("promote", user);
};
@action
demote = (user: User) => {
- return this.actionOnUser('demote', user);
+ return this.actionOnUser("demote", user);
};
@action
suspend = (user: User) => {
- return this.actionOnUser('suspend', user);
+ return this.actionOnUser("suspend", user);
};
@action
activate = (user: User) => {
- return this.actionOnUser('activate', user);
+ return this.actionOnUser("activate", user);
};
@action
invite = async (invites: { email: string, name: string }[]) => {
const res = await client.post(`/users.invite`, { invites });
- invariant(res && res.data, 'Data should be available');
+ invariant(res && res.data, "Data should be available");
runInAction(`invite`, () => {
res.data.users.forEach(this.add);
});
return res.data;
};
- notInCollection = (collectionId: string, query: string = '') => {
+ notInCollection = (collectionId: string, query: string = "") => {
const memberships = filter(
this.rootStore.memberships.orderedData,
member => member.collectionId === collectionId
@@ -109,7 +109,7 @@ export default class UsersStore extends BaseStore {
return queriedUsers(users, query);
};
- notInGroup = (groupId: string, query: string = '') => {
+ notInGroup = (groupId: string, query: string = "") => {
const memberships = filter(
this.rootStore.groupMemberships.orderedData,
member => member.groupId === groupId
@@ -142,7 +142,7 @@ export default class UsersStore extends BaseStore {
const res = await client.post(`/users.${action}`, {
id: user.id,
});
- invariant(res && res.data, 'Data should be available');
+ invariant(res && res.data, "Data should be available");
runInAction(`UsersStore#${action}`, () => {
this.addPolicies(res.policies);
diff --git a/app/stores/ViewsStore.js b/app/stores/ViewsStore.js
index 7cd03e7d..990f864f 100644
--- a/app/stores/ViewsStore.js
+++ b/app/stores/ViewsStore.js
@@ -1,11 +1,11 @@
// @flow
-import { reduce, filter, find, orderBy } from 'lodash';
-import BaseStore from './BaseStore';
-import RootStore from './RootStore';
-import View from 'models/View';
+import { reduce, filter, find, orderBy } from "lodash";
+import BaseStore from "./BaseStore";
+import RootStore from "./RootStore";
+import View from "models/View";
export default class ViewsStore extends BaseStore {
- actions = ['list', 'create'];
+ actions = ["list", "create"];
constructor(rootStore: RootStore) {
super(rootStore, View);
@@ -14,8 +14,8 @@ export default class ViewsStore extends BaseStore {
inDocument(documentId: string): View[] {
return orderBy(
filter(this.orderedData, view => view.documentId === documentId),
- 'lastViewedAt',
- 'desc'
+ "lastViewedAt",
+ "desc"
);
}
diff --git a/app/stores/index.js b/app/stores/index.js
index 052b30ac..bd27266d 100644
--- a/app/stores/index.js
+++ b/app/stores/index.js
@@ -1,5 +1,5 @@
// @flow
-import RootStore from 'stores/RootStore';
+import RootStore from "stores/RootStore";
const stores = new RootStore();
diff --git a/app/types/index.js b/app/types/index.js
index e9b9d89d..4fa9c1af 100644
--- a/app/types/index.js
+++ b/app/types/index.js
@@ -1,11 +1,11 @@
// @flow
-import Document from 'models/Document';
+import Document from "models/Document";
export type Toast = {
id: string,
createdAt: string,
message: string,
- type: 'warning' | 'error' | 'info' | 'success',
+ type: "warning" | "error" | "info" | "success",
timeout?: number,
action?: {
text: string,
@@ -39,7 +39,7 @@ export type PaginationParams = {
limit?: number,
offset?: number,
sort?: string,
- direction?: 'ASC' | 'DESC',
+ direction?: "ASC" | "DESC",
};
export type SearchResult = {
diff --git a/app/utils/ApiClient.js b/app/utils/ApiClient.js
index 57bc59df..b156ec14 100644
--- a/app/utils/ApiClient.js
+++ b/app/utils/ApiClient.js
@@ -1,9 +1,9 @@
// @flow
-import pkg from 'rich-markdown-editor/package.json';
-import { map, trim } from 'lodash';
-import invariant from 'invariant';
-import stores from 'stores';
-import download from './download';
+import pkg from "rich-markdown-editor/package.json";
+import { map, trim } from "lodash";
+import invariant from "invariant";
+import stores from "stores";
+import download from "./download";
import {
AuthorizationError,
NetworkError,
@@ -11,7 +11,7 @@ import {
OfflineError,
RequestError,
UpdateRequiredError,
-} from './errors';
+} from "./errors";
type Options = {
baseUrl?: string,
@@ -22,8 +22,8 @@ class ApiClient {
userAgent: string;
constructor(options: Options = {}) {
- this.baseUrl = options.baseUrl || '/api';
- this.userAgent = 'OutlineFrontend';
+ this.baseUrl = options.baseUrl || "/api";
+ this.userAgent = "OutlineFrontend";
}
fetch = async (
@@ -35,27 +35,27 @@ class ApiClient {
let body;
let modifiedPath;
- if (method === 'GET') {
+ if (method === "GET") {
if (data) {
modifiedPath = `${path}?${data && this.constructQueryString(data)}`;
} else {
modifiedPath = path;
}
- } else if (method === 'POST' || method === 'PUT') {
+ } else if (method === "POST" || method === "PUT") {
body = data ? JSON.stringify(data) : undefined;
}
// Construct headers
const headers = new Headers({
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'cache-control': 'no-cache',
- 'x-editor-version': pkg.version,
- pragma: 'no-cache',
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ "cache-control": "no-cache",
+ "x-editor-version": pkg.version,
+ pragma: "no-cache",
});
if (stores.auth.authenticated) {
- invariant(stores.auth.token, 'JWT token not set properly');
- headers.set('Authorization', `Bearer ${stores.auth.token}`);
+ invariant(stores.auth.token, "JWT token not set properly");
+ headers.set("Authorization", `Bearer ${stores.auth.token}`);
}
let response;
@@ -64,15 +64,15 @@ class ApiClient {
method,
body,
headers,
- redirect: 'follow',
- credentials: 'omit',
- cache: 'no-cache',
+ redirect: "follow",
+ credentials: "omit",
+ cache: "no-cache",
});
} catch (err) {
if (window.navigator.onLine) {
- throw new NetworkError('A network error occurred, try again?');
+ throw new NetworkError("A network error occurred, try again?");
} else {
- throw new OfflineError('No internet connection available');
+ throw new OfflineError("No internet connection available");
}
}
@@ -81,8 +81,8 @@ class ApiClient {
if (options.download && success) {
const blob = await response.blob();
const fileName = (
- response.headers.get('content-disposition') || ''
- ).split('filename=')[1];
+ response.headers.get("content-disposition") || ""
+ ).split("filename=")[1];
download(blob, trim(fileName, '"'));
return;
@@ -103,14 +103,14 @@ class ApiClient {
try {
const parsed = await response.json();
- error.message = parsed.message || '';
+ error.message = parsed.message || "";
error.error = parsed.error;
error.data = parsed.data;
} catch (_err) {
// we're trying to parse an error so JSON may not be valid
}
- if (response.status === 400 && error.error === 'editor_update_required') {
+ if (response.status === 400 && error.error === "editor_update_required") {
window.location.reload(true);
throw new UpdateRequiredError(error.message);
}
@@ -127,11 +127,11 @@ class ApiClient {
};
get = (path: string, data: ?Object, options?: Object) => {
- return this.fetch(path, 'GET', data, options);
+ return this.fetch(path, "GET", data, options);
};
post = (path: string, data: ?Object, options?: Object) => {
- return this.fetch(path, 'POST', data, options);
+ return this.fetch(path, "POST", data, options);
};
// Helpers
@@ -139,7 +139,7 @@ class ApiClient {
return map(
data,
(v, k) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`
- ).join('&');
+ ).join("&");
};
}
diff --git a/app/utils/download.js b/app/utils/download.js
index 46caf7e8..709c4a4d 100644
--- a/app/utils/download.js
+++ b/app/utils/download.js
@@ -14,22 +14,22 @@ export default function download(
strMimeType?: string
) {
var self = window, // this script is only for browsers anyway...
- u = 'application/octet-stream', // this default mime also triggers iframe downloads
+ u = "application/octet-stream", // this default mime also triggers iframe downloads
m = strMimeType || u,
x = data,
D = document,
- a = D.createElement('a'),
+ a = D.createElement("a"),
z = function(a, o) {
return String(a);
},
B = self.Blob || self.MozBlob || self.WebKitBlob || z,
BB = self.MSBlobBuilder || self.WebKitBlobBuilder || self.BlobBuilder,
- fn = strFileName || 'download',
+ fn = strFileName || "download",
blob,
b,
fr;
- if (String(this) === 'true') {
+ if (String(this) === "true") {
//reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
x = [x, m];
m = x[0];
@@ -56,12 +56,12 @@ export default function download(
}
function d2b(u) {
- if (typeof u !== 'string') {
- throw Error('Attempted to pass non-string to d2b');
+ if (typeof u !== "string") {
+ throw Error("Attempted to pass non-string to d2b");
}
var p = u.split(/[:;,]/),
t = p[1],
- dec = p[2] === 'base64' ? atob : decodeURIComponent,
+ dec = p[2] === "base64" ? atob : decodeURIComponent,
bin = dec(p.pop()),
mx = bin.length,
i = 0,
@@ -73,14 +73,14 @@ export default function download(
}
function saver(url, winMode) {
- if (typeof url !== 'string') {
- throw Error('Attempted to pass non-string url to saver');
+ if (typeof url !== "string") {
+ throw Error("Attempted to pass non-string url to saver");
}
- if ('download' in a) {
+ if ("download" in a) {
a.href = url;
- a.setAttribute('download', fn);
- a.innerHTML = 'downloading…';
+ a.setAttribute("download", fn);
+ a.innerHTML = "downloading…";
D.body && D.body.appendChild(a);
setTimeout(function() {
a.click();
@@ -95,11 +95,11 @@ export default function download(
}
//do iframe dataURL download (old ch+FF):
- var f = D.createElement('iframe');
+ var f = D.createElement("iframe");
D.body && D.body.appendChild(f);
if (!winMode) {
// force a mime that will download:
- url = 'data:' + url.replace(/^data:([\w\/\-\+]+)/, u);
+ url = "data:" + url.replace(/^data:([\w\/\-\+]+)/, u);
}
f.src = url;
@@ -121,14 +121,14 @@ export default function download(
// handle non-Blob()+non-URL browsers:
if (
blob &&
- (typeof blob === 'string' || blob.constructor === z) &&
- typeof m === 'string'
+ (typeof blob === "string" || blob.constructor === z) &&
+ typeof m === "string"
) {
try {
- return saver('data:' + m + ';base64,' + self.btoa(blob));
+ return saver("data:" + m + ";base64," + self.btoa(blob));
} catch (y) {
// $FlowIssue
- return saver('data:' + m + ',' + encodeURIComponent(blob));
+ return saver("data:" + m + "," + encodeURIComponent(blob));
}
}
diff --git a/app/utils/emoji.js b/app/utils/emoji.js
index 342e8af9..f9c9528d 100644
--- a/app/utils/emoji.js
+++ b/app/utils/emoji.js
@@ -15,7 +15,7 @@ export function toCodePoint(unicodeSurrogates: string, sep: ?string) {
r.push(c.toString(16));
}
}
- return r.join(sep || '-');
+ return r.join(sep || "-");
}
export function emojiToUrl(text: string) {
diff --git a/app/utils/errors.js b/app/utils/errors.js
index da15cf3c..662a57c7 100644
--- a/app/utils/errors.js
+++ b/app/utils/errors.js
@@ -1,5 +1,5 @@
// @flow
-import ExtendableError from 'es6-error';
+import ExtendableError from "es6-error";
export class AuthorizationError extends ExtendableError {}
export class NetworkError extends ExtendableError {}
diff --git a/app/utils/importFile.js b/app/utils/importFile.js
index 9afff572..49f4e62f 100644
--- a/app/utils/importFile.js
+++ b/app/utils/importFile.js
@@ -1,7 +1,7 @@
// @flow
-import Document from 'models/Document';
-import DocumentsStore from 'stores/DocumentsStore';
-import parseTitle from 'shared/utils/parseTitle';
+import Document from "models/Document";
+import DocumentsStore from "stores/DocumentsStore";
+import parseTitle from "shared/utils/parseTitle";
type Options = {
file: File,
@@ -25,14 +25,14 @@ const importFile = async ({
// If the first line of the imported file looks like a markdown heading
// then we can use this as the document title
- if (text.trim().startsWith('# ')) {
+ if (text.trim().startsWith("# ")) {
const result = parseTitle(text);
title = result.title;
- text = text.replace(`# ${title}\n`, '');
+ text = text.replace(`# ${title}\n`, "");
// otherwise, just use the filename without the extension as our best guess
} else {
- title = file.name.replace(/\.[^/.]+$/, '');
+ title = file.name.replace(/\.[^/.]+$/, "");
}
let document = new Document(
diff --git a/app/utils/isInternalUrl.js b/app/utils/isInternalUrl.js
index 2177d09c..8f0f59e4 100644
--- a/app/utils/isInternalUrl.js
+++ b/app/utils/isInternalUrl.js
@@ -1,8 +1,8 @@
// @flow
-import { parseDomain } from '../../shared/utils/domains';
+import { parseDomain } from "../../shared/utils/domains";
export default function isInternalUrl(href: string) {
- if (href[0] === '/') return true;
+ if (href[0] === "/") return true;
const outline = parseDomain(window.location.href);
const parsed = parseDomain(href);
diff --git a/app/utils/keyboard.js b/app/utils/keyboard.js
index 3b09fe0d..833738a9 100644
--- a/app/utils/keyboard.js
+++ b/app/utils/keyboard.js
@@ -1,3 +1,3 @@
// @flow
-export const meta = window.navigator.platform === 'MacIntel' ? '⌘' : 'Ctrl';
+export const meta = window.navigator.platform === "MacIntel" ? "⌘" : "Ctrl";
diff --git a/app/utils/routeHelpers.js b/app/utils/routeHelpers.js
index 0cbdf8ae..fe9d2b68 100644
--- a/app/utils/routeHelpers.js
+++ b/app/utils/routeHelpers.js
@@ -1,16 +1,16 @@
// @flow
-import Document from 'models/Document';
+import Document from "models/Document";
export function homeUrl(): string {
- return '/home';
+ return "/home";
}
export function starredUrl(): string {
- return '/starred';
+ return "/starred";
}
export function newCollectionUrl(): string {
- return '/collections/new';
+ return "/collections/new";
}
export function collectionUrl(collectionId: string, section: ?string): string {
@@ -43,10 +43,10 @@ export function documentHistoryUrl(doc: Document, revisionId?: string): string {
*/
export function updateDocumentUrl(oldUrl: string, newUrl: string): string {
// Update url to match the current one
- const urlParts = oldUrl.trim().split('/');
+ const urlParts = oldUrl.trim().split("/");
const actions = urlParts.slice(3);
if (actions[0]) {
- return [newUrl, actions].join('/');
+ return [newUrl, actions].join("/");
}
return newUrl;
}
@@ -65,7 +65,7 @@ export function newDocumentUrl(
}
export function searchUrl(query?: string, collectionId?: string): string {
- let route = '/search';
+ let route = "/search";
if (query) route += `/${encodeURIComponent(query)}`;
if (collectionId) {
@@ -75,10 +75,10 @@ export function searchUrl(query?: string, collectionId?: string): string {
}
export function notFoundUrl(): string {
- return '/404';
+ return "/404";
}
export const matchDocumentSlug =
- ':documentSlug([0-9a-zA-Z-_~]*-[a-zA-z0-9]{10,15})';
+ ":documentSlug([0-9a-zA-Z-_~]*-[a-zA-z0-9]{10,15})";
export const matchDocumentEdit = `/doc/${matchDocumentSlug}/edit`;
diff --git a/app/utils/uploadFile.js b/app/utils/uploadFile.js
index 46bbb288..f8cffc10 100644
--- a/app/utils/uploadFile.js
+++ b/app/utils/uploadFile.js
@@ -1,6 +1,6 @@
// @flow
-import { client } from './ApiClient';
-import invariant from 'invariant';
+import { client } from "./ApiClient";
+import invariant from "invariant";
type Options = {
name?: string,
@@ -10,10 +10,10 @@ type Options = {
export const uploadFile = async (
file: File | Blob,
- options?: Options = { name: '' }
+ options?: Options = { name: "" }
) => {
const name = file instanceof File ? file.name : options.name;
- const response = await client.post('/attachments.create', {
+ const response = await client.post("/attachments.create", {
public: options.public,
documentId: options.documentId,
contentType: file.type,
@@ -21,7 +21,7 @@ export const uploadFile = async (
name,
});
- invariant(response, 'Response should be available');
+ invariant(response, "Response should be available");
const data = response.data;
const attachment = data.attachment;
@@ -34,13 +34,13 @@ export const uploadFile = async (
// $FlowFixMe
if (file.blob) {
// $FlowFixMe
- formData.append('file', file.file);
+ formData.append("file", file.file);
} else {
- formData.append('file', file);
+ formData.append("file", file);
}
await fetch(data.uploadUrl, {
- method: 'post',
+ method: "post",
body: formData,
});
@@ -48,11 +48,11 @@ export const uploadFile = async (
};
export const dataUrlToBlob = (dataURL: string) => {
- var blobBin = atob(dataURL.split(',')[1]);
+ var blobBin = atob(dataURL.split(",")[1]);
var array = [];
for (var i = 0; i < blobBin.length; i++) {
array.push(blobBin.charCodeAt(i));
}
- const file = new Blob([new Uint8Array(array)], { type: 'image/png' });
+ const file = new Blob([new Uint8Array(array)], { type: "image/png" });
return file;
};
diff --git a/server/api/apiKeys.js b/server/api/apiKeys.js
index bf5ab8cb..bbff4c8e 100644
--- a/server/api/apiKeys.js
+++ b/server/api/apiKeys.js
@@ -1,21 +1,21 @@
// @flow
-import Router from 'koa-router';
+import Router from "koa-router";
-import auth from '../middlewares/authentication';
-import pagination from './middlewares/pagination';
-import { presentApiKey } from '../presenters';
-import { ApiKey, Event } from '../models';
-import policy from '../policies';
+import auth from "../middlewares/authentication";
+import pagination from "./middlewares/pagination";
+import { presentApiKey } from "../presenters";
+import { ApiKey, Event } from "../models";
+import policy from "../policies";
const { authorize } = policy;
const router = new Router();
-router.post('apiKeys.create', auth(), async ctx => {
+router.post("apiKeys.create", auth(), async ctx => {
const { name } = ctx.body;
- ctx.assertPresent(name, 'name is required');
+ ctx.assertPresent(name, "name is required");
const user = ctx.state.user;
- authorize(user, 'create', ApiKey);
+ authorize(user, "create", ApiKey);
const key = await ApiKey.create({
name,
@@ -23,7 +23,7 @@ router.post('apiKeys.create', auth(), async ctx => {
});
await Event.create({
- name: 'api_keys.create',
+ name: "api_keys.create",
modelId: key.id,
teamId: user.teamId,
actorId: user.id,
@@ -36,13 +36,13 @@ router.post('apiKeys.create', auth(), async ctx => {
};
});
-router.post('apiKeys.list', auth(), pagination(), async ctx => {
+router.post("apiKeys.list", auth(), pagination(), async ctx => {
const user = ctx.state.user;
const keys = await ApiKey.findAll({
where: {
userId: user.id,
},
- order: [['createdAt', 'DESC']],
+ order: [["createdAt", "DESC"]],
offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit,
});
@@ -53,18 +53,18 @@ router.post('apiKeys.list', auth(), pagination(), async ctx => {
};
});
-router.post('apiKeys.delete', auth(), async ctx => {
+router.post("apiKeys.delete", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const key = await ApiKey.findByPk(id);
- authorize(user, 'delete', key);
+ authorize(user, "delete", key);
await key.destroy();
await Event.create({
- name: 'api_keys.delete',
+ name: "api_keys.delete",
modelId: key.id,
teamId: user.teamId,
actorId: user.id,
diff --git a/server/api/attachments.js b/server/api/attachments.js
index 8134effa..98722bc3 100644
--- a/server/api/attachments.js
+++ b/server/api/attachments.js
@@ -1,29 +1,29 @@
// @flow
-import Router from 'koa-router';
-import uuid from 'uuid';
-import format from 'date-fns/format';
-import { Attachment, Document, Event } from '../models';
+import Router from "koa-router";
+import uuid from "uuid";
+import format from "date-fns/format";
+import { Attachment, Document, Event } from "../models";
import {
makePolicy,
getSignature,
publicS3Endpoint,
makeCredential,
getSignedImageUrl,
-} from '../utils/s3';
-import auth from '../middlewares/authentication';
-import { NotFoundError } from '../errors';
-import policy from '../policies';
+} from "../utils/s3";
+import auth from "../middlewares/authentication";
+import { NotFoundError } from "../errors";
+import policy from "../policies";
const { authorize } = policy;
const router = new Router();
-const AWS_S3_ACL = process.env.AWS_S3_ACL || 'private';
+const AWS_S3_ACL = process.env.AWS_S3_ACL || "private";
-router.post('attachments.create', auth(), async ctx => {
+router.post("attachments.create", auth(), async ctx => {
let { name, documentId, contentType, size } = ctx.body;
- ctx.assertPresent(name, 'name is required');
- ctx.assertPresent(contentType, 'contentType is required');
- ctx.assertPresent(size, 'size is required');
+ ctx.assertPresent(name, "name is required");
+ ctx.assertPresent(contentType, "contentType is required");
+ ctx.assertPresent(size, "size is required");
const { user } = ctx.state;
const s3Key = uuid.v4();
@@ -31,16 +31,16 @@ router.post('attachments.create', auth(), async ctx => {
const acl =
ctx.body.public === undefined
? AWS_S3_ACL
- : ctx.body.public ? 'public-read' : 'private';
+ : ctx.body.public ? "public-read" : "private";
const credential = makeCredential();
- const longDate = format(new Date(), 'YYYYMMDDTHHmmss\\Z');
+ const longDate = format(new Date(), "YYYYMMDDTHHmmss\\Z");
const policy = makePolicy(credential, longDate, acl);
const endpoint = publicS3Endpoint();
const url = `${endpoint}/${key}`;
if (documentId) {
const document = await Document.findByPk(documentId, { userId: user.id });
- authorize(user, 'update', document);
+ authorize(user, "update", document);
}
const attachment = await Attachment.create({
@@ -55,7 +55,7 @@ router.post('attachments.create', auth(), async ctx => {
});
await Event.create({
- name: 'attachments.create',
+ name: "attachments.create",
data: { name },
teamId: user.teamId,
userId: user.id,
@@ -67,15 +67,15 @@ router.post('attachments.create', auth(), async ctx => {
maxUploadSize: process.env.AWS_S3_UPLOAD_MAX_SIZE,
uploadUrl: endpoint,
form: {
- 'Cache-Control': 'max-age=31557600',
- 'Content-Type': contentType,
+ "Cache-Control": "max-age=31557600",
+ "Content-Type": contentType,
acl,
key,
policy,
- 'x-amz-algorithm': 'AWS4-HMAC-SHA256',
- 'x-amz-credential': credential,
- 'x-amz-date': longDate,
- 'x-amz-signature': getSignature(policy),
+ "x-amz-algorithm": "AWS4-HMAC-SHA256",
+ "x-amz-credential": credential,
+ "x-amz-date": longDate,
+ "x-amz-signature": getSignature(policy),
},
attachment: {
documentId,
@@ -88,9 +88,9 @@ router.post('attachments.create', auth(), async ctx => {
};
});
-router.post('attachments.redirect', auth(), async ctx => {
+router.post("attachments.redirect", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertPresent(id, 'id is required');
+ ctx.assertPresent(id, "id is required");
const user = ctx.state.user;
const attachment = await Attachment.findByPk(id);
@@ -103,7 +103,7 @@ router.post('attachments.redirect', auth(), async ctx => {
const document = await Document.findByPk(attachment.documentId, {
userId: user.id,
});
- authorize(user, 'read', document);
+ authorize(user, "read", document);
}
const accessUrl = await getSignedImageUrl(attachment.key);
diff --git a/server/api/attachments.test.js b/server/api/attachments.test.js
index 7ed1919c..b885013e 100644
--- a/server/api/attachments.test.js
+++ b/server/api/attachments.test.js
@@ -1,40 +1,40 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../app';
-import { flushdb } from '../test/support';
+import TestServer from "fetch-test-server";
+import app from "../app";
+import { flushdb } from "../test/support";
import {
buildUser,
buildCollection,
buildAttachment,
buildDocument,
-} from '../test/factories';
+} from "../test/factories";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('#attachments.redirect', async () => {
- it('should require authentication', async () => {
- const res = await server.post('/api/attachments.redirect');
+describe("#attachments.redirect", async () => {
+ it("should require authentication", async () => {
+ const res = await server.post("/api/attachments.redirect");
expect(res.status).toEqual(401);
});
- it('should return a redirect for an attachment belonging to a document user has access to', async () => {
+ it("should return a redirect for an attachment belonging to a document user has access to", async () => {
const user = await buildUser();
const attachment = await buildAttachment({
teamId: user.teamId,
userId: user.id,
});
- const res = await server.post('/api/attachments.redirect', {
+ const res = await server.post("/api/attachments.redirect", {
body: { token: user.getJwtToken(), id: attachment.id },
- redirect: 'manual',
+ redirect: "manual",
});
expect(res.status).toEqual(302);
});
- it('should always return a redirect for a public attachment', async () => {
+ it("should always return a redirect for a public attachment", async () => {
const user = await buildUser();
const collection = await buildCollection({
teamId: user.teamId,
@@ -52,15 +52,15 @@ describe('#attachments.redirect', async () => {
documentId: document.id,
});
- const res = await server.post('/api/attachments.redirect', {
+ const res = await server.post("/api/attachments.redirect", {
body: { token: user.getJwtToken(), id: attachment.id },
- redirect: 'manual',
+ redirect: "manual",
});
expect(res.status).toEqual(302);
});
- it('should not return a redirect for a private attachment belonging to a document user does not have access to', async () => {
+ it("should not return a redirect for a private attachment belonging to a document user does not have access to", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
@@ -74,10 +74,10 @@ describe('#attachments.redirect', async () => {
teamId: document.teamId,
userId: document.userId,
documentId: document.id,
- acl: 'private',
+ acl: "private",
});
- const res = await server.post('/api/attachments.redirect', {
+ const res = await server.post("/api/attachments.redirect", {
body: { token: user.getJwtToken(), id: attachment.id },
});
diff --git a/server/api/auth.js b/server/api/auth.js
index 2b886dc1..f168bc03 100644
--- a/server/api/auth.js
+++ b/server/api/auth.js
@@ -1,12 +1,12 @@
// @flow
-import Router from 'koa-router';
-import auth from '../middlewares/authentication';
-import { presentUser, presentTeam, presentPolicies } from '../presenters';
-import { Team } from '../models';
+import Router from "koa-router";
+import auth from "../middlewares/authentication";
+import { presentUser, presentTeam, presentPolicies } from "../presenters";
+import { Team } from "../models";
const router = new Router();
-router.post('auth.info', auth(), async ctx => {
+router.post("auth.info", auth(), async ctx => {
const user = ctx.state.user;
const team = await Team.findByPk(user.teamId);
diff --git a/server/api/collections.js b/server/api/collections.js
index 07555ce5..5da4c65f 100644
--- a/server/api/collections.js
+++ b/server/api/collections.js
@@ -1,9 +1,9 @@
// @flow
-import fs from 'fs';
-import Router from 'koa-router';
-import { Op } from '../sequelize';
-import auth from '../middlewares/authentication';
-import pagination from './middlewares/pagination';
+import fs from "fs";
+import Router from "koa-router";
+import { Op } from "../sequelize";
+import auth from "../middlewares/authentication";
+import pagination from "./middlewares/pagination";
import {
presentCollection,
presentUser,
@@ -11,7 +11,7 @@ import {
presentMembership,
presentGroup,
presentCollectionGroupMembership,
-} from '../presenters';
+} from "../presenters";
import {
Collection,
CollectionUser,
@@ -20,40 +20,40 @@ import {
Event,
User,
Group,
-} from '../models';
-import { ValidationError } from '../errors';
-import { exportCollections } from '../logistics';
-import { archiveCollection, archiveCollections } from '../utils/zip';
-import policy from '../policies';
+} from "../models";
+import { ValidationError } from "../errors";
+import { exportCollections } from "../logistics";
+import { archiveCollection, archiveCollections } from "../utils/zip";
+import policy from "../policies";
const { authorize } = policy;
const router = new Router();
-router.post('collections.create', auth(), async ctx => {
+router.post("collections.create", auth(), async ctx => {
const { name, color, description, icon, type } = ctx.body;
const isPrivate = ctx.body.private;
- ctx.assertPresent(name, 'name is required');
+ ctx.assertPresent(name, "name is required");
if (color) {
- ctx.assertHexColor(color, 'Invalid hex value (please use format #FFFFFF)');
+ ctx.assertHexColor(color, "Invalid hex value (please use format #FFFFFF)");
}
const user = ctx.state.user;
- authorize(user, 'create', Collection);
+ authorize(user, "create", Collection);
let collection = await Collection.create({
name,
description,
icon,
color,
- type: type || 'atlas',
+ type: type || "atlas",
teamId: user.teamId,
creatorId: user.id,
private: isPrivate,
});
await Event.create({
- name: 'collections.create',
+ name: "collections.create",
collectionId: collection.id,
teamId: collection.teamId,
actorId: user.id,
@@ -64,7 +64,7 @@ router.post('collections.create', auth(), async ctx => {
// we must reload the collection to get memberships for policy presenter
if (isPrivate) {
collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findByPk(collection.id);
}
@@ -74,15 +74,15 @@ router.post('collections.create', auth(), async ctx => {
};
});
-router.post('collections.info', auth(), async ctx => {
+router.post("collections.info", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findByPk(id);
- authorize(user, 'read', collection);
+ authorize(user, "read", collection);
ctx.body = {
data: presentCollection(collection),
@@ -90,18 +90,18 @@ router.post('collections.info', auth(), async ctx => {
};
});
-router.post('collections.add_group', auth(), async ctx => {
- const { id, groupId, permission = 'read_write' } = ctx.body;
- ctx.assertUuid(id, 'id is required');
- ctx.assertUuid(groupId, 'groupId is required');
+router.post("collections.add_group", auth(), async ctx => {
+ const { id, groupId, permission = "read_write" } = ctx.body;
+ ctx.assertUuid(id, "id is required");
+ ctx.assertUuid(groupId, "groupId is required");
const collection = await Collection.scope({
- method: ['withMembership', ctx.state.user.id],
+ method: ["withMembership", ctx.state.user.id],
}).findByPk(id);
- authorize(ctx.state.user, 'update', collection);
+ authorize(ctx.state.user, "update", collection);
const group = await Group.findByPk(groupId);
- authorize(ctx.state.user, 'read', group);
+ authorize(ctx.state.user, "read", group);
let membership = await CollectionGroup.findOne({
where: {
@@ -123,7 +123,7 @@ router.post('collections.add_group', auth(), async ctx => {
}
await Event.create({
- name: 'collections.add_group',
+ name: "collections.add_group",
collectionId: collection.id,
teamId: collection.teamId,
actorId: ctx.state.user.id,
@@ -140,23 +140,23 @@ router.post('collections.add_group', auth(), async ctx => {
};
});
-router.post('collections.remove_group', auth(), async ctx => {
+router.post("collections.remove_group", auth(), async ctx => {
const { id, groupId } = ctx.body;
- ctx.assertUuid(id, 'id is required');
- ctx.assertUuid(groupId, 'groupId is required');
+ ctx.assertUuid(id, "id is required");
+ ctx.assertUuid(groupId, "groupId is required");
const collection = await Collection.scope({
- method: ['withMembership', ctx.state.user.id],
+ method: ["withMembership", ctx.state.user.id],
}).findByPk(id);
- authorize(ctx.state.user, 'update', collection);
+ authorize(ctx.state.user, "update", collection);
const group = await Group.findByPk(groupId);
- authorize(ctx.state.user, 'read', group);
+ authorize(ctx.state.user, "read", group);
await collection.removeGroup(group);
await Event.create({
- name: 'collections.remove_group',
+ name: "collections.remove_group",
collectionId: collection.id,
teamId: collection.teamId,
actorId: ctx.state.user.id,
@@ -170,19 +170,19 @@ router.post('collections.remove_group', auth(), async ctx => {
});
router.post(
- 'collections.group_memberships',
+ "collections.group_memberships",
auth(),
pagination(),
async ctx => {
const { id, query, permission } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findByPk(id);
- authorize(user, 'read', collection);
+ authorize(user, "read", collection);
let where = {
collectionId: id,
@@ -207,13 +207,13 @@ router.post(
const memberships = await CollectionGroup.findAll({
where,
- order: [['createdAt', 'DESC']],
+ order: [["createdAt", "DESC"]],
offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit,
include: [
{
model: Group,
- as: 'group',
+ as: "group",
where: groupWhere,
required: true,
},
@@ -232,18 +232,18 @@ router.post(
}
);
-router.post('collections.add_user', auth(), async ctx => {
- const { id, userId, permission = 'read_write' } = ctx.body;
- ctx.assertUuid(id, 'id is required');
- ctx.assertUuid(userId, 'userId is required');
+router.post("collections.add_user", auth(), async ctx => {
+ const { id, userId, permission = "read_write" } = ctx.body;
+ ctx.assertUuid(id, "id is required");
+ ctx.assertUuid(userId, "userId is required");
const collection = await Collection.scope({
- method: ['withMembership', ctx.state.user.id],
+ method: ["withMembership", ctx.state.user.id],
}).findByPk(id);
- authorize(ctx.state.user, 'update', collection);
+ authorize(ctx.state.user, "update", collection);
const user = await User.findByPk(userId);
- authorize(ctx.state.user, 'read', user);
+ authorize(ctx.state.user, "read", user);
let membership = await CollectionUser.findOne({
where: {
@@ -265,7 +265,7 @@ router.post('collections.add_user', auth(), async ctx => {
}
await Event.create({
- name: 'collections.add_user',
+ name: "collections.add_user",
userId,
collectionId: collection.id,
teamId: collection.teamId,
@@ -282,23 +282,23 @@ router.post('collections.add_user', auth(), async ctx => {
};
});
-router.post('collections.remove_user', auth(), async ctx => {
+router.post("collections.remove_user", auth(), async ctx => {
const { id, userId } = ctx.body;
- ctx.assertUuid(id, 'id is required');
- ctx.assertUuid(userId, 'userId is required');
+ ctx.assertUuid(id, "id is required");
+ ctx.assertUuid(userId, "userId is required");
const collection = await Collection.scope({
- method: ['withMembership', ctx.state.user.id],
+ method: ["withMembership", ctx.state.user.id],
}).findByPk(id);
- authorize(ctx.state.user, 'update', collection);
+ authorize(ctx.state.user, "update", collection);
const user = await User.findByPk(userId);
- authorize(ctx.state.user, 'read', user);
+ authorize(ctx.state.user, "read", user);
await collection.removeUser(user);
await Event.create({
- name: 'collections.remove_user',
+ name: "collections.remove_user",
userId,
collectionId: collection.id,
teamId: collection.teamId,
@@ -313,15 +313,15 @@ router.post('collections.remove_user', auth(), async ctx => {
});
// DEPRECATED: Use collection.memberships which has pagination, filtering and permissions
-router.post('collections.users', auth(), async ctx => {
+router.post("collections.users", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findByPk(id);
- authorize(user, 'read', collection);
+ authorize(user, "read", collection);
const users = await collection.getUsers();
@@ -330,15 +330,15 @@ router.post('collections.users', auth(), async ctx => {
};
});
-router.post('collections.memberships', auth(), pagination(), async ctx => {
+router.post("collections.memberships", auth(), pagination(), async ctx => {
const { id, query, permission } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findByPk(id);
- authorize(user, 'read', collection);
+ authorize(user, "read", collection);
let where = {
collectionId: id,
@@ -363,13 +363,13 @@ router.post('collections.memberships', auth(), pagination(), async ctx => {
const memberships = await CollectionUser.findAll({
where,
- order: [['createdAt', 'DESC']],
+ order: [["createdAt", "DESC"]],
offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit,
include: [
{
model: User,
- as: 'user',
+ as: "user",
where: userWhere,
required: true,
},
@@ -385,20 +385,20 @@ router.post('collections.memberships', auth(), pagination(), async ctx => {
};
});
-router.post('collections.export', auth(), async ctx => {
+router.post("collections.export", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findByPk(id);
- authorize(user, 'export', collection);
+ authorize(user, "export", collection);
const filePath = await archiveCollection(collection);
await Event.create({
- name: 'collections.export',
+ name: "collections.export",
collectionId: collection.id,
teamId: user.teamId,
actorId: user.id,
@@ -407,19 +407,19 @@ router.post('collections.export', auth(), async ctx => {
});
ctx.attachment(`${collection.name}.zip`);
- ctx.set('Content-Type', 'application/force-download');
+ ctx.set("Content-Type", "application/force-download");
ctx.body = fs.createReadStream(filePath);
});
-router.post('collections.export_all', auth(), async ctx => {
+router.post("collections.export_all", auth(), async ctx => {
const { download = false } = ctx.body;
const user = ctx.state.user;
const team = await Team.findByPk(user.teamId);
- authorize(user, 'export', team);
+ authorize(user, "export", team);
await Event.create({
- name: 'collections.export',
+ name: "collections.export",
teamId: user.teamId,
actorId: user.id,
ip: ctx.request.ip,
@@ -428,12 +428,12 @@ router.post('collections.export_all', auth(), async ctx => {
if (download) {
const collections = await Collection.findAll({
where: { teamId: team.id },
- order: [['name', 'ASC']],
+ order: [["name", "ASC"]],
});
const filePath = await archiveCollections(collections);
ctx.attachment(`${team.name}.zip`);
- ctx.set('Content-Type', 'application/force-download');
+ ctx.set("Content-Type", "application/force-download");
ctx.body = fs.createReadStream(filePath);
} else {
// async operation to create zip archive and email user
@@ -445,22 +445,22 @@ router.post('collections.export_all', auth(), async ctx => {
}
});
-router.post('collections.update', auth(), async ctx => {
+router.post("collections.update", auth(), async ctx => {
const { id, name, description, icon, color } = ctx.body;
const isPrivate = ctx.body.private;
- ctx.assertPresent(name, 'name is required');
+ ctx.assertPresent(name, "name is required");
if (color) {
- ctx.assertHexColor(color, 'Invalid hex value (please use format #FFFFFF)');
+ ctx.assertHexColor(color, "Invalid hex value (please use format #FFFFFF)");
}
const user = ctx.state.user;
const collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findByPk(id);
- authorize(user, 'update', collection);
+ authorize(user, "update", collection);
// we're making this collection private right now, ensure that the current
// user has a read-write membership so that at least they can edit it
@@ -471,7 +471,7 @@ router.post('collections.update', auth(), async ctx => {
userId: user.id,
},
defaults: {
- permission: 'read_write',
+ permission: "read_write",
createdById: user.id,
},
});
@@ -488,7 +488,7 @@ router.post('collections.update', auth(), async ctx => {
await collection.save();
await Event.create({
- name: 'collections.update',
+ name: "collections.update",
collectionId: collection.id,
teamId: collection.teamId,
actorId: user.id,
@@ -508,17 +508,17 @@ router.post('collections.update', auth(), async ctx => {
};
});
-router.post('collections.list', auth(), pagination(), async ctx => {
+router.post("collections.list", auth(), pagination(), async ctx => {
const user = ctx.state.user;
const collectionIds = await user.collectionIds();
let collections = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findAll({
where: {
teamId: user.teamId,
id: collectionIds,
},
- order: [['updatedAt', 'DESC']],
+ order: [["updatedAt", "DESC"]],
offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit,
});
@@ -530,24 +530,24 @@ router.post('collections.list', auth(), pagination(), async ctx => {
};
});
-router.post('collections.delete', auth(), async ctx => {
+router.post("collections.delete", auth(), async ctx => {
const { id } = ctx.body;
const user = ctx.state.user;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findByPk(id);
- authorize(user, 'delete', collection);
+ authorize(user, "delete", collection);
const total = await Collection.count();
- if (total === 1) throw new ValidationError('Cannot delete last collection');
+ if (total === 1) throw new ValidationError("Cannot delete last collection");
await collection.destroy();
await Event.create({
- name: 'collections.delete',
+ name: "collections.delete",
collectionId: collection.id,
teamId: collection.teamId,
actorId: user.id,
diff --git a/server/api/collections.test.js b/server/api/collections.test.js
index fdc959ab..a7b31763 100644
--- a/server/api/collections.test.js
+++ b/server/api/collections.test.js
@@ -1,26 +1,26 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../app';
-import { flushdb, seed } from '../test/support';
-import { buildUser, buildGroup, buildCollection } from '../test/factories';
-import { Collection, CollectionUser, CollectionGroup } from '../models';
+import TestServer from "fetch-test-server";
+import app from "../app";
+import { flushdb, seed } from "../test/support";
+import { buildUser, buildGroup, buildCollection } from "../test/factories";
+import { Collection, CollectionUser, CollectionGroup } from "../models";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('#collections.list', async () => {
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.list');
+describe("#collections.list", async () => {
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.list");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should return collections', async () => {
+ it("should return collections", async () => {
const { user, collection } = await seed();
- const res = await server.post('/api/collections.list', {
+ const res = await server.post("/api/collections.list", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -32,13 +32,13 @@ describe('#collections.list', async () => {
expect(body.policies[0].abilities.read).toEqual(true);
});
- it('should not return private collections actor is not a member of', async () => {
+ it("should not return private collections actor is not a member of", async () => {
const { user, collection } = await seed();
await buildCollection({
private: true,
teamId: user.teamId,
});
- const res = await server.post('/api/collections.list', {
+ const res = await server.post("/api/collections.list", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -48,7 +48,7 @@ describe('#collections.list', async () => {
expect(body.data[0].id).toEqual(collection.id);
});
- it('should return private collections actor is a member of', async () => {
+ it("should return private collections actor is a member of", async () => {
const user = await buildUser();
await buildCollection({
private: true,
@@ -61,7 +61,7 @@ describe('#collections.list', async () => {
userId: user.id,
});
- const res = await server.post('/api/collections.list', {
+ const res = await server.post("/api/collections.list", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -72,7 +72,7 @@ describe('#collections.list', async () => {
expect(body.policies[0].abilities.read).toEqual(true);
});
- it('should return private collections actor is a group-member of', async () => {
+ it("should return private collections actor is a group-member of", async () => {
const user = await buildUser();
await buildCollection({
private: true,
@@ -89,10 +89,10 @@ describe('#collections.list', async () => {
await group.addUser(user, { through: { createdById: user.id } });
await collection.addGroup(group, {
- through: { permission: 'read', createdById: user.id },
+ through: { permission: "read", createdById: user.id },
});
- const res = await server.post('/api/collections.list', {
+ const res = await server.post("/api/collections.list", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -104,21 +104,21 @@ describe('#collections.list', async () => {
});
});
-describe('#collections.export', async () => {
- it('should now allow export of private collection not a member', async () => {
+describe("#collections.export", async () => {
+ it("should now allow export of private collection not a member", async () => {
const { user } = await seed();
const collection = await buildCollection({
private: true,
teamId: user.teamId,
});
- const res = await server.post('/api/collections.export', {
+ const res = await server.post("/api/collections.export", {
body: { token: user.getJwtToken(), id: collection.id },
});
expect(res.status).toEqual(403);
});
- it('should allow export of private collection when the actor is a member', async () => {
+ it("should allow export of private collection when the actor is a member", async () => {
const { user, collection } = await seed();
collection.private = true;
await collection.save();
@@ -127,17 +127,17 @@ describe('#collections.export', async () => {
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read',
+ permission: "read",
});
- const res = await server.post('/api/collections.export', {
+ const res = await server.post("/api/collections.export", {
body: { token: user.getJwtToken(), id: collection.id },
});
expect(res.status).toEqual(200);
});
- it('should allow export of private collection when the actor is a group member', async () => {
+ it("should allow export of private collection when the actor is a group member", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
@@ -148,27 +148,27 @@ describe('#collections.export', async () => {
await group.addUser(user, { through: { createdById: user.id } });
await collection.addGroup(group, {
- through: { permission: 'read', createdById: user.id },
+ through: { permission: "read", createdById: user.id },
});
- const res = await server.post('/api/collections.export', {
+ const res = await server.post("/api/collections.export", {
body: { token: user.getJwtToken(), id: collection.id },
});
expect(res.status).toEqual(200);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.export');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.export");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should return success', async () => {
+ it("should return success", async () => {
const { user, collection } = await seed();
- const res = await server.post('/api/collections.export', {
+ const res = await server.post("/api/collections.export", {
body: { token: user.getJwtToken(), id: collection.id },
});
@@ -176,47 +176,47 @@ describe('#collections.export', async () => {
});
});
-describe('#collections.export_all', async () => {
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.export_all');
+describe("#collections.export_all", async () => {
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.export_all");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const user = await buildUser();
- const res = await server.post('/api/collections.export_all', {
+ const res = await server.post("/api/collections.export_all", {
body: { token: user.getJwtToken() },
});
expect(res.status).toEqual(403);
});
- it('should return success', async () => {
+ it("should return success", async () => {
const { admin } = await seed();
- const res = await server.post('/api/collections.export_all', {
+ const res = await server.post("/api/collections.export_all", {
body: { token: admin.getJwtToken() },
});
expect(res.status).toEqual(200);
});
- it('should allow downloading directly', async () => {
+ it("should allow downloading directly", async () => {
const { admin } = await seed();
- const res = await server.post('/api/collections.export_all', {
+ const res = await server.post("/api/collections.export_all", {
body: { token: admin.getJwtToken(), download: true },
});
expect(res.status).toEqual(200);
- expect(res.headers.get('content-type')).toEqual(
- 'application/force-download'
+ expect(res.headers.get("content-type")).toEqual(
+ "application/force-download"
);
});
});
-describe('#collections.add_user', async () => {
- it('should add user to collection', async () => {
+describe("#collections.add_user", async () => {
+ it("should add user to collection", async () => {
const user = await buildUser();
const collection = await buildCollection({
teamId: user.teamId,
@@ -224,7 +224,7 @@ describe('#collections.add_user', async () => {
private: true,
});
const anotherUser = await buildUser({ teamId: user.teamId });
- const res = await server.post('/api/collections.add_user', {
+ const res = await server.post("/api/collections.add_user", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -237,14 +237,14 @@ describe('#collections.add_user', async () => {
expect(users.length).toEqual(2);
});
- it('should require user in team', async () => {
+ it("should require user in team", async () => {
const user = await buildUser();
const collection = await buildCollection({
teamId: user.teamId,
private: true,
});
const anotherUser = await buildUser();
- const res = await server.post('/api/collections.add_user', {
+ const res = await server.post("/api/collections.add_user", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -257,18 +257,18 @@ describe('#collections.add_user', async () => {
expect(body).toMatchSnapshot();
});
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.add_user');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.add_user");
expect(res.status).toEqual(401);
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { collection } = await seed();
const user = await buildUser();
const anotherUser = await buildUser({ teamId: user.teamId });
- const res = await server.post('/api/collections.add_user', {
+ const res = await server.post("/api/collections.add_user", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -279,8 +279,8 @@ describe('#collections.add_user', async () => {
});
});
-describe('#collections.add_group', async () => {
- it('should add group to collection', async () => {
+describe("#collections.add_group", async () => {
+ it("should add group to collection", async () => {
const user = await buildUser({ isAdmin: true });
const collection = await buildCollection({
teamId: user.teamId,
@@ -288,7 +288,7 @@ describe('#collections.add_group', async () => {
private: true,
});
const group = await buildGroup({ teamId: user.teamId });
- const res = await server.post('/api/collections.add_group', {
+ const res = await server.post("/api/collections.add_group", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -301,7 +301,7 @@ describe('#collections.add_group', async () => {
expect(res.status).toEqual(200);
});
- it('should require group in team', async () => {
+ it("should require group in team", async () => {
const user = await buildUser();
const collection = await buildCollection({
teamId: user.teamId,
@@ -309,7 +309,7 @@ describe('#collections.add_group', async () => {
private: true,
});
const group = await buildGroup();
- const res = await server.post('/api/collections.add_group', {
+ const res = await server.post("/api/collections.add_group", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -322,16 +322,16 @@ describe('#collections.add_group', async () => {
expect(body).toMatchSnapshot();
});
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.add_group');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.add_group");
expect(res.status).toEqual(401);
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const collection = await buildCollection();
const user = await buildUser();
const group = await buildGroup({ teamId: user.teamId });
- const res = await server.post('/api/collections.add_group', {
+ const res = await server.post("/api/collections.add_group", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -342,8 +342,8 @@ describe('#collections.add_group', async () => {
});
});
-describe('#collections.remove_group', async () => {
- it('should remove group from collection', async () => {
+describe("#collections.remove_group", async () => {
+ it("should remove group from collection", async () => {
const user = await buildUser({ isAdmin: true });
const collection = await buildCollection({
teamId: user.teamId,
@@ -352,7 +352,7 @@ describe('#collections.remove_group', async () => {
});
const group = await buildGroup({ teamId: user.teamId });
- await server.post('/api/collections.add_group', {
+ await server.post("/api/collections.add_group", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -363,7 +363,7 @@ describe('#collections.remove_group', async () => {
let users = await collection.getGroups();
expect(users.length).toEqual(1);
- const res = await server.post('/api/collections.remove_group', {
+ const res = await server.post("/api/collections.remove_group", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -376,14 +376,14 @@ describe('#collections.remove_group', async () => {
expect(users.length).toEqual(0);
});
- it('should require group in team', async () => {
+ it("should require group in team", async () => {
const user = await buildUser();
const collection = await buildCollection({
teamId: user.teamId,
private: true,
});
const group = await buildGroup();
- const res = await server.post('/api/collections.remove_group', {
+ const res = await server.post("/api/collections.remove_group", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -396,18 +396,18 @@ describe('#collections.remove_group', async () => {
expect(body).toMatchSnapshot();
});
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.remove_group');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.remove_group");
expect(res.status).toEqual(401);
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { collection } = await seed();
const user = await buildUser();
const group = await buildGroup({ teamId: user.teamId });
- const res = await server.post('/api/collections.remove_group', {
+ const res = await server.post("/api/collections.remove_group", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -418,8 +418,8 @@ describe('#collections.remove_group', async () => {
});
});
-describe('#collections.remove_user', async () => {
- it('should remove user from collection', async () => {
+describe("#collections.remove_user", async () => {
+ it("should remove user from collection", async () => {
const user = await buildUser();
const collection = await buildCollection({
teamId: user.teamId,
@@ -428,7 +428,7 @@ describe('#collections.remove_user', async () => {
});
const anotherUser = await buildUser({ teamId: user.teamId });
- await server.post('/api/collections.add_user', {
+ await server.post("/api/collections.add_user", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -436,7 +436,7 @@ describe('#collections.remove_user', async () => {
},
});
- const res = await server.post('/api/collections.remove_user', {
+ const res = await server.post("/api/collections.remove_user", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -449,14 +449,14 @@ describe('#collections.remove_user', async () => {
expect(users.length).toEqual(1);
});
- it('should require user in team', async () => {
+ it("should require user in team", async () => {
const user = await buildUser();
const collection = await buildCollection({
teamId: user.teamId,
private: true,
});
const anotherUser = await buildUser();
- const res = await server.post('/api/collections.remove_user', {
+ const res = await server.post("/api/collections.remove_user", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -469,18 +469,18 @@ describe('#collections.remove_user', async () => {
expect(body).toMatchSnapshot();
});
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.remove_user');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.remove_user");
expect(res.status).toEqual(401);
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { collection } = await seed();
const user = await buildUser();
const anotherUser = await buildUser({ teamId: user.teamId });
- const res = await server.post('/api/collections.remove_user', {
+ const res = await server.post("/api/collections.remove_user", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -491,8 +491,8 @@ describe('#collections.remove_user', async () => {
});
});
-describe('#collections.users', async () => {
- it('should return users in private collection', async () => {
+describe("#collections.users", async () => {
+ it("should return users in private collection", async () => {
const { collection, user } = await seed();
collection.private = true;
await collection.save();
@@ -501,10 +501,10 @@ describe('#collections.users', async () => {
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read',
+ permission: "read",
});
- const res = await server.post('/api/collections.users', {
+ const res = await server.post("/api/collections.users", {
body: { token: user.getJwtToken(), id: collection.id },
});
const body = await res.json();
@@ -513,26 +513,26 @@ describe('#collections.users', async () => {
expect(body.data.length).toEqual(1);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.users');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.users");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { collection } = await seed();
const user = await buildUser();
- const res = await server.post('/api/collections.users', {
+ const res = await server.post("/api/collections.users", {
body: { token: user.getJwtToken(), id: collection.id },
});
expect(res.status).toEqual(403);
});
});
-describe('#collections.group_memberships', async () => {
- it('should return groups in private collection', async () => {
+describe("#collections.group_memberships", async () => {
+ it("should return groups in private collection", async () => {
const user = await buildUser();
const group = await buildGroup({ teamId: user.teamId });
const collection = await buildCollection({
@@ -544,17 +544,17 @@ describe('#collections.group_memberships', async () => {
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read_write',
+ permission: "read_write",
});
await CollectionGroup.create({
createdById: user.id,
collectionId: collection.id,
groupId: group.id,
- permission: 'read_write',
+ permission: "read_write",
});
- const res = await server.post('/api/collections.group_memberships', {
+ const res = await server.post("/api/collections.group_memberships", {
body: { token: user.getJwtToken(), id: collection.id },
});
const body = await res.json();
@@ -564,14 +564,14 @@ describe('#collections.group_memberships', async () => {
expect(body.data.groups[0].id).toEqual(group.id);
expect(body.data.collectionGroupMemberships.length).toEqual(1);
expect(body.data.collectionGroupMemberships[0].permission).toEqual(
- 'read_write'
+ "read_write"
);
});
- it('should allow filtering groups in collection by name', async () => {
+ it("should allow filtering groups in collection by name", async () => {
const user = await buildUser();
- const group = await buildGroup({ name: 'will find', teamId: user.teamId });
- const group2 = await buildGroup({ name: 'wont find', teamId: user.teamId });
+ const group = await buildGroup({ name: "will find", teamId: user.teamId });
+ const group2 = await buildGroup({ name: "wont find", teamId: user.teamId });
const collection = await buildCollection({
private: true,
teamId: user.teamId,
@@ -581,28 +581,28 @@ describe('#collections.group_memberships', async () => {
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read_write',
+ permission: "read_write",
});
await CollectionGroup.create({
createdById: user.id,
collectionId: collection.id,
groupId: group.id,
- permission: 'read_write',
+ permission: "read_write",
});
await CollectionGroup.create({
createdById: user.id,
collectionId: collection.id,
groupId: group2.id,
- permission: 'read_write',
+ permission: "read_write",
});
- const res = await server.post('/api/collections.group_memberships', {
+ const res = await server.post("/api/collections.group_memberships", {
body: {
token: user.getJwtToken(),
id: collection.id,
- query: 'will',
+ query: "will",
},
});
@@ -613,7 +613,7 @@ describe('#collections.group_memberships', async () => {
expect(body.data.groups[0].id).toEqual(group.id);
});
- it('should allow filtering groups in collection by permission', async () => {
+ it("should allow filtering groups in collection by permission", async () => {
const user = await buildUser();
const group = await buildGroup({ teamId: user.teamId });
const group2 = await buildGroup({ teamId: user.teamId });
@@ -626,28 +626,28 @@ describe('#collections.group_memberships', async () => {
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read_write',
+ permission: "read_write",
});
await CollectionGroup.create({
createdById: user.id,
collectionId: collection.id,
groupId: group.id,
- permission: 'read_write',
+ permission: "read_write",
});
await CollectionGroup.create({
createdById: user.id,
collectionId: collection.id,
groupId: group2.id,
- permission: 'maintainer',
+ permission: "maintainer",
});
- const res = await server.post('/api/collections.group_memberships', {
+ const res = await server.post("/api/collections.group_memberships", {
body: {
token: user.getJwtToken(),
id: collection.id,
- permission: 'maintainer',
+ permission: "maintainer",
},
});
@@ -658,30 +658,30 @@ describe('#collections.group_memberships', async () => {
expect(body.data.groups[0].id).toEqual(group2.id);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.group_memberships');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.group_memberships");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
teamId: user.teamId,
});
- const res = await server.post('/api/collections.group_memberships', {
+ const res = await server.post("/api/collections.group_memberships", {
body: { token: user.getJwtToken(), id: collection.id },
});
expect(res.status).toEqual(403);
});
});
-describe('#collections.memberships', async () => {
- it('should return members in private collection', async () => {
+describe("#collections.memberships", async () => {
+ it("should return members in private collection", async () => {
const { collection, user } = await seed();
collection.private = true;
await collection.save();
@@ -690,10 +690,10 @@ describe('#collections.memberships', async () => {
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read_write',
+ permission: "read_write",
});
- const res = await server.post('/api/collections.memberships', {
+ const res = await server.post("/api/collections.memberships", {
body: { token: user.getJwtToken(), id: collection.id },
});
const body = await res.json();
@@ -702,26 +702,26 @@ describe('#collections.memberships', async () => {
expect(body.data.users.length).toEqual(1);
expect(body.data.users[0].id).toEqual(user.id);
expect(body.data.memberships.length).toEqual(1);
- expect(body.data.memberships[0].permission).toEqual('read_write');
+ expect(body.data.memberships[0].permission).toEqual("read_write");
});
- it('should allow filtering members in collection by name', async () => {
+ it("should allow filtering members in collection by name", async () => {
const { collection, user } = await seed();
const user2 = await buildUser({ name: "Won't find" });
await CollectionUser.create({
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read_write',
+ permission: "read_write",
});
await CollectionUser.create({
createdById: user2.id,
collectionId: collection.id,
userId: user2.id,
- permission: 'read_write',
+ permission: "read_write",
});
- const res = await server.post('/api/collections.memberships', {
+ const res = await server.post("/api/collections.memberships", {
body: {
token: user.getJwtToken(),
id: collection.id,
@@ -735,27 +735,27 @@ describe('#collections.memberships', async () => {
expect(body.data.users[0].id).toEqual(user.id);
});
- it('should allow filtering members in collection by permission', async () => {
+ it("should allow filtering members in collection by permission", async () => {
const { collection, user } = await seed();
const user2 = await buildUser();
await CollectionUser.create({
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read_write',
+ permission: "read_write",
});
await CollectionUser.create({
createdById: user2.id,
collectionId: collection.id,
userId: user2.id,
- permission: 'maintainer',
+ permission: "maintainer",
});
- const res = await server.post('/api/collections.memberships', {
+ const res = await server.post("/api/collections.memberships", {
body: {
token: user.getJwtToken(),
id: collection.id,
- permission: 'maintainer',
+ permission: "maintainer",
},
});
const body = await res.json();
@@ -765,28 +765,28 @@ describe('#collections.memberships', async () => {
expect(body.data.users[0].id).toEqual(user2.id);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.memberships');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.memberships");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { collection } = await seed();
const user = await buildUser();
- const res = await server.post('/api/collections.memberships', {
+ const res = await server.post("/api/collections.memberships", {
body: { token: user.getJwtToken(), id: collection.id },
});
expect(res.status).toEqual(403);
});
});
-describe('#collections.info', async () => {
- it('should return collection', async () => {
+describe("#collections.info", async () => {
+ it("should return collection", async () => {
const { user, collection } = await seed();
- const res = await server.post('/api/collections.info', {
+ const res = await server.post("/api/collections.info", {
body: { token: user.getJwtToken(), id: collection.id },
});
const body = await res.json();
@@ -795,18 +795,18 @@ describe('#collections.info', async () => {
expect(body.data.id).toEqual(collection.id);
});
- it('should require user member of collection', async () => {
+ it("should require user member of collection", async () => {
const { user, collection } = await seed();
collection.private = true;
await collection.save();
- const res = await server.post('/api/collections.info', {
+ const res = await server.post("/api/collections.info", {
body: { token: user.getJwtToken(), id: collection.id },
});
expect(res.status).toEqual(403);
});
- it('should allow user member of collection', async () => {
+ it("should allow user member of collection", async () => {
const { user, collection } = await seed();
collection.private = true;
await collection.save();
@@ -815,10 +815,10 @@ describe('#collections.info', async () => {
collectionId: collection.id,
userId: user.id,
createdById: user.id,
- permission: 'read',
+ permission: "read",
});
- const res = await server.post('/api/collections.info', {
+ const res = await server.post("/api/collections.info", {
body: { token: user.getJwtToken(), id: collection.id },
});
const body = await res.json();
@@ -827,52 +827,52 @@ describe('#collections.info', async () => {
expect(body.data.id).toEqual(collection.id);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.info');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.info");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { collection } = await seed();
const user = await buildUser();
- const res = await server.post('/api/collections.info', {
+ const res = await server.post("/api/collections.info", {
body: { token: user.getJwtToken(), id: collection.id },
});
expect(res.status).toEqual(403);
});
});
-describe('#collections.create', async () => {
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.create');
+describe("#collections.create", async () => {
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.create");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should create collection', async () => {
+ it("should create collection", async () => {
const { user } = await seed();
- const res = await server.post('/api/collections.create', {
- body: { token: user.getJwtToken(), name: 'Test' },
+ const res = await server.post("/api/collections.create", {
+ body: { token: user.getJwtToken(), name: "Test" },
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.data.id).toBeTruthy();
- expect(body.data.name).toBe('Test');
+ expect(body.data.name).toBe("Test");
expect(body.policies.length).toBe(1);
expect(body.policies[0].abilities.read).toBeTruthy();
expect(body.policies[0].abilities.export).toBeTruthy();
});
- it('should return correct policies with private collection', async () => {
+ it("should return correct policies with private collection", async () => {
const { user } = await seed();
- const res = await server.post('/api/collections.create', {
- body: { token: user.getJwtToken(), name: 'Test', private: true },
+ const res = await server.post("/api/collections.create", {
+ body: { token: user.getJwtToken(), name: "Test", private: true },
});
const body = await res.json();
@@ -884,11 +884,11 @@ describe('#collections.create', async () => {
});
});
-describe('#collections.update', async () => {
- it('should require authentication', async () => {
+describe("#collections.update", async () => {
+ it("should require authentication", async () => {
const collection = await buildCollection();
- const res = await server.post('/api/collections.update', {
- body: { id: collection.id, name: 'Test' },
+ const res = await server.post("/api/collections.update", {
+ body: { id: collection.id, name: "Test" },
});
const body = await res.json();
@@ -896,39 +896,39 @@ describe('#collections.update', async () => {
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { collection } = await seed();
const user = await buildUser();
- const res = await server.post('/api/collections.update', {
- body: { token: user.getJwtToken(), id: collection.id, name: 'Test' },
+ const res = await server.post("/api/collections.update", {
+ body: { token: user.getJwtToken(), id: collection.id, name: "Test" },
});
expect(res.status).toEqual(403);
});
- it('allows editing non-private collection', async () => {
+ it("allows editing non-private collection", async () => {
const { user, collection } = await seed();
- const res = await server.post('/api/collections.update', {
- body: { token: user.getJwtToken(), id: collection.id, name: 'Test' },
+ const res = await server.post("/api/collections.update", {
+ body: { token: user.getJwtToken(), id: collection.id, name: "Test" },
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.name).toBe('Test');
+ expect(body.data.name).toBe("Test");
expect(body.policies.length).toBe(1);
});
- it('allows editing from non-private to private collection', async () => {
+ it("allows editing from non-private to private collection", async () => {
const { user, collection } = await seed();
- const res = await server.post('/api/collections.update', {
+ const res = await server.post("/api/collections.update", {
body: {
token: user.getJwtToken(),
id: collection.id,
private: true,
- name: 'Test',
+ name: "Test",
},
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.name).toBe('Test');
+ expect(body.data.name).toBe("Test");
expect(body.data.private).toBe(true);
// ensure we return with a write level policy
@@ -936,7 +936,7 @@ describe('#collections.update', async () => {
expect(body.policies[0].abilities.update).toBe(true);
});
- it('allows editing from private to non-private collection', async () => {
+ it("allows editing from private to non-private collection", async () => {
const { user, collection } = await seed();
collection.private = true;
await collection.save();
@@ -945,20 +945,20 @@ describe('#collections.update', async () => {
collectionId: collection.id,
userId: user.id,
createdById: user.id,
- permission: 'read_write',
+ permission: "read_write",
});
- const res = await server.post('/api/collections.update', {
+ const res = await server.post("/api/collections.update", {
body: {
token: user.getJwtToken(),
id: collection.id,
private: false,
- name: 'Test',
+ name: "Test",
},
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.name).toBe('Test');
+ expect(body.data.name).toBe("Test");
expect(body.data.private).toBe(false);
// ensure we return with a write level policy
@@ -966,7 +966,7 @@ describe('#collections.update', async () => {
expect(body.policies[0].abilities.update).toBe(true);
});
- it('allows editing by read-write collection user', async () => {
+ it("allows editing by read-write collection user", async () => {
const { user, collection } = await seed();
collection.private = true;
await collection.save();
@@ -975,19 +975,19 @@ describe('#collections.update', async () => {
collectionId: collection.id,
userId: user.id,
createdById: user.id,
- permission: 'read_write',
+ permission: "read_write",
});
- const res = await server.post('/api/collections.update', {
- body: { token: user.getJwtToken(), id: collection.id, name: 'Test' },
+ const res = await server.post("/api/collections.update", {
+ body: { token: user.getJwtToken(), id: collection.id, name: "Test" },
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.name).toBe('Test');
+ expect(body.data.name).toBe("Test");
expect(body.policies.length).toBe(1);
});
- it('allows editing by read-write collection group user', async () => {
+ it("allows editing by read-write collection group user", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
@@ -998,19 +998,19 @@ describe('#collections.update', async () => {
await group.addUser(user, { through: { createdById: user.id } });
await collection.addGroup(group, {
- through: { permission: 'read_write', createdById: user.id },
+ through: { permission: "read_write", createdById: user.id },
});
- const res = await server.post('/api/collections.update', {
- body: { token: user.getJwtToken(), id: collection.id, name: 'Test' },
+ const res = await server.post("/api/collections.update", {
+ body: { token: user.getJwtToken(), id: collection.id, name: "Test" },
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.name).toBe('Test');
+ expect(body.data.name).toBe("Test");
expect(body.policies.length).toBe(1);
});
- it('does not allow editing by read-only collection user', async () => {
+ it("does not allow editing by read-only collection user", async () => {
const { user, collection } = await seed();
collection.private = true;
await collection.save();
@@ -1019,53 +1019,53 @@ describe('#collections.update', async () => {
collectionId: collection.id,
userId: user.id,
createdById: user.id,
- permission: 'read',
+ permission: "read",
});
- const res = await server.post('/api/collections.update', {
- body: { token: user.getJwtToken(), id: collection.id, name: 'Test' },
+ const res = await server.post("/api/collections.update", {
+ body: { token: user.getJwtToken(), id: collection.id, name: "Test" },
});
expect(res.status).toEqual(403);
});
});
-describe('#collections.delete', async () => {
- it('should require authentication', async () => {
- const res = await server.post('/api/collections.delete');
+describe("#collections.delete", async () => {
+ it("should require authentication", async () => {
+ const res = await server.post("/api/collections.delete");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { collection } = await seed();
const user = await buildUser();
- const res = await server.post('/api/collections.delete', {
+ const res = await server.post("/api/collections.delete", {
body: { token: user.getJwtToken(), id: collection.id },
});
expect(res.status).toEqual(403);
});
- it('should not allow deleting last collection', async () => {
+ it("should not allow deleting last collection", async () => {
const { user, collection } = await seed();
- const res = await server.post('/api/collections.delete', {
+ const res = await server.post("/api/collections.delete", {
body: { token: user.getJwtToken(), id: collection.id },
});
expect(res.status).toEqual(400);
});
- it('should delete collection', async () => {
+ it("should delete collection", async () => {
const { user, collection } = await seed();
await Collection.create({
- name: 'Blah',
- urlId: 'blah',
+ name: "Blah",
+ urlId: "blah",
teamId: user.teamId,
creatorId: user.id,
- type: 'atlas',
+ type: "atlas",
});
- const res = await server.post('/api/collections.delete', {
+ const res = await server.post("/api/collections.delete", {
body: { token: user.getJwtToken(), id: collection.id },
});
const body = await res.json();
@@ -1074,7 +1074,7 @@ describe('#collections.delete', async () => {
expect(body.success).toBe(true);
});
- it('allows deleting by read-write collection group user', async () => {
+ it("allows deleting by read-write collection group user", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
@@ -1088,10 +1088,10 @@ describe('#collections.delete', async () => {
await group.addUser(user, { through: { createdById: user.id } });
await collection.addGroup(group, {
- through: { permission: 'read_write', createdById: user.id },
+ through: { permission: "read_write", createdById: user.id },
});
- const res = await server.post('/api/collections.delete', {
+ const res = await server.post("/api/collections.delete", {
body: { token: user.getJwtToken(), id: collection.id },
});
const body = await res.json();
diff --git a/server/api/documents.js b/server/api/documents.js
index 22937254..9458ca41 100644
--- a/server/api/documents.js
+++ b/server/api/documents.js
@@ -1,14 +1,14 @@
// @flow
-import Router from 'koa-router';
-import Sequelize from 'sequelize';
-import auth from '../middlewares/authentication';
-import pagination from './middlewares/pagination';
-import documentMover from '../commands/documentMover';
+import Router from "koa-router";
+import Sequelize from "sequelize";
+import auth from "../middlewares/authentication";
+import pagination from "./middlewares/pagination";
+import documentMover from "../commands/documentMover";
import {
presentDocument,
presentCollection,
presentPolicies,
-} from '../presenters';
+} from "../presenters";
import {
Collection,
Document,
@@ -19,23 +19,23 @@ import {
Revision,
Backlink,
User,
-} from '../models';
-import { InvalidRequestError } from '../errors';
-import policy from '../policies';
-import { sequelize } from '../sequelize';
+} from "../models";
+import { InvalidRequestError } from "../errors";
+import policy from "../policies";
+import { sequelize } from "../sequelize";
const Op = Sequelize.Op;
const { authorize, cannot } = policy;
const router = new Router();
-router.post('documents.list', auth(), pagination(), async ctx => {
- const { sort = 'updatedAt', backlinkDocumentId, parentDocumentId } = ctx.body;
+router.post("documents.list", auth(), pagination(), async ctx => {
+ const { sort = "updatedAt", backlinkDocumentId, parentDocumentId } = ctx.body;
// collection and user are here for backwards compatablity
const collectionId = ctx.body.collectionId || ctx.body.collection;
const createdById = ctx.body.userId || ctx.body.user;
let direction = ctx.body.direction;
- if (direction !== 'ASC') direction = 'DESC';
+ if (direction !== "ASC") direction = "DESC";
// always filter by the current team
const user = ctx.state.user;
@@ -44,19 +44,19 @@ router.post('documents.list', auth(), pagination(), async ctx => {
// if a specific user is passed then add to filters. If the user doesn't
// exist in the team then nothing will be returned, so no need to check auth
if (createdById) {
- ctx.assertUuid(createdById, 'user must be a UUID');
+ ctx.assertUuid(createdById, "user must be a UUID");
where = { ...where, createdById };
}
// if a specific collection is passed then we need to check auth to view it
if (collectionId) {
- ctx.assertUuid(collectionId, 'collection must be a UUID');
+ ctx.assertUuid(collectionId, "collection must be a UUID");
where = { ...where, collectionId };
const collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findByPk(collectionId);
- authorize(user, 'read', collection);
+ authorize(user, "read", collection);
// otherwise, filter by all collections the user has access to
} else {
@@ -65,15 +65,15 @@ router.post('documents.list', auth(), pagination(), async ctx => {
}
if (parentDocumentId) {
- ctx.assertUuid(parentDocumentId, 'parentDocumentId must be a UUID');
+ ctx.assertUuid(parentDocumentId, "parentDocumentId must be a UUID");
where = { ...where, parentDocumentId };
}
if (backlinkDocumentId) {
- ctx.assertUuid(backlinkDocumentId, 'backlinkDocumentId must be a UUID');
+ ctx.assertUuid(backlinkDocumentId, "backlinkDocumentId must be a UUID");
const backlinks = await Backlink.findAll({
- attributes: ['reverseDocumentId'],
+ attributes: ["reverseDocumentId"],
where: {
documentId: backlinkDocumentId,
},
@@ -86,10 +86,10 @@ router.post('documents.list', auth(), pagination(), async ctx => {
}
// add the users starred state to the response by default
- const starredScope = { method: ['withStarred', user.id] };
- const collectionScope = { method: ['withCollection', user.id] };
+ const starredScope = { method: ["withStarred", user.id] };
+ const collectionScope = { method: ["withCollection", user.id] };
const documents = await Document.scope(
- 'defaultScope',
+ "defaultScope",
starredScope,
collectionScope
).findAll({
@@ -112,23 +112,23 @@ router.post('documents.list', auth(), pagination(), async ctx => {
};
});
-router.post('documents.pinned', auth(), pagination(), async ctx => {
- const { collectionId, sort = 'updatedAt' } = ctx.body;
+router.post("documents.pinned", auth(), pagination(), async ctx => {
+ const { collectionId, sort = "updatedAt" } = ctx.body;
let direction = ctx.body.direction;
- if (direction !== 'ASC') direction = 'DESC';
- ctx.assertUuid(collectionId, 'collectionId is required');
+ if (direction !== "ASC") direction = "DESC";
+ ctx.assertUuid(collectionId, "collectionId is required");
const user = ctx.state.user;
const collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findByPk(collectionId);
- authorize(user, 'read', collection);
+ authorize(user, "read", collection);
- const starredScope = { method: ['withStarred', user.id] };
- const collectionScope = { method: ['withCollection', user.id] };
+ const starredScope = { method: ["withStarred", user.id] };
+ const collectionScope = { method: ["withCollection", user.id] };
const documents = await Document.scope(
- 'defaultScope',
+ "defaultScope",
starredScope,
collectionScope
).findAll({
@@ -157,17 +157,17 @@ router.post('documents.pinned', auth(), pagination(), async ctx => {
};
});
-router.post('documents.archived', auth(), pagination(), async ctx => {
- const { sort = 'updatedAt' } = ctx.body;
+router.post("documents.archived", auth(), pagination(), async ctx => {
+ const { sort = "updatedAt" } = ctx.body;
let direction = ctx.body.direction;
- if (direction !== 'ASC') direction = 'DESC';
+ if (direction !== "ASC") direction = "DESC";
const user = ctx.state.user;
const collectionIds = await user.collectionIds();
- const collectionScope = { method: ['withCollection', user.id] };
+ const collectionScope = { method: ["withCollection", user.id] };
const documents = await Document.scope(
- 'defaultScope',
+ "defaultScope",
collectionScope
).findAll({
where: {
@@ -195,15 +195,15 @@ router.post('documents.archived', auth(), pagination(), async ctx => {
};
});
-router.post('documents.deleted', auth(), pagination(), async ctx => {
- const { sort = 'deletedAt' } = ctx.body;
+router.post("documents.deleted", auth(), pagination(), async ctx => {
+ const { sort = "deletedAt" } = ctx.body;
let direction = ctx.body.direction;
- if (direction !== 'ASC') direction = 'DESC';
+ if (direction !== "ASC") direction = "DESC";
const user = ctx.state.user;
const collectionIds = await user.collectionIds();
- const collectionScope = { method: ['withCollection', user.id] };
+ const collectionScope = { method: ["withCollection", user.id] };
const documents = await Document.scope(collectionScope).findAll({
where: {
teamId: user.teamId,
@@ -213,8 +213,8 @@ router.post('documents.deleted', auth(), pagination(), async ctx => {
},
},
include: [
- { model: User, as: 'createdBy', paranoid: false },
- { model: User, as: 'updatedBy', paranoid: false },
+ { model: User, as: "createdBy", paranoid: false },
+ { model: User, as: "updatedBy", paranoid: false },
],
paranoid: false,
order: [[sort, direction]],
@@ -235,9 +235,9 @@ router.post('documents.deleted', auth(), pagination(), async ctx => {
};
});
-router.post('documents.viewed', auth(), pagination(), async ctx => {
- let { sort = 'updatedAt', direction } = ctx.body;
- if (direction !== 'ASC') direction = 'DESC';
+router.post("documents.viewed", auth(), pagination(), async ctx => {
+ let { sort = "updatedAt", direction } = ctx.body;
+ if (direction !== "ASC") direction = "DESC";
const user = ctx.state.user;
const collectionIds = await user.collectionIds();
@@ -255,7 +255,7 @@ router.post('documents.viewed', auth(), pagination(), async ctx => {
include: [
{
model: Star,
- as: 'starred',
+ as: "starred",
where: { userId: user.id },
required: false,
},
@@ -280,9 +280,9 @@ router.post('documents.viewed', auth(), pagination(), async ctx => {
};
});
-router.post('documents.starred', auth(), pagination(), async ctx => {
- let { sort = 'updatedAt', direction } = ctx.body;
- if (direction !== 'ASC') direction = 'DESC';
+router.post("documents.starred", auth(), pagination(), async ctx => {
+ let { sort = "updatedAt", direction } = ctx.body;
+ if (direction !== "ASC") direction = "DESC";
const user = ctx.state.user;
const collectionIds = await user.collectionIds();
@@ -301,11 +301,11 @@ router.post('documents.starred', auth(), pagination(), async ctx => {
include: [
{
model: Collection,
- as: 'collection',
+ as: "collection",
},
{
model: Star,
- as: 'starred',
+ as: "starred",
where: {
userId: user.id,
},
@@ -331,16 +331,16 @@ router.post('documents.starred', auth(), pagination(), async ctx => {
};
});
-router.post('documents.drafts', auth(), pagination(), async ctx => {
- let { sort = 'updatedAt', direction } = ctx.body;
- if (direction !== 'ASC') direction = 'DESC';
+router.post("documents.drafts", auth(), pagination(), async ctx => {
+ let { sort = "updatedAt", direction } = ctx.body;
+ if (direction !== "ASC") direction = "DESC";
const user = ctx.state.user;
const collectionIds = await user.collectionIds();
- const collectionScope = { method: ['withCollection', user.id] };
+ const collectionScope = { method: ["withCollection", user.id] };
const documents = await Document.scope(
- 'defaultScope',
+ "defaultScope",
collectionScope
).findAll({
where: {
@@ -366,9 +366,9 @@ router.post('documents.drafts', auth(), pagination(), async ctx => {
};
});
-router.post('documents.info', auth({ required: false }), async ctx => {
+router.post("documents.info", auth({ required: false }), async ctx => {
const { id, shareId } = ctx.body;
- ctx.assertPresent(id || shareId, 'id or shareId is required');
+ ctx.assertPresent(id || shareId, "id or shareId is required");
const user = ctx.state.user;
let document;
@@ -384,16 +384,16 @@ router.post('documents.info', auth({ required: false }), async ctx => {
// unscoping here allows us to return unpublished documents
model: Document.unscoped(),
include: [
- { model: User, as: 'createdBy', paranoid: false },
- { model: User, as: 'updatedBy', paranoid: false },
+ { model: User, as: "createdBy", paranoid: false },
+ { model: User, as: "updatedBy", paranoid: false },
],
required: true,
- as: 'document',
+ as: "document",
},
],
});
if (!share || share.document.archivedAt) {
- throw new InvalidRequestError('Document could not be found for shareId');
+ throw new InvalidRequestError("Document could not be found for shareId");
}
document = share.document;
} else {
@@ -401,10 +401,10 @@ router.post('documents.info', auth({ required: false }), async ctx => {
id,
user ? { userId: user.id } : undefined
);
- authorize(user, 'read', document);
+ authorize(user, "read", document);
}
- const isPublic = cannot(user, 'read', document);
+ const isPublic = cannot(user, "read", document);
ctx.body = {
data: await presentDocument(document, { isPublic }),
@@ -412,9 +412,9 @@ router.post('documents.info', auth({ required: false }), async ctx => {
};
});
-router.post('documents.restore', auth(), async ctx => {
+router.post("documents.restore", auth(), async ctx => {
const { id, revisionId } = ctx.body;
- ctx.assertPresent(id, 'id is required');
+ ctx.assertPresent(id, "id is required");
const user = ctx.state.user;
const document = await Document.findByPk(id, {
@@ -423,13 +423,13 @@ router.post('documents.restore', auth(), async ctx => {
});
if (document.deletedAt) {
- authorize(user, 'restore', document);
+ authorize(user, "restore", document);
// restore a previously deleted document
await document.unarchive(user.id);
await Event.create({
- name: 'documents.restore',
+ name: "documents.restore",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
@@ -438,13 +438,13 @@ router.post('documents.restore', auth(), async ctx => {
ip: ctx.request.ip,
});
} else if (document.archivedAt) {
- authorize(user, 'unarchive', document);
+ authorize(user, "unarchive", document);
// restore a previously archived document
await document.unarchive(user.id);
await Event.create({
- name: 'documents.unarchive',
+ name: "documents.unarchive",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
@@ -454,17 +454,17 @@ router.post('documents.restore', auth(), async ctx => {
});
} else if (revisionId) {
// restore a document to a specific revision
- authorize(user, 'update', document);
+ authorize(user, "update", document);
const revision = await Revision.findByPk(revisionId);
- authorize(document, 'restore', revision);
+ authorize(document, "restore", revision);
document.text = revision.text;
document.title = revision.title;
await document.save();
await Event.create({
- name: 'documents.restore',
+ name: "documents.restore",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
@@ -473,7 +473,7 @@ router.post('documents.restore', auth(), async ctx => {
ip: ctx.request.ip,
});
} else {
- ctx.assertPresent(revisionId, 'revisionId is required');
+ ctx.assertPresent(revisionId, "revisionId is required");
}
ctx.body = {
@@ -482,7 +482,7 @@ router.post('documents.restore', auth(), async ctx => {
};
});
-router.post('documents.search', auth(), pagination(), async ctx => {
+router.post("documents.search", auth(), pagination(), async ctx => {
const {
query,
includeArchived,
@@ -493,34 +493,34 @@ router.post('documents.search', auth(), pagination(), async ctx => {
} = ctx.body;
const { offset, limit } = ctx.state.pagination;
const user = ctx.state.user;
- ctx.assertPresent(query, 'query is required');
+ ctx.assertPresent(query, "query is required");
if (collectionId) {
- ctx.assertUuid(collectionId, 'collectionId must be a UUID');
+ ctx.assertUuid(collectionId, "collectionId must be a UUID");
const collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findByPk(collectionId);
- authorize(user, 'read', collection);
+ authorize(user, "read", collection);
}
let collaboratorIds = undefined;
if (userId) {
- ctx.assertUuid(userId, 'userId must be a UUID');
+ ctx.assertUuid(userId, "userId must be a UUID");
collaboratorIds = [userId];
}
if (dateFilter) {
ctx.assertIn(
dateFilter,
- ['day', 'week', 'month', 'year'],
- 'dateFilter must be one of day,week,month,year'
+ ["day", "week", "month", "year"],
+ "dateFilter must be one of day,week,month,year"
);
}
const results = await Document.searchForUser(user, query, {
- includeArchived: includeArchived === 'true',
- includeDrafts: includeDrafts === 'true',
+ includeArchived: includeArchived === "true",
+ includeDrafts: includeDrafts === "true",
collaboratorIds,
collectionId,
dateFilter,
@@ -545,19 +545,19 @@ router.post('documents.search', auth(), pagination(), async ctx => {
};
});
-router.post('documents.pin', auth(), async ctx => {
+router.post("documents.pin", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertPresent(id, 'id is required');
+ ctx.assertPresent(id, "id is required");
const user = ctx.state.user;
const document = await Document.findByPk(id, { userId: user.id });
- authorize(user, 'pin', document);
+ authorize(user, "pin", document);
document.pinnedById = user.id;
await document.save();
await Event.create({
- name: 'documents.pin',
+ name: "documents.pin",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
@@ -572,19 +572,19 @@ router.post('documents.pin', auth(), async ctx => {
};
});
-router.post('documents.unpin', auth(), async ctx => {
+router.post("documents.unpin", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertPresent(id, 'id is required');
+ ctx.assertPresent(id, "id is required");
const user = ctx.state.user;
const document = await Document.findByPk(id, { userId: user.id });
- authorize(user, 'unpin', document);
+ authorize(user, "unpin", document);
document.pinnedById = null;
await document.save();
await Event.create({
- name: 'documents.unpin',
+ name: "documents.unpin",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
@@ -599,20 +599,20 @@ router.post('documents.unpin', auth(), async ctx => {
};
});
-router.post('documents.star', auth(), async ctx => {
+router.post("documents.star", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertPresent(id, 'id is required');
+ ctx.assertPresent(id, "id is required");
const user = ctx.state.user;
const document = await Document.findByPk(id, { userId: user.id });
- authorize(user, 'read', document);
+ authorize(user, "read", document);
await Star.findOrCreate({
where: { documentId: document.id, userId: user.id },
});
await Event.create({
- name: 'documents.star',
+ name: "documents.star",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
@@ -626,20 +626,20 @@ router.post('documents.star', auth(), async ctx => {
};
});
-router.post('documents.unstar', auth(), async ctx => {
+router.post("documents.unstar", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertPresent(id, 'id is required');
+ ctx.assertPresent(id, "id is required");
const user = ctx.state.user;
const document = await Document.findByPk(id, { userId: user.id });
- authorize(user, 'read', document);
+ authorize(user, "read", document);
await Star.destroy({
where: { documentId: document.id, userId: user.id },
});
await Event.create({
- name: 'documents.unstar',
+ name: "documents.unstar",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
@@ -653,46 +653,46 @@ router.post('documents.unstar', auth(), async ctx => {
};
});
-router.post('documents.create', auth(), async ctx => {
+router.post("documents.create", auth(), async ctx => {
const {
- title = '',
- text = '',
+ title = "",
+ text = "",
publish,
collectionId,
parentDocumentId,
index,
} = ctx.body;
- const editorVersion = ctx.headers['x-editor-version'];
+ const editorVersion = ctx.headers["x-editor-version"];
- ctx.assertUuid(collectionId, 'collectionId must be an uuid');
+ ctx.assertUuid(collectionId, "collectionId must be an uuid");
if (parentDocumentId) {
- ctx.assertUuid(parentDocumentId, 'parentDocumentId must be an uuid');
+ ctx.assertUuid(parentDocumentId, "parentDocumentId must be an uuid");
}
- if (index) ctx.assertPositiveInteger(index, 'index must be an integer (>=0)');
+ if (index) ctx.assertPositiveInteger(index, "index must be an integer (>=0)");
const user = ctx.state.user;
- authorize(user, 'create', Document);
+ authorize(user, "create", Document);
const collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findOne({
where: {
id: collectionId,
teamId: user.teamId,
},
});
- authorize(user, 'publish', collection);
+ authorize(user, "publish", collection);
let parentDocument;
- if (parentDocumentId && collection.type === 'atlas') {
+ if (parentDocumentId && collection.type === "atlas") {
parentDocument = await Document.findOne({
where: {
id: parentDocumentId,
collectionId: collection.id,
},
});
- authorize(user, 'read', parentDocument, { collection });
+ authorize(user, "read", parentDocument, { collection });
}
let document = await Document.create({
@@ -708,7 +708,7 @@ router.post('documents.create', auth(), async ctx => {
});
await Event.create({
- name: 'documents.create',
+ name: "documents.create",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
@@ -721,7 +721,7 @@ router.post('documents.create', auth(), async ctx => {
await document.publish();
await Event.create({
- name: 'documents.publish',
+ name: "documents.publish",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
@@ -745,7 +745,7 @@ router.post('documents.create', auth(), async ctx => {
};
});
-router.post('documents.update', auth(), async ctx => {
+router.post("documents.update", auth(), async ctx => {
const {
id,
title,
@@ -756,18 +756,18 @@ router.post('documents.update', auth(), async ctx => {
lastRevision,
append,
} = ctx.body;
- const editorVersion = ctx.headers['x-editor-version'];
+ const editorVersion = ctx.headers["x-editor-version"];
- ctx.assertPresent(id, 'id is required');
- ctx.assertPresent(title || text, 'title or text is required');
- if (append) ctx.assertPresent(text, 'Text is required while appending');
+ ctx.assertPresent(id, "id is required");
+ ctx.assertPresent(title || text, "title or text is required");
+ if (append) ctx.assertPresent(text, "Text is required while appending");
const user = ctx.state.user;
const document = await Document.findByPk(id, { userId: user.id });
- authorize(user, 'update', document);
+ authorize(user, "update", document);
if (lastRevision && lastRevision !== document.revisionCount) {
- throw new InvalidRequestError('Document has changed since last revision');
+ throw new InvalidRequestError("Document has changed since last revision");
}
// Update document
@@ -801,7 +801,7 @@ router.post('documents.update', auth(), async ctx => {
if (publish) {
await Event.create({
- name: 'documents.publish',
+ name: "documents.publish",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
@@ -811,7 +811,7 @@ router.post('documents.update', auth(), async ctx => {
});
} else {
await Event.create({
- name: 'documents.update',
+ name: "documents.update",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
@@ -834,31 +834,31 @@ router.post('documents.update', auth(), async ctx => {
};
});
-router.post('documents.move', auth(), async ctx => {
+router.post("documents.move", auth(), async ctx => {
const { id, collectionId, parentDocumentId, index } = ctx.body;
- ctx.assertUuid(id, 'id must be a uuid');
- ctx.assertUuid(collectionId, 'collectionId must be a uuid');
+ ctx.assertUuid(id, "id must be a uuid");
+ ctx.assertUuid(collectionId, "collectionId must be a uuid");
if (parentDocumentId) {
- ctx.assertUuid(parentDocumentId, 'parentDocumentId must be a uuid');
+ ctx.assertUuid(parentDocumentId, "parentDocumentId must be a uuid");
}
if (index) {
- ctx.assertPositiveInteger(index, 'index must be a positive integer');
+ ctx.assertPositiveInteger(index, "index must be a positive integer");
}
if (parentDocumentId === id) {
throw new InvalidRequestError(
- 'Infinite loop detected, cannot nest a document inside itself'
+ "Infinite loop detected, cannot nest a document inside itself"
);
}
const user = ctx.state.user;
const document = await Document.findByPk(id, { userId: user.id });
- authorize(user, 'move', document);
+ authorize(user, "move", document);
const { collection } = document;
- if (collection.type !== 'atlas' && parentDocumentId) {
+ if (collection.type !== "atlas" && parentDocumentId) {
throw new InvalidRequestError(
- 'Document cannot be nested in this collection type'
+ "Document cannot be nested in this collection type"
);
}
@@ -866,7 +866,7 @@ router.post('documents.move', auth(), async ctx => {
const parent = await Document.findByPk(parentDocumentId, {
userId: user.id,
});
- authorize(user, 'update', parent);
+ authorize(user, "update", parent);
}
const { documents, collections } = await documentMover({
@@ -891,18 +891,18 @@ router.post('documents.move', auth(), async ctx => {
};
});
-router.post('documents.archive', auth(), async ctx => {
+router.post("documents.archive", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertPresent(id, 'id is required');
+ ctx.assertPresent(id, "id is required");
const user = ctx.state.user;
const document = await Document.findByPk(id, { userId: user.id });
- authorize(user, 'archive', document);
+ authorize(user, "archive", document);
await document.archive(user.id);
await Event.create({
- name: 'documents.archive',
+ name: "documents.archive",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
@@ -917,18 +917,18 @@ router.post('documents.archive', auth(), async ctx => {
};
});
-router.post('documents.delete', auth(), async ctx => {
+router.post("documents.delete", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertPresent(id, 'id is required');
+ ctx.assertPresent(id, "id is required");
const user = ctx.state.user;
const document = await Document.findByPk(id, { userId: user.id });
- authorize(user, 'delete', document);
+ authorize(user, "delete", document);
await document.delete();
await Event.create({
- name: 'documents.delete',
+ name: "documents.delete",
documentId: document.id,
collectionId: document.collectionId,
teamId: document.teamId,
diff --git a/server/api/documents.test.js b/server/api/documents.test.js
index 364e90ca..f71c99a6 100644
--- a/server/api/documents.test.js
+++ b/server/api/documents.test.js
@@ -1,6 +1,6 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../app';
+import TestServer from "fetch-test-server";
+import app from "../app";
import {
Document,
View,
@@ -8,24 +8,24 @@ import {
Revision,
Backlink,
CollectionUser,
-} from '../models';
-import { flushdb, seed } from '../test/support';
+} from "../models";
+import { flushdb, seed } from "../test/support";
import {
buildShare,
buildCollection,
buildUser,
buildDocument,
-} from '../test/factories';
+} from "../test/factories";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('#documents.info', async () => {
- it('should return published document', async () => {
+describe("#documents.info", async () => {
+ it("should return published document", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.info', {
+ const res = await server.post("/api/documents.info", {
body: { token: user.getJwtToken(), id: document.id },
});
const body = await res.json();
@@ -34,10 +34,10 @@ describe('#documents.info', async () => {
expect(body.data.id).toEqual(document.id);
});
- it('should return archived document', async () => {
+ it("should return archived document", async () => {
const { user, document } = await seed();
await document.archive(user.id);
- const res = await server.post('/api/documents.info', {
+ const res = await server.post("/api/documents.info", {
body: { token: user.getJwtToken(), id: document.id },
});
const body = await res.json();
@@ -46,7 +46,7 @@ describe('#documents.info', async () => {
expect(body.data.id).toEqual(document.id);
});
- it('should not return published document in collection not a member of', async () => {
+ it("should not return published document in collection not a member of", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
@@ -54,19 +54,19 @@ describe('#documents.info', async () => {
});
const document = await buildDocument({ collectionId: collection.id });
- const res = await server.post('/api/documents.info', {
+ const res = await server.post("/api/documents.info", {
body: { token: user.getJwtToken(), id: document.id },
});
expect(res.status).toEqual(403);
});
- it('should return drafts', async () => {
+ it("should return drafts", async () => {
const { user, document } = await seed();
document.publishedAt = null;
await document.save();
- const res = await server.post('/api/documents.info', {
+ const res = await server.post("/api/documents.info", {
body: { token: user.getJwtToken(), id: document.id },
});
const body = await res.json();
@@ -75,7 +75,7 @@ describe('#documents.info', async () => {
expect(body.data.id).toEqual(document.id);
});
- it('should return document from shareId without token', async () => {
+ it("should return document from shareId without token", async () => {
const { document, user } = await seed();
const share = await buildShare({
documentId: document.id,
@@ -83,7 +83,7 @@ describe('#documents.info', async () => {
userId: user.id,
});
- const res = await server.post('/api/documents.info', {
+ const res = await server.post("/api/documents.info", {
body: { shareId: share.id },
});
const body = await res.json();
@@ -94,7 +94,7 @@ describe('#documents.info', async () => {
expect(body.data.updatedBy).toEqual(undefined);
});
- it('should not return document from revoked shareId', async () => {
+ it("should not return document from revoked shareId", async () => {
const { document, user } = await seed();
const share = await buildShare({
documentId: document.id,
@@ -103,13 +103,13 @@ describe('#documents.info', async () => {
});
await share.revoke(user.id);
- const res = await server.post('/api/documents.info', {
+ const res = await server.post("/api/documents.info", {
body: { shareId: share.id },
});
expect(res.status).toEqual(400);
});
- it('should not return document from archived shareId', async () => {
+ it("should not return document from archived shareId", async () => {
const { document, user } = await seed();
const share = await buildShare({
documentId: document.id,
@@ -118,13 +118,13 @@ describe('#documents.info', async () => {
});
await document.archive(user.id);
- const res = await server.post('/api/documents.info', {
+ const res = await server.post("/api/documents.info", {
body: { shareId: share.id },
});
expect(res.status).toEqual(400);
});
- it('should return document from shareId with token', async () => {
+ it("should return document from shareId with token", async () => {
const { user, document } = await seed();
const share = await buildShare({
documentId: document.id,
@@ -132,7 +132,7 @@ describe('#documents.info', async () => {
userId: user.id,
});
- const res = await server.post('/api/documents.info', {
+ const res = await server.post("/api/documents.info", {
body: { token: user.getJwtToken(), shareId: share.id },
});
const body = await res.json();
@@ -143,7 +143,7 @@ describe('#documents.info', async () => {
expect(body.data.updatedBy.id).toEqual(user.id);
});
- it('should return draft document from shareId with token', async () => {
+ it("should return draft document from shareId with token", async () => {
const { user, document } = await seed();
document.publishedAt = null;
await document.save();
@@ -154,7 +154,7 @@ describe('#documents.info', async () => {
userId: user.id,
});
- const res = await server.post('/api/documents.info', {
+ const res = await server.post("/api/documents.info", {
body: { token: user.getJwtToken(), shareId: share.id },
});
const body = await res.json();
@@ -165,7 +165,7 @@ describe('#documents.info', async () => {
expect(body.data.updatedBy.id).toEqual(user.id);
});
- it('should return document from shareId in collection not a member of', async () => {
+ it("should return document from shareId in collection not a member of", async () => {
const { user, document, collection } = await seed();
const share = await buildShare({
documentId: document.id,
@@ -176,7 +176,7 @@ describe('#documents.info', async () => {
collection.private = true;
await collection.save();
- const res = await server.post('/api/documents.info', {
+ const res = await server.post("/api/documents.info", {
body: { token: user.getJwtToken(), shareId: share.id },
});
const body = await res.json();
@@ -185,36 +185,36 @@ describe('#documents.info', async () => {
expect(body.data.id).toEqual(document.id);
});
- it('should require authorization without token', async () => {
+ it("should require authorization without token", async () => {
const { document } = await seed();
- const res = await server.post('/api/documents.info', {
+ const res = await server.post("/api/documents.info", {
body: { id: document.id },
});
expect(res.status).toEqual(403);
});
- it('should require authorization with incorrect token', async () => {
+ it("should require authorization with incorrect token", async () => {
const { document } = await seed();
const user = await buildUser();
- const res = await server.post('/api/documents.info', {
+ const res = await server.post("/api/documents.info", {
body: { token: user.getJwtToken(), id: document.id },
});
expect(res.status).toEqual(403);
});
- it('should require a valid shareId', async () => {
- const res = await server.post('/api/documents.info', {
+ it("should require a valid shareId", async () => {
+ const res = await server.post("/api/documents.info", {
body: { shareId: 123 },
});
expect(res.status).toEqual(400);
});
});
-describe('#documents.list', async () => {
- it('should return documents', async () => {
+describe("#documents.list", async () => {
+ it("should return documents", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.list', {
+ const res = await server.post("/api/documents.list", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -224,12 +224,12 @@ describe('#documents.list', async () => {
expect(body.data[0].id).toEqual(document.id);
});
- it('should not return unpublished documents', async () => {
+ it("should not return unpublished documents", async () => {
const { user, document } = await seed();
document.publishedAt = null;
await document.save();
- const res = await server.post('/api/documents.list', {
+ const res = await server.post("/api/documents.list", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -238,12 +238,12 @@ describe('#documents.list', async () => {
expect(body.data.length).toEqual(0);
});
- it('should not return documents in private collections not a member of', async () => {
+ it("should not return documents in private collections not a member of", async () => {
const { user, collection } = await seed();
collection.private = true;
await collection.save();
- const res = await server.post('/api/documents.list', {
+ const res = await server.post("/api/documents.list", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -252,16 +252,16 @@ describe('#documents.list', async () => {
expect(body.data.length).toEqual(0);
});
- it('should allow changing sort direction', async () => {
+ it("should allow changing sort direction", async () => {
const { user, document } = await seed();
const anotherDoc = await buildDocument({
- title: 'another document',
- text: 'random text',
+ title: "another document",
+ text: "random text",
userId: user.id,
teamId: user.teamId,
});
- const res = await server.post('/api/documents.list', {
- body: { token: user.getJwtToken(), direction: 'ASC' },
+ const res = await server.post("/api/documents.list", {
+ body: { token: user.getJwtToken(), direction: "ASC" },
});
const body = await res.json();
@@ -270,9 +270,9 @@ describe('#documents.list', async () => {
expect(body.data[1].id).toEqual(anotherDoc.id);
});
- it('should allow filtering by collection', async () => {
+ it("should allow filtering by collection", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.list', {
+ const res = await server.post("/api/documents.list", {
body: {
token: user.getJwtToken(),
collection: document.collectionId,
@@ -284,7 +284,7 @@ describe('#documents.list', async () => {
expect(body.data.length).toEqual(1);
});
- it('should allow filtering to private collection', async () => {
+ it("should allow filtering to private collection", async () => {
const { user, collection } = await seed();
collection.private = true;
await collection.save();
@@ -293,10 +293,10 @@ describe('#documents.list', async () => {
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read',
+ permission: "read",
});
- const res = await server.post('/api/documents.list', {
+ const res = await server.post("/api/documents.list", {
body: {
token: user.getJwtToken(),
collection: collection.id,
@@ -308,11 +308,11 @@ describe('#documents.list', async () => {
expect(body.data.length).toEqual(1);
});
- it('should return backlinks', async () => {
+ it("should return backlinks", async () => {
const { user, document } = await seed();
const anotherDoc = await buildDocument({
- title: 'another document',
- text: 'random text',
+ title: "another document",
+ text: "random text",
userId: user.id,
teamId: user.teamId,
});
@@ -323,7 +323,7 @@ describe('#documents.list', async () => {
userId: user.id,
});
- const res = await server.post('/api/documents.list', {
+ const res = await server.post("/api/documents.list", {
body: { token: user.getJwtToken(), backlinkDocumentId: document.id },
});
const body = await res.json();
@@ -333,8 +333,8 @@ describe('#documents.list', async () => {
expect(body.data[0].id).toEqual(anotherDoc.id);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/documents.list');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/documents.list");
const body = await res.json();
expect(res.status).toEqual(401);
@@ -342,13 +342,13 @@ describe('#documents.list', async () => {
});
});
-describe('#documents.pinned', async () => {
- it('should return pinned documents', async () => {
+describe("#documents.pinned", async () => {
+ it("should return pinned documents", async () => {
const { user, document } = await seed();
document.pinnedById = user.id;
await document.save();
- const res = await server.post('/api/documents.pinned', {
+ const res = await server.post("/api/documents.pinned", {
body: { token: user.getJwtToken(), collectionId: document.collectionId },
});
const body = await res.json();
@@ -358,7 +358,7 @@ describe('#documents.pinned', async () => {
expect(body.data[0].id).toEqual(document.id);
});
- it('should return pinned documents in private collections member of', async () => {
+ it("should return pinned documents in private collections member of", async () => {
const { user, collection, document } = await seed();
collection.private = true;
await collection.save();
@@ -370,10 +370,10 @@ describe('#documents.pinned', async () => {
collectionId: collection.id,
userId: user.id,
createdById: user.id,
- permission: 'read_write',
+ permission: "read_write",
});
- const res = await server.post('/api/documents.pinned', {
+ const res = await server.post("/api/documents.pinned", {
body: { token: user.getJwtToken(), collectionId: document.collectionId },
});
const body = await res.json();
@@ -383,33 +383,33 @@ describe('#documents.pinned', async () => {
expect(body.data[0].id).toEqual(document.id);
});
- it('should not return pinned documents in private collections not a member of', async () => {
+ it("should not return pinned documents in private collections not a member of", async () => {
const collection = await buildCollection({
private: true,
});
const user = await buildUser({ teamId: collection.teamId });
- const res = await server.post('/api/documents.pinned', {
+ const res = await server.post("/api/documents.pinned", {
body: { token: user.getJwtToken(), collectionId: collection.id },
});
expect(res.status).toEqual(403);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/documents.pinned');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/documents.pinned");
expect(res.status).toEqual(401);
});
});
-describe('#documents.drafts', async () => {
- it('should return unpublished documents', async () => {
+describe("#documents.drafts", async () => {
+ it("should return unpublished documents", async () => {
const { user, document } = await seed();
document.publishedAt = null;
await document.save();
- const res = await server.post('/api/documents.drafts', {
+ const res = await server.post("/api/documents.drafts", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -418,7 +418,7 @@ describe('#documents.drafts', async () => {
expect(body.data.length).toEqual(1);
});
- it('should not return documents in private collections not a member of', async () => {
+ it("should not return documents in private collections not a member of", async () => {
const { user, document, collection } = await seed();
document.publishedAt = null;
await document.save();
@@ -426,7 +426,7 @@ describe('#documents.drafts', async () => {
collection.private = true;
await collection.save();
- const res = await server.post('/api/documents.drafts', {
+ const res = await server.post("/api/documents.drafts", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -436,36 +436,36 @@ describe('#documents.drafts', async () => {
});
});
-describe('#documents.search', async () => {
- it('should return results', async () => {
+describe("#documents.search", async () => {
+ it("should return results", async () => {
const { user } = await seed();
- const res = await server.post('/api/documents.search', {
- body: { token: user.getJwtToken(), query: 'much' },
+ const res = await server.post("/api/documents.search", {
+ body: { token: user.getJwtToken(), query: "much" },
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.data.length).toEqual(1);
- expect(body.data[0].document.text).toEqual('# Much test support');
+ expect(body.data[0].document.text).toEqual("# Much test support");
});
- it('should return results in ranked order', async () => {
+ it("should return results in ranked order", async () => {
const { user } = await seed();
const firstResult = await buildDocument({
- title: 'search term',
- text: 'random text',
+ title: "search term",
+ text: "random text",
userId: user.id,
teamId: user.teamId,
});
const secondResult = await buildDocument({
- title: 'random text',
- text: 'search term',
+ title: "random text",
+ text: "search term",
userId: user.id,
teamId: user.teamId,
});
- const res = await server.post('/api/documents.search', {
- body: { token: user.getJwtToken(), query: 'search term' },
+ const res = await server.post("/api/documents.search", {
+ body: { token: user.getJwtToken(), query: "search term" },
});
const body = await res.json();
@@ -475,23 +475,23 @@ describe('#documents.search', async () => {
expect(body.data[1].document.id).toEqual(secondResult.id);
});
- it('should return partial results in ranked order', async () => {
+ it("should return partial results in ranked order", async () => {
const { user } = await seed();
const firstResult = await buildDocument({
- title: 'search term',
- text: 'random text',
+ title: "search term",
+ text: "random text",
userId: user.id,
teamId: user.teamId,
});
const secondResult = await buildDocument({
- title: 'random text',
- text: 'search term',
+ title: "random text",
+ text: "search term",
userId: user.id,
teamId: user.teamId,
});
- const res = await server.post('/api/documents.search', {
- body: { token: user.getJwtToken(), query: 'sear &' },
+ const res = await server.post("/api/documents.search", {
+ body: { token: user.getJwtToken(), query: "sear &" },
});
const body = await res.json();
@@ -501,17 +501,17 @@ describe('#documents.search', async () => {
expect(body.data[1].document.id).toEqual(secondResult.id);
});
- it('should strip junk from search term', async () => {
+ it("should strip junk from search term", async () => {
const { user } = await seed();
const firstResult = await buildDocument({
- title: 'search term',
- text: 'this is some random text of the document body',
+ title: "search term",
+ text: "this is some random text of the document body",
userId: user.id,
teamId: user.teamId,
});
- const res = await server.post('/api/documents.search', {
- body: { token: user.getJwtToken(), query: 'rando &\\;:()' },
+ const res = await server.post("/api/documents.search", {
+ body: { token: user.getJwtToken(), query: "rando &\\;:()" },
});
const body = await res.json();
@@ -520,19 +520,19 @@ describe('#documents.search', async () => {
expect(body.data[0].document.id).toEqual(firstResult.id);
});
- it('should not return draft documents', async () => {
+ it("should not return draft documents", async () => {
const { user } = await seed();
await buildDocument({
- title: 'search term',
- text: 'search term',
+ title: "search term",
+ text: "search term",
publishedAt: null,
userId: user.id,
teamId: user.teamId,
});
- const res = await server.post('/api/documents.search', {
+ const res = await server.post("/api/documents.search", {
body: {
token: user.getJwtToken(),
- query: 'search term',
+ query: "search term",
},
});
const body = await res.json();
@@ -541,20 +541,20 @@ describe('#documents.search', async () => {
expect(body.data.length).toEqual(0);
});
- it('should return draft documents created by user if chosen', async () => {
+ it("should return draft documents created by user if chosen", async () => {
const { user } = await seed();
const document = await buildDocument({
- title: 'search term',
- text: 'search term',
+ title: "search term",
+ text: "search term",
publishedAt: null,
userId: user.id,
teamId: user.teamId,
});
- const res = await server.post('/api/documents.search', {
+ const res = await server.post("/api/documents.search", {
body: {
token: user.getJwtToken(),
- query: 'search term',
- includeDrafts: 'true',
+ query: "search term",
+ includeDrafts: "true",
},
});
const body = await res.json();
@@ -564,19 +564,19 @@ describe('#documents.search', async () => {
expect(body.data[0].document.id).toEqual(document.id);
});
- it('should not return draft documents created by other users', async () => {
+ it("should not return draft documents created by other users", async () => {
const { user } = await seed();
await buildDocument({
- title: 'search term',
- text: 'search term',
+ title: "search term",
+ text: "search term",
publishedAt: null,
teamId: user.teamId,
});
- const res = await server.post('/api/documents.search', {
+ const res = await server.post("/api/documents.search", {
body: {
token: user.getJwtToken(),
- query: 'search term',
- includeDrafts: 'true',
+ query: "search term",
+ includeDrafts: "true",
},
});
const body = await res.json();
@@ -585,17 +585,17 @@ describe('#documents.search', async () => {
expect(body.data.length).toEqual(0);
});
- it('should not return archived documents', async () => {
+ it("should not return archived documents", async () => {
const { user } = await seed();
const document = await buildDocument({
- title: 'search term',
- text: 'search term',
+ title: "search term",
+ text: "search term",
teamId: user.teamId,
});
await document.archive(user.id);
- const res = await server.post('/api/documents.search', {
- body: { token: user.getJwtToken(), query: 'search term' },
+ const res = await server.post("/api/documents.search", {
+ body: { token: user.getJwtToken(), query: "search term" },
});
const body = await res.json();
@@ -603,20 +603,20 @@ describe('#documents.search', async () => {
expect(body.data.length).toEqual(0);
});
- it('should return archived documents if chosen', async () => {
+ it("should return archived documents if chosen", async () => {
const { user } = await seed();
const document = await buildDocument({
- title: 'search term',
- text: 'search term',
+ title: "search term",
+ text: "search term",
teamId: user.teamId,
});
await document.archive(user.id);
- const res = await server.post('/api/documents.search', {
+ const res = await server.post("/api/documents.search", {
body: {
token: user.getJwtToken(),
- query: 'search term',
- includeArchived: 'true',
+ query: "search term",
+ includeArchived: "true",
},
});
const body = await res.json();
@@ -626,27 +626,27 @@ describe('#documents.search', async () => {
expect(body.data[0].document.id).toEqual(document.id);
});
- it('should return documents for a specific user', async () => {
+ it("should return documents for a specific user", async () => {
const { user } = await seed();
const document = await buildDocument({
- title: 'search term',
- text: 'search term',
+ title: "search term",
+ text: "search term",
teamId: user.teamId,
userId: user.id,
});
// This one will be filtered out
await buildDocument({
- title: 'search term',
- text: 'search term',
+ title: "search term",
+ text: "search term",
teamId: user.teamId,
});
- const res = await server.post('/api/documents.search', {
+ const res = await server.post("/api/documents.search", {
body: {
token: user.getJwtToken(),
- query: 'search term',
+ query: "search term",
userId: user.id,
},
});
@@ -657,7 +657,7 @@ describe('#documents.search', async () => {
expect(body.data[0].document.id).toEqual(document.id);
});
- it('should return documents for a specific private collection', async () => {
+ it("should return documents for a specific private collection", async () => {
const { user, collection } = await seed();
collection.private = true;
await collection.save();
@@ -666,20 +666,20 @@ describe('#documents.search', async () => {
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read',
+ permission: "read",
});
const document = await buildDocument({
- title: 'search term',
- text: 'search term',
+ title: "search term",
+ text: "search term",
teamId: user.teamId,
collectionId: collection.id,
});
- const res = await server.post('/api/documents.search', {
+ const res = await server.post("/api/documents.search", {
body: {
token: user.getJwtToken(),
- query: 'search term',
+ query: "search term",
collectionId: collection.id,
},
});
@@ -690,28 +690,28 @@ describe('#documents.search', async () => {
expect(body.data[0].document.id).toEqual(document.id);
});
- it('should return documents for a specific collection', async () => {
+ it("should return documents for a specific collection", async () => {
const { user } = await seed();
const collection = await buildCollection();
const document = await buildDocument({
- title: 'search term',
- text: 'search term',
+ title: "search term",
+ text: "search term",
teamId: user.teamId,
});
// This one will be filtered out
await buildDocument({
- title: 'search term',
- text: 'search term',
+ title: "search term",
+ text: "search term",
teamId: user.teamId,
collectionId: collection.id,
});
- const res = await server.post('/api/documents.search', {
+ const res = await server.post("/api/documents.search", {
body: {
token: user.getJwtToken(),
- query: 'search term',
+ query: "search term",
collectionId: document.collectionId,
},
});
@@ -722,19 +722,19 @@ describe('#documents.search', async () => {
expect(body.data[0].document.id).toEqual(document.id);
});
- it('should not return documents in private collections not a member of', async () => {
+ it("should not return documents in private collections not a member of", async () => {
const { user } = await seed();
const collection = await buildCollection({ private: true });
await buildDocument({
- title: 'search term',
- text: 'search term',
+ title: "search term",
+ text: "search term",
publishedAt: null,
teamId: user.teamId,
collectionId: collection.id,
});
- const res = await server.post('/api/documents.search', {
- body: { token: user.getJwtToken(), query: 'search term' },
+ const res = await server.post("/api/documents.search", {
+ body: { token: user.getJwtToken(), query: "search term" },
});
const body = await res.json();
@@ -742,22 +742,22 @@ describe('#documents.search', async () => {
expect(body.data.length).toEqual(0);
});
- it('should not allow unknown dateFilter values', async () => {
+ it("should not allow unknown dateFilter values", async () => {
const { user } = await seed();
- const res = await server.post('/api/documents.search', {
+ const res = await server.post("/api/documents.search", {
body: {
token: user.getJwtToken(),
- query: 'search term',
- dateFilter: 'DROP TABLE students;',
+ query: "search term",
+ dateFilter: "DROP TABLE students;",
},
});
expect(res.status).toEqual(400);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/documents.search');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/documents.search");
const body = await res.json();
expect(res.status).toEqual(401);
@@ -765,8 +765,8 @@ describe('#documents.search', async () => {
});
});
-describe('#documents.archived', async () => {
- it('should return archived documents', async () => {
+describe("#documents.archived", async () => {
+ it("should return archived documents", async () => {
const { user } = await seed();
const document = await buildDocument({
userId: user.id,
@@ -774,7 +774,7 @@ describe('#documents.archived', async () => {
});
await document.archive(user.id);
- const res = await server.post('/api/documents.archived', {
+ const res = await server.post("/api/documents.archived", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -783,7 +783,7 @@ describe('#documents.archived', async () => {
expect(body.data.length).toEqual(1);
});
- it('should not return deleted documents', async () => {
+ it("should not return deleted documents", async () => {
const { user } = await seed();
const document = await buildDocument({
userId: user.id,
@@ -791,7 +791,7 @@ describe('#documents.archived', async () => {
});
await document.delete();
- const res = await server.post('/api/documents.archived', {
+ const res = await server.post("/api/documents.archived", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -800,7 +800,7 @@ describe('#documents.archived', async () => {
expect(body.data.length).toEqual(0);
});
- it('should not return documents in private collections not a member of', async () => {
+ it("should not return documents in private collections not a member of", async () => {
const { user } = await seed();
const collection = await buildCollection({ private: true });
@@ -810,7 +810,7 @@ describe('#documents.archived', async () => {
});
await document.archive(user.id);
- const res = await server.post('/api/documents.archived', {
+ const res = await server.post("/api/documents.archived", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -819,16 +819,16 @@ describe('#documents.archived', async () => {
expect(body.data.length).toEqual(0);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/documents.archived');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/documents.archived");
expect(res.status).toEqual(401);
});
});
-describe('#documents.viewed', async () => {
- it('should return empty result if no views', async () => {
+describe("#documents.viewed", async () => {
+ it("should return empty result if no views", async () => {
const { user } = await seed();
- const res = await server.post('/api/documents.viewed', {
+ const res = await server.post("/api/documents.viewed", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -837,11 +837,11 @@ describe('#documents.viewed', async () => {
expect(body.data.length).toEqual(0);
});
- it('should return recently viewed documents', async () => {
+ it("should return recently viewed documents", async () => {
const { user, document } = await seed();
await View.increment({ documentId: document.id, userId: user.id });
- const res = await server.post('/api/documents.viewed', {
+ const res = await server.post("/api/documents.viewed", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -851,12 +851,12 @@ describe('#documents.viewed', async () => {
expect(body.data[0].id).toEqual(document.id);
});
- it('should not return recently viewed but deleted documents', async () => {
+ it("should not return recently viewed but deleted documents", async () => {
const { user, document } = await seed();
await View.increment({ documentId: document.id, userId: user.id });
await document.destroy();
- const res = await server.post('/api/documents.viewed', {
+ const res = await server.post("/api/documents.viewed", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -865,13 +865,13 @@ describe('#documents.viewed', async () => {
expect(body.data.length).toEqual(0);
});
- it('should not return recently viewed documents in collection not a member of', async () => {
+ it("should not return recently viewed documents in collection not a member of", async () => {
const { user, document, collection } = await seed();
await View.increment({ documentId: document.id, userId: user.id });
collection.private = true;
await collection.save();
- const res = await server.post('/api/documents.viewed', {
+ const res = await server.post("/api/documents.viewed", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -880,8 +880,8 @@ describe('#documents.viewed', async () => {
expect(body.data.length).toEqual(0);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/documents.viewed');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/documents.viewed");
const body = await res.json();
expect(res.status).toEqual(401);
@@ -889,10 +889,10 @@ describe('#documents.viewed', async () => {
});
});
-describe('#documents.starred', async () => {
- it('should return empty result if no stars', async () => {
+describe("#documents.starred", async () => {
+ it("should return empty result if no stars", async () => {
const { user } = await seed();
- const res = await server.post('/api/documents.starred', {
+ const res = await server.post("/api/documents.starred", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -901,11 +901,11 @@ describe('#documents.starred', async () => {
expect(body.data.length).toEqual(0);
});
- it('should return starred documents', async () => {
+ it("should return starred documents", async () => {
const { user, document } = await seed();
await Star.create({ documentId: document.id, userId: user.id });
- const res = await server.post('/api/documents.starred', {
+ const res = await server.post("/api/documents.starred", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -916,8 +916,8 @@ describe('#documents.starred', async () => {
expect(body.policies[0].abilities.update).toEqual(true);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/documents.starred');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/documents.starred");
const body = await res.json();
expect(res.status).toEqual(401);
@@ -925,11 +925,11 @@ describe('#documents.starred', async () => {
});
});
-describe('#documents.pin', async () => {
- it('should pin the document', async () => {
+describe("#documents.pin", async () => {
+ it("should pin the document", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.pin', {
+ const res = await server.post("/api/documents.pin", {
body: { token: user.getJwtToken(), id: document.id },
});
const body = await res.json();
@@ -937,37 +937,37 @@ describe('#documents.pin', async () => {
expect(body.data.pinned).toEqual(true);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/documents.pin');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/documents.pin");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { document } = await seed();
const user = await buildUser();
- const res = await server.post('/api/documents.pin', {
+ const res = await server.post("/api/documents.pin", {
body: { token: user.getJwtToken(), id: document.id },
});
expect(res.status).toEqual(403);
});
});
-describe('#documents.restore', () => {
- it('should allow restore of archived documents', async () => {
+describe("#documents.restore", () => {
+ it("should allow restore of archived documents", async () => {
const { user, document } = await seed();
await document.archive(user.id);
- const res = await server.post('/api/documents.restore', {
+ const res = await server.post("/api/documents.restore", {
body: { token: user.getJwtToken(), id: document.id },
});
const body = await res.json();
expect(body.data.archivedAt).toEqual(null);
});
- it('should restore archived when previous parent is archived', async () => {
+ it("should restore archived when previous parent is archived", async () => {
const { user, document } = await seed();
const childDocument = await buildDocument({
userId: user.id,
@@ -978,7 +978,7 @@ describe('#documents.restore', () => {
await childDocument.archive(user.id);
await document.archive(user.id);
- const res = await server.post('/api/documents.restore', {
+ const res = await server.post("/api/documents.restore", {
body: { token: user.getJwtToken(), id: childDocument.id },
});
const body = await res.json();
@@ -986,7 +986,7 @@ describe('#documents.restore', () => {
expect(body.data.archivedAt).toEqual(null);
});
- it('should restore the document to a previous version', async () => {
+ it("should restore the document to a previous version", async () => {
const { user, document } = await seed();
const revision = await Revision.findOne({
where: { documentId: document.id },
@@ -995,17 +995,17 @@ describe('#documents.restore', () => {
const revisionId = revision.id;
// update the document contents
- document.text = 'UPDATED';
+ document.text = "UPDATED";
await document.save();
- const res = await server.post('/api/documents.restore', {
+ const res = await server.post("/api/documents.restore", {
body: { token: user.getJwtToken(), id: document.id, revisionId },
});
const body = await res.json();
expect(body.data.text).toEqual(previousText);
});
- it('should not allow restoring a revision in another document', async () => {
+ it("should not allow restoring a revision in another document", async () => {
const { user, document } = await seed();
const anotherDoc = await buildDocument();
const revision = await Revision.findOne({
@@ -1013,21 +1013,21 @@ describe('#documents.restore', () => {
});
const revisionId = revision.id;
- const res = await server.post('/api/documents.restore', {
+ const res = await server.post("/api/documents.restore", {
body: { token: user.getJwtToken(), id: document.id, revisionId },
});
expect(res.status).toEqual(403);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/documents.restore');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/documents.restore");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { document } = await seed();
const revision = await Revision.findOne({
where: { documentId: document.id },
@@ -1035,20 +1035,20 @@ describe('#documents.restore', () => {
const revisionId = revision.id;
const user = await buildUser();
- const res = await server.post('/api/documents.restore', {
+ const res = await server.post("/api/documents.restore", {
body: { token: user.getJwtToken(), id: document.id, revisionId },
});
expect(res.status).toEqual(403);
});
});
-describe('#documents.unpin', async () => {
- it('should unpin the document', async () => {
+describe("#documents.unpin", async () => {
+ it("should unpin the document", async () => {
const { user, document } = await seed();
document.pinnedBy = user;
await document.save();
- const res = await server.post('/api/documents.unpin', {
+ const res = await server.post("/api/documents.unpin", {
body: { token: user.getJwtToken(), id: document.id },
});
const body = await res.json();
@@ -1056,29 +1056,29 @@ describe('#documents.unpin', async () => {
expect(body.data.pinned).toEqual(false);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/documents.unpin');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/documents.unpin");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { document } = await seed();
const user = await buildUser();
- const res = await server.post('/api/documents.unpin', {
+ const res = await server.post("/api/documents.unpin", {
body: { token: user.getJwtToken(), id: document.id },
});
expect(res.status).toEqual(403);
});
});
-describe('#documents.star', async () => {
- it('should star the document', async () => {
+describe("#documents.star", async () => {
+ it("should star the document", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.star', {
+ const res = await server.post("/api/documents.star", {
body: { token: user.getJwtToken(), id: document.id },
});
@@ -1088,30 +1088,30 @@ describe('#documents.star', async () => {
expect(stars[0].documentId).toEqual(document.id);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/documents.star');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/documents.star");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { document } = await seed();
const user = await buildUser();
- const res = await server.post('/api/documents.star', {
+ const res = await server.post("/api/documents.star", {
body: { token: user.getJwtToken(), id: document.id },
});
expect(res.status).toEqual(403);
});
});
-describe('#documents.unstar', async () => {
- it('should unstar the document', async () => {
+describe("#documents.unstar", async () => {
+ it("should unstar the document", async () => {
const { user, document } = await seed();
await Star.create({ documentId: document.id, userId: user.id });
- const res = await server.post('/api/documents.unstar', {
+ const res = await server.post("/api/documents.unstar", {
body: { token: user.getJwtToken(), id: document.id },
});
@@ -1120,33 +1120,33 @@ describe('#documents.unstar', async () => {
expect(stars.length).toEqual(0);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/documents.star');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/documents.star");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { document } = await seed();
const user = await buildUser();
- const res = await server.post('/api/documents.unstar', {
+ const res = await server.post("/api/documents.unstar", {
body: { token: user.getJwtToken(), id: document.id },
});
expect(res.status).toEqual(403);
});
});
-describe('#documents.create', async () => {
- it('should create as a new document', async () => {
+describe("#documents.create", async () => {
+ it("should create as a new document", async () => {
const { user, collection } = await seed();
- const res = await server.post('/api/documents.create', {
+ const res = await server.post("/api/documents.create", {
body: {
token: user.getJwtToken(),
collectionId: collection.id,
- title: 'new document',
- text: 'hello',
+ title: "new document",
+ text: "hello",
publish: true,
},
});
@@ -1157,47 +1157,47 @@ describe('#documents.create', async () => {
expect(newDocument.collectionId).toBe(collection.id);
});
- it('should not allow very long titles', async () => {
+ it("should not allow very long titles", async () => {
const { user, collection } = await seed();
- const res = await server.post('/api/documents.create', {
+ const res = await server.post("/api/documents.create", {
body: {
token: user.getJwtToken(),
collectionId: collection.id,
title:
- 'This is a really long title that is not acceptable to Outline because it is so ridiculously long that we need to have a limit somewhere',
- text: ' ',
+ "This is a really long title that is not acceptable to Outline because it is so ridiculously long that we need to have a limit somewhere",
+ text: " ",
},
});
expect(res.status).toEqual(400);
});
- it('should create as a child and add to collection if published', async () => {
+ it("should create as a child and add to collection if published", async () => {
const { user, document, collection } = await seed();
- const res = await server.post('/api/documents.create', {
+ const res = await server.post("/api/documents.create", {
body: {
token: user.getJwtToken(),
collectionId: collection.id,
parentDocumentId: document.id,
- title: 'new document',
- text: 'hello',
+ title: "new document",
+ text: "hello",
publish: true,
},
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.title).toBe('new document');
+ expect(body.data.title).toBe("new document");
});
- it('should error with invalid parentDocument', async () => {
+ it("should error with invalid parentDocument", async () => {
const { user, collection } = await seed();
- const res = await server.post('/api/documents.create', {
+ const res = await server.post("/api/documents.create", {
body: {
token: user.getJwtToken(),
collectionId: collection.id,
- parentDocumentId: 'd7a4eb73-fac1-4028-af45-d7e34d54db8e',
- title: 'new document',
- text: 'hello',
+ parentDocumentId: "d7a4eb73-fac1-4028-af45-d7e34d54db8e",
+ title: "new document",
+ text: "hello",
},
});
const body = await res.json();
@@ -1206,45 +1206,45 @@ describe('#documents.create', async () => {
expect(body).toMatchSnapshot();
});
- it('should create as a child and not add to collection', async () => {
+ it("should create as a child and not add to collection", async () => {
const { user, document, collection } = await seed();
- const res = await server.post('/api/documents.create', {
+ const res = await server.post("/api/documents.create", {
body: {
token: user.getJwtToken(),
collectionId: collection.id,
parentDocumentId: document.id,
- title: 'new document',
- text: 'hello',
+ title: "new document",
+ text: "hello",
},
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.title).toBe('new document');
+ expect(body.data.title).toBe("new document");
});
});
-describe('#documents.update', async () => {
- it('should update document details in the root', async () => {
+describe("#documents.update", async () => {
+ it("should update document details in the root", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.update', {
+ const res = await server.post("/api/documents.update", {
body: {
token: user.getJwtToken(),
id: document.id,
- title: 'Updated title',
- text: 'Updated text',
+ title: "Updated title",
+ text: "Updated text",
lastRevision: document.revision,
},
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.title).toBe('Updated title');
- expect(body.data.text).toBe('Updated text');
+ expect(body.data.title).toBe("Updated title");
+ expect(body.data.text).toBe("Updated text");
});
- it('should allow publishing document in private collection', async () => {
+ it("should allow publishing document in private collection", async () => {
const { user, collection, document } = await seed();
document.publishedAt = null;
await document.save();
@@ -1256,15 +1256,15 @@ describe('#documents.update', async () => {
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read_write',
+ permission: "read_write",
});
- const res = await server.post('/api/documents.update', {
+ const res = await server.post("/api/documents.update", {
body: {
token: user.getJwtToken(),
id: document.id,
- title: 'Updated title',
- text: 'Updated text',
+ title: "Updated title",
+ text: "Updated text",
lastRevision: document.revision,
publish: true,
},
@@ -1276,31 +1276,31 @@ describe('#documents.update', async () => {
expect(body.policies[0].abilities.update).toEqual(true);
});
- it('should not edit archived document', async () => {
+ it("should not edit archived document", async () => {
const { user, document } = await seed();
await document.archive();
- const res = await server.post('/api/documents.update', {
+ const res = await server.post("/api/documents.update", {
body: {
token: user.getJwtToken(),
id: document.id,
- title: 'Updated title',
- text: 'Updated text',
+ title: "Updated title",
+ text: "Updated text",
lastRevision: document.revision,
},
});
expect(res.status).toEqual(403);
});
- it('should not create new version when autosave=true', async () => {
+ it("should not create new version when autosave=true", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.update', {
+ const res = await server.post("/api/documents.update", {
body: {
token: user.getJwtToken(),
id: document.id,
- title: 'Updated title',
- text: 'Updated text',
+ title: "Updated title",
+ text: "Updated text",
lastRevision: document.revision,
autosave: true,
},
@@ -1310,21 +1310,21 @@ describe('#documents.update', async () => {
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.title).toBe('Updated title');
- expect(body.data.text).toBe('Updated text');
+ expect(body.data.title).toBe("Updated title");
+ expect(body.data.text).toBe("Updated text");
const revisionRecords = await Revision.count();
expect(revisionRecords).toBe(prevRevisionRecords);
});
- it('should fail if document lastRevision does not match', async () => {
+ it("should fail if document lastRevision does not match", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.update', {
+ const res = await server.post("/api/documents.update", {
body: {
token: user.getJwtToken(),
id: document.id,
- text: 'Updated text',
+ text: "Updated text",
lastRevision: 123,
},
});
@@ -1334,18 +1334,18 @@ describe('#documents.update', async () => {
expect(body).toMatchSnapshot();
});
- it('should update document details for children', async () => {
+ it("should update document details for children", async () => {
const { user, document, collection } = await seed();
collection.documentStructure = [
{
- id: 'af1da94b-9591-4bab-897c-11774b804b77',
- url: '/d/some-beef-RSZwQDsfpc',
- title: 'some beef',
+ id: "af1da94b-9591-4bab-897c-11774b804b77",
+ url: "/d/some-beef-RSZwQDsfpc",
+ title: "some beef",
children: [
{
- id: 'ab1da94b-9591-4bab-897c-11774b804b66',
- url: '/d/another-doc-RSZwQDsfpc',
- title: 'Another doc',
+ id: "ab1da94b-9591-4bab-897c-11774b804b66",
+ url: "/d/another-doc-RSZwQDsfpc",
+ title: "Another doc",
children: [],
},
{ ...document.toJSON(), children: [] },
@@ -1354,20 +1354,20 @@ describe('#documents.update', async () => {
];
await collection.save();
- const res = await server.post('/api/documents.update', {
+ const res = await server.post("/api/documents.update", {
body: {
token: user.getJwtToken(),
id: document.id,
- title: 'Updated title',
+ title: "Updated title",
},
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.title).toBe('Updated title');
+ expect(body.data.title).toBe("Updated title");
});
- it('allows editing by read-write collection user', async () => {
+ it("allows editing by read-write collection user", async () => {
const { admin, document, collection } = await seed();
collection.private = true;
await collection.save();
@@ -1376,14 +1376,14 @@ describe('#documents.update', async () => {
collectionId: collection.id,
userId: admin.id,
createdById: admin.id,
- permission: 'read_write',
+ permission: "read_write",
});
- const res = await server.post('/api/documents.update', {
+ const res = await server.post("/api/documents.update", {
body: {
token: admin.getJwtToken(),
id: document.id,
- text: 'Changed text',
+ text: "Changed text",
lastRevision: document.revision,
},
});
@@ -1391,11 +1391,11 @@ describe('#documents.update', async () => {
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.text).toBe('Changed text');
+ expect(body.data.text).toBe("Changed text");
expect(body.data.updatedBy.id).toBe(admin.id);
});
- it('does not allow editing by read-only collection user', async () => {
+ it("does not allow editing by read-only collection user", async () => {
const { user, document, collection } = await seed();
collection.private = true;
await collection.save();
@@ -1404,14 +1404,14 @@ describe('#documents.update', async () => {
collectionId: collection.id,
userId: user.id,
createdById: user.id,
- permission: 'read',
+ permission: "read",
});
- const res = await server.post('/api/documents.update', {
+ const res = await server.post("/api/documents.update", {
body: {
token: user.getJwtToken(),
id: document.id,
- text: 'Changed text',
+ text: "Changed text",
lastRevision: document.revision,
},
});
@@ -1419,14 +1419,14 @@ describe('#documents.update', async () => {
expect(res.status).toEqual(403);
});
- it('should append document with text', async () => {
+ it("should append document with text", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.update', {
+ const res = await server.post("/api/documents.update", {
body: {
token: user.getJwtToken(),
id: document.id,
- text: 'Additional text',
+ text: "Additional text",
lastRevision: document.revision,
append: true,
},
@@ -1434,19 +1434,19 @@ describe('#documents.update', async () => {
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.text).toBe(document.text + 'Additional text');
+ expect(body.data.text).toBe(document.text + "Additional text");
expect(body.data.updatedBy.id).toBe(user.id);
});
- it('should require text while appending', async () => {
+ it("should require text while appending", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.update', {
+ const res = await server.post("/api/documents.update", {
body: {
token: user.getJwtToken(),
id: document.id,
lastRevision: document.revision,
- title: 'Updated Title',
+ title: "Updated Title",
append: true,
},
});
@@ -1456,28 +1456,28 @@ describe('#documents.update', async () => {
expect(body).toMatchSnapshot();
});
- it('should allow setting empty text', async () => {
+ it("should allow setting empty text", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.update', {
+ const res = await server.post("/api/documents.update", {
body: {
token: user.getJwtToken(),
id: document.id,
lastRevision: document.revision,
- title: 'Updated Title',
- text: '',
+ title: "Updated Title",
+ text: "",
},
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.text).toBe('');
+ expect(body.data.text).toBe("");
});
- it('should require authentication', async () => {
+ it("should require authentication", async () => {
const { document } = await seed();
- const res = await server.post('/api/documents.update', {
- body: { id: document.id, text: 'Updated' },
+ const res = await server.post("/api/documents.update", {
+ body: { id: document.id, text: "Updated" },
});
const body = await res.json();
@@ -1485,20 +1485,20 @@ describe('#documents.update', async () => {
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { document } = await seed();
const user = await buildUser();
- const res = await server.post('/api/documents.update', {
- body: { token: user.getJwtToken(), id: document.id, text: 'Updated' },
+ const res = await server.post("/api/documents.update", {
+ body: { token: user.getJwtToken(), id: document.id, text: "Updated" },
});
expect(res.status).toEqual(403);
});
});
-describe('#documents.archive', async () => {
- it('should allow archiving document', async () => {
+describe("#documents.archive", async () => {
+ it("should allow archiving document", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.archive', {
+ const res = await server.post("/api/documents.archive", {
body: { token: user.getJwtToken(), id: document.id },
});
const body = await res.json();
@@ -1508,19 +1508,19 @@ describe('#documents.archive', async () => {
expect(body.data.archivedAt).toBeTruthy();
});
- it('should require authentication', async () => {
+ it("should require authentication", async () => {
const { document } = await seed();
- const res = await server.post('/api/documents.archive', {
+ const res = await server.post("/api/documents.archive", {
body: { id: document.id },
});
expect(res.status).toEqual(401);
});
});
-describe('#documents.delete', async () => {
- it('should allow deleting document', async () => {
+describe("#documents.delete", async () => {
+ it("should allow deleting document", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/documents.delete', {
+ const res = await server.post("/api/documents.delete", {
body: { token: user.getJwtToken(), id: document.id },
});
const body = await res.json();
@@ -1529,12 +1529,12 @@ describe('#documents.delete', async () => {
expect(body.success).toEqual(true);
});
- it('should allow deleting document without collection', async () => {
+ it("should allow deleting document without collection", async () => {
const { user, document, collection } = await seed();
// delete collection without hooks to trigger document deletion
await collection.destroy({ hooks: false });
- const res = await server.post('/api/documents.delete', {
+ const res = await server.post("/api/documents.delete", {
body: { token: user.getJwtToken(), id: document.id },
});
const body = await res.json();
@@ -1543,9 +1543,9 @@ describe('#documents.delete', async () => {
expect(body.success).toEqual(true);
});
- it('should require authentication', async () => {
+ it("should require authentication", async () => {
const { document } = await seed();
- const res = await server.post('/api/documents.delete', {
+ const res = await server.post("/api/documents.delete", {
body: { id: document.id },
});
const body = await res.json();
diff --git a/server/api/events.js b/server/api/events.js
index 84e4fc7c..3b0b3dd8 100644
--- a/server/api/events.js
+++ b/server/api/events.js
@@ -1,19 +1,19 @@
// @flow
-import Sequelize from 'sequelize';
-import Router from 'koa-router';
-import auth from '../middlewares/authentication';
-import pagination from './middlewares/pagination';
-import { presentEvent } from '../presenters';
-import { Event, Team, User } from '../models';
-import policy from '../policies';
+import Sequelize from "sequelize";
+import Router from "koa-router";
+import auth from "../middlewares/authentication";
+import pagination from "./middlewares/pagination";
+import { presentEvent } from "../presenters";
+import { Event, Team, User } from "../models";
+import policy from "../policies";
const Op = Sequelize.Op;
const { authorize } = policy;
const router = new Router();
-router.post('events.list', auth(), pagination(), async ctx => {
- let { sort = 'createdAt', direction, auditLog = false } = ctx.body;
- if (direction !== 'ASC') direction = 'DESC';
+router.post("events.list", auth(), pagination(), async ctx => {
+ let { sort = "createdAt", direction, auditLog = false } = ctx.body;
+ if (direction !== "ASC") direction = "DESC";
const user = ctx.state.user;
const paranoid = false;
@@ -33,7 +33,7 @@ router.post('events.list', auth(), pagination(), async ctx => {
};
if (auditLog) {
- authorize(user, 'auditLog', Team);
+ authorize(user, "auditLog", Team);
where.name = Event.AUDIT_EVENTS;
}
@@ -43,7 +43,7 @@ router.post('events.list', auth(), pagination(), async ctx => {
include: [
{
model: User,
- as: 'actor',
+ as: "actor",
paranoid: false,
},
],
diff --git a/server/api/events.test.js b/server/api/events.test.js
index 6e6e037d..373871bb 100644
--- a/server/api/events.test.js
+++ b/server/api/events.test.js
@@ -1,21 +1,21 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../app';
-import { flushdb, seed } from '../test/support';
-import { buildEvent } from '../test/factories';
+import TestServer from "fetch-test-server";
+import app from "../app";
+import { flushdb, seed } from "../test/support";
+import { buildEvent } from "../test/factories";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('#events.list', async () => {
- it('should only return activity events', async () => {
+describe("#events.list", async () => {
+ it("should only return activity events", async () => {
const { user, admin, document, collection } = await seed();
// private event
await buildEvent({
- name: 'users.promote',
+ name: "users.promote",
teamId: user.teamId,
actorId: admin.id,
userId: user.id,
@@ -23,13 +23,13 @@ describe('#events.list', async () => {
// event viewable in activity stream
const event = await buildEvent({
- name: 'documents.publish',
+ name: "documents.publish",
collectionId: collection.id,
documentId: document.id,
teamId: user.teamId,
actorId: admin.id,
});
- const res = await server.post('/api/events.list', {
+ const res = await server.post("/api/events.list", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -39,12 +39,12 @@ describe('#events.list', async () => {
expect(body.data[0].id).toEqual(event.id);
});
- it('should return events with deleted actors', async () => {
+ it("should return events with deleted actors", async () => {
const { user, admin, document, collection } = await seed();
// event viewable in activity stream
const event = await buildEvent({
- name: 'documents.publish',
+ name: "documents.publish",
collectionId: collection.id,
documentId: document.id,
teamId: user.teamId,
@@ -53,7 +53,7 @@ describe('#events.list', async () => {
await user.destroy();
- const res = await server.post('/api/events.list', {
+ const res = await server.post("/api/events.list", {
body: { token: admin.getJwtToken() },
});
@@ -64,8 +64,8 @@ describe('#events.list', async () => {
expect(body.data[0].id).toEqual(event.id);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/events.list');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/events.list");
const body = await res.json();
expect(res.status).toEqual(401);
diff --git a/server/api/groups.js b/server/api/groups.js
index 4aceb55a..5dbbefe3 100644
--- a/server/api/groups.js
+++ b/server/api/groups.js
@@ -1,26 +1,26 @@
// @flow
-import Router from 'koa-router';
-import auth from '../middlewares/authentication';
-import pagination from './middlewares/pagination';
-import { Op } from '../sequelize';
-import { MAX_AVATAR_DISPLAY } from '../../shared/constants';
+import Router from "koa-router";
+import auth from "../middlewares/authentication";
+import pagination from "./middlewares/pagination";
+import { Op } from "../sequelize";
+import { MAX_AVATAR_DISPLAY } from "../../shared/constants";
import {
presentGroup,
presentPolicies,
presentUser,
presentGroupMembership,
-} from '../presenters';
-import { User, Event, Group, GroupUser } from '../models';
-import policy from '../policies';
+} from "../presenters";
+import { User, Event, Group, GroupUser } from "../models";
+import policy from "../policies";
const { authorize } = policy;
const router = new Router();
-router.post('groups.list', auth(), pagination(), async ctx => {
- const { sort = 'updatedAt' } = ctx.body;
+router.post("groups.list", auth(), pagination(), async ctx => {
+ const { sort = "updatedAt" } = ctx.body;
let direction = ctx.body.direction;
- if (direction !== 'ASC') direction = 'DESC';
+ if (direction !== "ASC") direction = "DESC";
const user = ctx.state.user;
let groups = await Group.findAll({
@@ -55,13 +55,13 @@ router.post('groups.list', auth(), pagination(), async ctx => {
};
});
-router.post('groups.info', auth(), async ctx => {
+router.post("groups.info", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const group = await Group.findByPk(id);
- authorize(user, 'read', group);
+ authorize(user, "read", group);
ctx.body = {
data: presentGroup(group),
@@ -69,13 +69,13 @@ router.post('groups.info', auth(), async ctx => {
};
});
-router.post('groups.create', auth(), async ctx => {
+router.post("groups.create", auth(), async ctx => {
const { name } = ctx.body;
- ctx.assertPresent(name, 'name is required');
+ ctx.assertPresent(name, "name is required");
const user = ctx.state.user;
- authorize(user, 'create', Group);
+ authorize(user, "create", Group);
let group = await Group.create({
name,
teamId: user.teamId,
@@ -86,7 +86,7 @@ router.post('groups.create', auth(), async ctx => {
group = await Group.findByPk(group.id);
await Event.create({
- name: 'groups.create',
+ name: "groups.create",
actorId: user.id,
teamId: user.teamId,
modelId: group.id,
@@ -100,22 +100,22 @@ router.post('groups.create', auth(), async ctx => {
};
});
-router.post('groups.update', auth(), async ctx => {
+router.post("groups.update", auth(), async ctx => {
const { id, name } = ctx.body;
- ctx.assertPresent(name, 'name is required');
- ctx.assertUuid(id, 'id is required');
+ ctx.assertPresent(name, "name is required");
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const group = await Group.findByPk(id);
- authorize(user, 'update', group);
+ authorize(user, "update", group);
group.name = name;
if (group.changed()) {
await group.save();
await Event.create({
- name: 'groups.update',
+ name: "groups.update",
teamId: user.teamId,
actorId: user.id,
modelId: group.id,
@@ -130,18 +130,18 @@ router.post('groups.update', auth(), async ctx => {
};
});
-router.post('groups.delete', auth(), async ctx => {
+router.post("groups.delete", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const { user } = ctx.state;
const group = await Group.findByPk(id);
- authorize(user, 'delete', group);
+ authorize(user, "delete", group);
await group.destroy();
await Event.create({
- name: 'groups.delete',
+ name: "groups.delete",
actorId: user.id,
modelId: group.id,
teamId: group.teamId,
@@ -154,13 +154,13 @@ router.post('groups.delete', auth(), async ctx => {
};
});
-router.post('groups.memberships', auth(), pagination(), async ctx => {
+router.post("groups.memberships", auth(), pagination(), async ctx => {
const { id, query } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const group = await Group.findByPk(id);
- authorize(user, 'read', group);
+ authorize(user, "read", group);
let userWhere;
if (query) {
@@ -173,13 +173,13 @@ router.post('groups.memberships', auth(), pagination(), async ctx => {
const memberships = await GroupUser.findAll({
where: { groupId: id },
- order: [['createdAt', 'DESC']],
+ order: [["createdAt", "DESC"]],
offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit,
include: [
{
model: User,
- as: 'user',
+ as: "user",
where: userWhere,
required: true,
},
@@ -195,16 +195,16 @@ router.post('groups.memberships', auth(), pagination(), async ctx => {
};
});
-router.post('groups.add_user', auth(), async ctx => {
+router.post("groups.add_user", auth(), async ctx => {
const { id, userId } = ctx.body;
- ctx.assertUuid(id, 'id is required');
- ctx.assertUuid(userId, 'userId is required');
+ ctx.assertUuid(id, "id is required");
+ ctx.assertUuid(userId, "userId is required");
const user = await User.findByPk(userId);
- authorize(ctx.state.user, 'read', user);
+ authorize(ctx.state.user, "read", user);
let group = await Group.findByPk(id);
- authorize(ctx.state.user, 'update', group);
+ authorize(ctx.state.user, "update", group);
let membership = await GroupUser.findOne({
where: {
@@ -230,7 +230,7 @@ router.post('groups.add_user', auth(), async ctx => {
group = await Group.findByPk(id);
await Event.create({
- name: 'groups.add_user',
+ name: "groups.add_user",
userId,
teamId: user.teamId,
modelId: group.id,
@@ -249,21 +249,21 @@ router.post('groups.add_user', auth(), async ctx => {
};
});
-router.post('groups.remove_user', auth(), async ctx => {
+router.post("groups.remove_user", auth(), async ctx => {
const { id, userId } = ctx.body;
- ctx.assertUuid(id, 'id is required');
- ctx.assertUuid(userId, 'userId is required');
+ ctx.assertUuid(id, "id is required");
+ ctx.assertUuid(userId, "userId is required");
let group = await Group.findByPk(id);
- authorize(ctx.state.user, 'update', group);
+ authorize(ctx.state.user, "update", group);
const user = await User.findByPk(userId);
- authorize(ctx.state.user, 'read', user);
+ authorize(ctx.state.user, "read", user);
await group.removeUser(user);
await Event.create({
- name: 'groups.remove_user',
+ name: "groups.remove_user",
userId,
modelId: group.id,
teamId: user.teamId,
diff --git a/server/api/groups.test.js b/server/api/groups.test.js
index 9381721b..732eec9c 100644
--- a/server/api/groups.test.js
+++ b/server/api/groups.test.js
@@ -1,21 +1,21 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../app';
-import { flushdb } from '../test/support';
-import { buildUser, buildGroup } from '../test/factories';
-import { Event } from '../models';
+import TestServer from "fetch-test-server";
+import app from "../app";
+import { flushdb } from "../test/support";
+import { buildUser, buildGroup } from "../test/factories";
+import { Event } from "../models";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('#groups.create', async () => {
- it('should create a group', async () => {
- const name = 'hello I am a group';
+describe("#groups.create", async () => {
+ it("should create a group", async () => {
+ const name = "hello I am a group";
const user = await buildUser({ isAdmin: true });
- const res = await server.post('/api/groups.create', {
+ const res = await server.post("/api/groups.create", {
body: { token: user.getJwtToken(), name },
});
@@ -26,11 +26,11 @@ describe('#groups.create', async () => {
});
});
-describe('#groups.update', async () => {
- it('should require authentication', async () => {
+describe("#groups.update", async () => {
+ it("should require authentication", async () => {
const group = await buildGroup();
- const res = await server.post('/api/groups.update', {
- body: { id: group.id, name: 'Test' },
+ const res = await server.post("/api/groups.update", {
+ body: { id: group.id, name: "Test" },
});
const body = await res.json();
@@ -38,26 +38,26 @@ describe('#groups.update', async () => {
expect(body).toMatchSnapshot();
});
- it('should require admin', async () => {
+ it("should require admin", async () => {
const group = await buildGroup();
const user = await buildUser();
- const res = await server.post('/api/groups.update', {
- body: { token: user.getJwtToken(), id: group.id, name: 'Test' },
+ const res = await server.post("/api/groups.update", {
+ body: { token: user.getJwtToken(), id: group.id, name: "Test" },
});
expect(res.status).toEqual(403);
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const group = await buildGroup();
const user = await buildUser({ isAdmin: true });
- const res = await server.post('/api/groups.update', {
- body: { token: user.getJwtToken(), id: group.id, name: 'Test' },
+ const res = await server.post("/api/groups.update", {
+ body: { token: user.getJwtToken(), id: group.id, name: "Test" },
});
expect(res.status).toEqual(403);
});
- describe('when user is admin', async () => {
+ describe("when user is admin", async () => {
let user, group;
beforeEach(async () => {
@@ -65,9 +65,9 @@ describe('#groups.update', async () => {
group = await buildGroup({ teamId: user.teamId });
});
- it('allows admin to edit a group', async () => {
- const res = await server.post('/api/groups.update', {
- body: { token: user.getJwtToken(), id: group.id, name: 'Test' },
+ it("allows admin to edit a group", async () => {
+ const res = await server.post("/api/groups.update", {
+ body: { token: user.getJwtToken(), id: group.id, name: "Test" },
});
const events = await Event.findAll();
@@ -75,11 +75,11 @@ describe('#groups.update', async () => {
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.name).toBe('Test');
+ expect(body.data.name).toBe("Test");
});
- it('does not create an event if the update is a noop', async () => {
- const res = await server.post('/api/groups.update', {
+ it("does not create an event if the update is a noop", async () => {
+ const res = await server.post("/api/groups.update", {
body: { token: user.getJwtToken(), id: group.id, name: group.name },
});
@@ -91,17 +91,17 @@ describe('#groups.update', async () => {
expect(body.data.name).toBe(group.name);
});
- it('fails with validation error when name already taken', async () => {
+ it("fails with validation error when name already taken", async () => {
await buildGroup({
teamId: user.teamId,
- name: 'test',
+ name: "test",
});
- const res = await server.post('/api/groups.update', {
+ const res = await server.post("/api/groups.update", {
body: {
token: user.getJwtToken(),
id: group.id,
- name: 'TEST',
+ name: "TEST",
},
});
@@ -112,40 +112,40 @@ describe('#groups.update', async () => {
});
});
-describe('#groups.list', async () => {
- it('should require authentication', async () => {
- const res = await server.post('/api/groups.list');
+describe("#groups.list", async () => {
+ it("should require authentication", async () => {
+ const res = await server.post("/api/groups.list");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should return groups with memberships preloaded', async () => {
+ it("should return groups with memberships preloaded", async () => {
const user = await buildUser();
const group = await buildGroup({ teamId: user.teamId });
await group.addUser(user, { through: { createdById: user.id } });
- const res = await server.post('/api/groups.list', {
+ const res = await server.post("/api/groups.list", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data['groups'].length).toEqual(1);
- expect(body.data['groups'][0].id).toEqual(group.id);
+ expect(body.data["groups"].length).toEqual(1);
+ expect(body.data["groups"][0].id).toEqual(group.id);
- expect(body.data['groupMemberships'].length).toEqual(1);
- expect(body.data['groupMemberships'][0].groupId).toEqual(group.id);
- expect(body.data['groupMemberships'][0].user.id).toEqual(user.id);
+ expect(body.data["groupMemberships"].length).toEqual(1);
+ expect(body.data["groupMemberships"][0].groupId).toEqual(group.id);
+ expect(body.data["groupMemberships"][0].user.id).toEqual(user.id);
expect(body.policies.length).toEqual(1);
expect(body.policies[0].abilities.read).toEqual(true);
});
- it('should return groups when membership user is deleted', async () => {
+ it("should return groups when membership user is deleted", async () => {
const me = await buildUser();
const user = await buildUser({ teamId: me.teamId });
const group = await buildGroup({ teamId: user.teamId });
@@ -154,31 +154,31 @@ describe('#groups.list', async () => {
await group.addUser(me, { through: { createdById: me.id } });
await user.destroy();
- const res = await server.post('/api/groups.list', {
+ const res = await server.post("/api/groups.list", {
body: { token: me.getJwtToken() },
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data['groups'].length).toEqual(1);
- expect(body.data['groups'][0].id).toEqual(group.id);
+ expect(body.data["groups"].length).toEqual(1);
+ expect(body.data["groups"][0].id).toEqual(group.id);
- expect(body.data['groupMemberships'].length).toEqual(1);
- expect(body.data['groupMemberships'][0].groupId).toEqual(group.id);
- expect(body.data['groupMemberships'][0].user.id).toEqual(me.id);
+ expect(body.data["groupMemberships"].length).toEqual(1);
+ expect(body.data["groupMemberships"][0].groupId).toEqual(group.id);
+ expect(body.data["groupMemberships"][0].user.id).toEqual(me.id);
expect(body.policies.length).toEqual(1);
expect(body.policies[0].abilities.read).toEqual(true);
});
});
-describe('#groups.info', async () => {
- it('should return group if admin', async () => {
+describe("#groups.info", async () => {
+ it("should return group if admin", async () => {
const user = await buildUser({ isAdmin: true });
const group = await buildGroup({ teamId: user.teamId });
- const res = await server.post('/api/groups.info', {
+ const res = await server.post("/api/groups.info", {
body: { token: user.getJwtToken(), id: group.id },
});
@@ -188,12 +188,12 @@ describe('#groups.info', async () => {
expect(body.data.id).toEqual(group.id);
});
- it('should return group if member', async () => {
+ it("should return group if member", async () => {
const user = await buildUser();
const group = await buildGroup({ teamId: user.teamId });
await group.addUser(user, { through: { createdById: user.id } });
- const res = await server.post('/api/groups.info', {
+ const res = await server.post("/api/groups.info", {
body: { token: user.getJwtToken(), id: group.id },
});
@@ -203,39 +203,39 @@ describe('#groups.info', async () => {
expect(body.data.id).toEqual(group.id);
});
- it('should not return group if non-member, non-admin', async () => {
+ it("should not return group if non-member, non-admin", async () => {
const user = await buildUser();
const group = await buildGroup({ teamId: user.teamId });
- const res = await server.post('/api/groups.info', {
+ const res = await server.post("/api/groups.info", {
body: { token: user.getJwtToken(), id: group.id },
});
expect(res.status).toEqual(403);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/groups.info');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/groups.info");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const user = await buildUser();
const group = await buildGroup();
- const res = await server.post('/api/groups.info', {
+ const res = await server.post("/api/groups.info", {
body: { token: user.getJwtToken(), id: group.id },
});
expect(res.status).toEqual(403);
});
});
-describe('#groups.delete', async () => {
- it('should require authentication', async () => {
+describe("#groups.delete", async () => {
+ it("should require authentication", async () => {
const group = await buildGroup();
- const res = await server.post('/api/groups.delete', {
+ const res = await server.post("/api/groups.delete", {
body: { id: group.id },
});
const body = await res.json();
@@ -244,30 +244,30 @@ describe('#groups.delete', async () => {
expect(body).toMatchSnapshot();
});
- it('should require admin', async () => {
+ it("should require admin", async () => {
const group = await buildGroup();
const user = await buildUser();
- const res = await server.post('/api/groups.delete', {
+ const res = await server.post("/api/groups.delete", {
body: { token: user.getJwtToken(), id: group.id },
});
expect(res.status).toEqual(403);
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const group = await buildGroup();
const user = await buildUser({ isAdmin: true });
- const res = await server.post('/api/groups.delete', {
+ const res = await server.post("/api/groups.delete", {
body: { token: user.getJwtToken(), id: group.id },
});
expect(res.status).toEqual(403);
});
- it('allows admin to delete a group', async () => {
+ it("allows admin to delete a group", async () => {
const user = await buildUser({ isAdmin: true });
const group = await buildGroup({ teamId: user.teamId });
- const res = await server.post('/api/groups.delete', {
+ const res = await server.post("/api/groups.delete", {
body: { token: user.getJwtToken(), id: group.id },
});
@@ -277,14 +277,14 @@ describe('#groups.delete', async () => {
});
});
-describe('#groups.memberships', async () => {
- it('should return members in a group', async () => {
+describe("#groups.memberships", async () => {
+ it("should return members in a group", async () => {
const user = await buildUser();
const group = await buildGroup({ teamId: user.teamId });
await group.addUser(user, { through: { createdById: user.id } });
- const res = await server.post('/api/groups.memberships', {
+ const res = await server.post("/api/groups.memberships", {
body: { token: user.getJwtToken(), id: group.id },
});
@@ -297,10 +297,10 @@ describe('#groups.memberships', async () => {
expect(body.data.groupMemberships[0].user.id).toEqual(user.id);
});
- it('should allow filtering members in group by name', async () => {
+ it("should allow filtering members in group by name", async () => {
const user = await buildUser();
const user2 = await buildUser({ name: "Won't find" });
- const user3 = await buildUser({ teamId: user.teamId, name: 'Deleted' });
+ const user3 = await buildUser({ teamId: user.teamId, name: "Deleted" });
const group = await buildGroup({ teamId: user.teamId });
await group.addUser(user, { through: { createdById: user.id } });
@@ -309,7 +309,7 @@ describe('#groups.memberships', async () => {
await user3.destroy();
- const res = await server.post('/api/groups.memberships', {
+ const res = await server.post("/api/groups.memberships", {
body: {
token: user.getJwtToken(),
id: group.id,
@@ -323,33 +323,33 @@ describe('#groups.memberships', async () => {
expect(body.data.users[0].id).toEqual(user.id);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/groups.memberships');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/groups.memberships");
const body = await res.json();
expect(res.status).toEqual(401);
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const user = await buildUser();
const group = await buildGroup();
- const res = await server.post('/api/groups.memberships', {
+ const res = await server.post("/api/groups.memberships", {
body: { token: user.getJwtToken(), id: group.id },
});
expect(res.status).toEqual(403);
});
});
-describe('#groups.add_user', async () => {
- it('should add user to group', async () => {
+describe("#groups.add_user", async () => {
+ it("should add user to group", async () => {
const user = await buildUser({ isAdmin: true });
const group = await buildGroup({
teamId: user.teamId,
});
- const res = await server.post('/api/groups.add_user', {
+ const res = await server.post("/api/groups.add_user", {
body: {
token: user.getJwtToken(),
id: group.id,
@@ -362,19 +362,19 @@ describe('#groups.add_user', async () => {
expect(users.length).toEqual(1);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/groups.add_user');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/groups.add_user");
expect(res.status).toEqual(401);
});
- it('should require user in team', async () => {
+ it("should require user in team", async () => {
const user = await buildUser({ isAdmin: true });
const group = await buildGroup({
teamId: user.teamId,
});
const anotherUser = await buildUser();
- const res = await server.post('/api/groups.add_user', {
+ const res = await server.post("/api/groups.add_user", {
body: {
token: user.getJwtToken(),
id: group.id,
@@ -388,14 +388,14 @@ describe('#groups.add_user', async () => {
expect(body).toMatchSnapshot();
});
- it('should require admin', async () => {
+ it("should require admin", async () => {
const user = await buildUser();
const group = await buildGroup({
teamId: user.teamId,
});
const anotherUser = await buildUser({ teamId: user.teamId });
- const res = await server.post('/api/groups.add_user', {
+ const res = await server.post("/api/groups.add_user", {
body: {
token: user.getJwtToken(),
id: group.id,
@@ -410,14 +410,14 @@ describe('#groups.add_user', async () => {
});
});
-describe('#groups.remove_user', async () => {
- it('should remove user from group', async () => {
+describe("#groups.remove_user", async () => {
+ it("should remove user from group", async () => {
const user = await buildUser({ isAdmin: true });
const group = await buildGroup({
teamId: user.teamId,
});
- await server.post('/api/groups.add_user', {
+ await server.post("/api/groups.add_user", {
body: {
token: user.getJwtToken(),
id: group.id,
@@ -428,7 +428,7 @@ describe('#groups.remove_user', async () => {
const users = await group.getUsers();
expect(users.length).toEqual(1);
- const res = await server.post('/api/groups.remove_user', {
+ const res = await server.post("/api/groups.remove_user", {
body: {
token: user.getJwtToken(),
id: group.id,
@@ -441,20 +441,20 @@ describe('#groups.remove_user', async () => {
expect(users1.length).toEqual(0);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/groups.remove_user');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/groups.remove_user");
expect(res.status).toEqual(401);
});
- it('should require user in team', async () => {
+ it("should require user in team", async () => {
const user = await buildUser({ isAdmin: true });
const group = await buildGroup({
teamId: user.teamId,
});
const anotherUser = await buildUser();
- const res = await server.post('/api/groups.remove_user', {
+ const res = await server.post("/api/groups.remove_user", {
body: {
token: user.getJwtToken(),
id: group.id,
@@ -467,7 +467,7 @@ describe('#groups.remove_user', async () => {
expect(body).toMatchSnapshot();
});
- it('should require admin', async () => {
+ it("should require admin", async () => {
const user = await buildUser();
const group = await buildGroup({
teamId: user.teamId,
@@ -476,7 +476,7 @@ describe('#groups.remove_user', async () => {
teamId: user.teamId,
});
- const res = await server.post('/api/groups.remove_user', {
+ const res = await server.post("/api/groups.remove_user", {
body: {
token: user.getJwtToken(),
id: group.id,
diff --git a/server/api/hooks.js b/server/api/hooks.js
index 118a2313..641ec1a4 100644
--- a/server/api/hooks.js
+++ b/server/api/hooks.js
@@ -1,35 +1,35 @@
// @flow
-import Router from 'koa-router';
-import { escapeRegExp } from 'lodash';
-import { AuthenticationError, InvalidRequestError } from '../errors';
-import { Authentication, Document, User, Team, Collection } from '../models';
-import { presentSlackAttachment } from '../presenters';
-import * as Slack from '../slack';
+import Router from "koa-router";
+import { escapeRegExp } from "lodash";
+import { AuthenticationError, InvalidRequestError } from "../errors";
+import { Authentication, Document, User, Team, Collection } from "../models";
+import { presentSlackAttachment } from "../presenters";
+import * as Slack from "../slack";
const router = new Router();
// triggered by a user posting a getoutline.com link in Slack
-router.post('hooks.unfurl', async ctx => {
+router.post("hooks.unfurl", async ctx => {
const { challenge, token, event } = ctx.body;
if (challenge) return (ctx.body = ctx.body.challenge);
if (token !== process.env.SLACK_VERIFICATION_TOKEN) {
- throw new AuthenticationError('Invalid token');
+ throw new AuthenticationError("Invalid token");
}
const user = await User.findOne({
- where: { service: 'slack', serviceId: event.user },
+ where: { service: "slack", serviceId: event.user },
});
if (!user) return;
const auth = await Authentication.findOne({
- where: { service: 'slack', teamId: user.teamId },
+ where: { service: "slack", teamId: user.teamId },
});
if (!auth) return;
// get content for unfurled links
let unfurls = {};
for (let link of event.links) {
- const id = link.url.substr(link.url.lastIndexOf('/') + 1);
+ const id = link.url.substr(link.url.lastIndexOf("/") + 1);
const doc = await Document.findByPk(id);
if (!doc || doc.teamId !== user.teamId) continue;
@@ -40,7 +40,7 @@ router.post('hooks.unfurl', async ctx => {
};
}
- await Slack.post('chat.unfurl', {
+ await Slack.post("chat.unfurl", {
token: auth.token,
channel: event.channel,
ts: event.message_ts,
@@ -49,17 +49,17 @@ router.post('hooks.unfurl', async ctx => {
});
// triggered by interactions with actions, dialogs, message buttons in Slack
-router.post('hooks.interactive', async ctx => {
+router.post("hooks.interactive", async ctx => {
const { payload } = ctx.body;
- ctx.assertPresent(payload, 'payload is required');
+ ctx.assertPresent(payload, "payload is required");
const data = JSON.parse(payload);
const { callback_id, token } = data;
- ctx.assertPresent(token, 'token is required');
- ctx.assertPresent(callback_id, 'callback_id is required');
+ ctx.assertPresent(token, "token is required");
+ ctx.assertPresent(callback_id, "callback_id is required");
if (token !== process.env.SLACK_VERIFICATION_TOKEN) {
- throw new AuthenticationError('Invalid verification token');
+ throw new AuthenticationError("Invalid verification token");
}
const team = await Team.findOne({
@@ -69,8 +69,8 @@ router.post('hooks.interactive', async ctx => {
if (!team) {
ctx.body = {
text:
- 'Sorry, we couldn’t find an integration for your team. Head to your Outline settings to set one up.',
- response_type: 'ephemeral',
+ "Sorry, we couldn’t find an integration for your team. Head to your Outline settings to set one up.",
+ response_type: "ephemeral",
replace_original: false,
};
return;
@@ -83,13 +83,13 @@ router.post('hooks.interactive', async ctx => {
teamId: team.id,
},
});
- if (!document) throw new InvalidRequestError('Invalid document');
+ if (!document) throw new InvalidRequestError("Invalid document");
const collection = await Collection.findByPk(document.collectionId);
// respond with a public message that will be posted in the original channel
ctx.body = {
- response_type: 'in_channel',
+ response_type: "in_channel",
replace_original: false,
attachments: [
presentSlackAttachment(document, collection, team, document.getSummary()),
@@ -98,25 +98,25 @@ router.post('hooks.interactive', async ctx => {
});
// triggered by the /outline command in Slack
-router.post('hooks.slack', async ctx => {
- const { token, team_id, user_id, text = '' } = ctx.body;
- ctx.assertPresent(token, 'token is required');
- ctx.assertPresent(team_id, 'team_id is required');
- ctx.assertPresent(user_id, 'user_id is required');
+router.post("hooks.slack", async ctx => {
+ const { token, team_id, user_id, text = "" } = ctx.body;
+ ctx.assertPresent(token, "token is required");
+ ctx.assertPresent(team_id, "team_id is required");
+ ctx.assertPresent(user_id, "user_id is required");
if (token !== process.env.SLACK_VERIFICATION_TOKEN) {
- throw new AuthenticationError('Invalid verification token');
+ throw new AuthenticationError("Invalid verification token");
}
// Handle "help" command or no input
- if (text.trim() === 'help' || !text.trim()) {
+ if (text.trim() === "help" || !text.trim()) {
ctx.body = {
- response_type: 'ephemeral',
- text: 'How to use /outline',
+ response_type: "ephemeral",
+ text: "How to use /outline",
attachments: [
{
text:
- 'To search your knowledgebase use `/outline keyword`. \nYou’ve already learned how to get help with `/outline help`.',
+ "To search your knowledgebase use `/outline keyword`. \nYou’ve already learned how to get help with `/outline help`.",
},
],
};
@@ -128,9 +128,9 @@ router.post('hooks.slack', async ctx => {
});
if (!team) {
ctx.body = {
- response_type: 'ephemeral',
+ response_type: "ephemeral",
text:
- 'Sorry, we couldn’t find an integration for your team. Head to your Outline settings to set one up.',
+ "Sorry, we couldn’t find an integration for your team. Head to your Outline settings to set one up.",
};
return;
}
@@ -138,7 +138,7 @@ router.post('hooks.slack', async ctx => {
const user = await User.findOne({
where: {
teamId: team.id,
- service: 'slack',
+ service: "slack",
serviceId: user_id,
},
});
@@ -166,9 +166,9 @@ router.post('hooks.slack', async ctx => {
process.env.SLACK_MESSAGE_ACTIONS
? [
{
- name: 'post',
- text: 'Post to Channel',
- type: 'button',
+ name: "post",
+ text: "Post to Channel",
+ type: "button",
value: result.document.id,
},
]
diff --git a/server/api/hooks.test.js b/server/api/hooks.test.js
index 87b2108b..c3e902ad 100644
--- a/server/api/hooks.test.js
+++ b/server/api/hooks.test.js
@@ -1,43 +1,43 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../app';
-import { Authentication } from '../models';
-import { flushdb, seed } from '../test/support';
-import { buildDocument } from '../test/factories';
-import * as Slack from '../slack';
+import TestServer from "fetch-test-server";
+import app from "../app";
+import { Authentication } from "../models";
+import { flushdb, seed } from "../test/support";
+import { buildDocument } from "../test/factories";
+import * as Slack from "../slack";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-jest.mock('../slack', () => ({
+jest.mock("../slack", () => ({
post: jest.fn(),
}));
-describe('#hooks.unfurl', async () => {
- it('should return documents', async () => {
+describe("#hooks.unfurl", async () => {
+ it("should return documents", async () => {
const { user, document } = await seed();
await Authentication.create({
- service: 'slack',
+ service: "slack",
userId: user.id,
teamId: user.teamId,
- token: '',
+ token: "",
});
- const res = await server.post('/api/hooks.unfurl', {
+ const res = await server.post("/api/hooks.unfurl", {
body: {
token: process.env.SLACK_VERIFICATION_TOKEN,
- team_id: 'TXXXXXXXX',
- api_app_id: 'AXXXXXXXXX',
+ team_id: "TXXXXXXXX",
+ api_app_id: "AXXXXXXXXX",
event: {
- type: 'link_shared',
- channel: 'Cxxxxxx',
+ type: "link_shared",
+ channel: "Cxxxxxx",
user: user.serviceId,
- message_ts: '123456789.9875',
+ message_ts: "123456789.9875",
links: [
{
- domain: 'getoutline.com',
+ domain: "getoutline.com",
url: document.url,
},
],
@@ -49,16 +49,16 @@ describe('#hooks.unfurl', async () => {
});
});
-describe('#hooks.slack', async () => {
- it('should return no matches', async () => {
+describe("#hooks.slack", async () => {
+ it("should return no matches", async () => {
const { user, team } = await seed();
- const res = await server.post('/api/hooks.slack', {
+ const res = await server.post("/api/hooks.slack", {
body: {
token: process.env.SLACK_VERIFICATION_TOKEN,
user_id: user.serviceId,
team_id: team.slackId,
- text: 'dsfkndfskndsfkn',
+ text: "dsfkndfskndsfkn",
},
});
const body = await res.json();
@@ -66,19 +66,19 @@ describe('#hooks.slack', async () => {
expect(body.attachments).toEqual(undefined);
});
- it('should return search results with summary if query is in title', async () => {
+ it("should return search results with summary if query is in title", async () => {
const { user, team } = await seed();
const document = await buildDocument({
- title: 'This title contains a search term',
+ title: "This title contains a search term",
userId: user.id,
teamId: user.teamId,
});
- const res = await server.post('/api/hooks.slack', {
+ const res = await server.post("/api/hooks.slack", {
body: {
token: process.env.SLACK_VERIFICATION_TOKEN,
user_id: user.serviceId,
team_id: team.slackId,
- text: 'contains',
+ text: "contains",
},
});
const body = await res.json();
@@ -88,19 +88,19 @@ describe('#hooks.slack', async () => {
expect(body.attachments[0].text).toEqual(document.getSummary());
});
- it('should return search results if query is regex-like', async () => {
+ it("should return search results if query is regex-like", async () => {
const { user, team } = await seed();
await buildDocument({
- title: 'This title contains a search term',
+ title: "This title contains a search term",
userId: user.id,
teamId: user.teamId,
});
- const res = await server.post('/api/hooks.slack', {
+ const res = await server.post("/api/hooks.slack", {
body: {
token: process.env.SLACK_VERIFICATION_TOKEN,
user_id: user.serviceId,
team_id: team.slackId,
- text: '*contains',
+ text: "*contains",
},
});
const body = await res.json();
@@ -108,19 +108,19 @@ describe('#hooks.slack', async () => {
expect(body.attachments.length).toEqual(1);
});
- it('should return search results with snippet if query is in text', async () => {
+ it("should return search results with snippet if query is in text", async () => {
const { user, team } = await seed();
const document = await buildDocument({
- text: 'This title contains a search term',
+ text: "This title contains a search term",
userId: user.id,
teamId: user.teamId,
});
- const res = await server.post('/api/hooks.slack', {
+ const res = await server.post("/api/hooks.slack", {
body: {
token: process.env.SLACK_VERIFICATION_TOKEN,
user_id: user.serviceId,
team_id: team.slackId,
- text: 'contains',
+ text: "contains",
},
});
const body = await res.json();
@@ -128,62 +128,62 @@ describe('#hooks.slack', async () => {
expect(body.attachments.length).toEqual(1);
expect(body.attachments[0].title).toEqual(document.title);
expect(body.attachments[0].text).toEqual(
- 'This title *contains* a search term'
+ "This title *contains* a search term"
);
});
- it('should respond with help content for help keyword', async () => {
+ it("should respond with help content for help keyword", async () => {
const { user, team } = await seed();
- const res = await server.post('/api/hooks.slack', {
+ const res = await server.post("/api/hooks.slack", {
body: {
token: process.env.SLACK_VERIFICATION_TOKEN,
user_id: user.serviceId,
team_id: team.slackId,
- text: 'help',
+ text: "help",
},
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.text.includes('How to use')).toEqual(true);
+ expect(body.text.includes("How to use")).toEqual(true);
});
- it('should respond with help content for no keyword', async () => {
+ it("should respond with help content for no keyword", async () => {
const { user, team } = await seed();
- const res = await server.post('/api/hooks.slack', {
+ const res = await server.post("/api/hooks.slack", {
body: {
token: process.env.SLACK_VERIFICATION_TOKEN,
user_id: user.serviceId,
team_id: team.slackId,
- text: '',
+ text: "",
},
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.text.includes('How to use')).toEqual(true);
+ expect(body.text.includes("How to use")).toEqual(true);
});
- it('should return search results with snippet for unknown user', async () => {
+ it("should return search results with snippet for unknown user", async () => {
const { user, team } = await seed();
// unpublished document will not be returned
await buildDocument({
- text: 'This title contains a search term',
+ text: "This title contains a search term",
userId: user.id,
teamId: user.teamId,
publishedAt: null,
});
const document = await buildDocument({
- text: 'This title contains a search term',
+ text: "This title contains a search term",
userId: user.id,
teamId: user.teamId,
});
- const res = await server.post('/api/hooks.slack', {
+ const res = await server.post("/api/hooks.slack", {
body: {
token: process.env.SLACK_VERIFICATION_TOKEN,
- user_id: 'unknown-slack-user-id',
+ user_id: "unknown-slack-user-id",
team_id: team.slackId,
- text: 'contains',
+ text: "contains",
},
});
const body = await res.json();
@@ -191,30 +191,30 @@ describe('#hooks.slack', async () => {
expect(body.attachments.length).toEqual(1);
expect(body.attachments[0].title).toEqual(document.title);
expect(body.attachments[0].text).toEqual(
- 'This title *contains* a search term'
+ "This title *contains* a search term"
);
});
- it('should error if incorrect verification token', async () => {
+ it("should error if incorrect verification token", async () => {
const { user, team } = await seed();
- const res = await server.post('/api/hooks.slack', {
+ const res = await server.post("/api/hooks.slack", {
body: {
- token: 'wrong-verification-token',
+ token: "wrong-verification-token",
team_id: team.slackId,
user_id: user.serviceId,
- text: 'Welcome',
+ text: "Welcome",
},
});
expect(res.status).toEqual(401);
});
});
-describe('#hooks.interactive', async () => {
- it('should respond with replacement message', async () => {
+describe("#hooks.interactive", async () => {
+ it("should respond with replacement message", async () => {
const { user, team } = await seed();
const document = await buildDocument({
- title: 'This title contains a search term',
+ title: "This title contains a search term",
userId: user.id,
teamId: user.teamId,
});
@@ -225,48 +225,48 @@ describe('#hooks.interactive', async () => {
team: { id: team.slackId },
callback_id: document.id,
});
- const res = await server.post('/api/hooks.interactive', {
+ const res = await server.post("/api/hooks.interactive", {
body: { payload },
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.response_type).toEqual('in_channel');
+ expect(body.response_type).toEqual("in_channel");
expect(body.attachments.length).toEqual(1);
expect(body.attachments[0].title).toEqual(document.title);
});
- it('should respond with replacement message if unknown user', async () => {
+ it("should respond with replacement message if unknown user", async () => {
const { user, team } = await seed();
const document = await buildDocument({
- title: 'This title contains a search term',
+ title: "This title contains a search term",
userId: user.id,
teamId: user.teamId,
});
const payload = JSON.stringify({
token: process.env.SLACK_VERIFICATION_TOKEN,
- user: { id: 'unknown-slack-user-id' },
+ user: { id: "unknown-slack-user-id" },
team: { id: team.slackId },
callback_id: document.id,
});
- const res = await server.post('/api/hooks.interactive', {
+ const res = await server.post("/api/hooks.interactive", {
body: { payload },
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.response_type).toEqual('in_channel');
+ expect(body.response_type).toEqual("in_channel");
expect(body.attachments.length).toEqual(1);
expect(body.attachments[0].title).toEqual(document.title);
});
- it('should error if incorrect verification token', async () => {
+ it("should error if incorrect verification token", async () => {
const { user } = await seed();
const payload = JSON.stringify({
- token: 'wrong-verification-token',
+ token: "wrong-verification-token",
user: { id: user.serviceId, name: user.name },
- callback_id: 'doesnt-matter',
+ callback_id: "doesnt-matter",
});
- const res = await server.post('/api/hooks.interactive', {
+ const res = await server.post("/api/hooks.interactive", {
body: { payload },
});
expect(res.status).toEqual(401);
diff --git a/server/api/index.js b/server/api/index.js
index 743aeba2..36e7f171 100644
--- a/server/api/index.js
+++ b/server/api/index.js
@@ -1,32 +1,32 @@
// @flow
-import bodyParser from 'koa-bodyparser';
-import Koa from 'koa';
-import Router from 'koa-router';
+import bodyParser from "koa-bodyparser";
+import Koa from "koa";
+import Router from "koa-router";
-import auth from './auth';
-import events from './events';
-import users from './users';
-import collections from './collections';
-import documents from './documents';
-import revisions from './revisions';
-import views from './views';
-import hooks from './hooks';
-import apiKeys from './apiKeys';
-import shares from './shares';
-import groups from './groups';
-import team from './team';
-import integrations from './integrations';
-import notificationSettings from './notificationSettings';
-import utils from './utils';
-import attachments from './attachments';
+import auth from "./auth";
+import events from "./events";
+import users from "./users";
+import collections from "./collections";
+import documents from "./documents";
+import revisions from "./revisions";
+import views from "./views";
+import hooks from "./hooks";
+import apiKeys from "./apiKeys";
+import shares from "./shares";
+import groups from "./groups";
+import team from "./team";
+import integrations from "./integrations";
+import notificationSettings from "./notificationSettings";
+import utils from "./utils";
+import attachments from "./attachments";
-import { NotFoundError } from '../errors';
-import errorHandling from '../middlewares/errorHandling';
-import validation from '../middlewares/validation';
-import methodOverride from '../middlewares/methodOverride';
-import cache from './middlewares/cache';
-import apiWrapper from './middlewares/apiWrapper';
-import editor from './middlewares/editor';
+import { NotFoundError } from "../errors";
+import errorHandling from "../middlewares/errorHandling";
+import validation from "../middlewares/validation";
+import methodOverride from "../middlewares/methodOverride";
+import cache from "./middlewares/cache";
+import apiWrapper from "./middlewares/apiWrapper";
+import editor from "./middlewares/editor";
const api = new Koa();
const router = new Router();
@@ -41,25 +41,25 @@ api.use(apiWrapper());
api.use(editor());
// routes
-router.use('/', auth.routes());
-router.use('/', events.routes());
-router.use('/', users.routes());
-router.use('/', collections.routes());
-router.use('/', documents.routes());
-router.use('/', revisions.routes());
-router.use('/', views.routes());
-router.use('/', hooks.routes());
-router.use('/', apiKeys.routes());
-router.use('/', shares.routes());
-router.use('/', team.routes());
-router.use('/', integrations.routes());
-router.use('/', notificationSettings.routes());
-router.use('/', attachments.routes());
-router.use('/', utils.routes());
-router.use('/', groups.routes());
+router.use("/", auth.routes());
+router.use("/", events.routes());
+router.use("/", users.routes());
+router.use("/", collections.routes());
+router.use("/", documents.routes());
+router.use("/", revisions.routes());
+router.use("/", views.routes());
+router.use("/", hooks.routes());
+router.use("/", apiKeys.routes());
+router.use("/", shares.routes());
+router.use("/", team.routes());
+router.use("/", integrations.routes());
+router.use("/", notificationSettings.routes());
+router.use("/", attachments.routes());
+router.use("/", utils.routes());
+router.use("/", groups.routes());
-router.post('*', ctx => {
- ctx.throw(new NotFoundError('Endpoint not found'));
+router.post("*", ctx => {
+ ctx.throw(new NotFoundError("Endpoint not found"));
});
// Router is embedded in a Koa application wrapper, because koa-router does not
diff --git a/server/api/index.test.js b/server/api/index.test.js
index b407716c..98200886 100644
--- a/server/api/index.test.js
+++ b/server/api/index.test.js
@@ -1,22 +1,22 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../app';
-import { flushdb } from '../test/support';
+import TestServer from "fetch-test-server";
+import app from "../app";
+import { flushdb } from "../test/support";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('POST unknown endpoint', async () => {
- it('should be not found', async () => {
- const res = await server.post('/api/blah');
+describe("POST unknown endpoint", async () => {
+ it("should be not found", async () => {
+ const res = await server.post("/api/blah");
expect(res.status).toEqual(404);
});
});
-describe('GET unknown endpoint', async () => {
- it('should be not found', async () => {
- const res = await server.get('/api/blah');
+describe("GET unknown endpoint", async () => {
+ it("should be not found", async () => {
+ const res = await server.get("/api/blah");
expect(res.status).toEqual(404);
});
});
diff --git a/server/api/integrations.js b/server/api/integrations.js
index 5a7fbcae..d2e2a8d8 100644
--- a/server/api/integrations.js
+++ b/server/api/integrations.js
@@ -1,18 +1,18 @@
// @flow
-import Router from 'koa-router';
-import Integration from '../models/Integration';
-import pagination from './middlewares/pagination';
-import auth from '../middlewares/authentication';
-import { Event } from '../models';
-import { presentIntegration } from '../presenters';
-import policy from '../policies';
+import Router from "koa-router";
+import Integration from "../models/Integration";
+import pagination from "./middlewares/pagination";
+import auth from "../middlewares/authentication";
+import { Event } from "../models";
+import { presentIntegration } from "../presenters";
+import policy from "../policies";
const { authorize } = policy;
const router = new Router();
-router.post('integrations.list', auth(), pagination(), async ctx => {
- let { sort = 'updatedAt', direction } = ctx.body;
- if (direction !== 'ASC') direction = 'DESC';
+router.post("integrations.list", auth(), pagination(), async ctx => {
+ let { sort = "updatedAt", direction } = ctx.body;
+ if (direction !== "ASC") direction = "DESC";
const user = ctx.state.user;
const integrations = await Integration.findAll({
@@ -28,18 +28,18 @@ router.post('integrations.list', auth(), pagination(), async ctx => {
};
});
-router.post('integrations.delete', auth(), async ctx => {
+router.post("integrations.delete", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const integration = await Integration.findByPk(id);
- authorize(user, 'delete', integration);
+ authorize(user, "delete", integration);
await integration.destroy();
await Event.create({
- name: 'integrations.delete',
+ name: "integrations.delete",
modelId: integration.id,
teamId: integration.teamId,
actorId: user.id,
diff --git a/server/api/middlewares/apiWrapper.js b/server/api/middlewares/apiWrapper.js
index 0f5a7ea5..b8e50a1e 100644
--- a/server/api/middlewares/apiWrapper.js
+++ b/server/api/middlewares/apiWrapper.js
@@ -1,6 +1,6 @@
// @flow
-import stream from 'stream';
-import { type Context } from 'koa';
+import stream from "stream";
+import { type Context } from "koa";
export default function apiWrapper() {
return async function apiWrapperMiddleware(
@@ -12,7 +12,7 @@ export default function apiWrapper() {
const ok = ctx.status < 400;
if (
- typeof ctx.body !== 'string' &&
+ typeof ctx.body !== "string" &&
!(ctx.body instanceof stream.Readable)
) {
// $FlowFixMe
diff --git a/server/api/middlewares/cache.js b/server/api/middlewares/cache.js
index b47008e6..a2a4a931 100644
--- a/server/api/middlewares/cache.js
+++ b/server/api/middlewares/cache.js
@@ -1,8 +1,8 @@
// @flow
-import debug from 'debug';
-import { type Context } from 'koa';
+import debug from "debug";
+import { type Context } from "koa";
-const log = debug('cache');
+const log = debug("cache");
export default function cache() {
return async function cacheMiddleware(ctx: Context, next: () => Promise<*>) {
diff --git a/server/api/middlewares/editor.js b/server/api/middlewares/editor.js
index e0868e93..a6f114e5 100644
--- a/server/api/middlewares/editor.js
+++ b/server/api/middlewares/editor.js
@@ -1,12 +1,12 @@
// @flow
-import semver from 'semver';
-import { type Context } from 'koa';
-import pkg from 'rich-markdown-editor/package.json';
-import { EditorUpdateError } from '../../errors';
+import semver from "semver";
+import { type Context } from "koa";
+import pkg from "rich-markdown-editor/package.json";
+import { EditorUpdateError } from "../../errors";
export default function editor() {
return async function editorMiddleware(ctx: Context, next: () => Promise<*>) {
- const clientVersion = ctx.headers['x-editor-version'];
+ const clientVersion = ctx.headers["x-editor-version"];
// If the editor version on the client is behind the current version being
// served in production by either a minor (new features), or major (breaking
diff --git a/server/api/middlewares/pagination.js b/server/api/middlewares/pagination.js
index 2228d783..8adaf6ab 100644
--- a/server/api/middlewares/pagination.js
+++ b/server/api/middlewares/pagination.js
@@ -1,7 +1,7 @@
// @flow
-import querystring from 'querystring';
-import { InvalidRequestError } from '../../errors';
-import { type Context } from 'koa';
+import querystring from "querystring";
+import { InvalidRequestError } from "../../errors";
+import { type Context } from "koa";
export default function pagination(options?: Object) {
return async function paginationMiddleware(
diff --git a/server/api/middlewares/pagination.test.js b/server/api/middlewares/pagination.test.js
index fb61fca7..40811313 100644
--- a/server/api/middlewares/pagination.test.js
+++ b/server/api/middlewares/pagination.test.js
@@ -1,54 +1,54 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../../app';
-import { flushdb, seed } from '../../test/support';
+import TestServer from "fetch-test-server";
+import app from "../../app";
+import { flushdb, seed } from "../../test/support";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('#pagination', async () => {
- it('should allow offset and limit', async () => {
+describe("#pagination", async () => {
+ it("should allow offset and limit", async () => {
const { user } = await seed();
- const res = await server.post('/api/users.list', {
+ const res = await server.post("/api/users.list", {
body: { token: user.getJwtToken(), limit: 1, offset: 1 },
});
expect(res.status).toEqual(200);
});
- it('should not allow negative limit', async () => {
+ it("should not allow negative limit", async () => {
const { user } = await seed();
- const res = await server.post('/api/users.list', {
+ const res = await server.post("/api/users.list", {
body: { token: user.getJwtToken(), limit: -1 },
});
expect(res.status).toEqual(400);
});
- it('should not allow non-integer limit', async () => {
+ it("should not allow non-integer limit", async () => {
const { user } = await seed();
- const res = await server.post('/api/users.list', {
- body: { token: user.getJwtToken(), limit: 'blah' },
+ const res = await server.post("/api/users.list", {
+ body: { token: user.getJwtToken(), limit: "blah" },
});
expect(res.status).toEqual(400);
});
- it('should not allow negative offset', async () => {
+ it("should not allow negative offset", async () => {
const { user } = await seed();
- const res = await server.post('/api/users.list', {
+ const res = await server.post("/api/users.list", {
body: { token: user.getJwtToken(), offset: -1 },
});
expect(res.status).toEqual(400);
});
- it('should not allow non-integer offset', async () => {
+ it("should not allow non-integer offset", async () => {
const { user } = await seed();
- const res = await server.post('/api/users.list', {
- body: { token: user.getJwtToken(), offset: 'blah' },
+ const res = await server.post("/api/users.list", {
+ body: { token: user.getJwtToken(), offset: "blah" },
});
expect(res.status).toEqual(400);
diff --git a/server/api/notificationSettings.js b/server/api/notificationSettings.js
index cad6d761..a911d9db 100644
--- a/server/api/notificationSettings.js
+++ b/server/api/notificationSettings.js
@@ -1,20 +1,20 @@
// @flow
-import Router from 'koa-router';
+import Router from "koa-router";
-import auth from '../middlewares/authentication';
-import { NotificationSetting } from '../models';
-import { presentNotificationSetting } from '../presenters';
-import policy from '../policies';
+import auth from "../middlewares/authentication";
+import { NotificationSetting } from "../models";
+import { presentNotificationSetting } from "../presenters";
+import policy from "../policies";
const { authorize } = policy;
const router = new Router();
-router.post('notificationSettings.create', auth(), async ctx => {
+router.post("notificationSettings.create", auth(), async ctx => {
const { event } = ctx.body;
- ctx.assertPresent(event, 'event is required');
+ ctx.assertPresent(event, "event is required");
const user = ctx.state.user;
- authorize(user, 'create', NotificationSetting);
+ authorize(user, "create", NotificationSetting);
const [setting] = await NotificationSetting.findOrCreate({
where: {
@@ -29,7 +29,7 @@ router.post('notificationSettings.create', auth(), async ctx => {
};
});
-router.post('notificationSettings.list', auth(), async ctx => {
+router.post("notificationSettings.list", auth(), async ctx => {
const user = ctx.state.user;
const settings = await NotificationSetting.findAll({
where: {
@@ -42,13 +42,13 @@ router.post('notificationSettings.list', auth(), async ctx => {
};
});
-router.post('notificationSettings.delete', auth(), async ctx => {
+router.post("notificationSettings.delete", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const setting = await NotificationSetting.findByPk(id);
- authorize(user, 'delete', setting);
+ authorize(user, "delete", setting);
await setting.destroy();
@@ -57,10 +57,10 @@ router.post('notificationSettings.delete', auth(), async ctx => {
};
});
-router.post('notificationSettings.unsubscribe', async ctx => {
+router.post("notificationSettings.unsubscribe", async ctx => {
const { id, token } = ctx.body;
- ctx.assertUuid(id, 'id is required');
- ctx.assertPresent(token, 'token is required');
+ ctx.assertUuid(id, "id is required");
+ ctx.assertPresent(token, "token is required");
const setting = await NotificationSetting.findByPk(id);
if (setting) {
diff --git a/server/api/revisions.js b/server/api/revisions.js
index af305c55..2b758239 100644
--- a/server/api/revisions.js
+++ b/server/api/revisions.js
@@ -1,18 +1,18 @@
// @flow
-import Router from 'koa-router';
-import auth from '../middlewares/authentication';
-import pagination from './middlewares/pagination';
-import { presentRevision } from '../presenters';
-import { Document, Revision } from '../models';
-import { NotFoundError } from '../errors';
-import policy from '../policies';
+import Router from "koa-router";
+import auth from "../middlewares/authentication";
+import pagination from "./middlewares/pagination";
+import { presentRevision } from "../presenters";
+import { Document, Revision } from "../models";
+import { NotFoundError } from "../errors";
+import policy from "../policies";
const { authorize } = policy;
const router = new Router();
-router.post('revisions.info', auth(), async ctx => {
+router.post("revisions.info", auth(), async ctx => {
let { id } = ctx.body;
- ctx.assertPresent(id, 'id is required');
+ ctx.assertPresent(id, "id is required");
const user = ctx.state.user;
const revision = await Revision.findByPk(id);
@@ -23,7 +23,7 @@ router.post('revisions.info', auth(), async ctx => {
const document = await Document.findByPk(revision.documentId, {
userId: user.id,
});
- authorize(user, 'read', document);
+ authorize(user, "read", document);
ctx.body = {
pagination: ctx.state.pagination,
@@ -31,14 +31,14 @@ router.post('revisions.info', auth(), async ctx => {
};
});
-router.post('revisions.list', auth(), pagination(), async ctx => {
- let { documentId, sort = 'updatedAt', direction } = ctx.body;
- if (direction !== 'ASC') direction = 'DESC';
- ctx.assertPresent(documentId, 'documentId is required');
+router.post("revisions.list", auth(), pagination(), async ctx => {
+ let { documentId, sort = "updatedAt", direction } = ctx.body;
+ if (direction !== "ASC") direction = "DESC";
+ ctx.assertPresent(documentId, "documentId is required");
const user = ctx.state.user;
const document = await Document.findByPk(documentId, { userId: user.id });
- authorize(user, 'read', document);
+ authorize(user, "read", document);
const revisions = await Revision.findAll({
where: { documentId: document.id },
diff --git a/server/api/revisions.test.js b/server/api/revisions.test.js
index 0a60bd1b..ceb75585 100644
--- a/server/api/revisions.test.js
+++ b/server/api/revisions.test.js
@@ -1,24 +1,24 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../app';
-import { flushdb, seed } from '../test/support';
-import { buildDocument, buildUser } from '../test/factories';
-import Revision from '../models/Revision';
+import TestServer from "fetch-test-server";
+import app from "../app";
+import { flushdb, seed } from "../test/support";
+import { buildDocument, buildUser } from "../test/factories";
+import Revision from "../models/Revision";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('#revisions.info', async () => {
- it('should return a document revision', async () => {
+describe("#revisions.info", async () => {
+ it("should return a document revision", async () => {
const { user, document } = await seed();
const revision = await Revision.findOne({
where: {
documentId: document.id,
},
});
- const res = await server.post('/api/revisions.info', {
+ const res = await server.post("/api/revisions.info", {
body: {
token: user.getJwtToken(),
id: revision.id,
@@ -31,7 +31,7 @@ describe('#revisions.info', async () => {
expect(body.data.title).toEqual(document.title);
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const document = await buildDocument();
const revision = await Revision.findOne({
where: {
@@ -39,7 +39,7 @@ describe('#revisions.info', async () => {
},
});
const user = await buildUser();
- const res = await server.post('/api/revisions.info', {
+ const res = await server.post("/api/revisions.info", {
body: {
token: user.getJwtToken(),
id: revision.id,
@@ -49,10 +49,10 @@ describe('#revisions.info', async () => {
});
});
-describe('#revisions.list', async () => {
+describe("#revisions.list", async () => {
it("should return a document's revisions", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/revisions.list', {
+ const res = await server.post("/api/revisions.list", {
body: {
token: user.getJwtToken(),
documentId: document.id,
@@ -66,22 +66,22 @@ describe('#revisions.list', async () => {
expect(body.data[0].title).toEqual(document.title);
});
- it('should not return revisions for document in collection not a member of', async () => {
+ it("should not return revisions for document in collection not a member of", async () => {
const { user, document, collection } = await seed();
collection.private = true;
await collection.save();
- const res = await server.post('/api/revisions.list', {
+ const res = await server.post("/api/revisions.list", {
body: { token: user.getJwtToken(), documentId: document.id },
});
expect(res.status).toEqual(403);
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const document = await buildDocument();
const user = await buildUser();
- const res = await server.post('/api/revisions.list', {
+ const res = await server.post("/api/revisions.list", {
body: {
token: user.getJwtToken(),
documentId: document.id,
diff --git a/server/api/shares.js b/server/api/shares.js
index 53dd6a31..2ed07810 100644
--- a/server/api/shares.js
+++ b/server/api/shares.js
@@ -1,32 +1,32 @@
// @flow
-import Router from 'koa-router';
-import Sequelize from 'sequelize';
-import auth from '../middlewares/authentication';
-import pagination from './middlewares/pagination';
-import { presentShare } from '../presenters';
-import { Document, User, Event, Share, Team } from '../models';
-import policy from '../policies';
+import Router from "koa-router";
+import Sequelize from "sequelize";
+import auth from "../middlewares/authentication";
+import pagination from "./middlewares/pagination";
+import { presentShare } from "../presenters";
+import { Document, User, Event, Share, Team } from "../models";
+import policy from "../policies";
const Op = Sequelize.Op;
const { authorize } = policy;
const router = new Router();
-router.post('shares.info', auth(), async ctx => {
+router.post("shares.info", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const share = await Share.findByPk(id);
- authorize(user, 'read', share);
+ authorize(user, "read", share);
ctx.body = {
data: presentShare(share),
};
});
-router.post('shares.list', auth(), pagination(), async ctx => {
- let { sort = 'updatedAt', direction } = ctx.body;
- if (direction !== 'ASC') direction = 'DESC';
+router.post("shares.list", auth(), pagination(), async ctx => {
+ let { sort = "updatedAt", direction } = ctx.body;
+ if (direction !== "ASC") direction = "DESC";
const user = ctx.state.user;
const where = {
@@ -47,7 +47,7 @@ router.post('shares.list', auth(), pagination(), async ctx => {
{
model: Document,
required: true,
- as: 'document',
+ as: "document",
where: {
collectionId: collectionIds,
},
@@ -55,7 +55,7 @@ router.post('shares.list', auth(), pagination(), async ctx => {
{
model: User,
required: true,
- as: 'user',
+ as: "user",
},
],
offset: ctx.state.pagination.offset,
@@ -68,15 +68,15 @@ router.post('shares.list', auth(), pagination(), async ctx => {
};
});
-router.post('shares.create', auth(), async ctx => {
+router.post("shares.create", auth(), async ctx => {
const { documentId } = ctx.body;
- ctx.assertPresent(documentId, 'documentId is required');
+ ctx.assertPresent(documentId, "documentId is required");
const user = ctx.state.user;
const document = await Document.findByPk(documentId, { userId: user.id });
const team = await Team.findByPk(user.teamId);
- authorize(user, 'share', document);
- authorize(user, 'share', team);
+ authorize(user, "share", document);
+ authorize(user, "share", team);
const [share] = await Share.findOrCreate({
where: {
@@ -88,7 +88,7 @@ router.post('shares.create', auth(), async ctx => {
});
await Event.create({
- name: 'shares.create',
+ name: "shares.create",
documentId,
collectionId: document.collectionId,
modelId: share.id,
@@ -106,20 +106,20 @@ router.post('shares.create', auth(), async ctx => {
};
});
-router.post('shares.revoke', auth(), async ctx => {
+router.post("shares.revoke", auth(), async ctx => {
const { id } = ctx.body;
- ctx.assertUuid(id, 'id is required');
+ ctx.assertUuid(id, "id is required");
const user = ctx.state.user;
const share = await Share.findByPk(id);
- authorize(user, 'revoke', share);
+ authorize(user, "revoke", share);
await share.revoke(user.id);
const document = await Document.findByPk(share.documentId);
await Event.create({
- name: 'shares.revoke',
+ name: "shares.revoke",
documentId: document.id,
collectionId: document.collectionId,
modelId: share.id,
diff --git a/server/api/shares.test.js b/server/api/shares.test.js
index 0cbba773..4dd501ba 100644
--- a/server/api/shares.test.js
+++ b/server/api/shares.test.js
@@ -1,17 +1,17 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../app';
-import { CollectionUser } from '../models';
-import { flushdb, seed } from '../test/support';
-import { buildUser, buildShare } from '../test/factories';
+import TestServer from "fetch-test-server";
+import app from "../app";
+import { CollectionUser } from "../models";
+import { flushdb, seed } from "../test/support";
+import { buildUser, buildShare } from "../test/factories";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('#shares.list', async () => {
- it('should only return shares created by user', async () => {
+describe("#shares.list", async () => {
+ it("should only return shares created by user", async () => {
const { user, admin, document } = await seed();
await buildShare({
documentId: document.id,
@@ -23,7 +23,7 @@ describe('#shares.list', async () => {
teamId: user.teamId,
userId: user.id,
});
- const res = await server.post('/api/shares.list', {
+ const res = await server.post("/api/shares.list", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -34,7 +34,7 @@ describe('#shares.list', async () => {
expect(body.data[0].documentTitle).toBe(document.title);
});
- it('should not return revoked shares', async () => {
+ it("should not return revoked shares", async () => {
const { user, document } = await seed();
const share = await buildShare({
documentId: document.id,
@@ -43,7 +43,7 @@ describe('#shares.list', async () => {
});
await share.revoke(user.id);
- const res = await server.post('/api/shares.list', {
+ const res = await server.post("/api/shares.list", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -52,14 +52,14 @@ describe('#shares.list', async () => {
expect(body.data.length).toEqual(0);
});
- it('admins should return shares created by all users', async () => {
+ it("admins should return shares created by all users", async () => {
const { user, admin, document } = await seed();
const share = await buildShare({
documentId: document.id,
teamId: admin.teamId,
userId: user.id,
});
- const res = await server.post('/api/shares.list', {
+ const res = await server.post("/api/shares.list", {
body: { token: admin.getJwtToken() },
});
const body = await res.json();
@@ -70,7 +70,7 @@ describe('#shares.list', async () => {
expect(body.data[0].documentTitle).toBe(document.title);
});
- it('admins should not return shares in collection not a member of', async () => {
+ it("admins should not return shares in collection not a member of", async () => {
const { admin, document, collection } = await seed();
await buildShare({
documentId: document.id,
@@ -81,7 +81,7 @@ describe('#shares.list', async () => {
collection.private = true;
await collection.save();
- const res = await server.post('/api/shares.list', {
+ const res = await server.post("/api/shares.list", {
body: { token: admin.getJwtToken() },
});
const body = await res.json();
@@ -90,8 +90,8 @@ describe('#shares.list', async () => {
expect(body.data.length).toEqual(0);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/shares.list');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/shares.list");
const body = await res.json();
expect(res.status).toEqual(401);
@@ -99,10 +99,10 @@ describe('#shares.list', async () => {
});
});
-describe('#shares.create', async () => {
- it('should allow creating a share record for document', async () => {
+describe("#shares.create", async () => {
+ it("should allow creating a share record for document", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/shares.create', {
+ const res = await server.post("/api/shares.create", {
body: { token: user.getJwtToken(), documentId: document.id },
});
const body = await res.json();
@@ -111,7 +111,7 @@ describe('#shares.create', async () => {
expect(body.data.documentTitle).toBe(document.title);
});
- it('should allow creating a share record for document in read-only collection', async () => {
+ it("should allow creating a share record for document in read-only collection", async () => {
const { user, document, collection } = await seed();
collection.private = true;
await collection.save();
@@ -120,10 +120,10 @@ describe('#shares.create', async () => {
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read',
+ permission: "read",
});
- const res = await server.post('/api/shares.create', {
+ const res = await server.post("/api/shares.create", {
body: { token: user.getJwtToken(), documentId: document.id },
});
const body = await res.json();
@@ -132,7 +132,7 @@ describe('#shares.create', async () => {
expect(body.data.documentTitle).toBe(document.title);
});
- it('should allow creating a share record if link previously revoked', async () => {
+ it("should allow creating a share record if link previously revoked", async () => {
const { user, document } = await seed();
const share = await buildShare({
documentId: document.id,
@@ -140,7 +140,7 @@ describe('#shares.create', async () => {
userId: user.id,
});
await share.revoke();
- const res = await server.post('/api/shares.create', {
+ const res = await server.post("/api/shares.create", {
body: { token: user.getJwtToken(), documentId: document.id },
});
const body = await res.json();
@@ -150,14 +150,14 @@ describe('#shares.create', async () => {
expect(body.data.documentTitle).toBe(document.title);
});
- it('should return existing share link for document and user', async () => {
+ it("should return existing share link for document and user", async () => {
const { user, document } = await seed();
const share = await buildShare({
documentId: document.id,
teamId: user.teamId,
userId: user.id,
});
- const res = await server.post('/api/shares.create', {
+ const res = await server.post("/api/shares.create", {
body: { token: user.getJwtToken(), documentId: document.id },
});
const body = await res.json();
@@ -166,18 +166,18 @@ describe('#shares.create', async () => {
expect(body.data.id).toBe(share.id);
});
- it('should not allow creating a share record if disabled', async () => {
+ it("should not allow creating a share record if disabled", async () => {
const { user, document, team } = await seed();
await team.update({ sharing: false });
- const res = await server.post('/api/shares.create', {
+ const res = await server.post("/api/shares.create", {
body: { token: user.getJwtToken(), documentId: document.id },
});
expect(res.status).toEqual(403);
});
- it('should require authentication', async () => {
+ it("should require authentication", async () => {
const { document } = await seed();
- const res = await server.post('/api/shares.create', {
+ const res = await server.post("/api/shares.create", {
body: { documentId: document.id },
});
const body = await res.json();
@@ -186,18 +186,18 @@ describe('#shares.create', async () => {
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { document } = await seed();
const user = await buildUser();
- const res = await server.post('/api/shares.create', {
+ const res = await server.post("/api/shares.create", {
body: { token: user.getJwtToken(), documentId: document.id },
});
expect(res.status).toEqual(403);
});
});
-describe('#shares.revoke', async () => {
- it('should allow author to revoke a share', async () => {
+describe("#shares.revoke", async () => {
+ it("should allow author to revoke a share", async () => {
const { user, document } = await seed();
const share = await buildShare({
documentId: document.id,
@@ -205,13 +205,13 @@ describe('#shares.revoke', async () => {
userId: user.id,
});
- const res = await server.post('/api/shares.revoke', {
+ const res = await server.post("/api/shares.revoke", {
body: { token: user.getJwtToken(), id: share.id },
});
expect(res.status).toEqual(200);
});
- it('should allow admin to revoke a share', async () => {
+ it("should allow admin to revoke a share", async () => {
const { user, admin, document } = await seed();
const share = await buildShare({
documentId: document.id,
@@ -219,20 +219,20 @@ describe('#shares.revoke', async () => {
userId: user.id,
});
- const res = await server.post('/api/shares.revoke', {
+ const res = await server.post("/api/shares.revoke", {
body: { token: admin.getJwtToken(), id: share.id },
});
expect(res.status).toEqual(200);
});
- it('should require authentication', async () => {
+ it("should require authentication", async () => {
const { user, document } = await seed();
const share = await buildShare({
documentId: document.id,
teamId: user.teamId,
userId: user.id,
});
- const res = await server.post('/api/shares.revoke', {
+ const res = await server.post("/api/shares.revoke", {
body: { id: share.id },
});
const body = await res.json();
@@ -241,10 +241,10 @@ describe('#shares.revoke', async () => {
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { document } = await seed();
const user = await buildUser();
- const res = await server.post('/api/shares.create', {
+ const res = await server.post("/api/shares.create", {
body: { token: user.getJwtToken(), documentId: document.id },
});
expect(res.status).toEqual(403);
diff --git a/server/api/team.js b/server/api/team.js
index 471fec29..2c856818 100644
--- a/server/api/team.js
+++ b/server/api/team.js
@@ -1,15 +1,15 @@
// @flow
-import Router from 'koa-router';
-import { Team } from '../models';
+import Router from "koa-router";
+import { Team } from "../models";
-import auth from '../middlewares/authentication';
-import { presentTeam, presentPolicies } from '../presenters';
-import policy from '../policies';
+import auth from "../middlewares/authentication";
+import { presentTeam, presentPolicies } from "../presenters";
+import policy from "../policies";
const { authorize } = policy;
const router = new Router();
-router.post('team.update', auth(), async ctx => {
+router.post("team.update", auth(), async ctx => {
const {
name,
avatarUrl,
@@ -20,10 +20,10 @@ router.post('team.update', auth(), async ctx => {
} = ctx.body;
const user = ctx.state.user;
const team = await Team.findByPk(user.teamId);
- authorize(user, 'update', team);
+ authorize(user, "update", team);
- if (subdomain !== undefined && process.env.SUBDOMAINS_ENABLED === 'true') {
- team.subdomain = subdomain === '' ? null : subdomain;
+ if (subdomain !== undefined && process.env.SUBDOMAINS_ENABLED === "true") {
+ team.subdomain = subdomain === "" ? null : subdomain;
}
if (name) team.name = name;
diff --git a/server/api/team.test.js b/server/api/team.test.js
index d817cfa9..d34e7908 100644
--- a/server/api/team.test.js
+++ b/server/api/team.test.js
@@ -1,37 +1,37 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../app';
+import TestServer from "fetch-test-server";
+import app from "../app";
-import { flushdb, seed } from '../test/support';
+import { flushdb, seed } from "../test/support";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('#team.update', async () => {
- it('should update team details', async () => {
+describe("#team.update", async () => {
+ it("should update team details", async () => {
const { admin } = await seed();
- const res = await server.post('/api/team.update', {
- body: { token: admin.getJwtToken(), name: 'New name' },
+ const res = await server.post("/api/team.update", {
+ body: { token: admin.getJwtToken(), name: "New name" },
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.name).toEqual('New name');
+ expect(body.data.name).toEqual("New name");
});
- it('should require admin', async () => {
+ it("should require admin", async () => {
const { user } = await seed();
- const res = await server.post('/api/team.update', {
- body: { token: user.getJwtToken(), name: 'New name' },
+ const res = await server.post("/api/team.update", {
+ body: { token: user.getJwtToken(), name: "New name" },
});
expect(res.status).toEqual(403);
});
- it('should require authentication', async () => {
+ it("should require authentication", async () => {
await seed();
- const res = await server.post('/api/team.update');
+ const res = await server.post("/api/team.update");
expect(res.status).toEqual(401);
});
});
diff --git a/server/api/users.js b/server/api/users.js
index bf69320e..0e292f1b 100644
--- a/server/api/users.js
+++ b/server/api/users.js
@@ -1,20 +1,20 @@
// @flow
-import Router from 'koa-router';
-import { Op } from '../sequelize';
-import { Event, User, Team } from '../models';
-import auth from '../middlewares/authentication';
-import pagination from './middlewares/pagination';
-import userInviter from '../commands/userInviter';
-import { presentUser } from '../presenters';
-import policy from '../policies';
+import Router from "koa-router";
+import { Op } from "../sequelize";
+import { Event, User, Team } from "../models";
+import auth from "../middlewares/authentication";
+import pagination from "./middlewares/pagination";
+import userInviter from "../commands/userInviter";
+import { presentUser } from "../presenters";
+import policy from "../policies";
const { authorize } = policy;
const router = new Router();
-router.post('users.list', auth(), pagination(), async ctx => {
- const { sort = 'createdAt', query, includeSuspended = false } = ctx.body;
+router.post("users.list", auth(), pagination(), async ctx => {
+ const { sort = "createdAt", query, includeSuspended = false } = ctx.body;
let direction = ctx.body.direction;
- if (direction !== 'ASC') direction = 'DESC';
+ if (direction !== "ASC") direction = "DESC";
const user = ctx.state.user;
let where = {
@@ -54,13 +54,13 @@ router.post('users.list', auth(), pagination(), async ctx => {
};
});
-router.post('users.info', auth(), async ctx => {
+router.post("users.info", auth(), async ctx => {
ctx.body = {
data: presentUser(ctx.state.user),
};
});
-router.post('users.update', auth(), async ctx => {
+router.post("users.update", auth(), async ctx => {
const { user } = ctx.state;
const { name, avatarUrl } = ctx.body;
@@ -76,19 +76,19 @@ router.post('users.update', auth(), async ctx => {
// Admin specific
-router.post('users.promote', auth(), async ctx => {
+router.post("users.promote", auth(), async ctx => {
const userId = ctx.body.id;
const teamId = ctx.state.user.teamId;
- ctx.assertPresent(userId, 'id is required');
+ ctx.assertPresent(userId, "id is required");
const user = await User.findByPk(userId);
- authorize(ctx.state.user, 'promote', user);
+ authorize(ctx.state.user, "promote", user);
const team = await Team.findByPk(teamId);
await team.addAdmin(user);
await Event.create({
- name: 'users.promote',
+ name: "users.promote",
actorId: ctx.state.user.id,
userId,
teamId,
@@ -101,19 +101,19 @@ router.post('users.promote', auth(), async ctx => {
};
});
-router.post('users.demote', auth(), async ctx => {
+router.post("users.demote", auth(), async ctx => {
const userId = ctx.body.id;
const teamId = ctx.state.user.teamId;
- ctx.assertPresent(userId, 'id is required');
+ ctx.assertPresent(userId, "id is required");
const user = await User.findByPk(userId);
- authorize(ctx.state.user, 'demote', user);
+ authorize(ctx.state.user, "demote", user);
const team = await Team.findByPk(teamId);
await team.removeAdmin(user);
await Event.create({
- name: 'users.demote',
+ name: "users.demote",
actorId: ctx.state.user.id,
userId,
teamId,
@@ -126,20 +126,20 @@ router.post('users.demote', auth(), async ctx => {
};
});
-router.post('users.suspend', auth(), async ctx => {
+router.post("users.suspend", auth(), async ctx => {
const admin = ctx.state.user;
const userId = ctx.body.id;
const teamId = ctx.state.user.teamId;
- ctx.assertPresent(userId, 'id is required');
+ ctx.assertPresent(userId, "id is required");
const user = await User.findByPk(userId);
- authorize(ctx.state.user, 'suspend', user);
+ authorize(ctx.state.user, "suspend", user);
const team = await Team.findByPk(teamId);
await team.suspendUser(user, admin);
await Event.create({
- name: 'users.suspend',
+ name: "users.suspend",
actorId: ctx.state.user.id,
userId,
teamId,
@@ -152,20 +152,20 @@ router.post('users.suspend', auth(), async ctx => {
};
});
-router.post('users.activate', auth(), async ctx => {
+router.post("users.activate", auth(), async ctx => {
const admin = ctx.state.user;
const userId = ctx.body.id;
const teamId = ctx.state.user.teamId;
- ctx.assertPresent(userId, 'id is required');
+ ctx.assertPresent(userId, "id is required");
const user = await User.findByPk(userId);
- authorize(ctx.state.user, 'activate', user);
+ authorize(ctx.state.user, "activate", user);
const team = await Team.findByPk(teamId);
await team.activateUser(user, admin);
await Event.create({
- name: 'users.activate',
+ name: "users.activate",
actorId: ctx.state.user.id,
userId,
teamId,
@@ -178,12 +178,12 @@ router.post('users.activate', auth(), async ctx => {
};
});
-router.post('users.invite', auth(), async ctx => {
+router.post("users.invite", auth(), async ctx => {
const { invites } = ctx.body;
- ctx.assertPresent(invites, 'invites is required');
+ ctx.assertPresent(invites, "invites is required");
const user = ctx.state.user;
- authorize(user, 'invite', User);
+ authorize(user, "invite", User);
const response = await userInviter({ user, invites, ip: ctx.request.ip });
@@ -195,17 +195,17 @@ router.post('users.invite', auth(), async ctx => {
};
});
-router.post('users.delete', auth(), async ctx => {
+router.post("users.delete", auth(), async ctx => {
const { confirmation, id } = ctx.body;
- ctx.assertPresent(confirmation, 'confirmation is required');
+ ctx.assertPresent(confirmation, "confirmation is required");
let user = ctx.state.user;
if (id) user = await User.findByPk(id);
- authorize(ctx.state.user, 'delete', user);
+ authorize(ctx.state.user, "delete", user);
await user.destroy();
await Event.create({
- name: 'users.delete',
+ name: "users.delete",
actorId: user.id,
userId: user.id,
teamId: user.teamId,
diff --git a/server/api/users.test.js b/server/api/users.test.js
index 3457b2f5..db18190b 100644
--- a/server/api/users.test.js
+++ b/server/api/users.test.js
@@ -1,29 +1,29 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../app';
+import TestServer from "fetch-test-server";
+import app from "../app";
-import { flushdb, seed } from '../test/support';
-import { buildUser } from '../test/factories';
+import { flushdb, seed } from "../test/support";
+import { buildUser } from "../test/factories";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('#users.list', async () => {
- it('should allow filtering by user name', async () => {
- const user = await buildUser({ name: 'Tester' });
+describe("#users.list", async () => {
+ it("should allow filtering by user name", async () => {
+ const user = await buildUser({ name: "Tester" });
// suspended user should not be returned
await buildUser({
- name: 'Tester',
+ name: "Tester",
teamId: user.teamId,
suspendedAt: new Date(),
});
- const res = await server.post('/api/users.list', {
+ const res = await server.post("/api/users.list", {
body: {
- query: 'test',
+ query: "test",
token: user.getJwtToken(),
},
});
@@ -34,17 +34,17 @@ describe('#users.list', async () => {
expect(body.data[0].id).toEqual(user.id);
});
- it('should allow including suspended', async () => {
- const user = await buildUser({ name: 'Tester' });
+ it("should allow including suspended", async () => {
+ const user = await buildUser({ name: "Tester" });
await buildUser({
- name: 'Tester',
+ name: "Tester",
teamId: user.teamId,
suspendedAt: new Date(),
});
- const res = await server.post('/api/users.list', {
+ const res = await server.post("/api/users.list", {
body: {
- query: 'test',
+ query: "test",
includeSuspended: true,
token: user.getJwtToken(),
},
@@ -55,10 +55,10 @@ describe('#users.list', async () => {
expect(body.data.length).toEqual(2);
});
- it('should return teams paginated user list', async () => {
+ it("should return teams paginated user list", async () => {
const { admin, user } = await seed();
- const res = await server.post('/api/users.list', {
+ const res = await server.post("/api/users.list", {
body: { token: admin.getJwtToken() },
});
const body = await res.json();
@@ -69,9 +69,9 @@ describe('#users.list', async () => {
expect(body.data[1].id).toEqual(admin.id);
});
- it('should require admin for detailed info', async () => {
+ it("should require admin for detailed info", async () => {
const { user, admin } = await seed();
- const res = await server.post('/api/users.list', {
+ const res = await server.post("/api/users.list", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -85,10 +85,10 @@ describe('#users.list', async () => {
});
});
-describe('#users.info', async () => {
- it('should return known user', async () => {
+describe("#users.info", async () => {
+ it("should return known user", async () => {
const user = await buildUser();
- const res = await server.post('/api/users.info', {
+ const res = await server.post("/api/users.info", {
body: { token: user.getJwtToken() },
});
const body = await res.json();
@@ -98,19 +98,19 @@ describe('#users.info', async () => {
expect(body.data.name).toEqual(user.name);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/users.info');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/users.info");
expect(res.status).toEqual(401);
});
});
-describe('#users.invite', async () => {
- it('should return sent invites', async () => {
+describe("#users.invite", async () => {
+ it("should return sent invites", async () => {
const user = await buildUser();
- const res = await server.post('/api/users.invite', {
+ const res = await server.post("/api/users.invite", {
body: {
token: user.getJwtToken(),
- invites: [{ email: 'test@example.com', name: 'Test', guest: false }],
+ invites: [{ email: "test@example.com", name: "Test", guest: false }],
},
});
const body = await res.json();
@@ -118,70 +118,70 @@ describe('#users.invite', async () => {
expect(body.data.sent.length).toEqual(1);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/users.invite');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/users.invite");
expect(res.status).toEqual(401);
});
});
-describe('#users.delete', async () => {
- it('should not allow deleting without confirmation', async () => {
+describe("#users.delete", async () => {
+ it("should not allow deleting without confirmation", async () => {
const user = await buildUser();
- const res = await server.post('/api/users.delete', {
+ const res = await server.post("/api/users.delete", {
body: { token: user.getJwtToken() },
});
expect(res.status).toEqual(400);
});
- it('should allow deleting last admin if only user', async () => {
+ it("should allow deleting last admin if only user", async () => {
const user = await buildUser({ isAdmin: true });
- const res = await server.post('/api/users.delete', {
+ const res = await server.post("/api/users.delete", {
body: { token: user.getJwtToken(), confirmation: true },
});
expect(res.status).toEqual(200);
});
- it('should not allow deleting last admin if many users', async () => {
+ it("should not allow deleting last admin if many users", async () => {
const user = await buildUser({ isAdmin: true });
await buildUser({ teamId: user.teamId, isAdmin: false });
- const res = await server.post('/api/users.delete', {
+ const res = await server.post("/api/users.delete", {
body: { token: user.getJwtToken(), confirmation: true },
});
expect(res.status).toEqual(400);
});
- it('should allow deleting user account with confirmation', async () => {
+ it("should allow deleting user account with confirmation", async () => {
const user = await buildUser();
- const res = await server.post('/api/users.delete', {
+ const res = await server.post("/api/users.delete", {
body: { token: user.getJwtToken(), confirmation: true },
});
expect(res.status).toEqual(200);
});
- it('should allow deleting pending user account with admin', async () => {
+ it("should allow deleting pending user account with admin", async () => {
const user = await buildUser({ isAdmin: true });
const pending = await buildUser({
teamId: user.teamId,
lastActiveAt: null,
});
- const res = await server.post('/api/users.delete', {
+ const res = await server.post("/api/users.delete", {
body: { token: user.getJwtToken(), id: pending.id, confirmation: true },
});
expect(res.status).toEqual(200);
});
- it('should not allow deleting another user account', async () => {
+ it("should not allow deleting another user account", async () => {
const user = await buildUser({ isAdmin: true });
const user2 = await buildUser({ teamId: user.teamId });
- const res = await server.post('/api/users.delete', {
+ const res = await server.post("/api/users.delete", {
body: { token: user.getJwtToken(), id: user2.id, confirmation: true },
});
expect(res.status).toEqual(403);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/users.delete');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/users.delete");
const body = await res.json();
expect(res.status).toEqual(401);
@@ -189,20 +189,20 @@ describe('#users.delete', async () => {
});
});
-describe('#users.update', async () => {
- it('should update user profile information', async () => {
+describe("#users.update", async () => {
+ it("should update user profile information", async () => {
const { user } = await seed();
- const res = await server.post('/api/users.update', {
- body: { token: user.getJwtToken(), name: 'New name' },
+ const res = await server.post("/api/users.update", {
+ body: { token: user.getJwtToken(), name: "New name" },
});
const body = await res.json();
expect(res.status).toEqual(200);
- expect(body.data.name).toEqual('New name');
+ expect(body.data.name).toEqual("New name");
});
- it('should require authentication', async () => {
- const res = await server.post('/api/users.update');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/users.update");
const body = await res.json();
expect(res.status).toEqual(401);
@@ -210,11 +210,11 @@ describe('#users.update', async () => {
});
});
-describe('#users.promote', async () => {
- it('should promote a new admin', async () => {
+describe("#users.promote", async () => {
+ it("should promote a new admin", async () => {
const { admin, user } = await seed();
- const res = await server.post('/api/users.promote', {
+ const res = await server.post("/api/users.promote", {
body: { token: admin.getJwtToken(), id: user.id },
});
const body = await res.json();
@@ -223,9 +223,9 @@ describe('#users.promote', async () => {
expect(body).toMatchSnapshot();
});
- it('should require admin', async () => {
+ it("should require admin", async () => {
const user = await buildUser();
- const res = await server.post('/api/users.promote', {
+ const res = await server.post("/api/users.promote", {
body: { token: user.getJwtToken(), id: user.id },
});
const body = await res.json();
@@ -235,12 +235,12 @@ describe('#users.promote', async () => {
});
});
-describe('#users.demote', async () => {
- it('should demote an admin', async () => {
+describe("#users.demote", async () => {
+ it("should demote an admin", async () => {
const { admin, user } = await seed();
await user.update({ isAdmin: true }); // Make another admin
- const res = await server.post('/api/users.demote', {
+ const res = await server.post("/api/users.demote", {
body: {
token: admin.getJwtToken(),
id: user.id,
@@ -252,10 +252,10 @@ describe('#users.demote', async () => {
expect(body).toMatchSnapshot();
});
- it('should not demote admins if only one available', async () => {
+ it("should not demote admins if only one available", async () => {
const admin = await buildUser({ isAdmin: true });
- const res = await server.post('/api/users.demote', {
+ const res = await server.post("/api/users.demote", {
body: {
token: admin.getJwtToken(),
id: admin.id,
@@ -267,9 +267,9 @@ describe('#users.demote', async () => {
expect(body).toMatchSnapshot();
});
- it('should require admin', async () => {
+ it("should require admin", async () => {
const user = await buildUser();
- const res = await server.post('/api/users.promote', {
+ const res = await server.post("/api/users.promote", {
body: { token: user.getJwtToken(), id: user.id },
});
const body = await res.json();
@@ -279,11 +279,11 @@ describe('#users.demote', async () => {
});
});
-describe('#users.suspend', async () => {
- it('should suspend an user', async () => {
+describe("#users.suspend", async () => {
+ it("should suspend an user", async () => {
const { admin, user } = await seed();
- const res = await server.post('/api/users.suspend', {
+ const res = await server.post("/api/users.suspend", {
body: {
token: admin.getJwtToken(),
id: user.id,
@@ -295,9 +295,9 @@ describe('#users.suspend', async () => {
expect(body).toMatchSnapshot();
});
- it('should not allow suspending the user themselves', async () => {
+ it("should not allow suspending the user themselves", async () => {
const admin = await buildUser({ isAdmin: true });
- const res = await server.post('/api/users.suspend', {
+ const res = await server.post("/api/users.suspend", {
body: {
token: admin.getJwtToken(),
id: admin.id,
@@ -309,9 +309,9 @@ describe('#users.suspend', async () => {
expect(body).toMatchSnapshot();
});
- it('should require admin', async () => {
+ it("should require admin", async () => {
const user = await buildUser();
- const res = await server.post('/api/users.suspend', {
+ const res = await server.post("/api/users.suspend", {
body: { token: user.getJwtToken(), id: user.id },
});
const body = await res.json();
@@ -321,8 +321,8 @@ describe('#users.suspend', async () => {
});
});
-describe('#users.activate', async () => {
- it('should activate a suspended user', async () => {
+describe("#users.activate", async () => {
+ it("should activate a suspended user", async () => {
const { admin, user } = await seed();
await user.update({
suspendedById: admin.id,
@@ -330,7 +330,7 @@ describe('#users.activate', async () => {
});
expect(user.isSuspended).toBe(true);
- const res = await server.post('/api/users.activate', {
+ const res = await server.post("/api/users.activate", {
body: {
token: admin.getJwtToken(),
id: user.id,
@@ -342,9 +342,9 @@ describe('#users.activate', async () => {
expect(body).toMatchSnapshot();
});
- it('should require admin', async () => {
+ it("should require admin", async () => {
const user = await buildUser();
- const res = await server.post('/api/users.activate', {
+ const res = await server.post("/api/users.activate", {
body: { token: user.getJwtToken(), id: user.id },
});
const body = await res.json();
diff --git a/server/api/utils.js b/server/api/utils.js
index 9f135994..19d788b6 100644
--- a/server/api/utils.js
+++ b/server/api/utils.js
@@ -1,22 +1,22 @@
// @flow
-import debug from 'debug';
-import Router from 'koa-router';
-import subDays from 'date-fns/sub_days';
-import { AuthenticationError } from '../errors';
-import { Document, Attachment } from '../models';
-import { Op } from '../sequelize';
+import debug from "debug";
+import Router from "koa-router";
+import subDays from "date-fns/sub_days";
+import { AuthenticationError } from "../errors";
+import { Document, Attachment } from "../models";
+import { Op } from "../sequelize";
const router = new Router();
-const log = debug('utils');
+const log = debug("utils");
-router.post('utils.gc', async ctx => {
+router.post("utils.gc", async ctx => {
const { token } = ctx.body;
if (process.env.UTILS_SECRET !== token) {
- throw new AuthenticationError('Invalid secret token');
+ throw new AuthenticationError("Invalid secret token");
}
- log('Permanently deleting documents older than 30 days…');
+ log("Permanently deleting documents older than 30 days…");
const where = {
deletedAt: {
@@ -24,8 +24,8 @@ router.post('utils.gc', async ctx => {
},
};
- const documents = await Document.scope('withUnpublished').findAll({
- attributes: ['id'],
+ const documents = await Document.scope("withUnpublished").findAll({
+ attributes: ["id"],
where,
});
const documentIds = documents.map(d => d.id);
@@ -36,7 +36,7 @@ router.post('utils.gc', async ctx => {
},
});
- await Document.scope('withUnpublished').destroy({
+ await Document.scope("withUnpublished").destroy({
where,
force: true,
});
diff --git a/server/api/utils.test.js b/server/api/utils.test.js
index 38b43357..a94fd540 100644
--- a/server/api/utils.test.js
+++ b/server/api/utils.test.js
@@ -1,19 +1,19 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import subDays from 'date-fns/sub_days';
-import app from '../app';
-import { Document } from '../models';
-import { sequelize } from '../sequelize';
-import { flushdb } from '../test/support';
-import { buildDocument } from '../test/factories';
+import TestServer from "fetch-test-server";
+import subDays from "date-fns/sub_days";
+import app from "../app";
+import { Document } from "../models";
+import { sequelize } from "../sequelize";
+import { flushdb } from "../test/support";
+import { buildDocument } from "../test/factories";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('#utils.gc', async () => {
- it('should destroy documents deleted more than 30 days ago', async () => {
+describe("#utils.gc", async () => {
+ it("should destroy documents deleted more than 30 days ago", async () => {
const document = await buildDocument({
publishedAt: new Date(),
});
@@ -25,7 +25,7 @@ describe('#utils.gc', async () => {
).toISOString()}' WHERE id = '${document.id}'`
);
- const res = await server.post('/api/utils.gc', {
+ const res = await server.post("/api/utils.gc", {
body: {
token: process.env.UTILS_SECRET,
},
@@ -40,7 +40,7 @@ describe('#utils.gc', async () => {
expect(reloaded).toBe(null);
});
- it('should destroy draft documents deleted more than 30 days ago', async () => {
+ it("should destroy draft documents deleted more than 30 days ago", async () => {
const document = await buildDocument({
publishedAt: undefined,
});
@@ -52,7 +52,7 @@ describe('#utils.gc', async () => {
).toISOString()}' WHERE id = '${document.id}'`
);
- const res = await server.post('/api/utils.gc', {
+ const res = await server.post("/api/utils.gc", {
body: {
token: process.env.UTILS_SECRET,
},
@@ -67,8 +67,8 @@ describe('#utils.gc', async () => {
expect(reloaded).toBe(null);
});
- it('should require authentication', async () => {
- const res = await server.post('/api/utils.gc');
+ it("should require authentication", async () => {
+ const res = await server.post("/api/utils.gc");
expect(res.status).toEqual(401);
});
});
diff --git a/server/api/views.js b/server/api/views.js
index f64760b9..3d9b0a70 100644
--- a/server/api/views.js
+++ b/server/api/views.js
@@ -1,20 +1,20 @@
// @flow
-import Router from 'koa-router';
-import auth from '../middlewares/authentication';
-import { presentView } from '../presenters';
-import { View, Document, Event } from '../models';
-import policy from '../policies';
+import Router from "koa-router";
+import auth from "../middlewares/authentication";
+import { presentView } from "../presenters";
+import { View, Document, Event } from "../models";
+import policy from "../policies";
const { authorize } = policy;
const router = new Router();
-router.post('views.list', auth(), async ctx => {
+router.post("views.list", auth(), async ctx => {
const { documentId } = ctx.body;
- ctx.assertUuid(documentId, 'documentId is required');
+ ctx.assertUuid(documentId, "documentId is required");
const user = ctx.state.user;
const document = await Document.findByPk(documentId, { userId: user.id });
- authorize(user, 'read', document);
+ authorize(user, "read", document);
const views = await View.findByDocument(documentId);
@@ -23,18 +23,18 @@ router.post('views.list', auth(), async ctx => {
};
});
-router.post('views.create', auth(), async ctx => {
+router.post("views.create", auth(), async ctx => {
const { documentId } = ctx.body;
- ctx.assertUuid(documentId, 'documentId is required');
+ ctx.assertUuid(documentId, "documentId is required");
const user = ctx.state.user;
const document = await Document.findByPk(documentId, { userId: user.id });
- authorize(user, 'read', document);
+ authorize(user, "read", document);
const view = await View.increment({ documentId, userId: user.id });
await Event.create({
- name: 'views.create',
+ name: "views.create",
actorId: user.id,
documentId: document.id,
collectionId: document.collectionId,
diff --git a/server/api/views.test.js b/server/api/views.test.js
index 2325352c..4fca2753 100644
--- a/server/api/views.test.js
+++ b/server/api/views.test.js
@@ -1,21 +1,21 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import TestServer from 'fetch-test-server';
-import app from '../app';
-import { View, CollectionUser } from '../models';
-import { flushdb, seed } from '../test/support';
-import { buildUser } from '../test/factories';
+import TestServer from "fetch-test-server";
+import app from "../app";
+import { View, CollectionUser } from "../models";
+import { flushdb, seed } from "../test/support";
+import { buildUser } from "../test/factories";
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
-describe('#views.list', async () => {
- it('should return views for a document', async () => {
+describe("#views.list", async () => {
+ it("should return views for a document", async () => {
const { user, document } = await seed();
await View.increment({ documentId: document.id, userId: user.id });
- const res = await server.post('/api/views.list', {
+ const res = await server.post("/api/views.list", {
body: { token: user.getJwtToken(), documentId: document.id },
});
const body = await res.json();
@@ -25,7 +25,7 @@ describe('#views.list', async () => {
expect(body.data[0].user.name).toBe(user.name);
});
- it('should return views for a document in read-only collection', async () => {
+ it("should return views for a document in read-only collection", async () => {
const { user, document, collection } = await seed();
collection.private = true;
await collection.save();
@@ -34,12 +34,12 @@ describe('#views.list', async () => {
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read',
+ permission: "read",
});
await View.increment({ documentId: document.id, userId: user.id });
- const res = await server.post('/api/views.list', {
+ const res = await server.post("/api/views.list", {
body: { token: user.getJwtToken(), documentId: document.id },
});
const body = await res.json();
@@ -49,9 +49,9 @@ describe('#views.list', async () => {
expect(body.data[0].user.name).toBe(user.name);
});
- it('should require authentication', async () => {
+ it("should require authentication", async () => {
const { document } = await seed();
- const res = await server.post('/api/views.list', {
+ const res = await server.post("/api/views.list", {
body: { documentId: document.id },
});
const body = await res.json();
@@ -60,20 +60,20 @@ describe('#views.list', async () => {
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { document } = await seed();
const user = await buildUser();
- const res = await server.post('/api/views.list', {
+ const res = await server.post("/api/views.list", {
body: { token: user.getJwtToken(), documentId: document.id },
});
expect(res.status).toEqual(403);
});
});
-describe('#views.create', async () => {
- it('should allow creating a view record for document', async () => {
+describe("#views.create", async () => {
+ it("should allow creating a view record for document", async () => {
const { user, document } = await seed();
- const res = await server.post('/api/views.create', {
+ const res = await server.post("/api/views.create", {
body: { token: user.getJwtToken(), documentId: document.id },
});
const body = await res.json();
@@ -82,7 +82,7 @@ describe('#views.create', async () => {
expect(body.data.count).toBe(1);
});
- it('should allow creating a view record for document in read-only collection', async () => {
+ it("should allow creating a view record for document in read-only collection", async () => {
const { user, document, collection } = await seed();
collection.private = true;
await collection.save();
@@ -91,10 +91,10 @@ describe('#views.create', async () => {
createdById: user.id,
collectionId: collection.id,
userId: user.id,
- permission: 'read',
+ permission: "read",
});
- const res = await server.post('/api/views.create', {
+ const res = await server.post("/api/views.create", {
body: { token: user.getJwtToken(), documentId: document.id },
});
const body = await res.json();
@@ -103,9 +103,9 @@ describe('#views.create', async () => {
expect(body.data.count).toBe(1);
});
- it('should require authentication', async () => {
+ it("should require authentication", async () => {
const { document } = await seed();
- const res = await server.post('/api/views.create', {
+ const res = await server.post("/api/views.create", {
body: { documentId: document.id },
});
const body = await res.json();
@@ -114,10 +114,10 @@ describe('#views.create', async () => {
expect(body).toMatchSnapshot();
});
- it('should require authorization', async () => {
+ it("should require authorization", async () => {
const { document } = await seed();
const user = await buildUser();
- const res = await server.post('/api/views.create', {
+ const res = await server.post("/api/views.create", {
body: { token: user.getJwtToken(), documentId: document.id },
});
expect(res.status).toEqual(403);
diff --git a/server/app.js b/server/app.js
index dd5175c9..a42bce3d 100644
--- a/server/app.js
+++ b/server/app.js
@@ -1,35 +1,35 @@
// @flow
-import compress from 'koa-compress';
-import { compact } from 'lodash';
+import compress from "koa-compress";
+import { compact } from "lodash";
import helmet, {
contentSecurityPolicy,
dnsPrefetchControl,
referrerPolicy,
-} from 'koa-helmet';
-import logger from 'koa-logger';
-import mount from 'koa-mount';
-import enforceHttps from 'koa-sslify';
-import Koa from 'koa';
-import onerror from 'koa-onerror';
-import * as Sentry from '@sentry/node';
-import updates from './utils/updates';
+} from "koa-helmet";
+import logger from "koa-logger";
+import mount from "koa-mount";
+import enforceHttps from "koa-sslify";
+import Koa from "koa";
+import onerror from "koa-onerror";
+import * as Sentry from "@sentry/node";
+import updates from "./utils/updates";
-import auth from './auth';
-import api from './api';
-import emails from './emails';
-import routes from './routes';
+import auth from "./auth";
+import api from "./api";
+import emails from "./emails";
+import routes from "./routes";
const app = new Koa();
app.use(compress());
-if (process.env.NODE_ENV === 'development') {
+if (process.env.NODE_ENV === "development") {
/* eslint-disable global-require */
- const convert = require('koa-convert');
- const webpack = require('webpack');
- const devMiddleware = require('koa-webpack-dev-middleware');
- const hotMiddleware = require('koa-webpack-hot-middleware');
- const config = require('../webpack.config.dev');
+ const convert = require("koa-convert");
+ const webpack = require("webpack");
+ const devMiddleware = require("koa-webpack-dev-middleware");
+ const hotMiddleware = require("koa-webpack-hot-middleware");
+ const config = require("../webpack.config.dev");
const compile = webpack(config);
/* eslint-enable global-require */
@@ -61,24 +61,24 @@ if (process.env.NODE_ENV === 'development') {
convert(
hotMiddleware(compile, {
log: console.log, // eslint-disable-line
- path: '/__webpack_hmr',
+ path: "/__webpack_hmr",
heartbeat: 10 * 1000,
})
)
);
app.use(logger());
- app.use(mount('/emails', emails));
-} else if (process.env.NODE_ENV === 'production') {
+ app.use(mount("/emails", emails));
+} else if (process.env.NODE_ENV === "production") {
// Force redirect to HTTPS protocol unless explicitly disabled
- if (process.env.FORCE_HTTPS !== 'false') {
+ if (process.env.FORCE_HTTPS !== "false") {
app.use(
enforceHttps({
trustProtoHeader: true,
})
);
} else {
- console.warn('Enforced https was disabled with FORCE_HTTPS env variable');
+ console.warn("Enforced https was disabled with FORCE_HTTPS env variable");
}
// trust header fields set by our proxy. eg X-Forwarded-For
@@ -97,24 +97,24 @@ if (process.env.SENTRY_DSN) {
// emitted by Koa when bots attempt to snoop on paths such as wp-admin
// or the user submits a bad request. These are expected in normal running
// of the application
- 'BadRequestError',
- 'UnauthorizedError',
+ "BadRequestError",
+ "UnauthorizedError",
],
});
}
-app.on('error', (error, ctx) => {
+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 });
+ 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'];
+ const requestId = ctx.headers["x-request-id"];
if (requestId) {
- scope.setTag('request_id', requestId);
+ scope.setTag("request_id", requestId);
}
scope.addEventProcessor(function(event) {
return Sentry.Handlers.parseRequest(event, ctx.request);
@@ -126,8 +126,8 @@ app.on('error', (error, ctx) => {
}
});
-app.use(mount('/auth', auth));
-app.use(mount('/api', api));
+app.use(mount("/auth", auth));
+app.use(mount("/api", api));
app.use(helmet());
app.use(
@@ -138,25 +138,25 @@ app.use(
"'self'",
"'unsafe-inline'",
"'unsafe-eval'",
- 'gist.github.com',
- 'www.google-analytics.com',
- 'browser.sentry-cdn.com',
+ "gist.github.com",
+ "www.google-analytics.com",
+ "browser.sentry-cdn.com",
],
- styleSrc: ["'self'", "'unsafe-inline'", 'github.githubassets.com'],
- imgSrc: ['*', 'data:', 'blob:'],
- frameSrc: ['*'],
+ styleSrc: ["'self'", "'unsafe-inline'", "github.githubassets.com"],
+ imgSrc: ["*", "data:", "blob:"],
+ frameSrc: ["*"],
connectSrc: compact([
"'self'",
- process.env.AWS_S3_UPLOAD_BUCKET_URL.replace('s3:', 'localhost:'),
- 'www.google-analytics.com',
- 'api.github.com',
- 'sentry.io',
+ process.env.AWS_S3_UPLOAD_BUCKET_URL.replace("s3:", "localhost:"),
+ "www.google-analytics.com",
+ "api.github.com",
+ "sentry.io",
]),
},
})
);
app.use(dnsPrefetchControl({ allow: true }));
-app.use(referrerPolicy({ policy: 'no-referrer' }));
+app.use(referrerPolicy({ policy: "no-referrer" }));
app.use(mount(routes));
/**
@@ -165,8 +165,8 @@ app.use(mount(routes));
* Set ENABLE_UPDATES=false to disable them for your installation
*/
if (
- process.env.ENABLE_UPDATES !== 'false' &&
- process.env.NODE_ENV === 'production'
+ process.env.ENABLE_UPDATES !== "false" &&
+ process.env.NODE_ENV === "production"
) {
updates();
setInterval(updates, 24 * 3600 * 1000);
diff --git a/server/auth/email.js b/server/auth/email.js
index 135bd3d2..b18de1cb 100644
--- a/server/auth/email.js
+++ b/server/auth/email.js
@@ -1,23 +1,23 @@
// @flow
-import Router from 'koa-router';
-import mailer from '../mailer';
-import subMinutes from 'date-fns/sub_minutes';
-import { getUserForEmailSigninToken } from '../utils/jwt';
-import { User, Team } from '../models';
-import methodOverride from '../middlewares/methodOverride';
-import validation from '../middlewares/validation';
-import auth from '../middlewares/authentication';
-import { AuthorizationError } from '../errors';
+import Router from "koa-router";
+import mailer from "../mailer";
+import subMinutes from "date-fns/sub_minutes";
+import { getUserForEmailSigninToken } from "../utils/jwt";
+import { User, Team } from "../models";
+import methodOverride from "../middlewares/methodOverride";
+import validation from "../middlewares/validation";
+import auth from "../middlewares/authentication";
+import { AuthorizationError } from "../errors";
const router = new Router();
router.use(methodOverride());
router.use(validation());
-router.post('email', async ctx => {
+router.post("email", async ctx => {
const { email } = ctx.body;
- ctx.assertEmail(email, 'email is required');
+ ctx.assertEmail(email, "email is required");
const user = await User.findOne({
where: { email: email.toLowerCase() },
@@ -29,7 +29,7 @@ router.post('email', async ctx => {
// If the user matches an email address associated with an SSO
// signin then just forward them directly to that service's
// login page
- if (user.service && user.service !== 'email') {
+ if (user.service && user.service !== "email") {
return ctx.redirect(`${team.url}/auth/${user.service}`);
}
@@ -63,10 +63,10 @@ router.post('email', async ctx => {
}
});
-router.get('email.callback', auth({ required: false }), async ctx => {
+router.get("email.callback", auth({ required: false }), async ctx => {
const { token } = ctx.request.query;
- ctx.assertPresent(token, 'token is required');
+ ctx.assertPresent(token, "token is required");
try {
const user = await getUserForEmailSigninToken(token);
@@ -77,12 +77,12 @@ router.get('email.callback', auth({ required: false }), async ctx => {
}
if (!user.service) {
- user.service = 'email';
+ user.service = "email";
await user.save();
}
// set cookies on response and redirect to team subdomain
- ctx.signIn(user, team, 'email', false);
+ ctx.signIn(user, team, "email", false);
} catch (err) {
ctx.redirect(`${process.env.URL}?notice=expired-token`);
}
diff --git a/server/auth/google.js b/server/auth/google.js
index 80bc1b16..779622e7 100644
--- a/server/auth/google.js
+++ b/server/auth/google.js
@@ -1,11 +1,11 @@
// @flow
-import Sequelize from 'sequelize';
-import crypto from 'crypto';
-import Router from 'koa-router';
-import { capitalize } from 'lodash';
-import { OAuth2Client } from 'google-auth-library';
-import { User, Team, Event } from '../models';
-import auth from '../middlewares/authentication';
+import Sequelize from "sequelize";
+import crypto from "crypto";
+import Router from "koa-router";
+import { capitalize } from "lodash";
+import { OAuth2Client } from "google-auth-library";
+import { User, Team, Event } from "../models";
+import auth from "../middlewares/authentication";
const Op = Sequelize.Op;
@@ -18,51 +18,51 @@ const client = new OAuth2Client(
const allowedDomainsEnv = process.env.GOOGLE_ALLOWED_DOMAINS;
// start the oauth process and redirect user to Google
-router.get('google', async ctx => {
+router.get("google", async ctx => {
// Generate the url that will be used for the consent dialog.
const authorizeUrl = client.generateAuthUrl({
- access_type: 'offline',
+ access_type: "offline",
scope: [
- 'https://www.googleapis.com/auth/userinfo.profile',
- 'https://www.googleapis.com/auth/userinfo.email',
+ "https://www.googleapis.com/auth/userinfo.profile",
+ "https://www.googleapis.com/auth/userinfo.email",
],
- prompt: 'consent',
+ prompt: "consent",
});
ctx.redirect(authorizeUrl);
});
// signin callback from Google
-router.get('google.callback', auth({ required: false }), async ctx => {
+router.get("google.callback", auth({ required: false }), async ctx => {
const { code } = ctx.request.query;
- ctx.assertPresent(code, 'code is required');
+ ctx.assertPresent(code, "code is required");
const response = await client.getToken(code);
client.setCredentials(response.tokens);
const profile = await client.request({
- url: 'https://www.googleapis.com/oauth2/v1/userinfo',
+ url: "https://www.googleapis.com/oauth2/v1/userinfo",
});
if (!profile.data.hd) {
- ctx.redirect('/?notice=google-hd');
+ ctx.redirect("/?notice=google-hd");
return;
}
// allow all domains by default if the env is not set
- const allowedDomains = allowedDomainsEnv && allowedDomainsEnv.split(',');
+ const allowedDomains = allowedDomainsEnv && allowedDomainsEnv.split(",");
if (allowedDomains && !allowedDomains.includes(profile.data.hd)) {
- ctx.redirect('/?notice=hd-not-allowed');
+ ctx.redirect("/?notice=hd-not-allowed");
return;
}
const googleId = profile.data.hd;
- const hostname = profile.data.hd.split('.')[0];
+ const hostname = profile.data.hd.split(".")[0];
const teamName = capitalize(hostname);
// attempt to get logo from Clearbit API. If one doesn't exist then
// fall back to using tiley to generate a placeholder logo
- const hash = crypto.createHash('sha256');
+ const hash = crypto.createHash("sha256");
hash.update(googleId);
- const hashedGoogleId = hash.digest('hex');
+ const hashedGoogleId = hash.digest("hex");
const cbUrl = `https://logo.clearbit.com/${profile.data.hd}`;
const tileyUrl = `https://tiley.herokuapp.com/avatar/${hashedGoogleId}/${
teamName[0]
@@ -85,7 +85,7 @@ router.get('google.callback', auth({ required: false }), async ctx => {
where: {
[Op.or]: [
{
- service: 'google',
+ service: "google",
serviceId: profile.data.id,
},
{
@@ -96,7 +96,7 @@ router.get('google.callback', auth({ required: false }), async ctx => {
teamId: team.id,
},
defaults: {
- service: 'google',
+ service: "google",
serviceId: profile.data.id,
name: profile.data.name,
email: profile.data.email,
@@ -108,7 +108,7 @@ router.get('google.callback', auth({ required: false }), async ctx => {
// update the user with fresh details if they just accepted an invite
if (!user.serviceId || !user.service) {
await user.update({
- service: 'google',
+ service: "google",
serviceId: profile.data.id,
avatarUrl: profile.data.picture,
});
@@ -126,25 +126,25 @@ router.get('google.callback', auth({ required: false }), async ctx => {
if (isFirstSignin) {
await Event.create({
- name: 'users.create',
+ name: "users.create",
actorId: user.id,
userId: user.id,
teamId: team.id,
data: {
name: user.name,
- service: 'google',
+ service: "google",
},
ip: ctx.request.ip,
});
}
// set cookies on response and redirect to team subdomain
- ctx.signIn(user, team, 'google', isFirstSignin);
+ ctx.signIn(user, team, "google", isFirstSignin);
} catch (err) {
if (err instanceof Sequelize.UniqueConstraintError) {
const exists = await User.findOne({
where: {
- service: 'email',
+ service: "email",
email: profile.data.email,
teamId: team.id,
},
diff --git a/server/auth/index.js b/server/auth/index.js
index 5773e051..154da4b8 100644
--- a/server/auth/index.js
+++ b/server/auth/index.js
@@ -1,38 +1,38 @@
// @flow
-import bodyParser from 'koa-bodyparser';
-import Koa from 'koa';
-import Router from 'koa-router';
-import validation from '../middlewares/validation';
-import auth from '../middlewares/authentication';
-import addMonths from 'date-fns/add_months';
-import { Team } from '../models';
-import { getCookieDomain } from '../../shared/utils/domains';
+import bodyParser from "koa-bodyparser";
+import Koa from "koa";
+import Router from "koa-router";
+import validation from "../middlewares/validation";
+import auth from "../middlewares/authentication";
+import addMonths from "date-fns/add_months";
+import { Team } from "../models";
+import { getCookieDomain } from "../../shared/utils/domains";
-import slack from './slack';
-import google from './google';
-import email from './email';
+import slack from "./slack";
+import google from "./google";
+import email from "./email";
const app = new Koa();
const router = new Router();
-router.use('/', slack.routes());
-router.use('/', google.routes());
-router.use('/', email.routes());
+router.use("/", slack.routes());
+router.use("/", google.routes());
+router.use("/", email.routes());
-router.get('/redirect', auth(), async ctx => {
+router.get("/redirect", auth(), async ctx => {
const user = ctx.state.user;
// transfer access token cookie from root to subdomain
- const rootToken = ctx.cookies.get('accessToken');
+ const rootToken = ctx.cookies.get("accessToken");
const jwtToken = user.getJwtToken();
if (rootToken === jwtToken) {
- ctx.cookies.set('accessToken', undefined, {
+ ctx.cookies.set("accessToken", undefined, {
httpOnly: true,
domain: getCookieDomain(ctx.request.hostname),
});
- ctx.cookies.set('accessToken', jwtToken, {
+ ctx.cookies.set("accessToken", jwtToken, {
httpOnly: false,
expires: addMonths(new Date(), 3),
});
diff --git a/server/auth/slack.js b/server/auth/slack.js
index b2f00203..6071a796 100644
--- a/server/auth/slack.js
+++ b/server/auth/slack.js
@@ -1,10 +1,10 @@
// @flow
-import Sequelize from 'sequelize';
-import Router from 'koa-router';
-import auth from '../middlewares/authentication';
-import addHours from 'date-fns/add_hours';
-import { getCookieDomain } from '../../shared/utils/domains';
-import { slackAuth } from '../../shared/utils/routeHelpers';
+import Sequelize from "sequelize";
+import Router from "koa-router";
+import auth from "../middlewares/authentication";
+import addHours from "date-fns/add_hours";
+import { getCookieDomain } from "../../shared/utils/domains";
+import { slackAuth } from "../../shared/utils/routeHelpers";
import {
Authentication,
Collection,
@@ -12,19 +12,19 @@ import {
User,
Event,
Team,
-} from '../models';
-import * as Slack from '../slack';
+} from "../models";
+import * as Slack from "../slack";
const Op = Sequelize.Op;
const router = new Router();
// start the oauth process and redirect user to Slack
-router.get('slack', async ctx => {
+router.get("slack", async ctx => {
const state = Math.random()
.toString(36)
.substring(7);
- ctx.cookies.set('state', state, {
+ ctx.cookies.set("state", state, {
httpOnly: false,
expires: addHours(new Date(), 1),
domain: getCookieDomain(ctx.request.hostname),
@@ -33,13 +33,13 @@ router.get('slack', async ctx => {
});
// signin callback from Slack
-router.get('slack.callback', auth({ required: false }), async ctx => {
+router.get("slack.callback", auth({ required: false }), async ctx => {
const { code, error, state } = ctx.request.query;
- ctx.assertPresent(code || error, 'code is required');
- ctx.assertPresent(state, 'state is required');
+ ctx.assertPresent(code || error, "code is required");
+ ctx.assertPresent(state, "state is required");
- if (state !== ctx.cookies.get('state')) {
- ctx.redirect('/?notice=auth-error&error=state_mismatch');
+ if (state !== ctx.cookies.get("state")) {
+ ctx.redirect("/?notice=auth-error&error=state_mismatch");
return;
}
if (error) {
@@ -64,7 +64,7 @@ router.get('slack.callback', auth({ required: false }), async ctx => {
where: {
[Op.or]: [
{
- service: 'slack',
+ service: "slack",
serviceId: data.user.id,
},
{
@@ -75,7 +75,7 @@ router.get('slack.callback', auth({ required: false }), async ctx => {
teamId: team.id,
},
defaults: {
- service: 'slack',
+ service: "slack",
serviceId: data.user.id,
name: data.user.name,
email: data.user.email,
@@ -87,7 +87,7 @@ router.get('slack.callback', auth({ required: false }), async ctx => {
// update the user with fresh details if they just accepted an invite
if (!user.serviceId || !user.service) {
await user.update({
- service: 'slack',
+ service: "slack",
serviceId: data.user.id,
avatarUrl: data.user.image_192,
});
@@ -105,25 +105,25 @@ router.get('slack.callback', auth({ required: false }), async ctx => {
if (isFirstSignin) {
await Event.create({
- name: 'users.create',
+ name: "users.create",
actorId: user.id,
userId: user.id,
teamId: team.id,
data: {
name: user.name,
- service: 'slack',
+ service: "slack",
},
ip: ctx.request.ip,
});
}
// set cookies on response and redirect to team subdomain
- ctx.signIn(user, team, 'slack', isFirstSignin);
+ ctx.signIn(user, team, "slack", isFirstSignin);
} catch (err) {
if (err instanceof Sequelize.UniqueConstraintError) {
const exists = await User.findOne({
where: {
- service: 'email',
+ service: "email",
email: data.user.email,
teamId: team.id,
},
@@ -142,10 +142,10 @@ router.get('slack.callback', auth({ required: false }), async ctx => {
}
});
-router.get('slack.commands', auth({ required: false }), async ctx => {
+router.get("slack.commands", auth({ required: false }), async ctx => {
const { code, state, error } = ctx.request.query;
const user = ctx.state.user;
- ctx.assertPresent(code || error, 'code is required');
+ ctx.assertPresent(code || error, "code is required");
if (error) {
ctx.redirect(`/settings/integrations/slack?error=${error}`);
@@ -172,35 +172,35 @@ router.get('slack.commands', auth({ required: false }), async ctx => {
}
}
- const endpoint = `${process.env.URL || ''}/auth/slack.commands`;
+ const endpoint = `${process.env.URL || ""}/auth/slack.commands`;
const data = await Slack.oauthAccess(code, endpoint);
const authentication = await Authentication.create({
- service: 'slack',
+ service: "slack",
userId: user.id,
teamId: user.teamId,
token: data.access_token,
- scopes: data.scope.split(','),
+ scopes: data.scope.split(","),
});
await Integration.create({
- service: 'slack',
- type: 'command',
+ service: "slack",
+ type: "command",
userId: user.id,
teamId: user.teamId,
authenticationId: authentication.id,
});
- ctx.redirect('/settings/integrations/slack');
+ ctx.redirect("/settings/integrations/slack");
});
-router.get('slack.post', auth({ required: false }), async ctx => {
+router.get("slack.post", auth({ required: false }), async ctx => {
const { code, error, state } = ctx.request.query;
const user = ctx.state.user;
- ctx.assertPresent(code || error, 'code is required');
+ ctx.assertPresent(code || error, "code is required");
const collectionId = state;
- ctx.assertUuid(collectionId, 'collectionId must be an uuid');
+ ctx.assertUuid(collectionId, "collectionId must be an uuid");
if (error) {
ctx.redirect(`/settings/integrations/slack?error=${error}`);
@@ -222,20 +222,20 @@ router.get('slack.post', auth({ required: false }), async ctx => {
}
}
- const endpoint = `${process.env.URL || ''}/auth/slack.post`;
+ const endpoint = `${process.env.URL || ""}/auth/slack.post`;
const data = await Slack.oauthAccess(code, endpoint);
const authentication = await Authentication.create({
- service: 'slack',
+ service: "slack",
userId: user.id,
teamId: user.teamId,
token: data.access_token,
- scopes: data.scope.split(','),
+ scopes: data.scope.split(","),
});
await Integration.create({
- service: 'slack',
- type: 'post',
+ service: "slack",
+ type: "post",
userId: user.id,
teamId: user.teamId,
authenticationId: authentication.id,
@@ -248,7 +248,7 @@ router.get('slack.post', auth({ required: false }), async ctx => {
},
});
- ctx.redirect('/settings/integrations/slack');
+ ctx.redirect("/settings/integrations/slack");
});
export default router;
diff --git a/server/commands/documentMover.js b/server/commands/documentMover.js
index d5893d8d..56c4d1d1 100644
--- a/server/commands/documentMover.js
+++ b/server/commands/documentMover.js
@@ -1,7 +1,7 @@
// @flow
-import { Document, Collection, Event } from '../models';
-import { sequelize } from '../sequelize';
-import { type Context } from 'koa';
+import { Document, Collection, Event } from "../models";
+import { sequelize } from "../sequelize";
+import { type Context } from "koa";
export default async function documentMover({
user,
@@ -77,7 +77,7 @@ export default async function documentMover({
await transaction.commit();
await Event.create({
- name: 'documents.move',
+ name: "documents.move",
actorId: user.id,
documentId: document.id,
collectionId,
diff --git a/server/commands/documentMover.test.js b/server/commands/documentMover.test.js
index 904ed7d4..9df52e18 100644
--- a/server/commands/documentMover.test.js
+++ b/server/commands/documentMover.test.js
@@ -1,14 +1,14 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import documentMover from '../commands/documentMover';
-import { flushdb, seed } from '../test/support';
-import { buildDocument, buildCollection } from '../test/factories';
+import documentMover from "../commands/documentMover";
+import { flushdb, seed } from "../test/support";
+import { buildDocument, buildCollection } from "../test/factories";
beforeEach(flushdb);
-describe('documentMover', async () => {
- const ip = '127.0.0.1';
+describe("documentMover", async () => {
+ const ip = "127.0.0.1";
- it('should move within a collection', async () => {
+ it("should move within a collection", async () => {
const { document, user, collection } = await seed();
const response = await documentMover({
@@ -22,15 +22,15 @@ describe('documentMover', async () => {
expect(response.documents.length).toEqual(1);
});
- it('should move with children', async () => {
+ it("should move with children", async () => {
const { document, user, collection } = await seed();
const newDocument = await buildDocument({
parentDocumentId: document.id,
collectionId: collection.id,
teamId: collection.teamId,
userId: collection.creatorId,
- title: 'Child document',
- text: 'content',
+ title: "Child document",
+ text: "content",
});
await collection.addDocumentToStructure(newDocument);
@@ -50,7 +50,7 @@ describe('documentMover', async () => {
expect(response.documents.length).toEqual(1);
});
- it('should move with children to another collection', async () => {
+ it("should move with children to another collection", async () => {
const { document, user, collection } = await seed();
const newCollection = await buildCollection({
teamId: collection.teamId,
@@ -60,8 +60,8 @@ describe('documentMover', async () => {
collectionId: collection.id,
teamId: collection.teamId,
userId: collection.creatorId,
- title: 'Child document',
- text: 'content',
+ title: "Child document",
+ text: "content",
});
await collection.addDocumentToStructure(newDocument);
diff --git a/server/commands/userInviter.js b/server/commands/userInviter.js
index 117c5996..22d39434 100644
--- a/server/commands/userInviter.js
+++ b/server/commands/userInviter.js
@@ -1,8 +1,8 @@
// @flow
-import { uniqBy } from 'lodash';
-import { User, Event, Team } from '../models';
-import mailer from '../mailer';
-import { sequelize } from '../sequelize';
+import { uniqBy } from "lodash";
+import { User, Event, Team } from "../models";
+import mailer from "../mailer";
+import { sequelize } from "../sequelize";
type Invite = { name: string, email: string, guest: boolean };
@@ -19,7 +19,7 @@ export default async function userInviter({
// filter out empties and obvious non-emails
const compactedInvites = invites.filter(
- invite => !!invite.email.trim() && invite.email.match('@')
+ invite => !!invite.email.trim() && invite.email.match("@")
);
// normalize to lowercase and remove duplicates
@@ -28,7 +28,7 @@ export default async function userInviter({
...invite,
email: invite.email.toLowerCase(),
})),
- 'email'
+ "email"
);
// filter out any existing users in the system
@@ -63,7 +63,7 @@ export default async function userInviter({
users.push(newUser);
await Event.create(
{
- name: 'users.invite',
+ name: "users.invite",
actorId: user.id,
teamId: user.teamId,
data: {
diff --git a/server/commands/userInviter.test.js b/server/commands/userInviter.test.js
index f75aab71..3a22c820 100644
--- a/server/commands/userInviter.test.js
+++ b/server/commands/userInviter.test.js
@@ -1,49 +1,49 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import userInviter from '../commands/userInviter';
-import { flushdb } from '../test/support';
-import { buildUser } from '../test/factories';
+import userInviter from "../commands/userInviter";
+import { flushdb } from "../test/support";
+import { buildUser } from "../test/factories";
beforeEach(flushdb);
-describe('userInviter', async () => {
- const ip = '127.0.0.1';
+describe("userInviter", async () => {
+ const ip = "127.0.0.1";
- it('should return sent invites', async () => {
+ it("should return sent invites", async () => {
const user = await buildUser();
const response = await userInviter({
- invites: [{ email: 'test@example.com', name: 'Test' }],
+ invites: [{ email: "test@example.com", name: "Test" }],
user,
ip,
});
expect(response.sent.length).toEqual(1);
});
- it('should filter empty invites', async () => {
+ it("should filter empty invites", async () => {
const user = await buildUser();
const response = await userInviter({
- invites: [{ email: ' ', name: 'Test' }],
+ invites: [{ email: " ", name: "Test" }],
user,
ip,
});
expect(response.sent.length).toEqual(0);
});
- it('should filter obviously bunk emails', async () => {
+ it("should filter obviously bunk emails", async () => {
const user = await buildUser();
const response = await userInviter({
- invites: [{ email: 'notanemail', name: 'Test' }],
+ invites: [{ email: "notanemail", name: "Test" }],
user,
ip,
});
expect(response.sent.length).toEqual(0);
});
- it('should not send duplicates', async () => {
+ it("should not send duplicates", async () => {
const user = await buildUser();
const response = await userInviter({
invites: [
- { email: 'the@same.com', name: 'Test' },
- { email: 'the@SAME.COM', name: 'Test' },
+ { email: "the@same.com", name: "Test" },
+ { email: "the@SAME.COM", name: "Test" },
],
user,
ip,
@@ -51,7 +51,7 @@ describe('userInviter', async () => {
expect(response.sent.length).toEqual(1);
});
- it('should not send invites to existing team members', async () => {
+ it("should not send invites to existing team members", async () => {
const user = await buildUser();
const response = await userInviter({
invites: [{ email: user.email, name: user.name }],
diff --git a/server/emails/CollectionNotificationEmail.js b/server/emails/CollectionNotificationEmail.js
index b5a0238f..62f882a8 100644
--- a/server/emails/CollectionNotificationEmail.js
+++ b/server/emails/CollectionNotificationEmail.js
@@ -1,13 +1,13 @@
// @flow
-import * as React from 'react';
-import { User, Collection } from '../models';
-import EmailTemplate from './components/EmailLayout';
-import Body from './components/Body';
-import Button from './components/Button';
-import Heading from './components/Heading';
-import Header from './components/Header';
-import Footer from './components/Footer';
-import EmptySpace from './components/EmptySpace';
+import * as React from "react";
+import { User, Collection } from "../models";
+import EmailTemplate from "./components/EmailLayout";
+import Body from "./components/Body";
+import Button from "./components/Button";
+import Heading from "./components/Heading";
+import Header from "./components/Header";
+import Footer from "./components/Footer";
+import EmptySpace from "./components/EmptySpace";
export type Props = {
actor: User,
@@ -19,7 +19,7 @@ export type Props = {
export const collectionNotificationEmailText = ({
actor,
collection,
- eventName = 'created',
+ eventName = "created",
}: Props) => `
${collection.name}
@@ -31,7 +31,7 @@ Open Collection: ${process.env.URL}${collection.url}
export const CollectionNotificationEmail = ({
actor,
collection,
- eventName = 'created',
+ eventName = "created",
unsubscribeUrl,
}: Props) => {
return (
diff --git a/server/emails/DocumentNotificationEmail.js b/server/emails/DocumentNotificationEmail.js
index dc3199ae..dc1c4248 100644
--- a/server/emails/DocumentNotificationEmail.js
+++ b/server/emails/DocumentNotificationEmail.js
@@ -1,13 +1,13 @@
// @flow
-import * as React from 'react';
-import { User, Document, Team, Collection } from '../models';
-import EmailTemplate from './components/EmailLayout';
-import Body from './components/Body';
-import Button from './components/Button';
-import Heading from './components/Heading';
-import Header from './components/Header';
-import Footer from './components/Footer';
-import EmptySpace from './components/EmptySpace';
+import * as React from "react";
+import { User, Document, Team, Collection } from "../models";
+import EmailTemplate from "./components/EmailLayout";
+import Body from "./components/Body";
+import Button from "./components/Button";
+import Heading from "./components/Heading";
+import Header from "./components/Header";
+import Footer from "./components/Footer";
+import EmptySpace from "./components/EmptySpace";
export type Props = {
actor: User,
@@ -23,7 +23,7 @@ export const documentNotificationEmailText = ({
team,
document,
collection,
- eventName = 'published',
+ eventName = "published",
}: Props) => `
"${document.title}" ${eventName}
@@ -39,7 +39,7 @@ export const DocumentNotificationEmail = ({
team,
document,
collection,
- eventName = 'published',
+ eventName = "published",
unsubscribeUrl,
}: Props) => {
return (
@@ -51,7 +51,7 @@ export const DocumentNotificationEmail = ({
"{document.title}" {eventName}
- {actor.name} {eventName} the document "{document.title}", in the{' '}
+ {actor.name} {eventName} the document "{document.title}", in the{" "}
{collection.name} collection.
diff --git a/server/emails/ExportEmail.js b/server/emails/ExportEmail.js
index 14b3572d..7be8245a 100644
--- a/server/emails/ExportEmail.js
+++ b/server/emails/ExportEmail.js
@@ -1,12 +1,12 @@
// @flow
-import * as React from 'react';
-import EmailTemplate from './components/EmailLayout';
-import Body from './components/Body';
-import Button from './components/Button';
-import Heading from './components/Heading';
-import Header from './components/Header';
-import Footer from './components/Footer';
-import EmptySpace from './components/EmptySpace';
+import * as React from "react";
+import EmailTemplate from "./components/EmailLayout";
+import Body from "./components/Body";
+import Button from "./components/Button";
+import Heading from "./components/Heading";
+import Header from "./components/Header";
+import Footer from "./components/Footer";
+import EmptySpace from "./components/EmptySpace";
export const exportEmailText = `
Your Data Export
diff --git a/server/emails/InviteEmail.js b/server/emails/InviteEmail.js
index 46a89cfe..c034cb3e 100644
--- a/server/emails/InviteEmail.js
+++ b/server/emails/InviteEmail.js
@@ -1,12 +1,12 @@
// @flow
-import * as React from 'react';
-import EmailTemplate from './components/EmailLayout';
-import Body from './components/Body';
-import Button from './components/Button';
-import Heading from './components/Heading';
-import Header from './components/Header';
-import Footer from './components/Footer';
-import EmptySpace from './components/EmptySpace';
+import * as React from "react";
+import EmailTemplate from "./components/EmailLayout";
+import Body from "./components/Body";
+import Button from "./components/Button";
+import Heading from "./components/Heading";
+import Header from "./components/Header";
+import Footer from "./components/Footer";
+import EmptySpace from "./components/EmptySpace";
export type Props = {
name: string,
@@ -30,7 +30,7 @@ ${actorName} (${
actorEmail
}) has invited you to join Outline, a place for your team to build and share knowledge.
-Join now: ${teamUrl}${guest ? '?guest=true' : ''}
+Join now: ${teamUrl}${guest ? "?guest=true" : ""}
`;
export const InviteEmail = ({
@@ -52,7 +52,7 @@ export const InviteEmail = ({
-
diff --git a/server/emails/SigninEmail.js b/server/emails/SigninEmail.js
index 2bf6e480..939c3447 100644
--- a/server/emails/SigninEmail.js
+++ b/server/emails/SigninEmail.js
@@ -1,12 +1,12 @@
// @flow
-import * as React from 'react';
-import EmailTemplate from './components/EmailLayout';
-import Body from './components/Body';
-import Button from './components/Button';
-import Heading from './components/Heading';
-import Header from './components/Header';
-import Footer from './components/Footer';
-import EmptySpace from './components/EmptySpace';
+import * as React from "react";
+import EmailTemplate from "./components/EmailLayout";
+import Body from "./components/Body";
+import Button from "./components/Button";
+import Heading from "./components/Heading";
+import Header from "./components/Header";
+import Footer from "./components/Footer";
+import EmptySpace from "./components/EmptySpace";
export type Props = {
token: string,
diff --git a/server/emails/WelcomeEmail.js b/server/emails/WelcomeEmail.js
index e1a1bb0d..4f4024e5 100644
--- a/server/emails/WelcomeEmail.js
+++ b/server/emails/WelcomeEmail.js
@@ -1,12 +1,12 @@
// @flow
-import * as React from 'react';
-import EmailTemplate from './components/EmailLayout';
-import Body from './components/Body';
-import Button from './components/Button';
-import Heading from './components/Heading';
-import Header from './components/Header';
-import Footer from './components/Footer';
-import EmptySpace from './components/EmptySpace';
+import * as React from "react";
+import EmailTemplate from "./components/EmailLayout";
+import Body from "./components/Body";
+import Button from "./components/Button";
+import Heading from "./components/Heading";
+import Header from "./components/Header";
+import Footer from "./components/Footer";
+import EmptySpace from "./components/EmptySpace";
export type Props = {
teamUrl: string,
diff --git a/server/emails/components/Body.js b/server/emails/components/Body.js
index 06c494e6..d0f74c96 100644
--- a/server/emails/components/Body.js
+++ b/server/emails/components/Body.js
@@ -1,8 +1,8 @@
// @flow
-import * as React from 'react';
-import { Table, TBody, TR, TD } from 'oy-vey';
+import * as React from "react";
+import { Table, TBody, TR, TD } from "oy-vey";
-import EmptySpace from './EmptySpace';
+import EmptySpace from "./EmptySpace";
type Props = {
children: React.Node,
diff --git a/server/emails/components/Button.js b/server/emails/components/Button.js
index 6b81faa5..812691d5 100644
--- a/server/emails/components/Button.js
+++ b/server/emails/components/Button.js
@@ -1,18 +1,18 @@
// @flow
-import * as React from 'react';
+import * as React from "react";
type Props = { href: string, children: React.Node };
export default (props: Props) => {
const style = {
- display: 'inline-block',
- padding: '10px 20px',
- color: '#FFFFFF',
- background: '#000000',
- borderRadius: '4px',
+ display: "inline-block",
+ padding: "10px 20px",
+ color: "#FFFFFF",
+ background: "#000000",
+ borderRadius: "4px",
fontWeight: 500,
- textDecoration: 'none',
- cursor: 'pointer',
+ textDecoration: "none",
+ cursor: "pointer",
};
return (
diff --git a/server/emails/components/EmailLayout.js b/server/emails/components/EmailLayout.js
index 17e8cf27..80424781 100644
--- a/server/emails/components/EmailLayout.js
+++ b/server/emails/components/EmailLayout.js
@@ -1,7 +1,7 @@
// @flow
-import * as React from 'react';
-import { Table, TBody, TR, TD } from 'oy-vey';
-import theme from '../../../shared/styles/theme';
+import * as React from "react";
+import { Table, TBody, TR, TD } from "oy-vey";
+import theme from "../../../shared/styles/theme";
type Props = {
children: React.Node,
diff --git a/server/emails/components/EmptySpace.js b/server/emails/components/EmptySpace.js
index 20635374..051fbeb0 100644
--- a/server/emails/components/EmptySpace.js
+++ b/server/emails/components/EmptySpace.js
@@ -1,13 +1,13 @@
// @flow
-import * as React from 'react';
-import { Table, TBody, TR, TD } from 'oy-vey';
+import * as React from "react";
+import { Table, TBody, TR, TD } from "oy-vey";
const EmptySpace = ({ height }: { height?: number }) => {
height = height || 16;
const style = {
lineHeight: `${height}px`,
- fontSize: '1px',
- msoLineHeightRule: 'exactly',
+ fontSize: "1px",
+ msoLineHeightRule: "exactly",
};
return (
@@ -18,7 +18,7 @@ const EmptySpace = ({ height }: { height?: number }) => {
width="100%"
height={`${height}px`}
style={style}
- dangerouslySetInnerHTML={{ __html: ' ' }}
+ dangerouslySetInnerHTML={{ __html: " " }}
/>
diff --git a/server/emails/components/Footer.js b/server/emails/components/Footer.js
index 548d529e..f32d59e8 100644
--- a/server/emails/components/Footer.js
+++ b/server/emails/components/Footer.js
@@ -1,8 +1,8 @@
// @flow
-import * as React from 'react';
-import { Table, TBody, TR, TD } from 'oy-vey';
-import { twitterUrl, spectrumUrl } from '../../../shared/utils/routeHelpers';
-import theme from '../../../shared/styles/theme';
+import * as React from "react";
+import { Table, TBody, TR, TD } from "oy-vey";
+import { twitterUrl, spectrumUrl } from "../../../shared/utils/routeHelpers";
+import theme from "../../../shared/styles/theme";
type Props = {
unsubscribeUrl?: string,
@@ -10,29 +10,29 @@ type Props = {
export default ({ unsubscribeUrl }: Props) => {
const footerStyle = {
- padding: '20px 0',
+ padding: "20px 0",
borderTop: `1px solid ${theme.smokeDark}`,
color: theme.slate,
- fontSize: '14px',
+ fontSize: "14px",
};
const unsubStyle = {
- padding: '0',
+ padding: "0",
color: theme.slate,
- fontSize: '14px',
+ fontSize: "14px",
};
const linkStyle = {
color: theme.slate,
fontWeight: 500,
- textDecoration: 'none',
- marginRight: '10px',
+ textDecoration: "none",
+ marginRight: "10px",
};
const externalLinkStyle = {
color: theme.slate,
- textDecoration: 'none',
- margin: '0 10px',
+ textDecoration: "none",
+ margin: "0 10px",
};
return (
diff --git a/server/emails/components/Header.js b/server/emails/components/Header.js
index fd25e4cb..54d0fe9d 100644
--- a/server/emails/components/Header.js
+++ b/server/emails/components/Header.js
@@ -1,7 +1,7 @@
// @flow
-import * as React from 'react';
-import { Table, TBody, TR, TD } from 'oy-vey';
-import EmptySpace from './EmptySpace';
+import * as React from "react";
+import { Table, TBody, TR, TD } from "oy-vey";
+import EmptySpace from "./EmptySpace";
export default () => {
return (
diff --git a/server/emails/components/Heading.js b/server/emails/components/Heading.js
index 3d7a1009..6138b2ac 100644
--- a/server/emails/components/Heading.js
+++ b/server/emails/components/Heading.js
@@ -1,9 +1,9 @@
// @flow
-import * as React from 'react';
+import * as React from "react";
const style = {
fontWeight: 500,
- fontSize: '18px',
+ fontSize: "18px",
};
type Props = {
diff --git a/server/emails/index.js b/server/emails/index.js
index cb6c86b6..83d187a2 100644
--- a/server/emails/index.js
+++ b/server/emails/index.js
@@ -1,13 +1,13 @@
// @flow
-import Koa from 'koa';
-import Router from 'koa-router';
-import { NotFoundError } from '../errors';
-import { Mailer } from '../mailer';
+import Koa from "koa";
+import Router from "koa-router";
+import { NotFoundError } from "../errors";
+import { Mailer } from "../mailer";
const emailPreviews = new Koa();
const router = new Router();
-router.get('/:type/:format', async ctx => {
+router.get("/:type/:format", async ctx => {
let mailerOutput;
let mailer = new Mailer();
mailer.transporter = {
@@ -21,13 +21,13 @@ router.get('/:type/:format', async ctx => {
default:
if (Object.getOwnPropertyNames(mailer).includes(ctx.params.type)) {
// $FlowIssue flow doesn't like this but we're ok with it
- mailer[ctx.params.type]('user@example.com');
- } else throw new NotFoundError('Email template could not be found');
+ mailer[ctx.params.type]("user@example.com");
+ } else throw new NotFoundError("Email template could not be found");
}
if (!mailerOutput) return;
- if (ctx.params.format === 'text') {
+ if (ctx.params.format === "text") {
ctx.body = mailerOutput.text;
} else {
ctx.body = mailerOutput.html;
diff --git a/server/errors.js b/server/errors.js
index 26a1111c..4e72e981 100644
--- a/server/errors.js
+++ b/server/errors.js
@@ -1,53 +1,53 @@
// @flow
-import httpErrors from 'http-errors';
+import httpErrors from "http-errors";
export function AuthenticationError(
- message: string = 'Invalid authentication'
+ message: string = "Invalid authentication"
) {
- return httpErrors(401, message, { id: 'authentication_required' });
+ return httpErrors(401, message, { id: "authentication_required" });
}
export function AuthorizationError(
- message: string = 'You do not have permission to access this resource'
+ message: string = "You do not have permission to access this resource"
) {
- return httpErrors(403, message, { id: 'permission_required' });
+ return httpErrors(403, message, { id: "permission_required" });
}
export function AdminRequiredError(
- message: string = 'An admin role is required to access this resource'
+ message: string = "An admin role is required to access this resource"
) {
- return httpErrors(403, message, { id: 'admin_required' });
+ return httpErrors(403, message, { id: "admin_required" });
}
export function UserSuspendedError({ adminEmail }: { adminEmail: string }) {
- return httpErrors(403, 'Your access has been suspended by the team admin', {
- id: 'user_suspended',
+ return httpErrors(403, "Your access has been suspended by the team admin", {
+ id: "user_suspended",
errorData: {
adminEmail,
},
});
}
-export function InvalidRequestError(message: string = 'Request invalid') {
- return httpErrors(400, message, { id: 'invalid_request' });
+export function InvalidRequestError(message: string = "Request invalid") {
+ return httpErrors(400, message, { id: "invalid_request" });
}
-export function NotFoundError(message: string = 'Resource not found') {
- return httpErrors(404, message, { id: 'not_found' });
+export function NotFoundError(message: string = "Resource not found") {
+ return httpErrors(404, message, { id: "not_found" });
}
export function ParamRequiredError(
- message: string = 'Required parameter missing'
+ message: string = "Required parameter missing"
) {
- return httpErrors(400, message, { id: 'param_required' });
+ return httpErrors(400, message, { id: "param_required" });
}
-export function ValidationError(message: string = 'Validation failed') {
- return httpErrors(400, message, { id: 'validation_error' });
+export function ValidationError(message: string = "Validation failed") {
+ return httpErrors(400, message, { id: "validation_error" });
}
export function EditorUpdateError(
- message: string = 'The client editor is out of date and must be reloaded'
+ message: string = "The client editor is out of date and must be reloaded"
) {
- return httpErrors(400, message, { id: 'editor_update_required' });
+ return httpErrors(400, message, { id: "editor_update_required" });
}
diff --git a/server/events.js b/server/events.js
index 3619466f..c1747188 100644
--- a/server/events.js
+++ b/server/events.js
@@ -1,21 +1,21 @@
// @flow
-import * as Sentry from '@sentry/node';
-import { createQueue } from './utils/queue';
-import services from './services';
+import * as Sentry from "@sentry/node";
+import { createQueue } from "./utils/queue";
+import services from "./services";
export type UserEvent =
| {
name: | 'users.create' // eslint-disable-line
- | 'users.update'
- | 'users.suspend'
- | 'users.activate'
- | 'users.delete',
+ | "users.update"
+ | "users.suspend"
+ | "users.activate"
+ | "users.delete",
userId: string,
teamId: string,
actorId: string,
}
| {
- name: 'users.invite',
+ name: "users.invite",
teamId: string,
actorId: string,
data: {
@@ -27,22 +27,22 @@ export type UserEvent =
export type DocumentEvent =
| {
name: | 'documents.create' // eslint-disable-line
- | 'documents.publish'
- | 'documents.delete'
- | 'documents.pin'
- | 'documents.unpin'
- | 'documents.archive'
- | 'documents.unarchive'
- | 'documents.restore'
- | 'documents.star'
- | 'documents.unstar',
+ | "documents.publish"
+ | "documents.delete"
+ | "documents.pin"
+ | "documents.unpin"
+ | "documents.archive"
+ | "documents.unarchive"
+ | "documents.restore"
+ | "documents.star"
+ | "documents.unstar",
documentId: string,
collectionId: string,
teamId: string,
actorId: string,
}
| {
- name: 'documents.move',
+ name: "documents.move",
documentId: string,
collectionId: string,
teamId: string,
@@ -53,7 +53,7 @@ export type DocumentEvent =
},
}
| {
- name: 'documents.update',
+ name: "documents.update",
documentId: string,
collectionId: string,
teamId: string,
@@ -67,21 +67,21 @@ export type DocumentEvent =
export type CollectionEvent =
| {
name: | 'collections.create' // eslint-disable-line
- | 'collections.update'
- | 'collections.delete',
+ | "collections.update"
+ | "collections.delete",
collectionId: string,
teamId: string,
actorId: string,
}
| {
- name: 'collections.add_user' | 'collections.remove_user',
+ name: "collections.add_user" | "collections.remove_user",
userId: string,
collectionId: string,
teamId: string,
actorId: string,
}
| {
- name: 'collections.add_group' | 'collections.remove_group',
+ name: "collections.add_group" | "collections.remove_group",
collectionId: string,
teamId: string,
actorId: string,
@@ -91,7 +91,7 @@ export type CollectionEvent =
export type GroupEvent =
| {
- name: 'groups.create' | 'groups.delete' | 'groups.update',
+ name: "groups.create" | "groups.delete" | "groups.update",
actorId: string,
modelId: string,
teamId: string,
@@ -99,7 +99,7 @@ export type GroupEvent =
ip: string,
}
| {
- name: 'groups.add_user' | 'groups.remove_user',
+ name: "groups.add_user" | "groups.remove_user",
actorId: string,
userId: string,
modelId: string,
@@ -109,7 +109,7 @@ export type GroupEvent =
};
export type IntegrationEvent = {
- name: 'integrations.create' | 'integrations.update',
+ name: "integrations.create" | "integrations.update",
modelId: string,
teamId: string,
actorId: string,
@@ -122,8 +122,8 @@ export type Event =
| IntegrationEvent
| GroupEvent;
-const globalEventsQueue = createQueue('global events');
-const serviceEventsQueue = createQueue('service events');
+const globalEventsQueue = createQueue("global events");
+const serviceEventsQueue = createQueue("service events");
// this queue processes global events and hands them off to service hooks
globalEventsQueue.process(async job => {
@@ -148,7 +148,7 @@ serviceEventsQueue.process(async job => {
service.on(event).catch(error => {
if (process.env.SENTRY_DSN) {
Sentry.withScope(function(scope) {
- scope.setExtra('event', event);
+ scope.setExtra("event", event);
Sentry.captureException(error);
});
} else {
diff --git a/server/index.js b/server/index.js
index 4f103101..1ec39eda 100644
--- a/server/index.js
+++ b/server/index.js
@@ -1,22 +1,22 @@
// @flow
-import http from 'http';
-import IO from 'socket.io';
-import SocketAuth from 'socketio-auth';
-import socketRedisAdapter from 'socket.io-redis';
-import { getUserForJWT } from './utils/jwt';
-import { Document, Collection, View } from './models';
-import { client, subscriber } from './redis';
-import app from './app';
-import policy from './policies';
+import http from "http";
+import IO from "socket.io";
+import SocketAuth from "socketio-auth";
+import socketRedisAdapter from "socket.io-redis";
+import { getUserForJWT } from "./utils/jwt";
+import { Document, Collection, View } from "./models";
+import { client, subscriber } from "./redis";
+import app from "./app";
+import policy from "./policies";
const server = http.createServer(app.callback());
let io;
-if (process.env.WEBSOCKETS_ENABLED === 'true') {
+if (process.env.WEBSOCKETS_ENABLED === "true") {
const { can } = policy;
io = IO(server, {
- path: '/realtime',
+ path: "/realtime",
serveClient: false,
cookie: false,
});
@@ -38,7 +38,7 @@ if (process.env.WEBSOCKETS_ENABLED === 'true') {
// store the mapping between socket id and user id in redis
// so that it is accessible across multiple server nodes
- await client.hset(socket.id, 'userId', user.id);
+ await client.hset(socket.id, "userId", user.id);
return callback(null, true);
} catch (err) {
@@ -64,15 +64,15 @@ if (process.env.WEBSOCKETS_ENABLED === 'true') {
socket.join(rooms);
// allow the client to request to join rooms
- socket.on('join', async event => {
+ socket.on("join", async event => {
// user is joining a collection channel, because their permissions have
// changed, granting them access.
if (event.collectionId) {
const collection = await Collection.scope({
- method: ['withMembership', user.id],
+ method: ["withMembership", user.id],
}).findByPk(event.collectionId);
- if (can(user, 'read', collection)) {
+ if (can(user, "read", collection)) {
socket.join(`collection-${event.collectionId}`);
}
}
@@ -84,7 +84,7 @@ if (process.env.WEBSOCKETS_ENABLED === 'true') {
userId: user.id,
});
- if (can(user, 'read', document)) {
+ if (can(user, "read", document)) {
const room = `document-${event.documentId}`;
await View.touch(event.documentId, user.id, event.isEditing);
@@ -94,7 +94,7 @@ if (process.env.WEBSOCKETS_ENABLED === 'true') {
socket.join(room, () => {
// let everyone else in the room know that a new user joined
- io.to(room).emit('user.join', {
+ io.to(room).emit("user.join", {
userId: user.id,
documentId: event.documentId,
isEditing: event.isEditing,
@@ -109,10 +109,10 @@ if (process.env.WEBSOCKETS_ENABLED === 'true') {
// makes this easy.
let userIds = new Map();
for (const socketId of sockets) {
- const userId = await client.hget(socketId, 'userId');
+ const userId = await client.hget(socketId, "userId");
userIds.set(userId, userId);
}
- socket.emit('document.presence', {
+ socket.emit("document.presence", {
documentId: event.documentId,
userIds: Array.from(userIds.keys()),
editingIds: editing.map(view => view.userId),
@@ -124,14 +124,14 @@ if (process.env.WEBSOCKETS_ENABLED === 'true') {
});
// allow the client to request to leave rooms
- socket.on('leave', event => {
+ socket.on("leave", event => {
if (event.collectionId) {
socket.leave(`collection-${event.collectionId}`);
}
if (event.documentId) {
const room = `document-${event.documentId}`;
socket.leave(room, () => {
- io.to(room).emit('user.leave', {
+ io.to(room).emit("user.leave", {
userId: user.id,
documentId: event.documentId,
});
@@ -139,13 +139,13 @@ if (process.env.WEBSOCKETS_ENABLED === 'true') {
}
});
- socket.on('disconnecting', () => {
+ socket.on("disconnecting", () => {
const rooms = Object.keys(socket.rooms);
rooms.forEach(room => {
- if (room.startsWith('document-')) {
- const documentId = room.replace('document-', '');
- io.to(room).emit('user.leave', {
+ if (room.startsWith("document-")) {
+ const documentId = room.replace("document-", "");
+ io.to(room).emit("user.leave", {
userId: user.id,
documentId,
});
@@ -153,7 +153,7 @@ if (process.env.WEBSOCKETS_ENABLED === 'true') {
});
});
- socket.on('presence', async event => {
+ socket.on("presence", async event => {
const room = `document-${event.documentId}`;
if (event.documentId && socket.rooms[room]) {
@@ -164,7 +164,7 @@ if (process.env.WEBSOCKETS_ENABLED === 'true') {
);
view.user = user;
- io.to(room).emit('user.presence', {
+ io.to(room).emit("user.presence", {
userId: user.id,
documentId: event.documentId,
isEditing: event.isEditing,
@@ -175,16 +175,16 @@ if (process.env.WEBSOCKETS_ENABLED === 'true') {
});
}
-server.on('error', err => {
+server.on("error", err => {
throw err;
});
-server.on('listening', () => {
+server.on("listening", () => {
const address = server.address();
console.log(`\n> Listening on http://localhost:${address.port}\n`);
});
-server.listen(process.env.PORT || '3000');
+server.listen(process.env.PORT || "3000");
export const socketio = io;
diff --git a/server/logistics.js b/server/logistics.js
index 2cd19037..37d589b1 100644
--- a/server/logistics.js
+++ b/server/logistics.js
@@ -1,31 +1,31 @@
// @flow
-import debug from 'debug';
-import mailer from './mailer';
-import { Collection, Team } from './models';
-import { archiveCollections } from './utils/zip';
-import { createQueue } from './utils/queue';
+import debug from "debug";
+import mailer from "./mailer";
+import { Collection, Team } from "./models";
+import { archiveCollections } from "./utils/zip";
+import { createQueue } from "./utils/queue";
-const log = debug('logistics');
-const logisticsQueue = createQueue('logistics');
+const log = debug("logistics");
+const logisticsQueue = createQueue("logistics");
const queueOptions = {
attempts: 2,
removeOnComplete: true,
backoff: {
- type: 'exponential',
+ type: "exponential",
delay: 60 * 1000,
},
};
async function exportAndEmailCollections(teamId: string, email: string) {
- log('Archiving team', teamId);
+ log("Archiving team", teamId);
const team = await Team.findByPk(teamId);
const collections = await Collection.findAll({
where: { teamId },
- order: [['name', 'ASC']],
+ order: [["name", "ASC"]],
});
const filePath = await archiveCollections(collections);
- log('Archive path', filePath);
+ log("Archive path", filePath);
mailer.export({
to: email,
@@ -39,10 +39,10 @@ async function exportAndEmailCollections(teamId: string, email: string) {
}
logisticsQueue.process(async job => {
- log('Process', job.data);
+ log("Process", job.data);
switch (job.data.type) {
- case 'export-collections':
+ case "export-collections":
return await exportAndEmailCollections(job.data.teamId, job.data.email);
default:
}
@@ -51,7 +51,7 @@ logisticsQueue.process(async job => {
export const exportCollections = (teamId: string, email: string) => {
logisticsQueue.add(
{
- type: 'export-collections',
+ type: "export-collections",
teamId,
email,
},
diff --git a/server/mailer.js b/server/mailer.js
index 80d31a30..04eee026 100644
--- a/server/mailer.js
+++ b/server/mailer.js
@@ -1,33 +1,33 @@
// @flow
-import * as React from 'react';
-import debug from 'debug';
-import * as Sentry from '@sentry/node';
-import nodemailer from 'nodemailer';
-import Oy from 'oy-vey';
-import { createQueue } from './utils/queue';
-import { baseStyles } from './emails/components/EmailLayout';
-import { WelcomeEmail, welcomeEmailText } from './emails/WelcomeEmail';
-import { ExportEmail, exportEmailText } from './emails/ExportEmail';
-import { SigninEmail, signinEmailText } from './emails/SigninEmail';
+import * as React from "react";
+import debug from "debug";
+import * as Sentry from "@sentry/node";
+import nodemailer from "nodemailer";
+import Oy from "oy-vey";
+import { createQueue } from "./utils/queue";
+import { baseStyles } from "./emails/components/EmailLayout";
+import { WelcomeEmail, welcomeEmailText } from "./emails/WelcomeEmail";
+import { ExportEmail, exportEmailText } from "./emails/ExportEmail";
+import { SigninEmail, signinEmailText } from "./emails/SigninEmail";
import {
type Props as InviteEmailT,
InviteEmail,
inviteEmailText,
-} from './emails/InviteEmail';
+} from "./emails/InviteEmail";
import {
type Props as DocumentNotificationEmailT,
DocumentNotificationEmail,
documentNotificationEmailText,
-} from './emails/DocumentNotificationEmail';
+} from "./emails/DocumentNotificationEmail";
import {
type Props as CollectionNotificationEmailT,
CollectionNotificationEmail,
collectionNotificationEmailText,
-} from './emails/CollectionNotificationEmail';
+} from "./emails/CollectionNotificationEmail";
-const log = debug('emails');
+const log = debug("emails");
-type Emails = 'welcome' | 'export';
+type Emails = "welcome" | "export";
type SendMailType = {
to: string,
@@ -67,7 +67,7 @@ export class Mailer {
if (transporter) {
const html = Oy.renderTemplate(data.html, {
title: data.title,
- headCSS: [baseStyles, data.headCSS].join(' '),
+ headCSS: [baseStyles, data.headCSS].join(" "),
previewText: data.previewText,
});
@@ -94,9 +94,9 @@ export class Mailer {
welcome = async (opts: { to: string, teamUrl: string }) => {
this.sendMail({
to: opts.to,
- title: 'Welcome to Outline',
+ title: "Welcome to Outline",
previewText:
- 'Outline is a place for your team to build and share knowledge.',
+ "Outline is a place for your team to build and share knowledge.",
html: ,
text: welcomeEmailText(opts),
});
@@ -106,7 +106,7 @@ export class Mailer {
this.sendMail({
to: opts.to,
attachments: opts.attachments,
- title: 'Your requested export',
+ title: "Your requested export",
previewText: "Here's your request data export from Outline",
html: ,
text: exportEmailText,
@@ -120,7 +120,7 @@ export class Mailer {
opts.teamName
}’s knowledgebase`,
previewText:
- 'Outline is a place for your team to build and share knowledge.',
+ "Outline is a place for your team to build and share knowledge.",
html: ,
text: inviteEmailText(opts),
});
@@ -129,8 +129,8 @@ export class Mailer {
signin = async (opts: { to: string, token: string, teamUrl: string }) => {
this.sendMail({
to: opts.to,
- title: 'Magic signin link',
- previewText: 'Here’s your link to signin to Outline.',
+ title: "Magic signin link",
+ previewText: "Here’s your link to signin to Outline.",
html: ,
text: signinEmailText(opts),
});
@@ -165,7 +165,7 @@ export class Mailer {
let smtpConfig = {
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
- secure: process.env.NODE_ENV === 'production',
+ secure: process.env.NODE_ENV === "production",
auth: undefined,
};
@@ -184,7 +184,7 @@ export class Mailer {
const mailer = new Mailer();
export default mailer;
-export const mailerQueue = createQueue('email');
+export const mailerQueue = createQueue("email");
mailerQueue.process(async (job: EmailJob) => {
// $FlowIssue flow doesn't like dynamic values
@@ -204,7 +204,7 @@ export const sendEmail = (type: Emails, to: string, options?: Object = {}) => {
attempts: 5,
removeOnComplete: true,
backoff: {
- type: 'exponential',
+ type: "exponential",
delay: 60 * 1000,
},
}
diff --git a/server/mailer.test.js b/server/mailer.test.js
index fc0d4945..31c00b40 100644
--- a/server/mailer.test.js
+++ b/server/mailer.test.js
@@ -1,13 +1,13 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import mailer from './mailer';
+import mailer from "./mailer";
-describe('Mailer', () => {
+describe("Mailer", () => {
let fakeMailer = mailer;
let sendMailOutput;
beforeEach(() => {
- process.env.URL = 'http://localhost:3000';
- process.env.SMTP_FROM_EMAIL = 'hello@example.com';
+ process.env.URL = "http://localhost:3000";
+ process.env.SMTP_FROM_EMAIL = "hello@example.com";
jest.resetModules();
fakeMailer.transporter = {
@@ -15,10 +15,10 @@ describe('Mailer', () => {
};
});
- test('#welcome', () => {
+ test("#welcome", () => {
fakeMailer.welcome({
- to: 'user@example.com',
- teamUrl: 'http://example.com',
+ to: "user@example.com",
+ teamUrl: "http://example.com",
});
expect(sendMailOutput).toMatchSnapshot();
});
diff --git a/server/middlewares/apexRedirect.js b/server/middlewares/apexRedirect.js
index 61651844..992837ad 100644
--- a/server/middlewares/apexRedirect.js
+++ b/server/middlewares/apexRedirect.js
@@ -1,12 +1,12 @@
// @flow
-import type { Context } from 'koa';
+import type { Context } from "koa";
export default function apexRedirect() {
return async function apexRedirectMiddleware(
ctx: Context,
next: () => Promise<*>
) {
- if (ctx.headers.host === 'getoutline.com') {
+ if (ctx.headers.host === "getoutline.com") {
ctx.redirect(`https://www.${ctx.headers.host}${ctx.path}`);
} else {
return next();
diff --git a/server/middlewares/authentication.js b/server/middlewares/authentication.js
index cbc7cca6..12a81641 100644
--- a/server/middlewares/authentication.js
+++ b/server/middlewares/authentication.js
@@ -1,20 +1,20 @@
// @flow
-import JWT from 'jsonwebtoken';
-import { type Context } from 'koa';
-import { User, ApiKey } from '../models';
-import { getUserForJWT } from '../utils/jwt';
-import { AuthenticationError, UserSuspendedError } from '../errors';
-import addMonths from 'date-fns/add_months';
-import addMinutes from 'date-fns/add_minutes';
-import { getCookieDomain } from '../../shared/utils/domains';
+import JWT from "jsonwebtoken";
+import { type Context } from "koa";
+import { User, ApiKey } from "../models";
+import { getUserForJWT } from "../utils/jwt";
+import { AuthenticationError, UserSuspendedError } from "../errors";
+import addMonths from "date-fns/add_months";
+import addMinutes from "date-fns/add_minutes";
+import { getCookieDomain } from "../../shared/utils/domains";
export default function auth(options?: { required?: boolean } = {}) {
return async function authMiddleware(ctx: Context, next: () => Promise<*>) {
let token;
- const authorizationHeader = ctx.request.get('authorization');
+ const authorizationHeader = ctx.request.get("authorization");
if (authorizationHeader) {
- const parts = authorizationHeader.split(' ');
+ const parts = authorizationHeader.split(" ");
if (parts.length === 2) {
const scheme = parts[0];
const credentials = parts[1];
@@ -33,11 +33,11 @@ export default function auth(options?: { required?: boolean } = {}) {
} else if (ctx.request.query.token) {
token = ctx.request.query.token;
} else {
- token = ctx.cookies.get('accessToken');
+ token = ctx.cookies.get("accessToken");
}
if (!token && options.required !== false) {
- throw new AuthenticationError('Authentication required');
+ throw new AuthenticationError("Authentication required");
}
let user;
@@ -52,13 +52,13 @@ export default function auth(options?: { required?: boolean } = {}) {
},
});
} catch (e) {
- throw new AuthenticationError('Invalid API key');
+ throw new AuthenticationError("Invalid API key");
}
- if (!apiKey) throw new AuthenticationError('Invalid API key');
+ if (!apiKey) throw new AuthenticationError("Invalid API key");
user = await User.findByPk(apiKey.userId);
- if (!user) throw new AuthenticationError('Invalid API key');
+ if (!user) throw new AuthenticationError("Invalid API key");
} else {
// JWT
user = await getUserForJWT(token);
@@ -83,7 +83,7 @@ export default function auth(options?: { required?: boolean } = {}) {
ctx.signIn = async (user, team, service, isFirstSignin = false) => {
if (user.isSuspended) {
- return ctx.redirect('/?notice=suspended');
+ return ctx.redirect("/?notice=suspended");
}
// update the database when the user last signed in
@@ -94,18 +94,18 @@ export default function auth(options?: { required?: boolean } = {}) {
// set a cookie for which service we last signed in with. This is
// only used to display a UI hint for the user for next time
- ctx.cookies.set('lastSignedIn', service, {
+ ctx.cookies.set("lastSignedIn", service, {
httpOnly: false,
- expires: new Date('2100'),
+ expires: new Date("2100"),
domain,
});
// set a transfer cookie for the access token itself and redirect
// to the teams subdomain if subdomains are enabled
- if (process.env.SUBDOMAINS_ENABLED === 'true' && team.subdomain) {
+ if (process.env.SUBDOMAINS_ENABLED === "true" && team.subdomain) {
// get any existing sessions (teams signed in) and add this team
const existing = JSON.parse(
- decodeURIComponent(ctx.cookies.get('sessions') || '') || '{}'
+ decodeURIComponent(ctx.cookies.get("sessions") || "") || "{}"
);
const sessions = encodeURIComponent(
JSON.stringify({
@@ -117,24 +117,24 @@ export default function auth(options?: { required?: boolean } = {}) {
},
})
);
- ctx.cookies.set('sessions', sessions, {
+ ctx.cookies.set("sessions", sessions, {
httpOnly: false,
expires,
domain,
});
- ctx.cookies.set('accessToken', user.getJwtToken(), {
+ ctx.cookies.set("accessToken", user.getJwtToken(), {
httpOnly: true,
expires: addMinutes(new Date(), 1),
domain,
});
ctx.redirect(`${team.url}/auth/redirect`);
} else {
- ctx.cookies.set('accessToken', user.getJwtToken(), {
+ ctx.cookies.set("accessToken", user.getJwtToken(), {
httpOnly: false,
expires,
});
- ctx.redirect(`${team.url}/home${isFirstSignin ? '?welcome' : ''}`);
+ ctx.redirect(`${team.url}/home${isFirstSignin ? "?welcome" : ""}`);
}
};
diff --git a/server/middlewares/authentication.test.js b/server/middlewares/authentication.test.js
index 751b5523..c01e8bf2 100644
--- a/server/middlewares/authentication.test.js
+++ b/server/middlewares/authentication.test.js
@@ -1,15 +1,15 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import { flushdb, seed } from '../test/support';
-import { buildUser } from '../test/factories';
-import { ApiKey } from '../models';
-import randomstring from 'randomstring';
-import auth from './authentication';
+import { flushdb, seed } from "../test/support";
+import { buildUser } from "../test/factories";
+import { ApiKey } from "../models";
+import randomstring from "randomstring";
+import auth from "./authentication";
beforeEach(flushdb);
-describe('Authentication middleware', async () => {
- describe('with JWT', () => {
- it('should authenticate with correct token', async () => {
+describe("Authentication middleware", async () => {
+ describe("with JWT", () => {
+ it("should authenticate with correct token", async () => {
const state = {};
const { user } = await seed();
const authMiddleware = auth();
@@ -27,7 +27,7 @@ describe('Authentication middleware', async () => {
expect(state.user.id).toEqual(user.id);
});
- it('should return error with invalid token', async () => {
+ it("should return error with invalid token", async () => {
const state = {};
const { user } = await seed();
const authMiddleware = auth();
@@ -44,13 +44,13 @@ describe('Authentication middleware', async () => {
jest.fn()
);
} catch (e) {
- expect(e.message).toBe('Invalid token');
+ expect(e.message).toBe("Invalid token");
}
});
});
- describe('with API key', () => {
- it('should authenticate user with valid API key', async () => {
+ describe("with API key", () => {
+ it("should authenticate user with valid API key", async () => {
const state = {};
const { user } = await seed();
const authMiddleware = auth();
@@ -71,7 +71,7 @@ describe('Authentication middleware', async () => {
expect(state.user.id).toEqual(user.id);
});
- it('should return error with invalid API key', async () => {
+ it("should return error with invalid API key", async () => {
const state = {};
const authMiddleware = auth();
@@ -87,12 +87,12 @@ describe('Authentication middleware', async () => {
jest.fn()
);
} catch (e) {
- expect(e.message).toBe('Invalid API key');
+ expect(e.message).toBe("Invalid API key");
}
});
});
- it('should return error message if no auth token is available', async () => {
+ it("should return error message if no auth token is available", async () => {
const state = {};
const authMiddleware = auth();
@@ -100,7 +100,7 @@ describe('Authentication middleware', async () => {
await authMiddleware(
{
request: {
- get: jest.fn(() => 'error'),
+ get: jest.fn(() => "error"),
},
state,
cache: {},
@@ -114,7 +114,7 @@ describe('Authentication middleware', async () => {
}
});
- it('should allow passing auth token as a GET param', async () => {
+ it("should allow passing auth token as a GET param", async () => {
const state = {};
const { user } = await seed();
const authMiddleware = auth();
@@ -136,7 +136,7 @@ describe('Authentication middleware', async () => {
expect(state.user.id).toEqual(user.id);
});
- it('should allow passing auth token in body params', async () => {
+ it("should allow passing auth token in body params", async () => {
const state = {};
const { user } = await seed();
const authMiddleware = auth();
@@ -157,7 +157,7 @@ describe('Authentication middleware', async () => {
expect(state.user.id).toEqual(user.id);
});
- it('should return an error for suspended users', async () => {
+ it("should return an error for suspended users", async () => {
const state = {};
const admin = await buildUser({});
const user = await buildUser({
@@ -179,7 +179,7 @@ describe('Authentication middleware', async () => {
);
} catch (e) {
expect(e.message).toEqual(
- 'Your access has been suspended by the team admin'
+ "Your access has been suspended by the team admin"
);
expect(e.errorData.adminEmail).toEqual(admin.email);
}
diff --git a/server/middlewares/errorHandling.js b/server/middlewares/errorHandling.js
index 9c5100b5..904fece5 100644
--- a/server/middlewares/errorHandling.js
+++ b/server/middlewares/errorHandling.js
@@ -1,7 +1,7 @@
// @flow
-import Sequelize from 'sequelize';
-import { snakeCase } from 'lodash';
-import { type Context } from 'koa';
+import Sequelize from "sequelize";
+import { snakeCase } from "lodash";
+import { type Context } from "koa";
export default function errorHandling() {
return async function errorHandlingMiddleware(
@@ -25,18 +25,18 @@ export default function errorHandling() {
if (message.match(/Not found/i)) {
ctx.status = 404;
- error = 'not_found';
+ error = "not_found";
}
if (message.match(/Authorization error/i)) {
ctx.status = 403;
- error = 'authorization_error';
+ error = "authorization_error";
}
if (ctx.status === 500) {
- message = 'Internal Server Error';
- error = 'internal_server_error';
- ctx.app.emit('error', err, ctx);
+ message = "Internal Server Error";
+ error = "internal_server_error";
+ ctx.app.emit("error", err, ctx);
}
ctx.body = {
diff --git a/server/middlewares/methodOverride.js b/server/middlewares/methodOverride.js
index 7872dfc1..fed29e57 100644
--- a/server/middlewares/methodOverride.js
+++ b/server/middlewares/methodOverride.js
@@ -1,16 +1,16 @@
// @flow
-import queryString from 'query-string';
-import { type Context } from 'koa';
+import queryString from "query-string";
+import { type Context } from "koa";
export default function methodOverride() {
return async function methodOverrideMiddleware(
ctx: Context,
next: () => Promise<*>
) {
- if (ctx.method === 'POST') {
+ if (ctx.method === "POST") {
// $FlowFixMe
ctx.body = ctx.request.body;
- } else if (ctx.method === 'GET') {
+ } else if (ctx.method === "GET") {
ctx.method = 'POST'; // eslint-disable-line
ctx.body = queryString.parse(ctx.querystring);
}
diff --git a/server/middlewares/validation.js b/server/middlewares/validation.js
index ae918250..d68f8025 100644
--- a/server/middlewares/validation.js
+++ b/server/middlewares/validation.js
@@ -1,13 +1,13 @@
// @flow
-import validator from 'validator';
-import { type Context } from 'koa';
-import { ParamRequiredError, ValidationError } from '../errors';
-import { validateColorHex } from '../../shared/utils/color';
+import validator from "validator";
+import { type Context } from "koa";
+import { ParamRequiredError, ValidationError } from "../errors";
+import { validateColorHex } from "../../shared/utils/color";
export default function validation() {
return function validationMiddleware(ctx: Context, next: () => Promise<*>) {
ctx.assertPresent = (value, message) => {
- if (value === undefined || value === null || value === '') {
+ if (value === undefined || value === null || value === "") {
throw new ParamRequiredError(message);
}
};
@@ -19,18 +19,18 @@ export default function validation() {
};
ctx.assertNotEmpty = (value, message) => {
- if (value === '') {
+ if (value === "") {
throw new ValidationError(message);
}
};
- ctx.assertEmail = (value = '', message) => {
+ ctx.assertEmail = (value = "", message) => {
if (!validator.isEmail(value)) {
throw new ValidationError(message);
}
};
- ctx.assertUuid = (value = '', message) => {
+ ctx.assertUuid = (value = "", message) => {
if (!validator.isUUID(value)) {
throw new ValidationError(message);
}
diff --git a/server/models/ApiKey.js b/server/models/ApiKey.js
index f0c0580b..0c89a119 100644
--- a/server/models/ApiKey.js
+++ b/server/models/ApiKey.js
@@ -1,9 +1,9 @@
// @flow
-import { DataTypes, sequelize } from '../sequelize';
-import randomstring from 'randomstring';
+import { DataTypes, sequelize } from "../sequelize";
+import randomstring from "randomstring";
const ApiKey = sequelize.define(
- 'apiKeys',
+ "apiKeys",
{
id: {
type: DataTypes.UUID,
@@ -17,12 +17,12 @@ const ApiKey = sequelize.define(
type: DataTypes.UUID,
allowNull: false,
references: {
- model: 'users',
+ model: "users",
},
},
},
{
- tableName: 'apiKeys',
+ tableName: "apiKeys",
paranoid: true,
hooks: {
beforeValidate: key => {
@@ -34,8 +34,8 @@ const ApiKey = sequelize.define(
ApiKey.associate = models => {
ApiKey.belongsTo(models.User, {
- as: 'user',
- foreignKey: 'userId',
+ as: "user",
+ foreignKey: "userId",
});
};
diff --git a/server/models/Attachment.js b/server/models/Attachment.js
index 7e14987a..54ff666c 100644
--- a/server/models/Attachment.js
+++ b/server/models/Attachment.js
@@ -1,10 +1,10 @@
// @flow
-import path from 'path';
-import { DataTypes, sequelize } from '../sequelize';
-import { deleteFromS3 } from '../utils/s3';
+import path from "path";
+import { DataTypes, sequelize } from "../sequelize";
+import { deleteFromS3 } from "../utils/s3";
const Attachment = sequelize.define(
- 'attachment',
+ "attachment",
{
id: {
type: DataTypes.UUID,
@@ -30,9 +30,9 @@ const Attachment = sequelize.define(
acl: {
type: DataTypes.STRING,
allowNull: false,
- defaultValue: 'public-read',
+ defaultValue: "public-read",
validate: {
- isIn: [['private', 'public-read']],
+ isIn: [["private", "public-read"]],
},
},
},
@@ -45,7 +45,7 @@ const Attachment = sequelize.define(
return `/api/attachments.redirect?id=${this.id}`;
},
isPrivate: function() {
- return this.acl === 'private';
+ return this.acl === "private";
},
},
}
diff --git a/server/models/Authentication.js b/server/models/Authentication.js
index e4a479dd..d9d48e52 100644
--- a/server/models/Authentication.js
+++ b/server/models/Authentication.js
@@ -1,7 +1,7 @@
// @flow
-import { DataTypes, sequelize, encryptedFields } from '../sequelize';
+import { DataTypes, sequelize, encryptedFields } from "../sequelize";
-const Authentication = sequelize.define('authentication', {
+const Authentication = sequelize.define("authentication", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@@ -9,17 +9,17 @@ const Authentication = sequelize.define('authentication', {
},
service: DataTypes.STRING,
scopes: DataTypes.ARRAY(DataTypes.STRING),
- token: encryptedFields.vault('token'),
+ token: encryptedFields.vault("token"),
});
Authentication.associate = models => {
Authentication.belongsTo(models.User, {
- as: 'user',
- foreignKey: 'userId',
+ as: "user",
+ foreignKey: "userId",
});
Authentication.belongsTo(models.Team, {
- as: 'team',
- foreignKey: 'teamId',
+ as: "team",
+ foreignKey: "teamId",
});
};
diff --git a/server/models/Backlink.js b/server/models/Backlink.js
index dfc60077..6188a78a 100644
--- a/server/models/Backlink.js
+++ b/server/models/Backlink.js
@@ -1,7 +1,7 @@
// @flow
-import { DataTypes, sequelize } from '../sequelize';
+import { DataTypes, sequelize } from "../sequelize";
-const Backlink = sequelize.define('backlink', {
+const Backlink = sequelize.define("backlink", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@@ -11,16 +11,16 @@ const Backlink = sequelize.define('backlink', {
Backlink.associate = models => {
Backlink.belongsTo(models.Document, {
- as: 'document',
- foreignKey: 'documentId',
+ as: "document",
+ foreignKey: "documentId",
});
Backlink.belongsTo(models.Document, {
- as: 'reverseDocument',
- foreignKey: 'reverseDocumentId',
+ as: "reverseDocument",
+ foreignKey: "reverseDocumentId",
});
Backlink.belongsTo(models.User, {
- as: 'user',
- foreignKey: 'userId',
+ as: "user",
+ foreignKey: "userId",
});
};
diff --git a/server/models/Collection.js b/server/models/Collection.js
index 2b25b1de..7830bd2c 100644
--- a/server/models/Collection.js
+++ b/server/models/Collection.js
@@ -1,15 +1,15 @@
// @flow
-import { find, concat, remove, uniq } from 'lodash';
-import slug from 'slug';
-import randomstring from 'randomstring';
-import { DataTypes, sequelize } from '../sequelize';
-import Document from './Document';
-import CollectionUser from './CollectionUser';
+import { find, concat, remove, uniq } from "lodash";
+import slug from "slug";
+import randomstring from "randomstring";
+import { DataTypes, sequelize } from "../sequelize";
+import Document from "./Document";
+import CollectionUser from "./CollectionUser";
-slug.defaults.mode = 'rfc3986';
+slug.defaults.mode = "rfc3986";
const Collection = sequelize.define(
- 'collection',
+ "collection",
{
id: {
type: DataTypes.UUID,
@@ -25,14 +25,14 @@ const Collection = sequelize.define(
maintainerApprovalRequired: DataTypes.BOOLEAN,
type: {
type: DataTypes.STRING,
- validate: { isIn: [['atlas', 'journal']] },
+ validate: { isIn: [["atlas", "journal"]] },
},
/* type: atlas */
documentStructure: DataTypes.JSONB,
},
{
- tableName: 'collections',
+ tableName: "collections",
paranoid: true,
hooks: {
beforeValidate: (collection: Collection) => {
@@ -47,8 +47,8 @@ const Collection = sequelize.define(
}
);
-Collection.addHook('beforeSave', async model => {
- if (model.icon === 'collection') {
+Collection.addHook("beforeSave", async model => {
+ if (model.icon === "collection") {
model.icon = null;
}
});
@@ -57,48 +57,48 @@ Collection.addHook('beforeSave', async model => {
Collection.associate = models => {
Collection.hasMany(models.Document, {
- as: 'documents',
- foreignKey: 'collectionId',
- onDelete: 'cascade',
+ as: "documents",
+ foreignKey: "collectionId",
+ onDelete: "cascade",
});
Collection.hasMany(models.CollectionUser, {
- as: 'memberships',
- foreignKey: 'collectionId',
- onDelete: 'cascade',
+ as: "memberships",
+ foreignKey: "collectionId",
+ onDelete: "cascade",
});
Collection.hasMany(models.CollectionGroup, {
- as: 'collectionGroupMemberships',
- foreignKey: 'collectionId',
- onDelete: 'cascade',
+ as: "collectionGroupMemberships",
+ foreignKey: "collectionId",
+ onDelete: "cascade",
});
Collection.belongsToMany(models.User, {
- as: 'users',
+ as: "users",
through: models.CollectionUser,
- foreignKey: 'collectionId',
+ foreignKey: "collectionId",
});
Collection.belongsToMany(models.Group, {
- as: 'groups',
+ as: "groups",
through: models.CollectionGroup,
- foreignKey: 'collectionId',
+ foreignKey: "collectionId",
});
Collection.belongsTo(models.User, {
- as: 'user',
- foreignKey: 'creatorId',
+ as: "user",
+ foreignKey: "creatorId",
});
Collection.belongsTo(models.Team, {
- as: 'team',
+ as: "team",
});
- Collection.addScope('withMembership', userId => ({
+ Collection.addScope("withMembership", userId => ({
include: [
{
model: models.CollectionUser,
- as: 'memberships',
+ as: "memberships",
where: { userId },
required: false,
},
{
model: models.CollectionGroup,
- as: 'collectionGroupMemberships',
+ as: "collectionGroupMemberships",
required: false,
// use of "separate" property: sequelize breaks when there are
@@ -111,11 +111,11 @@ Collection.associate = models => {
// CollectionGroup [inner join] Group [inner join] GroupUser [where] userId
include: {
model: models.Group,
- as: 'group',
+ as: "group",
required: true,
include: {
model: models.GroupUser,
- as: 'groupMemberships',
+ as: "groupMemberships",
required: true,
where: { userId },
},
@@ -123,16 +123,16 @@ Collection.associate = models => {
},
],
}));
- Collection.addScope('withAllMemberships', {
+ Collection.addScope("withAllMemberships", {
include: [
{
model: models.CollectionUser,
- as: 'memberships',
+ as: "memberships",
required: false,
},
{
model: models.CollectionGroup,
- as: 'collectionGroupMemberships',
+ as: "collectionGroupMemberships",
required: false,
// use of "separate" property: sequelize breaks when there are
@@ -145,11 +145,11 @@ Collection.associate = models => {
// CollectionGroup [inner join] Group [inner join] GroupUser [where] userId
include: {
model: models.Group,
- as: 'group',
+ as: "group",
required: true,
include: {
model: models.GroupUser,
- as: 'groupMemberships',
+ as: "groupMemberships",
required: true,
},
},
@@ -158,7 +158,7 @@ Collection.associate = models => {
});
};
-Collection.addHook('afterDestroy', async (model: Collection) => {
+Collection.addHook("afterDestroy", async (model: Collection) => {
await Document.destroy({
where: {
collectionId: model.id,
@@ -166,7 +166,7 @@ Collection.addHook('afterDestroy', async (model: Collection) => {
});
});
-Collection.addHook('afterCreate', (model: Collection, options) => {
+Collection.addHook("afterCreate", (model: Collection, options) => {
if (model.private) {
return CollectionUser.findOrCreate({
where: {
@@ -174,7 +174,7 @@ Collection.addHook('afterCreate', (model: Collection, options) => {
userId: model.creatorId,
},
defaults: {
- permission: 'read_write',
+ permission: "read_write",
createdById: model.creatorId,
},
transaction: options.transaction,
@@ -186,7 +186,7 @@ Collection.addHook('afterCreate', (model: Collection, options) => {
// get all the membership relationshps a user could have with the collection
Collection.membershipUserIds = async (collectionId: string) => {
- const collection = await Collection.scope('withAllMemberships').findByPk(
+ const collection = await Collection.scope("withAllMemberships").findByPk(
collectionId
);
diff --git a/server/models/Collection.test.js b/server/models/Collection.test.js
index 23a68758..f1c30fcb 100644
--- a/server/models/Collection.test.js
+++ b/server/models/Collection.test.js
@@ -1,31 +1,31 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import { flushdb, seed } from '../test/support';
-import { Collection, Document } from '../models';
+import { flushdb, seed } from "../test/support";
+import { Collection, Document } from "../models";
import {
buildUser,
buildGroup,
buildCollection,
buildTeam,
-} from '../test/factories';
-import uuid from 'uuid';
+} from "../test/factories";
+import uuid from "uuid";
beforeEach(flushdb);
beforeEach(jest.resetAllMocks);
-describe('#url', () => {
- test('should return correct url for the collection', () => {
- const collection = new Collection({ id: '1234' });
- expect(collection.url).toBe('/collections/1234');
+describe("#url", () => {
+ test("should return correct url for the collection", () => {
+ const collection = new Collection({ id: "1234" });
+ expect(collection.url).toBe("/collections/1234");
});
});
-describe('#addDocumentToStructure', async () => {
- test('should add as last element without index', async () => {
+describe("#addDocumentToStructure", async () => {
+ test("should add as last element without index", async () => {
const { collection } = await seed();
const id = uuid.v4();
const newDocument = new Document({
id,
- title: 'New end node',
+ title: "New end node",
parentDocumentId: null,
});
@@ -34,12 +34,12 @@ describe('#addDocumentToStructure', async () => {
expect(collection.documentStructure[1].id).toBe(id);
});
- test('should add with an index', async () => {
+ test("should add with an index", async () => {
const { collection } = await seed();
const id = uuid.v4();
const newDocument = new Document({
id,
- title: 'New end node',
+ title: "New end node",
parentDocumentId: null,
});
@@ -48,12 +48,12 @@ describe('#addDocumentToStructure', async () => {
expect(collection.documentStructure[1].id).toBe(id);
});
- test('should add as a child if with parent', async () => {
+ test("should add as a child if with parent", async () => {
const { collection, document } = await seed();
const id = uuid.v4();
const newDocument = new Document({
id,
- title: 'New end node',
+ title: "New end node",
parentDocumentId: document.id,
});
@@ -64,17 +64,17 @@ describe('#addDocumentToStructure', async () => {
expect(collection.documentStructure[0].children[0].id).toBe(id);
});
- test('should add as a child if with parent with index', async () => {
+ test("should add as a child if with parent with index", async () => {
const { collection, document } = await seed();
const newDocument = new Document({
id: uuid.v4(),
- title: 'node',
+ title: "node",
parentDocumentId: document.id,
});
const id = uuid.v4();
const secondDocument = new Document({
id,
- title: 'New start node',
+ title: "New start node",
parentDocumentId: document.id,
});
@@ -86,13 +86,13 @@ describe('#addDocumentToStructure', async () => {
expect(collection.documentStructure[0].children[0].id).toBe(id);
});
- describe('options: documentJson', async () => {
+ describe("options: documentJson", async () => {
test("should append supplied json over document's own", async () => {
const { collection } = await seed();
const id = uuid.v4();
const newDocument = new Document({
id: uuid.v4(),
- title: 'New end node',
+ title: "New end node",
parentDocumentId: null,
});
@@ -101,7 +101,7 @@ describe('#addDocumentToStructure', async () => {
children: [
{
id,
- title: 'Totally fake',
+ title: "Totally fake",
children: [],
},
],
@@ -113,16 +113,16 @@ describe('#addDocumentToStructure', async () => {
});
});
-describe('#updateDocument', () => {
+describe("#updateDocument", () => {
test("should update root document's data", async () => {
const { collection, document } = await seed();
- document.title = 'Updated title';
+ document.title = "Updated title";
await document.save();
await collection.updateDocument(document);
- expect(collection.documentStructure[0].title).toBe('Updated title');
+ expect(collection.documentStructure[0].title).toBe("Updated title");
});
test("should update child document's data", async () => {
@@ -134,32 +134,32 @@ describe('#updateDocument', () => {
userId: collection.creatorId,
lastModifiedById: collection.creatorId,
createdById: collection.creatorId,
- title: 'Child document',
- text: 'content',
+ title: "Child document",
+ text: "content",
});
await collection.addDocumentToStructure(newDocument);
- newDocument.title = 'Updated title';
+ newDocument.title = "Updated title";
await newDocument.save();
await collection.updateDocument(newDocument);
expect(collection.documentStructure[0].children[0].title).toBe(
- 'Updated title'
+ "Updated title"
);
});
});
-describe('#removeDocument', () => {
- test('should save if removing', async () => {
+describe("#removeDocument", () => {
+ test("should save if removing", async () => {
const { collection, document } = await seed();
- jest.spyOn(collection, 'save');
+ jest.spyOn(collection, "save");
await collection.deleteDocument(document);
expect(collection.save).toBeCalled();
});
- test('should remove documents from root', async () => {
+ test("should remove documents from root", async () => {
const { collection, document } = await seed();
await collection.deleteDocument(document);
@@ -174,7 +174,7 @@ describe('#removeDocument', () => {
expect(collectionDocuments.count).toBe(0);
});
- test('should remove a document with child documents', async () => {
+ test("should remove a document with child documents", async () => {
const { collection, document } = await seed();
// Add a child for testing
@@ -185,8 +185,8 @@ describe('#removeDocument', () => {
userId: collection.creatorId,
lastModifiedById: collection.creatorId,
createdById: collection.creatorId,
- title: 'Child document',
- text: 'content',
+ title: "Child document",
+ text: "content",
});
await collection.addDocumentToStructure(newDocument);
expect(collection.documentStructure[0].children.length).toBe(1);
@@ -202,7 +202,7 @@ describe('#removeDocument', () => {
expect(collectionDocuments.count).toBe(0);
});
- test('should remove a child document', async () => {
+ test("should remove a child document", async () => {
const { collection, document } = await seed();
// Add a child for testing
@@ -214,8 +214,8 @@ describe('#removeDocument', () => {
lastModifiedById: collection.creatorId,
createdById: collection.creatorId,
publishedAt: new Date(),
- title: 'Child document',
- text: 'content',
+ title: "Child document",
+ text: "content",
});
await collection.addDocumentToStructure(newDocument);
expect(collection.documentStructure.length).toBe(1);
@@ -236,8 +236,8 @@ describe('#removeDocument', () => {
});
});
-describe('#membershipUserIds', () => {
- test('should return collection and group memberships', async () => {
+describe("#membershipUserIds", () => {
+ test("should return collection and group memberships", async () => {
const team = await buildTeam();
const teamId = team.id;
diff --git a/server/models/CollectionGroup.js b/server/models/CollectionGroup.js
index 5988fc97..4a3ca253 100644
--- a/server/models/CollectionGroup.js
+++ b/server/models/CollectionGroup.js
@@ -1,14 +1,14 @@
// @flow
-import { DataTypes, sequelize } from '../sequelize';
+import { DataTypes, sequelize } from "../sequelize";
const CollectionGroup = sequelize.define(
- 'collection_group',
+ "collection_group",
{
permission: {
type: DataTypes.STRING,
- defaultValue: 'read_write',
+ defaultValue: "read_write",
validate: {
- isIn: [['read', 'read_write', 'maintainer']],
+ isIn: [["read", "read_write", "maintainer"]],
},
},
},
@@ -20,18 +20,18 @@ const CollectionGroup = sequelize.define(
CollectionGroup.associate = models => {
CollectionGroup.belongsTo(models.Collection, {
- as: 'collection',
- foreignKey: 'collectionId',
+ as: "collection",
+ foreignKey: "collectionId",
primary: true,
});
CollectionGroup.belongsTo(models.Group, {
- as: 'group',
- foreignKey: 'groupId',
+ as: "group",
+ foreignKey: "groupId",
primary: true,
});
CollectionGroup.belongsTo(models.User, {
- as: 'createdBy',
- foreignKey: 'createdById',
+ as: "createdBy",
+ foreignKey: "createdById",
});
};
diff --git a/server/models/CollectionUser.js b/server/models/CollectionUser.js
index b0085d12..73cd448d 100644
--- a/server/models/CollectionUser.js
+++ b/server/models/CollectionUser.js
@@ -1,14 +1,14 @@
// @flow
-import { DataTypes, sequelize } from '../sequelize';
+import { DataTypes, sequelize } from "../sequelize";
const CollectionUser = sequelize.define(
- 'collection_user',
+ "collection_user",
{
permission: {
type: DataTypes.STRING,
- defaultValue: 'read_write',
+ defaultValue: "read_write",
validate: {
- isIn: [['read', 'read_write', 'maintainer']],
+ isIn: [["read", "read_write", "maintainer"]],
},
},
},
@@ -19,16 +19,16 @@ const CollectionUser = sequelize.define(
CollectionUser.associate = models => {
CollectionUser.belongsTo(models.Collection, {
- as: 'collection',
- foreignKey: 'collectionId',
+ as: "collection",
+ foreignKey: "collectionId",
});
CollectionUser.belongsTo(models.User, {
- as: 'user',
- foreignKey: 'userId',
+ as: "user",
+ foreignKey: "userId",
});
CollectionUser.belongsTo(models.User, {
- as: 'createdBy',
- foreignKey: 'createdById',
+ as: "createdBy",
+ foreignKey: "createdById",
});
};
diff --git a/server/models/Document.js b/server/models/Document.js
index 256dcbad..7dcee840 100644
--- a/server/models/Document.js
+++ b/server/models/Document.js
@@ -1,17 +1,17 @@
// @flow
-import { map, find, compact, uniq } from 'lodash';
-import MarkdownSerializer from 'slate-md-serializer';
-import randomstring from 'randomstring';
-import Sequelize, { type Transaction } from 'sequelize';
-import removeMarkdown from '@tommoor/remove-markdown';
+import { map, find, compact, uniq } from "lodash";
+import MarkdownSerializer from "slate-md-serializer";
+import randomstring from "randomstring";
+import Sequelize, { type Transaction } from "sequelize";
+import removeMarkdown from "@tommoor/remove-markdown";
-import isUUID from 'validator/lib/isUUID';
-import { Collection, User } from '../models';
-import { DataTypes, sequelize } from '../sequelize';
-import parseTitle from '../../shared/utils/parseTitle';
-import unescape from '../../shared/utils/unescape';
-import slugify from '../utils/slugify';
-import Revision from './Revision';
+import isUUID from "validator/lib/isUUID";
+import { Collection, User } from "../models";
+import { DataTypes, sequelize } from "../sequelize";
+import parseTitle from "../../shared/utils/parseTitle";
+import unescape from "../../shared/utils/unescape";
+import slugify from "../utils/slugify";
+import Revision from "./Revision";
const Op = Sequelize.Op;
const URL_REGEX = /^[0-9a-zA-Z-_~]*-([a-zA-Z0-9]{10,15})$/;
@@ -25,8 +25,8 @@ const createRevision = (doc, options = {}) => {
// we don't create revisions if identical to previous
if (
- doc.text === doc.previous('text') &&
- doc.title === doc.previous('title')
+ doc.text === doc.previous("text") &&
+ doc.title === doc.previous("title")
) {
return;
}
@@ -64,7 +64,7 @@ const beforeSave = async doc => {
doc.emoji = emoji;
// ensure documents have a title
- doc.title = doc.title || '';
+ doc.title = doc.title || "";
// add the current user as a collaborator on this doc
if (!doc.collaboratorIds) doc.collaboratorIds = [];
@@ -77,7 +77,7 @@ const beforeSave = async doc => {
};
const Document = sequelize.define(
- 'document',
+ "document",
{
id: {
type: DataTypes.UUID,
@@ -93,7 +93,7 @@ const Document = sequelize.define(
validate: {
len: {
args: [0, 100],
- msg: 'Document title must be less than 100 characters',
+ msg: "Document title must be less than 100 characters",
},
},
},
@@ -134,45 +134,45 @@ const Document = sequelize.define(
Document.associate = models => {
Document.belongsTo(models.Collection, {
- as: 'collection',
- foreignKey: 'collectionId',
- onDelete: 'cascade',
+ as: "collection",
+ foreignKey: "collectionId",
+ onDelete: "cascade",
});
Document.belongsTo(models.Team, {
- as: 'team',
- foreignKey: 'teamId',
+ as: "team",
+ foreignKey: "teamId",
});
Document.belongsTo(models.User, {
- as: 'createdBy',
- foreignKey: 'createdById',
+ as: "createdBy",
+ foreignKey: "createdById",
});
Document.belongsTo(models.User, {
- as: 'updatedBy',
- foreignKey: 'lastModifiedById',
+ as: "updatedBy",
+ foreignKey: "lastModifiedById",
});
Document.belongsTo(models.User, {
- as: 'pinnedBy',
- foreignKey: 'pinnedById',
+ as: "pinnedBy",
+ foreignKey: "pinnedById",
});
Document.hasMany(models.Revision, {
- as: 'revisions',
- onDelete: 'cascade',
+ as: "revisions",
+ onDelete: "cascade",
});
Document.hasMany(models.Backlink, {
- as: 'backlinks',
- onDelete: 'cascade',
+ as: "backlinks",
+ onDelete: "cascade",
});
Document.hasMany(models.Star, {
- as: 'starred',
- onDelete: 'cascade',
+ as: "starred",
+ onDelete: "cascade",
});
Document.hasMany(models.View, {
- as: 'views',
+ as: "views",
});
- Document.addScope('defaultScope', {
+ Document.addScope("defaultScope", {
include: [
- { model: models.User, as: 'createdBy', paranoid: false },
- { model: models.User, as: 'updatedBy', paranoid: false },
+ { model: models.User, as: "createdBy", paranoid: false },
+ { model: models.User, as: "updatedBy", paranoid: false },
],
where: {
publishedAt: {
@@ -180,38 +180,38 @@ Document.associate = models => {
},
},
});
- Document.addScope('withCollection', userId => {
+ Document.addScope("withCollection", userId => {
if (userId) {
return {
include: [
{
model: models.Collection.scope({
- method: ['withMembership', userId],
+ method: ["withMembership", userId],
}),
- as: 'collection',
+ as: "collection",
},
],
};
}
return {
- include: [{ model: models.Collection, as: 'collection' }],
+ include: [{ model: models.Collection, as: "collection" }],
};
});
- Document.addScope('withUnpublished', {
+ Document.addScope("withUnpublished", {
include: [
- { model: models.User, as: 'createdBy', paranoid: false },
- { model: models.User, as: 'updatedBy', paranoid: false },
+ { model: models.User, as: "createdBy", paranoid: false },
+ { model: models.User, as: "updatedBy", paranoid: false },
],
});
- Document.addScope('withViews', userId => ({
+ Document.addScope("withViews", userId => ({
include: [
- { model: models.View, as: 'views', where: { userId }, required: false },
+ { model: models.View, as: "views", where: { userId }, required: false },
],
}));
- Document.addScope('withStarred', userId => ({
+ Document.addScope("withStarred", userId => ({
include: [
- { model: models.Star, as: 'starred', where: { userId }, required: false },
+ { model: models.Star, as: "starred", where: { userId }, required: false },
],
}));
};
@@ -219,8 +219,8 @@ Document.associate = models => {
Document.findByPk = async function(id, options = {}) {
// allow default preloading of collection membership if `userId` is passed in find options
// almost every endpoint needs the collection membership to determine policy permissions.
- const scope = this.scope('withUnpublished', {
- method: ['withCollection', options.userId],
+ const scope = this.scope("withUnpublished", {
+ method: ["withCollection", options.userId],
});
if (isUUID(id)) {
@@ -248,7 +248,7 @@ type SearchOptions = {
limit?: number,
offset?: number,
collectionId?: string,
- dateFilter?: 'day' | 'week' | 'month' | 'year',
+ dateFilter?: "day" | "week" | "month" | "year",
collaboratorIds?: string[],
includeArchived?: boolean,
includeDrafts?: boolean,
@@ -257,7 +257,7 @@ type SearchOptions = {
function escape(query: string): string {
// replace "\" with escaped "\\" because sequelize.escape doesn't do it
// https://github.com/sequelize/sequelize/issues/2950
- return sequelize.escape(query).replace('\\', '\\\\');
+ return sequelize.escape(query).replace("\\", "\\\\");
}
Document.searchForTeam = async (
@@ -308,12 +308,12 @@ Document.searchForTeam = async (
// Final query to get associated document data
const documents = await Document.findAll({
where: {
- id: map(results, 'id'),
+ id: map(results, "id"),
},
include: [
- { model: Collection, as: 'collection' },
- { model: User, as: 'createdBy', paranoid: false },
- { model: User, as: 'updatedBy', paranoid: false },
+ { model: Collection, as: "collection" },
+ { model: User, as: "createdBy", paranoid: false },
+ { model: User, as: "updatedBy", paranoid: false },
],
});
@@ -366,14 +366,14 @@ Document.searchForUser = async (
"teamId" = :teamId AND
"collectionId" IN(:collectionIds) AND
${
- options.dateFilter ? '"updatedAt" > now() - interval :dateFilter AND' : ''
+ options.dateFilter ? '"updatedAt" > now() - interval :dateFilter AND' : ""
}
${
options.collaboratorIds
? '"collaboratorIds" @> ARRAY[:collaboratorIds]::uuid[] AND'
- : ''
+ : ""
}
- ${options.includeArchived ? '' : '"archivedAt" IS NULL AND'}
+ ${options.includeArchived ? "" : '"archivedAt" IS NULL AND'}
"deletedAt" IS NULL AND
${
options.includeDrafts
@@ -404,18 +404,18 @@ Document.searchForUser = async (
// Final query to get associated document data
const documents = await Document.scope(
{
- method: ['withViews', user.id],
+ method: ["withViews", user.id],
},
{
- method: ['withCollection', user.id],
+ method: ["withCollection", user.id],
}
).findAll({
where: {
- id: map(results, 'id'),
+ id: map(results, "id"),
},
include: [
- { model: User, as: 'createdBy', paranoid: false },
- { model: User, as: 'updatedBy', paranoid: false },
+ { model: User, as: "createdBy", paranoid: false },
+ { model: User, as: "updatedBy", paranoid: false },
],
});
@@ -430,21 +430,21 @@ Document.searchForUser = async (
// Hooks
-Document.addHook('beforeSave', async model => {
+Document.addHook("beforeSave", async model => {
if (!model.publishedAt) return;
const collection = await Collection.findByPk(model.collectionId);
- if (!collection || collection.type !== 'atlas') return;
+ if (!collection || collection.type !== "atlas") return;
await collection.updateDocument(model);
model.collection = collection;
});
-Document.addHook('afterCreate', async model => {
+Document.addHook("afterCreate", async model => {
if (!model.publishedAt) return;
const collection = await Collection.findByPk(model.collectionId);
- if (!collection || collection.type !== 'atlas') return;
+ if (!collection || collection.type !== "atlas") return;
await collection.addDocumentToStructure(model);
model.collection = collection;
@@ -470,7 +470,7 @@ Document.prototype.migrateVersion = function() {
// migrate from document version 0 -> 1
if (!this.version) {
// removing the title from the document text attribute
- this.text = this.text.replace(/^#\s(.*)\n/, '');
+ this.text = this.text.replace(/^#\s(.*)\n/, "");
this.version = 1;
migrated = true;
}
@@ -535,7 +535,7 @@ Document.prototype.publish = async function(options) {
if (this.publishedAt) return this.save(options);
const collection = await Collection.findByPk(this.collectionId);
- if (collection.type !== 'atlas') return this.save(options);
+ if (collection.type !== "atlas") return this.save(options);
await collection.addDocumentToStructure(this);
@@ -618,14 +618,14 @@ Document.prototype.getSummary = function() {
const plain = removeMarkdown(unescape(this.text), {
stripHTML: false,
});
- const lines = compact(plain.split('\n'));
+ const lines = compact(plain.split("\n"));
const notEmpty = lines.length >= 1;
if (this.version) {
- return notEmpty ? lines[0] : '';
+ return notEmpty ? lines[0] : "";
}
- return notEmpty ? lines[1] : '';
+ return notEmpty ? lines[1] : "";
};
Document.prototype.toJSON = function() {
diff --git a/server/models/Document.test.js b/server/models/Document.test.js
index b87c1ff6..f70b28ad 100644
--- a/server/models/Document.test.js
+++ b/server/models/Document.test.js
@@ -1,13 +1,13 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import { flushdb } from '../test/support';
-import { Document } from '../models';
-import { buildDocument, buildCollection, buildTeam } from '../test/factories';
+import { flushdb } from "../test/support";
+import { Document } from "../models";
+import { buildDocument, buildCollection, buildTeam } from "../test/factories";
beforeEach(flushdb);
beforeEach(jest.resetAllMocks);
-describe('#getSummary', () => {
- test('should strip markdown', async () => {
+describe("#getSummary", () => {
+ test("should strip markdown", async () => {
const document = await buildDocument({
version: 1,
text: `*paragraph*
@@ -15,10 +15,10 @@ describe('#getSummary', () => {
paragraph 2`,
});
- expect(document.getSummary()).toBe('paragraph');
+ expect(document.getSummary()).toBe("paragraph");
});
- test('should strip title when no version', async () => {
+ test("should strip title when no version", async () => {
const document = await buildDocument({
version: null,
text: `# Heading
@@ -26,12 +26,12 @@ paragraph 2`,
*paragraph*`,
});
- expect(document.getSummary()).toBe('paragraph');
+ expect(document.getSummary()).toBe("paragraph");
});
});
-describe('#migrateVersion', () => {
- test('should maintain empty paragraph under headings', async () => {
+describe("#migrateVersion", () => {
+ test("should maintain empty paragraph under headings", async () => {
const document = await buildDocument({
version: 1,
text: `# Heading
@@ -44,7 +44,7 @@ paragraph`,
paragraph`);
});
- test('should add breaks under headings with extra paragraphs', async () => {
+ test("should add breaks under headings with extra paragraphs", async () => {
const document = await buildDocument({
version: 1,
text: `# Heading
@@ -60,7 +60,7 @@ paragraph`,
paragraph`);
});
- test('should add breaks between paragraphs', async () => {
+ test("should add breaks between paragraphs", async () => {
const document = await buildDocument({
version: 1,
text: `paragraph
@@ -74,7 +74,7 @@ paragraph`,
paragraph`);
});
- test('should add breaks for multiple empty paragraphs', async () => {
+ test("should add breaks for multiple empty paragraphs", async () => {
const document = await buildDocument({
version: 1,
text: `paragraph
@@ -90,7 +90,7 @@ paragraph`,
paragraph`);
});
- test('should add breaks with non-latin characters', async () => {
+ test("should add breaks with non-latin characters", async () => {
const document = await buildDocument({
version: 1,
text: `除。
@@ -104,7 +104,7 @@ paragraph`);
通`);
});
- test('should update task list formatting', async () => {
+ test("should update task list formatting", async () => {
const document = await buildDocument({
version: 1,
text: `[ ] list item
@@ -115,7 +115,7 @@ paragraph`);
`);
});
- test('should update task list with multiple items', async () => {
+ test("should update task list with multiple items", async () => {
const document = await buildDocument({
version: 1,
text: `[ ] list item
@@ -128,7 +128,7 @@ paragraph`);
`);
});
- test('should update checked task list formatting', async () => {
+ test("should update checked task list formatting", async () => {
const document = await buildDocument({
version: 1,
text: `[x] list item
@@ -139,7 +139,7 @@ paragraph`);
`);
});
- test('should update nested task list formatting', async () => {
+ test("should update nested task list formatting", async () => {
const document = await buildDocument({
version: 1,
text: `[x] list item
@@ -155,22 +155,22 @@ paragraph`);
});
});
-describe('#searchForTeam', () => {
- test('should return search results from public collections', async () => {
+describe("#searchForTeam", () => {
+ test("should return search results from public collections", async () => {
const team = await buildTeam();
const collection = await buildCollection({ teamId: team.id });
const document = await buildDocument({
teamId: team.id,
collectionId: collection.id,
- title: 'test',
+ title: "test",
});
- const results = await Document.searchForTeam(team, 'test');
+ const results = await Document.searchForTeam(team, "test");
expect(results.length).toBe(1);
expect(results[0].document.id).toBe(document.id);
});
- test('should not return search results from private collections', async () => {
+ test("should not return search results from private collections", async () => {
const team = await buildTeam();
const collection = await buildCollection({
private: true,
@@ -179,16 +179,16 @@ describe('#searchForTeam', () => {
await buildDocument({
teamId: team.id,
collectionId: collection.id,
- title: 'test',
+ title: "test",
});
- const results = await Document.searchForTeam(team, 'test');
+ const results = await Document.searchForTeam(team, "test");
expect(results.length).toBe(0);
});
- test('should handle no collections', async () => {
+ test("should handle no collections", async () => {
const team = await buildTeam();
- const results = await Document.searchForTeam(team, 'test');
+ const results = await Document.searchForTeam(team, "test");
expect(results.length).toBe(0);
});
});
diff --git a/server/models/Event.js b/server/models/Event.js
index eaf43181..8d7df44f 100644
--- a/server/models/Event.js
+++ b/server/models/Event.js
@@ -1,8 +1,8 @@
// @flow
-import { DataTypes, sequelize } from '../sequelize';
-import events from '../events';
+import { DataTypes, sequelize } from "../sequelize";
+import events from "../events";
-const Event = sequelize.define('event', {
+const Event = sequelize.define("event", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@@ -16,31 +16,31 @@ const Event = sequelize.define('event', {
Event.associate = models => {
Event.belongsTo(models.User, {
- as: 'user',
- foreignKey: 'userId',
+ as: "user",
+ foreignKey: "userId",
});
Event.belongsTo(models.User, {
- as: 'actor',
- foreignKey: 'actorId',
+ as: "actor",
+ foreignKey: "actorId",
});
Event.belongsTo(models.Collection, {
- as: 'collection',
- foreignKey: 'collectionId',
+ as: "collection",
+ foreignKey: "collectionId",
});
Event.belongsTo(models.Collection, {
- as: 'document',
- foreignKey: 'documentId',
+ as: "document",
+ foreignKey: "documentId",
});
Event.belongsTo(models.Team, {
- as: 'team',
- foreignKey: 'teamId',
+ as: "team",
+ foreignKey: "teamId",
});
};
Event.beforeCreate(event => {
if (event.ip) {
// cleanup IPV6 representations of IPV4 addresses
- event.ip = event.ip.replace(/^::ffff:/, '');
+ event.ip = event.ip.replace(/^::ffff:/, "");
}
});
@@ -49,48 +49,48 @@ Event.afterCreate(event => {
});
Event.ACTIVITY_EVENTS = [
- 'users.create',
- 'documents.publish',
- 'documents.archive',
- 'documents.unarchive',
- 'documents.pin',
- 'documents.unpin',
- 'documents.delete',
- 'documents.restore',
- 'collections.create',
- 'collections.delete',
+ "users.create",
+ "documents.publish",
+ "documents.archive",
+ "documents.unarchive",
+ "documents.pin",
+ "documents.unpin",
+ "documents.delete",
+ "documents.restore",
+ "collections.create",
+ "collections.delete",
];
Event.AUDIT_EVENTS = [
- 'api_keys.create',
- 'api_keys.delete',
- 'users.create',
- 'users.promote',
- 'users.demote',
- 'users.invite',
- 'users.suspend',
- 'users.activate',
- 'users.delete',
- 'documents.publish',
- 'documents.update',
- 'documents.archive',
- 'documents.unarchive',
- 'documents.pin',
- 'documents.unpin',
- 'documents.move',
- 'documents.delete',
- 'shares.create',
- 'shares.revoke',
- 'groups.create',
- 'groups.update',
- 'groups.delete',
- 'collections.create',
- 'collections.update',
- 'collections.add_user',
- 'collections.remove_user',
- 'collections.add_group',
- 'collections.remove_group',
- 'collections.delete',
+ "api_keys.create",
+ "api_keys.delete",
+ "users.create",
+ "users.promote",
+ "users.demote",
+ "users.invite",
+ "users.suspend",
+ "users.activate",
+ "users.delete",
+ "documents.publish",
+ "documents.update",
+ "documents.archive",
+ "documents.unarchive",
+ "documents.pin",
+ "documents.unpin",
+ "documents.move",
+ "documents.delete",
+ "shares.create",
+ "shares.revoke",
+ "groups.create",
+ "groups.update",
+ "groups.delete",
+ "collections.create",
+ "collections.update",
+ "collections.add_user",
+ "collections.remove_user",
+ "collections.add_group",
+ "collections.remove_group",
+ "collections.delete",
];
export default Event;
diff --git a/server/models/Group.js b/server/models/Group.js
index 2dccd10c..2fea3813 100644
--- a/server/models/Group.js
+++ b/server/models/Group.js
@@ -1,9 +1,9 @@
// @flow
-import { Op, DataTypes, sequelize } from '../sequelize';
-import { CollectionGroup, GroupUser } from '../models';
+import { Op, DataTypes, sequelize } from "../sequelize";
+import { CollectionGroup, GroupUser } from "../models";
const Group = sequelize.define(
- 'group',
+ "group",
{
id: {
type: DataTypes.UUID,
@@ -32,7 +32,7 @@ const Group = sequelize.define(
},
});
if (foundItem) {
- throw new Error('The name of this group is already in use');
+ throw new Error("The name of this group is already in use");
}
},
},
@@ -41,39 +41,39 @@ const Group = sequelize.define(
Group.associate = models => {
Group.hasMany(models.GroupUser, {
- as: 'groupMemberships',
- foreignKey: 'groupId',
+ as: "groupMemberships",
+ foreignKey: "groupId",
});
Group.hasMany(models.CollectionGroup, {
- as: 'collectionGroupMemberships',
- foreignKey: 'groupId',
+ as: "collectionGroupMemberships",
+ foreignKey: "groupId",
});
Group.belongsTo(models.Team, {
- as: 'team',
- foreignKey: 'teamId',
+ as: "team",
+ foreignKey: "teamId",
});
Group.belongsTo(models.User, {
- as: 'createdBy',
- foreignKey: 'createdById',
+ as: "createdBy",
+ foreignKey: "createdById",
});
Group.belongsToMany(models.User, {
- as: 'users',
+ as: "users",
through: models.GroupUser,
- foreignKey: 'groupId',
+ foreignKey: "groupId",
});
- Group.addScope('defaultScope', {
+ Group.addScope("defaultScope", {
include: [
{
- association: 'groupMemberships',
+ association: "groupMemberships",
required: false,
},
],
- order: [['name', 'ASC']],
+ order: [["name", "ASC"]],
});
};
// Cascade deletes to group and collection relations
-Group.addHook('afterDestroy', async (group, options) => {
+Group.addHook("afterDestroy", async (group, options) => {
if (!group.deletedAt) return;
await GroupUser.destroy({ where: { groupId: group.id } });
diff --git a/server/models/Group.test.js b/server/models/Group.test.js
index 8f98d075..9909b898 100644
--- a/server/models/Group.test.js
+++ b/server/models/Group.test.js
@@ -1,13 +1,13 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import { flushdb } from '../test/support';
-import { CollectionGroup, GroupUser } from '../models';
-import { buildUser, buildGroup, buildCollection } from '../test/factories';
+import { flushdb } from "../test/support";
+import { CollectionGroup, GroupUser } from "../models";
+import { buildUser, buildGroup, buildCollection } from "../test/factories";
beforeEach(flushdb);
beforeEach(jest.resetAllMocks);
-describe('afterDestroy hook', () => {
- test('should destroy associated group and collection join relations', async () => {
+describe("afterDestroy hook", () => {
+ test("should destroy associated group and collection join relations", async () => {
const group = await buildGroup();
const teamId = group.teamId;
diff --git a/server/models/GroupUser.js b/server/models/GroupUser.js
index 68da219c..1c0a92f7 100644
--- a/server/models/GroupUser.js
+++ b/server/models/GroupUser.js
@@ -1,8 +1,8 @@
// @flow
-import { sequelize } from '../sequelize';
+import { sequelize } from "../sequelize";
const GroupUser = sequelize.define(
- 'group_user',
+ "group_user",
{},
{
timestamps: true,
@@ -12,21 +12,21 @@ const GroupUser = sequelize.define(
GroupUser.associate = models => {
GroupUser.belongsTo(models.Group, {
- as: 'group',
- foreignKey: 'groupId',
+ as: "group",
+ foreignKey: "groupId",
primary: true,
});
GroupUser.belongsTo(models.User, {
- as: 'user',
- foreignKey: 'userId',
+ as: "user",
+ foreignKey: "userId",
primary: true,
});
GroupUser.belongsTo(models.User, {
- as: 'createdBy',
- foreignKey: 'createdById',
+ as: "createdBy",
+ foreignKey: "createdById",
});
- GroupUser.addScope('defaultScope', {
- include: [{ association: 'user' }],
+ GroupUser.addScope("defaultScope", {
+ include: [{ association: "user" }],
});
};
diff --git a/server/models/Integration.js b/server/models/Integration.js
index 9251ba7c..b7dc24a9 100644
--- a/server/models/Integration.js
+++ b/server/models/Integration.js
@@ -1,7 +1,7 @@
// @flow
-import { DataTypes, sequelize } from '../sequelize';
+import { DataTypes, sequelize } from "../sequelize";
-const Integration = sequelize.define('integration', {
+const Integration = sequelize.define("integration", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@@ -15,20 +15,20 @@ const Integration = sequelize.define('integration', {
Integration.associate = models => {
Integration.belongsTo(models.User, {
- as: 'user',
- foreignKey: 'userId',
+ as: "user",
+ foreignKey: "userId",
});
Integration.belongsTo(models.Team, {
- as: 'team',
- foreignKey: 'teamId',
+ as: "team",
+ foreignKey: "teamId",
});
Integration.belongsTo(models.Collection, {
- as: 'collection',
- foreignKey: 'collectionId',
+ as: "collection",
+ foreignKey: "collectionId",
});
Integration.belongsTo(models.Authentication, {
- as: 'authentication',
- foreignKey: 'authenticationId',
+ as: "authentication",
+ foreignKey: "authenticationId",
});
};
diff --git a/server/models/Notification.js b/server/models/Notification.js
index 2302b219..0174e0f1 100644
--- a/server/models/Notification.js
+++ b/server/models/Notification.js
@@ -1,8 +1,8 @@
// @flow
-import { DataTypes, sequelize } from '../sequelize';
+import { DataTypes, sequelize } from "../sequelize";
const Notification = sequelize.define(
- 'notification',
+ "notification",
{
id: {
type: DataTypes.UUID,
@@ -24,12 +24,12 @@ const Notification = sequelize.define(
Notification.associate = models => {
Notification.belongsTo(models.User, {
- as: 'actor',
- foreignKey: 'actorId',
+ as: "actor",
+ foreignKey: "actorId",
});
Notification.belongsTo(models.User, {
- as: 'user',
- foreignKey: 'userId',
+ as: "user",
+ foreignKey: "userId",
});
};
diff --git a/server/models/NotificationSetting.js b/server/models/NotificationSetting.js
index 6e5be653..24cdd03c 100644
--- a/server/models/NotificationSetting.js
+++ b/server/models/NotificationSetting.js
@@ -1,9 +1,9 @@
// @flow
-import crypto from 'crypto';
-import { DataTypes, sequelize } from '../sequelize';
+import crypto from "crypto";
+import { DataTypes, sequelize } from "../sequelize";
const NotificationSetting = sequelize.define(
- 'notification_setting',
+ "notification_setting",
{
id: {
type: DataTypes.UUID,
@@ -15,11 +15,11 @@ const NotificationSetting = sequelize.define(
validate: {
isIn: [
[
- 'documents.publish',
- 'documents.update',
- 'collections.create',
- 'emails.onboarding',
- 'emails.features',
+ "documents.publish",
+ "documents.update",
+ "collections.create",
+ "emails.onboarding",
+ "emails.features",
],
],
},
@@ -43,20 +43,20 @@ const NotificationSetting = sequelize.define(
);
NotificationSetting.getUnsubscribeToken = userId => {
- const hash = crypto.createHash('sha256');
+ const hash = crypto.createHash("sha256");
hash.update(`${userId}-${process.env.SECRET_KEY}`);
- return hash.digest('hex');
+ return hash.digest("hex");
};
NotificationSetting.associate = models => {
NotificationSetting.belongsTo(models.User, {
- as: 'user',
- foreignKey: 'userId',
- onDelete: 'cascade',
+ as: "user",
+ foreignKey: "userId",
+ onDelete: "cascade",
});
NotificationSetting.belongsTo(models.Team, {
- as: 'team',
- foreignKey: 'teamId',
+ as: "team",
+ foreignKey: "teamId",
});
};
diff --git a/server/models/Revision.js b/server/models/Revision.js
index 75c45861..9377d447 100644
--- a/server/models/Revision.js
+++ b/server/models/Revision.js
@@ -1,10 +1,10 @@
// @flow
-import { DataTypes, sequelize } from '../sequelize';
-import MarkdownSerializer from 'slate-md-serializer';
+import { DataTypes, sequelize } from "../sequelize";
+import MarkdownSerializer from "slate-md-serializer";
const serializer = new MarkdownSerializer();
-const Revision = sequelize.define('revision', {
+const Revision = sequelize.define("revision", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@@ -23,18 +23,18 @@ const Revision = sequelize.define('revision', {
Revision.associate = models => {
Revision.belongsTo(models.Document, {
- as: 'document',
- foreignKey: 'documentId',
- onDelete: 'cascade',
+ as: "document",
+ foreignKey: "documentId",
+ onDelete: "cascade",
});
Revision.belongsTo(models.User, {
- as: 'user',
- foreignKey: 'userId',
+ as: "user",
+ foreignKey: "userId",
});
Revision.addScope(
- 'defaultScope',
+ "defaultScope",
{
- include: [{ model: models.User, as: 'user', paranoid: false }],
+ include: [{ model: models.User, as: "user", paranoid: false }],
},
{ override: true }
);
@@ -46,7 +46,7 @@ Revision.prototype.migrateVersion = function() {
// migrate from document version 0 -> 1
if (!this.version) {
// removing the title from the document text attribute
- this.text = this.text.replace(/^#\s(.*)\n/, '');
+ this.text = this.text.replace(/^#\s(.*)\n/, "");
this.version = 1;
migrated = true;
}
diff --git a/server/models/Share.js b/server/models/Share.js
index 6d77b631..f76a2a12 100644
--- a/server/models/Share.js
+++ b/server/models/Share.js
@@ -1,8 +1,8 @@
// @flow
-import { DataTypes, sequelize } from '../sequelize';
+import { DataTypes, sequelize } from "../sequelize";
const Share = sequelize.define(
- 'share',
+ "share",
{
id: {
type: DataTypes.UUID,
@@ -23,16 +23,16 @@ const Share = sequelize.define(
Share.associate = models => {
Share.belongsTo(models.User, {
- as: 'user',
- foreignKey: 'userId',
+ as: "user",
+ foreignKey: "userId",
});
Share.belongsTo(models.Team, {
- as: 'team',
- foreignKey: 'teamId',
+ as: "team",
+ foreignKey: "teamId",
});
Share.belongsTo(models.Document, {
- as: 'document',
- foreignKey: 'documentId',
+ as: "document",
+ foreignKey: "documentId",
});
};
diff --git a/server/models/Star.js b/server/models/Star.js
index 43d40452..74f3dc98 100644
--- a/server/models/Star.js
+++ b/server/models/Star.js
@@ -1,7 +1,7 @@
// @flow
-import { DataTypes, sequelize } from '../sequelize';
+import { DataTypes, sequelize } from "../sequelize";
-const Star = sequelize.define('star', {
+const Star = sequelize.define("star", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
diff --git a/server/models/Team.js b/server/models/Team.js
index bdbed537..fa9d63cd 100644
--- a/server/models/Team.js
+++ b/server/models/Team.js
@@ -1,25 +1,25 @@
// @flow
-import uuid from 'uuid';
-import { URL } from 'url';
-import fs from 'fs';
-import util from 'util';
-import path from 'path';
-import { DataTypes, sequelize, Op } from '../sequelize';
-import { publicS3Endpoint, uploadToS3FromUrl } from '../utils/s3';
+import uuid from "uuid";
+import { URL } from "url";
+import fs from "fs";
+import util from "util";
+import path from "path";
+import { DataTypes, sequelize, Op } from "../sequelize";
+import { publicS3Endpoint, uploadToS3FromUrl } from "../utils/s3";
import {
stripSubdomain,
RESERVED_SUBDOMAINS,
-} from '../../shared/utils/domains';
-import { ValidationError } from '../errors';
+} from "../../shared/utils/domains";
+import { ValidationError } from "../errors";
-import Collection from './Collection';
-import Document from './Document';
-import User from './User';
+import Collection from "./Collection";
+import Document from "./Document";
+import User from "./User";
const readFile = util.promisify(fs.readFile);
const Team = sequelize.define(
- 'team',
+ "team",
{
id: {
type: DataTypes.UUID,
@@ -33,16 +33,16 @@ const Team = sequelize.define(
validate: {
isLowercase: true,
is: {
- args: [/^[a-z\d-]+$/, 'i'],
- msg: 'Must be only alphanumeric and dashes',
+ args: [/^[a-z\d-]+$/, "i"],
+ msg: "Must be only alphanumeric and dashes",
},
len: {
args: [4, 32],
- msg: 'Must be between 4 and 32 characters',
+ msg: "Must be between 4 and 32 characters",
},
notIn: {
args: [RESERVED_SUBDOMAINS],
- msg: 'You chose a restricted word, please try another.',
+ msg: "You chose a restricted word, please try another.",
},
},
unique: true,
@@ -66,13 +66,13 @@ const Team = sequelize.define(
{
getterMethods: {
url() {
- if (!this.subdomain || process.env.SUBDOMAINS_ENABLED !== 'true') {
+ if (!this.subdomain || process.env.SUBDOMAINS_ENABLED !== "true") {
return process.env.URL;
}
const url = new URL(process.env.URL);
url.host = `${this.subdomain}.${stripSubdomain(url.host)}`;
- return url.href.replace(/\/$/, '');
+ return url.href.replace(/\/$/, "");
},
logoUrl() {
return (
@@ -84,9 +84,9 @@ const Team = sequelize.define(
);
Team.associate = models => {
- Team.hasMany(models.Collection, { as: 'collections' });
- Team.hasMany(models.Document, { as: 'documents' });
- Team.hasMany(models.User, { as: 'users' });
+ Team.hasMany(models.Collection, { as: "collections" });
+ Team.hasMany(models.Document, { as: "documents" });
+ Team.hasMany(models.User, { as: "users" });
};
const uploadAvatar = async model => {
@@ -95,14 +95,14 @@ const uploadAvatar = async model => {
if (
avatarUrl &&
- !avatarUrl.startsWith('/api') &&
+ !avatarUrl.startsWith("/api") &&
!avatarUrl.startsWith(endpoint)
) {
try {
const newUrl = await uploadToS3FromUrl(
avatarUrl,
`avatars/${model.id}/${uuid.v4()}`,
- 'public-read'
+ "public-read"
);
if (newUrl) model.avatarUrl = newUrl;
} catch (err) {
@@ -131,10 +131,10 @@ Team.prototype.provisionSubdomain = async function(subdomain) {
Team.prototype.provisionFirstCollection = async function(userId) {
const collection = await Collection.create({
- name: 'Welcome',
+ name: "Welcome",
description:
- 'This collection is a quick guide to what Outline is all about. Feel free to delete this collection once your team is up to speed with the basics!',
- type: 'atlas',
+ "This collection is a quick guide to what Outline is all about. Feel free to delete this collection once your team is up to speed with the basics!",
+ type: "atlas",
teamId: this.id,
creatorId: userId,
});
@@ -142,15 +142,15 @@ Team.prototype.provisionFirstCollection = async function(userId) {
// For the first collection we go ahead and create some intitial documents to get
// the team started. You can edit these in /server/onboarding/x.md
const onboardingDocs = [
- '❤️ Support',
- '🚀 Integrations & API',
- '📝 Our Editor',
- '👋 What is Outline',
+ "❤️ Support",
+ "🚀 Integrations & API",
+ "📝 Our Editor",
+ "👋 What is Outline",
];
for (const title of onboardingDocs) {
const text = await readFile(
- path.join(__dirname, '..', 'onboarding', `${title}.md`),
- 'utf8'
+ path.join(__dirname, "..", "onboarding", `${title}.md`),
+ "utf8"
);
const document = await Document.create({
version: 1,
@@ -186,13 +186,13 @@ Team.prototype.removeAdmin = async function(user: User) {
if (res.count >= 1) {
return user.update({ isAdmin: false });
} else {
- throw new ValidationError('At least one admin is required');
+ throw new ValidationError("At least one admin is required");
}
};
Team.prototype.suspendUser = async function(user: User, admin: User) {
if (user.id === admin.id)
- throw new ValidationError('Unable to suspend the current user');
+ throw new ValidationError("Unable to suspend the current user");
return user.update({
suspendedById: admin.id,
suspendedAt: new Date(),
@@ -208,7 +208,7 @@ Team.prototype.activateUser = async function(user: User, admin: User) {
Team.prototype.collectionIds = async function(paranoid: boolean = true) {
let models = await Collection.findAll({
- attributes: ['id', 'private'],
+ attributes: ["id", "private"],
where: { teamId: this.id, private: false },
paranoid,
});
diff --git a/server/models/Team.test.js b/server/models/Team.test.js
index 4563bc32..2373309d 100644
--- a/server/models/Team.test.js
+++ b/server/models/Team.test.js
@@ -1,28 +1,28 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import { flushdb } from '../test/support';
-import { buildTeam } from '../test/factories';
+import { flushdb } from "../test/support";
+import { buildTeam } from "../test/factories";
beforeEach(flushdb);
-it('should set subdomain if available', async () => {
+it("should set subdomain if available", async () => {
const team = await buildTeam();
- const subdomain = await team.provisionSubdomain('testy');
- expect(subdomain).toEqual('testy');
- expect(team.subdomain).toEqual('testy');
+ const subdomain = await team.provisionSubdomain("testy");
+ expect(subdomain).toEqual("testy");
+ expect(team.subdomain).toEqual("testy");
});
-it('should set subdomain with append if unavailable', async () => {
- await buildTeam({ subdomain: 'myteam' });
+it("should set subdomain with append if unavailable", async () => {
+ await buildTeam({ subdomain: "myteam" });
const team = await buildTeam();
- const subdomain = await team.provisionSubdomain('myteam');
- expect(subdomain).toEqual('myteam1');
- expect(team.subdomain).toEqual('myteam1');
+ const subdomain = await team.provisionSubdomain("myteam");
+ expect(subdomain).toEqual("myteam1");
+ expect(team.subdomain).toEqual("myteam1");
});
-it('should do nothing if subdomain already set', async () => {
- const team = await buildTeam({ subdomain: 'example' });
- const subdomain = await team.provisionSubdomain('myteam');
- expect(subdomain).toEqual('example');
- expect(team.subdomain).toEqual('example');
+it("should do nothing if subdomain already set", async () => {
+ const team = await buildTeam({ subdomain: "example" });
+ const subdomain = await team.provisionSubdomain("myteam");
+ expect(subdomain).toEqual("example");
+ expect(team.subdomain).toEqual("example");
});
diff --git a/server/models/User.js b/server/models/User.js
index d2afaf48..4a1a49c0 100644
--- a/server/models/User.js
+++ b/server/models/User.js
@@ -1,18 +1,18 @@
// @flow
-import crypto from 'crypto';
-import uuid from 'uuid';
-import JWT from 'jsonwebtoken';
-import subMinutes from 'date-fns/sub_minutes';
-import { ValidationError } from '../errors';
-import { DataTypes, sequelize, encryptedFields } from '../sequelize';
-import { publicS3Endpoint, uploadToS3FromUrl } from '../utils/s3';
-import { sendEmail } from '../mailer';
-import { Star, Team, Collection, NotificationSetting, ApiKey } from '.';
+import crypto from "crypto";
+import uuid from "uuid";
+import JWT from "jsonwebtoken";
+import subMinutes from "date-fns/sub_minutes";
+import { ValidationError } from "../errors";
+import { DataTypes, sequelize, encryptedFields } from "../sequelize";
+import { publicS3Endpoint, uploadToS3FromUrl } from "../utils/s3";
+import { sendEmail } from "../mailer";
+import { Star, Team, Collection, NotificationSetting, ApiKey } from ".";
-const DEFAULT_AVATAR_HOST = 'https://tiley.herokuapp.com';
+const DEFAULT_AVATAR_HOST = "https://tiley.herokuapp.com";
const User = sequelize.define(
- 'user',
+ "user",
{
id: {
type: DataTypes.UUID,
@@ -27,7 +27,7 @@ const User = sequelize.define(
service: { type: DataTypes.STRING, allowNull: true },
serviceId: { type: DataTypes.STRING, allowNull: true, unique: true },
slackData: DataTypes.JSONB,
- jwtSecret: encryptedFields.vault('jwtSecret'),
+ jwtSecret: encryptedFields.vault("jwtSecret"),
lastActiveAt: DataTypes.DATE,
lastActiveIp: { type: DataTypes.STRING, allowNull: true },
lastSignedInAt: DataTypes.DATE,
@@ -43,15 +43,15 @@ const User = sequelize.define(
return !!this.suspendedAt;
},
avatarUrl() {
- const original = this.getDataValue('avatarUrl');
+ const original = this.getDataValue("avatarUrl");
if (original) {
return original;
}
const hash = crypto
- .createHash('md5')
- .update(this.email || '')
- .digest('hex');
+ .createHash("md5")
+ .update(this.email || "")
+ .digest("hex");
return `${DEFAULT_AVATAR_HOST}/avatar/${hash}/${this.name[0]}.png`;
},
},
@@ -60,22 +60,22 @@ const User = sequelize.define(
// Class methods
User.associate = models => {
- User.hasMany(models.ApiKey, { as: 'apiKeys', onDelete: 'cascade' });
+ User.hasMany(models.ApiKey, { as: "apiKeys", onDelete: "cascade" });
User.hasMany(models.NotificationSetting, {
- as: 'notificationSettings',
- onDelete: 'cascade',
+ as: "notificationSettings",
+ onDelete: "cascade",
});
- User.hasMany(models.Document, { as: 'documents' });
- User.hasMany(models.View, { as: 'views' });
+ User.hasMany(models.Document, { as: "documents" });
+ User.hasMany(models.View, { as: "views" });
User.belongsTo(models.Team);
};
// Instance methods
User.prototype.collectionIds = async function(paranoid: boolean = true) {
const collectionStubs = await Collection.scope({
- method: ['withMembership', this.id],
+ method: ["withMembership", this.id],
}).findAll({
- attributes: ['id', 'private'],
+ attributes: ["id", "private"],
where: { teamId: this.teamId },
paranoid,
});
@@ -113,8 +113,8 @@ User.prototype.getJwtToken = function() {
};
User.prototype.getEmailSigninToken = function() {
- if (this.service && this.service !== 'email') {
- throw new Error('Cannot generate email signin token for OAuth user');
+ if (this.service && this.service !== "email") {
+ throw new Error("Cannot generate email signin token for OAuth user");
}
return JWT.sign(
@@ -129,7 +129,7 @@ const uploadAvatar = async model => {
if (
avatarUrl &&
- !avatarUrl.startsWith('/api') &&
+ !avatarUrl.startsWith("/api") &&
!avatarUrl.startsWith(endpoint) &&
!avatarUrl.startsWith(DEFAULT_AVATAR_HOST)
) {
@@ -137,7 +137,7 @@ const uploadAvatar = async model => {
const newUrl = await uploadToS3FromUrl(
avatarUrl,
`avatars/${model.id}/${uuid.v4()}`,
- 'public-read'
+ "public-read"
);
if (newUrl) model.avatarUrl = newUrl;
} catch (err) {
@@ -148,7 +148,7 @@ const uploadAvatar = async model => {
};
const setRandomJwtSecret = model => {
- model.jwtSecret = crypto.randomBytes(64).toString('hex');
+ model.jwtSecret = crypto.randomBytes(64).toString("hex");
};
const removeIdentifyingInfo = async (model, options) => {
@@ -166,8 +166,8 @@ const removeIdentifyingInfo = async (model, options) => {
});
model.email = null;
- model.name = 'Unknown';
- model.avatarUrl = '';
+ model.name = "Unknown";
+ model.avatarUrl = "";
model.serviceId = null;
model.username = null;
model.slackData = null;
@@ -188,7 +188,7 @@ const checkLastAdmin = async model => {
if (userCount > 1 && adminCount <= 1) {
throw new ValidationError(
- 'Cannot delete account as only admin. Please transfer admin permissions to another user and try again.'
+ "Cannot delete account as only admin. Please transfer admin permissions to another user and try again."
);
}
}
@@ -204,8 +204,8 @@ User.afterCreate(async user => {
// From Slack support:
// If you wish to contact users at an email address obtained through Slack,
// you need them to opt-in through a clear and separate process.
- if (user.service && user.service !== 'slack') {
- sendEmail('welcome', user.email, { teamUrl: team.url });
+ if (user.service && user.service !== "slack") {
+ sendEmail("welcome", user.email, { teamUrl: team.url });
}
});
@@ -217,7 +217,7 @@ User.afterCreate(async (user, options) => {
where: {
userId: user.id,
teamId: user.teamId,
- event: 'documents.update',
+ event: "documents.update",
},
transaction: options.transaction,
}),
@@ -225,7 +225,7 @@ User.afterCreate(async (user, options) => {
where: {
userId: user.id,
teamId: user.teamId,
- event: 'emails.onboarding',
+ event: "emails.onboarding",
},
transaction: options.transaction,
}),
diff --git a/server/models/User.test.js b/server/models/User.test.js
index cce0a743..cb49fbe9 100644
--- a/server/models/User.test.js
+++ b/server/models/User.test.js
@@ -1,10 +1,10 @@
/* eslint-disable flowtype/require-valid-file-annotation */
-import { flushdb } from '../test/support';
-import { buildUser } from '../test/factories';
+import { flushdb } from "../test/support";
+import { buildUser } from "../test/factories";
beforeEach(flushdb);
-it('should set JWT secret', async () => {
+it("should set JWT secret", async () => {
const user = await buildUser();
expect(user.getJwtToken()).toBeTruthy();
});
diff --git a/server/models/View.js b/server/models/View.js
index b24cfc08..4de79ad4 100644
--- a/server/models/View.js
+++ b/server/models/View.js
@@ -1,11 +1,11 @@
// @flow
-import subMilliseconds from 'date-fns/sub_milliseconds';
-import { Op, DataTypes, sequelize } from '../sequelize';
-import { User } from '../models';
-import { USER_PRESENCE_INTERVAL } from '../../shared/constants';
+import subMilliseconds from "date-fns/sub_milliseconds";
+import { Op, DataTypes, sequelize } from "../sequelize";
+import { User } from "../models";
+import { USER_PRESENCE_INTERVAL } from "../../shared/constants";
const View = sequelize.define(
- 'view',
+ "view",
{
id: {
type: DataTypes.UUID,
@@ -42,7 +42,7 @@ View.increment = async where => {
View.findByDocument = async documentId => {
return View.findAll({
where: { documentId },
- order: [['updatedAt', 'DESC']],
+ order: [["updatedAt", "DESC"]],
include: [
{
model: User,
@@ -60,7 +60,7 @@ View.findRecentlyEditingByDocument = async documentId => {
[Op.gt]: subMilliseconds(new Date(), USER_PRESENCE_INTERVAL * 2),
},
},
- order: [['lastEditingAt', 'DESC']],
+ order: [["lastEditingAt", "DESC"]],
});
};
diff --git a/server/models/index.js b/server/models/index.js
index cf5c27d1..73c219aa 100644
--- a/server/models/index.js
+++ b/server/models/index.js
@@ -1,24 +1,24 @@
// @flow
-import ApiKey from './ApiKey';
-import Attachment from './Attachment';
-import Authentication from './Authentication';
-import Backlink from './Backlink';
-import Collection from './Collection';
-import CollectionUser from './CollectionUser';
-import CollectionGroup from './CollectionGroup';
-import Document from './Document';
-import Event from './Event';
-import Integration from './Integration';
-import Group from './Group';
-import GroupUser from './GroupUser';
-import Notification from './Notification';
-import NotificationSetting from './NotificationSetting';
-import Revision from './Revision';
-import Share from './Share';
-import Star from './Star';
-import Team from './Team';
-import User from './User';
-import View from './View';
+import ApiKey from "./ApiKey";
+import Attachment from "./Attachment";
+import Authentication from "./Authentication";
+import Backlink from "./Backlink";
+import Collection from "./Collection";
+import CollectionUser from "./CollectionUser";
+import CollectionGroup from "./CollectionGroup";
+import Document from "./Document";
+import Event from "./Event";
+import Integration from "./Integration";
+import Group from "./Group";
+import GroupUser from "./GroupUser";
+import Notification from "./Notification";
+import NotificationSetting from "./NotificationSetting";
+import Revision from "./Revision";
+import Share from "./Share";
+import Star from "./Star";
+import Team from "./Team";
+import User from "./User";
+import View from "./View";
const models = {
ApiKey,
@@ -45,7 +45,7 @@ const models = {
// based on https://github.com/sequelize/express-example/blob/master/models/index.js
Object.keys(models).forEach(modelName => {
- if ('associate' in models[modelName]) {
+ if ("associate" in models[modelName]) {
models[modelName].associate(models);
}
});
diff --git a/server/pages/Home.js b/server/pages/Home.js
index 2f5207b4..789963f2 100644
--- a/server/pages/Home.js
+++ b/server/pages/Home.js
@@ -1,17 +1,17 @@
// @flow
-import * as React from 'react';
-import { Helmet } from 'react-helmet';
-import styled from 'styled-components';
-import Grid from 'styled-components-grid';
-import AuthNotices from './components/AuthNotices';
-import Hero from './components/Hero';
-import HeroText from './components/HeroText';
-import SigninButtons from './components/SigninButtons';
-import Branding from '../../shared/components/Branding';
-import { githubUrl } from '../../shared/utils/routeHelpers';
+import * as React from "react";
+import { Helmet } from "react-helmet";
+import styled from "styled-components";
+import Grid from "styled-components-grid";
+import AuthNotices from "./components/AuthNotices";
+import Hero from "./components/Hero";
+import HeroText from "./components/HeroText";
+import SigninButtons from "./components/SigninButtons";
+import Branding from "../../shared/components/Branding";
+import { githubUrl } from "../../shared/utils/routeHelpers";
type Props = {
- notice?: 'google-hd' | 'auth-error' | 'hd-not-allowed',
+ notice?: "google-hd" | "auth-error" | "hd-not-allowed",
lastSignedIn: string,
googleSigninEnabled: boolean,
slackSigninEnabled: boolean,
diff --git a/server/pages/SubdomainSignin.js b/server/pages/SubdomainSignin.js
index be1c7416..90038606 100644
--- a/server/pages/SubdomainSignin.js
+++ b/server/pages/SubdomainSignin.js
@@ -1,20 +1,20 @@
// @flow
-import * as React from 'react';
-import styled from 'styled-components';
-import Grid from 'styled-components-grid';
-import Hero from './components/Hero';
-import HeroText from './components/HeroText';
-import Button from './components/Button';
-import SigninButtons from './components/SigninButtons';
-import AuthNotices from './components/AuthNotices';
-import Centered from './components/Centered';
-import PageTitle from './components/PageTitle';
-import { Team } from '../models';
+import * as React from "react";
+import styled from "styled-components";
+import Grid from "styled-components-grid";
+import Hero from "./components/Hero";
+import HeroText from "./components/HeroText";
+import Button from "./components/Button";
+import SigninButtons from "./components/SigninButtons";
+import AuthNotices from "./components/AuthNotices";
+import Centered from "./components/Centered";
+import PageTitle from "./components/PageTitle";
+import { Team } from "../models";
type Props = {
team: Team,
guest?: boolean,
- notice?: 'google-hd' | 'auth-error' | 'hd-not-allowed' | 'guest-success',
+ notice?: "google-hd" | "auth-error" | "hd-not-allowed" | "guest-success",
lastSignedIn: string,
googleSigninEnabled: boolean,
slackSigninEnabled: boolean,
@@ -37,7 +37,7 @@ function SubdomainSignin({
const guestSigninForm = (