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_NAME=bucket_name_here
AWS_S3_UPLOAD_MAX_SIZE=26214400
AWS_S3_FORCE_PATH_STYLE=true
# uploaded s3 objects permission level, default is private
# set to "public-read" to allow public access
AWS_S3_ACL=private

View File

@@ -92,6 +92,11 @@
"value": "26214400",
"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": {
"value": "us-east-1",
"description": "Region in which the above S3 bucket exists",

View File

@@ -1,20 +1,23 @@
// @flow
import ArrowKeyNavigation from "boundless-arrow-key-navigation";
import { observable, action } from "mobx";
import { observer, inject } from "mobx-react";
import { action, observable } from "mobx";
import { inject, observer } from "mobx-react";
import { CloseIcon } from "outline-icons";
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 styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore";
import DocumentsStore from "stores/DocumentsStore";
import RevisionsStore from "stores/RevisionsStore";
import Button from "components/Button";
import Flex from "components/Flex";
import { ListPlaceholder } from "components/LoadingPlaceholder";
import Revision from "./components/Revision";
import { documentHistoryUrl } from "utils/routeHelpers";
import { documentHistoryUrl, documentUrl } from "utils/routeHelpers";
type Props = {
match: Match,
@@ -29,6 +32,7 @@ class DocumentHistory extends React.Component<Props> {
@observable isFetching: boolean = false;
@observable offset: number = 0;
@observable allowLoadMore: boolean = true;
@observable redirectTo: ?string;
async componentDidMount() {
await this.loadMoreResults();
@@ -86,15 +90,34 @@ class DocumentHistory extends React.Component<Props> {
return this.props.revisions.getDocumentRevisions(document.id);
}
onCloseHistory = () => {
const document = this.props.documents.getByUrl(
this.props.match.params.documentSlug
);
this.redirectTo = documentUrl(document);
};
render() {
const document = this.props.documents.getByUrl(
this.props.match.params.documentSlug
);
const showLoading = (!this.isLoaded && this.isFetching) || !document;
if (this.redirectTo) return <Redirect to={this.redirectTo} push />;
return (
<Sidebar>
<Wrapper column>
<Header>
<Title>History</Title>
<Button
icon={<CloseIcon />}
onClick={this.onCloseHistory}
borderOnHover
neutral
/>
</Header>
{showLoading ? (
<Loading>
<ListPlaceholder count={5} />
@@ -140,10 +163,36 @@ const Wrapper = styled(Flex)`
`;
const Sidebar = styled(Flex)`
display: none;
background: ${(props) => props.theme.background};
min-width: ${(props) => props.theme.sidebarWidth};
border-left: 1px solid ${(props) => props.theme.divider};
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);

View File

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

View File

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

View File

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

View File

@@ -127,6 +127,7 @@ class CollectionMenu extends React.Component<Props> {
collection={collection}
onSubmit={this.handleMembersModalClose}
handleEditCollectionOpen={this.handleEditCollectionOpen}
onEdit={this.handleEditCollectionOpen}
/>
</Modal>
<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
team by{" "}
<a role="button" onClick={this.props.onEdit}>
changing its visibility
changing the visibility
</a>
.
</HelpText>

View File

@@ -18,6 +18,7 @@ import Branding from "components/Branding";
import ErrorBoundary from "components/ErrorBoundary";
import Flex from "components/Flex";
import LoadingIndicator from "components/LoadingIndicator";
import LoadingPlaceholder from "components/LoadingPlaceholder";
import Notice from "components/Notice";
import PageTitle from "components/PageTitle";
import Time from "components/Time";
@@ -67,7 +68,7 @@ type Props = {
@observer
class DocumentScene extends React.Component<Props> {
@observable editor: ?any;
@observable editor = React.createRef();
@observable isUploading: boolean = false;
@observable isSaving: boolean = false;
@observable isPublishing: boolean = false;
@@ -380,7 +381,7 @@ class DocumentScene extends React.Component<Props> {
)}
<MaxWidth
archived={document.isArchived}
tocVisible={ui.tocVisible}
tocVisible={ui.tocVisible && readOnly}
column
auto
>
@@ -412,50 +413,52 @@ class DocumentScene extends React.Component<Props> {
)}
</Notice>
)}
<Flex auto={!readOnly}>
{ui.tocVisible && readOnly && (
<Contents
headings={this.editor ? this.editor.getHeadings() : []}
<React.Suspense fallback={<LoadingPlaceholder />}>
<Flex auto={!readOnly}>
{ui.tocVisible && readOnly && (
<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
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>
</>
)}
</React.Suspense>
</MaxWidth>
</Container>
</Background>

View File

@@ -13,13 +13,11 @@ import DocumentsStore from "stores/DocumentsStore";
import UiStore from "stores/UiStore";
import Document from "models/Document";
import Flex from "components/Flex";
import Input from "components/Input";
import { Outline } from "components/Input";
import Labeled from "components/Labeled";
import Modal from "components/Modal";
import PathToDocument from "components/PathToDocument";
const MAX_RESULTS = 8;
type Props = {|
document: Document,
documents: DocumentsStore,
@@ -36,14 +34,19 @@ class DocumentMove extends React.Component<Props> {
@computed
get searchIndex() {
const { collections } = this.props;
const { collections, documents } = this.props;
const paths = collections.pathsToDocuments;
const index = new Search("id");
index.addIndex("title");
// Build index
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);
return index;
@@ -136,35 +139,41 @@ class DocumentMove extends React.Component<Props> {
</Section>
<Section column>
<Labeled label="Choose a new location">
<Input
type="search"
placeholder="Search collections & documents…"
onKeyDown={this.handleKeyDown}
onChange={this.handleFilter}
required
autoFocus
/>
</Labeled>
<Flex column>
<StyledArrowKeyNavigation
mode={ArrowKeyNavigation.mode.VERTICAL}
defaultActiveChildIndex={0}
>
{this.results.slice(0, MAX_RESULTS).map((result, index) => (
<PathToDocument
key={result.id}
result={result}
document={document}
collection={collections.get(result.collectionId)}
ref={(ref) =>
index === 0 && this.setFirstDocumentRef(ref)
}
onSuccess={this.handleSuccess}
/>
))}
</StyledArrowKeyNavigation>
</Flex>
<Labeled label="Choose a new location" />
<NewLocation>
<InputWrapper>
<Input
type="search"
placeholder="Search collections & documents…"
onKeyDown={this.handleKeyDown}
onChange={this.handleFilter}
required
autoFocus
/>
</InputWrapper>
<Results>
<Flex column>
<StyledArrowKeyNavigation
mode={ArrowKeyNavigation.mode.VERTICAL}
defaultActiveChildIndex={0}
>
{this.results.map((result, index) => (
<PathToDocument
key={result.id}
result={result}
document={document}
collection={collections.get(result.collectionId)}
ref={(ref) =>
index === 0 && this.setFirstDocumentRef(ref)
}
onSuccess={this.handleSuccess}
/>
))}
</StyledArrowKeyNavigation>
</Flex>
</Results>
</NewLocation>
</Section>
</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)`
margin-bottom: 24px;
`;

View File

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

View File

@@ -9,7 +9,7 @@ import Document from "models/Document";
import Button from "components/Button";
import Flex from "components/Flex";
import HelpText from "components/HelpText";
import { collectionUrl } from "utils/routeHelpers";
import { collectionUrl, documentUrl } from "utils/routeHelpers";
type Props = {
history: RouterHistory,
@@ -24,15 +24,27 @@ class DocumentDelete extends React.Component<Props> {
@observable isDeleting: boolean;
handleSubmit = async (ev: SyntheticEvent<>) => {
const { documents, document } = this.props;
ev.preventDefault();
this.isDeleting = true;
try {
await this.props.document.delete();
if (this.props.ui.activeDocumentId === this.props.document.id) {
this.props.history.push(
collectionUrl(this.props.document.collectionId)
);
await document.delete();
// only redirect if we're currently viewing the document that's deleted
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();
} catch (err) {

View File

@@ -66,7 +66,7 @@
"@babel/preset-flow": "^7.10.4",
"@babel/preset-react": "^7.10.4",
"@rehooks/window-scroll-position": "^1.0.1",
"@sentry/node": "^5.12.2",
"@sentry/node": "^5.22.3",
"@tippy.js/react": "^2.2.2",
"@tommoor/remove-markdown": "0.3.1",
"autotrack": "^2.4.1",
@@ -138,7 +138,7 @@
"react-portal": "^4.0.0",
"react-router-dom": "^5.1.2",
"react-waypoint": "^9.0.2",
"rich-markdown-editor": "^10.6.1",
"rich-markdown-editor": "^10.6.5",
"semver": "^7.3.2",
"sequelize": "^6.3.4",
"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 });
authorize(user, "delete", document);
await document.delete();
await document.delete(user.id);
await Event.create({
name: "documents.delete",

View File

@@ -17,7 +17,6 @@ if (process.env.AWS_ACCESS_KEY_ID) {
"AWS_REGION",
"AWS_SECRET_ACCESS_KEY",
"AWS_S3_UPLOAD_BUCKET_URL",
"AWS_S3_UPLOAD_BUCKET_NAME",
"AWS_S3_UPLOAD_MAX_SIZE",
].forEach((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
Document.prototype.unarchive = async function (userId) {
Document.prototype.unarchive = async function (userId: string) {
const collection = await this.getCollection();
// 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.
Document.prototype.delete = function (options) {
return sequelize.transaction(async (transaction: Transaction): Promise<*> => {
if (!this.archivedAt) {
// delete any children and remove from the document structure
const collection = await this.getCollection();
if (collection) await collection.deleteDocument(this, { transaction });
Document.prototype.delete = function (userId: string) {
return sequelize.transaction(
async (transaction: Transaction): Promise<Document> => {
if (!this.archivedAt && !this.template) {
// delete any children and remove from the document structure
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 () {

View File

@@ -1,6 +1,11 @@
/* eslint-disable flowtype/require-valid-file-annotation */
import { Document } from "../models";
import { buildDocument, buildCollection, buildTeam } from "../test/factories";
import {
buildDocument,
buildCollection,
buildTeam,
buildUser,
} from "../test/factories";
import { flushdb } from "../test/support";
beforeEach(() => flushdb());
@@ -192,3 +197,16 @@ describe("#searchForTeam", () => {
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>
<div id="root"></div>
<script>//inject-env//</script>
<script src="https://browser.sentry-cdn.com/5.12.1/bundle.min.js"
integrity="sha384-y+an4eARFKvjzOivf/Z7JtMJhaN6b+lLQ5oFbBbUwZNNVir39cYtkjW1r6Xjbxg3" crossorigin="anonymous">
<script src="https://browser.sentry-cdn.com/5.22.3/bundle.min.js"
integrity="sha384-A1qzcXXJWl+bzYr+r8AdFzSaLbdcbYRFmG37MEDKr4EYjtraUyoZ6UiMw31jHcV9" crossorigin="anonymous">
</script>
<script>
if ('//inject-sentry-dsn//') {
@@ -44,6 +44,7 @@
'NotFoundError',
'OfflineError',
'UpdateRequiredError',
'ChunkLoadError'
],
});
}

View File

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

175
yarn.lock
View File

@@ -1024,10 +1024,10 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
"@emotion/is-prop-valid@^0.8.3":
version "0.8.7"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.7.tgz#803449993f436f9a6c67752251ea3fc492a1044c"
integrity sha512-OPkKzUeiid0vEKjZqnGcy2mzxjIlCffin+L2C02pdz/bVlt5zZZE2VzO0D3XOPnH0NEeF21QNKSXiZphjr4xiQ==
"@emotion/is-prop-valid@^0.8.8":
version "0.8.8"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
dependencies:
"@emotion/memoize" "0.7.4"
@@ -1372,83 +1372,72 @@
execa "^4.0.0"
java-properties "^1.0.0"
"@sentry/apm@5.15.4":
version "5.15.4"
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.15.4.tgz#59af766d2bb4c9d98eda5ddba7a32a79ecc807a2"
integrity sha512-gcW225Jls1ShyBXMWN6zZyuVJwBOIQ63sI+URI2NSFsdpBpdpZ8yennIm+oMlSfb25Nzt9SId7TRSjPhlSbTZQ==
"@sentry/core@5.22.3":
version "5.22.3"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.22.3.tgz#030f435f2b518f282ba8bd954dac90cd70888bd7"
integrity sha512-eGL5uUarw3o4i9QUb9JoFHnhriPpWCaqeaIBB06HUpdcvhrjoowcKZj1+WPec5lFg5XusE35vez7z/FPzmJUDw==
dependencies:
"@sentry/browser" "5.15.4"
"@sentry/hub" "5.15.4"
"@sentry/minimal" "5.15.4"
"@sentry/types" "5.15.4"
"@sentry/utils" "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/browser@5.15.4":
version "5.15.4"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.15.4.tgz#5a7e7bad088556665ed8e69bceb0e18784e4f6c7"
integrity sha512-l/auT1HtZM3KxjCGQHYO/K51ygnlcuOrM+7Ga8gUUbU9ZXDYw6jRi0+Af9aqXKmdDw1naNxr7OCSy6NBrLWVZw==
"@sentry/hub@5.22.3":
version "5.22.3"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.22.3.tgz#08309a70d2ea8d5e313d05840c1711f34f2fffe5"
integrity sha512-INo47m6N5HFEs/7GMP9cqxOIt7rmRxdERunA3H2L37owjcr77MwHVeeJ9yawRS6FMtbWXplgWTyTIWIYOuqVbw==
dependencies:
"@sentry/core" "5.15.4"
"@sentry/types" "5.15.4"
"@sentry/utils" "5.15.4"
"@sentry/types" "5.22.3"
"@sentry/utils" "5.22.3"
tslib "^1.9.3"
"@sentry/core@5.15.4":
version "5.15.4"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.15.4.tgz#08b617e093a636168be5aebad141d1f744217085"
integrity sha512-9KP4NM4SqfV5NixpvAymC7Nvp36Zj4dU2fowmxiq7OIbzTxGXDhwuN/t0Uh8xiqlkpkQqSECZ1OjSFXrBldetQ==
"@sentry/minimal@5.22.3":
version "5.22.3"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.22.3.tgz#706e4029ae5494123d3875c658ba8911aa5cc440"
integrity sha512-HoINpYnVYCpNjn2XIPIlqH5o4BAITpTljXjtAftOx6Hzj+Opjg8tR8PWliyKDvkXPpc4kXK9D6TpEDw8MO0wZA==
dependencies:
"@sentry/hub" "5.15.4"
"@sentry/minimal" "5.15.4"
"@sentry/types" "5.15.4"
"@sentry/utils" "5.15.4"
"@sentry/hub" "5.22.3"
"@sentry/types" "5.22.3"
tslib "^1.9.3"
"@sentry/hub@5.15.4":
version "5.15.4"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.15.4.tgz#cb64473725a60eec63b0be58ed1143eaaf894bee"
integrity sha512-1XJ1SVqadkbUT4zLS0TVIVl99si7oHizLmghR8LMFl5wOkGEgehHSoOydQkIAX2C7sJmaF5TZ47ORBHgkqclUg==
"@sentry/node@^5.22.3":
version "5.22.3"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.22.3.tgz#adea622eae6811e11edc8f34209c912caed91336"
integrity sha512-TCCKO7hJKiQi1nGmJcQfvbbqv98P08LULh7pb/NaO5pV20t1FtICfGx8UMpORRDehbcAiYq/f7rPOF6X/Xl5iw==
dependencies:
"@sentry/types" "5.15.4"
"@sentry/utils" "5.15.4"
tslib "^1.9.3"
"@sentry/minimal@5.15.4":
version "5.15.4"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.15.4.tgz#113f01fefb86b7830994c3dfa7ad4889ba7b2003"
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"
"@sentry/core" "5.22.3"
"@sentry/hub" "5.22.3"
"@sentry/tracing" "5.22.3"
"@sentry/types" "5.22.3"
"@sentry/utils" "5.22.3"
cookie "^0.4.1"
https-proxy-agent "^5.0.0"
lru_map "^0.3.3"
tslib "^1.9.3"
"@sentry/types@5.15.4":
version "5.15.4"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.15.4.tgz#37f30e35b06e8e12ad1101f1beec3e9b88ca1aab"
integrity sha512-quPHPpeAuwID48HLPmqBiyXE3xEiZLZ5D3CEbU3c3YuvvAg8qmfOOTI6z4Z3Eedi7flvYpnx3n7N3dXIEz30Eg==
"@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==
"@sentry/tracing@5.22.3":
version "5.22.3"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.22.3.tgz#9b5a376e3164c007a22e8642ec094104468cac0c"
integrity sha512-Zp59kMCk5v56ZAyErqjv/QvGOWOQ5fRltzeVQVp8unIDTk6gEFXfhwPsYHOokJe1mfkmrgPDV6xAkYgtL3KCDQ==
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"
"@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"
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:
version "6.0.0"
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.keys "2.3.x"
cookie@0.3.1, cookie@^0.3.1:
cookie@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
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:
version "0.8.0"
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"
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:
version "5.0.0"
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-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"
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.2.5.tgz#7a3e2c61fcdbaf1d0844a2a3bc34fc3524e9809c"
integrity sha512-eqeIaxWtUfOnpA1ERrXCuSIMzqIJtL9Qrs5uJMCjY5RMSaH5o4pc390SAjn/IDPeIlw6auh0hCCXs3wRvGnQug==
@@ -9847,10 +9828,10 @@ retry-as-promised@^3.2.0:
dependencies:
any-promise "^1.3.0"
rich-markdown-editor@^10.6.1:
version "10.6.1"
resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-10.6.1.tgz#22099c12b8b1e193f9523c16cb83ef0824c08d58"
integrity sha512-HfjrNXanFwy6iy3dkNqOZF1siwm5tuou8B9y2gj0PcP73KJyQ/FoFPUSEZ/E3ttU45NJCARLvd8X5Qc2wdtLVA==
rich-markdown-editor@^10.6.5:
version "10.6.5"
resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-10.6.5.tgz#b74ae2e7d05eaa3c8ef34744e5cb0ed2dbdb0958"
integrity sha512-C/C+6L7BTXC4zSHgOYMljOQ3CvFt8zNCT829woKBHcDWSnXiUzpjgZZ4qEeNRlh/XJmqeFZYfqY+OzIMsVP2+Q==
dependencies:
copy-to-clipboard "^3.0.8"
lodash "^4.17.11"
@@ -9869,6 +9850,7 @@ rich-markdown-editor@^10.6.1:
prosemirror-schema-list "^1.1.2"
prosemirror-state "^1.3.3"
prosemirror-tables "^1.0.0"
prosemirror-transform "1.2.5"
prosemirror-utils "^0.9.6"
prosemirror-view "^1.14.11"
react-medium-image-zoom "^3.0.16"
@@ -9876,8 +9858,8 @@ rich-markdown-editor@^10.6.1:
refractor "^2.10.1"
slugify "^1.4.0"
smooth-scroll-into-view-if-needed "^1.1.27"
styled-components "^5.0.0"
typescript "^3.7.5"
styled-components "^5.1.0"
typescript "3.7.5"
rimraf@2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3:
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"
integrity sha512-PkS7p3MkPJx/v930Q3MPJU8llfFJTxk8o009jl0p+OUFmVb2AlHmVclX1MBHSXk8sZYGoVTTVIPDuZCELi7QIg==
styled-components@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.0.1.tgz#57782a6471031abefb2db5820a1876ae853bc619"
integrity sha512-E0xKTRIjTs4DyvC1MHu/EcCXIj6+ENCP8hP01koyoADF++WdBUOrSGwU1scJRw7/YaYOhDvvoad6VlMG+0j53A==
styled-components@^5.0.0, styled-components@^5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.1.1.tgz#96dfb02a8025794960863b9e8e365e3b6be5518d"
integrity sha512-1ps8ZAYu2Husx+Vz8D+MvXwEwvMwFv+hqqUwhNlDN5ybg6A+3xyW1ECrAgywhvXapNfXiz79jJyU0x22z0FFTg==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@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/unitless" "^0.7.4"
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"
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"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.2.tgz#64e9c8e9be6ea583c54607677dd4680a1cf35db9"
integrity sha512-q2ktq4n/uLuNNShyayit+DTobV2ApPEo/6so68JaD5ojvc/6GClBipedB9zNWYxRSAlZXAe405Rlijzl6qDiSw==