Add in restore button to track changes

This commit is contained in:
James Allen 2014-03-11 12:13:46 +00:00
parent e364fd9c5f
commit afb8bb6a42
12 changed files with 113 additions and 27 deletions

View file

@ -6,7 +6,7 @@ module.exports = TrackChangesController =
proxyToTrackChangesApi: (req, res, next = (error) ->) -> proxyToTrackChangesApi: (req, res, next = (error) ->) ->
url = settings.apis.trackchanges.url + req.url url = settings.apis.trackchanges.url + req.url
logger.log url: url, "proxying to track-changes api" logger.log url: url, "proxying to track-changes api"
getReq = request.get(url) getReq = request(url: url, method: req.method)
getReq.pipe(res) getReq.pipe(res)
getReq.on "error", (error) -> getReq.on "error", (error) ->
logger.error err: error, "track-changes API error" logger.error err: error, "track-changes API error"

View file

@ -125,6 +125,7 @@ module.exports = class Router
app.get "/project/:Project_id/doc/:doc_id/updates", SecutiryManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi app.get "/project/:Project_id/doc/:doc_id/updates", SecutiryManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
app.get "/project/:Project_id/doc/:doc_id/diff", SecutiryManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi app.get "/project/:Project_id/doc/:doc_id/diff", SecutiryManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
app.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", SecutiryManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
app.post '/project/:project_id/leave', AuthenticationController.requireLogin(), CollaboratorsController.removeSelfFromProject app.post '/project/:project_id/leave', AuthenticationController.requireLogin(), CollaboratorsController.removeSelfFromProject
app.get '/project/:Project_id/collaborators', SecutiryManager.requestCanAccessProject(allow_auth_token: true), CollaboratorsController.getCollaborators app.get '/project/:Project_id/collaborators', SecutiryManager.requestCanAccessProject(allow_auth_token: true), CollaboratorsController.getCollaborators

View file

@ -140,8 +140,7 @@
button.btn.btn-primary ok button.btn.btn-primary ok
script(type="text/template")#genericModalButtonTemplate script(type="text/template")#genericModalButtonTemplate
a(href="#",class="btn {{ class }}") {{ text }} button(class="btn {{ class }}") {{ text }}
script(type="text/template")#editorPanelTemplate script(type="text/template")#editorPanelTemplate
#editorArea(style='display: none;') #editorArea(style='display: none;')
@ -445,6 +444,9 @@
div.color-square(style="background-color: hsl({{hue}}, 100%, 70%);") div.color-square(style="background-color: hsl({{hue}}, 100%, 70%);")
span {{name}} span {{name}}
div(class='restore')
a(href="#") Restore to here
script(type='text/template')#changeListTemplate script(type='text/template')#changeListTemplate
ul.change-list.nav.nav-pills.nav-stacked ul.change-list.nav.nav-pills.nav-stacked
li.loading-changes Loading... li.loading-changes Loading...

View file

@ -144,6 +144,11 @@ define [
# it's not the root folder so keep going # it's not the root folder so keep going
path = entity.get("name") + "/" + path path = entity.get("name") + "/" + path
return path return path
getNameOfEntityId: (entity_id) ->
entity = @getEntity(entity_id)
return if !entity?
return entity.get("name")
# RENAMING # RENAMING
renameSelected: () -> renameSelected: () ->

View file

@ -49,4 +49,7 @@ define [
find: (id) -> find: (id) ->
@loadedModel ||= {} @loadedModel ||= {}
return @loadedModel[id] return @loadedModel[id]
getAnonymousUser: () ->
return User.findOrBuild("anonymous", { first_name: "Anonymous", email: "anon@sharelatex.com" })
} }

View file

@ -70,6 +70,9 @@ define [
delete @hoverFromIndex delete @hoverFromIndex
@resetHoverStates() @resetHoverStates()
view.on "click:restore", (e) =>
@trigger "restore", view.model
view.resetSelector(index, @selectedFromIndex, @selectedToIndex) view.resetSelector(index, @selectedFromIndex, @selectedToIndex)
resetAllSelectors: () -> resetAllSelectors: () ->
@ -77,11 +80,11 @@ define [
view.resetSelector(i, @selectedFromIndex, @selectedToIndex) view.resetSelector(i, @selectedFromIndex, @selectedToIndex)
resetHoverStates: () -> resetHoverStates: () ->
if @hoverToIndex? if @hoverToIndex? and @hoverToIndex != @selectedToIndex
@$("ul").addClass("hover-state") @$("ul").addClass("hover-state")
for view, i in @itemViews for view, i in @itemViews
view.resetHoverState(i, @selectedFromIndex, @hoverToIndex) view.resetHoverState(i, @selectedFromIndex, @hoverToIndex)
else if @hoverFromIndex? else if @hoverFromIndex? and @hoverFromIndex != @selectedFromIndex
@$("ul").addClass("hover-state") @$("ul").addClass("hover-state")
for view, i in @itemViews for view, i in @itemViews
view.resetHoverState(i, @hoverFromIndex, @selectedToIndex) view.resetHoverState(i, @hoverFromIndex, @selectedToIndex)
@ -152,6 +155,7 @@ define [
@trigger "mouseenter:from", args... @trigger "mouseenter:from", args...
"mouseleave .change-selector-from": (args...) -> "mouseleave .change-selector-from": (args...) ->
@trigger "mouseleave:from", args... @trigger "mouseleave:from", args...
"click .restore a": "onRestoreClick"
template : $("#changeListItemTemplate").html() template : $("#changeListItemTemplate").html()
@ -183,6 +187,10 @@ define [
onFromSelectorClick: (e) -> onFromSelectorClick: (e) ->
@trigger "selected:from", e, @ @trigger "selected:from", e, @
onRestoreClick: (e) ->
e.preventDefault()
@trigger "click:restore", e, @
isSelectedFrom: () -> isSelectedFrom: () ->
@$(".change-selector-from").is(":checked") @$(".change-selector-from").is(":checked")

View file

@ -3,21 +3,23 @@ define [
"track-changes/models/Diff" "track-changes/models/Diff"
"track-changes/ChangeListView" "track-changes/ChangeListView"
"track-changes/DiffView" "track-changes/DiffView"
], (ChangeList, Diff, ChangeListView, DiffView) -> "utils/Modal"
"moment"
], (ChangeList, Diff, ChangeListView, DiffView, Modal, moment) ->
class TrackChangesManager class TrackChangesManager
template: $("#trackChangesPanelTemplate").html() template: $("#trackChangesPanelTemplate").html()
constructor: (@ide) -> constructor: (@ide) ->
@$el = $(@template) @$el = $(@template)
$("#editorWrapper").append(@$el) $("#editorWrapper").append(@$el)
@hideEl() @hide()
@ide.editor.on "change:doc", () => @ide.editor.on "change:doc", () =>
@hideEl() @hide()
@$el.find(".track-changes-close").on "click", (e) => @$el.find(".track-changes-close").on "click", (e) =>
e.preventDefault e.preventDefault
@hideEl() @hide()
show: () -> show: () ->
@project_id = window.userSettings.project_id @project_id = window.userSettings.project_id
@ -44,14 +46,50 @@ define [
) )
@diff.fetch() @diff.fetch()
@changeListView.on "restore", (change) =>
@restore(change)
@showEl() @showEl()
showEl: -> showEl: ->
@ide.editor.hide() @ide.editor.hide()
@$el.show() @$el.show()
hideEl: () -> hide: () ->
@ide.editor.show() @ide.editor.show()
@$el.hide() @$el.hide()
restore: (change) ->
name = @ide.fileTreeManager.getNameOfEntityId(@doc_id)
date = moment(change.get("start_ts")).format("Do MMM YYYY, h:mm:ss a")
modal = new Modal({
title: "Restore document"
message: "Are you sure you want to restore <strong>#{name}</strong> to before the changes on #{date}"
buttons: [{
text: "Cancel"
}, {
text: "Restore"
class: "btn-success"
close: false
callback: ($button) =>
$button.text("Restoring...")
$button.prop("disabled", true)
@doRestore change.get("version"), (error) =>
modal.remove()
@hide()
}]
})
doRestore: (version, callback = (error) ->) ->
$.ajax {
url: "/project/#{@project_id}/doc/#{@doc_id}/version/#{version}/restore"
type: "POST"
headers:
"X-CSRF-Token": window.csrfToken
success: () ->
callback()
error: (error) ->
callback(error)
}
return TrackChangesManager return TrackChangesManager

View file

@ -4,9 +4,13 @@ define [
], (User)-> ], (User)->
Change = Backbone.Model.extend Change = Backbone.Model.extend
parse: (change) -> parse: (change) ->
return { model = {
start_ts: change.meta.start_ts start_ts: change.meta.start_ts
end_ts: change.meta.end_ts end_ts: change.meta.end_ts
user: User.findOrBuild(change.meta.user.id, change.meta.user)
version: change.v version: change.v
} }
if change.meta.user?
model.user = User.findOrBuild(change.meta.user.id, change.meta.user)
else
model.user = User.getAnonymousUser()
return model

View file

@ -8,6 +8,9 @@ define [
parse: (diff) -> parse: (diff) ->
for entry in diff.diff for entry in diff.diff
if entry.meta? and entry.meta.user? if entry.meta?
entry.meta.user = User.findOrBuild(entry.meta.user.id, entry.meta.user) if entry.meta.user?
entry.meta.user = User.findOrBuild(entry.meta.user.id, entry.meta.user)
else
entry.meta.user = User.getAnonymousUser()
return diff return diff

View file

@ -19,8 +19,9 @@ define [
button.on "click", (e) -> button.on "click", (e) ->
e.preventDefault() e.preventDefault()
if buttonOptions.callback? if buttonOptions.callback?
buttonOptions.callback() buttonOptions.callback(button)
self.remove() if !buttonOptions.close? or buttonOptions.close
self.remove()
@$el.modal @$el.modal
# make sure we control when the modal is hidden # make sure we control when the modal is hidden
@ -39,7 +40,6 @@ define [
@$el.find('input').focus() @$el.find('input').focus()
remove: () -> remove: () ->
@$el.modal("hide") @$el.modal("hide")
Backbone.View.prototype.remove.call(this) Backbone.View.prototype.remove.call(this)

View file

@ -71,11 +71,9 @@
ul.change-list { ul.change-list {
li { li {
padding: 6px 4px;
position: relative; position: relative;
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
cursor: pointer; cursor: pointer;
min-height: 38px;
.change-selectors { .change-selectors {
.change-selector-from { .change-selector-from {
position: absolute; position: absolute;
@ -98,7 +96,8 @@
} }
} }
.change-description { .change-description {
padding-left: 26px; padding: 6px 4px 6px 30px;
min-height: 38px;
} }
.change-name { .change-name {
font-size: 0.9em; font-size: 0.9em;
@ -112,6 +111,15 @@
margin-right: 4px; margin-right: 4px;
margin-bottom: -1px; margin-bottom: -1px;
} }
.restore {
a {
display: block;
padding: 4px;
text-align: center;
border-top: 1px solid #ccc;
}
display: none;
}
&:hover { &:hover {
background-color: #eaeaea; background-color: #eaeaea;
} }
@ -137,12 +145,16 @@
li.selected-from { li.selected-from {
.change-selectors { .change-selectors {
.range { .range {
bottom: 10px; bottom: 37px;
} }
.change-selector-from { .change-selector-from {
opacity: 1; opacity: 1;
bottom: 32px;
} }
} }
.restore {
display: block;
}
} }
} }
ul.change-list.hover-state { ul.change-list.hover-state {
@ -182,5 +194,12 @@
} }
} }
} }
li.selected-from.hover-selected-from {
.change-selectors {
.range {
bottom: 37px;
}
}
}
} }
} }

View file

@ -7,13 +7,13 @@ SandboxedModule = require('sandboxed-module')
describe "TrackChangesController", -> describe "TrackChangesController", ->
beforeEach -> beforeEach ->
@TrackChangesController = SandboxedModule.require modulePath, requires: @TrackChangesController = SandboxedModule.require modulePath, requires:
"request" : @request = {} "request" : @request = sinon.stub()
"settings-sharelatex": @settings = {} "settings-sharelatex": @settings = {}
"logger-sharelatex": @logger = {log: sinon.stub(), error: sinon.stub()} "logger-sharelatex": @logger = {log: sinon.stub(), error: sinon.stub()}
describe "proxyToTrackChangesApi", -> describe "proxyToTrackChangesApi", ->
beforeEach -> beforeEach ->
@req = { url: "/mock/url" } @req = { url: "/mock/url", method: "POST" }
@res = "mock-res" @res = "mock-res"
@next = sinon.stub() @next = sinon.stub()
@settings.apis = @settings.apis =
@ -23,13 +23,16 @@ describe "TrackChangesController", ->
events: {} events: {}
pipe: sinon.stub() pipe: sinon.stub()
on: (event, handler) -> @events[event] = handler on: (event, handler) -> @events[event] = handler
@request.get = sinon.stub().returns @proxy @request.returns @proxy
@TrackChangesController.proxyToTrackChangesApi @req, @res, @next @TrackChangesController.proxyToTrackChangesApi @req, @res, @next
describe "successfully", -> describe "successfully", ->
it "should call the track changes api", -> it "should call the track changes api", ->
@request.get @request
.calledWith("#{@settings.apis.trackchanges.url}#{@req.url}") .calledWith({
url: "#{@settings.apis.trackchanges.url}#{@req.url}"
method: @req.method
})
.should.equal true .should.equal true
it "should pipe the response to the client", -> it "should pipe the response to the client", ->