From ff41aa4a176ba22378dcca4affa5334589978fdf Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 11 Aug 2018 14:09:59 -0700 Subject: [PATCH 1/8] Update debug dependency. Low severity CVE-2017-16137 --- package.json | 2 +- yarn.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 3edd22a41..16c0febd8 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "copy-to-clipboard": "^3.0.6", "css-loader": "^0.28.7", "date-fns": "1.29.0", - "debug": "2.2.0", + "debug": "2.6.9", "dotenv": "^4.0.0", "emoji-regex": "^6.5.1", "exports-loader": "^0.6.4", diff --git a/yarn.lock b/yarn.lock index c50d32058..680590e25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2526,19 +2526,13 @@ debug@*, debug@2.6.8, debug@^2.2.0, debug@^2.3.2, debug@^2.6.1, debug@^2.6.3, de dependencies: ms "2.0.0" -debug@2.2.0, debug@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" - dependencies: - ms "0.7.1" - debug@2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" dependencies: ms "2.0.0" -debug@^2.6.9: +debug@2.6.9, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -2556,6 +2550,12 @@ debug@^3.0.1, debug@^3.1.0: dependencies: ms "2.0.0" +debug@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" + dependencies: + ms "0.7.1" + debuglog@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" From 4c1e33110ee17c62c1db88d217e0227b8005b4c3 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 13 Aug 2018 21:20:44 -0700 Subject: [PATCH 2/8] Upgrade to rich-markdown-editor 4.0 --- app/scenes/Document/Document.js | 6 ++++-- package.json | 2 +- yarn.lock | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/scenes/Document/Document.js b/app/scenes/Document/Document.js index 839595122..977fa2e49 100644 --- a/app/scenes/Document/Document.js +++ b/app/scenes/Document/Document.js @@ -194,16 +194,18 @@ class DocumentScene extends React.Component { this.isUploading = false; }; - onChange = text => { + onChange = debounce(getText => { let document = this.document; if (!document) return; + + const text = getText(); if (document.text.trim() === text.trim()) return; document.updateData({ text }, true); // prevent autosave before anything has been written if (!document.title && !document.id) return; this.autosave(); - }; + }, 250); onDiscard = () => { let url; diff --git a/package.json b/package.json index 16c0febd8..3ea7caf86 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "react-waypoint": "^7.3.1", "redis": "^2.6.2", "redis-lock": "^0.1.0", - "rich-markdown-editor": "3.1.4", + "rich-markdown-editor": "4.0.0", "safestart": "1.1.0", "sequelize": "4.28.6", "sequelize-cli": "^2.7.0", diff --git a/yarn.lock b/yarn.lock index 680590e25..f05eceae9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8858,9 +8858,9 @@ retry-axios@0.3.2, retry-axios@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/retry-axios/-/retry-axios-0.3.2.tgz#5757c80f585b4cc4c4986aa2ffd47a60c6d35e13" -rich-markdown-editor@3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-3.1.4.tgz#90e60f599e0ad57b52d893880c89d7ebdfdd97cd" +rich-markdown-editor@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-4.0.0.tgz#483f2b24867553245b66a8c8f56ec3f8d70ae248" dependencies: "@tommoor/slate-drop-or-paste-images" "^0.8.1" babel-plugin-transform-async-to-generator "^6.24.1" From c140c64346549f3c6f1860711d16b6b12b206c15 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 16 Aug 2018 21:19:31 -0700 Subject: [PATCH 3/8] Use new RME callback to reduce amount of document serialization Account for new RME autoFocus prop in 5.0 --- app/scenes/Document/Document.js | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/app/scenes/Document/Document.js b/app/scenes/Document/Document.js index 977fa2e49..1686205cb 100644 --- a/app/scenes/Document/Document.js +++ b/app/scenes/Document/Document.js @@ -61,6 +61,7 @@ type Props = { class DocumentScene extends React.Component { savedTimeout: TimeoutID; viewTimeout: TimeoutID; + getEditorText: () => string; @observable editorComponent; @observable editCache: ?string; @@ -163,7 +164,19 @@ class DocumentScene extends React.Component { options: { done?: boolean, publish?: boolean, autosave?: boolean } = {} ) => { let document = this.document; - if (!document || !document.allowSave) return; + if (!document) return; + + // get the latest version of the editor text value + const text = this.getEditorText(); + + // prevent autosave if nothing has changed + if (options.autosave && document.text.trim() === text.trim()) return; + + document.updateData({ text }, true); + if (!document.allowSave) return; + + // prevent autosave before anything has been written + if (options.autosave && !document.title && !document.id) return; let isNew = !document.id; this.editCache = null; @@ -182,7 +195,7 @@ class DocumentScene extends React.Component { } }; - autosave = debounce(async () => { + autosave = debounce(() => { this.onSave({ done: false, autosave: true }); }, AUTOSAVE_INTERVAL); @@ -194,18 +207,10 @@ class DocumentScene extends React.Component { this.isUploading = false; }; - onChange = debounce(getText => { - let document = this.document; - if (!document) return; - - const text = getText(); - if (document.text.trim() === text.trim()) return; - document.updateData({ text }, true); - - // prevent autosave before anything has been written - if (!document.title && !document.id) return; + onChange = getEditorText => { + this.getEditorText = getEditorText; this.autosave(); - }, 250); + }; onDiscard = () => { let url; @@ -342,6 +347,7 @@ class DocumentScene extends React.Component { onCancel={this.onDiscard} onShowToast={this.onShowToast} readOnly={!this.isEditing} + autoFocus={!document.text} toc /> From be09b290b7754d949a0ff0ee5aceed99d0956b50 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 16 Aug 2018 22:51:51 -0700 Subject: [PATCH 4/8] RME upgrade, fix autoFocus logic --- app/scenes/Document/Document.js | 1 - app/scenes/Document/components/Editor.js | 10 ++++++++++ package.json | 2 +- yarn.lock | 6 +++--- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/scenes/Document/Document.js b/app/scenes/Document/Document.js index 1686205cb..a11f43121 100644 --- a/app/scenes/Document/Document.js +++ b/app/scenes/Document/Document.js @@ -347,7 +347,6 @@ class DocumentScene extends React.Component { onCancel={this.onDiscard} onShowToast={this.onShowToast} readOnly={!this.isEditing} - autoFocus={!document.text} toc /> diff --git a/app/scenes/Document/components/Editor.js b/app/scenes/Document/components/Editor.js index c45fc6040..cb11a2218 100644 --- a/app/scenes/Document/components/Editor.js +++ b/app/scenes/Document/components/Editor.js @@ -41,10 +41,20 @@ schema.document.normalize = ( class Editor extends React.Component { editor: *; + componentDidMount() { + if (!this.props.defaultValue) { + this.focusAtStart(); + } + } + setEditorRef = (ref: RichMarkdownEditor) => { this.editor = ref; }; + focusAtStart = () => { + if (this.editor) this.editor.focusAtStart(); + }; + focusAtEnd = () => { if (this.editor) this.editor.focusAtEnd(); }; diff --git a/package.json b/package.json index 3ea7caf86..3d47ccc10 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "react-waypoint": "^7.3.1", "redis": "^2.6.2", "redis-lock": "^0.1.0", - "rich-markdown-editor": "4.0.0", + "rich-markdown-editor": "5.0.0", "safestart": "1.1.0", "sequelize": "4.28.6", "sequelize-cli": "^2.7.0", diff --git a/yarn.lock b/yarn.lock index f05eceae9..580eee2ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8858,9 +8858,9 @@ retry-axios@0.3.2, retry-axios@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/retry-axios/-/retry-axios-0.3.2.tgz#5757c80f585b4cc4c4986aa2ffd47a60c6d35e13" -rich-markdown-editor@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-4.0.0.tgz#483f2b24867553245b66a8c8f56ec3f8d70ae248" +rich-markdown-editor@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-5.0.0.tgz#7c581f5ec3a52f749bc7633ea46b9f3d5b113935" dependencies: "@tommoor/slate-drop-or-paste-images" "^0.8.1" babel-plugin-transform-async-to-generator "^6.24.1" From e58da006d0e276ac93c2dfa37c345737df622910 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 16 Aug 2018 22:53:58 -0700 Subject: [PATCH 5/8] :shirt: --- app/scenes/Document/Document.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scenes/Document/Document.js b/app/scenes/Document/Document.js index a11f43121..9b62fcb4f 100644 --- a/app/scenes/Document/Document.js +++ b/app/scenes/Document/Document.js @@ -165,7 +165,7 @@ class DocumentScene extends React.Component { ) => { let document = this.document; if (!document) return; - + // get the latest version of the editor text value const text = this.getEditorText(); From 3da078e98bf241079acab5e27e58ff04eb920f0c Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 18 Aug 2018 17:41:09 -0700 Subject: [PATCH 6/8] Move isDirty to view concern. Document model is not updated until save --- app/models/Collection.js | 2 -- app/models/Document.js | 5 +---- app/scenes/Document/Document.js | 13 +++++++++++-- app/scenes/Document/components/Header.js | 9 ++------- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/app/models/Collection.js b/app/models/Collection.js index 91881f2e2..625bb87ba 100644 --- a/app/models/Collection.js +++ b/app/models/Collection.js @@ -11,7 +11,6 @@ import type { NavigationNode } from 'types'; class Collection extends BaseModel { isSaving: boolean = false; - hasPendingChanges: boolean = false; ui: UiStore; data: Object; @@ -109,7 +108,6 @@ class Collection extends BaseModel { runInAction('Collection#save', () => { invariant(res && res.data, 'Data should be available'); this.updateData(res.data); - this.hasPendingChanges = false; }); } catch (e) { this.ui.showToast('Collection failed saving'); diff --git a/app/models/Document.js b/app/models/Document.js index 883effcf2..d45601ccf 100644 --- a/app/models/Document.js +++ b/app/models/Document.js @@ -15,7 +15,6 @@ type SaveOptions = { publish?: boolean, done?: boolean, autosave?: boolean }; class Document extends BaseModel { isSaving: boolean = false; - hasPendingChanges: boolean = false; ui: *; store: *; @@ -209,7 +208,6 @@ class Document extends BaseModel { runInAction('Document#save', () => { invariant(res && res.data, 'Data should be available'); this.updateData(res.data); - this.hasPendingChanges = false; if (isCreating) { this.emit('documents.create', this); @@ -288,13 +286,12 @@ class Document extends BaseModel { a.click(); }; - updateData(data: Object = {}, dirty: boolean = false) { + updateData(data: Object = {}) { if (data.text) { const { title, emoji } = parseTitle(data.text); data.title = title; data.emoji = emoji; } - if (dirty) this.hasPendingChanges = true; extendObservable(this, data); } diff --git a/app/scenes/Document/Document.js b/app/scenes/Document/Document.js index 9b62fcb4f..8bfc29995 100644 --- a/app/scenes/Document/Document.js +++ b/app/scenes/Document/Document.js @@ -70,6 +70,7 @@ class DocumentScene extends React.Component { @observable isUploading = false; @observable isSaving = false; @observable isPublishing = false; + @observable isDirty = false; @observable notFound = false; @observable moveModalOpen: boolean = false; @@ -117,6 +118,7 @@ class DocumentScene extends React.Component { props.match.params.documentSlug, { shareId } ); + this.isDirty = false; const document = this.document; @@ -167,7 +169,7 @@ class DocumentScene extends React.Component { if (!document) return; // get the latest version of the editor text value - const text = this.getEditorText(); + const text = this.getEditorText ? this.getEditorText() : document.text; // prevent autosave if nothing has changed if (options.autosave && document.text.trim() === text.trim()) return; @@ -183,6 +185,7 @@ class DocumentScene extends React.Component { this.isSaving = true; this.isPublishing = !!options.publish; document = await document.save(options); + this.isDirty = false; this.isSaving = false; this.isPublishing = false; @@ -199,6 +202,10 @@ class DocumentScene extends React.Component { this.onSave({ done: false, autosave: true }); }, AUTOSAVE_INTERVAL); + updateIsDirty = debounce(() => { + this.isDirty = this.getEditorText().trim() !== this.document.text.trim(); + }, 500); + onImageUploadStart = () => { this.isUploading = true; }; @@ -209,6 +216,7 @@ class DocumentScene extends React.Component { onChange = getEditorText => { this.getEditorText = getEditorText; + this.updateIsDirty(); this.autosave(); }; @@ -312,7 +320,7 @@ class DocumentScene extends React.Component { {this.isEditing && ( @@ -322,6 +330,7 @@ class DocumentScene extends React.Component {
{ document, isEditing, isDraft, + isDirty, isPublishing, isSaving, savingIsDisabled, @@ -161,13 +163,6 @@ class Header extends React.Component { Edit )} - {isEditing && - !isSaving && - document.hasPendingChanges && ( - - Discard - - )} {!isEditing && ( From f401b6f2e076331e156b6813c92948d9e78ef503 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 18 Aug 2018 17:45:56 -0700 Subject: [PATCH 7/8] Tidying, possible fix for excessive view count --- app/scenes/Document/Document.js | 17 ++++++----------- app/scenes/Document/components/Header.js | 2 -- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/app/scenes/Document/Document.js b/app/scenes/Document/Document.js index 8bfc29995..4d6d725db 100644 --- a/app/scenes/Document/Document.js +++ b/app/scenes/Document/Document.js @@ -36,7 +36,8 @@ import Search from 'scenes/Search'; import Error404 from 'scenes/Error404'; import ErrorOffline from 'scenes/ErrorOffline'; -const AUTOSAVE_INTERVAL = 3000; +const AUTOSAVE_DELAY = 3000; +const IS_DIRTY_DELAY = 500; const MARK_AS_VIEWED_AFTER = 3000; const DISCARD_CHANGES = ` You have unsaved changes. @@ -59,7 +60,6 @@ type Props = { @observer class DocumentScene extends React.Component { - savedTimeout: TimeoutID; viewTimeout: TimeoutID; getEditorText: () => string; @@ -85,14 +85,13 @@ class DocumentScene extends React.Component { this.props.match.params.documentSlug ) { this.notFound = false; + clearTimeout(this.viewTimeout); this.loadDocument(nextProps); } } componentWillUnmount() { - clearTimeout(this.savedTimeout); clearTimeout(this.viewTimeout); - this.props.ui.clearActiveDocument(); } @@ -200,11 +199,11 @@ class DocumentScene extends React.Component { autosave = debounce(() => { this.onSave({ done: false, autosave: true }); - }, AUTOSAVE_INTERVAL); + }, AUTOSAVE_DELAY); updateIsDirty = debounce(() => { this.isDirty = this.getEditorText().trim() !== this.document.text.trim(); - }, 500); + }, IS_DIRTY_DELAY); onImageUploadStart = () => { this.isUploading = true; @@ -319,10 +318,7 @@ class DocumentScene extends React.Component { {this.isEditing && ( - + )} @@ -330,7 +326,6 @@ class DocumentScene extends React.Component {
{ document, isEditing, isDraft, - isDirty, isPublishing, isSaving, savingIsDisabled, From 61872373ece623538885763a9cda857a21e4f459 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 18 Aug 2018 17:48:56 -0700 Subject: [PATCH 8/8] Remove nolonger neccessary editCache --- app/scenes/Document/Document.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/scenes/Document/Document.js b/app/scenes/Document/Document.js index 4d6d725db..684f03105 100644 --- a/app/scenes/Document/Document.js +++ b/app/scenes/Document/Document.js @@ -64,7 +64,6 @@ class DocumentScene extends React.Component { getEditorText: () => string; @observable editorComponent; - @observable editCache: ?string; @observable document: ?Document; @observable newDocument: ?Document; @observable isUploading = false; @@ -124,9 +123,6 @@ class DocumentScene extends React.Component { if (document) { this.props.ui.setActiveDocument(document); - // Cache data if user enters edit mode and cancels - this.editCache = document.text; - if (this.props.auth.user && !shareId) { if (!this.isEditing && document.publishedAt) { this.viewTimeout = setTimeout(document.view, MARK_AS_VIEWED_AFTER); @@ -180,7 +176,6 @@ class DocumentScene extends React.Component { if (options.autosave && !document.title && !document.id) return; let isNew = !document.id; - this.editCache = null; this.isSaving = true; this.isPublishing = !!options.publish; document = await document.save(options); @@ -223,7 +218,6 @@ class DocumentScene extends React.Component { let url; if (this.document && this.document.url) { url = this.document.url; - if (this.editCache) this.document.updateData({ text: this.editCache }); } else { url = collectionUrl(this.props.match.params.id); }