mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Add in restore button to track changes
This commit is contained in:
parent
e364fd9c5f
commit
afb8bb6a42
12 changed files with 113 additions and 27 deletions
|
@ -6,7 +6,7 @@ module.exports = TrackChangesController =
|
|||
proxyToTrackChangesApi: (req, res, next = (error) ->) ->
|
||||
url = settings.apis.trackchanges.url + req.url
|
||||
logger.log url: url, "proxying to track-changes api"
|
||||
getReq = request.get(url)
|
||||
getReq = request(url: url, method: req.method)
|
||||
getReq.pipe(res)
|
||||
getReq.on "error", (error) ->
|
||||
logger.error err: error, "track-changes API error"
|
||||
|
|
|
@ -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/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.get '/project/:Project_id/collaborators', SecutiryManager.requestCanAccessProject(allow_auth_token: true), CollaboratorsController.getCollaborators
|
||||
|
|
|
@ -140,8 +140,7 @@
|
|||
button.btn.btn-primary ok
|
||||
|
||||
script(type="text/template")#genericModalButtonTemplate
|
||||
a(href="#",class="btn {{ class }}") {{ text }}
|
||||
|
||||
button(class="btn {{ class }}") {{ text }}
|
||||
|
||||
script(type="text/template")#editorPanelTemplate
|
||||
#editorArea(style='display: none;')
|
||||
|
@ -445,6 +444,9 @@
|
|||
div.color-square(style="background-color: hsl({{hue}}, 100%, 70%);")
|
||||
span {{name}}
|
||||
|
||||
div(class='restore')
|
||||
a(href="#") Restore to here
|
||||
|
||||
script(type='text/template')#changeListTemplate
|
||||
ul.change-list.nav.nav-pills.nav-stacked
|
||||
li.loading-changes Loading...
|
||||
|
|
|
@ -145,6 +145,11 @@ define [
|
|||
path = entity.get("name") + "/" + path
|
||||
return path
|
||||
|
||||
getNameOfEntityId: (entity_id) ->
|
||||
entity = @getEntity(entity_id)
|
||||
return if !entity?
|
||||
return entity.get("name")
|
||||
|
||||
# RENAMING
|
||||
renameSelected: () ->
|
||||
entity_id = @getSelectedEntityId()
|
||||
|
|
|
@ -49,4 +49,7 @@ define [
|
|||
find: (id) ->
|
||||
@loadedModel ||= {}
|
||||
return @loadedModel[id]
|
||||
|
||||
getAnonymousUser: () ->
|
||||
return User.findOrBuild("anonymous", { first_name: "Anonymous", email: "anon@sharelatex.com" })
|
||||
}
|
||||
|
|
|
@ -70,6 +70,9 @@ define [
|
|||
delete @hoverFromIndex
|
||||
@resetHoverStates()
|
||||
|
||||
view.on "click:restore", (e) =>
|
||||
@trigger "restore", view.model
|
||||
|
||||
view.resetSelector(index, @selectedFromIndex, @selectedToIndex)
|
||||
|
||||
resetAllSelectors: () ->
|
||||
|
@ -77,11 +80,11 @@ define [
|
|||
view.resetSelector(i, @selectedFromIndex, @selectedToIndex)
|
||||
|
||||
resetHoverStates: () ->
|
||||
if @hoverToIndex?
|
||||
if @hoverToIndex? and @hoverToIndex != @selectedToIndex
|
||||
@$("ul").addClass("hover-state")
|
||||
for view, i in @itemViews
|
||||
view.resetHoverState(i, @selectedFromIndex, @hoverToIndex)
|
||||
else if @hoverFromIndex?
|
||||
else if @hoverFromIndex? and @hoverFromIndex != @selectedFromIndex
|
||||
@$("ul").addClass("hover-state")
|
||||
for view, i in @itemViews
|
||||
view.resetHoverState(i, @hoverFromIndex, @selectedToIndex)
|
||||
|
@ -152,6 +155,7 @@ define [
|
|||
@trigger "mouseenter:from", args...
|
||||
"mouseleave .change-selector-from": (args...) ->
|
||||
@trigger "mouseleave:from", args...
|
||||
"click .restore a": "onRestoreClick"
|
||||
|
||||
|
||||
template : $("#changeListItemTemplate").html()
|
||||
|
@ -183,6 +187,10 @@ define [
|
|||
onFromSelectorClick: (e) ->
|
||||
@trigger "selected:from", e, @
|
||||
|
||||
onRestoreClick: (e) ->
|
||||
e.preventDefault()
|
||||
@trigger "click:restore", e, @
|
||||
|
||||
isSelectedFrom: () ->
|
||||
@$(".change-selector-from").is(":checked")
|
||||
|
||||
|
|
|
@ -3,21 +3,23 @@ define [
|
|||
"track-changes/models/Diff"
|
||||
"track-changes/ChangeListView"
|
||||
"track-changes/DiffView"
|
||||
], (ChangeList, Diff, ChangeListView, DiffView) ->
|
||||
"utils/Modal"
|
||||
"moment"
|
||||
], (ChangeList, Diff, ChangeListView, DiffView, Modal, moment) ->
|
||||
class TrackChangesManager
|
||||
template: $("#trackChangesPanelTemplate").html()
|
||||
|
||||
constructor: (@ide) ->
|
||||
@$el = $(@template)
|
||||
$("#editorWrapper").append(@$el)
|
||||
@hideEl()
|
||||
@hide()
|
||||
|
||||
@ide.editor.on "change:doc", () =>
|
||||
@hideEl()
|
||||
@hide()
|
||||
|
||||
@$el.find(".track-changes-close").on "click", (e) =>
|
||||
e.preventDefault
|
||||
@hideEl()
|
||||
@hide()
|
||||
|
||||
show: () ->
|
||||
@project_id = window.userSettings.project_id
|
||||
|
@ -44,14 +46,50 @@ define [
|
|||
)
|
||||
@diff.fetch()
|
||||
|
||||
@changeListView.on "restore", (change) =>
|
||||
@restore(change)
|
||||
|
||||
@showEl()
|
||||
|
||||
showEl: ->
|
||||
@ide.editor.hide()
|
||||
@$el.show()
|
||||
|
||||
hideEl: () ->
|
||||
hide: () ->
|
||||
@ide.editor.show()
|
||||
@$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
|
||||
|
|
|
@ -4,9 +4,13 @@ define [
|
|||
], (User)->
|
||||
Change = Backbone.Model.extend
|
||||
parse: (change) ->
|
||||
return {
|
||||
model = {
|
||||
start_ts: change.meta.start_ts
|
||||
end_ts: change.meta.end_ts
|
||||
user: User.findOrBuild(change.meta.user.id, change.meta.user)
|
||||
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
|
|
@ -8,6 +8,9 @@ define [
|
|||
|
||||
parse: (diff) ->
|
||||
for entry in diff.diff
|
||||
if entry.meta? and entry.meta.user?
|
||||
entry.meta.user = User.findOrBuild(entry.meta.user.id, entry.meta.user)
|
||||
if entry.meta?
|
||||
if entry.meta.user?
|
||||
entry.meta.user = User.findOrBuild(entry.meta.user.id, entry.meta.user)
|
||||
else
|
||||
entry.meta.user = User.getAnonymousUser()
|
||||
return diff
|
||||
|
|
|
@ -19,8 +19,9 @@ define [
|
|||
button.on "click", (e) ->
|
||||
e.preventDefault()
|
||||
if buttonOptions.callback?
|
||||
buttonOptions.callback()
|
||||
self.remove()
|
||||
buttonOptions.callback(button)
|
||||
if !buttonOptions.close? or buttonOptions.close
|
||||
self.remove()
|
||||
|
||||
@$el.modal
|
||||
# make sure we control when the modal is hidden
|
||||
|
@ -39,7 +40,6 @@ define [
|
|||
|
||||
@$el.find('input').focus()
|
||||
|
||||
|
||||
remove: () ->
|
||||
@$el.modal("hide")
|
||||
Backbone.View.prototype.remove.call(this)
|
||||
|
|
|
@ -71,11 +71,9 @@
|
|||
|
||||
ul.change-list {
|
||||
li {
|
||||
padding: 6px 4px;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #ccc;
|
||||
cursor: pointer;
|
||||
min-height: 38px;
|
||||
.change-selectors {
|
||||
.change-selector-from {
|
||||
position: absolute;
|
||||
|
@ -98,7 +96,8 @@
|
|||
}
|
||||
}
|
||||
.change-description {
|
||||
padding-left: 26px;
|
||||
padding: 6px 4px 6px 30px;
|
||||
min-height: 38px;
|
||||
}
|
||||
.change-name {
|
||||
font-size: 0.9em;
|
||||
|
@ -112,6 +111,15 @@
|
|||
margin-right: 4px;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.restore {
|
||||
a {
|
||||
display: block;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
display: none;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #eaeaea;
|
||||
}
|
||||
|
@ -137,12 +145,16 @@
|
|||
li.selected-from {
|
||||
.change-selectors {
|
||||
.range {
|
||||
bottom: 10px;
|
||||
bottom: 37px;
|
||||
}
|
||||
.change-selector-from {
|
||||
opacity: 1;
|
||||
bottom: 32px;
|
||||
}
|
||||
}
|
||||
.restore {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
ul.change-list.hover-state {
|
||||
|
@ -182,5 +194,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
li.selected-from.hover-selected-from {
|
||||
.change-selectors {
|
||||
.range {
|
||||
bottom: 37px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,13 +7,13 @@ SandboxedModule = require('sandboxed-module')
|
|||
describe "TrackChangesController", ->
|
||||
beforeEach ->
|
||||
@TrackChangesController = SandboxedModule.require modulePath, requires:
|
||||
"request" : @request = {}
|
||||
"request" : @request = sinon.stub()
|
||||
"settings-sharelatex": @settings = {}
|
||||
"logger-sharelatex": @logger = {log: sinon.stub(), error: sinon.stub()}
|
||||
|
||||
describe "proxyToTrackChangesApi", ->
|
||||
beforeEach ->
|
||||
@req = { url: "/mock/url" }
|
||||
@req = { url: "/mock/url", method: "POST" }
|
||||
@res = "mock-res"
|
||||
@next = sinon.stub()
|
||||
@settings.apis =
|
||||
|
@ -23,13 +23,16 @@ describe "TrackChangesController", ->
|
|||
events: {}
|
||||
pipe: sinon.stub()
|
||||
on: (event, handler) -> @events[event] = handler
|
||||
@request.get = sinon.stub().returns @proxy
|
||||
@request.returns @proxy
|
||||
@TrackChangesController.proxyToTrackChangesApi @req, @res, @next
|
||||
|
||||
describe "successfully", ->
|
||||
it "should call the track changes api", ->
|
||||
@request.get
|
||||
.calledWith("#{@settings.apis.trackchanges.url}#{@req.url}")
|
||||
@request
|
||||
.calledWith({
|
||||
url: "#{@settings.apis.trackchanges.url}#{@req.url}"
|
||||
method: @req.method
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should pipe the response to the client", ->
|
||||
|
|
Loading…
Reference in a new issue