From 02f48be8256606c63dc4275713cc56a39d32aa8d Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 5 Jun 2014 16:18:25 +0100 Subject: [PATCH] Allow docs to be restored --- .../Editor/EditorHttpController.coffee | 21 +++++++++ .../Project/ProjectEntityHandler.coffee | 7 +++ services/web/app/coffee/router.coffee | 3 ++ services/web/app/views/templates.jade | 3 ++ .../public/coffee/file-tree/EntityView.coffee | 1 + .../coffee/file-tree/FileTreeManager.coffee | 11 ++++- .../coffee/track-changes/DiffView.coffee | 13 +++++- .../track-changes/TrackChangesManager.coffee | 26 ++++++++++- .../public/stylesheets/less/trackchanges.less | 9 +++- .../Editor/EditorHttpControllerTests.coffee | 46 +++++++++++++++++++ .../Project/ProjectEntityHandlerTests.coffee | 25 ++++++++++ 11 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 services/web/app/coffee/Features/Editor/EditorHttpController.coffee create mode 100644 services/web/test/UnitTests/coffee/Editor/EditorHttpControllerTests.coffee diff --git a/services/web/app/coffee/Features/Editor/EditorHttpController.coffee b/services/web/app/coffee/Features/Editor/EditorHttpController.coffee new file mode 100644 index 0000000000..57b65d7355 --- /dev/null +++ b/services/web/app/coffee/Features/Editor/EditorHttpController.coffee @@ -0,0 +1,21 @@ +ProjectEntityHandler = require "../Project/ProjectEntityHandler" +logger = require "logger-sharelatex" +EditorRealTimeController = require "./EditorRealTimeController" + +module.exports = EditorHttpController = + restoreDoc: (req, res, next) -> + project_id = req.params.Project_id + doc_id = req.params.doc_id + name = req.body.name + + if !name? + return res.send 400 # Malformed request + + logger.log project_id: project_id, doc_id: doc_id, "restoring doc" + ProjectEntityHandler.restoreDoc project_id, doc_id, name, (err, doc, folder_id) => + return next(error) if error? + EditorRealTimeController.emitToRoom(project_id, 'reciveNewDoc', folder_id, doc) + res.json { + doc_id: doc._id + } + diff --git a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee index 355bb298ec..ac9119d748 100644 --- a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee @@ -135,6 +135,13 @@ module.exports = ProjectEntityHandler = return callback(err) if err? callback(null, doc, folder_id) + restoreDoc: (project_id, doc_id, name, callback = (error, doc, folder_id) ->) -> + # getDoc will return the deleted doc's lines, but we don't actually remove + # the deleted doc, just create a new one from its lines. + ProjectEntityHandler.getDoc project_id, doc_id, (error, lines) -> + return callback(error) if error? + ProjectEntityHandler.addDoc project_id, null, name, lines, callback + addFile: (project_or_id, folder_id, fileName, path, sl_req_id, callback = (error, fileRef, folder_id) ->)-> {callback, sl_req_id} = slReqIdHelper.getCallbackAndReqId(callback, sl_req_id) Project.getProject project_or_id, "", (err, project) -> diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index cae1e9e4c5..aa7d19c1c7 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -7,6 +7,7 @@ SpellingController = require('./Features/Spelling/SpellingController') SecurityManager = require('./managers/SecurityManager') AuthorizationManager = require('./Features/Security/AuthorizationManager') EditorController = require("./Features/Editor/EditorController") +EditorHttpController = require("./Features/Editor/EditorHttpController") EditorUpdatesController = require("./Features/Editor/EditorUpdatesController") Settings = require('settings-sharelatex') TpdsController = require('./Features/ThirdPartyDataStore/TpdsController') @@ -125,6 +126,8 @@ module.exports = class Router app.get "/project/:Project_id/doc/:doc_id/diff", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi app.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi + app.post "/project/:Project_id/doc/:doc_id/restore", SecurityManager.requestCanAccessProject, EditorHttpController.restoreDoc + app.post '/project/:project_id/leave', AuthenticationController.requireLogin(), CollaboratorsController.removeSelfFromProject app.get '/project/:Project_id/collaborators', SecurityManager.requestCanAccessProject(allow_auth_token: true), CollaboratorsController.getCollaborators diff --git a/services/web/app/views/templates.jade b/services/web/app/views/templates.jade index 9e04573b65..81f3d12dc5 100644 --- a/services/web/app/views/templates.jade +++ b/services/web/app/views/templates.jade @@ -472,6 +472,9 @@ .track-changes-diff-toolbar.btn-toolbar .number-of-changes {{ changes }} in {{ name }} a(href="#").restore.btn.btn-small.btn-danger Restore to before these changes + .deleted-info(style="display:none;") + span This file has been deleted + a(href="#").restore-deleted.btn.btn-small.btn-success Restore .track-changes-diff-editor script(type='text/template')#changeListItemTemplate diff --git a/services/web/public/coffee/file-tree/EntityView.coffee b/services/web/public/coffee/file-tree/EntityView.coffee index b00c0cf017..913d076cc5 100644 --- a/services/web/public/coffee/file-tree/EntityView.coffee +++ b/services/web/public/coffee/file-tree/EntityView.coffee @@ -9,6 +9,7 @@ define [ initialize: () -> @ide = @options.manager.ide @manager = @options.manager + console.log "Registering view", @model, @model.id, @ @manager.registerView(@model.id, @) @bindToModel() diff --git a/services/web/public/coffee/file-tree/FileTreeManager.coffee b/services/web/public/coffee/file-tree/FileTreeManager.coffee index 19cf2f2382..4e8470f45b 100644 --- a/services/web/public/coffee/file-tree/FileTreeManager.coffee +++ b/services/web/public/coffee/file-tree/FileTreeManager.coffee @@ -38,6 +38,9 @@ define [ populateFileTree: () -> @view.bindToRootFolder(@project.get("rootFolder")) + + if @deletedDocsView? + @deletedDocsView.$el.remove() @deletedDocsView = new FolderView(model: @project.get("deletedDocs"), manager: @) @deletedDocsView.render() $("#sections").append(@deletedDocsView.$el) @@ -72,6 +75,7 @@ define [ @onMoveEntity(entity_id, folder_id) registerView: (entity_id, view) -> + console.log "inside", entity_id, view @views[entity_id] = view addEntityToFolder: (entity, folder_id) -> @@ -284,8 +288,6 @@ define [ _doDelete: (entity) -> @ide.socket.emit 'deleteEntity', entity.id, entity.get("type") - if entity.get("type") == "doc" - @project.get("deletedDocs").get("children").add entity @onDeleteEntity entity.id onDeleteEntity: (entity_id) -> @@ -294,6 +296,11 @@ define [ entity.set("deleted", true) entity.collection?.remove(entity) delete @views[entity_id] + + # Do this after the remove so that it's never in two places at once + # and so that it doesn't get reset by deleting from @views + if entity.get("type") == "doc" + @project.get("deletedDocs").get("children").add entity setLabels: (labels) -> @view.setLabels(labels) diff --git a/services/web/public/coffee/track-changes/DiffView.coffee b/services/web/public/coffee/track-changes/DiffView.coffee index 0ac6002e8c..58e444f72f 100644 --- a/services/web/public/coffee/track-changes/DiffView.coffee +++ b/services/web/public/coffee/track-changes/DiffView.coffee @@ -10,9 +10,14 @@ define [ template: $("#trackChangesDiffTemplate").html() events: - "click .restore": () -> - console.log "click" + "click .restore": (e) -> + e.preventDefault() @trigger "restore" + "click .restore-deleted": (e) -> + e.preventDefault() + @$("a.restore-deleted").attr("disabled", true) + @$("a.restore-deleted").text("Restoring...") + @trigger "restore-deleted" initialize: () -> @model.on "change:diff", () => @render() @@ -31,6 +36,10 @@ define [ if !@model.get("from")? or !@model.get("to")? or changes == 0 @$(".restore").hide() + if @model.get("doc").get("deleted") + @$(".restore").hide() + @$(".deleted-info").show() + @createAceEditor() @aceEditor.setValue(@getPlainDiffContent()) @aceEditor.clearSelection() diff --git a/services/web/public/coffee/track-changes/TrackChangesManager.coffee b/services/web/public/coffee/track-changes/TrackChangesManager.coffee index 4d146c923b..63cbaa871e 100644 --- a/services/web/public/coffee/track-changes/TrackChangesManager.coffee +++ b/services/web/public/coffee/track-changes/TrackChangesManager.coffee @@ -164,7 +164,7 @@ define [ @diffView.remove() if !@diff.get("doc")? - console.log "This document has been deleted. What should we do?" + console.log "This document does not exist. What should we do?" return @diffView = new DiffView( @@ -175,6 +175,15 @@ define [ @diffView.on "restore", () => @restoreDiff(@diff) + @diffView.on "restore-deleted", () => + @restoreDeletedDoc @diff.get("doc"), (error, doc_id) => + return if error? or !doc_id? + setTimeout () => + # Give doc a chance to appear in file tree via socket.io + @hide() + @ide.fileTreeManager.openDoc(doc_id) + , 1000 + @diff.fetch() @ide.fileTreeManager.selectEntity(@doc_id) @@ -221,6 +230,21 @@ define [ }] }) + restoreDeletedDoc: (doc, callback) -> + $.ajax { + url: "/project/#{@project_id}/doc/#{doc.get("id")}/restore" + type: "POST" + dataType: "json" + data: + name: doc.get("name") + headers: + "X-CSRF-Token": window.csrfToken + success: (body, status, response) -> + callback(null, body?.doc_id) + error: (error) -> + callback(error) + } + enable: () -> @enabled = true diff --git a/services/web/public/stylesheets/less/trackchanges.less b/services/web/public/stylesheets/less/trackchanges.less index f9f12bbcc8..963e58fb16 100644 --- a/services/web/public/stylesheets/less/trackchanges.less +++ b/services/web/public/stylesheets/less/trackchanges.less @@ -30,7 +30,7 @@ background-color: #282828; color: white; border-right: 1px solid white; - .number-of-changes, .restore { + .number-of-changes, .restore, .deleted-info { position: absolute; } .number-of-changes { @@ -42,6 +42,13 @@ bottom: 5px; padding: 3px 9px; } + .deleted-info { + right: 10px; + bottom: 5px; + a { + padding: 3px 9px; + } + } } } diff --git a/services/web/test/UnitTests/coffee/Editor/EditorHttpControllerTests.coffee b/services/web/test/UnitTests/coffee/Editor/EditorHttpControllerTests.coffee new file mode 100644 index 0000000000..706476a6c4 --- /dev/null +++ b/services/web/test/UnitTests/coffee/Editor/EditorHttpControllerTests.coffee @@ -0,0 +1,46 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../../app/js/Features/Editor/EditorHttpController' + +describe "EditorHttpController", -> + beforeEach -> + @EditorHttpController = SandboxedModule.require modulePath, requires: + '../Project/ProjectEntityHandler' : @ProjectEntityHandler = {} + "./EditorRealTimeController": @EditorRealTimeController = {} + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + @project_id = "mock-project-id" + @doc_id = "mock-doc-id" + @req = {} + @res = + send: sinon.stub() + json: sinon.stub() + + describe "restoreDoc", -> + beforeEach -> + @req.params = + Project_id: @project_id + doc_id: @doc_id + @req.body = + name: @name = "doc-name" + @ProjectEntityHandler.restoreDoc = sinon.stub().callsArgWith(3, null, + @doc = { "mock": "doc", _id: @new_doc_id = "new-doc-id" } + @folder_id = "mock-folder-id" + ) + @EditorRealTimeController.emitToRoom = sinon.stub() + @EditorHttpController.restoreDoc @req, @res + + it "should restore the doc", -> + @ProjectEntityHandler.restoreDoc + .calledWith(@project_id, @doc_id, @name) + .should.equal true + + it "should the real-time clients about the new doc", -> + @EditorRealTimeController.emitToRoom + .calledWith(@project_id, 'reciveNewDoc', @folder_id, @doc) + .should.equal true + + it "should return the new doc id", -> + @res.json + .calledWith(doc_id: @new_doc_id) + .should.equal true diff --git a/services/web/test/UnitTests/coffee/Project/ProjectEntityHandlerTests.coffee b/services/web/test/UnitTests/coffee/Project/ProjectEntityHandlerTests.coffee index a66a8f9884..5145e4d64a 100644 --- a/services/web/test/UnitTests/coffee/Project/ProjectEntityHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/Project/ProjectEntityHandlerTests.coffee @@ -339,6 +339,31 @@ describe 'ProjectEntityHandler', -> .calledWith(project_id, @doc._id.toString(), @lines) .should.equal true + describe "restoreDoc", -> + beforeEach -> + @name = "doc-name" + @lines = ['1234','abc'] + @doc = { "mock": "doc" } + @folder_id = "mock-folder-id" + @callback = sinon.stub() + @ProjectEntityHandler.getDoc = sinon.stub().callsArgWith(2, null, @lines) + @ProjectEntityHandler.addDoc = sinon.stub().callsArgWith(4, null, @doc, @folder_id) + + @ProjectEntityHandler.restoreDoc project_id, doc_id, @name, @callback + + it 'should get the doc lines', -> + @ProjectEntityHandler.getDoc + .calledWith(project_id, doc_id) + .should.equal true + + it "should add a new doc with these doc lines", -> + @ProjectEntityHandler.addDoc + .calledWith(project_id, null, @name, @lines) + .should.equal true + + it "should call the callback with the new folder and doc", -> + @callback.calledWith(null, @doc, @folder_id).should.equal true + describe 'adding file', -> fileName = "something.jpg" beforeEach ->