diff --git a/.env.sample b/.env.sample index b1ee6045b..da9204d2e 100644 --- a/.env.sample +++ b/.env.sample @@ -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 diff --git a/app.json b/app.json index 09221239a..5aa7e9909 100644 --- a/app.json +++ b/app.json @@ -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", diff --git a/app/components/DocumentHistory/DocumentHistory.js b/app/components/DocumentHistory/DocumentHistory.js index 778f61fa8..e6ce3c262 100644 --- a/app/components/DocumentHistory/DocumentHistory.js +++ b/app/components/DocumentHistory/DocumentHistory.js @@ -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 { @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 { 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 ; + return ( +
+ History +
{showLoading ? ( @@ -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); diff --git a/app/components/Modal.js b/app/components/Modal.js index c5f615b5e..8dbf6197b 100644 --- a/app/components/Modal.js +++ b/app/components/Modal.js @@ -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} > - ev.stopPropagation()} column> - {title &&

{title}

} - - {children} + + ev.stopPropagation()} column> + {title &&

{title}

} + {children} +
@@ -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; diff --git a/app/components/PathToDocument.js b/app/components/PathToDocument.js index 5ab83073c..147e724ef 100644 --- a/app/components/PathToDocument.js +++ b/app/components/PathToDocument.js @@ -42,20 +42,23 @@ class PathToDocument extends React.Component { return ( {collection && } +   {result.path .map((doc) => {doc.title}) .reduce((prev, curr) => [prev, , curr])} {document && ( - + {" "} {document.title} - + )} ); } } +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; + } } `; diff --git a/app/index.js b/app/index.js index 41b75aa4b..4b78a5bc6 100644 --- a/app/index.js +++ b/app/index.js @@ -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"; diff --git a/app/menus/CollectionMenu.js b/app/menus/CollectionMenu.js index 8a149811c..df7e38a3c 100644 --- a/app/menus/CollectionMenu.js +++ b/app/menus/CollectionMenu.js @@ -127,6 +127,7 @@ class CollectionMenu extends React.Component { collection={collection} onSubmit={this.handleMembersModalClose} handleEditCollectionOpen={this.handleEditCollectionOpen} + onEdit={this.handleEditCollectionOpen} /> diff --git a/app/scenes/CollectionMembers/CollectionMembers.js b/app/scenes/CollectionMembers/CollectionMembers.js index 3a0a991ab..028a86a95 100644 --- a/app/scenes/CollectionMembers/CollectionMembers.js +++ b/app/scenes/CollectionMembers/CollectionMembers.js @@ -132,7 +132,7 @@ class CollectionMembers extends React.Component { collection. You can make this collection visible to the entire team by{" "} - changing its visibility + changing the visibility . diff --git a/app/scenes/Document/components/Document.js b/app/scenes/Document/components/Document.js index c9f702652..73812570f 100644 --- a/app/scenes/Document/components/Document.js +++ b/app/scenes/Document/components/Document.js @@ -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 { - @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 { )} @@ -412,50 +413,52 @@ class DocumentScene extends React.Component { )} )} - - {ui.tocVisible && readOnly && ( - }> + + {ui.tocVisible && readOnly && ( + + )} + + + {readOnly && !isShare && !revision && ( + <> + + + + + )} - { - 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} - /> - - {readOnly && !isShare && !revision && ( - <> - - - - - - )} + diff --git a/app/scenes/Document/components/DocumentMove.js b/app/scenes/Document/components/DocumentMove.js index 0bba3ed30..aab06eb51 100644 --- a/app/scenes/Document/components/DocumentMove.js +++ b/app/scenes/Document/components/DocumentMove.js @@ -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 { @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 {
- - - - - - {this.results.slice(0, MAX_RESULTS).map((result, index) => ( - - index === 0 && this.setFirstDocumentRef(ref) - } - onSuccess={this.handleSuccess} - /> - ))} - - + + + + + + + + + + {this.results.map((result, index) => ( + + index === 0 && this.setFirstDocumentRef(ref) + } + onSuccess={this.handleSuccess} + /> + ))} + + + +
)} @@ -173,6 +182,37 @@ class DocumentMove extends React.Component { } } +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; `; diff --git a/app/scenes/Document/components/Editor.js b/app/scenes/Document/components/Editor.js index 023aea02a..4962451dd 100644 --- a/app/scenes/Document/components/Editor.js +++ b/app/scenes/Document/components/Editor.js @@ -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 { @observable activeLinkEvent: ?MouseEvent; - editor = React.createRef(); 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 { isDraft, isShare, readOnly, + innerRef, } = this.props; const { emoji } = parseTitle(title); const startsWithEmojiAndSpace = !!(emoji && title.startsWith(`${emoji} `)); return ( - }> - + <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> ); } diff --git a/app/scenes/DocumentDelete.js b/app/scenes/DocumentDelete.js index d375f17e9..e4beeed28 100644 --- a/app/scenes/DocumentDelete.js +++ b/app/scenes/DocumentDelete.js @@ -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) { diff --git a/package.json b/package.json index 8c7e1245a..d6667d771 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server/api/documents.js b/server/api/documents.js index 90f88e1a7..7135992d0 100644 --- a/server/api/documents.js +++ b/server/api/documents.js @@ -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", diff --git a/server/index.js b/server/index.js index 6fd134454..f8986a381 100644 --- a/server/index.js +++ b/server/index.js @@ -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]) { diff --git a/server/models/Document.js b/server/models/Document.js index 6518bd1c6..4d08f077d 100644 --- a/server/models/Document.js +++ b/server/models/Document.js @@ -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 () { diff --git a/server/models/Document.test.js b/server/models/Document.test.js index 1bdae5e12..c8e25c6be 100644 --- a/server/models/Document.test.js +++ b/server/models/Document.test.js @@ -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(); + }); +}); diff --git a/server/static/index.html b/server/static/index.html index 01223e95e..a3d395764 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -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' ], }); } diff --git a/server/utils/s3.js b/server/utils/s3.js index 4e72a12b6..05ed6d9e0 100644 --- a/server/utils/s3.js +++ b/server/utils/s3.js @@ -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, }; diff --git a/yarn.lock b/yarn.lock index 71bcab315..4b8397c6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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==