diff --git a/cypress/e2e/conflict.spec.js b/cypress/e2e/conflict.spec.js index 1396f6f9bef..cf1e7484a05 100644 --- a/cypress/e2e/conflict.spec.js +++ b/cypress/e2e/conflict.spec.js @@ -32,7 +32,7 @@ const variants = [ variants.forEach(function({ fixture, mime }) { const fileName = fixture describe(`${mime} (${fileName})`, function() { - const getWrapper = () => cy.get('.viewer__content .text-editor__wrapper.has-conflicts') + const getWrapper = () => cy.get('.text-editor__wrapper.has-conflicts') before(() => { initUserAndFiles(user, fileName) @@ -40,20 +40,35 @@ variants.forEach(function({ fixture, mime }) { beforeEach(function() { cy.login(user) - cy.visit('/apps/files') }) - it('displays conflicts', function() { - cy.openFile(fileName) - - cy.log('Inspect editor') - cy.getContent() - .type('Hello you cruel conflicting world') + it('no actual conflict - just reload', function() { + // start with different content + cy.uploadFile('frontmatter.md', mime, fileName) + // just a read only session opened + cy.shareFile(`/${fileName}`) + .then(token => cy.visit(`/s/${token}`)) + cy.getContent().should('contain', 'Heading') + cy.intercept({ method: 'POST', url: '**/session/*/push' }) + .as('push') + cy.wait('@push') cy.uploadFile(fileName, mime) + cy.get('#editor-container .document-status', { timeout: 30000 }) + .should('contain', 'session has expired') + // Reload button works + cy.get('#editor-container .document-status a.button') + .contains('Reload') + .click() + getWrapper().should('not.exist') + cy.getContent().should('contain', 'Hello world') + cy.getContent().should('not.contain', 'Heading') + }) + + it('displays conflicts', function() { + createConflict(fileName, mime) - cy.get('#viewer .modal-header button.header-close').click() - cy.get('#viewer').should('not.exist') cy.openFile(fileName) + cy.get('.text-editor .document-status') .should('contain', 'Document has been changed outside of the editor.') getWrapper() @@ -68,57 +83,55 @@ variants.forEach(function({ fixture, mime }) { }) it('resolves conflict using current editing session', function() { - cy.openFile(fileName) - - cy.log('Inspect editor') - cy.getContent() - .type('Hello you cruel conflicting world') - cy.uploadFile(fileName, mime) + createConflict(fileName, mime) - cy.get('#viewer .modal-header button.header-close').click() - cy.get('#viewer').should('not.exist') cy.openFile(fileName) - cy.get('[data-cy="resolveThisVersion"]').click() - getWrapper() - .should('not.exist') - + getWrapper().should('not.exist') cy.get('[data-cy="resolveThisVersion"]') .should('not.exist') - - cy.get('.text-editor__main') - .should('contain', 'Hello world') - cy.get('.text-editor__main') - .should('contain', 'cruel conflicting') + cy.getContent().should('contain', 'Hello world') + cy.getContent().should('contain', 'cruel conflicting') }) it('resolves conflict using server version', function() { - cy.openFile(fileName) - - cy.log('Inspect editor') - cy.getContent() - .type('Hello you cruel conflicting world') - cy.uploadFile(fileName, mime) + createConflict(fileName, mime) - cy.get('#viewer .modal-header button.header-close').click() - cy.get('#viewer').should('not.exist') cy.openFile(fileName) - cy.get('[data-cy="resolveServerVersion"]') .click() - getWrapper() - .should('not.exist') + getWrapper().should('not.exist') cy.get('[data-cy="resolveThisVersion"]') .should('not.exist') cy.get('[data-cy="resolveServerVersion"]') .should('not.exist') + cy.getContent().should('contain', 'Hello world') + cy.getContent().should('not.contain', 'cruel conflicting') + }) - cy.get('.text-editor__main') - .should('contain', 'Hello world') - cy.get('.text-editor__main') - .should('not.contain', 'cruel conflicting') + it('hides conflict in read only session', function() { + createConflict(fileName, mime) + cy.shareFile(`/${fileName}`) + .then((token) => { + cy.logout() + cy.visit(`/s/${token}`) + }) + cy.getContent().should('contain', 'cruel conflicting') + getWrapper().should('not.exist') }) + }) }) + +function createConflict(fileName, mime) { + cy.visit('/apps/files') + cy.openFile(fileName) + cy.log('Inspect editor') + cy.getContent() + .type('Hello you cruel conflicting world') + cy.uploadFile(fileName, mime) + cy.get('#viewer .modal-header button.header-close').click() + cy.get('#viewer').should('not.exist') +} diff --git a/lib/Listeners/BeforeNodeDeletedListener.php b/lib/Listeners/BeforeNodeDeletedListener.php index a1e0cc0864b..e913b535564 100644 --- a/lib/Listeners/BeforeNodeDeletedListener.php +++ b/lib/Listeners/BeforeNodeDeletedListener.php @@ -50,9 +50,12 @@ public function handle(Event $event): void { return; } $node = $event->getNode(); - if ($node instanceof File && $node->getMimeType() === 'text/markdown') { + if (!$node instanceof File) { + return; + } + if ($node->getMimeType() === 'text/markdown') { $this->attachmentService->deleteAttachments($node); - $this->documentService->resetDocument($node->getId(), true); } + $this->documentService->resetDocument($node->getId(), true); } } diff --git a/lib/Listeners/BeforeNodeWrittenListener.php b/lib/Listeners/BeforeNodeWrittenListener.php index 73b85a3c970..950703b7280 100644 --- a/lib/Listeners/BeforeNodeWrittenListener.php +++ b/lib/Listeners/BeforeNodeWrittenListener.php @@ -31,7 +31,6 @@ use OCP\EventDispatcher\IEventListener; use OCP\Files\Events\Node\BeforeNodeWrittenEvent; use OCP\Files\File; -use OCP\Files\NotFoundException; /** * @template-implements IEventListener @@ -48,19 +47,17 @@ public function handle(Event $event): void { return; } $node = $event->getNode(); + if (!$node instanceof File) { + return; + } + if ($this->documentService->isSaveFromText()) { + return; + } + // Reset document session to avoid manual conflict resolution if there's no unsaved steps try { - if ($node instanceof File && $node->getMimeType() === 'text/markdown') { - if (!$this->documentService->isSaveFromText()) { - // Reset document session to avoid manual conflict resolution if there's no unsaved steps - try { - $this->documentService->resetDocument($node->getId()); - } catch (DocumentHasUnsavedChangesException) { - // Do not throw during event handling in this is expected to happen - } - } - } - } catch (NotFoundException) { - // This might occur on new files when a NonExistingFile is passed and we cannot access the mimetype + $this->documentService->resetDocument($node->getId()); + } catch (DocumentHasUnsavedChangesException) { + // Do not throw during event handling in this is expected to happen } } } diff --git a/src/components/Editor.vue b/src/components/Editor.vue index 8fadb7cb891..5157ddde4e6 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -29,13 +29,14 @@ - @@ -258,6 +259,9 @@ export default { isRichWorkspace() { return this.richWorkspace }, + isResolvingConflict() { + return this.hasSyncCollission && !this.readOnly + }, hasSyncCollission() { return this.syncError && this.syncError.type === ERROR_TYPE.SAVE_COLLISSION }, diff --git a/src/components/Editor/DocumentStatus.vue b/src/components/Editor/DocumentStatus.vue index 0061099826c..c271e83b2b3 100644 --- a/src/components/Editor/DocumentStatus.vue +++ b/src/components/Editor/DocumentStatus.vue @@ -51,7 +51,7 @@

- + @@ -89,6 +89,10 @@ export default { type: Boolean, require: true, }, + isResolvingConflict: { + type: Boolean, + require: true, + }, }, data() { diff --git a/src/components/Editor/Wrapper.vue b/src/components/Editor/Wrapper.vue index 7e717f1a670..803fd11c5dd 100644 --- a/src/components/Editor/Wrapper.vue +++ b/src/components/Editor/Wrapper.vue @@ -23,7 +23,7 @@