Просмотр исходного кода

Merge branch 'master' of github.com:sharelatex/web-sharelatex

Henry Oswald 12 лет назад
Родитель
Сommit
ddbf04c267

+ 47 - 2
services/web/public/coffee/editor/Document.coffee

@@ -1,6 +1,7 @@
 define [
 	"editor/ShareJsDoc"
 	"libs/backbone"
+	"libs/underscore"
 ], (ShareJsDoc) ->
 	class Document
 		@getDocument: (ide, doc_id) ->
@@ -9,18 +10,34 @@ define [
 				@openDocs[doc_id] = new Document(ide, doc_id)
 			return @openDocs[doc_id]
 
+		@hasUnsavedChanges: () ->
+			for doc_id, doc of (@openDocs or {})
+				return true if doc.hasBufferedOps()
+			return false
+
 		constructor: (@ide, @doc_id) ->
 			@connected = @ide.socket.socket.connected
 			@joined = false
 			@wantToBeJoined = false
+			@_checkConsistency = _.bind(@_checkConsistency, @)
 			@_bindToEditorEvents()
 			@_bindToSocketEvents()
 
-		attachToAce: (ace) ->
-			@doc?.attachToAce(ace)
+		attachToAce: (@ace) ->
+			@doc?.attachToAce(@ace)
+			editorDoc = @ace.getSession().getDocument()
+			editorDoc.on "change", @_checkConsistency
 
 		detachFromAce: () ->
 			@doc?.detachFromAce()
+			editorDoc = @ace?.getSession().getDocument()
+			editorDoc?.off "change", @_checkConsistency
+
+		_checkConsistency: () ->
+			editorValue = @ace?.getValue()
+			sharejsValue = @doc?.getSnapshot()
+			if editorValue != sharejsValue
+				@_onError "error", new Error("Editor text does not match server text")
 
 		getSnapshot: () ->
 			@doc?.getSnapshot()
@@ -28,6 +45,15 @@ define [
 		getType: () ->
 			@doc?.getType()
 
+		getInflightOp: () ->
+			@doc?.getInflightOp()
+
+		getPendingOp: () ->
+			@doc?.getPendingOp()
+
+		hasBufferedOps: () ->
+			@doc?.hasBufferedOps()
+
 		_bindToSocketEvents: () ->
 			@_onUpdateAppliedHandler = (update) => @_onUpdateApplied(update)
 			@ide.socket.on "otUpdateApplied", @_onUpdateAppliedHandler
@@ -72,6 +98,25 @@ define [
 			else
 				@_leaveDoc(callback)
 
+		pollSavedStatus: () ->
+			# returns false if doc has ops waiting to be acknowledged or
+			# sent that haven't changed since the last time we checked.
+			# Otherwise returns true.
+			inflightOp = @getInflightOp()
+			pendingOp = @getPendingOp()
+			if !inflightOp? and !pendingOp?
+				# there's nothing going on
+				saved = true
+			else if inflightOp == @oldInflightOp
+				saved = false
+			else if pendingOp?
+				saved = false
+			else
+				saved = true
+
+			@oldInflightOp = inflightOp
+			return saved
+
 		_cancelLeave: () ->
 			if @_leaveCallbacks?
 				delete @_leaveCallbacks

+ 3 - 5
services/web/public/coffee/editor/Editor.coffee

@@ -210,11 +210,6 @@ define [
 				callback null, @document
 
 		_bindToDocumentEvents: (document) ->
-			document.on "op:sent", () =>
-				@ide.savingAreaManager.saving()
-			document.on "op:acknowledged", () =>
-				@ide.savingAreaManager.saved()
-
 			document.on "remoteop", () =>
 				@undoManager.nextUpdateIsRemote = true
 
@@ -364,3 +359,6 @@ define [
 
 		disable: () ->
 			@enabled = false
+
+		hasUnsavedChanges: () ->
+			Document.hasUnsavedChanges()

+ 3 - 0
services/web/public/coffee/editor/ShareJsDoc.coffee

@@ -89,6 +89,9 @@ define [
 		hasBufferedOps: () ->
 			@_doc.inflightOp? or @_doc.pendingOp?
 
+		getInflightOp: () -> @_doc.inflightOp
+		getPendingOp: () -> @_doc.pendingOp
+
 		attachToAce: (ace) -> @_doc.attach_ace(ace)
 		detachFromAce: () -> @_doc.detach_ace?()
 	

+ 8 - 20
services/web/public/coffee/ide.coffee

@@ -11,6 +11,7 @@ define [
 	"ide/TabManager"
 	"ide/LayoutManager"
 	"ide/FileUploadManager"
+	"ide/SavingAreaManager"
 	"spelling/SpellingManager"
 	"search/SearchManager"
 	"models/Project"
@@ -45,6 +46,7 @@ define [
 	TabManager,
 	LayoutManager,
 	FileUploadManager,
+	SavingAreaManager,
 	SpellingManager,
 	SearchManager,
 	Project,
@@ -174,8 +176,11 @@ define [
 			meta.client_now = new Date()
 			meta.recent_events = @recentEvents
 			errorObj = {}
-			for key in Object.getOwnPropertyNames(error)
-				errorObj[key] = error[key]
+			if typeof error == "object"
+				for key in Object.getOwnPropertyNames(error)
+					errorObj[key] = error[key]
+			else if typeof error == "string"
+				errorObj.message = error
 			$.ajax
 				url: "/error/client"
 				type: "POST"
@@ -202,22 +207,5 @@ define [
 	ide.tourManager = new IdeTour ide
 	ide.debugManager = new DebugManager(ide)
 
-	ide.savingAreaManager =
-		$savingArea : $('#saving-area')
-		timeOut: undefined
-		saved:->
-			@clearTimeout()
-			$("#savingProblems").hide()
-		saving:->
-			return if @timeOut?
-			@clearTimeout()
-			@timeOut = setTimeout((=>
-				ga?('send', 'event', 'editor-interaction', 'notification-shown', "saving")
-				$("#savingProblems").show()
-			), 3000)
-
-		clearTimeout:->
-			if @timeOut?
-				clearTimeout @timeOut
-			delete @timeOut
+	ide.savingAreaManager = new SavingAreaManager(ide)
 

+ 30 - 0
services/web/public/coffee/ide/SavingAreaManager.coffee

@@ -0,0 +1,30 @@
+define [
+], () ->
+	class SavingAreaManager
+		$el: $('#saving-area')
+
+		constructor: (@ide) ->
+			@unsavedSeconds = 0
+			setInterval () =>
+				@pollSavedStatus()
+			, 1000
+
+			$(window).bind 'beforeunload', () =>
+				@warnAboutUnsavedChanges()
+
+		pollSavedStatus: () ->
+			saved = @ide.editor.document.pollSavedStatus()
+			if saved
+				@unsavedSeconds = 0
+			else
+				@unsavedSeconds += 1
+
+			if @unsavedSeconds >= 4
+				$("#savingProblems").text("Saving... (#{@unsavedSeconds} seconds of unsaved changes)")
+				$("#savingProblems").show()
+			else
+				$("#savingProblems").hide()
+
+		warnAboutUnsavedChanges: () ->
+			if @ide.editor.hasUnsavedChanges()
+				return "You have unsaved changes. If you leave now they will not be saved."