Merge develop

This commit is contained in:
Tom Moor
2020-08-31 19:40:41 -07:00
20 changed files with 415 additions and 291 deletions

View File

@@ -45,6 +45,7 @@ AWS_REGION=xx-xxxx-x
AWS_S3_UPLOAD_BUCKET_URL=http://s3:4569 AWS_S3_UPLOAD_BUCKET_URL=http://s3:4569
AWS_S3_UPLOAD_BUCKET_NAME=bucket_name_here AWS_S3_UPLOAD_BUCKET_NAME=bucket_name_here
AWS_S3_UPLOAD_MAX_SIZE=26214400 AWS_S3_UPLOAD_MAX_SIZE=26214400
AWS_S3_FORCE_PATH_STYLE=true
# uploaded s3 objects permission level, default is private # uploaded s3 objects permission level, default is private
# set to "public-read" to allow public access # set to "public-read" to allow public access
AWS_S3_ACL=private AWS_S3_ACL=private

View File

@@ -92,6 +92,11 @@
"value": "26214400", "value": "26214400",
"required": false "required": false
}, },
"AWS_S3_FORCE_PATH_STYLE": {
"description": "Use path-style URL's for connecting to S3 instead of subdomain. This is useful for S3-compatible storage.",
"value": "true",
"required": false
},
"AWS_REGION": { "AWS_REGION": {
"value": "us-east-1", "value": "us-east-1",
"description": "Region in which the above S3 bucket exists", "description": "Region in which the above S3 bucket exists",

View File

@@ -1,20 +1,23 @@
// @flow // @flow
import ArrowKeyNavigation from "boundless-arrow-key-navigation"; import ArrowKeyNavigation from "boundless-arrow-key-navigation";
import { observable, action } from "mobx"; import { action, observable } from "mobx";
import { observer, inject } from "mobx-react"; import { inject, observer } from "mobx-react";
import { CloseIcon } from "outline-icons";
import * as React from "react"; import * as React from "react";
import { type RouterHistory, type Match } from "react-router-dom"; import { type Match, Redirect, type RouterHistory } from "react-router-dom";
import { Waypoint } from "react-waypoint"; import { Waypoint } from "react-waypoint";
import styled from "styled-components"; import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore"; import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore";
import DocumentsStore from "stores/DocumentsStore"; import DocumentsStore from "stores/DocumentsStore";
import RevisionsStore from "stores/RevisionsStore"; import RevisionsStore from "stores/RevisionsStore";
import Button from "components/Button";
import Flex from "components/Flex"; import Flex from "components/Flex";
import { ListPlaceholder } from "components/LoadingPlaceholder"; import { ListPlaceholder } from "components/LoadingPlaceholder";
import Revision from "./components/Revision"; import Revision from "./components/Revision";
import { documentHistoryUrl } from "utils/routeHelpers"; import { documentHistoryUrl, documentUrl } from "utils/routeHelpers";
type Props = { type Props = {
match: Match, match: Match,
@@ -29,6 +32,7 @@ class DocumentHistory extends React.Component<Props> {
@observable isFetching: boolean = false; @observable isFetching: boolean = false;
@observable offset: number = 0; @observable offset: number = 0;
@observable allowLoadMore: boolean = true; @observable allowLoadMore: boolean = true;
@observable redirectTo: ?string;
async componentDidMount() { async componentDidMount() {
await this.loadMoreResults(); await this.loadMoreResults();
@@ -86,15 +90,34 @@ class DocumentHistory extends React.Component<Props> {
return this.props.revisions.getDocumentRevisions(document.id); return this.props.revisions.getDocumentRevisions(document.id);
} }
onCloseHistory = () => {
const document = this.props.documents.getByUrl(
this.props.match.params.documentSlug
);
this.redirectTo = documentUrl(document);
};
render() { render() {
const document = this.props.documents.getByUrl( const document = this.props.documents.getByUrl(
this.props.match.params.documentSlug this.props.match.params.documentSlug
); );
const showLoading = (!this.isLoaded && this.isFetching) || !document; const showLoading = (!this.isLoaded && this.isFetching) || !document;
if (this.redirectTo) return <Redirect to={this.redirectTo} push />;
return ( return (
<Sidebar> <Sidebar>
<Wrapper column> <Wrapper column>
<Header>
<Title>History</Title>
<Button
icon={<CloseIcon />}
onClick={this.onCloseHistory}
borderOnHover
neutral
/>
</Header>
{showLoading ? ( {showLoading ? (
<Loading> <Loading>
<ListPlaceholder count={5} /> <ListPlaceholder count={5} />
@@ -140,10 +163,36 @@ const Wrapper = styled(Flex)`
`; `;
const Sidebar = styled(Flex)` const Sidebar = styled(Flex)`
display: none;
background: ${(props) => props.theme.background}; background: ${(props) => props.theme.background};
min-width: ${(props) => props.theme.sidebarWidth}; min-width: ${(props) => props.theme.sidebarWidth};
border-left: 1px solid ${(props) => props.theme.divider}; border-left: 1px solid ${(props) => props.theme.divider};
z-index: 1; z-index: 1;
${breakpoint("tablet")`
display: flex;
`};
`;
const Title = styled(Flex)`
font-size: 16px;
font-weight: 600;
text-align: center;
align-items: center;
justify-content: flex-start;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: 0;
flex-grow: 1;
`;
const Header = styled(Flex)`
align-items: center;
position: relative;
padding: 12px;
border-bottom: 1px solid ${(props) => props.theme.divider};
color: ${(props) => props.theme.text};
`; `;
export default inject("documents", "revisions")(DocumentHistory); export default inject("documents", "revisions")(DocumentHistory);

View File

@@ -9,6 +9,7 @@ import breakpoint from "styled-components-breakpoint";
import { fadeAndScaleIn } from "shared/styles/animations"; import { fadeAndScaleIn } from "shared/styles/animations";
import Flex from "components/Flex"; import Flex from "components/Flex";
import NudeButton from "components/NudeButton"; import NudeButton from "components/NudeButton";
import Scrollable from "components/Scrollable";
ReactModal.setAppElement("#root"); ReactModal.setAppElement("#root");
@@ -27,7 +28,8 @@ const GlobalStyles = createGlobalStyle`
} }
${breakpoint("tablet")` ${breakpoint("tablet")`
.ReactModalPortal + .ReactModalPortal { .ReactModalPortal + .ReactModalPortal,
.ReactModalPortal + [data-react-modal-body-trap] + .ReactModalPortal {
.ReactModal__Overlay { .ReactModal__Overlay {
margin-left: 12px; margin-left: 12px;
box-shadow: 0 -2px 10px ${(props) => props.theme.shadow}; box-shadow: 0 -2px 10px ${(props) => props.theme.shadow};
@@ -36,13 +38,15 @@ const GlobalStyles = createGlobalStyle`
} }
} }
.ReactModalPortal + .ReactModalPortal + .ReactModalPortal { .ReactModalPortal + .ReactModalPortal + .ReactModalPortal,
.ReactModalPortal + .ReactModalPortal + [data-react-modal-body-trap] + .ReactModalPortal {
.ReactModal__Overlay { .ReactModal__Overlay {
margin-left: 24px; margin-left: 24px;
} }
} }
.ReactModalPortal + .ReactModalPortal + .ReactModalPortal + .ReactModalPortal { .ReactModalPortal + .ReactModalPortal + .ReactModalPortal + .ReactModalPortal,
.ReactModalPortal + .ReactModalPortal + .ReactModalPortal + [data-react-modal-body-trap] + .ReactModalPortal {
.ReactModal__Overlay { .ReactModal__Overlay {
margin-left: 36px; margin-left: 36px;
} }
@@ -72,10 +76,11 @@ const Modal = ({
isOpen={isOpen} isOpen={isOpen}
{...rest} {...rest}
> >
<Content onClick={(ev) => ev.stopPropagation()} column> <Content>
{title && <h1>{title}</h1>} <Centered onClick={(ev) => ev.stopPropagation()} column>
{title && <h1>{title}</h1>}
{children} {children}
</Centered>
</Content> </Content>
<Back onClick={onRequestClose}> <Back onClick={onRequestClose}>
<BackIcon size={32} color="currentColor" /> <BackIcon size={32} color="currentColor" />
@@ -89,10 +94,20 @@ const Modal = ({
); );
}; };
const Content = styled(Flex)` const Content = styled(Scrollable)`
width: 100%;
padding: 8vh 2rem 2rem;
${breakpoint("tablet")`
padding-top: 13vh;
`};
`;
const Centered = styled(Flex)`
width: 640px; width: 640px;
max-width: 100%; max-width: 100%;
position: relative; position: relative;
margin: 0 auto;
`; `;
const StyledModal = styled(ReactModal)` const StyledModal = styled(ReactModal)`
@@ -107,16 +122,9 @@ const StyledModal = styled(ReactModal)`
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;
overflow-x: hidden;
overflow-y: auto;
background: ${(props) => props.theme.background}; background: ${(props) => props.theme.background};
transition: ${(props) => props.theme.backgroundTransition}; transition: ${(props) => props.theme.backgroundTransition};
padding: 8vh 2rem 2rem;
outline: none; outline: none;
${breakpoint("tablet")`
padding-top: 13vh;
`};
`; `;
const Text = styled.span` const Text = styled.span`
@@ -147,7 +155,7 @@ const Close = styled(NudeButton)`
`; `;
const Back = styled(NudeButton)` const Back = styled(NudeButton)`
position: fixed; position: absolute;
display: none; display: none;
align-items: center; align-items: center;
top: 2rem; top: 2rem;

View File

@@ -42,20 +42,23 @@ class PathToDocument extends React.Component<Props> {
return ( return (
<Component ref={ref} onClick={this.handleClick} href="" selectable> <Component ref={ref} onClick={this.handleClick} href="" selectable>
{collection && <CollectionIcon collection={collection} />} {collection && <CollectionIcon collection={collection} />}
&nbsp;
{result.path {result.path
.map((doc) => <Title key={doc.id}>{doc.title}</Title>) .map((doc) => <Title key={doc.id}>{doc.title}</Title>)
.reduce((prev, curr) => [prev, <StyledGoToIcon />, curr])} .reduce((prev, curr) => [prev, <StyledGoToIcon />, curr])}
{document && ( {document && (
<Flex> <DocumentTitle>
{" "} {" "}
<StyledGoToIcon /> <Title>{document.title}</Title> <StyledGoToIcon /> <Title>{document.title}</Title>
</Flex> </DocumentTitle>
)} )}
</Component> </Component>
); );
} }
} }
const DocumentTitle = styled(Flex)``;
const Title = styled.span` const Title = styled.span`
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -79,13 +82,20 @@ const ResultWrapper = styled.div`
const ResultWrapperLink = styled(ResultWrapper.withComponent("a"))` const ResultWrapperLink = styled(ResultWrapper.withComponent("a"))`
margin: 0 -8px; margin: 0 -8px;
padding: 8px 4px; padding: 8px 4px;
border-radius: 8px;
${DocumentTitle} {
display: none;
}
&:hover, &:hover,
&:active, &:active,
&:focus { &:focus {
background: ${(props) => props.theme.listItemHoverBackground}; background: ${(props) => props.theme.listItemHoverBackground};
outline: none; outline: none;
${DocumentTitle} {
display: flex;
}
} }
`; `;

View File

@@ -1,4 +1,5 @@
// @flow // @flow
import "mobx-react-lite/batchingForReactDom";
import { Provider } from "mobx-react"; import { Provider } from "mobx-react";
import * as React from "react"; import * as React from "react";
import { render } from "react-dom"; import { render } from "react-dom";

View File

@@ -127,6 +127,7 @@ class CollectionMenu extends React.Component<Props> {
collection={collection} collection={collection}
onSubmit={this.handleMembersModalClose} onSubmit={this.handleMembersModalClose}
handleEditCollectionOpen={this.handleEditCollectionOpen} handleEditCollectionOpen={this.handleEditCollectionOpen}
onEdit={this.handleEditCollectionOpen}
/> />
</Modal> </Modal>
<DropdownMenu onOpen={onOpen} onClose={onClose} position={position}> <DropdownMenu onOpen={onOpen} onClose={onClose} position={position}>

View File

@@ -132,7 +132,7 @@ class CollectionMembers extends React.Component<Props> {
collection. You can make this collection visible to the entire collection. You can make this collection visible to the entire
team by{" "} team by{" "}
<a role="button" onClick={this.props.onEdit}> <a role="button" onClick={this.props.onEdit}>
changing its visibility changing the visibility
</a> </a>
. .
</HelpText> </HelpText>

View File

@@ -18,6 +18,7 @@ import Branding from "components/Branding";
import ErrorBoundary from "components/ErrorBoundary"; import ErrorBoundary from "components/ErrorBoundary";
import Flex from "components/Flex"; import Flex from "components/Flex";
import LoadingIndicator from "components/LoadingIndicator"; import LoadingIndicator from "components/LoadingIndicator";
import LoadingPlaceholder from "components/LoadingPlaceholder";
import Notice from "components/Notice"; import Notice from "components/Notice";
import PageTitle from "components/PageTitle"; import PageTitle from "components/PageTitle";
import Time from "components/Time"; import Time from "components/Time";
@@ -67,7 +68,7 @@ type Props = {
@observer @observer
class DocumentScene extends React.Component<Props> { class DocumentScene extends React.Component<Props> {
@observable editor: ?any; @observable editor = React.createRef();
@observable isUploading: boolean = false; @observable isUploading: boolean = false;
@observable isSaving: boolean = false; @observable isSaving: boolean = false;
@observable isPublishing: boolean = false; @observable isPublishing: boolean = false;
@@ -380,7 +381,7 @@ class DocumentScene extends React.Component<Props> {
)} )}
<MaxWidth <MaxWidth
archived={document.isArchived} archived={document.isArchived}
tocVisible={ui.tocVisible} tocVisible={ui.tocVisible && readOnly}
column column
auto auto
> >
@@ -412,50 +413,52 @@ class DocumentScene extends React.Component<Props> {
)} )}
</Notice> </Notice>
)} )}
<Flex auto={!readOnly}> <React.Suspense fallback={<LoadingPlaceholder />}>
{ui.tocVisible && readOnly && ( <Flex auto={!readOnly}>
<Contents {ui.tocVisible && readOnly && (
headings={this.editor ? this.editor.getHeadings() : []} <Contents
headings={
this.editor.current
? this.editor.current.getHeadings()
: []
}
/>
)}
<Editor
id={document.id}
innerRef={this.editor}
isShare={isShare}
isDraft={document.isDraft}
template={document.isTemplate}
key={[injectTemplate, disableEmbeds].join("-")}
title={revision ? revision.title : this.title}
document={document}
value={readOnly ? value : undefined}
defaultValue={value}
disableEmbeds={disableEmbeds}
onImageUploadStart={this.onImageUploadStart}
onImageUploadStop={this.onImageUploadStop}
onSearchLink={this.props.onSearchLink}
onCreateLink={this.props.onCreateLink}
onChangeTitle={this.onChangeTitle}
onChange={this.onChange}
onSave={this.onSave}
onPublish={this.onPublish}
onCancel={this.goBack}
readOnly={readOnly}
readOnlyWriteCheckboxes={readOnly && abilities.update}
ui={this.props.ui}
/> />
</Flex>
{readOnly && !isShare && !revision && (
<>
<MarkAsViewed document={document} />
<ReferencesWrapper isOnlyTitle={document.isOnlyTitle}>
<References document={document} />
</ReferencesWrapper>
</>
)} )}
<Editor </React.Suspense>
id={document.id}
ref={(ref) => {
if (ref) {
this.editor = ref;
}
}}
isShare={isShare}
isDraft={document.isDraft}
template={document.isTemplate}
key={[injectTemplate, disableEmbeds].join("-")}
title={revision ? revision.title : this.title}
document={document}
value={readOnly ? value : undefined}
defaultValue={value}
disableEmbeds={disableEmbeds}
onImageUploadStart={this.onImageUploadStart}
onImageUploadStop={this.onImageUploadStop}
onSearchLink={this.props.onSearchLink}
onCreateLink={this.props.onCreateLink}
onChangeTitle={this.onChangeTitle}
onChange={this.onChange}
onSave={this.onSave}
onPublish={this.onPublish}
onCancel={this.goBack}
readOnly={readOnly}
readOnlyWriteCheckboxes={readOnly && abilities.update}
ui={this.props.ui}
/>
</Flex>
{readOnly && !isShare && !revision && (
<>
<MarkAsViewed document={document} />
<ReferencesWrapper isOnlyTitle={document.isOnlyTitle}>
<References document={document} />
</ReferencesWrapper>
</>
)}
</MaxWidth> </MaxWidth>
</Container> </Container>
</Background> </Background>

View File

@@ -13,13 +13,11 @@ import DocumentsStore from "stores/DocumentsStore";
import UiStore from "stores/UiStore"; import UiStore from "stores/UiStore";
import Document from "models/Document"; import Document from "models/Document";
import Flex from "components/Flex"; import Flex from "components/Flex";
import Input from "components/Input"; import { Outline } from "components/Input";
import Labeled from "components/Labeled"; import Labeled from "components/Labeled";
import Modal from "components/Modal"; import Modal from "components/Modal";
import PathToDocument from "components/PathToDocument"; import PathToDocument from "components/PathToDocument";
const MAX_RESULTS = 8;
type Props = {| type Props = {|
document: Document, document: Document,
documents: DocumentsStore, documents: DocumentsStore,
@@ -36,14 +34,19 @@ class DocumentMove extends React.Component<Props> {
@computed @computed
get searchIndex() { get searchIndex() {
const { collections } = this.props; const { collections, documents } = this.props;
const paths = collections.pathsToDocuments; const paths = collections.pathsToDocuments;
const index = new Search("id"); const index = new Search("id");
index.addIndex("title"); index.addIndex("title");
// Build index // Build index
const indexeableDocuments = []; const indexeableDocuments = [];
paths.forEach((path) => indexeableDocuments.push(path)); paths.forEach((path) => {
const doc = documents.get(path.id);
if (!doc || !doc.isTemplate) {
indexeableDocuments.push(path);
}
});
index.addDocuments(indexeableDocuments); index.addDocuments(indexeableDocuments);
return index; return index;
@@ -136,35 +139,41 @@ class DocumentMove extends React.Component<Props> {
</Section> </Section>
<Section column> <Section column>
<Labeled label="Choose a new location"> <Labeled label="Choose a new location" />
<Input <NewLocation>
type="search" <InputWrapper>
placeholder="Search collections & documents…" <Input
onKeyDown={this.handleKeyDown} type="search"
onChange={this.handleFilter} placeholder="Search collections & documents…"
required onKeyDown={this.handleKeyDown}
autoFocus onChange={this.handleFilter}
/> required
</Labeled> autoFocus
<Flex column> />
<StyledArrowKeyNavigation </InputWrapper>
mode={ArrowKeyNavigation.mode.VERTICAL}
defaultActiveChildIndex={0} <Results>
> <Flex column>
{this.results.slice(0, MAX_RESULTS).map((result, index) => ( <StyledArrowKeyNavigation
<PathToDocument mode={ArrowKeyNavigation.mode.VERTICAL}
key={result.id} defaultActiveChildIndex={0}
result={result} >
document={document} {this.results.map((result, index) => (
collection={collections.get(result.collectionId)} <PathToDocument
ref={(ref) => key={result.id}
index === 0 && this.setFirstDocumentRef(ref) result={result}
} document={document}
onSuccess={this.handleSuccess} collection={collections.get(result.collectionId)}
/> ref={(ref) =>
))} index === 0 && this.setFirstDocumentRef(ref)
</StyledArrowKeyNavigation> }
</Flex> onSuccess={this.handleSuccess}
/>
))}
</StyledArrowKeyNavigation>
</Flex>
</Results>
</NewLocation>
</Section> </Section>
</Flex> </Flex>
)} )}
@@ -173,6 +182,37 @@ class DocumentMove extends React.Component<Props> {
} }
} }
const InputWrapper = styled("div")`
padding: 8px;
width: 100%;
`;
const Input = styled("input")`
width: 100%;
outline: none;
background: none;
border-radius: 4px;
height: 30px;
border: 0;
color: ${(props) => props.theme.text};
&::placeholder {
color: ${(props) => props.theme.placeholder};
}
`;
const NewLocation = styled(Outline)`
flex-direction: column;
`;
const Results = styled(Flex)`
display: block;
width: 100%;
max-height: 40vh;
overflow-y: auto;
padding: 8px;
`;
const Section = styled(Flex)` const Section = styled(Flex)`
margin-bottom: 24px; margin-bottom: 24px;
`; `;

View File

@@ -11,7 +11,6 @@ import DocumentMetaWithViews from "components/DocumentMetaWithViews";
import Editor from "components/Editor"; import Editor from "components/Editor";
import Flex from "components/Flex"; import Flex from "components/Flex";
import HoverPreview from "components/HoverPreview"; import HoverPreview from "components/HoverPreview";
import LoadingPlaceholder from "components/LoadingPlaceholder";
import { documentHistoryUrl } from "utils/routeHelpers"; import { documentHistoryUrl } from "utils/routeHelpers";
type Props = { type Props = {
@@ -22,33 +21,25 @@ type Props = {
isDraft: boolean, isDraft: boolean,
isShare: boolean, isShare: boolean,
readOnly?: boolean, readOnly?: boolean,
innerRef: { current: any },
}; };
@observer @observer
class DocumentEditor extends React.Component<Props> { class DocumentEditor extends React.Component<Props> {
@observable activeLinkEvent: ?MouseEvent; @observable activeLinkEvent: ?MouseEvent;
editor = React.createRef<any>();
focusAtStart = () => { focusAtStart = () => {
if (this.editor.current) { if (this.props.innerRef.current) {
this.editor.current.focusAtStart(); this.props.innerRef.current.focusAtStart();
} }
}; };
focusAtEnd = () => { focusAtEnd = () => {
if (this.editor.current) { if (this.props.innerRef.current) {
this.editor.current.focusAtEnd(); this.props.innerRef.current.focusAtEnd();
} }
}; };
getHeadings = () => {
if (this.editor.current) {
return this.editor.current.getHeadings();
}
return [];
};
handleTitleKeyDown = (event: SyntheticKeyboardEvent<>) => { handleTitleKeyDown = (event: SyntheticKeyboardEvent<>) => {
if (event.key === "Enter" || event.key === "Tab") { if (event.key === "Enter" || event.key === "Tab") {
event.preventDefault(); event.preventDefault();
@@ -72,49 +63,46 @@ class DocumentEditor extends React.Component<Props> {
isDraft, isDraft,
isShare, isShare,
readOnly, readOnly,
innerRef,
} = this.props; } = this.props;
const { emoji } = parseTitle(title); const { emoji } = parseTitle(title);
const startsWithEmojiAndSpace = !!(emoji && title.startsWith(`${emoji} `)); const startsWithEmojiAndSpace = !!(emoji && title.startsWith(`${emoji} `));
return ( return (
<Flex auto column> <Flex auto column>
<React.Suspense fallback={<LoadingPlaceholder />}> <Title
<Title type="text"
type="text" onChange={onChangeTitle}
onChange={onChangeTitle} onKeyDown={this.handleTitleKeyDown}
onKeyDown={this.handleTitleKeyDown} placeholder={document.placeholder}
placeholder={document.placeholder} value={!title && readOnly ? document.titleWithDefault : title}
value={!title && readOnly ? document.titleWithDefault : title} style={startsWithEmojiAndSpace ? { marginLeft: "-1.2em" } : undefined}
style={ readOnly={readOnly}
startsWithEmojiAndSpace ? { marginLeft: "-1.2em" } : undefined autoFocus={!title}
} maxLength={100}
readOnly={readOnly} />
autoFocus={!title} <DocumentMetaWithViews
maxLength={100} isDraft={isDraft}
document={document}
to={documentHistoryUrl(document)}
/>
<Editor
ref={innerRef}
autoFocus={title && !this.props.defaultValue}
placeholder="…the rest is up to you"
onHoverLink={this.handleLinkActive}
scrollTo={window.location.hash}
grow
{...this.props}
/>
{!readOnly && <ClickablePadding onClick={this.focusAtEnd} grow />}
{this.activeLinkEvent && !isShare && readOnly && (
<HoverPreview
node={this.activeLinkEvent.target}
event={this.activeLinkEvent}
onClose={this.handleLinkInactive}
/> />
<DocumentMetaWithViews )}
isDraft={isDraft}
document={document}
to={documentHistoryUrl(document)}
/>
<Editor
ref={this.editor}
autoFocus={title && !this.props.defaultValue}
placeholder="…the rest is up to you"
onHoverLink={this.handleLinkActive}
scrollTo={window.location.hash}
grow
{...this.props}
/>
{!readOnly && <ClickablePadding onClick={this.focusAtEnd} grow />}
{this.activeLinkEvent && !isShare && readOnly && (
<HoverPreview
node={this.activeLinkEvent.target}
event={this.activeLinkEvent}
onClose={this.handleLinkInactive}
/>
)}
</React.Suspense>
</Flex> </Flex>
); );
} }

View File

@@ -9,7 +9,7 @@ import Document from "models/Document";
import Button from "components/Button"; import Button from "components/Button";
import Flex from "components/Flex"; import Flex from "components/Flex";
import HelpText from "components/HelpText"; import HelpText from "components/HelpText";
import { collectionUrl } from "utils/routeHelpers"; import { collectionUrl, documentUrl } from "utils/routeHelpers";
type Props = { type Props = {
history: RouterHistory, history: RouterHistory,
@@ -24,15 +24,27 @@ class DocumentDelete extends React.Component<Props> {
@observable isDeleting: boolean; @observable isDeleting: boolean;
handleSubmit = async (ev: SyntheticEvent<>) => { handleSubmit = async (ev: SyntheticEvent<>) => {
const { documents, document } = this.props;
ev.preventDefault(); ev.preventDefault();
this.isDeleting = true; this.isDeleting = true;
try { try {
await this.props.document.delete(); await document.delete();
if (this.props.ui.activeDocumentId === this.props.document.id) {
this.props.history.push( // only redirect if we're currently viewing the document that's deleted
collectionUrl(this.props.document.collectionId) if (this.props.ui.activeDocumentId === document.id) {
); // If the document has a parent and it's available in the store then
// redirect to it
if (document.parentDocumentId) {
const parent = documents.get(document.parentDocumentId);
if (parent) {
this.props.history.push(documentUrl(parent));
return;
}
}
// otherwise, redirect to the collection home
this.props.history.push(collectionUrl(document.collectionId));
} }
this.props.onSubmit(); this.props.onSubmit();
} catch (err) { } catch (err) {

View File

@@ -66,7 +66,7 @@
"@babel/preset-flow": "^7.10.4", "@babel/preset-flow": "^7.10.4",
"@babel/preset-react": "^7.10.4", "@babel/preset-react": "^7.10.4",
"@rehooks/window-scroll-position": "^1.0.1", "@rehooks/window-scroll-position": "^1.0.1",
"@sentry/node": "^5.12.2", "@sentry/node": "^5.22.3",
"@tippy.js/react": "^2.2.2", "@tippy.js/react": "^2.2.2",
"@tommoor/remove-markdown": "0.3.1", "@tommoor/remove-markdown": "0.3.1",
"autotrack": "^2.4.1", "autotrack": "^2.4.1",
@@ -138,7 +138,7 @@
"react-portal": "^4.0.0", "react-portal": "^4.0.0",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-waypoint": "^9.0.2", "react-waypoint": "^9.0.2",
"rich-markdown-editor": "^10.6.1", "rich-markdown-editor": "^10.6.5",
"semver": "^7.3.2", "semver": "^7.3.2",
"sequelize": "^6.3.4", "sequelize": "^6.3.4",
"sequelize-cli": "^6.2.0", "sequelize-cli": "^6.2.0",

View File

@@ -1001,7 +1001,7 @@ router.post("documents.delete", auth(), async (ctx) => {
const document = await Document.findByPk(id, { userId: user.id }); const document = await Document.findByPk(id, { userId: user.id });
authorize(user, "delete", document); authorize(user, "delete", document);
await document.delete(); await document.delete(user.id);
await Event.create({ await Event.create({
name: "documents.delete", name: "documents.delete",

View File

@@ -17,7 +17,6 @@ if (process.env.AWS_ACCESS_KEY_ID) {
"AWS_REGION", "AWS_REGION",
"AWS_SECRET_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY",
"AWS_S3_UPLOAD_BUCKET_URL", "AWS_S3_UPLOAD_BUCKET_URL",
"AWS_S3_UPLOAD_BUCKET_NAME",
"AWS_S3_UPLOAD_MAX_SIZE", "AWS_S3_UPLOAD_MAX_SIZE",
].forEach((key) => { ].forEach((key) => {
if (!process.env[key]) { if (!process.env[key]) {

View File

@@ -570,7 +570,7 @@ Document.prototype.archive = async function (userId) {
}; };
// Restore an archived document back to being visible to the team // Restore an archived document back to being visible to the team
Document.prototype.unarchive = async function (userId) { Document.prototype.unarchive = async function (userId: string) {
const collection = await this.getCollection(); const collection = await this.getCollection();
// check to see if the documents parent hasn't been archived also // check to see if the documents parent hasn't been archived also
@@ -602,23 +602,27 @@ Document.prototype.unarchive = async function (userId) {
}; };
// Delete a document, archived or otherwise. // Delete a document, archived or otherwise.
Document.prototype.delete = function (options) { Document.prototype.delete = function (userId: string) {
return sequelize.transaction(async (transaction: Transaction): Promise<*> => { return sequelize.transaction(
if (!this.archivedAt) { async (transaction: Transaction): Promise<Document> => {
// delete any children and remove from the document structure if (!this.archivedAt && !this.template) {
const collection = await this.getCollection(); // delete any children and remove from the document structure
if (collection) await collection.deleteDocument(this, { transaction }); const collection = await this.getCollection();
if (collection) await collection.deleteDocument(this, { transaction });
}
await Revision.destroy({
where: { documentId: this.id },
transaction,
});
this.lastModifiedById = userId;
this.deletedAt = new Date();
await this.save({ transaction });
return this;
} }
);
await Revision.destroy({
where: { documentId: this.id },
transaction,
});
await this.destroy({ transaction, ...options });
return this;
});
}; };
Document.prototype.getTimestamp = function () { Document.prototype.getTimestamp = function () {

View File

@@ -1,6 +1,11 @@
/* eslint-disable flowtype/require-valid-file-annotation */ /* eslint-disable flowtype/require-valid-file-annotation */
import { Document } from "../models"; import { Document } from "../models";
import { buildDocument, buildCollection, buildTeam } from "../test/factories"; import {
buildDocument,
buildCollection,
buildTeam,
buildUser,
} from "../test/factories";
import { flushdb } from "../test/support"; import { flushdb } from "../test/support";
beforeEach(() => flushdb()); beforeEach(() => flushdb());
@@ -192,3 +197,16 @@ describe("#searchForTeam", () => {
expect(results.length).toBe(0); expect(results.length).toBe(0);
}); });
}); });
describe("#delete", () => {
test("should soft delete and set last modified", async () => {
let document = await buildDocument();
let user = await buildUser();
await document.delete(user.id);
document = await Document.findByPk(document.id, { paranoid: false });
expect(document.lastModifiedById).toBe(user.id);
expect(document.deletedAt).toBeTruthy();
});
});

View File

@@ -31,8 +31,8 @@
<body> <body>
<div id="root"></div> <div id="root"></div>
<script>//inject-env//</script> <script>//inject-env//</script>
<script src="https://browser.sentry-cdn.com/5.12.1/bundle.min.js" <script src="https://browser.sentry-cdn.com/5.22.3/bundle.min.js"
integrity="sha384-y+an4eARFKvjzOivf/Z7JtMJhaN6b+lLQ5oFbBbUwZNNVir39cYtkjW1r6Xjbxg3" crossorigin="anonymous"> integrity="sha384-A1qzcXXJWl+bzYr+r8AdFzSaLbdcbYRFmG37MEDKr4EYjtraUyoZ6UiMw31jHcV9" crossorigin="anonymous">
</script> </script>
<script> <script>
if ('//inject-sentry-dsn//') { if ('//inject-sentry-dsn//') {
@@ -44,6 +44,7 @@
'NotFoundError', 'NotFoundError',
'OfflineError', 'OfflineError',
'UpdateRequiredError', 'UpdateRequiredError',
'ChunkLoadError'
], ],
}); });
} }

View File

@@ -4,18 +4,18 @@ import * as Sentry from "@sentry/node";
import AWS from "aws-sdk"; import AWS from "aws-sdk";
import addHours from "date-fns/add_hours"; import addHours from "date-fns/add_hours";
import format from "date-fns/format"; import format from "date-fns/format";
import invariant from "invariant";
import fetch from "isomorphic-fetch"; import fetch from "isomorphic-fetch";
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY; const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
const AWS_REGION = process.env.AWS_REGION; const AWS_REGION = process.env.AWS_REGION;
const AWS_S3_UPLOAD_BUCKET_NAME = process.env.AWS_S3_UPLOAD_BUCKET_NAME; const AWS_S3_UPLOAD_BUCKET_NAME = process.env.AWS_S3_UPLOAD_BUCKET_NAME || "";
const AWS_S3_FORCE_PATH_STYLE = process.env.AWS_S3_FORCE_PATH_STYLE !== "false";
const s3 = new AWS.S3({ const s3 = new AWS.S3({
s3ForcePathStyle: true, s3ForcePathStyle: AWS_S3_FORCE_PATH_STYLE,
accessKeyId: process.env.AWS_ACCESS_KEY_ID, accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, secretAccessKey: AWS_SECRET_ACCESS_KEY,
endpoint: new AWS.Endpoint(process.env.AWS_S3_UPLOAD_BUCKET_URL), endpoint: new AWS.Endpoint(process.env.AWS_S3_UPLOAD_BUCKET_URL),
signatureVersion: "v4", signatureVersion: "v4",
}); });
@@ -84,9 +84,9 @@ export const publicS3Endpoint = (isServerUpload?: boolean) => {
"localhost:" "localhost:"
).replace(/\/$/, ""); ).replace(/\/$/, "");
return `${host}/${isServerUpload && isDocker ? "s3/" : ""}${ return `${host}/${
process.env.AWS_S3_UPLOAD_BUCKET_NAME isServerUpload && isDocker ? "s3/" : ""
}`; }${AWS_S3_UPLOAD_BUCKET_NAME}`;
}; };
export const uploadToS3FromUrl = async ( export const uploadToS3FromUrl = async (
@@ -94,8 +94,6 @@ export const uploadToS3FromUrl = async (
key: string, key: string,
acl: string acl: string
) => { ) => {
invariant(AWS_S3_UPLOAD_BUCKET_NAME, "AWS_S3_UPLOAD_BUCKET_NAME not set");
try { try {
// $FlowIssue https://github.com/facebook/flow/issues/2171 // $FlowIssue https://github.com/facebook/flow/issues/2171
const res = await fetch(url); const res = await fetch(url);
@@ -103,7 +101,7 @@ export const uploadToS3FromUrl = async (
await s3 await s3
.putObject({ .putObject({
ACL: acl, ACL: acl,
Bucket: process.env.AWS_S3_UPLOAD_BUCKET_NAME, Bucket: AWS_S3_UPLOAD_BUCKET_NAME,
Key: key, Key: key,
ContentType: res.headers["content-type"], ContentType: res.headers["content-type"],
ContentLength: res.headers["content-length"], ContentLength: res.headers["content-length"],
@@ -126,18 +124,17 @@ export const uploadToS3FromUrl = async (
export const deleteFromS3 = (key: string) => { export const deleteFromS3 = (key: string) => {
return s3 return s3
.deleteObject({ .deleteObject({
Bucket: process.env.AWS_S3_UPLOAD_BUCKET_NAME, Bucket: AWS_S3_UPLOAD_BUCKET_NAME,
Key: key, Key: key,
}) })
.promise(); .promise();
}; };
export const getSignedImageUrl = async (key: string) => { export const getSignedImageUrl = async (key: string) => {
invariant(AWS_S3_UPLOAD_BUCKET_NAME, "AWS_S3_UPLOAD_BUCKET_NAME not set");
const isDocker = process.env.AWS_S3_UPLOAD_BUCKET_URL.match(/http:\/\/s3:/); const isDocker = process.env.AWS_S3_UPLOAD_BUCKET_URL.match(/http:\/\/s3:/);
const params = { const params = {
Bucket: process.env.AWS_S3_UPLOAD_BUCKET_NAME, Bucket: AWS_S3_UPLOAD_BUCKET_NAME,
Key: key, Key: key,
Expires: 60, Expires: 60,
}; };
@@ -149,7 +146,7 @@ export const getSignedImageUrl = async (key: string) => {
export const getImageByKey = async (key: string) => { export const getImageByKey = async (key: string) => {
const params = { const params = {
Bucket: process.env.AWS_S3_UPLOAD_BUCKET_NAME, Bucket: AWS_S3_UPLOAD_BUCKET_NAME,
Key: key, Key: key,
}; };

175
yarn.lock
View File

@@ -1024,10 +1024,10 @@
exec-sh "^0.3.2" exec-sh "^0.3.2"
minimist "^1.2.0" minimist "^1.2.0"
"@emotion/is-prop-valid@^0.8.3": "@emotion/is-prop-valid@^0.8.8":
version "0.8.7" version "0.8.8"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.7.tgz#803449993f436f9a6c67752251ea3fc492a1044c" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
integrity sha512-OPkKzUeiid0vEKjZqnGcy2mzxjIlCffin+L2C02pdz/bVlt5zZZE2VzO0D3XOPnH0NEeF21QNKSXiZphjr4xiQ== integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
dependencies: dependencies:
"@emotion/memoize" "0.7.4" "@emotion/memoize" "0.7.4"
@@ -1372,83 +1372,72 @@
execa "^4.0.0" execa "^4.0.0"
java-properties "^1.0.0" java-properties "^1.0.0"
"@sentry/apm@5.15.4": "@sentry/core@5.22.3":
version "5.15.4" version "5.22.3"
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.15.4.tgz#59af766d2bb4c9d98eda5ddba7a32a79ecc807a2" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.22.3.tgz#030f435f2b518f282ba8bd954dac90cd70888bd7"
integrity sha512-gcW225Jls1ShyBXMWN6zZyuVJwBOIQ63sI+URI2NSFsdpBpdpZ8yennIm+oMlSfb25Nzt9SId7TRSjPhlSbTZQ== integrity sha512-eGL5uUarw3o4i9QUb9JoFHnhriPpWCaqeaIBB06HUpdcvhrjoowcKZj1+WPec5lFg5XusE35vez7z/FPzmJUDw==
dependencies: dependencies:
"@sentry/browser" "5.15.4" "@sentry/hub" "5.22.3"
"@sentry/hub" "5.15.4" "@sentry/minimal" "5.22.3"
"@sentry/minimal" "5.15.4" "@sentry/types" "5.22.3"
"@sentry/types" "5.15.4" "@sentry/utils" "5.22.3"
"@sentry/utils" "5.15.4"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/browser@5.15.4": "@sentry/hub@5.22.3":
version "5.15.4" version "5.22.3"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.15.4.tgz#5a7e7bad088556665ed8e69bceb0e18784e4f6c7" resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.22.3.tgz#08309a70d2ea8d5e313d05840c1711f34f2fffe5"
integrity sha512-l/auT1HtZM3KxjCGQHYO/K51ygnlcuOrM+7Ga8gUUbU9ZXDYw6jRi0+Af9aqXKmdDw1naNxr7OCSy6NBrLWVZw== integrity sha512-INo47m6N5HFEs/7GMP9cqxOIt7rmRxdERunA3H2L37owjcr77MwHVeeJ9yawRS6FMtbWXplgWTyTIWIYOuqVbw==
dependencies: dependencies:
"@sentry/core" "5.15.4" "@sentry/types" "5.22.3"
"@sentry/types" "5.15.4" "@sentry/utils" "5.22.3"
"@sentry/utils" "5.15.4"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/core@5.15.4": "@sentry/minimal@5.22.3":
version "5.15.4" version "5.22.3"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.15.4.tgz#08b617e093a636168be5aebad141d1f744217085" resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.22.3.tgz#706e4029ae5494123d3875c658ba8911aa5cc440"
integrity sha512-9KP4NM4SqfV5NixpvAymC7Nvp36Zj4dU2fowmxiq7OIbzTxGXDhwuN/t0Uh8xiqlkpkQqSECZ1OjSFXrBldetQ== integrity sha512-HoINpYnVYCpNjn2XIPIlqH5o4BAITpTljXjtAftOx6Hzj+Opjg8tR8PWliyKDvkXPpc4kXK9D6TpEDw8MO0wZA==
dependencies: dependencies:
"@sentry/hub" "5.15.4" "@sentry/hub" "5.22.3"
"@sentry/minimal" "5.15.4" "@sentry/types" "5.22.3"
"@sentry/types" "5.15.4"
"@sentry/utils" "5.15.4"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/hub@5.15.4": "@sentry/node@^5.22.3":
version "5.15.4" version "5.22.3"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.15.4.tgz#cb64473725a60eec63b0be58ed1143eaaf894bee" resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.22.3.tgz#adea622eae6811e11edc8f34209c912caed91336"
integrity sha512-1XJ1SVqadkbUT4zLS0TVIVl99si7oHizLmghR8LMFl5wOkGEgehHSoOydQkIAX2C7sJmaF5TZ47ORBHgkqclUg== integrity sha512-TCCKO7hJKiQi1nGmJcQfvbbqv98P08LULh7pb/NaO5pV20t1FtICfGx8UMpORRDehbcAiYq/f7rPOF6X/Xl5iw==
dependencies: dependencies:
"@sentry/types" "5.15.4" "@sentry/core" "5.22.3"
"@sentry/utils" "5.15.4" "@sentry/hub" "5.22.3"
tslib "^1.9.3" "@sentry/tracing" "5.22.3"
"@sentry/types" "5.22.3"
"@sentry/minimal@5.15.4": "@sentry/utils" "5.22.3"
version "5.15.4" cookie "^0.4.1"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.15.4.tgz#113f01fefb86b7830994c3dfa7ad4889ba7b2003" https-proxy-agent "^5.0.0"
integrity sha512-GL4GZ3drS9ge+wmxkHBAMEwulaE7DMvAEfKQPDAjg2p3MfcCMhAYfuY4jJByAC9rg9OwBGGehz7UmhWMFjE0tw==
dependencies:
"@sentry/hub" "5.15.4"
"@sentry/types" "5.15.4"
tslib "^1.9.3"
"@sentry/node@^5.12.2":
version "5.15.4"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.15.4.tgz#e7bc3962d321a12b633743200165ca5f1757cb68"
integrity sha512-OfdhNEvOJZ55ZkCUcVgctjaZkOw7rmLzO5VyDTSgevA4uLsPaTNXSAeK2GSQBXc5J0KdRpNz4sSIyuxOS4Z7Vg==
dependencies:
"@sentry/apm" "5.15.4"
"@sentry/core" "5.15.4"
"@sentry/hub" "5.15.4"
"@sentry/types" "5.15.4"
"@sentry/utils" "5.15.4"
cookie "^0.3.1"
https-proxy-agent "^4.0.0"
lru_map "^0.3.3" lru_map "^0.3.3"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/types@5.15.4": "@sentry/tracing@5.22.3":
version "5.15.4" version "5.22.3"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.15.4.tgz#37f30e35b06e8e12ad1101f1beec3e9b88ca1aab" resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.22.3.tgz#9b5a376e3164c007a22e8642ec094104468cac0c"
integrity sha512-quPHPpeAuwID48HLPmqBiyXE3xEiZLZ5D3CEbU3c3YuvvAg8qmfOOTI6z4Z3Eedi7flvYpnx3n7N3dXIEz30Eg== integrity sha512-Zp59kMCk5v56ZAyErqjv/QvGOWOQ5fRltzeVQVp8unIDTk6gEFXfhwPsYHOokJe1mfkmrgPDV6xAkYgtL3KCDQ==
"@sentry/utils@5.15.4":
version "5.15.4"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.15.4.tgz#02865ab3c9b745656cea0ab183767ec26c96f6e6"
integrity sha512-lO8SLBjrUDGADl0LOkd55R5oL510d/1SaI08/IBHZCxCUwI4TiYo5EPECq8mrj3XGfgCyq9osw33bymRlIDuSQ==
dependencies: dependencies:
"@sentry/types" "5.15.4" "@sentry/hub" "5.22.3"
"@sentry/minimal" "5.22.3"
"@sentry/types" "5.22.3"
"@sentry/utils" "5.22.3"
tslib "^1.9.3"
"@sentry/types@5.22.3":
version "5.22.3"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.22.3.tgz#d1d547b30ee8bd7771fa893af74c4f3d71f0fd18"
integrity sha512-cv+VWK0YFgCVDvD1/HrrBWOWYG3MLuCUJRBTkV/Opdy7nkdNjhCAJQrEyMM9zX0sac8FKWKOHT0sykNh8KgmYw==
"@sentry/utils@5.22.3":
version "5.22.3"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.22.3.tgz#e3bda3e789239eb16d436f768daa12829f33d18f"
integrity sha512-AHNryXMBvIkIE+GQxTlmhBXD0Ksh+5w1SwM5qi6AttH+1qjWLvV6WB4+4pvVvEoS8t5F+WaVUZPQLmCCWp6zKw==
dependencies:
"@sentry/types" "5.22.3"
tslib "^1.9.3" tslib "^1.9.3"
"@sindresorhus/is@^0.7.0": "@sindresorhus/is@^0.7.0":
@@ -1808,11 +1797,6 @@ after@0.8.2:
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
agent-base@5:
version "5.1.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
agent-base@6: agent-base@6:
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.0.tgz#5d0101f19bbfaed39980b22ae866de153b93f09a" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.0.tgz#5d0101f19bbfaed39980b22ae866de153b93f09a"
@@ -3428,11 +3412,16 @@ convert-units@^2.3.4:
lodash.foreach "2.3.x" lodash.foreach "2.3.x"
lodash.keys "2.3.x" lodash.keys "2.3.x"
cookie@0.3.1, cookie@^0.3.1: cookie@0.3.1:
version "0.3.1" version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
cookie@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
cookies@~0.8.0: cookies@~0.8.0:
version "0.8.0" version "0.8.0"
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90" resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90"
@@ -5682,14 +5671,6 @@ https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
https-proxy-agent@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b"
integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==
dependencies:
agent-base "5"
debug "4"
https-proxy-agent@^5.0.0: https-proxy-agent@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
@@ -9105,7 +9086,7 @@ prosemirror-tables@^1.0.0:
prosemirror-transform "^1.2.1" prosemirror-transform "^1.2.1"
prosemirror-view "^1.13.3" prosemirror-view "^1.13.3"
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.2.1: prosemirror-transform@1.2.5, prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.2.1:
version "1.2.5" version "1.2.5"
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.2.5.tgz#7a3e2c61fcdbaf1d0844a2a3bc34fc3524e9809c" resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.2.5.tgz#7a3e2c61fcdbaf1d0844a2a3bc34fc3524e9809c"
integrity sha512-eqeIaxWtUfOnpA1ERrXCuSIMzqIJtL9Qrs5uJMCjY5RMSaH5o4pc390SAjn/IDPeIlw6auh0hCCXs3wRvGnQug== integrity sha512-eqeIaxWtUfOnpA1ERrXCuSIMzqIJtL9Qrs5uJMCjY5RMSaH5o4pc390SAjn/IDPeIlw6auh0hCCXs3wRvGnQug==
@@ -9847,10 +9828,10 @@ retry-as-promised@^3.2.0:
dependencies: dependencies:
any-promise "^1.3.0" any-promise "^1.3.0"
rich-markdown-editor@^10.6.1: rich-markdown-editor@^10.6.5:
version "10.6.1" version "10.6.5"
resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-10.6.1.tgz#22099c12b8b1e193f9523c16cb83ef0824c08d58" resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-10.6.5.tgz#b74ae2e7d05eaa3c8ef34744e5cb0ed2dbdb0958"
integrity sha512-HfjrNXanFwy6iy3dkNqOZF1siwm5tuou8B9y2gj0PcP73KJyQ/FoFPUSEZ/E3ttU45NJCARLvd8X5Qc2wdtLVA== integrity sha512-C/C+6L7BTXC4zSHgOYMljOQ3CvFt8zNCT829woKBHcDWSnXiUzpjgZZ4qEeNRlh/XJmqeFZYfqY+OzIMsVP2+Q==
dependencies: dependencies:
copy-to-clipboard "^3.0.8" copy-to-clipboard "^3.0.8"
lodash "^4.17.11" lodash "^4.17.11"
@@ -9869,6 +9850,7 @@ rich-markdown-editor@^10.6.1:
prosemirror-schema-list "^1.1.2" prosemirror-schema-list "^1.1.2"
prosemirror-state "^1.3.3" prosemirror-state "^1.3.3"
prosemirror-tables "^1.0.0" prosemirror-tables "^1.0.0"
prosemirror-transform "1.2.5"
prosemirror-utils "^0.9.6" prosemirror-utils "^0.9.6"
prosemirror-view "^1.14.11" prosemirror-view "^1.14.11"
react-medium-image-zoom "^3.0.16" react-medium-image-zoom "^3.0.16"
@@ -9876,8 +9858,8 @@ rich-markdown-editor@^10.6.1:
refractor "^2.10.1" refractor "^2.10.1"
slugify "^1.4.0" slugify "^1.4.0"
smooth-scroll-into-view-if-needed "^1.1.27" smooth-scroll-into-view-if-needed "^1.1.27"
styled-components "^5.0.0" styled-components "^5.1.0"
typescript "^3.7.5" typescript "3.7.5"
rimraf@2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3: rimraf@2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3:
version "2.7.1" version "2.7.1"
@@ -10819,14 +10801,14 @@ styled-components-breakpoint@^2.1.1:
resolved "https://registry.yarnpkg.com/styled-components-breakpoint/-/styled-components-breakpoint-2.1.1.tgz#37c1b92b0e96c1bbc5d293724d7a114daaa15fca" resolved "https://registry.yarnpkg.com/styled-components-breakpoint/-/styled-components-breakpoint-2.1.1.tgz#37c1b92b0e96c1bbc5d293724d7a114daaa15fca"
integrity sha512-PkS7p3MkPJx/v930Q3MPJU8llfFJTxk8o009jl0p+OUFmVb2AlHmVclX1MBHSXk8sZYGoVTTVIPDuZCELi7QIg== integrity sha512-PkS7p3MkPJx/v930Q3MPJU8llfFJTxk8o009jl0p+OUFmVb2AlHmVclX1MBHSXk8sZYGoVTTVIPDuZCELi7QIg==
styled-components@^5.0.0: styled-components@^5.0.0, styled-components@^5.1.0:
version "5.0.1" version "5.1.1"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.0.1.tgz#57782a6471031abefb2db5820a1876ae853bc619" resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.1.1.tgz#96dfb02a8025794960863b9e8e365e3b6be5518d"
integrity sha512-E0xKTRIjTs4DyvC1MHu/EcCXIj6+ENCP8hP01koyoADF++WdBUOrSGwU1scJRw7/YaYOhDvvoad6VlMG+0j53A== integrity sha512-1ps8ZAYu2Husx+Vz8D+MvXwEwvMwFv+hqqUwhNlDN5ybg6A+3xyW1ECrAgywhvXapNfXiz79jJyU0x22z0FFTg==
dependencies: dependencies:
"@babel/helper-module-imports" "^7.0.0" "@babel/helper-module-imports" "^7.0.0"
"@babel/traverse" "^7.4.5" "@babel/traverse" "^7.4.5"
"@emotion/is-prop-valid" "^0.8.3" "@emotion/is-prop-valid" "^0.8.8"
"@emotion/stylis" "^0.8.4" "@emotion/stylis" "^0.8.4"
"@emotion/unitless" "^0.7.4" "@emotion/unitless" "^0.7.4"
babel-plugin-styled-components ">= 1" babel-plugin-styled-components ">= 1"
@@ -11316,7 +11298,12 @@ typescript-compiler@^1.4.1-2:
resolved "https://registry.yarnpkg.com/typescript-compiler/-/typescript-compiler-1.4.1-2.tgz#ba4f7db22d91534a1929d90009dce161eb72fd3f" resolved "https://registry.yarnpkg.com/typescript-compiler/-/typescript-compiler-1.4.1-2.tgz#ba4f7db22d91534a1929d90009dce161eb72fd3f"
integrity sha1-uk99si2RU0oZKdkACdzhYety/T8= integrity sha1-uk99si2RU0oZKdkACdzhYety/T8=
typescript@^3.4, typescript@^3.7.5: typescript@3.7.5:
version "3.7.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
typescript@^3.4:
version "3.9.2" version "3.9.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.2.tgz#64e9c8e9be6ea583c54607677dd4680a1cf35db9" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.2.tgz#64e9c8e9be6ea583c54607677dd4680a1cf35db9"
integrity sha512-q2ktq4n/uLuNNShyayit+DTobV2ApPEo/6so68JaD5ojvc/6GClBipedB9zNWYxRSAlZXAe405Rlijzl6qDiSw== integrity sha512-q2ktq4n/uLuNNShyayit+DTobV2ApPEo/6so68JaD5ojvc/6GClBipedB9zNWYxRSAlZXAe405Rlijzl6qDiSw==