diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index 12926c7c2b..b685cb2ea8 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -16,10 +16,19 @@ SecurityManager = require("../../managers/SecurityManager") module.exports = - deleteProject: (req, res)-> + deleteProject: (req, res) -> project_id = req.params.Project_id - logger.log project_id:project_id, "recived request to delete project" - projectDeleter.deleteProject project_id, (err)-> + logger.log project_id:project_id, "received request to delete project" + projectDeleter.archiveProject project_id, (err)-> + if err? + res.send 500 + else + res.send 200 + + restoreProject: (req, res) -> + project_id = req.params.Project_id + logger.log project_id:project_id, "received request to restore project" + projectDeleter.restoreProject project_id, (err)-> if err? res.send 500 else @@ -73,8 +82,6 @@ module.exports = timer = new metrics.Timer("project-list") user_id = req.session.user._id async.parallel { - subscription: (cb)-> - SubscriptionLocator.getUsersSubscription user_id, cb tags: (cb)-> TagsHandler.getAllTags user_id, cb projects: (cb)-> @@ -84,10 +91,17 @@ module.exports = logger.err err:err, "error getting data for project list page" return res.send 500 logger.log results:results, user_id:user_id, "rendering project list" - viewModel = _buildListViewModel results.projects[0], results.projects[1], results.projects[2], results.tags[0], results.tags[1], results.subscription?[0] + viewModel = _buildListViewModel results.projects[0], results.projects[1], results.projects[2], results.tags[0], results.tags[1] res.render 'project/list', viewModel timer.done() + archivedProjects: (req, res, next)-> + user_id = req.session.user._id + projectDeleter.findArchivedProjects user_id, 'name lastUpdated publicAccesLevel', (error, projects) -> + return next(error) if error? + logger.log projects: projects, user_id:user_id, "rendering archived project list" + viewModel = _buildListViewModel projects, [], [], [], {} + res.render 'project/archived', viewModel loadEditor: (req, res, next)-> timer = new metrics.Timer("load-editor") @@ -166,7 +180,6 @@ module.exports = languages: Settings.languages timer.done() - defaultSettingsForAnonymousUser = (user_id)-> id : user_id ace: @@ -183,15 +196,7 @@ defaultSettingsForAnonymousUser = (user_id)-> dropbox: false trackChanges: false - - -_buildListViewModel = (projects, collabertions, readOnlyProjects, tags, tagsGroupedByProject, subscription)-> - # TODO: Remove this one month after the ability to start free trials was removed - if subscription? and subscription.freeTrial? and subscription.freeTrial.expiresAt? - freeTrial = - expired: !!subscription.freeTrial.downgraded - expiresAt: SubscriptionFormatters.formatDate(subscription.freeTrial.expiresAt) - +_buildListViewModel = (projects, collabertions, readOnlyProjects, tags, tagsGroupedByProject)-> for project in projects project.accessLevel = "owner" for project in collabertions @@ -211,7 +216,6 @@ _buildListViewModel = (projects, collabertions, readOnlyProjects, tags, tagsGrou title:'Your Projects' priority_title: true projects: sortedProjects - freeTrial: freeTrial tags:tags projectTabActive: true } \ No newline at end of file diff --git a/services/web/app/coffee/Features/Project/ProjectDeleter.coffee b/services/web/app/coffee/Features/Project/ProjectDeleter.coffee index 242e69f9e4..434c94294f 100644 --- a/services/web/app/coffee/Features/Project/ProjectDeleter.coffee +++ b/services/web/app/coffee/Features/Project/ProjectDeleter.coffee @@ -20,8 +20,7 @@ module.exports = logger.log owner_id:owner_id, "deleting users projects" Project.remove owner_ref:owner_id, callback - - deleteProject: (project_id, callback = (error) ->)-> + archiveProject: (project_id, callback = (error) ->)-> logger.log project_id:project_id, "deleting project" Project.findById project_id, (err, project)=> if err? or !project? @@ -48,3 +47,12 @@ module.exports = if err? logger.err err:err, "problem deleting project" callback(err) + + restoreProject: (project_id, callback = (error) ->) -> + Project.update {_id:project_id}, { $unset: { archived: true }}, callback + + findArchivedProjects: (owner_id, fields, callback = (error, projects) ->) -> + Project.find { + owner_ref: owner_id + archived: true + }, fields, callback diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 1052f3cdae..cae1e9e4c5 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -92,6 +92,7 @@ module.exports = class Router app.get '/user/:user_id/personal_info', httpAuth, UserInfoController.getPersonalInfo app.get '/project', AuthenticationController.requireLogin(), ProjectController.projectListPage + app.get '/project/archived', AuthenticationController.requireLogin(), ProjectController.archivedProjects app.post '/project/new', AuthenticationController.requireLogin(), ProjectController.newProject app.get '/project/new/template', TemplatesMiddlewear.saveTemplateDataInSession, AuthenticationController.requireLogin(), TemplatesController.createProjectFromZipTemplate @@ -115,7 +116,8 @@ module.exports = class Router app.del '/Project/:Project_id', SecurityManager.requestIsOwner, ProjectController.deleteProject - app.post '/Project/:Project_id/clone', SecurityManager.requestCanAccessProject, ProjectController.cloneProject + app.post '/Project/:Project_id/restore', SecurityManager.requestIsOwner, ProjectController.restoreProject + app.post '/Project/:Project_id/clone', SecurityManager.requestCanAccessProject, ProjectController.cloneProject app.post '/project/:Project_id/rename', SecurityManager.requestIsOwner, ProjectController.renameProject diff --git a/services/web/app/views/project/archived.jade b/services/web/app/views/project/archived.jade new file mode 100644 index 0000000000..af65611433 --- /dev/null +++ b/services/web/app/views/project/archived.jade @@ -0,0 +1,46 @@ +extends ../layout + +block content + mixin projectList(projects) + -each project in projects + - project_id = project._id.toString() + .project_entry(id=project_id) + .btn-group.project-actions + a.btn(href='/project/'+project_id+'/restore', data-csrf=csrfToken, data-id=project_id).restoreProject Restore + .projectName #{project.name} + + include ../general/sidebar + + .content-with-navigation-sidebar + .box#projectListArea + .row-fluid + .span12 + .page-header + h1 Archived Projects + - if (projects.length > 0) + .row-fluid + .span9 + ul#projectList + mixin projectList(projects) + + + - else + .row-fluid + .span12 You don't have any archived projects + + include ../general/small-footer + + - locals.supressDefaultJs = true + + script + window.requirejs = { + "paths" : { + "moment": "libs/moment" + } + }; + script( + data-main=jsPath+'list.js?fingerprint='+fingerprint(jsPath + 'list.js'), + baseurl=jsPath, + src=jsPath+'libs/require.js?fingerprint='+fingerprint(jsPath + 'libs/require.js') + ) + diff --git a/services/web/app/views/project/flat.jade b/services/web/app/views/project/flat.jade deleted file mode 100644 index 29e78ef361..0000000000 --- a/services/web/app/views/project/flat.jade +++ /dev/null @@ -1,27 +0,0 @@ -extends ../layout - -block content - .container - .row - .span12.span-box - .page-header - h1 ShareLaTeX - Online LaTeX Editor : #{title} - //- .btn-group#newProject - //- a.btn.btn-success(href='/project/#{project_id}') go to editor mode - -each doc in resources - h2 #{doc.path} - if (doc.url != null) - img(src='/project/#{project_id}/file/#{doc.id}') - else - pre.prettyprint.lang-tex.linenums - -each line in doc.content - //leave the line below #{line} - span. - #{line} - - hr - - - - locals.supressDefaultJs = true - script(data-main=jsPath+'codeprettifyer.js', src=jsPath+'libs/require.js', baseurl=jsPath) - diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index 6c1ce6cfe9..914d059e06 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -124,23 +124,6 @@ block content a(href="/learn") help guides | . - - - if freeTrial && freeTrial.expired == false - .row-fluid - .span12 - .alert.alert-info.alert-free-trial - p You are currently using a free trial which expires on #{freeTrial.expiresAt}. - p - a(href="/user/subscription").btn.btn-primary Upgrade now - if freeTrial && freeTrial.expired == true - .row-fluid - .span12 - .alert.alert-danger.alert-free-trial - p Your free trial has expired! Upgrade now to continue using ShareLaTeX uninterrupted. - p - a(href="/user/subscription").btn.btn-danger Upgrade now - include ../general/small-footer script(type="text/template")#tagTemplate mixin tag('{{ project_id }}', '{{ tagName }}', true) diff --git a/services/web/app/views/project/table.jade b/services/web/app/views/project/table.jade deleted file mode 100644 index 39151e16df..0000000000 --- a/services/web/app/views/project/table.jade +++ /dev/null @@ -1,10 +0,0 @@ -.container - table.table.table-striped.table-bordered#revisionList(cellpadding='0', cellspacing='0', border='0') - thead - tr - th Date - th Files Changed - tbody - - - diff --git a/services/web/public/coffee/list.coffee b/services/web/public/coffee/list.coffee index 08aa6306db..a08a94a729 100644 --- a/services/web/public/coffee/list.coffee +++ b/services/web/public/coffee/list.coffee @@ -169,6 +169,18 @@ require [ $modal.find('.cancel').click (e)-> $modal.modal('hide') + $('.restoreProject').click (event) -> + event.preventDefault() + + id = $(@).data("id") + $.ajax + url: @href + type: 'POST' + data: + _csrf: $(@).data("csrf") + success: (data)-> + $("##{id}").fadeOut(1000) + newProject = (template, fileToOpen) -> $modal = $('#newProjectModal') $confirm = $('#confirmNewProject') diff --git a/services/web/public/stylesheets/less/list.less b/services/web/public/stylesheets/less/list.less index c44a5ce2f4..d3f2b54465 100644 --- a/services/web/public/stylesheets/less/list.less +++ b/services/web/public/stylesheets/less/list.less @@ -7,6 +7,7 @@ &:hover { background-color: #eaeaea; } + min-height: 40px; padding-left: 10px; padding-top: 10px; diff --git a/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee b/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee index b667212f0f..380cd7e1c0 100644 --- a/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee @@ -18,7 +18,9 @@ describe "ProjectController", -> url:"chat.com" siteUrl: "mysite.com" @ProjectDeleter = - deleteProject: sinon.stub().callsArgWith(1) + archiveProject: sinon.stub().callsArgWith(1) + restoreProject: sinon.stub().callsArgWith(1) + findArchivedProjects: sinon.stub() @ProjectDuplicator = duplicate: sinon.stub().callsArgWith(3, null, {_id:@project_id}) @ProjectCreationHandler = @@ -68,18 +70,22 @@ describe "ProjectController", -> jsPath:"js path here" describe "deleteProject", -> - it "should tell the project deleter", (done)-> - @res.send = (code)=> - @ProjectDeleter.deleteProject.calledWith(@project_id).should.equal true + @ProjectDeleter.archiveProject.calledWith(@project_id).should.equal true code.should.equal 200 done() @ProjectController.deleteProject @req, @res + describe "restoreProject", -> + it "should tell the project deleter", (done)-> + @res.send = (code)=> + @ProjectDeleter.restoreProject.calledWith(@project_id).should.equal true + code.should.equal 200 + done() + @ProjectController.restoreProject @req, @res describe "cloneProject", -> - it "should call the project duplicator", (done)-> @res.send = (json)=> @ProjectDuplicator.duplicate.calledWith(@user, @project_id, @projectName).should.equal true @@ -135,6 +141,23 @@ describe "ProjectController", -> done() @ProjectController.projectListPage @req, @res + describe "archivedProjects", -> + beforeEach -> + @projects = [{lastUpdated:1, _id:1}, {lastUpdated:2, _id:2}] + @ProjectDeleter.findArchivedProjects.callsArgWith(2, null, @projects) + + it "should render the project/archived page", (done)-> + @res.render = (pageName, opts)=> + pageName.should.equal "project/archived" + done() + @ProjectController.archivedProjects @req, @res + + it "should send the projects", (done)-> + @res.render = (pageName, opts)=> + opts.projects.length.should.equal (@projects.length) + done() + @ProjectController.archivedProjects @req, @res + describe "renameProject", -> beforeEach -> @newProjectName = "my supper great new project" diff --git a/services/web/test/UnitTests/coffee/Project/ProjectDeleterTests.coffee b/services/web/test/UnitTests/coffee/Project/ProjectDeleterTests.coffee index 8963f0d2a2..931f7d89c4 100644 --- a/services/web/test/UnitTests/coffee/Project/ProjectDeleterTests.coffee +++ b/services/web/test/UnitTests/coffee/Project/ProjectDeleterTests.coffee @@ -56,17 +56,17 @@ describe 'Project deleter', -> done() - describe "deleteProject", -> + describe "archiveProject", -> beforeEach -> @Project.update.callsArgWith(2) it "should flushProjectToMongoAndDelete in doc updater", (done)-> - @deleter.deleteProject @project_id, => + @deleter.archiveProject @project_id, => @documentUpdaterHandler.flushProjectToMongoAndDelete.calledWith(@project_id).should.equal true done() it "should remove the project", (done)-> - @deleter.deleteProject @project_id, => + @deleter.archiveProject @project_id, => @Project.update.calledWith({ _id:@project_id }, { @@ -75,7 +75,7 @@ describe 'Project deleter', -> done() it "should removeProjectFromAllTags", (done)-> - @deleter.deleteProject @project_id, => + @deleter.archiveProject @project_id, => @TagsHandler.removeProjectFromAllTags.calledWith(@project.owner_ref, @project_id).should.equal true @TagsHandler.removeProjectFromAllTags.calledWith(@project.collaberator_refs[0], @project_id).should.equal true @TagsHandler.removeProjectFromAllTags.calledWith(@project.collaberator_refs[1], @project_id).should.equal true @@ -84,3 +84,32 @@ describe 'Project deleter', -> done() + describe "restoreProject", -> + beforeEach -> + @Project.update.callsArgWith(2) + + it "should unset the archive attribute", (done)-> + @deleter.restoreProject @project_id, => + @Project.update.calledWith({ + _id: @project_id + }, { + $unset: { archived: true } + }).should.equal true + done() + + describe "findArchivedProjects", -> + beforeEach -> + @projects = ["mock-project"] + @owner_id = "mock-owner-id" + @callback = sinon.stub() + @Project.find = sinon.stub().callsArgWith(2, null, @projects) + @deleter.findArchivedProjects @owner_id, @fields = "name lastModified", @callback + + it "should find the archived projects for the owner", -> + @Project.find + .calledWith(owner_ref: @owner_id, archived: true, @fields) + .should.equal true + + it "should return the projects", -> + @callback.calledWith(null, @projects).should.equal true +