fix: Improved handling of simultaneous edits
This commit is contained in:
@ -128,22 +128,6 @@ class SocketProvider extends React.Component<Props> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this to the document scene once data loading
|
|
||||||
// has been refactored to be friendlier there.
|
|
||||||
if (
|
|
||||||
auth.user &&
|
|
||||||
documentId === ui.activeDocumentId &&
|
|
||||||
document.updatedBy.id !== auth.user.id
|
|
||||||
) {
|
|
||||||
ui.showToast(`Document updated by ${document.updatedBy.name}`, {
|
|
||||||
timeout: 30 * 1000,
|
|
||||||
action: {
|
|
||||||
text: 'Refresh',
|
|
||||||
onClick: () => window.location.reload(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,12 @@ import Revision from 'models/Revision';
|
|||||||
import User from 'models/User';
|
import User from 'models/User';
|
||||||
import DocumentsStore from 'stores/DocumentsStore';
|
import DocumentsStore from 'stores/DocumentsStore';
|
||||||
|
|
||||||
type SaveOptions = { publish?: boolean, done?: boolean, autosave?: boolean };
|
type SaveOptions = {
|
||||||
|
publish?: boolean,
|
||||||
|
done?: boolean,
|
||||||
|
autosave?: boolean,
|
||||||
|
lastRevision?: number,
|
||||||
|
};
|
||||||
|
|
||||||
export default class Document extends BaseModel {
|
export default class Document extends BaseModel {
|
||||||
@observable isSaving: boolean = false;
|
@observable isSaving: boolean = false;
|
||||||
@ -185,7 +190,7 @@ export default class Document extends BaseModel {
|
|||||||
id: this.id,
|
id: this.id,
|
||||||
title: this.title,
|
title: this.title,
|
||||||
text: this.text,
|
text: this.text,
|
||||||
lastRevision: this.revision,
|
lastRevision: options.lastRevision,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
documentMoveUrl,
|
documentMoveUrl,
|
||||||
documentHistoryUrl,
|
documentHistoryUrl,
|
||||||
documentEditUrl,
|
documentEditUrl,
|
||||||
|
documentUrl,
|
||||||
} from 'utils/routeHelpers';
|
} from 'utils/routeHelpers';
|
||||||
import { emojiToUrl } from 'utils/emoji';
|
import { emojiToUrl } from 'utils/emoji';
|
||||||
|
|
||||||
@ -74,11 +75,13 @@ class DocumentScene extends React.Component<Props> {
|
|||||||
@observable isDirty: boolean = false;
|
@observable isDirty: boolean = false;
|
||||||
@observable isEmpty: boolean = true;
|
@observable isEmpty: boolean = true;
|
||||||
@observable moveModalOpen: boolean = false;
|
@observable moveModalOpen: boolean = false;
|
||||||
|
@observable lastRevision: number;
|
||||||
@observable title: string;
|
@observable title: string;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super();
|
super();
|
||||||
this.title = props.document.title;
|
this.title = props.document.title;
|
||||||
|
this.lastRevision = props.document.revision;
|
||||||
this.loadEditor();
|
this.loadEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +89,29 @@ class DocumentScene extends React.Component<Props> {
|
|||||||
this.updateIsDirty();
|
this.updateIsDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const { auth, document } = this.props;
|
||||||
|
|
||||||
|
if (this.props.readOnly) {
|
||||||
|
this.lastRevision = document.revision;
|
||||||
|
} else if (prevProps.document.revision !== this.lastRevision) {
|
||||||
|
if (auth.user && document.updatedBy.id !== auth.user.id) {
|
||||||
|
this.props.ui.showToast(
|
||||||
|
`Document updated by ${document.updatedBy.name}`,
|
||||||
|
{
|
||||||
|
timeout: 30 * 1000,
|
||||||
|
action: {
|
||||||
|
text: 'Reload',
|
||||||
|
onClick: () => {
|
||||||
|
window.location.href = documentUrl(document);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keydown('m')
|
@keydown('m')
|
||||||
goToMove(ev) {
|
goToMove(ev) {
|
||||||
if (!this.props.readOnly) return;
|
if (!this.props.readOnly) return;
|
||||||
@ -174,7 +200,11 @@ class DocumentScene extends React.Component<Props> {
|
|||||||
handleOpenMoveModal = () => (this.moveModalOpen = true);
|
handleOpenMoveModal = () => (this.moveModalOpen = true);
|
||||||
|
|
||||||
onSave = async (
|
onSave = async (
|
||||||
options: { done?: boolean, publish?: boolean, autosave?: boolean } = {}
|
options: {
|
||||||
|
done?: boolean,
|
||||||
|
publish?: boolean,
|
||||||
|
autosave?: boolean,
|
||||||
|
} = {}
|
||||||
) => {
|
) => {
|
||||||
const { document } = this.props;
|
const { document } = this.props;
|
||||||
|
|
||||||
@ -202,10 +232,14 @@ class DocumentScene extends React.Component<Props> {
|
|||||||
let isNew = !document.id;
|
let isNew = !document.id;
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
this.isPublishing = !!options.publish;
|
this.isPublishing = !!options.publish;
|
||||||
const savedDocument = await document.save(options);
|
|
||||||
|
try {
|
||||||
|
const savedDocument = await document.save({
|
||||||
|
...options,
|
||||||
|
lastRevision: this.lastRevision,
|
||||||
|
});
|
||||||
this.isDirty = false;
|
this.isDirty = false;
|
||||||
this.isSaving = false;
|
this.lastRevision = savedDocument.revision;
|
||||||
this.isPublishing = false;
|
|
||||||
|
|
||||||
if (options.done) {
|
if (options.done) {
|
||||||
this.props.history.push(savedDocument.url);
|
this.props.history.push(savedDocument.url);
|
||||||
@ -214,6 +248,12 @@ class DocumentScene extends React.Component<Props> {
|
|||||||
this.props.history.push(documentEditUrl(savedDocument));
|
this.props.history.push(documentEditUrl(savedDocument));
|
||||||
this.props.ui.setActiveDocument(savedDocument);
|
this.props.ui.setActiveDocument(savedDocument);
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.props.ui.showToast(err.message);
|
||||||
|
} finally {
|
||||||
|
this.isSaving = false;
|
||||||
|
this.isPublishing = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
autosave = debounce(() => {
|
autosave = debounce(() => {
|
||||||
|
Reference in New Issue
Block a user