Selaa lähdekoodia

Sort out front end coffee/js files and minification

James Allen 12 vuotta sitten
vanhempi
sitoutus
b9909bbd84
100 muutettua tiedostoa jossa 21 lisäystä ja 9199 poistoa
  1. 11 21
      services/web/Gruntfile.coffee
  2. 7 7
      services/web/app/views/layout.jade
  3. 2 16
      services/web/app/views/project/editor.jade
  4. 1 2
      services/web/app/views/project/list.jade
  5. 0 88
      services/web/public/coffee/SubscriptionGroupsManager.coffee
  6. 0 70
      services/web/public/coffee/account/AccountManager.coffee
  7. 0 33
      services/web/public/coffee/admin.coffee
  8. 0 33
      services/web/public/coffee/analytics/AnalyticsManager.coffee
  9. 0 14
      services/web/public/coffee/app/base.coffee
  10. 0 63
      services/web/public/coffee/app/directives/asyncForm.coffee
  11. 0 15
      services/web/public/coffee/app/directives/equals.coffee
  12. 0 67
      services/web/public/coffee/app/directives/fineUpload.coffee
  13. 0 67
      services/web/public/coffee/app/directives/focus.coffee
  14. 0 10
      services/web/public/coffee/app/directives/onEnter.coffee
  15. 0 67
      services/web/public/coffee/app/directives/selectAll.coffee
  16. 0 18
      services/web/public/coffee/app/directives/stopPropagation.coffee
  17. 0 18
      services/web/public/coffee/app/filters/formatDate.coffee
  18. 0 70
      services/web/public/coffee/app/ide.coffee
  19. 0 12
      services/web/public/coffee/app/ide/binary-files/BinaryFilesManager.coffee
  20. 0 7
      services/web/public/coffee/app/ide/binary-files/controllers/BinaryFileController.coffee
  21. 0 7
      services/web/public/coffee/app/ide/chat/controllers/ChatButtonController.coffee
  22. 0 64
      services/web/public/coffee/app/ide/chat/controllers/ChatController.coffee
  23. 0 12
      services/web/public/coffee/app/ide/chat/controllers/ChatMessageController.coffee
  24. 0 5
      services/web/public/coffee/app/ide/chat/index.coffee
  25. 0 113
      services/web/public/coffee/app/ide/connection/ConnectionManager.coffee
  26. 0 101
      services/web/public/coffee/app/ide/directives/layout.coffee
  27. 0 254
      services/web/public/coffee/app/ide/editor/Document.coffee
  28. 0 103
      services/web/public/coffee/app/ide/editor/EditorManager.coffee
  29. 0 123
      services/web/public/coffee/app/ide/editor/ShareJsDoc.coffee
  30. 0 95
      services/web/public/coffee/app/ide/editor/auto-complete/AutoCompleteManager.coffee
  31. 0 100
      services/web/public/coffee/app/ide/editor/auto-complete/Snippets.coffee
  32. 0 126
      services/web/public/coffee/app/ide/editor/auto-complete/SuggestionManager.coffee
  33. 0 33
      services/web/public/coffee/app/ide/editor/controllers/SavingNotificationController.coffee
  34. 0 52
      services/web/public/coffee/app/ide/editor/cursor-position/CursorPositionManager.coffee
  35. 0 187
      services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee
  36. 0 228
      services/web/public/coffee/app/ide/editor/highlights/HighlightsManager.coffee
  37. 0 150
      services/web/public/coffee/app/ide/editor/spell-check/HighlightedWordManager.coffee
  38. 0 191
      services/web/public/coffee/app/ide/editor/spell-check/SpellCheckManager.coffee
  39. 0 82
      services/web/public/coffee/app/ide/editor/spell-check/SpellingMenuView.coffee
  40. 0 374
      services/web/public/coffee/app/ide/editor/undo/UndoManager.coffee
  41. 0 281
      services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee
  42. 0 113
      services/web/public/coffee/app/ide/file-tree/controllers/FileTreeController.coffee
  43. 0 49
      services/web/public/coffee/app/ide/file-tree/controllers/FileTreeEntityController.coffee
  44. 0 51
      services/web/public/coffee/app/ide/file-tree/controllers/FileTreeFolderController.coffee
  45. 0 13
      services/web/public/coffee/app/ide/file-tree/controllers/FileTreeRootFolderController.coffee
  46. 0 14
      services/web/public/coffee/app/ide/file-tree/directives/draggable.coffee
  47. 0 17
      services/web/public/coffee/app/ide/file-tree/directives/droppable.coffee
  48. 0 23
      services/web/public/coffee/app/ide/file-tree/directives/fileEntity.coffee
  49. 0 71
      services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee
  50. 0 20
      services/web/public/coffee/app/ide/pdf/PdfManager.coffee
  51. 0 277
      services/web/public/coffee/app/ide/pdf/controllers/PdfController.coffee
  52. 0 196
      services/web/public/coffee/app/ide/pdf/directives/pdfJs.coffee
  53. 0 19
      services/web/public/coffee/app/ide/permissions/PermissionsManager.coffee
  54. 0 37
      services/web/public/coffee/app/ide/services/ide.coffee
  55. 0 26
      services/web/public/coffee/app/ide/settings/controllers/ProjectNameController.coffee
  56. 0 51
      services/web/public/coffee/app/ide/settings/controllers/SettingsController.coffee
  57. 0 7
      services/web/public/coffee/app/ide/settings/index.coffee
  58. 0 14
      services/web/public/coffee/app/ide/settings/services/settings.coffee
  59. 0 11
      services/web/public/coffee/app/ide/share/controllers/ShareController.coffee
  60. 0 104
      services/web/public/coffee/app/ide/share/controllers/ShareProjectModalController.coffee
  61. 0 5
      services/web/public/coffee/app/ide/share/index.coffee
  62. 0 30
      services/web/public/coffee/app/ide/share/services/projectMembers.coffee
  63. 0 256
      services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee
  64. 0 9
      services/web/public/coffee/app/ide/track-changes/controllers/TrackChangesDiffController.coffee
  65. 0 109
      services/web/public/coffee/app/ide/track-changes/controllers/TrackChangesListController.coffee
  66. 0 45
      services/web/public/coffee/app/ide/track-changes/directives/infiniteScroll.coffee
  67. 0 16
      services/web/public/coffee/app/main.coffee
  68. 0 54
      services/web/public/coffee/app/main/account-settings.coffee
  69. 0 54
      services/web/public/coffee/app/main/group-members.coffee
  70. 0 6
      services/web/public/coffee/app/main/plans.coffee
  71. 0 583
      services/web/public/coffee/app/main/project-list.coffee
  72. 0 58
      services/web/public/coffee/app/main/user-details.coffee
  73. 0 44
      services/web/public/coffee/app/modules/recursionHelper.coffee
  74. 0 33
      services/web/public/coffee/app/utils/EventEmitter.coffee
  75. 0 79
      services/web/public/coffee/auto-complete/AutoCompleteManager.coffee
  76. 0 100
      services/web/public/coffee/auto-complete/Snippets.coffee
  77. 0 126
      services/web/public/coffee/auto-complete/SuggestionManager.coffee
  78. 0 99
      services/web/public/coffee/cursors/CursorManager.coffee
  79. 0 32
      services/web/public/coffee/debug/DebugManager.coffee
  80. 0 40
      services/web/public/coffee/editor/AceUpdateManager.coffee
  81. 0 257
      services/web/public/coffee/editor/Document.coffee
  82. 0 374
      services/web/public/coffee/editor/Editor.coffee
  83. 0 3
      services/web/public/coffee/editor/ShareJSHeader.coffee
  84. 0 127
      services/web/public/coffee/editor/ShareJsDoc.coffee
  85. 0 130
      services/web/public/coffee/editor/sharejs/client/ace.coffee
  86. 0 119
      services/web/public/coffee/editor/sharejs/client/ace.js
  87. 0 94
      services/web/public/coffee/editor/sharejs/client/cm.coffee
  88. 0 167
      services/web/public/coffee/editor/sharejs/client/connection.coffee
  89. 0 327
      services/web/public/coffee/editor/sharejs/client/doc.coffee
  90. 0 332
      services/web/public/coffee/editor/sharejs/client/doc.js
  91. 0 73
      services/web/public/coffee/editor/sharejs/client/index.coffee
  92. 0 46
      services/web/public/coffee/editor/sharejs/client/microevent.coffee
  93. 0 85
      services/web/public/coffee/editor/sharejs/client/microevent.js
  94. 0 69
      services/web/public/coffee/editor/sharejs/client/textarea.coffee
  95. 0 13
      services/web/public/coffee/editor/sharejs/client/web-prelude.coffee
  96. 0 5
      services/web/public/coffee/editor/sharejs/index.coffee
  97. 0 333
      services/web/public/coffee/editor/sharejs/server/browserchannel.coffee
  98. 0 149
      services/web/public/coffee/editor/sharejs/server/db/couchdb.coffee
  99. 0 28
      services/web/public/coffee/editor/sharejs/server/db/index.coffee
  100. 0 198
      services/web/public/coffee/editor/sharejs/server/db/pg.coffee

+ 11 - 21
services/web/Gruntfile.coffee

@@ -37,15 +37,13 @@ module.exports = (grunt) ->
 					join: true
 					join: true
 				files:
 				files:
 					"public/js/libs/sharejs.js": [
 					"public/js/libs/sharejs.js": [
-						"public/coffee/editor/ShareJSHeader.coffee"
-						"public/coffee/editor/sharejs/types/helpers.coffee"
-						"public/coffee/editor/sharejs/types/text.coffee"
-						"public/coffee/editor/sharejs/types/text-api.coffee"
-						"public/coffee/editor/sharejs/types/json.coffee"
-						"public/coffee/editor/sharejs/types/json-api.coffee"
-						"public/coffee/editor/sharejs/client/microevent.coffee"
-						"public/coffee/editor/sharejs/client/doc.coffee"
-						"public/coffee/editor/sharejs/client/ace.coffee"
+						"public/coffee/ide/editor/sharejs/header.coffee"
+						"public/coffee/ide/editor/sharejs/vendor/types/helpers.coffee"
+						"public/coffee/ide/editor/sharejs/vendor/types/text.coffee"
+						"public/coffee/ide/editor/sharejs/vendor/types/text-api.coffee"
+						"public/coffee/ide/editor/sharejs/vendor/client/microevent.coffee"
+						"public/coffee/ide/editor/sharejs/vendor/client/doc.coffee"
+						"public/coffee/ide/editor/sharejs/vendor/client/ace.coffee"
 					]
 					]
 
 
 			client:
 			client:
@@ -89,12 +87,8 @@ module.exports = (grunt) ->
 					inlineText: false
 					inlineText: false
 					preserveLicenseComments: false
 					preserveLicenseComments: false
 					paths:
 					paths:
-						"underscore": "libs/underscore"
-						"jquery": "libs/jquery"
-						"moment": "libs/moment"
+						"moment": "libs/moment-2.7.0"
 					shim:
 					shim:
-						"libs/backbone":
-							deps: ["libs/underscore"]
 						"libs/pdfListView/PdfListView":
 						"libs/pdfListView/PdfListView":
 							deps: ["libs/pdf"]
 							deps: ["libs/pdf"]
 						"libs/pdf":
 						"libs/pdf":
@@ -104,16 +98,12 @@ module.exports = (grunt) ->
 					modules: [
 					modules: [
 						{
 						{
 							name: "main",
 							name: "main",
-							exclude: ["jquery"]
+							exclude: ["libs"]
 						}, {
 						}, {
 							name: "ide",
 							name: "ide",
-							exclude: ["jquery"]
+							exclude: ["libs", "libs/jquery-layout"]
 						}, {
 						}, {
-							name: "home",
-							exclude: ["jquery"]
-						}, {
-							name: "list",
-							exclude: ["jquery"]
+							name: "libs"
 						}
 						}
 					]
 					]
 
 

+ 7 - 7
services/web/app/views/layout.jade

@@ -30,11 +30,8 @@ html(itemscope, itemtype='http://schema.org/Product')
 		script(type="text/javascript").
 		script(type="text/javascript").
 			window.csrfToken = "#{csrfToken}";
 			window.csrfToken = "#{csrfToken}";
 
 
-		script(src=jsPath+'libs/jquery.js')
-		script(src=jsPath+'libs/angular-1.2.17.js')
-		script(src=jsPath+'libs/moment-2.4.0.js')
-		script(src=jsPath+'libs/underscore-1.3.3.js')
-		block scripts
+		script(src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js")
+		script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.19/angular.min.js")
 		
 		
 	body
 	body
 		- if(typeof(suppressNavbar) == "undefined")
 		- if(typeof(suppressNavbar) == "undefined")
@@ -61,10 +58,13 @@ html(itemscope, itemtype='http://schema.org/Product')
 		- if(typeof(suppressFooter) == "undefined")
 		- if(typeof(suppressFooter) == "undefined")
 			script(type='text/javascript').
 			script(type='text/javascript').
 				window.requirejs = {
 				window.requirejs = {
-					"urlArgs" : "fingerprint=#{fingerprint(jsPath + 'app/main.js')}"
+					"urlArgs" : "fingerprint=#{fingerprint(jsPath + 'app/main.js')}",
+					"paths" : {
+						"moment": "libs/moment-2.7.0"
+					}
 				};	
 				};	
 			script(
 			script(
-				data-main=jsPath+'app/main.js',
+				data-main=jsPath+'main.js',
 				baseurl=jsPath,
 				baseurl=jsPath,
 				src=jsPath+'libs/require.js?fingerprint='+fingerprint(jsPath + 'libs/require.js')
 				src=jsPath+'libs/require.js?fingerprint='+fingerprint(jsPath + 'libs/require.js')
 			)
 			)

+ 2 - 16
services/web/app/views/project/editor.jade

@@ -5,13 +5,6 @@ block vars
 	- var suppressFooter = true
 	- var suppressFooter = true
 	- var suppressDefaultJs = true
 	- var suppressDefaultJs = true
 
 
-block scripts
-	//- Only use the native bootstrap on the editor page,
-	//- since we use the Angular-based bootstrap elsewhere.
-	//- script(src=jsPath+'libs/bootstrap-3.1.1.js')
-	script(src=jsPath+'libs/jquery-layout.js')
-	script(src=jsPath+'libs/jquery.storage.js')
-
 block content
 block content
 	.editor(ng-controller="IdeController")
 	.editor(ng-controller="IdeController")
 		.loading-screen(ng-show="state.loading")
 		.loading-screen(ng-show="state.loading")
@@ -92,19 +85,12 @@ block content
 		window.csrfToken = "!{csrfToken}";
 		window.csrfToken = "!{csrfToken}";
 		window.requirejs = {
 		window.requirejs = {
 			"paths" : {
 			"paths" : {
-				"underscore": "../libs/underscore-1.3.3",
 				"mathjax": "https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML",
 				"mathjax": "https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML",
-				"moment": "libs/moment-2.4.0",
-				"ace": "#{jsPath}ace",
-				"libs": "#{jsPath}libs",
-				"text": "#{jsPath}text"
+				"moment": "libs/moment-2.7.0"
 			},
 			},
 			"urlArgs" : "fingerprint=#{fingerprint(jsPath + 'ide.js')}",
 			"urlArgs" : "fingerprint=#{fingerprint(jsPath + 'ide.js')}",
 			"waitSeconds": 0,
 			"waitSeconds": 0,
 			"shim": {
 			"shim": {
-				"libs/backbone": {
-					deps: ["libs/underscore-1.3.3"]
-				},
 				"libs/pdfListView/PdfListView": {
 				"libs/pdfListView/PdfListView": {
 					deps: ["libs/pdf"]
 					deps: ["libs/pdf"]
 				},
 				},
@@ -125,7 +111,7 @@ block content
 		window.sharelatex.pdfJsWorkerPath = "#{pdfJsWorkerPath}"
 		window.sharelatex.pdfJsWorkerPath = "#{pdfJsWorkerPath}"
 
 
 	script(
 	script(
-		data-main=jsPath+'app/ide.js',
+		data-main=jsPath+'ide.js',
 		baseurl=jsPath,
 		baseurl=jsPath,
 		data-ace-base=jsPath+'ace',
 		data-ace-base=jsPath+'ace',
 		src=jsPath+'libs/require.js?fingerprint='+fingerprint(jsPath + 'libs/require.js')
 		src=jsPath+'libs/require.js?fingerprint='+fingerprint(jsPath + 'libs/require.js')

+ 1 - 2
services/web/app/views/project/list.jade

@@ -1,6 +1,6 @@
 extends ../layout
 extends ../layout
 
 
-block scripts
+block content
 	script(type="text/javascript").
 	script(type="text/javascript").
 		window.data = {
 		window.data = {
 			projects: !{JSON.stringify(projects)},
 			projects: !{JSON.stringify(projects)},
@@ -13,7 +13,6 @@ block scripts
 			}
 			}
 		};
 		};
 
 
-block content
 	.content.content-alt(ng-controller="ProjectPageController")
 	.content.content-alt(ng-controller="ProjectPageController")
 		.container
 		.container
 			.row
 			.row

+ 0 - 88
services/web/public/coffee/SubscriptionGroupsManager.coffee

@@ -1,88 +0,0 @@
-require [
-	"libs/mustache"
-	"./main"
-	"underscore"
-], (m)->
-	$(document).ready ->
-
-		tableRowTemplate = '''
-			<tr>
-				<td> <input type="checkbox" class="select-one"></td>
-				<td> {{ email }} </td>
-				<td> {{ first_name }} {{ last_name }} </td>
-				<td> {{ !holdingAccount }} </td>
-				<td>
-					<input type="hidden" name="user_id" value="{{_id}}" class="user_id">
-				</td>
-			</tr>
-		'''
-
-		window.temp = tableRowTemplate
-
-		$form = $('form#addUserToGroup')
-
-		addUser = (e)->
-
-			parseEmails = (emailsString)->
-				regexBySpaceOrComma = /[\s,]+/
-				emails = emailsString.split(regexBySpaceOrComma)
-				emails = _.map emails, (email)->
-					email = email.trim()
-				emails = _.select emails, (email)->
-					email.indexOf("@") != -1
-				return emails
-
-
-			sendNewUserToServer = (email)->
-				$.ajax
-					url: "/subscription/group/user"
-					type: 'POST'
-					data:
-						email: email
-						_csrf: csrfToken
-					success: (data)->
-						if data.limitReached
-							alert("You have reached your maximum number of members")
-						else
-							renderNewUserInList data.user
-
-			renderNewUserInList = (user)->
-				html = Mustache.to_html(tableRowTemplate, user)
-				$('#userList').append(html)
-
-			e.preventDefault()
-			val = $form.find("input[name=email]").val()
-			emails = parseEmails(val)
-			emails.forEach (email)->
-				sendNewUserToServer(email)
-			$form.find("input").val('')
-
-		removeUsers = (e)->
-			selectedUserRows = $('td input.select-one:checked').closest('tr').find(".user_id").toArray()
-			do deleteNext = () ->
-				row = selectedUserRows.pop()
-				if row?
-					user_id = $(row).val()
-					$.ajax
-						url: "/subscription/group/user/#{user_id}"
-						type: 'DELETE'
-						data:
-							_csrf: csrfToken
-						success: ->
-							$(row).parents("tr").fadeOut(250)
-							deleteNext()
-
-		$form.on 'keypress', (e)->
-			if(e.keyCode == 13)
-				addUser(e)
-
-		$form.find(".addUser").on 'click', addUser
-
-		$('#deleteUsers').on 'click', removeUsers
-
-		$('input.select-all').on "change", () ->
-			if $(@).is(":checked")
-				$("input.select-one").prop( "checked", true )
-			else
-				$("input.select-one").prop( "checked", false )
-

+ 0 - 70
services/web/public/coffee/account/AccountManager.coffee

@@ -1,70 +0,0 @@
-define [
-	"utils/Modal"
-], (Modal) ->
-
-
-
-	AccountManager =
-		askToUpgrade: (ide, options = {}) ->
-			options.why ||= "to use this feature"
-			if ide.project.get("owner") == ide.user
-				if ide.user.get("subscription").freeTrial.allowed
-					@showCreditCardFreeTrialModal(options)
-				else
-					@showUpgradeDialog(ide, options)
-			else
-				@showAskOwnerDialog(ide, options)
-
-		showCreditCardFreeTrialModal: (options) ->
-			Modal.createModal
-				title: "Start your free trial"
-				message: "You need to upgrade your account #{options.why}. Would you like to start a 30 day free trial? You can cancel at any point."
-				buttons: [{
-					text: "Cancel"
-					class: ""
-				},{
-					text: "Enter Billing Information"
-					class: "btn-primary"
-					callback: () =>
-						options.onUpgrade?()
-						@gotoSubscriptionsPage()
-				}]
-
-		gotoSubscriptionsPage: () ->
-			window.open("/user/subscription/new?planCode=student_free_trial")
-			Modal.createModal
-				title: "Please refresh"
-				message: "Please refresh this page after starting your free trial. This will make sure all of your features are enabled."
-				buttons: [{
-					text: "OK"
-					class: ""
-				}]
-
-		showUpgradeDialog: (ide, options = {}) ->
-			options.message ||= """
-				Sorry, you need to upgrade your account #{options.why}.
-				You can do this on your account settings page,
-				accessible in the top right hand corner.
-			"""
-			Modal.createModal
-				title: "Please upgrade your account"
-				message: options.message
-				buttons: [{
-					text: "OK"
-					class: "btn"
-					callback: () ->	options.onCancel() if options.onCancel
-				},{
-					text: "See Plans"
-					class: "btn-success"
-					callback: () -> window.open("/user/subscription/plans")
-				}]
-		
-		showAskOwnerDialog: (ide, options = {}) ->
-			Modal.createModal
-				title: "Owner needs an upgraded account"
-				message: "Please ask the owner of this project to upgrade their account #{options.why}."
-				buttons: [{
-					text: "OK"
-					class: "btn-primary"
-					callback: () ->	options.onCancel() if options.onCancel
-				}]

+ 0 - 33
services/web/public/coffee/admin.coffee

@@ -1,33 +0,0 @@
-require [
-	"main"
-	"libs/jquery.tablesorter"
-], ()->
-	$(document).ready ()->
-		$('#connected-users').tablesorter()
-
-		$('button#disconnectAll').click (event)->
-			event.preventDefault()
-			$.ajax
-				url: "/admin/dissconectAllUsers",
-				type:'POST',
-				data:
-					_csrf: $(@).data("csrf")
-				success: (data)->
-			
-		$('button#closeEditor').click (event)->
-			event.preventDefault()
-			$.ajax
-				url: "/admin/closeEditor",
-				type:'POST',
-				data:
-					_csrf: $(@).data("csrf")
-				success: (data)->
-
-		$('button#pollTpds').click (event)->
-			event.preventDefault()
-			$.ajax
-				url: "/admin/pollUsersWithDropbox",
-				type:'POST',
-				data:
-					_csrf: $(@).data("csrf")
-				success: (data)->

+ 0 - 33
services/web/public/coffee/analytics/AnalyticsManager.coffee

@@ -1,33 +0,0 @@
-define [
-	"libs/md5"
-], () ->
-	class AnalyticsManager
-		constructor: (@ide) ->
-			@ide.editor.on "update:doc", () =>
-				@updateCount ||= 0
-				@updateCount++
-				if @updateCount == 100
-					ga('send', 'event', 'editor-interaction', 'multi-doc-update')
-
-			@ide.pdfManager.on "compile:pdf", () =>
-				@compileCount ||= 0
-				@compileCount++
-				if @compileCount == 1
-					ga('send', 'event', 'editor-interaction', 'single-compile')
-				if @compileCount == 3
-					ga('send', 'event', 'editor-interaction', 'multi-compile')
-
-		getABTestBucket: (test_name, buckets = []) ->
-			hash = CryptoJS.MD5("#{@ide.user.get("id")}:#{test_name}")
-			bucketIndex = parseInt(hash.toString().slice(0,2), 16) % buckets.length
-			return buckets[bucketIndex]
-
-		startABTest: (test_name, buckets = []) ->
-			value = @getABTestBucket(test_name, buckets)
-			ga('send', 'event', 'ab_tests', test_name, "viewed-#{value}")
-			return value
-
-		endABTest: (test_name, buckets = []) ->
-			value = @getABTestBucket(test_name, buckets)
-			ga('send', 'event', 'ab_tests', test_name, "converted-#{value}")
-			return value

+ 0 - 14
services/web/public/coffee/app/base.coffee

@@ -1,14 +0,0 @@
-define [
-	"../libs/angular-autocomplete/angular-autocomplete"
-	"../libs/ui-bootstrap"
-	"modules/recursionHelper"
-	"../libs/ng-context-menu-0.1.4"
-], () ->
-	App = angular.module("SharelatexApp", [
-		"ui.bootstrap"
-		"autocomplete"
-		"RecursionHelper"
-		"ng-context-menu"
-	])
-
-	return App

+ 0 - 63
services/web/public/coffee/app/directives/asyncForm.coffee

@@ -1,63 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.directive "asyncForm", ($http) ->
-		return {
-			link: (scope, element, attrs) ->
-				formName = attrs.asyncForm
-
-				scope[attrs.name].response = response = {}
-
-				element.on "submit", (e) ->
-					e.preventDefault()
-
-					formData = {}
-					for data in element.serializeArray()
-						formData[data.name] = data.value
-
-					$http
-						.post(element.attr('action'), formData)
-						.success (data, status, headers, config) ->
-							response.success = true
-							response.error = false
-
-							if data.redir?
-								ga('send', 'event', formName, 'success')
-								window.location = data.redir
-							else if data.message?
-								response.message = data.message
-
-								if data.message.type == "error"
-									response.success = false
-									response.error = true
-									ga('send', 'event', formName, 'failure', data.message)
-								else
-									ga('send', 'event', formName, 'success')
-									
-						.error (data, status, headers, config) ->
-							response.success = false
-							response.error = true
-							response.message =
-								text: data.message or "Something went wrong talking to the server :(. Please try again."
-								type: 'error'
-							ga('send', 'event', formName, 'failure', data.message)
-		}
-
-	App.directive "formMessages", () ->
-		return {
-			restrict: "E"
-			template: """
-				<div class="alert" ng-class="{
-					'alert-danger': form.response.message.type == 'error',
-					'alert-success': form.response.message.type != 'error'
-				}" ng-show="!!form.response.message">
-					{{form.response.message.text}}
-				</div>
-				<div ng-transclude></div>
-			"""
-			transclude: true
-			scope: {
-				form: "=for"
-			}
-
-		}

+ 0 - 15
services/web/public/coffee/app/directives/equals.coffee

@@ -1,15 +0,0 @@
-define [
-	"base"
-], (App) ->
-
-	App.directive 'equals', () ->
-		return {
-			require: "ngModel",
-			link: (scope, element, attrs, ngModel) ->
-				scope.$watch attrs.ngModel, () -> validate()
-				attrs.$observe 'equals', () -> validate()
-
-				validate = () ->
-					equal = (attrs.equals == ngModel.$viewValue)
-					ngModel.$setValidity('areEqual', equal)
-		}

+ 0 - 67
services/web/public/coffee/app/directives/fineUpload.coffee

@@ -1,67 +0,0 @@
-define [
-	"base"
-	"../../libs/fineuploader"
-], (App) ->
-	App.directive 'fineUpload', ($timeout) ->
-		return {
-			scope: {
-				multiple: "="
-				endpoint: "@"
-				waitingForResponseText: "@"
-				failedUploadText: "@"
-				uploadButtonText: "@"
-				dragAreaText: "@"
-				hintText: "@"
-				allowedExtensions: "="
-				onCompleteCallback: "="
-				onUploadCallback: "="
-				params: "="
-			}
-			link: (scope, element, attrs) ->
-				multiple = scope.multiple or false
-				endpoint = scope.endpoint
-				if scope.allowedExtensions?
-					validation = 
-						allowedExtensions: scope.allowedExtensions
-				else
-					validation = {}
-				text =
-					waitingForResponse: scope.waitingForResponseText or "Processing..."
-					failUpload: scope.failedUploadText or "Failed :("
-					uploadButton: scope.uploadButtonText or "Upload"
-				dragAreaText = scope.dragAreaText or "drag here"
-				hintText = scope.hintText or ""
-
-				onComplete = scope.onCompleteCallback or () ->
-				onUpload   = scope.onUploadCallback or () ->
-				params     = scope.params or {}
-				params._csrf = window.csrfToken
-
-				new qq.FineUploader
-					element: element[0]
-					multiple: multiple
-					disabledCancelForFormUploads: true
-					validation: validation
-					request:
-						endpoint: endpoint
-						forceMultipart: true
-						params: params
-						paramsInBody: false
-					callbacks:
-						onComplete: onComplete
-						onUpload:   onUpload
-					text: text
-					template: """
-						<div class="qq-uploader">
-							<div class="qq-upload-drop-area"><span>{dragZoneText}</span></div>
-							<div class="qq-upload-button btn btn-primary btn-lg">
-								<div>{uploadButtonText}</div>
-							</div>
-							<span class="or btn-lg"> or </span>
-							<span class="drag-here btn-lg">#{dragAreaText}</span>
-							<span class="qq-drop-processing"><span>{dropProcessingText}</span><span class="qq-drop-processing-spinner"></span></span>
-							<div class="small">#{hintText}</div>
-							<ul class="qq-upload-list"></ul>
-						</div>
-					"""
-		}

+ 0 - 67
services/web/public/coffee/app/directives/focus.coffee

@@ -1,67 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.directive "focusWhen", ($timeout) ->
-		return {
-			restrict: "A"
-			link: (scope, element, attr) ->
-				scope.$watch attr.focusWhen, (value) ->
-					if value
-						$timeout ->
-							element.focus()
-		}
-
-	App.directive 'focusOn', ($timeout) ->
-		return {
-			restrict: 'A'
-			link: (scope, element, attrs) ->
-				scope.$on attrs.focusOn, () ->
-					element.focus()
-		}
-
-	App.directive "selectWhen", ($timeout) ->
-		return {
-			restrict: "A"
-			link: (scope, element, attr) ->
-				scope.$watch attr.selectWhen, (value) ->
-					if value
-						$timeout ->
-							element.select()
-		}
-
-	App.directive 'selectOn', ($timeout) ->
-		return {
-			restrict: 'A'
-			link: (scope, element, attrs) ->
-				scope.$on attrs.selectOn, () ->
-					element.select()
-		}
-
-	App.directive "selectNameWhen", ($timeout) ->
-		return {
-			restrict: 'A'
-			link: (scope, element, attrs) ->
-				scope.$watch attrs.selectNameWhen, (value) ->
-					if value
-						$timeout () ->
-							selectName(element)
-		}
-
-	App.directive "selectNameOn", () ->
-		return {
-			restrict: 'A'
-			link: (scope, element, attrs) ->
-				scope.$on attrs.selectNameOn, () ->
-					selectName(element)
-		}
-
-	selectName = (element) ->
-		# Select up to last '.'. I.e. everything
-		# except the file extension
-		element.focus()
-		name = element.val()
-		if element[0].setSelectionRange?
-			selectionEnd = name.lastIndexOf(".")
-			if selectionEnd == -1
-				selectionEnd = name.length
-			element[0].setSelectionRange(0, selectionEnd)

+ 0 - 10
services/web/public/coffee/app/directives/onEnter.coffee

@@ -1,10 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.directive 'onEnter', () ->
-		return (scope, element, attrs) ->
-			element.bind "keydown keypress", (event) ->
-				if event.which == 13
-					scope.$apply () ->
-						scope.$eval(attrs.onEnter, event: event)
-					event.preventDefault()

+ 0 - 67
services/web/public/coffee/app/directives/selectAll.coffee

@@ -1,67 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.directive "selectAllList", () ->
-		return {
-			controller: ["$scope", ($scope) ->
-				# Selecting or deselecting all should apply to all projects
-				selectAll = () ->
-					$scope.$broadcast "select-all:select"
-
-				deselectAll = () ->
-					$scope.$broadcast "select-all:deselect"
-
-				clearSelectAllState = () ->
-					$scope.$broadcast "select-all:clear"
-
-				return {
-					clearSelectAllState: clearSelectAllState
-					selectAll: selectAll
-					deselectAll: deselectAll
-				}
-			]
-			link: (scope, element, attrs) ->
-
-
-		}
-
-	App.directive "selectAll", () ->
-		return {
-			require: "^selectAllList"
-			link: (scope, element, attrs, selectAllListController) ->
-				scope.$on "select-all:clear", () ->
-					element.prop("checked", false)
-
-				element.change () ->
-					if element.is(":checked")
-						selectAllListController.selectAll()
-					else
-						selectAllListController.deselectAll()
-					return true
-		}
-
-	App.directive "selectIndividual", () ->
-		return {
-			require: "^selectAllList"
-			scope: {
-				ngModel: "="
-			}
-			link: (scope, element, attrs, selectAllListController) ->
-				ignoreChanges = false
-
-				scope.$watch "ngModel", (value) ->
-					if value? and !ignoreChanges
-						selectAllListController.clearSelectAllState()
-
-				scope.$on "select-all:select", () ->
-					ignoreChanges = true
-					scope.$apply () ->
-						scope.ngModel = true
-					ignoreChanges = false
-
-				scope.$on "select-all:deselect", () ->
-					ignoreChanges = true
-					scope.$apply () ->
-						scope.ngModel = false
-					ignoreChanges = false
-		}

+ 0 - 18
services/web/public/coffee/app/directives/stopPropagation.coffee

@@ -1,18 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.directive "stopPropagation", ($http) ->
-		return {
-			restrict: "A",
-			link: (scope, element, attrs) ->
-				element.bind attrs.stopPropagation, (e) ->
-					e.stopPropagation()
-		}
-
-	App.directive "preventDefault", ($http) ->
-		return {
-			restrict: "A",
-			link: (scope, element, attrs) ->
-				element.bind attrs.preventDefault, (e) ->
-					e.preventDefault()
-		}

+ 0 - 18
services/web/public/coffee/app/filters/formatDate.coffee

@@ -1,18 +0,0 @@
-define [
-	"base"
-], (App) ->
-	moment.lang "en", calendar:
-		lastDay : '[Yesterday]'
-		sameDay : '[Today]'
-		nextDay : '[Tomorrow]'
-		lastWeek : "ddd, Do MMM YY"
-		nextWeek : "ddd, Do MMM YY"
-		sameElse : 'ddd, Do MMM YY'
-
-	App.filter "formatDate", () ->
-		(date, format = "Do MMM YYYY, h:mm a") ->
-			moment(date).format(format)
-
-	App.filter "relativeDate", () ->
-		(date) ->
-			moment(date).calendar()

+ 0 - 70
services/web/public/coffee/app/ide.coffee

@@ -1,70 +0,0 @@
-define [
-	"base"
-	"ide/file-tree/FileTreeManager"
-	"ide/connection/ConnectionManager"
-	"ide/editor/EditorManager"
-	"ide/online-users/OnlineUsersManager"
-	"ide/track-changes/TrackChangesManager"
-	"ide/permissions/PermissionsManager"
-	"ide/pdf/PdfManager"
-	"ide/binary-files/BinaryFilesManager"
-	"ide/settings/index"
-	"ide/share/index"
-	"ide/chat/index"
-	"ide/directives/layout"
-	"ide/services/ide"
-	"directives/focus"
-	"directives/fineUpload"
-	"directives/onEnter"
-	"filters/formatDate"
-], (
-	App
-	FileTreeManager
-	ConnectionManager
-	EditorManager
-	OnlineUsersManager
-	TrackChangesManager
-	PermissionsManager
-	PdfManager
-	BinaryFilesManager
-) ->
-	App.controller "IdeController", ["$scope", "$timeout", "ide", ($scope, $timeout, ide) ->
-		# Don't freak out if we're already in an apply callback
-		$scope.$originalApply = $scope.$apply
-		$scope.$apply = (fn = () ->) ->
-			phase = @$root.$$phase
-			if (phase == '$apply' || phase == '$digest')
-				fn()
-			else
-				this.$originalApply(fn);
-
-		$scope.state = {
-			loading: true
-			load_progress: 40
-		}
-		$scope.ui = {
-			leftMenuShown: false
-			view: "editor"
-			chatOpen: false
-		}
-		$scope.user = window.user
-		$scope.settings = window.userSettings
-
-		$scope.chat = {}
-
-		window._ide = ide
-
-		ide.project_id = $scope.project_id = window.project_id
-		ide.$scope = $scope
-
-		ide.connectionManager = new ConnectionManager(ide, $scope)
-		ide.fileTreeManager = new FileTreeManager(ide, $scope)
-		ide.editorManager = new EditorManager(ide, $scope)
-		ide.onlineUsersManager = new OnlineUsersManager(ide, $scope)
-		ide.trackChangesManager = new TrackChangesManager(ide, $scope)
-		ide.pdfManager = new PdfManager(ide, $scope)
-		ide.permissionsManager = new PermissionsManager(ide, $scope)
-		ide.binaryFilesManager = new BinaryFilesManager(ide, $scope)
-	]
-
-	angular.bootstrap(document.body, ["SharelatexApp"])

+ 0 - 12
services/web/public/coffee/app/ide/binary-files/BinaryFilesManager.coffee

@@ -1,12 +0,0 @@
-define [
-	"ide/binary-files/controllers/BinaryFileController"
-], () ->
-	class BinaryFilesManager
-		constructor: (@ide, @$scope) ->
-			@$scope.$on "entity:selected", (event, entity) =>
-				if (@$scope.ui.view != "track-changes" and entity.type == "file")
-					@openFile(entity)
-
-		openFile: (file) ->
-			@$scope.ui.view = "file"
-			@$scope.openFile = file

+ 0 - 7
services/web/public/coffee/app/ide/binary-files/controllers/BinaryFileController.coffee

@@ -1,7 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "BinaryFileController", ["$scope", ($scope) ->
-		$scope.extension = (file) ->
-			return file.name.split(".").pop()?.toLowerCase()
-	]

+ 0 - 7
services/web/public/coffee/app/ide/chat/controllers/ChatButtonController.coffee

@@ -1,7 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "ChatButtonController", ["$scope", ($scope) ->
-		$scope.toggleChat = () ->
-			$scope.ui.chatOpen = !$scope.ui.chatOpen
-	]

+ 0 - 64
services/web/public/coffee/app/ide/chat/controllers/ChatController.coffee

@@ -1,64 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "ChatController", ["$scope", ($scope) ->
-		james = {
-			id: $scope.user.id
-			first_name: "James"
-			last_name: "Allen"
-			email: "james@sharelatex.com"
-		}
-		james2 = {
-			id: "james2-id"
-			first_name: "James"
-			last_name: "Allen"
-			email: "jamesallen0108@gmail.com"
-		}
-		henry = {
-			id: "henry-id"
-			first_name: "Henry"
-			last_name: "Oswald"
-			email: "henry.oswald@sharelatex.com"
-		}
-		$scope.chat.messages = [
-			{ content: "Hello world", timestamp: Date.now() - 2000, user: james2 }
-			{ content: "Hello, this is the new chat", timestamp: Date.now() - 4000, user: james }
-			{ content: "Here are some longer messages to show what it looks like when I say a lot!", timestamp: Date.now() - 20000, user: henry }
-			{ content: "What about some maths? $x^2 = 1$?", timestamp: Date.now() - 22000, user: james2 }
-			{ content: "Nope, that doesn't work yet!", timestamp: Date.now() - 45000, user: henry }
-			{ content: "I'm running out of things to say.", timestamp: Date.now() - 56000, user: henry }
-			{ content: "Yep, me too", timestamp: Date.now() - 100000, user: james }
-			{ content: "Hmm, looks like we've had this conversation backwards", timestamp: Date.now() - 120000, user: james }
-			{ content: "Hello world", timestamp: Date.now() - 202000, user: james2 }
-			{ content: "Hello, this is the new chat", timestamp: Date.now() - 204000, user: james }
-			{ content: "Here are some longer messages to show what it looks like when I say a lot!", timestamp: Date.now() - 2020000, user: henry }
-			{ content: "What about some maths? $x^2 = 1$?", timestamp: Date.now() - 12022000, user: james2 }
-			{ content: "Nope, that doesn't work yet!", timestamp: Date.now() - 12045000, user: henry }
-			{ content: "I'm running out of things to say.", timestamp: Date.now() - 22056000, user: henry }
-			{ content: "Yep, me too", timestamp: Date.now() - 220100000, user: james }
-			{ content: "Hmm, looks like we've had this conversation backwards", timestamp: Date.now() - 520120000, user: james }
-		].reverse()
-
-		$scope.$watch "chat.messages", (messages) ->
-			if messages?
-				$scope.chat.groupedMessages = groupMessages(messages)
-
-		TIMESTAMP_GROUP_SIZE = 5 * 60 * 1000 # 5 minutes
-		groupMessages = (messages) ->
-			previousMessage = null
-			groupedMessages = []
-			for message in messages
-				shouldGroup = previousMessage? and
-					previousMessage.user == message.user and
-					message.timestamp - previousMessage.timestamp < TIMESTAMP_GROUP_SIZE
-				if shouldGroup
-					previousMessage.timestamp = message.timestamp
-					previousMessage.contents.push message.content
-				else
-					groupedMessages.push(previousMessage = {
-						user: message.user
-						timestamp: message.timestamp
-						contents: [message.content]
-					})
-			return groupedMessages
-	]

+ 0 - 12
services/web/public/coffee/app/ide/chat/controllers/ChatMessageController.coffee

@@ -1,12 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "ChatMessageController", ["$scope", "ide", ($scope, ide) ->
-		$scope.gravatarUrl = (user) ->
-			email = user.email.trim().toLowerCase()
-			hash = CryptoJS.MD5(email).toString()
-			return "//www.gravatar.com/avatar/#{hash}?d=mm&s=50"
-
-		$scope.hue = (user) ->
-			ide.onlineUsersManager.getHueForUserId(user.id)
-	]

+ 0 - 5
services/web/public/coffee/app/ide/chat/index.coffee

@@ -1,5 +0,0 @@
-define [
-	"ide/chat/controllers/ChatButtonController"
-	"ide/chat/controllers/ChatController"
-	"ide/chat/controllers/ChatMessageController"
-], () ->

+ 0 - 113
services/web/public/coffee/app/ide/connection/ConnectionManager.coffee

@@ -1,113 +0,0 @@
-define [], () ->
-	class ConnectionManager
-		constructor: (@ide, @$scope) ->
-			@connected = false
-
-			@$scope.connection = 
-				reconnecting: false
-				# If we need to force everyone to reload the editor
-				forced_disconnect: false
-
-			@$scope.tryReconnectNow = () =>
-				@tryReconnect()
-
-			@ide.socket = io.connect null,
-				reconnect: false
-				"force new connection": true
-
-			@ide.socket.on "connect", () =>
-				@connected = true
-				@ide.pushEvent("connected")
-
-				@$scope.$apply () =>
-					@$scope.connection.reconnecting = false
-					if @$scope.state.loading
-						@$scope.state.load_progress = 80
-
-				setTimeout(() =>
-					@joinProject()
-				, 100)
-
-			@ide.socket.on 'disconnect', () =>
-				@connected = false
-				@ide.pushEvent("disconnected")
-
-				@$scope.$apply () =>
-					@$scope.connection.reconnecting = false
-
-				setTimeout(=>
-					ga('send', 'event', 'editor-interaction', 'disconnect')
-				, 2000)
-
-				if !$scope.connection.forced_disconnect
-					@startAutoReconnectCountdown()
-
-			@ide.socket.on 'forceDisconnect', (message) =>
-				@$scope.$apply () =>
-					@$scope.connection.forced_disconnect = true
-				@socket.disconnect()
-
-		joinProject: () ->
-			@ide.socket.emit 'joinProject', {
-				project_id: @ide.project_id
-			}, (err, project, permissionsLevel, protocolVersion) =>
-				if @$scope.protocolVersion? and @$scope.protocolVersion != protocolVersion
-					location.reload(true)
-
-				@$scope.$apply () =>
-					@$scope.protocolVersion = protocolVersion
-					@$scope.project = project
-					@$scope.permissionsLevel = permissionsLevel
-					@$scope.state.load_progress = 100
-					@$scope.state.loading = false
-					@$scope.$emit "project:joined"
-
-		reconnectImmediately: () ->
-			@disconnect()
-			@tryReconnect()
-
-		disconnect: () ->
-			@ide.socket.disconnect()
-
-		startAutoReconnectCountdown: () ->
-			lastUpdated = @ide.editorManager.lastUpdated()
-
-			twoMinutes = 2 * 60 * 1000
-			if lastUpdated? and new Date() - lastUpdated > twoMinutes
-				# between 1 minute and 3 minutes
-				countdown = 60 + Math.floor(Math.random() * 120)
-			else
-				countdown = 3 + Math.floor(Math.random() * 7)
-
-			@$scope.$apply () =>
-				@$scope.connection.reconnecting = false
-				@$scope.connection.reconnection_countdown = countdown
-
-			setTimeout(=>
-				if !@connected
-					@timeoutId = setTimeout (=> @decreaseCountdown()), 1000
-			, 200)
-
-		cancelReconnect: () ->
-			clearTimeout @timeoutId if @timeoutId?
-					
-		decreaseCountdown: () ->
-			console.log "Decreasing countdown"
-			return if !@$scope.connection.reconnection_countdown?
-			@$scope.$apply () =>
-				@$scope.connection.reconnection_countdown--
-
-			if @$scope.connection.reconnection_countdown <= 0
-				@$scope.$apply () =>
-					@tryReconnect()
-			else
-				@timeoutId = setTimeout (=> @decreaseCountdown()), 1000
-
-		tryReconnect: () ->
-			console.log "Trying reconnect"
-			@cancelReconnect()
-			@$scope.connection.reconnecting = true
-			delete @$scope.connection.reconnection_countdown
-			@ide.socket.socket.reconnect()
-			setTimeout (=> @startAutoReconnectCountdown() if !@connected), 2000
-

+ 0 - 101
services/web/public/coffee/app/ide/directives/layout.coffee

@@ -1,101 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.directive "layout", ["$parse", ($parse) ->
-		return {
-			compile: () ->
-				pre: (scope, element, attrs) ->
-					name = attrs.layout
-
-					if attrs.spacingOpen?
-						spacingOpen = parseInt(attrs.spacingOpen, 10)
-					else
-						spacingOpen = 24
-
-					if attrs.spacingClosed?
-						spacingClosed = parseInt(attrs.spacingClosed, 10)
-					else
-						spacingClosed = 24
-
-					options =
-						spacing_open: spacingOpen
-						spacing_closed: spacingClosed
-						slidable: false
-						onresize: () =>
-							onInternalResize()
-						maskIframesOnResize: scope.$eval(
-							attrs.maskIframesOnResize or "false"
-						)
-						east:
-							size: scope.$eval(attrs.initialSizeEast)
-							initClosed: scope.$eval(attrs.initClosedEast)
-						west:
-							size: scope.$eval(attrs.initialSizeEast)
-							initClosed: scope.$eval(attrs.initClosedWest)
-
-					# Restore previously recorded state
-					if (state = $.localStorage("layout.#{name}"))?
-						options.west = state.west
-						options.east = state.east
-
-					# Someone moved the resizer
-					onInternalResize = () ->
-						state = element.layout().readState()
-						scope.$broadcast "layout:#{name}:resize", state
-						repositionControls()
-						resetOpenStates()
-
-					oldWidth = element.width()
-					# Something resized our parent element
-					onExternalResize = () ->
-						console.log "EXTERNAL RESIOZE", name, attrs.resizeProportionally
-						if attrs.resizeProportionally? and scope.$eval(attrs.resizeProportionally)
-							eastState = element.layout().readState().east
-							if eastState?
-								newInternalWidth = eastState.size / oldWidth * element.width()
-								oldWidth = element.width()
-								element.layout().sizePane("east", newInternalWidth)
-								return
-							
-						element.layout().resizeAll()
-
-					element.layout options
-					element.layout().resizeAll()
-
-					if attrs.resizeOn?
-						scope.$on attrs.resizeOn, () -> onExternalResize()
-
-					# Save state when exiting
-					$(window).unload () ->
-						$.localStorage("layout.#{name}", element.layout().readState())
-
-					repositionControls = () ->
-						state = element.layout().readState()
-						if state.east?
-							element.find("> .ui-layout-resizer-controls").css({
-								position: "absolute"
-								right: state.east.size
-								"z-index": 10
-							})
-
-					resetOpenStates = () ->
-						state = element.layout().readState()
-						if attrs.openEast?
-							openEast = $parse(attrs.openEast)
-							openEast.assign(scope, !state.east.initClosed)
-
-					if attrs.openEast?
-						scope.$watch attrs.openEast, (value, oldValue) ->
-							console.log "Open East", value, oldValue
-							if value? and value != oldValue
-								if value
-									element.layout().open("east")
-								else
-									element.layout().close("east")
-							setTimeout () ->
-								scope.$digest()
-							, 0
-
-					resetOpenStates()
-		}
-	]

+ 0 - 254
services/web/public/coffee/app/ide/editor/Document.coffee

@@ -1,254 +0,0 @@
-define [
-	"utils/EventEmitter"
-	"ide/editor/ShareJsDoc"
-	"underscore"
-], (EventEmitter, ShareJsDoc) ->
-	class Document extends EventEmitter
-		@getDocument: (ide, doc_id) ->
-			@openDocs ||= {}
-			if !@openDocs[doc_id]?
-				@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, @)
-			@inconsistentCount = 0
-			@_bindToEditorEvents()
-			@_bindToSocketEvents()
-
-		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: () ->
-			# We've been seeing a lot of errors when I think there shouldn't be
-			# any, which may be related to this check happening before the change is
-			# applied. If we use a timeout, hopefully we can reduce this.
-			setTimeout () =>
-				editorValue = @ace?.getValue()
-				sharejsValue = @doc?.getSnapshot()
-				if editorValue != sharejsValue
-					@inconsistentCount++
-				else
-					@inconsistentCount = 0
-
-				if @inconsistentCount >= 3
-					@_onError new Error("Editor text does not match server text")
-			, 0
-
-		getSnapshot: () ->
-			@doc?.getSnapshot()
-
-		getType: () ->
-			@doc?.getType()
-
-		getInflightOp: () ->
-			@doc?.getInflightOp()
-
-		getPendingOp: () ->
-			@doc?.getPendingOp()
-
-		hasBufferedOps: () ->
-			@doc?.hasBufferedOps()
-
-		_bindToSocketEvents: () ->
-			@_onUpdateAppliedHandler = (update) => @_onUpdateApplied(update)
-			@ide.socket.on "otUpdateApplied", @_onUpdateAppliedHandler
-			@_onErrorHandler = (error, update) => @_onError(error, update)
-			@ide.socket.on "otUpdateError", @_onErrorHandler
-			@_onDisconnectHandler = (error) => @_onDisconnect(error)
-			@ide.socket.on "disconnect", @_onDisconnectHandler
-
-		_bindToEditorEvents: () ->
-			onReconnectHandler = (update) =>
-				@_onReconnect(update)
-			@_unsubscribeReconnectHandler = @ide.$scope.$on "project:joined", onReconnectHandler
-
-		_unBindFromEditorEvents: () ->
-			@_unsubscribeReconnectHandler()
-
-		_unBindFromSocketEvents: () ->
-			@ide.socket.removeListener "otUpdateApplied", @_onUpdateAppliedHandler
-			@ide.socket.removeListener "otUpdateError", @_onUpdateErrorHandler
-			@ide.socket.removeListener "disconnect", @_onDisconnectHandler
-
-		leaveAndCleanUp: () ->
-			@leave (error) =>
-				@_cleanUp()
-
-		join: (callback = (error) ->) ->
-			@wantToBeJoined = true
-			@_cancelLeave()
-			if @connected
-				return @_joinDoc callback
-			else
-				@_joinCallbacks ||= []
-				@_joinCallbacks.push callback
-
-		leave: (callback = (error) ->) ->
-			@wantToBeJoined = false
-			@_cancelJoin()
-			if (@doc? and @doc.hasBufferedOps())
-				@_leaveCallbacks ||= []
-				@_leaveCallbacks.push callback
-			else if !@connected
-				callback()
-			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
-
-		_cancelJoin: () ->
-			if @_joinCallbacks?
-				delete @_joinCallbacks
-
-		_onUpdateApplied: (update) ->
-			@ide.pushEvent "received-update",
-				doc_id: @doc_id
-				remote_doc_id: update?.doc
-				wantToBeJoined: @wantToBeJoined
-				update: update
-
-			if Math.random() < (@ide.disconnectRate or 0)
-				console.log "Simulating disconnect"
-				@ide.connectionManager.disconnect()
-				return
-
-			if Math.random() < (@ide.ignoreRate or 0)
-				console.log "Simulating lost update"
-				return
-
-			if update?.doc == @doc_id and @doc?
-				@doc.processUpdateFromServer update
-
-				if !@wantToBeJoined
-					@leave()
-
-		_onDisconnect: () ->
-			@connected = false
-			@joined = false
-			@doc?.updateConnectionState "disconnected"
-
-		_onReconnect: () ->
-			@ide.pushEvent "reconnected:afterJoinProject"
-
-			@connected = true
-			if @wantToBeJoined or @doc?.hasBufferedOps()
-				@_joinDoc (error) =>
-					return @_onError(error) if error?
-					@doc.updateConnectionState "ok"
-					@doc.flushPendingOps()
-					@_callJoinCallbacks()
-
-		_callJoinCallbacks: () ->
-			for callback in @_joinCallbacks or []
-				callback()
-			delete @_joinCallbacks
-
-		_joinDoc: (callback = (error) ->) ->
-			if @doc?
-				@ide.socket.emit 'joinDoc', @doc_id, @doc.getVersion(), (error, docLines, version, updates) =>
-					return callback(error) if error?
-					@joined = true
-					@doc.catchUp( updates )
-					callback()
-			else
-				@ide.socket.emit 'joinDoc', @doc_id, (error, docLines, version) =>
-					return callback(error) if error?
-					@joined = true
-					@doc = new ShareJsDoc @doc_id, docLines, version, @ide.socket
-					@_bindToShareJsDocEvents()
-					callback()
-
-		_leaveDoc: (callback = (error) ->) ->
-			@ide.socket.emit 'leaveDoc', @doc_id, (error) =>
-				return callback(error) if error?
-				@joined = false
-				for callback in @_leaveCallbacks or []
-					callback(error)
-				delete @_leaveCallbacks
-				callback(error)
-
-		_cleanUp: () ->
-			delete Document.openDocs[@doc_id]
-			@_unBindFromEditorEvents()
-			@_unBindFromSocketEvents()
-
-		_bindToShareJsDocEvents: () ->
-			@doc.on "error", (error, meta) => @_onError error, meta
-			@doc.on "externalUpdate", () => 
-				@ide.pushEvent "externalUpdate",
-					doc_id: @doc_id
-				@trigger "externalUpdate"
-			@doc.on "remoteop", () => 
-				@ide.pushEvent "remoteop",
-					doc_id: @doc_id
-				@trigger "remoteop"
-			@doc.on "op:sent", (op) =>
-				@ide.pushEvent "op:sent",
-					doc_id: @doc_id
-					op: op
-				@trigger "op:sent"
-			@doc.on "op:acknowledged", (op) =>
-				@ide.pushEvent "op:acknowledged",
-					doc_id: @doc_id
-					op: op
-				@trigger "op:acknowledged"
-			@doc.on "op:timeout", (op) =>
-				@ide.pushEvent "op:timeout",
-					doc_id: @doc_id
-					op: op
-				@trigger "op:timeout"
-				ga?('send', 'event', 'error', "op timeout", "Op was now acknowledged - #{@ide.socket.socket.transport.name}" )
-				@ide.connectionManager.reconnectImmediately()
-			@doc.on "flush", (inflightOp, pendingOp, version) =>
-				@ide.pushEvent "flush",
-					doc_id: @doc_id,
-					inflightOp: inflightOp,
-					pendingOp: pendingOp
-					v: version
-
-		_onError: (error, meta = {}) ->
-			console.error "ShareJS error", error, meta
-			ga?('send', 'event', 'error', "shareJsError", "#{error.message} - #{@ide.socket.socket.transport.name}" )
-			@ide.socket.disconnect()
-			meta.doc_id = @doc_id
-			@ide.reportError(error, meta)
-			@doc?.clearInflightAndPendingOps()
-			@_cleanUp()
-			@trigger "error", error

+ 0 - 103
services/web/public/coffee/app/ide/editor/EditorManager.coffee

@@ -1,103 +0,0 @@
-define [
-	"ide/editor/Document"
-	"ide/editor/directives/aceEditor"
-	"ide/editor/controllers/SavingNotificationController"
-], (Document) ->
-	class EditorManager
-		constructor: (@ide, @$scope) ->
-			@$scope.editor = {
-				sharejs_doc: null
-				last_updated: null
-				open_doc_id: null
-				opening: true
-				cursorPosition: {}
-				gotoLine: null
-			}
-
-			@$scope.$on "entity:selected", (event, entity) =>
-				if (@$scope.ui.view != "track-changes" and entity.type == "doc")
-					@openDoc(entity)
-
-			initialized = false
-			@$scope.$on "file-tree:initialized", () =>
-				if !initialized
-					initialized = true
-					@autoOpenDoc()
-
-		autoOpenDoc: () ->
-			open_doc_id = 
-				$.localStorage("doc.open_id.#{@$scope.project_id}") or
-				@$scope.project.rootDoc_id
-			return if !open_doc_id?
-			doc = @ide.fileTreeManager.findEntityById(open_doc_id)
-			return if !doc?
-			@openDoc(doc)
-
-		openDoc: (doc, options = {}) ->
-			@$scope.ui.view = "editor"
-
-			done = () =>
-				if options.gotoLine?
-					@$scope.editor.gotoLine = options.gotoLine
-			
-			if doc.id == @$scope.editor.open_doc_id and !options.forceReopen
-				@$scope.$apply () =>
-					done()
-				return
-
-			@$scope.editor.open_doc_id = doc.id
-
-			$.localStorage "doc.open_id.#{@$scope.project_id}", doc.id
-			@ide.fileTreeManager.selectEntity(doc)
-
-			@$scope.editor.opening = true
-			@_openNewDocument doc, (error, sharejs_doc) =>
-				if error?
-					@ide.showGenericServerErrorMessage()
-					return
-
-				@$scope.$broadcast "doc:opened"
-
-				@$scope.$apply () =>
-					@$scope.editor.opening = false
-					@$scope.editor.sharejs_doc = sharejs_doc
-					done()
-
-		_openNewDocument: (doc, callback = (error, sharejs_doc) ->) ->
-			current_sharejs_doc = @$scope.editor.sharejs_doc
-			if current_sharejs_doc?
-				current_sharejs_doc.leaveAndCleanUp()
-				@_unbindFromDocumentEvents(current_sharejs_doc)
-
-			new_sharejs_doc = Document.getDocument @ide, doc.id
-
-			new_sharejs_doc.join (error) =>
-				return callback(error) if error?
-				@_bindToDocumentEvents(doc, new_sharejs_doc)
-				callback null, new_sharejs_doc
-
-		_bindToDocumentEvents: (doc, sharejs_doc) ->
-			sharejs_doc.on "error", (error) =>
-				@openDoc(doc, forceReopen: true)
-				@ide.showGenericMessageModal(
-					"Out of sync"
-					"Sorry, this file has gone out of sync and we need to do a full refresh. Please let us know if this happens frequently."
-				)
-
-			sharejs_doc.on "externalUpdate", () =>
-				@ide.showGenericMessageModal(
-					"Document Updated Externally"
-					"This document was just updated externally. Any recent changes you have made may have been overwritten. To see previous versions please look in the history."
-				)
-
-		_unbindFromDocumentEvents: (document) ->
-			document.off()
-
-		lastUpdated: () ->
-			@$scope.editor.last_updated
-
-		getCurrentDocValue: () ->
-			@$scope.editor.sharejs_doc?.getSnapshot()
-
-		getCurrentDocId: () ->
-			@$scope.editor.open_doc_id

+ 0 - 123
services/web/public/coffee/app/ide/editor/ShareJsDoc.coffee

@@ -1,123 +0,0 @@
-define [
-	"utils/EventEmitter"
-	"../../../libs/sharejs"
-], (EventEmitter, ShareJs) ->
-	class ShareJsDoc extends EventEmitter
-		constructor: (@doc_id, docLines, version, @socket) ->
-			# Dencode any binary bits of data
-			# See http://ecmanaut.blogspot.co.uk/2006/07/encoding-decoding-utf8-in-javascript.html
-			@type = "text"
-			docLines = for line in docLines
-				if line.text?
-					@type = "json"
-					line.text = decodeURIComponent(escape(line.text))
-				else
-					@type = "text"
-					line = decodeURIComponent(escape(line))
-				line
-
-			if @type == "text"
-				snapshot = docLines.join("\n")
-			else if @type == "json"
-				snapshot = { lines: docLines }
-			else
-				throw new Error("Unknown type: #{@type}")
-
-			@connection = {
-				send: (update) =>
-					@_startInflightOpTimeout(update)
-					@socket.emit "applyOtUpdate", @doc_id, update
-				state: "ok"
-				id:    @socket.socket.sessionid
-			}
-
-			@_doc = new ShareJs.Doc @connection, @doc_id,
-				type: @type
-			@_doc.on "change", () =>
-				@trigger "change"
-			@_doc.on "acknowledge", () =>
-				@trigger "acknowledge"
-			@_doc.on "remoteop", () =>
-				@trigger "remoteop"
-
-			@_bindToDocChanges(@_doc)
-
-			@processUpdateFromServer
-				open: true
-				v: version
-				snapshot: snapshot
-
-		submitOp: (args...) -> @_doc.submitOp(args...)
-
-		processUpdateFromServer: (message) ->
-			try
-				@_doc._onMessage message
-			catch error
-				# Version mismatches are thrown as errors
-				@_handleError(error)
-
-			if message?.meta?.type == "external"
-				@trigger "externalUpdate"
-
-		catchUp: (updates) ->
-			for update, i in updates
-				update.v   = @_doc.version
-				update.doc = @doc_id
-				@processUpdateFromServer(update)
-
-		getSnapshot: () -> @_doc.snapshot
-		getVersion: () -> @_doc.version
-		getType: () -> @type
-
-		clearInflightAndPendingOps: () ->
-			@_doc.inflightOp = null
-			@_doc.inflightCallbacks = []
-			@_doc.pendingOp = null
-			@_doc.pendingCallbacks = []
-
-		flushPendingOps: () ->
-			# This will flush any ops that are pending.
-			# If there is an inflight op it will do nothing.
-			@_doc.flush()
-
-		updateConnectionState: (state) ->
-			@connection.state = state
-			@connection.id = @socket.socket.sessionid
-			@_doc.autoOpen = false
-			@_doc._connectionStateChanged(state)
-
-		hasBufferedOps: () ->
-			@_doc.inflightOp? or @_doc.pendingOp?
-
-		getInflightOp: () -> @_doc.inflightOp
-		getPendingOp: () -> @_doc.pendingOp
-
-		attachToAce: (ace) -> @_doc.attach_ace(ace)
-		detachFromAce: () -> @_doc.detach_ace?()
-	
-		INFLIGHT_OP_TIMEOUT: 10000
-		_startInflightOpTimeout: (update) ->
-			meta =
-				v: update.v
-				op_sent_at: new Date()
-			timer = setTimeout () =>
-				@trigger "op:timeout", update
-			, @INFLIGHT_OP_TIMEOUT
-			@_doc.inflightCallbacks.push () =>
-				clearTimeout timer
-
-		_handleError: (error, meta = {}) ->
-			@trigger "error", error, meta
-
-		_bindToDocChanges: (doc) ->
-			submitOp = doc.submitOp
-			doc.submitOp = (args...) =>
-				@trigger "op:sent", args...
-				doc.pendingCallbacks.push () =>
-					@trigger "op:acknowledged", args...
-				submitOp.apply(doc, args)
-
-			flush = doc.flush
-			doc.flush = (args...) =>
-				@trigger "flush", doc.inflightOp, doc.pendingOp, doc.version
-				flush.apply(doc, args)

+ 0 - 95
services/web/public/coffee/app/ide/editor/auto-complete/AutoCompleteManager.coffee

@@ -1,95 +0,0 @@
-define [
-	"ide/editor/auto-complete/SuggestionManager"
-	"ide/editor/auto-complete/Snippets"
-	"ace/autocomplete/util"
-	"ace/autocomplete"
-	"ace/range"
-	"ace/ext/language_tools"
-], (SuggestionManager, Snippets, Util, AutoComplete) ->
-	Range = require("ace/range").Range
-	Autocomplete = AutoComplete.Autocomplete
-
-	Util.retrievePrecedingIdentifier = (text, pos, regex) ->
-		currentLineOffset = 0
-		for i in [(pos-1)..0]
-			if text[i] == "\n"
-				currentLineOffset = i + 1
-				break
-		currentLine = text.slice(currentLineOffset, pos)
-		fragment = getLastCommandFragment(currentLine) or ""
-		return fragment
-
-	getLastCommandFragment = (lineUpToCursor) ->
-		if m = lineUpToCursor.match(/(\\[^\\ ]+)$/)
-			return m[1]
-		else
-			return null
-
-	class AutoCompleteManager
-		constructor: (@$scope, @editor) ->
-			@suggestionManager = new SuggestionManager()
-
-			if !Autocomplete::_insertMatch?
-				# Only override this once since it's global but we may create multiple
-				# autocomplete handlers
-				Autocomplete::_insertMatch = Autocomplete::insertMatch
-				Autocomplete::insertMatch = (data) ->
-					pos = editor.getCursorPosition()
-					range = new Range(pos.row, pos.column, pos.row, pos.column + 1)
-					nextChar = editor.session.getTextRange(range)
-
-					console.log "INSERT MATCH", this
-
-					# If we are in \begin{it|}, then we need to remove the trailing }
-					# since it will be adding in with the autocomplete of \begin{item}...
-					if this.completions.filterText.match(/^\\begin\{/) and nextChar == "}"
-						editor.session.remove(range)
-					
-					Autocomplete::_insertMatch.call this, data
-
-			@$scope.$watch "autoComplete", (autocomplete) =>
-				console.log "autocomplete change", autocomplete
-				if autocomplete
-					@enable()
-				else
-					@disable()
-
-
-			@editor.on "changeSession", (e) =>
-				@bindToSession(e.session)
-
-		enable: () ->
-			@editor.setOptions({
-				enableBasicAutocompletion: true,
-				enableSnippets: true
-			})
-			
-			SnippetCompleter =
-				getCompletions: (editor, session, pos, prefix, callback) ->
-					callback null, Snippets
-			@editor.completers = [@suggestionManager, SnippetCompleter]
-
-		disable: () ->
-			@editor.setOptions({
-				enableBasicAutocompletion: false,
-				enableSnippets: false
-			})
-
-		bindToSession: (@aceSession) ->
-			@aceSession.on "change", (change) => @onChange(change)
-
-		onChange: (change) ->
-			cursorPosition = @editor.getCursorPosition()
-			end = change.data.range.end
-			# Check that this change was made by us, not a collaborator
-			# (Cursor is still one place behind)
-			if end.row == cursorPosition.row and end.column == cursorPosition.column + 1
-				if change.data.action == "insertText"
-					range = new Range(end.row, 0, end.row, end.column)
-					lineUpToCursor = @aceSession.getTextRange(range)
-					commandFragment = getLastCommandFragment(lineUpToCursor)
-
-					if commandFragment? and commandFragment.length > 2
-					 	setTimeout () =>
-					 		@editor.execCommand("startAutocomplete")
-					 	, 0

+ 0 - 100
services/web/public/coffee/app/ide/editor/auto-complete/Snippets.coffee

@@ -1,100 +0,0 @@
-define () ->
-	environments = [
-		"abstract", 
-		"align", "align*",
-		"equation", "equation*",
-		"gather", "gather*",
-		"multline", "multline*",
-		"split",
-		"verbatim"
-	]
-
-	snippets = for env in environments
-		{
-			caption: "\\begin{#{env}}..."
-			snippet: """
-				\\begin{#{env}}
-				$1
-				\\end{#{env}}
-			"""
-			meta: "env"
-		}
-
-	snippets = snippets.concat [{
-		caption: "\\begin{array}..."
-		snippet: """
-			\\begin{array}{${1:cc}}
-			$2 & $3 \\\\\\\\
-			$4 & $5
-			\\end{array}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{figure}..."
-		snippet: """
-			\\begin{figure}
-			\\centering
-			\\includegraphics{$1}
-			\\caption{${2:Caption}}
-			\\label{${3:fig:my_label}}
-			\\end{figure}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{tabular}..."
-		snippet: """
-			\\begin{tabular}{${1:c|c}}
-			$2 & $3 \\\\\\\\
-			$4 & $5
-			\\end{tabular}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{table}..."
-		snippet: """
-			\\begin{table}[$1]
-			\\centering
-			\\begin{tabular}{${2:c|c}}
-			$3 & $4 \\\\\\\\
-			$5 & $6
-			\\end{tabular}
-			\\caption{${7:Caption}}
-			\\label{${8:tab:my_label}}
-			\\end{table}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{list}..."
-		snippet: """
-			\\begin{list}
-			\\item $1
-			\\end{list}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{enumerate}..."
-		snippet: """
-			\\begin{enumerate}
-			\\item $1
-			\\end{enumerate}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{itemize}..."
-		snippet: """
-			\\begin{itemize}
-			\\item $1
-			\\end{itemize}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{frame}..."
-		snippet: """
-			\\begin{frame}{${1:Frame Title}}
-			$2
-			\\end{frame}
-		"""
-		meta: "env"
-	}]
-
-	return snippets

+ 0 - 126
services/web/public/coffee/app/ide/editor/auto-complete/SuggestionManager.coffee

@@ -1,126 +0,0 @@
-define [], () ->
-	class Parser
-		constructor: (@doc) ->
-
-		parse: () ->
-			commands = []
-			seen = {}
-			while command = @nextCommand()
-				docState = @doc
-
-				optionalArgs = 0
-				while @consumeArgument("[", "]")
-					optionalArgs++
-
-				args = 0
-				while @consumeArgument("{", "}")
-					args++
-
-				commandHash = "#{command}\\#{optionalArgs}\\#{args}"
-				if !seen[commandHash]?
-					seen[commandHash] = true
-					commands.push [command, optionalArgs, args]
-
-				# Reset to before argument to handle nested commands
-				@doc = docState
-
-			return commands
-
-		# Ignore single letter commands since auto complete is moot then.
-		commandRegex: /\\([a-zA-Z][a-zA-Z]+)/
-		
-		nextCommand: () ->
-			i = @doc.search(@commandRegex)
-			if i == -1
-				return false
-			else
-				match = @doc.match(@commandRegex)[1]
-				@doc = @doc.substr(i + match.length + 1)
-				return match
-
-		consumeWhitespace: () ->
-			match = @doc.match(/^[ \t\n]*/m)[0]
-			@doc = @doc.substr(match.length)
-
-		consumeArgument: (openingBracket, closingBracket) ->
-			@consumeWhitespace()
-
-			if @doc[0] == openingBracket
-				i = 1
-				bracketParity = 1
-				while bracketParity > 0 and i < @doc.length
-					if @doc[i] == openingBracket
-						bracketParity++
-					else if @doc[i] == closingBracket
-						bracketParity--
-					i++
-
-				if bracketParity == 0
-					@doc = @doc.substr(i)
-					return true
-				else
-					return false
-			else
-				return false
-
-	class SuggestionManager
-		getCompletions: (editor, session, pos, prefix, callback) ->
-			doc = session.getValue()
-			parser = new Parser(doc)
-			commands = parser.parse()
-
-			completions = []
-			for command in commands
-				caption = "\\#{command[0]}"
-				snippet = caption
-				i = 1
-				_.times command[1], () ->
-					snippet += "[${#{i}}]"
-					caption += "[]"
-					i++
-				_.times command[2], () ->
-					snippet += "{${#{i}}}"
-					caption += "{}"
-					i++
-				unless caption == prefix
-					completions.push {
-						caption: caption
-						snippet: snippet
-						meta: "cmd"
-					}
-
-			callback null, completions
-
-		loadCommandsFromDoc: (doc) ->
-			parser = new Parser(doc)
-			@commands = parser.parse()
-
-		getSuggestions: (commandFragment) ->
-			matchingCommands = _.filter @commands, (command) ->
-				command[0].slice(0, commandFragment.length) == commandFragment
-
-			return _.map matchingCommands, (command) ->
-				base = "\\" + commandFragment
-
-				args = ""
-				_.times command[1], () -> args = args + "[]"
-				_.times command[2], () -> args = args + "{}"
-				completionBase = command[0].slice(commandFragment.length)
-
-				squareArgsNo = command[1]
-				curlyArgsNo = command[2]
-				totalArgs = squareArgsNo + curlyArgsNo
-				if totalArgs == 0
-					completionBeforeCursor = completionBase
-					completionAfterCurspr = ""
-				else
-					completionBeforeCursor = completionBase + args[0]
-					completionAfterCursor = args.slice(1)
-
-				return {
-					base: base,
-					completion: completionBase + args,
-					completionBeforeCursor: completionBeforeCursor
-					completionAfterCursor: completionAfterCursor
-				}
-

+ 0 - 33
services/web/public/coffee/app/ide/editor/controllers/SavingNotificationController.coffee

@@ -1,33 +0,0 @@
-define [
-	"base"
-	"ide/editor/Document"
-], (App, Document) ->
-	App.controller "SavingNotificationController", ["$scope", "$interval", "ide", ($scope, $interval, ide) ->
-		$interval () ->
-			pollSavedStatus()
-		, 1000
-
-		$(window).bind 'beforeunload', () =>
-			warnAboutUnsavedChanges()
-
-		$scope.docSavingStatus = {}
-		pollSavedStatus = () ->
-			oldStatus = $scope.docSavingStatus
-			$scope.docSavingStatus = {}
-
-			for doc_id, doc of Document.openDocs
-				saving = doc.pollSavedStatus()
-				if !saving
-					if oldStatus[doc_id]?
-						$scope.docSavingStatus[doc_id] = oldStatus[doc_id]
-						$scope.docSavingStatus[doc_id].unsavedSeconds += 1
-					else
-						$scope.docSavingStatus[doc_id] = {
-							unsavedSeconds: 0
-							doc: ide.fileTreeManager.findEntityById(doc_id)
-						}
-
-		warnAboutUnsavedChanges = () ->
-			if Document.hasUnsavedChanges()
-				return "You have unsaved changes. If you leave now they will not be saved."
-	]

+ 0 - 52
services/web/public/coffee/app/ide/editor/cursor-position/CursorPositionManager.coffee

@@ -1,52 +0,0 @@
-define [], () ->
-	class CursorPositionManager
-		constructor: (@$scope, @editor, @element) ->
-
-			@editor.on "changeSession", (e) =>
-				e.session.on "changeScrollTop", (e) =>
-					@onScrollTopChange(e)
-
-				e.session.selection.on 'changeCursor', (e) =>
-					@onCursorChange(e)
-
-				@gotoStoredPosition()
-
-			@editor.on "changeSelection", () =>
-				cursor = @editor.getCursorPosition()
-				@$scope.$apply () =>
-					if @$scope.cursorPosition?
-						@$scope.cursorPosition = cursor
-
-			@$scope.$watch "gotoLine", (value) =>
-				console.log "Going to line", value
-				if value?
-					setTimeout () =>
-						@gotoLine(value)
-						@$scope.$apply () =>
-							@$scope.gotoLine = null
-					, 0
-
-		onScrollTopChange: (event) ->
-			if !@ignoreCursorPositionChanges and doc_id = @$scope.sharejsDoc?.doc_id
-				docPosition = $.localStorage("doc.position.#{doc_id}") || {}
-				docPosition.scrollTop = @editor.getSession().getScrollTop()
-				$.localStorage("doc.position.#{doc_id}", docPosition)
-			
-		onCursorChange: (event) ->
-			if !@ignoreCursorPositionChanges and doc_id = @$scope.sharejsDoc?.doc_id
-				docPosition = $.localStorage("doc.position.#{doc_id}") || {}
-				docPosition.cursorPosition = @editor.getCursorPosition()
-				$.localStorage("doc.position.#{doc_id}", docPosition)
-
-		gotoStoredPosition: () ->
-			doc_id = @$scope.sharejsDoc?.doc_id
-			return if !doc_id?
-			pos = $.localStorage("doc.position.#{doc_id}") || {}
-			@ignoreCursorPositionChanges = true
-			@editor.moveCursorToPosition(pos.cursorPosition or {row: 0, column: 0})
-			@editor.getSession().setScrollTop(pos.scrollTop or 0)
-			delete @ignoreCursorPositionChanges
-
-		gotoLine: (line) ->
-			@editor.gotoLine(line)
-			@editor.focus()

+ 0 - 187
services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee

@@ -1,187 +0,0 @@
-define [
-	"base"
-	"ace/ace"
-	"ide/editor/undo/UndoManager"
-	"ide/editor/auto-complete/AutoCompleteManager"
-	"ide/editor/spell-check/SpellCheckManager"
-	"ide/editor/highlights/HighlightsManager"
-	"ide/editor/cursor-position/CursorPositionManager"
-	"ace/keyboard/vim"
-	"ace/keyboard/emacs"
-	"ace/mode/latex"
-	"ace/edit_session"
-], (App, Ace, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager) ->
-	LatexMode = require("ace/mode/latex").Mode
-	EditSession = require('ace/edit_session').EditSession
-
-	App.directive "aceEditor", ["$timeout", ($timeout) ->
-		return  {
-			scope: {
-				theme: "="
-				showPrintMargin: "="
-				keybindings: "="
-				fontSize: "="
-				autoComplete: "="
-				sharejsDoc: "="
-				lastUpdated: "="
-				spellCheckLanguage: "="
-				cursorPosition: "="
-				highlights: "="
-				text: "="
-				readOnly: "="
-				gotoLine: "="
-				annotations: "="
-			}
-			link: (scope, element, attrs) ->
-				# Don't freak out if we're already in an apply callback
-				scope.$originalApply = scope.$apply
-				scope.$apply = (fn = () ->) ->
-					phase = @$root.$$phase
-					if (phase == '$apply' || phase == '$digest')
-						fn()
-					else
-						@$originalApply(fn);
-
-				editor = Ace.edit(element.find(".ace-editor-body")[0])
-				window.editors ||= []
-				window.editors.push editor
-
-				autoCompleteManager   = new AutoCompleteManager(scope, editor, element)
-				spellCheckManager     = new SpellCheckManager(scope, editor, element)
-				undoManager           = new UndoManager(scope, editor, element)
-				highlightsManager     = new HighlightsManager(scope, editor, element)
-				cursorPositionManager = new CursorPositionManager(scope, editor, element)
-
-				# Prevert Ctrl|Cmd-S from triggering save dialog
-				editor.commands.addCommand
-					name: "save",
-					bindKey: win: "Ctrl-S", mac: "Command-S"
-					exec: () ->
-					readOnly: true
-				editor.commands.removeCommand "transposeletters"
-				editor.commands.removeCommand "showSettingsMenu"
-				editor.commands.removeCommand "foldall"
-
-				if attrs.resizeOn?
-					for event in attrs.resizeOn.split(",")
-						scope.$on event, () ->
-							editor.resize()
-
-				scope.$watch "theme", (value) ->
-					editor.setTheme("ace/theme/#{value}")
-
-				scope.$watch "showPrintMargin", (value) ->
-					editor.setShowPrintMargin(value)
-
-				scope.$watch "keybindings", (value) ->
-					Vim = require("ace/keyboard/vim").handler
-					Emacs = require("ace/keyboard/emacs").handler
-					keybindings = vim: Vim, emacs: Emacs
-					editor.setKeyboardHandler(keybindings[value])
-
-				scope.$watch "fontSize", (value) ->
-					element.find(".ace_editor, .ace_content").css({
-						"font-size": value + "px"
-					})
-
-				scope.$watch "sharejsDoc", (sharejs_doc, old_sharejs_doc) ->
-					if old_sharejs_doc?
-						detachFromAce(old_sharejs_doc)
-
-					if sharejs_doc?
-						attachToAce(sharejs_doc)
-
-				scope.$watch "text", (text) ->
-					if text?
-						editor.setValue(text, -1)
-						session = editor.getSession()
-						session.setUseWrapMode(true)
-						session.setMode(new LatexMode())
-
-				scope.$watch "annotations", (annotations) ->
-					console.log "SETTING ANNOTATIONS", annotations
-					if annotations?
-						session = editor.getSession() 
-						session.setAnnotations annotations
-
-				scope.$watch "readOnly", (value) ->
-					editor.setReadOnly !!value
-
-				resetSession = () ->
-					session = editor.getSession()
-					session.setUseWrapMode(true)
-					session.setMode(new LatexMode())
-					session.setAnnotations scope.annotations
-
-				attachToAce = (sharejs_doc) ->
-					lines = sharejs_doc.getSnapshot().split("\n") 
-					editor.setSession(new EditSession(lines))
-					resetSession()
-					session = editor.getSession()
-
-					doc = session.getDocument()
-					doc.on "change", () ->
-						scope.$apply () ->
-							scope.lastUpdated = new Date()
-
-					sharejs_doc.on "remoteop.recordForUndo", () =>
-						undoManager.nextUpdateIsRemote = true
-
-					sharejs_doc.attachToAce(editor)
-
-					editor.focus()
-
-				detachFromAce = (sharejs_doc) ->
-					sharejs_doc.detachFromAce()
-					sharejs_doc.off "remoteop.recordForUndo"
-
-			template: """
-				<div class="ace-editor-wrapper">
-					<div
-						class="undo-conflict-warning alert alert-danger small"
-						ng-show="undo.show_remote_warning"
-					>
-						<strong>Watch out!</strong>
-						We had to undo some of your collaborators changes before we could undo yours.
-						<a
-							href="#"
-							class="pull-right"
-							ng-click="undo.show_remote_warning = false"
-						>Dismiss</a>
-					</div>
-					<div class="ace-editor-body"></div>
-					<div 
-						id="spellCheckMenu"
-						class="dropdown context-menu"
-						ng-show="spellingMenu.open"
-						ng-style="{top: spellingMenu.top, left: spellingMenu.left}"
-						ng-class="{open: spellingMenu.open}"
-					>
-						<ul class="dropdown-menu">
-							<li ng-repeat="suggestion in spellingMenu.highlight.suggestions | limitTo:8">
-								<a href ng-click="replaceWord(spellingMenu.highlight, suggestion)">{{ suggestion }}</a>
-							</li>
-							<li class="divider"></li>
-							<li>
-								<a href ng-click="learnWord(spellingMenu.highlight)">Add to Dictionary</a>
-							</li>
-						</ul>
-					</div>
-					<div
-						class="annotation-label"
-						ng-show="annotationLabel.show"
-						ng-style="{
-							position: 'absolute',
-							left:     annotationLabel.left,
-							right:    annotationLabel.right,
-							bottom:   annotationLabel.bottom,
-							top:      annotationLabel.top,
-							'background-color': annotationLabel.backgroundColor
-						}"
-					>
-						{{ annotationLabel.text }}
-					</div>
-				</div>
-			"""
-		}
-	]

+ 0 - 228
services/web/public/coffee/app/ide/editor/highlights/HighlightsManager.coffee

@@ -1,228 +0,0 @@
-define [
-	"ace/range"
-], () ->
-	Range = require("ace/range").Range
-
-	class HighlightsManager
-		constructor: (@$scope, @editor, @element) ->
-			@markerIds = []
-			@labels = []
-
-			@$scope.annotationLabel = {
-				show: false
-				right:  "auto"
-				left:   "auto"
-				top:    "auto"
-				bottom: "auto"
-				backgroundColor: "black"
-				text: ""
-			}
-
-			@$scope.$watch "highlights", (value) =>
-				@redrawAnnotations()
-
-			@$scope.$watch "theme", (value) =>
-				@redrawAnnotations()
-
-			@editor.on "mousemove", (e) =>
-				position = @editor.renderer.screenToTextCoordinates(e.clientX, e.clientY)
-				e.position = position
-				@showAnnotationLabels(position)
-
-			@editor.on "changeSession", () =>
-				@redrawAnnotations()
-
-		redrawAnnotations: () ->
-			@_clearMarkers()
-			@_clearLabels()
-
-			for annotation in @$scope.highlights or []
-				do (annotation) =>
-					colorScheme = @_getColorScheme(annotation.hue)
-					if annotation.cursor?
-						console.log "DRAWING CURSOR", annotation
-						@labels.push {
-							text: annotation.label
-							range: new Range(
-								annotation.cursor.row, annotation.cursor.column,
-								annotation.cursor.row, annotation.cursor.column + 1
-							)
-							colorScheme: colorScheme
-							snapToStartOfRange: true
-						}
-						@_drawCursor(annotation, colorScheme)
-					else if annotation.highlight?
-						@labels.push {
-							text: annotation.label
-							range: new Range(
-								annotation.highlight.start.row, annotation.highlight.start.column,
-								annotation.highlight.end.row,   annotation.highlight.end.column
-							)
-							colorScheme: colorScheme
-						}
-						@_drawHighlight(annotation, colorScheme)
-					else if annotation.strikeThrough?
-						@labels.push {
-							text: annotation.label
-							range: new Range(
-								annotation.strikeThrough.start.row, annotation.strikeThrough.start.column,
-								annotation.strikeThrough.end.row,   annotation.strikeThrough.end.column
-							)
-							colorScheme: colorScheme
-						}
-						@_drawStrikeThrough(annotation, colorScheme)
-
-		showAnnotationLabels: (position) ->
-			labelToShow = null
-			for label in @labels or []
-				if label.range.contains(position.row, position.column)
-					labelToShow = label
-
-			if !labelToShow?
-				@$scope.$apply () =>
-					@$scope.annotationLabel.show = false
-			else
-				$ace = $(@editor.renderer.container).find(".ace_scroller")
-				# Move the label into the Ace content area so that offsets and positions are easy to calculate.
-				$ace.append(@element.find(".annotation-label"))
-
-				if labelToShow.snapToStartOfRange
-					coords = @editor.renderer.textToScreenCoordinates(labelToShow.range.start.row, labelToShow.range.start.column)
-				else
-					coords = @editor.renderer.textToScreenCoordinates(position.row, position.column)
-
-				offset = $ace.offset()
-				height = $ace.height()
-				coords.pageX = coords.pageX - offset.left
-				coords.pageY = coords.pageY - offset.top
-
-				if coords.pageY > @editor.renderer.lineHeight * 2
-					top    = "auto"
-					bottom = height - coords.pageY
-				else
-					top    = coords.pageY + @editor.renderer.lineHeight
-					bottom = "auto"
-
-				# Apply this first that the label has the correct width when calculating below
-				@$scope.$apply () =>
-					@$scope.annotationLabel.text = labelToShow.text
-					@$scope.annotationLabel.show = true
-
-				$label = @element.find(".annotation-label")
-				console.log "pageX", coords.pageX, "label", $label.outerWidth(), "ace", $ace.width()
-
-				if coords.pageX + $label.outerWidth() < $ace.width()
-					left  = coords.pageX
-					right = "auto"
-				else
-					right = 0
-					left = "auto"
-
-				@$scope.$apply () =>
-					@$scope.annotationLabel = {
-						show:   true
-						left:   left
-						right:  right
-						bottom: bottom
-						top:    top
-						backgroundColor: labelToShow.colorScheme.labelBackgroundColor
-						text:   labelToShow.text
-					}
-
-		_clearMarkers: () ->
-			for marker_id in @markerIds
-				@editor.getSession().removeMarker(marker_id)
-			@markerIds = []
-
-		_clearLabels: () ->
-			@labels = []
-
-		_drawCursor: (annotation, colorScheme) ->
-			@markerIds.push @editor.getSession().addMarker new Range(
-				annotation.cursor.row, annotation.cursor.column,
-				annotation.cursor.row, annotation.cursor.column + 1
-			), "annotation remote-cursor", (html, range, left, top, config) ->
-				div = """
-					<div
-						class='remote-cursor custom ace_start'
-						style='height: #{config.lineHeight}px; top:#{top}px; left:#{left}px; border-color: #{colorScheme.cursor};'
-					>
-						<div class="nubbin" style="bottom: #{config.lineHeight}px; background-color: #{colorScheme.cursor};"></div>
-					</div>
-				"""
-				html.push div
-			, true
-
-		_drawHighlight: (annotation, colorScheme) ->
-			@_addMarkerWithCustomStyle(
-				new Range(
-					annotation.highlight.start.row, annotation.highlight.start.column,
-					annotation.highlight.end.row,   annotation.highlight.end.column + 1
-				),
-				"annotation highlight",
-				false,
-				"background-color: #{colorScheme.highlightBackgroundColor}"
-			)
-
-		_drawStrikeThrough: (annotation, colorScheme) ->
-			lineHeight = @editor.renderer.lineHeight
-			@_addMarkerWithCustomStyle(
-				new Range(
-					annotation.strikeThrough.start.row, annotation.strikeThrough.start.column,
-					annotation.strikeThrough.end.row,   annotation.strikeThrough.end.column + 1
-				),
-				"annotation strike-through-background",
-				false,
-				"background-color: #{colorScheme.strikeThroughBackgroundColor}"
-			)
-			@_addMarkerWithCustomStyle(
-				new Range(
-					annotation.strikeThrough.start.row, annotation.strikeThrough.start.column,
-					annotation.strikeThrough.end.row,   annotation.strikeThrough.end.column + 1
-				), 
-				"annotation strike-through-foreground",
-				true, 
-				"""
-					height: #{Math.round(lineHeight/2) + 2}px;
-					border-bottom: 2px solid #{colorScheme.strikeThroughForegroundColor};
-				"""
-			)
-
-		_addMarkerWithCustomStyle: (range, klass, foreground, style) ->
-			if foreground?
-				markerLayer = @editor.renderer.$markerBack
-			else
-				markerLayer = @editor.renderer.$markerFront
-
-			@markerIds.push @editor.getSession().addMarker range, klass, (html, range, left, top, config) ->
-				if range.isMultiLine()
-					markerLayer.drawTextMarker(html, range, klass, config, style)
-				else
-					markerLayer.drawSingleLineMarker(html, range, "#{klass} ace_start", config, 0, style)
-			, foreground
-
-		_getColorScheme: (hue) ->
-			if @_isDarkTheme()
-				return {
-					cursor: "hsl(#{hue}, 70%, 50%)"
-					labelBackgroundColor: "hsl(#{hue}, 70%, 50%)"
-					highlightBackgroundColor: "hsl(#{hue}, 100%, 28%);"
-					strikeThroughBackgroundColor: "hsl(#{hue}, 100%, 20%);"
-					strikeThroughForegroundColor: "hsl(#{hue}, 100%, 60%);"
-				}
-			else
-				return {
-					cursor: "hsl(#{hue}, 70%, 50%)"
-					labelBackgroundColor: "hsl(#{hue}, 70%, 50%)"
-					highlightBackgroundColor: "hsl(#{hue}, 70%, 85%);"
-					strikeThroughBackgroundColor: "hsl(#{hue}, 70%, 95%);"
-					strikeThroughForegroundColor: "hsl(#{hue}, 70%, 40%);"
-				}
-
-		_isDarkTheme: () ->
-			rgb = @element.find(".ace_editor").css("background-color");
-			[m, r, g, b] = rgb.match(/rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)/)
-			r = parseInt(r, 10)
-			g = parseInt(g, 10)
-			b = parseInt(b, 10)
-			return r + g + b < 3 * 128

+ 0 - 150
services/web/public/coffee/app/ide/editor/spell-check/HighlightedWordManager.coffee

@@ -1,150 +0,0 @@
-define [
-	"ace/range"
-], () ->
-	Range = require("ace/range").Range
-
-	class Highlight
-		constructor: (options) ->
-			@row = options.row
-			@column = options.column
-			@word = options.word
-			@suggestions = options.suggestions
-
-	class HighlightedWordManager
-		constructor: (@editor) ->
-			@highlights = rows: []
-
-		addHighlight: (highlight) ->
-			unless highlight instanceof Highlight
-				highlight = new Highlight(highlight)
-			range = new Range(
-				highlight.row, highlight.column,
-				highlight.row, highlight.column + highlight.word.length
-			)
-			highlight.markerId = @editor.getSession().addMarker range, "spelling-highlight"
-			@highlights.rows[highlight.row] ||= []
-			@highlights.rows[highlight.row].push highlight
-
-		removeHighlight: (highlight) ->
-			@editor.getSession().removeMarker(highlight.markerId)
-			for h, i in @highlights.rows[highlight.row]
-				if h == highlight
-					@highlights.rows[highlight.row].splice(i, 1)
-
-		removeWord: (word) ->
-			toRemove = []
-			for row in @highlights.rows
-				for highlight in (row || [])
-					if highlight.word == word
-						toRemove.push(highlight)
-			for highlight in toRemove
-				@removeHighlight highlight
-
-		moveHighlight: (highlight, position) ->
-			@removeHighlight highlight
-			highlight.row = position.row
-			highlight.column = position.column
-			@addHighlight highlight
-
-		clearRows: (from, to) ->
-			from ||= 0
-			to ||= @highlights.rows.length - 1
-			for row in @highlights.rows.slice(from, to + 1)
-				for highlight in (row || []).slice(0)
-					@removeHighlight highlight
-
-		insertRows: (offset, number) ->
-			# rows are inserted after offset. i.e. offset row is not modified
-			affectedHighlights = []
-			for row in @highlights.rows.slice(offset)
-				affectedHighlights.push(highlight) for highlight in (row || [])
-			for highlight in affectedHighlights
-				@moveHighlight highlight,
-					row: highlight.row + number
-					column: highlight.column
-
-		removeRows: (offset, number) ->
-			# offset is the first row to delete
-			affectedHighlights = []
-			for row in @highlights.rows.slice(offset)
-				affectedHighlights.push(highlight) for highlight in (row || [])
-			for highlight in affectedHighlights
-				if highlight.row >= offset + number
-					@moveHighlight highlight,
-						row: highlight.row - number
-						column: highlight.column
-				else
-					@removeHighlight highlight
-
-		findHighlightWithinRange: (range) ->
-			rows = @highlights.rows.slice(range.start.row, range.end.row + 1)
-			for row in rows
-				for highlight in (row || [])
-					if @_doesHighlightOverlapRange(highlight, range.start, range.end)
-						return highlight
-			return null
-
-		applyChange: (change) ->
-			start = change.range.start
-			end = change.range.end
-			if change.action == "insertText"
-				if start.row != end.row
-					rowsAdded = end.row - start.row
-					@insertRows start.row + 1, rowsAdded
-				# make a copy since we're going to modify in place
-				oldHighlights = (@highlights.rows[start.row] || []).slice(0)
-				for highlight in oldHighlights
-					if highlight.column > start.column
-						# insertion was fully before this highlight
-						@moveHighlight highlight,
-							row: end.row
-							column: highlight.column + (end.column - start.column)
-					else if highlight.column + highlight.word.length >= start.column
-						# insertion was inside this highlight
-						@removeHighlight highlight
-
-			else if change.action == "insertLines"
-				@insertRows start.row, change.lines.length
-
-			else if change.action == "removeText"
-				if start.row == end.row
-					oldHighlights = (@highlights.rows[start.row] || []).slice(0)
-				else
-					rowsRemoved = end.row - start.row
-					oldHighlights =
-						(@highlights.rows[start.row] || []).concat(
-							(@highlights.rows[end.row] || [])
-						)
-					@removeRows start.row + 1, rowsRemoved
-
-				for highlight in oldHighlights
-					if @_doesHighlightOverlapRange highlight, start, end
-						@removeHighlight highlight
-					else if @_isHighlightAfterRange highlight, start, end
-						@moveHighlight highlight,
-							row: start.row
-							column: highlight.column - (end.column - start.column)
-
-			else if change.action == "removeLines"
-				@removeRows start.row, change.lines.length
-
-		_doesHighlightOverlapRange: (highlight, start, end) ->
-			highlightIsAllBeforeRange =
-				highlight.row < start.row or
-				(highlight.row == start.row and highlight.column + highlight.word.length <= start.column)
-			highlightIsAllAfterRange =
-				highlight.row > end.row or
-				(highlight.row == end.row and highlight.column >= end.column)
-			!(highlightIsAllBeforeRange or highlightIsAllAfterRange)
-
-		_isHighlightAfterRange: (highlight, start, end) ->
-			return true if highlight.row > end.row
-			return false if highlight.row < end.row
-			highlight.column >= end.column
-			
-
-
-			
-
-	
-

+ 0 - 191
services/web/public/coffee/app/ide/editor/spell-check/SpellCheckManager.coffee

@@ -1,191 +0,0 @@
-define [
-	"ide/editor/spell-check/HighlightedWordManager"
-	"ace/range"
-], (HighlightedWordManager) ->
-	Range = require("ace/range").Range
-
-	class SpellCheckManager
-		constructor: (@$scope, @editor, @element) ->
-			@updatedLines = []
-			@highlightedWordManager = new HighlightedWordManager(@editor)
-
-			@$scope.$watch "spellCheckLanguage", (language, oldLanguage) =>
-				if language != oldLanguage and oldLanguage?
-					@runFullCheck()
-
-			@editor.on "changeSession", (e) =>
-				@runFullCheck()
-
-				doc = e.session.getDocument()
-				doc.on "change", (e) =>
-					@runCheckOnChange(e)
-
-			@$scope.spellingMenu = {left: '0px', top: '0px'}
-
-			@editor.on "nativecontextmenu", (e) =>
-				@closeContextMenu(e.domEvent)
-				@openContextMenu(e.domEvent)
-
-			$(document).on "click", (e) =>
-				@closeContextMenu(e)
-				return true
-			# $(document).on "contextmenu", (e) =>
-			# 	@closeContextMenu(e)
-
-			@$scope.replaceWord = (highlight, suggestion) =>
-				@replaceWord(highlight, suggestion)
-
-			@$scope.learnWord = (highlight) =>
-				@learnWord(highlight)
-
-		runFullCheck: () ->
-			console.log "Running full check"
-			@highlightedWordManager.clearRows()
-			if @$scope.spellCheckLanguage and @$scope.spellCheckLanguage != ""
-				@runSpellCheck()
-
-		runCheckOnChange: (e) ->
-			console.log "Checking change", e.data
-			if @$scope.spellCheckLanguage and @$scope.spellCheckLanguage != ""
-				@highlightedWordManager.applyChange(e.data)
-				@markLinesAsUpdated(e.data)
-				@runSpellCheckSoon()
-
-		openContextMenu: (e) ->
-			position = @editor.renderer.screenToTextCoordinates(e.clientX, e.clientY)
-			highlight = @highlightedWordManager.findHighlightWithinRange
-				start: position
-				end:   position
-
-			@$scope.$apply () =>
-				@$scope.spellingMenu.highlight = highlight
-
-			console.log "highlight", @$scope.highlight_under_mouse
-
-			if highlight
-				e.stopPropagation()
-				e.preventDefault()
-
-				@editor.getSession().getSelection().setSelectionRange(
-					new Range(
-						highlight.row, highlight.column
-						highlight.row, highlight.column + highlight.word.length
-					)
-				)
-
-				console.log "Height", @element.find(".context-menu").height()
-
-				@$scope.$apply () =>
-					@$scope.spellingMenu.open = true
-					@$scope.spellingMenu.left = e.clientX + 'px'
-					@$scope.spellingMenu.top = e.clientY + 'px'
-
-		closeContextMenu: (e) ->
-			@$scope.$apply () =>
-				@$scope.spellingMenu.open = false
-
-		replaceWord: (highlight, text) ->
-			@editor.getSession().replace(new Range(
-				highlight.row, highlight.column,
-				highlight.row, highlight.column + highlight.word.length
-			), text)
-
-		learnWord: (highlight) ->
-			@apiRequest "/learn", word: highlight.word
-			@highlightedWordManager.removeWord highlight.word
-
-		getHighlightedWordAtCursor: () ->
-			cursor = @editor.getCursorPosition()
-			highlight = @highlightedWordManager.findHighlightWithinRange
-				start: cursor
-				end: cursor
-			return highlight
-
-		runSpellCheckSoon: () ->
-			run = () =>
-				delete @timeoutId
-				@runSpellCheck(@updatedLines)
-				@updatedLines = []
-			if @timeoutId?
-				clearTimeout @timeoutId
-			@timeoutId = setTimeout run, 1000
-
-		markLinesAsUpdated: (change) ->
-			start = change.range.start
-			end = change.range.end
-
-			insertLines = () =>
-				lines = end.row - start.row
-				while lines--
-					@updatedLines.splice(start.row, 0, true)
-
-			removeLines = () =>
-				lines = end.row - start.row
-				while lines--
-					@updatedLines.splice(start.row + 1, 1)
-
-			if change.action == "insertText"
-				@updatedLines[start.row] = true
-				insertLines()
-			else if change.action == "removeText"
-				@updatedLines[start.row] = true
-				removeLines()
-			else if change.action == "insertLines"
-				insertLines()
-			else if change.action == "removeLines"
-				removeLines()
-
-		runSpellCheck: (linesToProcess) ->
-			{words, positions} = @getWords(linesToProcess)
-			language = @$scope.spellCheckLanguage
-			@apiRequest "/check", {language: language, words: words}, (error, result) =>
-				if error? or !result? or !result.misspellings?
-					return null
-
-				if linesToProcess?
-					for shouldProcess, row in linesToProcess
-						@highlightedWordManager.clearRows(row, row) if shouldProcess
-				else
-					@highlightedWordManager.clearRows()
-
-				for misspelling in result.misspellings
-					word = words[misspelling.index]
-					position = positions[misspelling.index]
-					@highlightedWordManager.addHighlight
-						column: position.column
-						row: position.row
-						word: word
-						suggestions: misspelling.suggestions
-
-		getWords: (linesToProcess) ->
-			lines = @editor.getValue().split("\n")
-			words = []
-			positions = []
-			for line, row in lines
-				if !linesToProcess? or linesToProcess[row]
-					wordRegex = /\\?['a-zA-Z\u00C0-\u00FF]+/g
-					while (result = wordRegex.exec(line))
-						word = result[0]
-						if word[0] == "'"
-							word = word.slice(1)
-						if word[word.length - 1] == "'"
-							word = word.slice(0,-1)
-						positions.push row: row, column: result.index
-						words.push(word)
-			return words: words, positions: positions
-
-		apiRequest: (endpoint, data, callback = (error, result) ->)->
-			data.token = window.user.id
-			data._csrf = window.csrfToken
-			options =
-				url: "/spelling" + endpoint
-				type: "POST"
-				dataType: "json"
-				headers:
-					"Content-Type": "application/json"
-				data: JSON.stringify data
-				success: (data, status, xhr) ->
-					callback null, data
-				error: (xhr, status, error) ->
-					callback error
-			$.ajax options

+ 0 - 82
services/web/public/coffee/app/ide/editor/spell-check/SpellingMenuView.coffee

@@ -1,82 +0,0 @@
-define [
-	"libs/backbone"
-	"libs/mustache"
-], () ->
-	SUGGESTIONS_TO_SHOW = 5
-
-	SpellingMenuView = Backbone.View.extend
-		templates:
-			menu: $("#spellingMenuTemplate").html()
-			entry: $("#spellingMenuEntryTemplate").html()
-
-		events:
-			"click a#learnWord": ->
-				@trigger "click:learn", @_currentHighlight
-				@hide()
-
-		initialize: (options) ->
-			@ide = options.ide
-			@ide.editor.getContainerElement().append @render().el
-			@ide.editor.on "click", () => @hide()
-			@ide.editor.on "scroll", () => @hide()
-			@ide.editor.on "update:doc", () => @hide()
-			@ide.editor.on "change:doc", () => @hide()
-
-		render: () ->
-			@setElement($(@templates.menu))
-			@$el.css "z-index" : 10000
-			@$(".dropdown-toggle").dropdown()
-			@hide()
-			return @
-
-		showForHighlight: (highlight) ->
-			if @_currentHighlight? and highlight != @_currentHighlight
-				@_close()
-			
-			if !@_currentHighlight?
-				@_currentHighlight = highlight
-				@_setSuggestions(highlight)
-				position = @ide.editor.textToEditorCoordinates(
-					highlight.row
-					highlight.column + highlight.word.length
-				)
-				@_position(position.x, position.y)
-				@_show()
-
-		hideIfAppropriate: (cursorPosition) ->
-			if @_currentHighlight?
-				if !@_cursorCloseToHighlight(cursorPosition, @_currentHighlight) and !@_isOpen()
-					@hide()
-
-		hide: () ->
-			delete @_currentHighlight
-			@_close()
-			@$el.hide()
-
-		_setSuggestions: (highlight) ->
-			@$(".spelling-suggestion").remove()
-			divider = @$(".divider")
-			for suggestion in highlight.suggestions.slice(0, SUGGESTIONS_TO_SHOW)
-				do (suggestion) =>
-					entry = $(Mustache.to_html(@templates.entry, word: suggestion))
-					divider.before(entry)
-					entry.on "click", () =>
-						@trigger "click:suggestion", suggestion, highlight
-
-		_show: () -> @$el.show()
-
-		_isOpen: () ->
-			@$(".dropdown-menu").is(":visible")
-
-		_close: () ->
-			if @_isOpen()
-				@$el.dropdown("toggle")
-
-		_cursorCloseToHighlight: (position, highlight) ->
-			position.row == highlight.row and
-			position.column >= highlight.column and
-			position.column <= highlight.column + highlight.word.length + 1
-
-		_position: (x,y) -> @$el.css left: x, top: y
-
-

+ 0 - 374
services/web/public/coffee/app/ide/editor/undo/UndoManager.coffee

@@ -1,374 +0,0 @@
-define [
-	"ace/range"
-	"ace/edit_session"
-	"ace/document"
-], () ->
-	Range = require("ace/range").Range
-	EditSession = require("ace/edit_session").EditSession
-	Doc = require("ace/document").Document
-
-	class UndoManager
-		constructor: (@$scope, @editor) ->
-			@$scope.undo =
-				show_remote_warning: false
-				
-			@reset()
-			@nextUpdateIsRemote = false
-
-			@editor.on "changeSession", (e) =>
-				console.log "setting undo manager", e.session
-				e.session.setUndoManager(@)
-
-		showUndoConflictWarning: () ->
-			@$scope.$apply () =>
-				@$scope.undo.show_remote_warning = true
-
-			setTimeout () => 
-				@$scope.$apply () =>
-					@$scope.undo.show_remote_warning = false
-			, 4000
-
-		reset: () ->
-			@undoStack = []
-			@redoStack = []
-
-		execute: (options) ->
-			aceDeltaSets = options.args[0]
-			@session = options.args[1]
-			return if !aceDeltaSets?
-
-			lines = @session.getDocument().getAllLines()
-			linesBeforeChange = @_revertAceDeltaSetsOnDocLines(aceDeltaSets, lines)
-			simpleDeltaSets = @_aceDeltaSetsToSimpleDeltaSets(aceDeltaSets, linesBeforeChange)
-			@undoStack.push(
-				deltaSets: simpleDeltaSets
-				remote: @nextUpdateIsRemote
-			)
-			@redoStack = []
-			@nextUpdateIsRemote = false
-
-		undo: (dontSelect) ->
-			localUpdatesMade = @_shiftLocalChangeToTopOfUndoStack()
-			return if !localUpdatesMade
-
-			update = @undoStack.pop()
-			return if !update?
-
-			if update.remote
-				@showUndoConflictWarning()
-
-			lines = @session.getDocument().getAllLines()
-			linesBeforeDelta = @_revertSimpleDeltaSetsOnDocLines(update.deltaSets, lines)
-			deltaSets = @_simpleDeltaSetsToAceDeltaSets(update.deltaSets, linesBeforeDelta)
-			selectionRange = @session.undoChanges(deltaSets, dontSelect)
-			@redoStack.push(update)
-			return selectionRange
-		
-		redo: (dontSelect) ->
-			update = @redoStack.pop()
-			return if !update?
-			lines = @session.getDocument().getAllLines()
-			deltaSets = @_simpleDeltaSetsToAceDeltaSets(update.deltaSets, lines)
-			selectionRange = @session.redoChanges(deltaSets, dontSelect)
-			@undoStack.push(update)
-			return selectionRange
-
-		_shiftLocalChangeToTopOfUndoStack: () ->
-			head = []
-			localChangeExists = false
-			while @undoStack.length > 0
-				update = @undoStack.pop()
-				head.unshift update
-				if !update.remote
-					localChangeExists = true
-					break
-
-			if !localChangeExists
-				@undoStack = @undoStack.concat head
-				return false
-			else
-				# Undo stack looks like undoStack ++ reorderedhead ++ head
-				# Reordered head starts of empty and consumes entries from head
-				# while keeping the localChange at the top for as long as it can
-				localChange = head.shift()
-				reorderedHead = [localChange]
-				while head.length > 0
-					remoteChange = head.shift()
-					localChange = reorderedHead.pop()
-					result = @_swapSimpleDeltaSetsOrder(localChange.deltaSets, remoteChange.deltaSets)
-					if result?
-						remoteChange.deltaSets = result[0]
-						localChange.deltaSets = result[1]
-						reorderedHead.push remoteChange
-						reorderedHead.push localChange
-					else
-						reorderedHead.push localChange
-						reorderedHead.push remoteChange
-						break
-				@undoStack = @undoStack.concat(reorderedHead).concat(head)
-				return true
-				
-
-		_swapSimpleDeltaSetsOrder: (firstDeltaSets, secondDeltaSets) ->
-			newFirstDeltaSets = @_copyDeltaSets(firstDeltaSets)
-			newSecondDeltaSets = @_copyDeltaSets(secondDeltaSets)
-			for firstDeltaSet in newFirstDeltaSets.slice(0).reverse()
-				for firstDelta in firstDeltaSet.deltas.slice(0).reverse()
-					for secondDeltaSet in newSecondDeltaSets
-						for secondDelta in secondDeltaSet.deltas
-							success = @_swapSimpleDeltaOrderInPlace(firstDelta, secondDelta)
-							return null if !success
-			return [newSecondDeltaSets, newFirstDeltaSets]
-
-		_copyDeltaSets: (deltaSets) ->
-			newDeltaSets = []
-			for deltaSet in deltaSets
-				newDeltaSet =
-					deltas: []
-					group: deltaSet.group
-				newDeltaSets.push newDeltaSet
-				for delta in deltaSet.deltas
-					newDelta =
-						position: delta.position
-					newDelta.insert = delta.insert if delta.insert?
-					newDelta.remove = delta.remove if delta.remove?
-					newDeltaSet.deltas.push newDelta
-			return newDeltaSets
-
-		_swapSimpleDeltaOrderInPlace: (firstDelta, secondDelta) ->
-			result = @_swapSimpleDeltaOrder(firstDelta, secondDelta)
-			return false if !result?
-			firstDelta.position = result[1].position
-			secondDelta.position = result[0].position
-			return true
-
-		_swapSimpleDeltaOrder: (firstDelta, secondDelta) ->
-			if firstDelta.insert? and secondDelta.insert?
-				if secondDelta.position >= firstDelta.position + firstDelta.insert.length
-					secondDelta.position -= firstDelta.insert.length
-					return [secondDelta, firstDelta]
-				else if secondDelta.position > firstDelta.position
-					return null
-				else
-					firstDelta.position += secondDelta.insert.length
-					return [secondDelta, firstDelta]
-			else if firstDelta.remove? and secondDelta.remove?
-				if secondDelta.position >= firstDelta.position
-					secondDelta.position += firstDelta.remove.length
-					return [secondDelta, firstDelta]
-				else if secondDelta.position + secondDelta.remove.length > firstDelta.position
-					return null
-				else
-					firstDelta.position -= secondDelta.remove.length
-					return [secondDelta, firstDelta]
-			else if firstDelta.insert? and secondDelta.remove?
-				if secondDelta.position >= firstDelta.position + firstDelta.insert.length
-					secondDelta.position -= firstDelta.insert.length
-					return [secondDelta, firstDelta]
-				else if secondDelta.position + secondDelta.remove.length > firstDelta.position
-					return null
-				else
-					firstDelta.position -= secondDelta.remove.length
-					return [secondDelta, firstDelta]
-			else if firstDelta.remove? and secondDelta.insert?
-				if secondDelta.position >= firstDelta.position
-					secondDelta.position += firstDelta.remove.length
-					return [secondDelta, firstDelta]
-				else
-					firstDelta.position += secondDelta.insert.length
-					return [secondDelta, firstDelta]
-			else
-				throw "Unknown delta types"
-
-		_applyAceDeltasToDocLines: (deltas, docLines) ->
-			doc = new Doc(docLines.join("\n"))
-			doc.applyDeltas(deltas)
-			return doc.getAllLines()
-
-		_revertAceDeltaSetsOnDocLines: (deltaSets, docLines) ->
-			session = new EditSession(docLines.join("\n"))
-			session.undoChanges(deltaSets)
-			return session.getDocument().getAllLines()
-
-		_revertSimpleDeltaSetsOnDocLines: (deltaSets, docLines) ->
-			doc = docLines.join("\n")
-			for deltaSet in deltaSets.slice(0).reverse()
-				for delta in deltaSet.deltas.slice(0).reverse()
-					if delta.remove?
-						doc = doc.slice(0, delta.position) + delta.remove + doc.slice(delta.position)
-					else if delta.insert?
-						doc = doc.slice(0, delta.position) + doc.slice(delta.position + delta.insert.length)
-					else
-						throw "Unknown delta type"
-			return doc.split("\n")
-
-		_aceDeltaSetsToSimpleDeltaSets: (aceDeltaSets, docLines) ->
-			for deltaSet in aceDeltaSets
-				simpleDeltas = []
-				for delta in deltaSet.deltas
-					simpleDeltas.push @_aceDeltaToSimpleDelta(delta, docLines)
-					docLines = @_applyAceDeltasToDocLines([delta], docLines)
-				{
-					deltas: simpleDeltas
-					group: deltaSet.group
-				}
-			
-		_simpleDeltaSetsToAceDeltaSets: (simpleDeltaSets, docLines) ->
-			for deltaSet in simpleDeltaSets
-				aceDeltas = []
-				for delta in deltaSet.deltas
-					newAceDeltas = @_simpleDeltaToAceDeltas(delta, docLines)
-					docLines = @_applyAceDeltasToDocLines(newAceDeltas, docLines)
-					aceDeltas = aceDeltas.concat newAceDeltas
-				{
-					deltas: aceDeltas
-					group: deltaSet.group
-				}
-
-		_aceDeltaToSimpleDelta: (aceDelta, docLines) ->
-			start = aceDelta.range.start
-			linesBefore = docLines.slice(0, start.row)
-			position =
-				linesBefore.join("").length + # full lines
-				linesBefore.length + # new line characters
-				start.column # partial line
-			switch aceDelta.action
-				when "insertText"
-					return {
-						position: position
-						insert: aceDelta.text
-					}
-				when "insertLines"
-					return {
-						position: position
-						insert: aceDelta.lines.join("\n") + "\n"
-					}
-				when "removeText"
-					return {
-						position: position
-						remove: aceDelta.text
-					}
-				when "removeLines"
-					return {
-						position: position
-						remove: aceDelta.lines.join("\n") + "\n"
-					}
-				else
-					throw "Unknown Ace action: #{aceDelta.action}"
-
-		_simplePositionToAcePosition: (position, docLines) ->
-			column = 0
-			row = 0
-			for line in docLines
-				if position > line.length
-					row++
-					position -= (line + "\n").length
-				else
-					column = position
-					break
-			return {row: row, column: column}
-
-		_textToAceActions: (simpleText, row, column, type) ->
-			aceDeltas = []
-			lines = simpleText.split("\n")
-
-			range = (options) -> new Range(options.start.row, options.start.column, options.end.row, options.end.column)
-
-			do stripFirstLine = () ->
-				firstLine = lines.shift()
-				if firstLine.length > 0
-					aceDeltas.push {
-						text: firstLine
-						range: range(
-							start: column: column, row: row
-							end: column: column + firstLine.length, row: row
-						)
-						action: "#{type}Text"
-					}
-					column += firstLine.length
-
-			do stripFirstNewLine = () ->
-				if lines.length > 0
-					aceDeltas.push {
-						text: "\n"
-						range: range(
-							start: column: column, row: row
-							end: column: 0, row: row + 1
-						)
-						action: "#{type}Text"
-					}
-					row += 1
-
-			do stripMiddleFullLines = () ->
-				middleLines = lines.slice(0, -1)
-				if middleLines.length > 0
-					aceDeltas.push {
-						lines: middleLines
-						range: range(
-							start: column: 0, row: row
-							end: column: 0, row: row + middleLines.length
-						)
-						action: "#{type}Lines"
-					}
-					row += middleLines.length
-
-			do stripLastLine = () ->
-				if lines.length > 0
-					lastLine = lines.pop()
-					aceDeltas.push {
-						text: lastLine
-						range: range(
-							start: column: 0, row: row
-							end: column: lastLine.length , row: row
-						)
-						action: "#{type}Text"
-					}
-
-			return aceDeltas
-
-
-		_simpleDeltaToAceDeltas: (simpleDelta, docLines) ->
-			{row, column} = @_simplePositionToAcePosition(simpleDelta.position, docLines)
-
-			if simpleDelta.insert?
-				return @_textToAceActions(simpleDelta.insert, row, column, "insert")
-			if simpleDelta.remove?
-				return @_textToAceActions(simpleDelta.remove, row, column, "remove").reverse()
-			else
-				throw "Unknown simple delta: #{simpleDelta}"
-
-		_concatSimpleDeltas: (deltas) ->
-			return [] if deltas.length == 0
-
-			concattedDeltas = []
-			previousDelta = deltas.shift()
-			for delta in deltas
-				if delta.insert? and previousDelta.insert?
-					if previousDelta.position + previousDelta.insert.length == delta.position
-						previousDelta =
-							insert: previousDelta.insert + delta.insert
-							position: previousDelta.position
-					else
-						concattedDeltas.push previousDelta
-						previousDelta = delta
-
-				else if delta.remove? and previousDelta.remove?
-					if previousDelta.position == delta.position
-						previousDelta =
-							remove: previousDelta.remove + delta.remove
-							position: delta.position
-					else
-						concattedDeltas.push previousDelta
-						previousDelta = delta
-				else
-					concattedDeltas.push previousDelta
-					previousDelta = delta
-			concattedDeltas.push previousDelta
-					
-
-			return concattedDeltas
-				
-
-		hasUndo: () -> @undoStack.length > 0
-		hasRedo: () -> @redoStack.length > 0
-		

+ 0 - 281
services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee

@@ -1,281 +0,0 @@
-define [
-	"ide/file-tree/directives/fileEntity"
-	"ide/file-tree/directives/draggable"
-	"ide/file-tree/directives/droppable"
-	"ide/file-tree/controllers/FileTreeController"
-	"ide/file-tree/controllers/FileTreeEntityController"
-	"ide/file-tree/controllers/FileTreeFolderController"
-	"ide/file-tree/controllers/FileTreeRootFolderController"
-], () ->
-	class FileTreeManager
-		constructor: (@ide, @$scope) ->
-			@$scope.$on "project:joined", =>
-				@loadRootFolder()
-				@loadDeletedDocs()
-				@$scope.$emit "file-tree:initialized"
-
-			@_bindToSocketEvents()
-
-		_bindToSocketEvents: () ->
-			@ide.socket.on "reciveNewDoc", (parent_folder_id, doc) =>
-				parent_folder = @findEntityById(parent_folder_id) or @$scope.rootFolder
-				@$scope.$apply () ->
-					parent_folder.children.push {
-						name: doc.name
-						id:   doc._id
-						type: "doc"
-					}
-
-			@ide.socket.on "reciveNewFile", (parent_folder_id, file) =>
-				parent_folder = @findEntityById(parent_folder_id) or @$scope.rootFolder
-				@$scope.$apply () ->
-					parent_folder.children.push {
-						name: file.name
-						id:   file._id
-						type: "file"
-					}
-
-			@ide.socket.on "reciveNewFolder", (parent_folder_id, folder) =>
-				parent_folder = @findEntityById(parent_folder_id) or @$scope.rootFolder
-				@$scope.$apply () ->
-					parent_folder.children.push {
-						name: folder.name
-						id:   folder._id
-						type: "folder"
-						children: []
-					}
-
-			@ide.socket.on "reciveEntityRename", (entity_id, name) =>
-				entity = @findEntityById(entity_id)
-				return if !entity?
-				@$scope.$apply () ->
-					entity.name = name
-
-			@ide.socket.on "removeEntity", (entity_id) =>
-				entity = @findEntityById(entity_id)
-				return if !entity?
-				@$scope.$apply () =>
-					@_deleteEntityFromScope entity
-
-			@ide.socket.on "reciveEntityMove", (entity_id, folder_id) =>
-				entity = @findEntityById(entity_id)
-				folder = @findEntityById(folder_id)
-				console.log "Got recive ENTITY", entity_id, folder_id, entity, folder
-				@$scope.$apply () =>
-					@_moveEntityInScope(entity, folder)
-
-		selectEntity: (entity) ->
-			@selected_entity_id = entity.id # For reselecting after a reconnect
-			@ide.fileTreeManager.forEachEntity (entity) ->
-				entity.selected = false
-			entity.selected = true
-
-		findSelectedEntity: () ->
-			selected = null
-			@forEachEntity (entity) ->
-				selected = entity if entity.selected
-			return selected
-
-		findEntityById: (id, options = {}) ->
-			return @$scope.rootFolder if @$scope.rootFolder.id == id
-
-			entity = @_findEntityByIdInFolder @$scope.rootFolder, id
-			return entity if entity?
-
-			if options.includeDeleted
-				for entity in @$scope.deletedDocs
-					return entity if entity.id == id
-
-			return null
-
-		_findEntityByIdInFolder: (folder, id) ->
-			for entity in folder.children or []
-				if entity.id == id
-					return entity
-				else if entity.children?
-					result = @_findEntityByIdInFolder(entity, id)
-					return result if result?
-
-			return null
-
-		findEntityByPath: (path) ->
-			@_findEntityByPathInFolder @$scope.rootFolder, path
-
-		_findEntityByPathInFolder: (folder, path) ->
-			parts = path.split("/")
-			name = parts.shift()
-			rest = parts.join("/")
-			
-			if name == "."
-				return @_findEntityByPathInFolder(folder, rest)
-
-			for entity in folder.children
-				if entity.name == name
-					if rest == ""
-						return entity
-					else if entity.type == "folder"
-						return @_findEntityByPathInFolder(entity, rest)
-			return null
-
-		forEachEntity: (callback = (entity, parent_folder) ->) ->
-			@_forEachEntityInFolder(@$scope.rootFolder, callback)
-
-			for entity in @$scope.deletedDocs or []
-				callback(entity)
-
-		_forEachEntityInFolder: (folder, callback) ->
-			for entity in folder.children or []
-				callback(entity, folder)
-				if entity.children?
-					@_forEachEntityInFolder(entity, callback)
-
-		getEntityPath: (entity) ->
-			@_getEntityPathInFolder @$scope.rootFolder, entity
-
-		_getEntityPathInFolder: (folder, entity) ->
-			for child in folder.children or []
-				if child == entity
-					return entity.name
-				else if child.type == "folder"
-					path = @_getEntityPathInFolder(child, entity)
-					if path?
-						return child.name + "/" + path
-			return null
-
-		getRootDocDirname: () ->
-			rootDoc = @findEntityById @$scope.project.rootDoc_id
-			return if !rootDoc?
-			path = @getEntityPath(rootDoc)
-			return if !path?
-			return path.split("/").slice(0, -1).join("/")
-
-		# forEachFolder: (callback) ->
-		# 	@forEachEntity (entity) ->
-		# 		if entity.type == "folder"
-		# 			callback(entity)
-
-		loadRootFolder: () ->
-			@$scope.rootFolder = @_parseFolder(@$scope.project.rootFolder[0])
-
-		_parseFolder: (rawFolder) ->
-			folder = {
-				name: rawFolder.name
-				id:   rawFolder._id
-				type: "folder"
-				children: []
-				selected: (rawFolder._id == @selected_entity_id)
-			}
-
-			for doc in rawFolder.docs or []
-				folder.children.push {
-					name: doc.name
-					id:   doc._id
-					type: "doc"
-					selected: (doc._id == @selected_entity_id)
-				}
-
-			for file in rawFolder.fileRefs or []
-				folder.children.push {
-					name: file.name
-					id:   file._id
-					type: "file"
-					selected: (file._id == @selected_entity_id)
-				}
-
-			for childFolder in rawFolder.folders or []
-				folder.children.push @_parseFolder(childFolder)
-
-			return folder
-
-		loadDeletedDocs: () ->
-			@$scope.deletedDocs = []
-			for doc in @$scope.project.deletedDocs
-				@$scope.deletedDocs.push {
-					name: doc.name
-					id:   doc._id
-					type: "doc"
-					deleted: true
-				}
-
-		getCurrentFolder: () ->
-			# Return the root folder if nothing is selected
-			@_getCurrentFolder(@$scope.rootFolder) or @$scope.rootFolder
-
-		_getCurrentFolder: (startFolder = @$scope.rootFolder) ->
-			for entity in startFolder.children or []
-				# The 'current' folder is either the one selected, or
-				# the one containing the selected doc/file
-				if entity.selected
-					if entity.type == "folder"
-						return entity
-					else
-						return startFolder
-
-				if entity.type == "folder"
-					result = @_getCurrentFolder(entity)
-					return result if result?
-
-			return null
-
-		createDoc: (name, parent_folder = @getCurrentFolder()) ->
-			# We'll wait for the socket.io notification to actually
-			# add the doc for us.
-			@ide.$http.post "/project/#{@ide.project_id}/doc", {
-				name: name,
-				parent_folder_id: parent_folder?.id
-				_csrf: window.csrfToken
-			}
-
-		createFolder: (name, parent_folder = @getCurrentFolder()) ->
-			# We'll wait for the socket.io notification to actually
-			# add the folder for us.
-			return @ide.$http.post "/project/#{@ide.project_id}/folder", {
-				name: name,
-				parent_folder_id: parent_folder?.id
-				_csrf: window.csrfToken
-			}
-
-		renameEntity: (entity, name, callback = (error) ->) ->
-			return if entity.name == name
-			entity.name = name
-			return @ide.$http.post "/project/#{@ide.project_id}/#{entity.type}/#{entity.id}/rename", {
-				name: name,
-				_csrf: window.csrfToken
-			}
-
-		deleteEntity: (entity, callback = (error) ->) ->
-			# We'll wait for the socket.io notification to 
-			# delete from scope.
-			return @ide.$http {
-				method: "DELETE"
-				url:    "/project/#{@ide.project_id}/#{entity.type}/#{entity.id}"
-				headers:
-					"X-Csrf-Token": window.csrfToken
-			}
-
-		moveEntity: (entity, parent_folder, callback = (error) ->) ->
-			@_moveEntityInScope(entity, parent_folder)
-			return @ide.$http.post "/project/#{@ide.project_id}/#{entity.type}/#{entity.id}/move", {
-				folder_id: parent_folder.id
-				_csrf: window.csrfToken
-			}
-
-		_deleteEntityFromScope: (entity, options = { moveToDeleted: true }) ->
-			parent_folder = null
-			@forEachEntity (possible_entity, folder) ->
-				if possible_entity == entity
-					parent_folder = folder
-
-			if parent_folder?
-				index = parent_folder.children.indexOf(entity)
-				if index > -1
-					parent_folder.children.splice(index, 1)
-
-			if entity.type == "doc" and options.moveToDeleted
-				entity.deleted = true
-				@$scope.deletedDocs.push entity
-
-		_moveEntityInScope: (entity, parent_folder) ->
-			return if entity in parent_folder.children
-			@_deleteEntityFromScope(entity, moveToDeleted: false)
-			parent_folder.children.push(entity)

+ 0 - 113
services/web/public/coffee/app/ide/file-tree/controllers/FileTreeController.coffee

@@ -1,113 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "FileTreeController", ["$scope", "$modal", "ide", ($scope, $modal, ide) ->
-		$scope.openNewDocModal = () ->
-			$modal.open(
-				templateUrl: "newDocModalTemplate"
-				controller:  "NewDocModalController"
-				resolve: {
-					parent_folder: () -> ide.fileTreeManager.getCurrentFolder()
-				}
-			)
-
-		$scope.openNewFolderModal = () ->
-			$modal.open(
-				templateUrl: "newFolderModalTemplate"
-				controller:  "NewFolderModalController"
-				resolve: {
-					parent_folder: () -> ide.fileTreeManager.getCurrentFolder()
-				}
-			)
-
-		$scope.openUploadFileModal = () ->
-			$modal.open(
-				templateUrl: "uploadFileModalTemplate"
-				controller:  "UploadFileModalController"
-				scope: $scope
-				resolve: {
-					parent_folder: () -> ide.fileTreeManager.getCurrentFolder()
-				}
-			)
-
-		$scope.orderByFoldersFirst = (entity) ->
-			return '0' if entity.type == "folder"
-			return '1'
-
-		$scope.startRenamingSelected = () ->
-			$scope.$broadcast "rename:selected"
-
-		$scope.openDeleteModalForSelected = () ->
-			$scope.$broadcast "delete:selected"
-	]
-
-	App.controller "NewDocModalController", [
-		"$scope", "ide", "$modalInstance", "$timeout", "parent_folder",
-		($scope,   ide,   $modalInstance,   $timeout,   parent_folder) ->
-			$scope.inputs = 
-				name: "name.tex"
-			$scope.state =
-				inflight: false
-
-			$modalInstance.opened.then () ->
-				$timeout () ->
-					$scope.$broadcast "open"
-				, 200
-
-			$scope.create = () ->
-				$scope.state.inflight = true
-				ide.fileTreeManager
-					.createDoc($scope.inputs.name, parent_folder)
-					.success () ->
-						$scope.state.inflight = false
-						$modalInstance.close()
-
-			$scope.cancel = () ->
-				$modalInstance.dismiss('cancel')
-	]
-
-	App.controller "NewFolderModalController", [
-		"$scope", "ide", "$modalInstance", "$timeout", "parent_folder",
-		($scope,   ide,   $modalInstance,   $timeout,   parent_folder) ->
-			$scope.inputs = 
-				name: "name"
-			$scope.state =
-				inflight: false
-
-			$modalInstance.opened.then () ->
-				$timeout () ->
-					$scope.$broadcast "open"
-				, 200
-
-			$scope.create = () ->
-				$scope.state.inflight = true
-				ide.fileTreeManager
-					.createFolder($scope.inputs.name, parent_folder)
-					.success () ->
-						$scope.state.inflight = false
-						$modalInstance.close()
-
-			$scope.cancel = () ->
-				$modalInstance.dismiss('cancel')
-	]
-
-	App.controller "UploadFileModalController", [
-		"$scope", "ide", "$modalInstance", "$timeout", "parent_folder",
-		($scope,   ide,   $modalInstance,   $timeout,   parent_folder) ->
-			console.log "PArent folder", parent_folder
-			$scope.parent_folder_id = parent_folder?.id
-
-			uploadCount = 0
-			$scope.onUpload = () ->
-				uploadCount++
-
-			$scope.onComplete = (error, name, response) ->
-				$timeout (() ->
-					uploadCount--
-					if uploadCount == 0 and response? and response.success
-						$modalInstance.close("done")
-				), 250
-
-			$scope.cancel = () ->
-				$modalInstance.dismiss('cancel')
-	]

+ 0 - 49
services/web/public/coffee/app/ide/file-tree/controllers/FileTreeEntityController.coffee

@@ -1,49 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "FileTreeEntityController", ["$scope", "ide", "$modal", ($scope, ide, $modal) ->
-		$scope.select = () ->
-			ide.fileTreeManager.selectEntity($scope.entity)
-			$scope.$emit "entity:selected", $scope.entity
-
-		$scope.inputs =
-			name: $scope.entity.name
-
-		$scope.startRenaming = () ->
-			$scope.entity.renaming = true
-
-		$scope.finishRenaming = () ->
-			delete $scope.entity.renaming
-			ide.fileTreeManager.renameEntity($scope.entity, $scope.inputs.name)
-
-		$scope.$on "rename:selected", () ->
-			$scope.startRenaming() if $scope.entity.selected
-
-		$scope.openDeleteModal = () ->
-			$modal.open(
-				templateUrl: "deleteEntityModalTemplate"
-				controller:  "DeleteEntityModalController"
-				scope: $scope
-			)
-
-		$scope.$on "delete:selected", () ->
-			$scope.openDeleteModal() if $scope.entity.selected
-	]
-
-	App.controller "DeleteEntityModalController", [
-		"$scope", "ide", "$modalInstance",
-		($scope,   ide,   $modalInstance) ->
-			$scope.state =
-				inflight: false
-
-			$scope.delete = () ->
-				$scope.state.inflight = true
-				ide.fileTreeManager
-					.deleteEntity($scope.entity)
-					.success () ->
-						$scope.state.inflight = false
-						$modalInstance.close()
-
-			$scope.cancel = () ->
-				$modalInstance.dismiss('cancel')
-	]

+ 0 - 51
services/web/public/coffee/app/ide/file-tree/controllers/FileTreeFolderController.coffee

@@ -1,51 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "FileTreeFolderController", ["$scope", "ide", "$modal", ($scope, ide, $modal) ->
-		$scope.expanded = $.localStorage("folder.#{$scope.entity.id}.expanded") or false
-
-		$scope.toggleExpanded = () ->
-			$scope.expanded = !$scope.expanded
-			$.localStorage("folder.#{$scope.entity.id}.expanded", $scope.expanded)
-
-		$scope.onDrop = (events, ui) ->
-			source = $(ui.draggable).scope().entity
-			return if !source?
-			ide.fileTreeManager.moveEntity(source, $scope.entity)
-
-		$scope.orderByFoldersFirst = (entity) ->
-			# We need this here as well as in FileTreeController
-			# since the file-entity diretive creates a new scope
-			# that doesn't inherit from previous scopes.
-			return '0' if entity.type == "folder"
-			return '1'
-
-		$scope.openNewDocModal = () ->
-			$modal.open(
-				templateUrl: "newDocModalTemplate"
-				controller:  "NewDocModalController"
-				resolve: {
-					parent_folder: () -> $scope.entity
-				}
-			)
-
-		$scope.openNewFolderModal = () ->
-			$modal.open(
-				templateUrl: "newFolderModalTemplate"
-				controller:  "NewFolderModalController"
-				resolve: {
-					parent_folder: () -> $scope.entity
-				}
-			)
-
-		$scope.openUploadFileModal = () ->
-			$scope.project_id = ide.project_id
-			$modal.open(
-				templateUrl: "uploadFileModalTemplate"
-				controller:  "UploadFileModalController"
-				scope: $scope
-				resolve: {
-					parent_folder: () -> $scope.entity
-				}
-			)
-	]

+ 0 - 13
services/web/public/coffee/app/ide/file-tree/controllers/FileTreeRootFolderController.coffee

@@ -1,13 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "FileTreeRootFolderController", ["$scope", "ide", ($scope, ide) ->
-		console.log "CREATING FileTreeRootFolderController"
-		rootFolder = $scope.rootFolder
-		console.log "ROOT FOLDER", rootFolder
-		$scope.onDrop = (events, ui) ->
-			source = $(ui.draggable).scope().entity
-			console.log "DROPPED INTO ROOT", source, rootFolder
-			return if !source?
-			ide.fileTreeManager.moveEntity(source, rootFolder)
-	]

+ 0 - 14
services/web/public/coffee/app/ide/file-tree/directives/draggable.coffee

@@ -1,14 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.directive "draggable", () ->
-		return {
-			link: (scope, element, attrs) ->
-				scope.$watch attrs.draggable, (draggable) ->
-					if draggable
-						element.draggable
-							delay: 250
-							opacity: 0.7
-							helper: "clone"
-							scroll: true
-		}

+ 0 - 17
services/web/public/coffee/app/ide/file-tree/directives/droppable.coffee

@@ -1,17 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.directive "droppable", () ->
-		return {
-			scope: {
-				onDropCallback: "="
-			}
-			link: (scope, element, attrs) ->
-				scope.$watch attrs.droppable, (droppable) ->
-					if droppable
-						element.droppable
-							greedy: true
-							hoverClass: "droppable-hover"
-							accept: attrs.accept
-							drop: scope.onDropCallback
-		}

+ 0 - 23
services/web/public/coffee/app/ide/file-tree/directives/fileEntity.coffee

@@ -1,23 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.directive "fileEntity", ["RecursionHelper", (RecursionHelper) ->
-		return {
-			restrict: "E"
-			scope: {
-				entity: "="
-				permissions: "="
-			}
-			templateUrl: "entityListItemTemplate"
-			compile: (element) ->
-				RecursionHelper.compile element, (scope, element, attrs, ctrl) ->
-					# Don't freak out if we're already in an apply callback
-					scope.$originalApply = scope.$apply
-					scope.$apply = (fn = () ->) ->
-						phase = @$root.$$phase
-						if (phase == '$apply' || phase == '$digest')
-							fn()
-						else
-							@$originalApply(fn);
-		}
-	]

+ 0 - 71
services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee

@@ -1,71 +0,0 @@
-define [
-	"../../../libs/md5"
-], () ->
-	class OnlineUsersManager
-		constructor: (@ide, @$scope) ->
-			@$scope.onlineUsers = {}
-			@$scope.onlineUserCursorHighlights = {}
-
-			@$scope.$watch "editor.cursorPosition", (position) =>
-				if position?
-					@sendCursorPositionUpdate()
-
-			@ide.socket.on "clientTracking.clientUpdated", (client) =>
-				if client.id != @ide.socket.socket.sessionid # Check it's not me!
-					@$scope.$apply () =>
-						@$scope.onlineUsers[client.id] = client
-						@updateCursorHighlights()
-
-			@ide.socket.on "clientTracking.clientDisconnected", (client_id) =>
-				@$scope.$apply () =>
-					delete @$scope.onlineUsers[client_id]
-					@updateCursorHighlights()
-
-		updateCursorHighlights: () ->
-			console.log "UPDATING CURSOR HIGHLIGHTS"
-			@$scope.onlineUserCursorHighlights = {}
-			for client_id, client of @$scope.onlineUsers
-				doc_id = client.doc_id
-				continue if !doc_id?
-				@$scope.onlineUserCursorHighlights[doc_id] ||= []
-				@$scope.onlineUserCursorHighlights[doc_id].push {
-					label: client.name
-					cursor:
-						row: client.row
-						column: client.column
-					hue: @getHueForUserId(client.user_id)
-				}
-
-		UPDATE_INTERVAL: 500
-		sendCursorPositionUpdate: () ->
-			console.log "SENDING CURSOR POSITION UPDATE", @$scope.editor.cursorPosition
-			if !@cursorUpdateTimeout?
-				@cursorUpdateTimeout = setTimeout ()=>
-					position = @$scope.editor.cursorPosition
-					doc_id   = @$scope.editor.open_doc_id
-
-					@ide.socket.emit "clientTracking.updatePosition", {
-						row: position.row
-						column: position.column
-						doc_id: doc_id
-					}
-
-					delete @cursorUpdateTimeout
-				, @UPDATE_INTERVAL
-
-		OWN_HUE: 200 # We will always appear as this color to ourselves
-		ANONYMOUS_HUE: 100
-		getHueForUserId: (user_id) ->
-			if !user_id? or user_id == "anonymous-user"
-				return @ANONYMOUS_HUE
-
-			if window.user.id == user_id
-				return @OWN_HUE
-
-			hash = CryptoJS.MD5(user_id)
-			hue = parseInt(hash.toString().slice(0,8), 16) % 320
-			# Avoid 20 degrees either side of the personal hue
-			if hue > @OWNER_HUE - 20
-				hue = hue + 40
-			return hue
-

+ 0 - 20
services/web/public/coffee/app/ide/pdf/PdfManager.coffee

@@ -1,20 +0,0 @@
-define [
-	"ide/pdf/controllers/PdfController"
-	"ide/pdf/directives/pdfJs"
-], () ->
-	class PdfManager
-		constructor: (@ide, @$scope) ->
-			@$scope.pdf =
-				url: null # Pdf Url
-				error: false # Server error
-				timeout: false # Server timed out
-				failure: false # PDF failed to compile
-				compiling: false
-				uncompiled: true
-				logEntries: []
-				logEntryAnnotations: {}
-				rawLog: ""
-				view: null # 'pdf' 'logs'
-				showRawLog: false
-				highlights: []
-				position: null

+ 0 - 277
services/web/public/coffee/app/ide/pdf/controllers/PdfController.coffee

@@ -1,277 +0,0 @@
-define [
-	"base"
-	"libs/latex-log-parser"
-], (App, LogParser) ->
-	App.controller "PdfController", ["$scope", "$http", "ide", "$modal", "synctex", ($scope, $http, ide, $modal, synctex) ->
-		autoCompile = true
-		$scope.$on "doc:opened", () ->
-			return if !autoCompile
-			autoCompile = false
-			$scope.recompile(isAutoCompile: true)
-
-		sendCompileRequest = (options = {}) ->
-			url = "/project/#{$scope.project_id}/compile"
-			if options.isAutoCompile
-				url += "?auto_compile=true"
-			return $http.post url, {
-				settingsOverride:
-					rootDoc_id: options.rootDocOverride_id or null
-				_csrf: window.csrfToken
-			}
-
-		parseCompileResponse = (response) ->
-			# Reset everything
-			$scope.pdf.error      = false
-			$scope.pdf.timedout   = false
-			$scope.pdf.failure    = false
-			$scope.pdf.uncompiled = false
-			$scope.pdf.url        = null
-
-			if response.status == "timedout"
-				$scope.pdf.timedout = true
-			else if response.status == "autocompile-backoff"
-				$scope.pdf.uncompiled = true
-			else if response.status == "failure"
-				$scope.pdf.failure = true
-				fetchLogs()
-			else if response.status == "success"
-				$scope.pdf.url = "/project/#{$scope.project_id}/output/output.pdf?cache_bust=#{Date.now()}"
-				fetchLogs()
-
-			IGNORE_FILES = ["output.fls", "output.fdb_latexmk"]
-			$scope.pdf.outputFiles = []
-			for file in response.outputFiles
-				if IGNORE_FILES.indexOf(file.path) == -1
-					# Turn 'output.blg' into 'blg file'.
-					if file.path.match(/^output\./)
-						file.name = "#{file.path.replace(/^output\./, "")} file"
-					else
-						file.name = file.path
-					$scope.pdf.outputFiles.push file
-
-		fetchLogs = () ->
-			$http.get "/project/#{$scope.project_id}/output/output.log"
-				.success (log) ->
-					$scope.pdf.rawLog = log
-					logEntries = LogParser.parse(log, ignoreDuplicates: true)
-					$scope.pdf.logEntries = logEntries
-					$scope.pdf.logEntries.all = logEntries.errors.concat(logEntries.warnings).concat(logEntries.typesetting)
-
-					$scope.pdf.logEntryAnnotations = {}
-					for entry in logEntries.all
-						entry.file = normalizeFilePath(entry.file)
-
-						entity = ide.fileTreeManager.findEntityByPath(entry.file)
-						if entity?
-							$scope.pdf.logEntryAnnotations[entity.id] ||= []
-							$scope.pdf.logEntryAnnotations[entity.id].push {
-								row: entry.line - 1
-								type: if entry.level == "error" then "error" else "warning"
-								text: entry.message
-							}
-
-				.error () ->
-					$scope.pdf.logEntries = []
-					$scope.pdf.rawLog = ""
-
-		getRootDocOverride_id = () ->
-			doc = ide.editorManager.getCurrentDocValue()
-			return null if !doc?
-			for line in doc.split("\n")
-				match = line.match /(.*)\\documentclass/
-				if match and !match[1].match /%/
-					return ide.editorManager.getCurrentDocId()
-			return null
-
-		normalizeFilePath = (path) ->
-			path = path.replace(/^(.*)\/compiles\/[0-9a-f]{24}\/(\.\/)?/, "")
-			path = path.replace(/^\/compile\//, "")
-
-			rootDocDirname = ide.fileTreeManager.getRootDocDirname()
-			if rootDocDirname?
-				path = path.replace(/^\.\//, rootDocDirname + "/")
-
-			return path
-
-		$scope.recompile = (options = {}) ->
-			console.log "Recompiling", options
-			return if $scope.pdf.compiling
-			$scope.pdf.compiling = true
-
-			options.rootDocOverride_id = getRootDocOverride_id()
-
-			sendCompileRequest(options)
-				.success (data) ->
-					$scope.pdf.view = "pdf"
-					$scope.pdf.compiling = false
-					parseCompileResponse(data)
-				.error () ->
-					$scope.pdf.compiling = false
-					$scope.pdf.error = true
-
-		$scope.clearCache = () ->
-			$http {
-				url: "/project/#{$scope.project_id}/output"
-				method: "DELETE"
-				headers:
-					"X-Csrf-Token": window.csrfToken
-			}
-
-		$scope.toggleLogs = () ->
-			if !$scope.pdf.view? or $scope.pdf.view == "pdf"
-				$scope.pdf.view = "logs"
-			else
-				$scope.pdf.view = "pdf"
-
-		$scope.showPdf = () ->
-			$scope.pdf.view = "pdf"
-
-		$scope.toggleRawLog = () ->
-			$scope.pdf.showRawLog = !$scope.pdf.showRawLog
-
-		$scope.openOutputFile = (file) ->
-			window.open("/project/#{$scope.project_id}/output/#{file.path}")
-
-		$scope.openClearCacheModal = () ->
-			modalInstance = $modal.open(
-				templateUrl: "clearCacheModalTemplate"
-				controller: "ClearCacheModalController"
-				scope: $scope
-			)
-
-		$scope.syncToCode = (position) ->
-			console.log "SYNCING VIA DBL CLICK", position
-			synctex
-				.syncToCode(position)
-				.then (data) ->
-					{doc, line} = data
-					ide.editorManager.openDoc(doc, gotoLine: line)
-
-	]
-
-	App.factory "synctex", ["ide", "$http", "$q", (ide, $http, $q) ->
-		synctex =
-			syncToPdf: (cursorPosition) ->
-				deferred = $q.defer()
-
-				doc_id = ide.editorManager.getCurrentDocId()
-				if !doc_id?
-					deferred.reject()
-					return deferred.promise
-				doc = ide.fileTreeManager.findEntityById(doc_id)
-				if !doc?
-					deferred.reject()
-					return deferred.promise
-				path = ide.fileTreeManager.getEntityPath(doc)
-				if !path?
-					deferred.reject()
-					return deferred.promise
-				
-				# If the root file is folder/main.tex, then synctex sees the
-				# path as folder/./main.tex
-				rootDocDirname = ide.fileTreeManager.getRootDocDirname()
-				if rootDocDirname? and rootDocDirname != ""
-					path = path.replace(RegExp("^#{rootDocDirname}"), "#{rootDocDirname}/.")
-
-				{row, column} = cursorPosition
-
-				$http({
-						url: "/project/#{ide.project_id}/sync/code", 
-						method: "GET",
-						params: {
-							file: path
-							line: row + 1
-							column: column
-						}
-					})
-					.success (data) ->
-						deferred.resolve(data.pdf or [])
-					.error (error) ->
-						deferred.reject(error)
-
-				return deferred.promise
-
-			syncToCode: (position, options = {}) ->
-				deferred = $q.defer()
-				if !position?
-					deferred.reject()
-					return deferred.promise
-
-				# It's not clear exactly where we should sync to if it wasn't directly
-				# clicked on, but a little bit down from the very top seems best.
-				if options.includeVisualOffset
-					position.offset.top = position.offset.top + 80
-
-				$http({
-						url: "/project/#{ide.project_id}/sync/pdf", 
-						method: "GET",
-						params: {
-							page: position.page + 1
-							h: position.offset.left.toFixed(2)
-							v: position.offset.top.toFixed(2)
-						}
-					})
-					.success (data) ->
-						if data.code? and data.code.length > 0
-							doc = ide.fileTreeManager.findEntityByPath(data.code[0].file)
-							return if !doc?
-							deferred.resolve({doc: doc, line: data.code[0].line})
-					.error (error) ->
-						deferred.reject(error)
-
-				return deferred.promise
-
-		return synctex
-	]
-
-	App.controller "PdfSynctexController", ["$scope", "synctex", "ide", ($scope, synctex, ide) ->
-		$scope.showControls = true
-		$scope.$on "layout:pdf:resize", (event, data) ->
-			if data.east.initClosed
-				$scope.showControls = false
-			else
-				$scope.showControls = true
-			setTimeout () ->
-				$scope.$digest()
-			, 0
-
-		$scope.syncToPdf = () ->
-			synctex
-				.syncToPdf($scope.editor.cursorPosition)
-				.then (highlights) ->
-					$scope.pdf.highlights = highlights
-
-		$scope.syncToCode = () ->
-			synctex
-				.syncToCode($scope.pdf.position, includeVisualOffset: true)
-				.then (data) ->
-					{doc, line} = data
-					console.log "OPENING DOC", doc, line
-					ide.editorManager.openDoc(doc, gotoLine: line)
-	]
-
-	App.controller "PdfLogEntryController", ["$scope", "ide", ($scope, ide) ->
-		$scope.openInEditor = (entry) ->
-			console.log "OPENING", entry.file, entry.line
-			entity = ide.fileTreeManager.findEntityByPath(entry.file)
-			return if !entity? or entity.type != "doc"
-			if entry.line?
-				line = entry.line
-			ide.editorManager.openDoc(entity, gotoLine: line)
-	]
-
-	App.controller 'ClearCacheModalController', ["$scope", "$modalInstance", ($scope, $modalInstance) ->
-		$scope.state =
-			inflight: false
-
-		$scope.clear = () ->
-			$scope.state.inflight = true
-			$scope
-				.clearCache()
-				.then () ->
-					$scope.state.inflight = false
-					$modalInstance.close()
-
-		$scope.cancel = () ->
-			$modalInstance.dismiss('cancel')
-	]

+ 0 - 196
services/web/public/coffee/app/ide/pdf/directives/pdfJs.coffee

@@ -1,196 +0,0 @@
-define [
-	"base"
-	"libs/pdfListView/PdfListView"
-	"libs/pdfListView/TextLayerBuilder"
-	"libs/pdfListView/AnnotationsLayerBuilder"
-	"libs/pdfListView/HighlightsLayerBuilder"
-	"text!libs/pdfListView/TextLayer.css"
-	"text!libs/pdfListView/AnnotationsLayer.css"
-	"text!libs/pdfListView/HighlightsLayer.css"
-], (
-	App
-	PDFListView
-	TextLayerBuilder
-	AnnotationsLayerBuilder
-	HighlightsLayerBuilder
-	textLayerCss
-	annotationsLayerCss
-	highlightsLayerCss
-) ->
-	if PDFJS?
-		PDFJS.workerSrc = "#{window.sharelatex.pdfJsWorkerPath}"
-
-	style = $("<style/>")
-	style.text(textLayerCss + "\n" + annotationsLayerCss + "\n" + highlightsLayerCss)
-	$("body").append(style)
-
-	App.directive "pdfjs", ["$timeout", ($timeout) ->
-		return {
-			scope: {
-				"pdfSrc": "="
-				"highlights": "="
-				"position": "="
-				"dblClickCallback": "="
-			}
-			link: (scope, element, attrs) ->
-				pdfListView = new PDFListView element.find(".pdfjs-viewer")[0],
-					textLayerBuilder: TextLayerBuilder
-					annotationsLayerBuilder: AnnotationsLayerBuilder
-					highlightsLayerBuilder: HighlightsLayerBuilder
-					ondblclick: (e) -> onDoubleClick(e)
-					logLevel: PDFListView.Logger.DEBUG
-				pdfListView.listView.pageWidthOffset = 20
-				pdfListView.listView.pageHeightOffset = 20
-
-				scope.loading = false
-
-				onProgress = (progress) ->
-					scope.$apply () ->
-						scope.progress = Math.floor(progress.loaded/progress.total*100)
-						console.log "PROGRESS", scope.progress, progress.loaded, progress.total
-
-				initializedPosition = false
-				initializePosition = () ->
-					return if initializedPosition
-					initializedPosition = true
-
-					if (scale = $.localStorage("pdf.scale"))?
-						pdfListView.setScaleMode(scale.scaleMode, scale.scale)
-					else
-						pdfListView.setToFitWidth()
-
-					if (position = $.localStorage("pdf.position.#{attrs.key}"))
-						pdfListView.setPdfPosition(position)
-
-					scope.position = pdfListView.getPdfPosition(true)
-
-					$(window).unload () =>
-						$.localStorage "pdf.scale", {
-							scaleMode: pdfListView.getScaleMode()
-							scale: pdfListView.getScale()
-						}
-						$.localStorage "pdf.position.#{attrs.key}", pdfListView.getPdfPosition()
-
-				flashControls = () ->
-					scope.flashControls = true
-					$timeout () ->
-						scope.flashControls = false
-					, 1000
-
-				element.find(".pdfjs-viewer").scroll () ->
-					console.log "UPDATING POSITION", pdfListView.getPdfPosition(true)
-					scope.position = pdfListView.getPdfPosition(true)
-
-				onDoubleClick = (e) ->
-					scope.dblClickCallback?(page: e.page, offset: { top: e.y, left: e.x })
-
-				scope.$watch "pdfSrc", (url) ->
-					if url
-						scope.loading = true
-						scope.progress = 0
-
-						pdfListView
-							.loadPdf(url, onProgress)
-							.then () ->
-								scope.$apply () ->
-									scope.loading = false
-									delete scope.progress
-									initializePosition()
-									flashControls()
-
-				scope.$watch "highlights", (areas) ->
-					console.log "UPDATING HIGHLIGHTS", areas
-					return if !areas?
-					highlights = for area in areas or []
-						{
-							page: area.page - 1
-							highlight:
-								left: area.h
-								top: area.v
-								height: area.height
-								width: area.width
-						}
-
-					if highlights.length > 0
-						first = highlights[0]
-						pdfListView.setPdfPosition({
-							page: first.page
-							offset:
-								left: first.highlight.left
-								top: first.highlight.top - 80
-						}, true)
-
-					pdfListView.clearHighlights()
-					pdfListView.setHighlights(highlights, true)
-					
-					setTimeout () =>
-						pdfListView.clearHighlights()
-					, 1000
-
-				scope.fitToHeight = () ->
-					pdfListView.setToFitHeight()
-
-				scope.fitToWidth = () ->
-					pdfListView.setToFitWidth()
-
-				scope.zoomIn = () ->
-					scale = pdfListView.getScale()
-					pdfListView.setScale(scale * 1.2)
-
-				scope.zoomOut = () ->
-					scale = pdfListView.getScale()
-					pdfListView.setScale(scale / 1.2)
-
-				if attrs.resizeOn?
-					for event in attrs.resizeOn.split(",")
-						scope.$on event, () ->
-							pdfListView.onResize()
-
-			template: """
-				<div class="pdfjs-viewer"></div>
-				<div class="pdfjs-controls" ng-class="{'flash': flashControls }">
-					<div class="btn-group">
-						<a href
-							class="btn btn-info btn-lg"
-							ng-click="fitToWidth()"
-							tooltip="Fit to Width"
-							tooltip-append-to-body="true"
-							tooltip-placement="bottom"
-						>
-							<i class="fa fa-fw fa-arrows-h"></i>
-						</a>
-						<a href
-							class="btn btn-info btn-lg"
-							ng-click="fitToHeight()"
-							tooltip="Fit to Height"
-							tooltip-append-to-body="true"
-							tooltip-placement="bottom"
-						>
-							<i class="fa fa-fw fa-arrows-v"></i>
-						</a>
-						<a href
-							class="btn btn-info btn-lg"
-							ng-click="zoomIn()"
-							tooltip="Zoom In"
-							tooltip-append-to-body="true"
-							tooltip-placement="bottom"
-						>
-							<i class="fa fa-fw fa-search-plus"></i>
-						</a>
-						<a href
-							class="btn btn-info btn-lg"
-							ng-click="zoomOut()"
-							tooltip="Zoom Out"
-							tooltip-append-to-body="true"
-							tooltip-placement="bottom"
-						>
-							<i class="fa fa-fw fa-search-minus"></i>
-						</a>
-					</div>
-				</div>
-				<div class="progress-thin" ng-show="loading">
-					<div class="progress-bar" ng-style="{ 'width': progress + '%' }"></div>
-				</div>
-			"""
-		}
-	]

+ 0 - 19
services/web/public/coffee/app/ide/permissions/PermissionsManager.coffee

@@ -1,19 +0,0 @@
-define [], () ->
-	class PermissionsManager
-		constructor: (@ide, @$scope) ->
-			@$scope.$watch "permissionsLevel", (permissionsLevel) =>
-				@$scope.permissions =
-					read:  false
-					write: false
-					admin: false
-				if permissionsLevel?
-					if permissionsLevel == "readOnly"
-						@$scope.permissions.read = true
-					else if permissionsLevel == "readAndWrite"
-						@$scope.permissions.read = true
-						@$scope.permissions.write = true
-					else if permissionsLevel == "owner"
-						@$scope.permissions.read = true
-						@$scope.permissions.write = true
-						@$scope.permissions.admin = true
-

+ 0 - 37
services/web/public/coffee/app/ide/services/ide.coffee

@@ -1,37 +0,0 @@
-define [
-	"base"
-], (App) ->
-	# We create and provide this as service so that we can access the global ide
-	# from within other parts of the angular app.
-	App.factory "ide", ["$http", "$modal", ($http, $modal) ->
-		ide = {}
-		ide.$http = $http
-
-		ide.pushEvent = () ->
-			#console.log "PUSHING EVENT STUB", arguments
-
-		ide.reportError = () ->
-			console.log "REPORTING ERROR STUB", arguments
-
-		ide.showGenericServerErrorMessage = () ->
-			console.error "GENERIC SERVER ERROR MESSAGE STUB"
-
-		ide.showGenericMessageModal = (title, message) ->
-			$modal.open {
-				templateUrl: "genericMessageModalTemplate"
-				controller:  "GenericMessageModalController"
-				resolve:
-					title:   -> title
-					message: -> message
-			}
-
-		return ide
-	]
-
-	App.controller "GenericMessageModalController", ["$scope", "$modalInstance", "title", "message", ($scope, $modalInstance, title, message) ->
-		$scope.title = title
-		$scope.message = message
-
-		$scope.done = () ->
-			$modalInstance.close()
-	]

+ 0 - 26
services/web/public/coffee/app/ide/settings/controllers/ProjectNameController.coffee

@@ -1,26 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "ProjectNameController", ["$scope", "settings", "ide", ($scope, settings, ide) ->
-		$scope.state =
-			renaming: false
-		$scope.inputs = {}
-
-		$scope.startRenaming = () ->
-			$scope.inputs.name = $scope.project.name
-			$scope.state.renaming = true
-			$scope.$emit "project:rename:start"
-
-		$scope.finishRenaming = () ->
-			$scope.project.name = $scope.inputs.name
-			settings.saveProjectSettings({name: $scope.inputs.name})
-			$scope.state.renaming = false
-
-		ide.socket.on "projectNameUpdated", (name) ->
-			$scope.$apply () ->
-				$scope.project.name = name
-
-		$scope.$watch "project.name", (name) ->
-			if name?
-				window.document.title = name + " - Online LaTeX Editor ShareLaTeX"
-	]

+ 0 - 51
services/web/public/coffee/app/ide/settings/controllers/SettingsController.coffee

@@ -1,51 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "SettingsController", ["$scope", "settings", "ide", ($scope, settings, ide) ->
-		if $scope.settings.mode not in ["default", "vim", "emacs"]
-			$scope.settings.mode = "default"
-
-		$scope.$watch "settings.theme", (theme, oldTheme) =>
-			if theme != oldTheme
-				settings.saveSettings({theme: theme})
-
-		$scope.$watch "settings.fontSize", (fontSize, oldFontSize) =>
-			if fontSize != oldFontSize
-				settings.saveSettings({fontSize: parseInt(fontSize, 10)})
-
-		$scope.$watch "settings.mode", (mode, oldMode) =>
-			if mode != oldMode
-				settings.saveSettings({mode: mode})
-
-		$scope.$watch "settings.autoComplete", (autoComplete, oldAutoComplete) =>
-			if autoComplete != oldAutoComplete
-				settings.saveSettings({autoComplete: autoComplete})
-
-		$scope.$watch "settings.pdfViewer", (pdfViewer, oldPdfViewer) =>
-			if pdfViewer != oldPdfViewer
-				settings.saveSettings({pdfViewer: pdfViewer})
-
-		$scope.$watch "project.spellCheckLanguage", (language, oldLanguage) =>
-			return if @ignoreUpdates
-			if oldLanguage? and language != oldLanguage
-				settings.saveProjectSettings({spellCheckLanguage: language})
-				# Also set it as the default for the user
-				settings.saveSettings({spellCheckLanguage: language})
-
-		$scope.$watch "project.compiler", (compiler, oldCompiler) =>
-			return if @ignoreUpdates
-			if oldCompiler? and compiler != oldCompiler
-				settings.saveProjectSettings({compiler: compiler})
-
-		ide.socket.on "compilerUpdated", (compiler) =>
-			@ignoreUpdates = true
-			$scope.$apply () =>
-				$scope.project.compiler = compiler
-			delete @ignoreUpdates
-
-		ide.socket.on "spellCheckLanguageUpdated", (languageCode) =>
-			@ignoreUpdates = true
-			$scope.$apply () =>
-				$scope.project.spellCheckLanguage = languageCode
-			delete @ignoreUpdates
-	]

+ 0 - 7
services/web/public/coffee/app/ide/settings/index.coffee

@@ -1,7 +0,0 @@
-define [
-	"ide/settings/services/settings"
-	"ide/settings/controllers/SettingsController"
-	"ide/settings/controllers/ProjectNameController"
-
-], () ->
-			

+ 0 - 14
services/web/public/coffee/app/ide/settings/services/settings.coffee

@@ -1,14 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.factory "settings", ["ide", (ide) ->
-		return {
-			saveSettings: (data) ->
-				data._csrf = window.csrfToken
-				ide.$http.post "/user/settings", data
-
-			saveProjectSettings: (data) ->
-				data._csrf = window.csrfToken
-				ide.$http.post "/project/#{ide.project_id}/settings", data
-		}
-	]

+ 0 - 11
services/web/public/coffee/app/ide/share/controllers/ShareController.coffee

@@ -1,11 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "ShareController", ["$scope", "$modal", ($scope, $modal) ->
-		$scope.openShareProjectModal = () ->
-			$modal.open(
-				templateUrl: "shareProjectModalTemplate"
-				controller:  "ShareProjectModalController"
-				scope: $scope
-			)
-	]

+ 0 - 104
services/web/public/coffee/app/ide/share/controllers/ShareProjectModalController.coffee

@@ -1,104 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "ShareProjectModalController", ["$scope", "$modalInstance", "$timeout", "projectMembers", "$modal", ($scope, $modalInstance, $timeout, projectMembers, $modal) ->
-		$scope.inputs = {
-			privileges: "readAndWrite"
-			email: ""
-		}
-		$scope.state = {
-			error: null
-			inflight: false
-			startedFreeTrial: false
-		}
-
-		$modalInstance.opened.then () ->
-			$timeout () ->
-				$scope.$broadcast "open"
-			, 200
-
-		INFINITE_COLLABORATORS = -1
-		$scope.$watch "project.members.length", (noOfMembers) ->
-			allowedNoOfMembers = $scope.project.features.collaborators
-			$scope.canAddCollaborators = noOfMembers < allowedNoOfMembers or allowedNoOfMembers == INFINITE_COLLABORATORS
-
-		$scope.addMember = () ->
-			console.log "EMAIL", $scope.inputs.email
-			return if !$scope.inputs.email? or $scope.inputs.email == ""
-			$scope.state.error = null
-			$scope.state.inflight = true
-			projectMembers
-				.addMember($scope.inputs.email, $scope.inputs.privileges)
-				.then (user) ->
-					$scope.state.inflight = false
-					$scope.inputs.email = ""
-					console.log "GOT USER", user
-					$scope.project.members.push user
-				.catch () ->
-					$scope.state.inflight = false
-					$scope.state.error = "Sorry, something went wrong :("
-
-
-		$scope.removeMember = (member) ->
-			$scope.state.error = null
-			$scope.state.inflight = true
-			projectMembers
-				.removeMember(member)
-				.then () ->
-					$scope.state.inflight = false
-					index = $scope.project.members.indexOf(member)
-					return if index == -1
-					$scope.project.members.splice(index, 1)
-				.catch () ->
-					$scope.state.inflight = false
-					$scope.state.error = "Sorry, something went wrong :("
-
-		$scope.startFreeTrial = () ->
-			ga?('send', 'event', 'subscription-funnel', 'upgraded-free-trial', "projectMembers")
-			window.open("/user/subscription/plans")
-			$scope.state.startedFreeTrial = true
-
-		$scope.openMakePublicModal = () ->
-			$modal.open {
-				templateUrl: "makePublicModalTemplate"
-				controller:  "MakePublicModalController"
-				scope: $scope
-			}
-
-		$scope.openMakePrivateModal = () ->
-			$modal.open {
-				templateUrl: "makePrivateModalTemplate"
-				controller:  "MakePrivateModalController"
-				scope: $scope
-			}
-
-		$scope.done = () ->
-			$modalInstance.close()
-
-		$scope.cancel = () ->
-			$modalInstance.dismiss()
-	]
-
-	App.controller "MakePublicModalController", ["$scope", "$modalInstance", "settings", ($scope, $modalInstance, settings) ->
-		$scope.inputs = {
-			privileges: "readAndWrite"
-		}
-
-		$scope.makePublic = () ->
-			$scope.project.publicAccesLevel = $scope.inputs.privileges
-			settings.saveProjectSettings({publicAccessLevel: $scope.inputs.privileges})
-			$modalInstance.close()
-
-		$scope.cancel = () ->
-			$modalInstance.dismiss()
-	]
-
-	App.controller "MakePrivateModalController", ["$scope", "$modalInstance", "settings", ($scope, $modalInstance, settings) ->
-		$scope.makePrivate = () ->
-			$scope.project.publicAccesLevel = "private"
-			settings.saveProjectSettings({publicAccessLevel: "private"})
-			$modalInstance.close()
-
-		$scope.cancel = () ->
-			$modalInstance.dismiss()
-	]

+ 0 - 5
services/web/public/coffee/app/ide/share/index.coffee

@@ -1,5 +0,0 @@
-define [
-	"ide/share/controllers/ShareController"
-	"ide/share/controllers/ShareProjectModalController"
-	"ide/share/services/projectMembers"
-], () ->

+ 0 - 30
services/web/public/coffee/app/ide/share/services/projectMembers.coffee

@@ -1,30 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.factory "projectMembers", ["ide", "$q", (ide, $q) ->
-		return {
-			removeMember: (member) ->
-				deferred = $q.defer()
-
-				ide.socket.emit "removeUserFromProject", member._id, (error) =>
-					if error?
-						return deferred.reject(error)
-					deferred.resolve()
-
-				return deferred.promise
-
-			addMember: (email, privileges) ->
-				deferred = $q.defer()
-
-				ide.socket.emit "addUserToProject", email, privileges, (error, user) =>
-					if error?
-						return deferred.reject(error)
-
-					if !user
-						deferred.reject()
-					else
-						deferred.resolve(user)
-
-				return deferred.promise
-		}
-	]

+ 0 - 256
services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee

@@ -1,256 +0,0 @@
-define [
-	"ide/track-changes/controllers/TrackChangesListController"
-	"ide/track-changes/controllers/TrackChangesDiffController"
-	"ide/track-changes/directives/infiniteScroll"
-], () ->
-	class TrackChangesManager
-		constructor: (@ide, @$scope) ->
-			@reset()
-
-			@$scope.toggleTrackChanges = () =>
-				if @$scope.ui.view == "track-changes"
-					@hide()
-				else
-					@show()
-
-			@$scope.$watch "trackChanges.selection.updates", (updates) =>
-				if updates? and updates.length > 0
-					@_selectDocFromUpdates()
-					@reloadDiff()
-
-			@$scope.$on "entity:selected", (event, entity) =>
-				if (@$scope.ui.view == "track-changes") and (entity.type == "doc")
-					@$scope.trackChanges.selection.doc = entity
-					@reloadDiff()
-
-		show: () ->
-			@$scope.ui.view = "track-changes"
-			@reset()
-
-		hide: () ->
-			@$scope.ui.view = "editor"
-			# Make sure we run the 'open' logic for whatever is currently selected
-			@$scope.$emit "entity:selected", @ide.fileTreeManager.findSelectedEntity()
-
-		reset: () ->
-			@$scope.trackChanges = {
-				updates: []
-				nextBeforeTimestamp: null
-				atEnd: false
-				selection: {
-					updates: []
-					doc: null
-					range: {
-						fromV: null
-						toV: null
-						start_ts: null
-						end_ts: null
-					}
-				}
-				diff: null
-			}
-
-		autoSelectRecentUpdates: () ->
-			console.log "AUTO SELECTING UPDATES", @$scope.trackChanges.updates.length
-			return if @$scope.trackChanges.updates.length == 0
-
-			@$scope.trackChanges.updates[0].selectedTo = true
-
-			indexOfLastUpdateNotByMe = 0
-			for update, i in @$scope.trackChanges.updates
-				if @_updateContainsUserId(update, @$scope.user.id)
-					break
-				indexOfLastUpdateNotByMe = i
-
-			@$scope.trackChanges.updates[indexOfLastUpdateNotByMe].selectedFrom = true
-
-		BATCH_SIZE: 4
-		fetchNextBatchOfUpdates: () ->
-			url = "/project/#{@ide.project_id}/updates?min_count=#{@BATCH_SIZE}"
-			if @$scope.trackChanges.nextBeforeTimestamp?
-				url += "&before=#{@$scope.trackChanges.nextBeforeTimestamp}"
-			@$scope.trackChanges.loading = true
-			@ide.$http
-				.get(url)
-				.success (data) =>
-					@_loadUpdates(data.updates)
-					@$scope.trackChanges.nextBeforeTimestamp = data.nextBeforeTimestamp
-					if !data.nextBeforeTimestamp?
-						@$scope.trackChanges.atEnd = true
-					@$scope.trackChanges.loading = false
-
-		reloadDiff: () ->
-			diff = @$scope.trackChanges.diff
-			{updates, doc} = @$scope.trackChanges.selection
-			{fromV, toV}   = @_calculateRangeFromSelection()
-
-			console.log "Checking if diff has changed", doc?.id, fromV, toV, updates
-
-			return if !doc?
-
-			return if diff? and
-				diff.doc   == doc   and
-				diff.fromV == fromV and
-				diff.toV   == toV
-
-			console.log "Loading diff", fromV, toV, doc?.id
-
-			@$scope.trackChanges.diff = diff = {
-				fromV:   fromV
-				toV:     toV
-				doc:     doc
-				error:   false
-			}
-
-			if !doc.deleted
-				diff.loading = true
-				url = "/project/#{@$scope.project_id}/doc/#{diff.doc.id}/diff"
-				if diff.fromV? and diff.toV?
-					url += "?from=#{diff.fromV}&to=#{diff.toV}"
-
-				@ide.$http
-					.get(url)
-					.success (data) =>
-						diff.loading = false
-						{text, highlights} = @_parseDiff(data)
-						diff.text = text
-						diff.highlights = highlights
-					.error () ->
-						diff.loading = false
-						diff.error = true
-			else
-				diff.deleted = true
-				console.log "DOC IS DELETED - NO DIFF FOR YOU!"
-
-		restoreDeletedDoc: (doc) ->
-			@ide.$http.post "/project/#{@$scope.project_id}/doc/#{doc.id}/restore", {
-				name: doc.name
-				_csrf: window.csrfToken
-			}
-
-		_parseDiff: (diff) ->
-			row    = 0
-			column = 0
-			highlights = []
-			text   = ""
-			for entry, i in diff.diff or []
-				content = entry.u or entry.i or entry.d
-				content ||= ""
-				text += content
-				lines   = content.split("\n")
-				startRow    = row
-				startColumn = column
-				if lines.length > 1
-					endRow    = startRow + lines.length - 1
-					endColumn = lines[lines.length - 1].length
-				else
-					endRow    = startRow
-					endColumn = startColumn + lines[0].length
-				row    = endRow
-				column = endColumn
-
-				range = {
-					start:
-						row: startRow
-						column: startColumn
-					end:
-						row: endRow
-						column: endColumn
-				}
-
-				if entry.i? or entry.d?
-					if entry.meta.user?
-						name = "#{entry.meta.user.first_name} #{entry.meta.user.last_name}"
-					else
-						name = "Anonymous"
-					if entry.meta.user?.id == @$scope.user.id
-						name = "you"
-					date = moment(entry.meta.end_ts).format("Do MMM YYYY, h:mm a")
-					if entry.i?
-						highlights.push {
-							label: "Added by #{name} on #{date}"
-							highlight: range
-							hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user?.id)
-						}
-					else if entry.d?
-						highlights.push {
-							label: "Deleted by #{name} on #{date}"
-							strikeThrough: range
-							hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user?.id)
-						}
-
-			return {text, highlights}
-
-		_loadUpdates: (updates = []) ->
-			previousUpdate = @$scope.trackChanges.updates[@$scope.trackChanges.updates.length - 1]
-
-			for update in updates
-				for doc_id, doc of update.docs or {}
-					doc.entity = @ide.fileTreeManager.findEntityById(doc_id, includeDeleted: true)
-
-				for user in update.meta.users or []
-					user.hue = @ide.onlineUsersManager.getHueForUserId(user.id)
-
-				if !previousUpdate? or !moment(previousUpdate.meta.end_ts).isSame(update.meta.end_ts, "day")
-					update.meta.first_in_day = true
-
-				update.selectedFrom = false
-				update.selectedTo = false
-				update.inSelection = false
-
-				previousUpdate = update
-
-			firstLoad = @$scope.trackChanges.updates.length == 0
-
-			@$scope.trackChanges.updates =
-				@$scope.trackChanges.updates.concat(updates)
-
-			@autoSelectRecentUpdates() if firstLoad
-
-		_calculateRangeFromSelection: () ->
-			fromV = toV = start_ts = end_ts = null
-
-			selected_doc_id = @$scope.trackChanges.selection.doc?.id
-
-			for update in @$scope.trackChanges.selection.updates or []
-				for doc_id, doc of update.docs
-					if doc_id == selected_doc_id
-						if fromV? and toV?
-							fromV = Math.min(fromV, doc.fromV)
-							toV = Math.max(toV, doc.toV)
-							start_ts = Math.min(start_ts, update.meta.start_ts)
-							end_ts = Math.max(end_ts, update.meta.end_ts)
-						else
-							fromV = doc.fromV
-							toV = doc.toV
-							start_ts = update.meta.start_ts
-							end_ts = update.meta.end_ts
-						break
-
-			return {fromV, toV, start_ts, end_ts}
-
-		# Set the track changes selected doc to one of the docs in the range
-		# of currently selected updates. If we already have a selected doc
-		# then prefer this one if present.
-		_selectDocFromUpdates: () ->
-			affected_docs = {}
-			for update in @$scope.trackChanges.selection.updates
-				for doc_id, doc of update.docs
-					affected_docs[doc_id] = doc.entity
-
-			selected_doc = @$scope.trackChanges.selection.doc
-			if selected_doc? and affected_docs[selected_doc.id]?
-				console.log "An affected doc is already open, bravo!"
-			else
-				console.log "selected doc is not open, selecting first one"
-				for doc_id, doc of affected_docs
-					selected_doc = doc
-					break
-
-			@$scope.trackChanges.selection.doc = selected_doc
-			@ide.fileTreeManager.selectEntity(selected_doc)
-
-		_updateContainsUserId: (update, user_id) ->
-			for user in update.meta.users
-				return true if user.id == user_id
-			return false

+ 0 - 9
services/web/public/coffee/app/ide/track-changes/controllers/TrackChangesDiffController.coffee

@@ -1,9 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "TrackChangesDiffController", ["$scope", "ide", ($scope, ide) ->
-		$scope.restoreDeletedDoc = () ->
-			ide.trackChangesManager.restoreDeletedDoc(
-				$scope.trackChanges.diff.doc
-			)
-	]

+ 0 - 109
services/web/public/coffee/app/ide/track-changes/controllers/TrackChangesListController.coffee

@@ -1,109 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "TrackChangesListController", ["$scope", "ide", ($scope, ide) ->
-		$scope.hoveringOverListSelectors = false
-
-		$scope.loadMore = () =>
-			ide.trackChangesManager.fetchNextBatchOfUpdates()
-
-		$scope.recalculateSelectedUpdates = () ->
-			console.log "RECALCULATING UPDATES"
-			beforeSelection = true
-			afterSelection = false
-			$scope.trackChanges.selection.updates = []
-			for update in $scope.trackChanges.updates
-				if update.selectedTo
-					inSelection = true
-					beforeSelection = false
-
-				update.beforeSelection = beforeSelection
-				update.inSelection = inSelection
-				update.afterSelection = afterSelection
-
-				if inSelection
-					$scope.trackChanges.selection.updates.push update
-
-				if update.selectedFrom
-					inSelection = false
-					afterSelection = true
-
-		$scope.recalculateHoveredUpdates = () ->
-			hoverSelectedFrom = false
-			hoverSelectedTo = false
-			for update in $scope.trackChanges.updates
-				# Figure out whether the to or from selector is hovered over
-				if update.hoverSelectedFrom
-					hoverSelectedFrom = true
-				if update.hoverSelectedTo
-					hoverSelectedTo = true
-
-			if hoverSelectedFrom
-				# We want to 'hover select' everything between hoverSelectedFrom and selectedTo
-				inHoverSelection = false
-				for update in $scope.trackChanges.updates
-					if update.selectedTo
-						update.hoverSelectedTo = true
-						inHoverSelection = true
-					update.inHoverSelection = inHoverSelection
-					if update.hoverSelectedFrom
-						inHoverSelection = false
-			if hoverSelectedTo
-				# We want to 'hover select' everything between hoverSelectedTo and selectedFrom
-				inHoverSelection = false
-				for update in $scope.trackChanges.updates
-					if update.hoverSelectedTo
-						inHoverSelection = true
-					update.inHoverSelection = inHoverSelection
-					if update.selectedFrom
-						update.hoverSelectedFrom = true
-						inHoverSelection = false
-
-		$scope.resetHoverState = () ->
-			for update in $scope.trackChanges.updates
-				delete update.hoverSelectedFrom
-				delete update.hoverSelectedTo
-				delete update.inHoverSelection
-
-		$scope.$watch "trackChanges.updates.length", () ->
-			$scope.recalculateSelectedUpdates()
-	]
-
-	App.controller "TrackChangesListItemController", ["$scope", ($scope) ->
-		$scope.$watch "update.selectedFrom", (selectedFrom, oldSelectedFrom) ->
-			if selectedFrom
-				console.log "SELECTED FROM CHANGED", $scope.update, selectedFrom, oldSelectedFrom
-				for update in $scope.trackChanges.updates
-					update.selectedFrom = false unless update == $scope.update
-				$scope.recalculateSelectedUpdates()		
-
-		$scope.$watch "update.selectedTo", (selectedTo, oldSelectedTo) ->
-			if selectedTo
-				console.log "SELECTED TO CHANGED", $scope.update, selectedTo, oldSelectedTo
-				for update in $scope.trackChanges.updates
-					update.selectedTo = false unless update == $scope.update
-				$scope.recalculateSelectedUpdates()
-
-		$scope.select = () ->
-			$scope.update.selectedTo = true
-			$scope.update.selectedFrom = true
-
-		$scope.mouseOverSelectedFrom = () ->
-			$scope.trackChanges.hoveringOverListSelectors = true
-			$scope.update.hoverSelectedFrom = true
-			$scope.recalculateHoveredUpdates()
-
-		$scope.mouseOutSelectedFrom = () ->
-			$scope.trackChanges.hoveringOverListSelectors = false
-			$scope.resetHoverState()
-
-		$scope.mouseOverSelectedTo = () ->
-			$scope.trackChanges.hoveringOverListSelectors = true
-			$scope.update.hoverSelectedTo = true
-			$scope.recalculateHoveredUpdates()
-
-		$scope.mouseOutSelectedTo = () ->
-			$scope.trackChanges.hoveringOverListSelectors = false
-			$scope.resetHoverState()
-
-	]

+ 0 - 45
services/web/public/coffee/app/ide/track-changes/directives/infiniteScroll.coffee

@@ -1,45 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.directive "infiniteScroll", () ->
-		return {
-			link: (scope, element, attrs, ctrl) ->
-				innerElement = element.find(".infinite-scroll-inner")
-				element.css 'overflow-y': 'auto'
-
-				atEndOfListView = () ->
-					element.scrollTop() + element.height() >= innerElement.height() - 30
-
-				listShorterThanContainer = () ->
-					element.innerHeight() > @$(".change-list").outerHeight()
-
-				loadUntilFull = () ->
-					if (listShorterThanContainer() or atEndOfListView()) and not scope.$eval(attrs.infiniteScrollDisabled)
-						console.log "Loading more"
-						promise = scope.$eval(attrs.infiniteScroll)
-						console.log promise
-						promise.then () ->
-							loadUntilFull()
-						# @collection.fetchNextBatch
-						# 	error: (error) =>
-						# 		@hideLoading()
-						# 		@showEmptyMessageIfCollectionEmpty()
-						# 		callback(error)
-						# 	success: (collection, response) =>
-						# 		@hideLoading()
-						# 		if @collection.isAtEnd()
-						# 			@atEndOfCollection = true
-						# 			@showEmptyMessageIfCollectionEmpty()
-						# 			callback()
-						# 		else
-						# 			@loadUntilFull(callback)
-
-				element.on "scroll", (event) ->
-					loadUntilFull()
-
-				scope.$watch attrs.infiniteScrollInitialize, (value) ->
-					console.log "INITIALIZE", value
-					if value
-						loadUntilFull()
-
-		}

+ 0 - 16
services/web/public/coffee/app/main.coffee

@@ -1,16 +0,0 @@
-define [
-	"main/project-list"
-	"main/user-details"
-	"main/account-settings"
-	"main/plans"
-	"main/group-members"
-	"directives/asyncForm"
-	"directives/stopPropagation"
-	"directives/focus"
-	"directives/equals"
-	"directives/fineUpload"
-	"directives/onEnter"
-	"directives/selectAll"
-	"filters/formatDate"
-], () ->
-	angular.bootstrap(document.body, ["SharelatexApp"])

+ 0 - 54
services/web/public/coffee/app/main/account-settings.coffee

@@ -1,54 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "AccountSettingsController", ["$scope", "$http", "$modal", ($scope, $http, $modal) ->
-		$scope.subscribed = true
-
-		$scope.unsubscribe = () ->
-			$scope.unsubscribing = true
-			$http({
-					method: "DELETE"
-					url: "/user/newsletter/unsubscribe"
-					headers:
-						"X-CSRF-Token": window.csrfToken
-				})
-				.success () ->
-					$scope.unsubscribing = false
-					$scope.subscribed = false
-				.error () ->
-					$scope.unsubscribing = true
-
-		$scope.deleteAccount = () ->
-			modalInstance = $modal.open(
-				templateUrl: "deleteAccountModalTemplate"
-				controller: "DeleteAccountModalController"
-			)
-	]
-
-	App.controller "DeleteAccountModalController", [
-		"$scope", "$modalInstance", "$timeout", "$http",
-		($scope,   $modalInstance,   $timeout,   $http) ->
-			$scope.state =
-				inflight: false
-
-			$modalInstance.opened.then () ->
-				$timeout () ->
-					$scope.$broadcast "open"
-				, 700
-
-			$scope.delete = () ->
-				$scope.state.inflight = true
-
-				$http({
-						method: "DELETE"
-						url: "/user"
-						headers:
-							"X-CSRF-Token": window.csrfToken
-					})
-					.success () ->
-						$modalInstance.close()
-						window.location = "/"
-
-			$scope.cancel = () ->
-				$modalInstance.dismiss('cancel')
-	]

+ 0 - 54
services/web/public/coffee/app/main/group-members.coffee

@@ -1,54 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "GroupMembersController", ($scope, queuedHttp) ->
-		$scope.users = window.users
-		$scope.groupSize = window.groupSize
-		$scope.selectedUsers = []
-
-		$scope.inputs =
-			emails: ""
-
-		parseEmails = (emailsString)->
-			regexBySpaceOrComma = /[\s,]+/
-			emails = emailsString.split(regexBySpaceOrComma)
-			emails = _.map emails, (email)->
-				email = email.trim()
-			emails = _.select emails, (email)->
-				email.indexOf("@") != -1
-			return emails
-
-		$scope.addMembers = () ->
-			emails = parseEmails($scope.inputs.emails)
-			for email in emails
-				queuedHttp
-					.post("/subscription/group/user", {
-						email: email,
-						_csrf: window.csrfToken
-					})
-					.success (data) ->
-						$scope.users.push data.user if data.user?
-						$scope.inputs.emails = ""
-
-		$scope.removeMembers = () ->
-			for user in $scope.selectedUsers
-				do (user) ->
-					queuedHttp({
-						method: "DELETE",
-						url: "/subscription/group/user/#{user._id}"
-						headers:
-							"X-Csrf-Token": window.csrfToken
-					})
-						.success () ->
-							index = $scope.users.indexOf(user)
-							return if index == -1
-							$scope.users.splice(index, 1)
-			$scope.selectedUsers = []
-
-		$scope.updateSelectedUsers = () ->
-			$scope.selectedUsers = $scope.users.filter (user) -> user.selected
-
-	App.controller "GroupMemberListItemController", ($scope) ->
-		$scope.$watch "user.selected", (value) ->
-			if value?
-				$scope.updateSelectedUsers()

+ 0 - 6
services/web/public/coffee/app/main/plans.coffee

@@ -1,6 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.controller "PlansController", ($scope) ->
-		$scope.ui =
-			view: "monthly"

+ 0 - 583
services/web/public/coffee/app/main/project-list.coffee

@@ -1,583 +0,0 @@
-define [
-	"base"
-], (App) ->
-	App.factory "queuedHttp", ["$http", "$q", ($http, $q) ->
-		pendingRequests = []
-		inflight = false
-
-		processPendingRequests = () ->
-			return if inflight
-			doRequest = pendingRequests.shift()
-			if doRequest?
-				inflight = true
-				doRequest()
-					.success () ->
-						inflight = false
-						processPendingRequests()
-					.error () ->
-						inflight = false
-						processPendingRequests()
-
-		queuedHttp = (args...) ->
-			deferred = $q.defer()
-			promise = deferred.promise
-
-			# Adhere to the $http promise conventions
-			promise.success = (callback) ->
-				promise.then(callback)
-				return promise
-
-			promise.error = (callback) ->
-				promise.catch(callback)
-				return promise
-
-			doRequest = () ->
-				$http(args...)
-					.success (successArgs...) ->
-						deferred.resolve(successArgs...)
-					.error (errorArgs...) ->
-						deferred.reject(errorArgs...)
-
-			pendingRequests.push doRequest
-			processPendingRequests()
-
-			return promise
-
-		queuedHttp.post = (url, data) ->
-			queuedHttp({method: "POST", url: url, data: data})
-
-		return queuedHttp
-
-	]
-
-	App.controller "ProjectPageController", ($scope, $modal, $q, queuedHttp) ->
-		$scope.projects = window.data.projects
-		$scope.visibleProjects = $scope.projects
-		$scope.tags = window.data.tags
-		$scope.allSelected = false
-		$scope.selectedProjects = []
-		$scope.filter = "all"
-
-		# Allow tags to be accessed on projects as well
-		projectsById = {}
-		for project in $scope.projects
-			projectsById[project.id] = project
-
-		for tag in $scope.tags
-			for project_id in tag.project_ids or []
-				project = projectsById[project_id]
-				if project?
-					project.tags ||= []
-					project.tags.push tag
-
-		$scope.$watch "searchText", (value) ->
-			$scope.updateVisibleProjects()
-
-		$scope.clearSearchText = () ->
-			$scope.searchText = ""
-			$scope.$emit "search:clear"
-
-		$scope.setFilter = (filter) ->
-			$scope.filter = filter
-			$scope.updateVisibleProjects()
-
-		$scope.updateSelectedProjects = () ->
-			$scope.selectedProjects = $scope.projects.filter (project) -> project.selected
-
-		$scope.getSelectedProjects = () ->
-			$scope.selectedProjects
-
-		$scope.getSelectedProjectIds = () ->
-			$scope.selectedProjects.map (project) -> project.id
-
-		$scope.getFirstSelectedProject = () ->
-			$scope.selectedProjects[0]
-
-		$scope.updateVisibleProjects = () ->
-			$scope.visibleProjects = []
-			selectedTag = $scope.getSelectedTag()
-			for project in $scope.projects
-				visible = true
-				# Only show if it matches any search text
-				if $scope.searchText? and $scope.searchText != ""
-					if !project.name.toLowerCase().match($scope.searchText.toLowerCase())
-						visible = false
-				# Only show if it matches the selected tag
-				if $scope.filter == "tag" and selectedTag? and project.id not in selectedTag.project_ids
-					visible = false
-
-				# Hide projects we own if we only want to see shared projects
-				if $scope.filter == "shared" and project.accessLevel == "owner"
-					visible = false
-
-				# Hide projects we don't own if we only want to see owned projects
-				if $scope.filter == "owned" and project.accessLevel != "owner"
-					visible = false
-
-				if $scope.filter == "archived"
-					# Only show archived projects
-					if !project.archived
-						visible = false
-				else
-					# Only show non-archived projects
-					if project.archived
-						visible = false
-
-				if visible
-					$scope.visibleProjects.push project
-				else
-					# We don't want hidden selections
-					project.selected = false
-			$scope.updateSelectedProjects()
-
-		$scope.getSelectedTag = () ->
-			for tag in $scope.tags
-				return tag if tag.selected
-			return null
-
-		$scope._removeProjectIdsFromTagArray = (tag, remove_project_ids) ->
-			# Remove project_id from tag.project_ids
-			remaining_project_ids = []
-			removed_project_ids = []
-			for project_id in tag.project_ids
-				if project_id not in remove_project_ids
-					remaining_project_ids.push project_id
-				else
-					removed_project_ids.push project_id
-			tag.project_ids = remaining_project_ids
-			return removed_project_ids
-
-		$scope._removeProjectFromList = (project) ->
-			index = $scope.projects.indexOf(project)
-			if index > -1
-				$scope.projects.splice(index, 1)
-
-		$scope.removeSelectedProjectsFromTag = (tag) ->
-			tag.showWhenEmpty = true
-
-			selected_project_ids = $scope.getSelectedProjectIds()
-			selected_projects = $scope.getSelectedProjects()
-
-			removed_project_ids = $scope._removeProjectIdsFromTagArray(tag, selected_project_ids)
-
-			# Remove tag from project.tags
-			remaining_tags = []
-			for project in selected_projects
-				project.tags ||= []
-				index = project.tags.indexOf tag
-				if index > -1
-					project.tags.splice(index, 1)
-
-			for project_id in removed_project_ids
-				queuedHttp.post "/project/#{project_id}/tag", {
-					deletedTag: tag.name
-					_csrf: window.csrfToken
-				}
-
-			# If we're filtering by this tag then we need to remove
-			# the projects from view
-			$scope.updateVisibleProjects()
-
-		$scope.addSelectedProjectsToTag = (tag) ->
-			selected_projects = $scope.getSelectedProjects()
-
-			# Add project_ids into tag.project_ids
-			added_project_ids = []
-			for project_id in $scope.getSelectedProjectIds()
-				unless project_id in tag.project_ids
-					tag.project_ids.push project_id
-					added_project_ids.push project_id
-
-			# Add tag into each project.tags
-			for project in selected_projects
-				project.tags ||= []
-				unless tag in project.tags
-					project.tags.push tag
-
-			for project_id in added_project_ids
-				queuedHttp.post "/project/#{project_id}/tag", {
-					tag: tag.name
-					_csrf: window.csrfToken
-				}
-
-		$scope.createTag = (name) ->
-			$scope.tags.push {
-				name: name
-				project_ids: []
-				showWhenEmpty: true
-			}
-
-		$scope.openNewTagModal = (e) ->
-			modalInstance = $modal.open(
-				templateUrl: "newTagModalTemplate"
-				controller: "NewTagModalController"
-			)
-
-			modalInstance.result.then(
-				(newTagName) ->
-					$scope.createTag(newTagName)
-			)
-
-		$scope.createProject = (name, template = "none") ->
-			deferred = $q.defer()
-
-			queuedHttp
-				.post("/project/new", {
-					_csrf: window.csrfToken
-					projectName: name
-					template: template
-				})
-				.success((data, status, headers, config) ->
-					$scope.projects.push {
-						name: name
-						_id: data.project_id
-						accessLevel: "owner"
-						# TODO: Check access level if correct after adding it in
-						# to the rest of the app
-					}
-					$scope.updateVisibleProjects()
-					deferred.resolve(data.project_id)
-				)
-				.error((data, status, headers, config) ->
-					deferred.reject()
-				)
-
-			return deferred.promise
-
-		$scope.openCreateProjectModal = (template = "none") ->
-			modalInstance = $modal.open(
-				templateUrl: "newProjectModalTemplate"
-				controller: "NewProjectModalController"
-				resolve:
-					template: () -> template
-				scope: $scope
-			)
-
-			modalInstance.result.then (project_id) ->
-				window.location = "/project/#{project_id}"
-
-		$scope.renameProject = (project, newName) ->
-			project.name = newName
-			queuedHttp.post "/project/#{project.id}/rename", {
-				newProjectName: newName
-				_csrf: window.csrfToken
-			}
-
-		$scope.openRenameProjectModal = () ->
-			project = $scope.getFirstSelectedProject()
-			return if !project? or project.accessLevel != "owner"
-
-			modalInstance = $modal.open(
-				templateUrl: "renameProjectModalTemplate"
-				controller: "RenameProjectModalController"
-				resolve:
-					projectName: () -> project.name
-			)
-
-			modalInstance.result.then(
-				(newName) ->
-					$scope.renameProject(project, newName)
-			)
-
-		$scope.cloneProject = (project, cloneName) ->
-			deferred = $q.defer()
-
-			queuedHttp
-				.post("/project/#{project.id}/clone", {
-					_csrf: window.csrfToken
-					projectName: cloneName
-				})
-				.success((data, status, headers, config) ->
-					$scope.projects.push {
-						name: cloneName
-						id: data.project_id
-						accessLevel: "owner"
-						# TODO: Check access level if correct after adding it in
-						# to the rest of the app
-					}
-					$scope.updateVisibleProjects()
-					deferred.resolve(data.project_id)
-				)
-				.error((data, status, headers, config) ->
-					deferred.reject()
-				)
-
-			return deferred.promise
-
-		$scope.openCloneProjectModal = () ->
-			project = $scope.getFirstSelectedProject()
-			return if !project?
-
-			modalInstance = $modal.open(
-				templateUrl: "cloneProjectModalTemplate"
-				controller: "CloneProjectModalController"
-				resolve:
-					project: () -> project
-				scope: $scope
-			)
-
-		$scope.openArchiveProjectsModal = () ->
-			modalInstance = $modal.open(
-				templateUrl: "deleteProjectsModalTemplate"
-				controller: "DeleteProjectsModalController"
-				resolve:
-					projects: () -> $scope.getSelectedProjects()
-			)
-
-			modalInstance.result.then () ->
-				$scope.archiveOrLeaveSelectedProjects()
-
-		$scope.archiveOrLeaveSelectedProjects = () ->
-			selected_projects = $scope.getSelectedProjects()
-			selected_project_ids = $scope.getSelectedProjectIds()
-
-			# Remove project from any tags
-			for tag in $scope.tags
-				$scope._removeProjectIdsFromTagArray(tag, selected_project_ids)
-
-			for project in selected_projects
-				if project.accessLevel == "owner"
-					project.archived = true
-					queuedHttp {
-						method: "DELETE"
-						url: "/project/#{project.id}"
-						headers:
-							"X-CSRF-Token": window.csrfToken
-					}
-				else
-					$scope._removeProjectFromList project
-
-					queuedHttp {
-						method: "POST"
-						url: "/project/#{project.id}/leave"
-						headers:
-							"X-CSRF-Token": window.csrfToken
-					}
-
-			$scope.updateVisibleProjects()
-
-
-		$scope.openDeleteProjectsModal = () ->
-			modalInstance = $modal.open(
-				templateUrl: "deleteProjectsModalTemplate"
-				controller: "DeleteProjectsModalController"
-				resolve:
-					projects: () -> $scope.getSelectedProjects()
-			)
-
-			modalInstance.result.then () ->
-				$scope.deleteSelectedProjects()
-
-		$scope.deleteSelectedProjects = () ->
-			selected_projects = $scope.getSelectedProjects()
-			selected_project_ids = $scope.getSelectedProjectIds()
-
-			# Remove projects from array
-			for project in selected_projects
-				$scope._removeProjectFromList project
-
-			# Remove project from any tags
-			for tag in $scope.tags
-				$scope._removeProjectIdsFromTagArray(tag, selected_project_ids)
-
-			for project_id in selected_project_ids
-				queuedHttp {
-					method: "DELETE"
-					url: "/project/#{project_id}?forever=true"
-					headers:
-						"X-CSRF-Token": window.csrfToken
-				}
-
-			$scope.updateVisibleProjects()
-
-		$scope.restoreSelectedProjects = () ->
-			selected_projects = $scope.getSelectedProjects()
-			selected_project_ids = $scope.getSelectedProjectIds()
-
-			for project in selected_projects
-				project.archived = false
-
-			for project_id in selected_project_ids
-				queuedHttp {
-					method: "POST"
-					url: "/project/#{project_id}/restore"
-					headers:
-						"X-CSRF-Token": window.csrfToken
-				}
-
-			$scope.updateVisibleProjects()
-
-		$scope.openUploadProjectModal = () ->
-			modalInstance = $modal.open(
-				templateUrl: "uploadProjectModalTemplate"
-				controller: "UploadProjectModalController"
-			)
-
-		$scope.downloadSelectedProjects = () ->
-			selected_project_ids = $scope.getSelectedProjectIds()
-
-			if selected_project_ids.length > 1
-				path = "/project/download/zip?project_ids=#{selected_project_ids.join(',')}"
-			else
-				path = "/project/#{selected_project_ids[0]}/download/zip"
-
-			window.location = path
-
-
-	App.controller "ProjectListItemController", ($scope) ->
-		$scope.ownerName = () ->
-			if $scope.project.accessLevel == "owner"
-				return "You"
-			else if $scope.project.owner?
-				return "#{$scope.project.owner.first_name} #{$scope.project.owner.last_name}"
-			else
-				return "?"
-
-		$scope.$watch "project.selected", (value) ->
-			if value?
-				$scope.updateSelectedProjects()
-
-	App.controller "TagListController", ($scope) ->
-		$scope.filterProjects = (filter = "all") ->
-			$scope._clearTags()
-			$scope.setFilter(filter)
-
-		$scope._clearTags = () ->
-			for tag in $scope.tags
-				tag.selected = false
-
-		$scope.nonEmpty = (tag) ->
-			# The showWhenEmpty property will be set on any tag which we have
-			# modified during this session. Otherwise, tags which are empty
-			# when loading the page are not shown.
-			tag.project_ids.length > 0 or !!tag.showWhenEmpty
-
-	App.controller "TagListItemController", ($scope) ->
-		$scope.selectTag = () ->
-			$scope._clearTags()
-			$scope.tag.selected = true
-			$scope.setFilter("tag")
-
-	App.controller "TagDropdownItemController", ($scope) ->
-		$scope.recalculateProjectsInTag = () ->
-			$scope.areSelectedProjectsInTag = false
-			for project_id in $scope.getSelectedProjectIds()
-				if project_id in $scope.tag.project_ids
-					$scope.areSelectedProjectsInTag = true
-				else
-					partialSelection = true
-
-			if $scope.areSelectedProjectsInTag and partialSelection
-				$scope.areSelectedProjectsInTag = "partial"
-
-		$scope.addOrRemoveProjectsFromTag = () ->
-			if $scope.areSelectedProjectsInTag == true
-				$scope.removeSelectedProjectsFromTag($scope.tag)
-				$scope.areSelectedProjectsInTag = false
-			else if $scope.areSelectedProjectsInTag == false or $scope.areSelectedProjectsInTag == "partial"
-				$scope.addSelectedProjectsToTag($scope.tag)
-				$scope.areSelectedProjectsInTag = true
-
-		$scope.$watch "selectedProjects", () ->
-			$scope.recalculateProjectsInTag()
-		$scope.recalculateProjectsInTag()
-
-	App.controller 'NewTagModalController', ($scope, $modalInstance, $timeout) ->
-		$scope.inputs = 
-			newTagName: ""
-
-		$modalInstance.opened.then () ->
-			$timeout () ->
-				$scope.$broadcast "open"
-			, 700
-
-		$scope.create = () ->
-			$modalInstance.close($scope.inputs.newTagName)
-
-		$scope.cancel = () ->
-			$modalInstance.dismiss('cancel')
-
-	App.controller 'RenameProjectModalController', ($scope, $modalInstance, $timeout, projectName) ->
-		$scope.inputs = 
-			projectName: projectName
-
-		$modalInstance.opened.then () ->
-			$timeout () ->
-				$scope.$broadcast "open"
-			, 700
-
-		$scope.rename = () ->
-			$modalInstance.close($scope.inputs.projectName)
-
-		$scope.cancel = () ->
-			$modalInstance.dismiss('cancel')
-
-	App.controller 'CloneProjectModalController', ($scope, $modalInstance, $timeout, project) ->
-		$scope.inputs = 
-			projectName: project.name + " (Copy)"
-		$scope.state =
-			inflight: false
-
-		$modalInstance.opened.then () ->
-			$timeout () ->
-				$scope.$broadcast "open"
-			, 700
-
-		$scope.clone = () ->
-			$scope.state.inflight = true
-			$scope
-				.cloneProject(project, $scope.inputs.projectName)
-				.then (project_id) ->
-					$scope.state.inflight = false
-					$modalInstance.close(project_id)
-
-		$scope.cancel = () ->
-			$modalInstance.dismiss('cancel')
-
-	App.controller 'NewProjectModalController', ($scope, $modalInstance, $timeout, template) ->
-		$scope.inputs = 
-			projectName: ""
-		$scope.state =
-			inflight: false
-
-		$modalInstance.opened.then () ->
-			$timeout () ->
-				$scope.$broadcast "open"
-			, 700
-
-		$scope.create = () ->
-			$scope.state.inflight = true
-			$scope
-				.createProject($scope.inputs.projectName, template)
-				.then (project_id) ->
-					$scope.state.inflight = false
-					$modalInstance.close(project_id)
-
-		$scope.cancel = () ->
-			$modalInstance.dismiss('cancel')
-
-	App.controller 'DeleteProjectsModalController', ($scope, $modalInstance, $timeout, projects) ->
-		$scope.projectsToDelete = projects.filter (project) -> project.accessLevel == "owner"
-		$scope.projectsToLeave = projects.filter (project) -> project.accessLevel != "owner"
-
-		if $scope.projectsToLeave.length > 0 and $scope.projectsToDelete.length > 0
-			$scope.action = "Delete & Leave"
-		else if $scope.projectsToLeave.length == 0 and $scope.projectsToDelete.length > 0
-			$scope.action = "Delete"
-		else
-			$scope.action = "Leave"
-
-		$scope.delete = () ->
-			$modalInstance.close()
-
-		$scope.cancel = () ->
-			$modalInstance.dismiss('cancel')
-
-
-	App.controller 'UploadProjectModalController', ($scope, $modalInstance, $timeout) ->
-		$scope.cancel = () ->
-			$modalInstance.dismiss('cancel')
-
-		$scope.onComplete = (error, name, response) ->
-			if response.project_id?
-				window.location = '/project/' + response.project_id

+ 0 - 58
services/web/public/coffee/app/main/user-details.coffee

@@ -1,58 +0,0 @@
-define [
-	"base"
-	"../../libs/algolia"
-], (App, algolia)->
-	app.factory "Institutions", ->
-		new AlgoliaSearch(window.algolia.institutions.app_id, window.algolia.institutions.api_key).initIndex("institutions")
-
-	App.controller "UserProfileController", ($scope, $modal, $http)->
-		$scope.institutions = []
-		$http.get("/user/personal_info").success (data)->
-			$scope.userInfoForm =
-				first_name: data.first_name || ""
-				last_name: data.last_name || ""
-				role: 	   data.role || ""
-				institution: data.institution || ""
-				_csrf : window.csrfToken
-
-		$scope.showForm = ->
-			$scope.formVisable = true
-
-		$scope.getPercentComplete = ->
-			results = _.filter $scope.userInfoForm, (value)-> !value? or value?.length != 0
-			results.length * 20
-
-		$scope.$watch "userInfoForm", (value) ->
-			if value?
-				$scope.percentComplete = $scope.getPercentComplete()
-		, true
-
-		$scope.openUserProfileModal = () ->
-			$modal.open {
-				templateUrl: "userProfileModalTemplate"
-				controller: "UserProfileModalController"
-				scope: $scope
-			}
-
-	App.controller "UserProfileModalController", ($scope, $modalInstance, $http, Institutions) ->
-		$scope.roles = ["Student", "Post-graduate student", "Post-doctoral researcher", "Lecturer", "Professor"]
-
-		$scope.sendUpdate = ->
-			request = $http.post "/user/settings", $scope.userInfoForm
-			request.success (data, status)->
-			request.error (data, status)->
-				console.log "the request failed"
-
-		$scope.updateInstitutionsList = (inputVal)->
-
-			# this is a little hack to use until we change auto compelete lib with redesign and can 
-			# listen for blur events on institution field to send the post
-			if inputVal?.indexOf("(") != -1 and inputVal?.indexOf(")") != -1 
-				$scope.sendUpdate()
-				
-			Institutions.search $scope.userInfoForm.institution, (err, response)->
-				$scope.institutions = _.map response.hits, (institution)->
-					"#{institution.name} (#{institution.domain})"
-
-		$scope.done = () ->
-			$modalInstance.close()

+ 0 - 44
services/web/public/coffee/app/modules/recursionHelper.coffee

@@ -1,44 +0,0 @@
-# 
-# * An Angular service which helps with creating recursive directives.
-# * @author Mark Lagendijk
-# * @license MIT
-# 
-# From: https://github.com/marklagendijk/angular-recursion
-angular.module("RecursionHelper", []).factory "RecursionHelper", [
-  "$compile"
-  ($compile) ->
-    
-    ###
-    Manually compiles the element, fixing the recursion loop.
-    @param element
-    @param [link] A post-link function, or an object with function(s) registered via pre and post properties.
-    @returns An object containing the linking functions.
-    ###
-    return compile: (element, link) ->
-      
-      # Normalize the link parameter
-      link = post: link  if angular.isFunction(link)
-      
-      # Break the recursion loop by removing the contents
-      contents = element.contents().remove()
-      compiledContents = undefined
-      pre: (if (link and link.pre) then link.pre else null)
-      
-      ###
-      Compiles and re-adds the contents
-      ###
-      post: (scope, element) ->
-        
-        # Compile the contents
-        compiledContents = $compile(contents)  unless compiledContents
-        
-        # Re-add the compiled contents to the element
-        compiledContents scope, (clone) ->
-          element.append clone
-          return
-
-        
-        # Call the post-linking function, if any
-        link.post.apply null, arguments if link and link.post
-        return
-]

+ 0 - 33
services/web/public/coffee/app/utils/EventEmitter.coffee

@@ -1,33 +0,0 @@
-define [], () ->
-	class EventEmitter
-		on: (event, callback) ->
-			@events ||= {}
-			[event, namespace] = event.split(".")
-			@events[event] ||= []
-			@events[event].push {
-				callback: callback
-				namespace: namespace
-			}
-
-		off: (event) ->
-			@events ||= {}
-			if event?
-				[event, namespace] = event.split(".")
-				if !namespace?
-					# Clear all listeners for event
-					delete @events[event]
-				else
-					# Clear only namespaced listeners
-					remaining_events = []
-					for callback in @events[event] or []
-						if callback.namespace != namespace
-							remaining_events.push callback
-					@events[event] = remaining_events
-			else
-				# Remove all listeners
-				@events = {}
-
-		trigger: (event, args...) ->
-			@events ||= {}
-			for callback in @events[event] or []
-				callback.callback(args...)

+ 0 - 79
services/web/public/coffee/auto-complete/AutoCompleteManager.coffee

@@ -1,79 +0,0 @@
-define [
-	"auto-complete/SuggestionManager"
-	"auto-complete/Snippets"
-	"ace/autocomplete/util"
-	"ace/autocomplete"
-	"ace/range"
-	"ace/ext/language_tools"
-], (SuggestionManager, Snippets, Util, AutoComplete) ->
-	Range = require("ace/range").Range
-	Autocomplete = AutoComplete.Autocomplete
-
-	Util.retrievePrecedingIdentifier = (text, pos, regex) ->
-		currentLineOffset = 0
-		for i in [(pos-1)..0]
-			if text[i] == "\n"
-				currentLineOffset = i + 1
-				break
-		currentLine = text.slice(currentLineOffset, pos)
-		fragment = getLastCommandFragment(currentLine) or ""
-		return fragment
-
-	getLastCommandFragment = (lineUpToCursor) ->
-		if m = lineUpToCursor.match(/(\\[^\\ ]+)$/)
-			return m[1]
-		else
-			return null
-
-	class AutoCompleteManager
-		constructor: (@ide) ->
-			@aceEditor = @ide.editor.aceEditor
-			@aceEditor.setOptions({
-				enableBasicAutocompletion: true,
-				enableSnippets: true
-			})
-
-			SnippetCompleter =
-				getCompletions: (editor, session, pos, prefix, callback) ->
-					callback null, Snippets
-			@suggestionManager = new SuggestionManager()
-
-			@aceEditor.completers = [@suggestionManager, SnippetCompleter]
-
-			insertMatch = Autocomplete::insertMatch
-			editor = @aceEditor
-			Autocomplete::insertMatch = (data) ->
-				pos = editor.getCursorPosition()
-				range = new Range(pos.row, pos.column, pos.row, pos.column + 1)
-				nextChar = editor.session.getTextRange(range)
-
-				console.log "CALLING OLD"
-
-				# If we are in \begin{it|}, then we need to remove the trailing }
-				# since it will be adding in with the autocomplete of \begin{item}...
-				if this.completions.filterText.match(/^\\begin\{/) and nextChar == "}"
-					editor.session.remove(range)
-				
-				insertMatch.call editor.completer, data
-
-			@bindToEditorEvents()
-
-		bindToEditorEvents: () ->
-			@ide.editor.on "change:doc", (@aceSession) =>
-				@aceSession.on "change", (change) => @onChange(change)
-
-		onChange: (change) ->
-			cursorPosition = @aceEditor.getCursorPosition()
-			end = change.data.range.end
-			# Check that this change was made by us, not a collaborator
-			# (Cursor is still one place behind)
-			if end.row == cursorPosition.row and end.column == cursorPosition.column + 1
-				if change.data.action == "insertText"
-					range = new Range(end.row, 0, end.row, end.column)
-					lineUpToCursor = @aceSession.getTextRange(range)
-					commandFragment = getLastCommandFragment(lineUpToCursor)
-
-					if commandFragment? and commandFragment.length > 2
-					 	setTimeout () =>
-					 		@aceEditor.execCommand("startAutocomplete")
-					 	, 0

+ 0 - 100
services/web/public/coffee/auto-complete/Snippets.coffee

@@ -1,100 +0,0 @@
-define () ->
-	environments = [
-		"abstract", 
-		"align", "align*",
-		"equation", "equation*",
-		"gather", "gather*",
-		"multline", "multline*",
-		"split",
-		"verbatim"
-	]
-
-	snippets = for env in environments
-		{
-			caption: "\\begin{#{env}}..."
-			snippet: """
-				\\begin{#{env}}
-				$1
-				\\end{#{env}}
-			"""
-			meta: "env"
-		}
-
-	snippets = snippets.concat [{
-		caption: "\\begin{array}..."
-		snippet: """
-			\\begin{array}{${1:cc}}
-			$2 & $3 \\\\\\\\
-			$4 & $5
-			\\end{array}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{figure}..."
-		snippet: """
-			\\begin{figure}
-			\\centering
-			\\includegraphics{$1}
-			\\caption{${2:Caption}}
-			\\label{${3:fig:my_label}}
-			\\end{figure}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{tabular}..."
-		snippet: """
-			\\begin{tabular}{${1:c|c}}
-			$2 & $3 \\\\\\\\
-			$4 & $5
-			\\end{tabular}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{table}..."
-		snippet: """
-			\\begin{table}[$1]
-			\\centering
-			\\begin{tabular}{${2:c|c}}
-			$3 & $4 \\\\\\\\
-			$5 & $6
-			\\end{tabular}
-			\\caption{${7:Caption}}
-			\\label{${8:tab:my_label}}
-			\\end{table}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{list}..."
-		snippet: """
-			\\begin{list}
-			\\item $1
-			\\end{list}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{enumerate}..."
-		snippet: """
-			\\begin{enumerate}
-			\\item $1
-			\\end{enumerate}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{itemize}..."
-		snippet: """
-			\\begin{itemize}
-			\\item $1
-			\\end{itemize}
-		"""
-		meta: "env"
-	}, {
-		caption: "\\begin{frame}..."
-		snippet: """
-			\\begin{frame}{${1:Frame Title}}
-			$2
-			\\end{frame}
-		"""
-		meta: "env"
-	}]
-
-	return snippets

+ 0 - 126
services/web/public/coffee/auto-complete/SuggestionManager.coffee

@@ -1,126 +0,0 @@
-define [], () ->
-	class Parser
-		constructor: (@doc) ->
-
-		parse: () ->
-			commands = []
-			seen = {}
-			while command = @nextCommand()
-				docState = @doc
-
-				optionalArgs = 0
-				while @consumeArgument("[", "]")
-					optionalArgs++
-
-				args = 0
-				while @consumeArgument("{", "}")
-					args++
-
-				commandHash = "#{command}\\#{optionalArgs}\\#{args}"
-				if !seen[commandHash]?
-					seen[commandHash] = true
-					commands.push [command, optionalArgs, args]
-
-				# Reset to before argument to handle nested commands
-				@doc = docState
-
-			return commands
-
-		# Ignore single letter commands since auto complete is moot then.
-		commandRegex: /\\([a-zA-Z][a-zA-Z]+)/
-		
-		nextCommand: () ->
-			i = @doc.search(@commandRegex)
-			if i == -1
-				return false
-			else
-				match = @doc.match(@commandRegex)[1]
-				@doc = @doc.substr(i + match.length + 1)
-				return match
-
-		consumeWhitespace: () ->
-			match = @doc.match(/^[ \t\n]*/m)[0]
-			@doc = @doc.substr(match.length)
-
-		consumeArgument: (openingBracket, closingBracket) ->
-			@consumeWhitespace()
-
-			if @doc[0] == openingBracket
-				i = 1
-				bracketParity = 1
-				while bracketParity > 0 and i < @doc.length
-					if @doc[i] == openingBracket
-						bracketParity++
-					else if @doc[i] == closingBracket
-						bracketParity--
-					i++
-
-				if bracketParity == 0
-					@doc = @doc.substr(i)
-					return true
-				else
-					return false
-			else
-				return false
-
-	class SuggestionManager
-		getCompletions: (editor, session, pos, prefix, callback) ->
-			doc = session.getValue()
-			parser = new Parser(doc)
-			commands = parser.parse()
-
-			completions = []
-			for command in commands
-				caption = "\\#{command[0]}"
-				snippet = caption
-				i = 1
-				_.times command[1], () ->
-					snippet += "[${#{i}}]"
-					caption += "[]"
-					i++
-				_.times command[2], () ->
-					snippet += "{${#{i}}}"
-					caption += "{}"
-					i++
-				unless caption == prefix
-					completions.push {
-						caption: caption
-						snippet: snippet
-						meta: "cmd"
-					}
-
-			callback null, completions
-
-		loadCommandsFromDoc: (doc) ->
-			parser = new Parser(doc)
-			@commands = parser.parse()
-
-		getSuggestions: (commandFragment) ->
-			matchingCommands = _.filter @commands, (command) ->
-				command[0].slice(0, commandFragment.length) == commandFragment
-
-			return _.map matchingCommands, (command) ->
-				base = "\\" + commandFragment
-
-				args = ""
-				_.times command[1], () -> args = args + "[]"
-				_.times command[2], () -> args = args + "{}"
-				completionBase = command[0].slice(commandFragment.length)
-
-				squareArgsNo = command[1]
-				curlyArgsNo = command[2]
-				totalArgs = squareArgsNo + curlyArgsNo
-				if totalArgs == 0
-					completionBeforeCursor = completionBase
-					completionAfterCurspr = ""
-				else
-					completionBeforeCursor = completionBase + args[0]
-					completionAfterCursor = args.slice(1)
-
-				return {
-					base: base,
-					completion: completionBase + args,
-					completionBeforeCursor: completionBeforeCursor
-					completionAfterCursor: completionAfterCursor
-				}
-

+ 0 - 99
services/web/public/coffee/cursors/CursorManager.coffee

@@ -1,99 +0,0 @@
-define () ->
-	class CursorManager
-		UPDATE_INTERVAL: 500
-
-		constructor: (@ide) ->
-			@clients = {}
-			@ide.socket.on "clientTracking.clientUpdated", (cursorUpdate) => @onRemoteClientUpdate(cursorUpdate)
-			@ide.socket.on "clientTracking.clientDisconnected", (client_id) => @onRemoteClientDisconnect(client_id)
-			@ide.editor.on "change:doc", (session) =>
-				@bindToAceSession(session)
-			@ide.editor.on "mousemove", (e) =>
-				@mousePosition = e.position
-				@updateVisibleNames()
-
-		bindToAceSession: (session) ->
-			@clients = {}
-			@ide.editor.aceEditor.on "changeSelection", => @onLocalCursorUpdate()
-
-		onLocalCursorUpdate: () ->
-			if !@cursorUpdateTimeout?
-				@cursorUpdateTimeout = setTimeout (=>
-					@_sendLocalCursorUpdate()
-					delete @cursorUpdateTimeout
-				), @UPDATE_INTERVAL
-
-		_sendLocalCursorUpdate: () ->
-			cursor = @ide.editor.getCursorPosition()
-			if !@currentCursorPosition? or not (cursor.row == @currentCursorPosition.row and cursor.column == @currentCursorPosition.column)
-				@currentCursorPosition = cursor
-				@ide.socket.emit "clientTracking.updatePosition", {
-					row: cursor.row
-					column: cursor.column
-					doc_id: @ide.editor.getCurrentDocId()
-				}
-
-		onRemoteClientUpdate: (clientData) ->
-			if clientData.id != ide.socket.socket.sessionid
-				client = @clients[clientData.id] ||= {}
-				client.row = clientData.row
-				client.column = clientData.column
-				client.name = clientData.name
-				client.doc_id = clientData.doc_id
-				@redrawCursors()
-
-		onRemoteClientDisconnect: (client_id) ->
-			@removeCursor(client_id)
-			delete @clients[client_id]
-
-		removeCursor: (client_id) ->
-			client = @clients[client_id]
-			return if !client?
-			@ide.editor.removeMarker(client.cursorMarkerId)
-			delete client.cursorMarkerId
-
-		redrawCursors: () ->
-			for clientId, clientData of @clients
-				do (clientId, clientData) =>
-					if clientData.cursorMarkerId?
-						@removeCursor(clientId)
-					if clientData.doc_id == @ide.editor.getCurrentDocId()
-						colorId = @getColorIdFromName(clientData.name)
-						clientData.cursorMarkerId = @ide.editor.addMarker {
-								row: clientData.row
-								column: clientData.column
-								length: 1
-							}, "sharelatex-remote-cursor", (html, range, left, top, config) ->
-								div = """
-									<div
-										id='cursor-#{clientId}'
-										class='sharelatex-remote-cursor custom ace_start sharelatex-remote-cursor-#{colorId}'
-										style='height: #{config.lineHeight}px; top:#{top}px; left:#{left}px;'
-									>
-										<div class="nubbin" style="bottom: #{config.lineHeight - 2}px"></div>
-										<div class="name" style="display: none; bottom: #{config.lineHeight - 2}px">#{$('<div/>').text(clientData.name).html()}</div>
-									</div>
-								"""
-								html.push div
-							, true
-			setTimeout =>
-				@updateVisibleNames()
-			, 0
-
-		updateVisibleNames: () ->
-			for clientId, clientData of @clients
-				if @mousePosition? and clientData.row == @mousePosition.row and clientData.column == @mousePosition.column
-					$("#cursor-#{clientId}").find(".name").show()
-					$("#cursor-#{clientId}").find(".nubbin").hide()
-				else
-					$("#cursor-#{clientId}").find(".name").hide()
-					$("#cursor-#{clientId}").find(".nubbin").show()
-
-		getColorIdFromName: (name) ->
-			@currentColorId ||= 0
-			@colorIds ||= {}
-			if !@colorIds[name]?
-				@colorIds[name] = @currentColorId
-				@currentColorId++
-			return @colorIds[name]
-		

+ 0 - 32
services/web/public/coffee/debug/DebugManager.coffee

@@ -1,32 +0,0 @@
-define [
-	"utils/Modal"
-], (Modal) ->
-	class DebugManager
-		template: $("#DebugLinkTemplate").html()
-
-		constructor: (@ide) ->
-			@$el = $(@template)
-			$("#toolbar-footer").append(@$el)
-			@$el.on "click", (e) =>
-				e.preventDefault()
-				@showDebugModal()
-
-		showDebugModal: () ->
-			useragent = navigator.userAgent
-			server_id = document.cookie.match(/SERVERID=([^;]*)/)?[1]
-			transport = @ide.socket.socket.transport.name
-
-			new Modal(
-				title: "Debug info"
-				message: """
-					Please give this information to the ShareLaTeX team:
-					<p><pre>
-					user-agent: #{useragent}
-					server-id: #{server_id}
-					transport: #{transport}
-					</pre></p>
-				"""
-				buttons: [
-					text: "OK"
-				]
-			)

+ 0 - 40
services/web/public/coffee/editor/AceUpdateManager.coffee

@@ -1,40 +0,0 @@
-define [
-	"documentUpdater"
-	"ace/range"
-], () ->
-	Range = require("ace/range").Range
-	Modal = require("utils/Modal")
- 
-	class AceUpdateManager
-		constructor: (@editor) ->
-			@ide = @editor.ide
-
-			guidGenerator=()->
-				S4 = ()->
-					return (((1+Math.random())*0x10000)|0).toString(16).substring(1)
-				return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4())
-			@window_id = guidGenerator()
-
-			@bindToServerEvents()
-
-		bindToServerEvents: () ->
-
-
-
-
-			@ide.socket.on 'reciveTextUpdate', (updating_id, change) =>
-				@ide.savingAreaManager.saved()
-				if(@window_id == updating_id)
-					return
-				@ownChange = true
-				doc = @editor.getDocument()
-				documentUpdater.applyChange doc, change, Range, ()=>
-					@ownChange = false
-
-		bindToDocument: (doc_id, docLines, version) ->
-			@current_doc_id = doc_id
-			aceDoc = @editor.getDocument()
-			aceDoc.on 'change', (change) =>
-				if(!@ownChange)
-					@ide.socket.emit 'sendUpdate',
-						@current_doc_id, @window_id, change.data

+ 0 - 257
services/web/public/coffee/editor/Document.coffee

@@ -1,257 +0,0 @@
-define [
-	"editor/ShareJsDoc"
-	"libs/backbone"
-	"underscore"
-], (ShareJsDoc) ->
-	class Document
-		@getDocument: (ide, doc_id) ->
-			@openDocs ||= {}
-			if !@openDocs[doc_id]?
-				@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, @)
-			@inconsistentCount = 0
-			@_bindToEditorEvents()
-			@_bindToSocketEvents()
-
-		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: () ->
-			# We've been seeing a lot of errors when I think there shouldn't be
-			# any, which may be related to this check happening before the change is
-			# applied. If we use a timeout, hopefully we can reduce this.
-			setTimeout () =>
-				editorValue = @ace?.getValue()
-				sharejsValue = @doc?.getSnapshot()
-				if editorValue != sharejsValue
-					@inconsistentCount++
-				else
-					@inconsistentCount = 0
-
-				if @inconsistentCount >= 3
-					@_onError new Error("Editor text does not match server text")
-			, 0
-
-		getSnapshot: () ->
-			@doc?.getSnapshot()
-
-		getType: () ->
-			@doc?.getType()
-
-		getInflightOp: () ->
-			@doc?.getInflightOp()
-
-		getPendingOp: () ->
-			@doc?.getPendingOp()
-
-		hasBufferedOps: () ->
-			@doc?.hasBufferedOps()
-
-		_bindToSocketEvents: () ->
-			@_onUpdateAppliedHandler = (update) => @_onUpdateApplied(update)
-			@ide.socket.on "otUpdateApplied", @_onUpdateAppliedHandler
-			@_onErrorHandler = (error, update) => @_onError(error, update)
-			@ide.socket.on "otUpdateError", @_onErrorHandler
-			@_onDisconnectHandler = (error) => @_onDisconnect(error)
-			@ide.socket.on "disconnect", @_onDisconnectHandler
-
-		_bindToEditorEvents: () ->
-			@_onReconnectHandler = (update) => @_onReconnect(update)
-			@ide.on "afterJoinProject", @_onReconnectHandler
-
-		unBindFromSocketEvents: () ->
-			@ide.socket.removeListener "otUpdateApplied", @_onUpdateAppliedHandler
-			@ide.socket.removeListener "otUpdateError", @_onUpdateErrorHandler
-			@ide.socket.removeListener "disconnect", @_onDisconnectHandler
-
-		unBindFromEditorEvents: () ->
-			@ide.off "afterJoinProject", @_onReconnectHandler
-
-		leaveAndCleanUp: () ->
-			@leave (error) =>
-				@_cleanUp()
-
-		join: (callback = (error) ->) ->
-			@wantToBeJoined = true
-			@_cancelLeave()
-			if @connected
-				return @_joinDoc callback
-			else
-				@_joinCallbacks ||= []
-				@_joinCallbacks.push callback
-
-		leave: (callback = (error) ->) ->
-			@wantToBeJoined = false
-			@_cancelJoin()
-			if (@doc? and @doc.hasBufferedOps())
-				@_leaveCallbacks ||= []
-				@_leaveCallbacks.push callback
-			else if !@connected
-				callback()
-			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
-
-		_cancelJoin: () ->
-			if @_joinCallbacks?
-				delete @_joinCallbacks
-
-		_onUpdateApplied: (update) ->
-			@ide.pushEvent "received-update",
-				doc_id: @doc_id
-				remote_doc_id: update?.doc
-				wantToBeJoined: @wantToBeJoined
-				update: update
-
-			if Math.random() < (@ide.disconnectRate or 0)
-				console.log "Simulating disconnect"
-				@ide.connectionManager.disconnect()
-				return
-
-			if update?.doc == @doc_id and @doc?
-				@doc.processUpdateFromServer update
-
-				if !@wantToBeJoined
-					@leave()
-
-		_onDisconnect: () ->
-			@connected = false
-			@joined = false
-			@doc?.updateConnectionState "disconnected"
-
-		_onReconnect: () ->
-			@ide.pushEvent "reconnected:afterJoinProject"
-
-			@connected = true
-			if @wantToBeJoined or @doc?.hasBufferedOps()
-				@_joinDoc (error) =>
-					return @_onError(error) if error?
-					@doc.updateConnectionState "ok"
-					@doc.flushPendingOps()
-					@_callJoinCallbacks()
-
-		_callJoinCallbacks: () ->
-			for callback in @_joinCallbacks or []
-				callback()
-			delete @_joinCallbacks
-
-		_joinDoc: (callback = (error) ->) ->
-			if @doc?
-				@ide.socket.emit 'joinDoc', @doc_id, @doc.getVersion(), (error, docLines, version, updates) =>
-					return callback(error) if error?
-					@joined = true
-					@doc.catchUp( updates )
-					callback()
-			else
-				@ide.socket.emit 'joinDoc', @doc_id, (error, docLines, version) =>
-					return callback(error) if error?
-					@joined = true
-					@doc = new ShareJsDoc @doc_id, docLines, version, @ide.socket
-					@_bindToShareJsDocEvents()
-					callback()
-
-		_leaveDoc: (callback = (error) ->) ->
-			@ide.socket.emit 'leaveDoc', @doc_id, (error) =>
-				return callback(error) if error?
-				@joined = false
-				for callback in @_leaveCallbacks or []
-					callback(error)
-				delete @_leaveCallbacks
-				callback(error)
-
-		_cleanUp: () ->
-			delete Document.openDocs[@doc_id]
-			@unBindFromEditorEvents()
-			@unBindFromSocketEvents()
-
-		_bindToShareJsDocEvents: () ->
-			@doc.on "error", (error, meta) => @_onError error, meta
-			@doc.on "externalUpdate", () => 
-				@ide.pushEvent "externalUpdate",
-					doc_id: @doc_id
-				@trigger "externalUpdate"
-			@doc.on "remoteop", () => 
-				@ide.pushEvent "remoteop",
-					doc_id: @doc_id
-				@trigger "remoteop"
-			@doc.on "op:sent", (op) =>
-				@ide.pushEvent "op:sent",
-					doc_id: @doc_id
-					op: op
-				@trigger "op:sent"
-			@doc.on "op:acknowledged", (op) =>
-				@ide.pushEvent "op:acknowledged",
-					doc_id: @doc_id
-					op: op
-				@trigger "op:acknowledged"
-			@doc.on "op:timeout", (op) =>
-				@ide.pushEvent "op:timeout",
-					doc_id: @doc_id
-					op: op
-				@trigger "op:timeout"
-				ga?('send', 'event', 'error', "op timeout", "Op was now acknowledged - #{ide.socket.socket.transport.name}" )
-				@ide.connectionManager.reconnectImmediately()
-			@doc.on "flush", (inflightOp, pendingOp, version) =>
-				@ide.pushEvent "flush",
-					doc_id: @doc_id,
-					inflightOp: inflightOp,
-					pendingOp: pendingOp
-					v: version
-
-		_onError: (error, meta = {}) ->
-			console.error "ShareJS error", error, meta
-			ga?('send', 'event', 'error', "shareJsError", "#{error.message} - #{ide.socket.socket.transport.name}" )
-			@ide.socket.disconnect()
-			meta.doc_id = @doc_id
-			@ide.reportError(error, meta)
-			@doc?.clearInflightAndPendingOps()
-			@_cleanUp()
-			@trigger "error", error
-
-	_.extend(Document::, Backbone.Events)
-
-	return Document
-
-
-
-	

+ 0 - 374
services/web/public/coffee/editor/Editor.coffee

@@ -1,374 +0,0 @@
-define [
-	"editor/Document"
-	"undo/UndoManager"
-	"utils/Modal"
-	"ace/ace"
-	"ace/edit_session"
-	"ace/mode/latex"
-	"ace/range"
-	"ace/keyboard/vim"
-	"ace/keyboard/emacs"
-	"libs/backbone"
-	"libs/jquery.storage"
-], (Document, UndoManager, Modal) ->
-	AceEditor = require("ace/ace")
-	EditSession = require('ace/edit_session').EditSession
-	LatexMode = require("ace/mode/latex").Mode
-	Range = require("ace/range").Range
-	Vim = require("ace/keyboard/vim").handler
-	Emacs = require("ace/keyboard/emacs").handler
-	keybindings = ace: null, vim: Vim, emacs: Emacs
-
-	class Editor
-		templates:
-			editorPanel:      $("#editorPanelTemplate").html()
-			loadingIndicator: $("#loadingIndicatorTemplate").html()
-
-		viewOptions: {flatView:"flatView", splitView:"splitView"}
-		currentViewState: undefined
-		compilationErrors: {}
-
-		constructor: (@ide) ->
-			_.extend @, Backbone.Events
-			@editorPanel = $(@templates.editorPanel)
-			@ide.mainAreaManager.addArea
-				identifier: "editor"
-				element: @editorPanel
-			@initializeEditor()
-			@bindToFileTreeEvents()
-			@enable()
-			@loadingIndicator = $(@templates.loadingIndicator)
-			@editorPanel.find("#editor").append(@loadingIndicator)
-			@leftPanel = @editorPanel.find("#leftEditorPanel")
-			@rightPanel = @editorPanel.find("#rightEditorPanel")
-			@initSplitView()
-			@switchToFlatView()
-
-		bindToFileTreeEvents: () ->
-			@ide.fileTreeManager.on "open:doc", (doc_id, options = {}) =>
-				if @enabled
-					@openDoc doc_id, options
-
-		initSplitView: () ->
-			@$splitter = splitter = @editorPanel.find("#editorSplitter")
-			options =
-				spacing_open: 8
-				spacing_closed: 16
-				east:
-					size: "50%"
-				maskIframesOnResize: true
-				onresize: () =>
-					@trigger("resize")
-
-			if (state = $.localStorage("layout.editor"))?
-				options.east = state.east
-
-			splitter.layout options
-			
-			$(window).unload () =>
-				@_saveSplitterState()
-
-		_saveSplitterState: () ->
-			if $("#editorSplitter").is(":visible")
-				state = $("#editorSplitter").layout().readState()
-				eastWidth = state.east.size + $("#editorSplitter .ui-layout-resizer-east").width()
-				percentWidth = eastWidth / $("#editorSplitter").width() * 100 + "%"
-				state.east.size = percentWidth
-				$.localStorage("layout.editor", state)
-
-		switchToSplitView: () ->
-			if @currentViewState != @viewOptions.splitView
-				@currentViewState = @viewOptions.splitView
-				@leftPanel.prepend(
-					@editorPanel.find("#editorWrapper")
-				)
-				splitter = @editorPanel.find("#editorSplitter")
-				splitter.show()
-				@ide.layoutManager.resizeAllSplitters()
-
-		switchToFlatView: () ->
-			if @currentViewState != @viewOptions.flatView
-				@_saveSplitterState()
-				@currentViewState = @viewOptions.flatView
-				@editorPanel.prepend(
-					@editorPanel.find("#editorWrapper")
-				)
-				@editorPanel.find("#editorSplitter").hide()
-				@aceEditor.resize(true)
-
-		showLoading: () ->
-			delay = 600 # ms
-			@loading = true
-			setTimeout ( =>
-				if @loading
-					@loadingIndicator.show()
-			), delay
-
-		hideLoading: () ->
-			@loading = false
-			@loadingIndicator.hide()
-
-		showUndoConflictWarning: () ->
-			$("#editor").prepend($("#undoConflictWarning"))
-			$("#undoConflictWarning").show()
-			hideBtn = $("#undoConflictWarning .js-hide")
-			hideBtn.off("click")
-			hideBtn.on "click", (e) ->
-				e.preventDefault()
-				$("#undoConflictWarning").hide()
-			if @hideUndoWarningTimeout?
-				clearTimeout @hideUndoWarningTimeout
-				delete @hideUndoWarningTimeout
-			@hideUndoWarningTimeout = setTimeout ->
-				$("#undoConflictWarning").fadeOut("slow")
-			, 4000
-				
-
-		initializeEditor: () ->
-			@aceEditor = aceEditor = AceEditor.edit("editor")
-
-			@on "resize", => @aceEditor.resize()
-			@ide.layoutManager.on "resize", => @trigger "resize"
-
-			mode = window.userSettings.mode
-			theme = window.userSettings.theme
-
-			chosenKeyBindings = keybindings[mode]
-			aceEditor.setKeyboardHandler(chosenKeyBindings)
-			aceEditor.setTheme("ace/theme/#{window.userSettings.theme}")
-			aceEditor.setShowPrintMargin(false)
-
-			# Prevert Ctrl|Cmd-S from triggering save dialog
-			aceEditor.commands.addCommand
-				name: "save",
-				bindKey: win: "Ctrl-S", mac: "Command-S"
-				exec: () ->
-				readOnly: true
-			aceEditor.commands.removeCommand "transposeletters"
-			aceEditor.commands.removeCommand "showSettingsMenu"
-			aceEditor.commands.removeCommand "foldall"
-
-			aceEditor.showCommandLine = (args...) =>
-				@trigger "showCommandLine", aceEditor, args...
-
-			aceEditor.on "dblclick", (e) => @trigger "dblclick", e
-			aceEditor.on "click", (e) => @trigger "click", e
-			aceEditor.on "mousemove", (e) =>
-				position = @aceEditor.renderer.screenToTextCoordinates(e.clientX, e.clientY)
-				e.position = position
-				@trigger "mousemove", e
-
-		setIdeToEditorPanel: (options = {}) ->
-			@aceEditor.focus()
-			@aceEditor.resize()
-			loadDocument = =>
-				@refreshCompilationErrors()
-
-				@ide.layoutManager.resizeAllSplitters()
-
-				if options.line?
-					@gotoLine(options.line)
-				else
-					pos = $.localStorage("doc.position.#{@current_doc_id}") || {}
-					@ignoreCursorPositionChanges = true
-					@setCursorPosition(pos.cursorPosition or {row: 0, column: 0})
-					@setScrollTop(pos.scrollTop or 0)
-					@ignoreCursorPositionChanges = false
-			@ide.mainAreaManager.change 'editor', =>
-				setTimeout loadDocument, 0
-
-		refreshCompilationErrors: () ->
-			@getSession().setAnnotations @compilationErrors[@current_doc_id]
-			
-		openDoc: (doc_id, options = {}) ->
-			if @current_doc_id == doc_id && !options.forceReopen
-				@setIdeToEditorPanel(line: options.line)
-			else
-				@showLoading()
-				@current_doc_id = doc_id
-				@_openNewDocument doc_id, (error, document) =>
-					if error?
-						@ide.showGenericServerErrorMessage()
-						return
-
-					@setIdeToEditorPanel(line: options.line)
-					@hideLoading()
-					@trigger "change:doc", @getSession()
-
-		_openNewDocument: (doc_id, callback = (error, document) ->) ->
-			if @document?
-				@document.leaveAndCleanUp()
-				@_unbindFromDocumentEvents(@document)
-				@_detachDocumentFromEditor(@document)
-
-			@document = Document.getDocument @ide, doc_id
-
-			@document.join (error) =>
-				return callback(error) if error?
-				@_bindToDocumentEvents(@document)
-				@_bindDocumentToEditor(@document)
-				callback null, @document
-
-		_bindToDocumentEvents: (document) ->
-			document.on "remoteop", () =>
-				@undoManager.nextUpdateIsRemote = true
-
-			document.on "error", (error) =>
-				@openDoc(document.doc_id, forceReopen: true)
-
-				Modal.createModal
-					title: "Out of sync"
-					message: "Sorry, this file has gone out of sync and we need to do a full refresh. Please let us know if this happens frequently."
-					buttons:[
-						text: "Ok"
-					]
-
-			document.on "externalUpdate", () =>
-				Modal.createModal
-					title: "Document Updated Externally"
-					message: "This document was just updated externally. Any recent changes you have made may have been overwritten. To see previous versions please look in the history."
-					buttons:[
-						text: "Ok"
-					]
-
-		_unbindFromDocumentEvents: (document) ->
-			document.off()
-		
-		_bindDocumentToEditor: (document) ->
-			$("#editor").show()
-			@_bindNewDocToAce(document)
-
-		_detachDocumentFromEditor: (document) ->
-			document.detachFromAce()
-
-		_bindNewDocToAce: (document) ->
-			@_createNewSessionFromDocLines(document.getSnapshot().split("\n"))
-			@_setReadWritePermission()
-			@_bindToAceEvents()
-
-			# Updating the doc can cause the cursor to jump around
-			# but we shouldn't record that
-			@ignoreCursorPositionChanges = true
-			document.attachToAce(@aceEditor)
-			@ignoreCursorPositionChanges = false
-
-		_bindToAceEvents: () ->
-			aceDoc = @getDocument()
-			aceDoc.on 'change', (change) => @onDocChange(change)
-
-			session = @getSession()
-			session.on "changeScrollTop", (e) => @onScrollTopChange(e)
-			session.selection.on 'changeCursor', (e) => @onCursorChange(e)
-
-		_createNewSessionFromDocLines: (docLines) ->
-			@aceEditor.setSession(new EditSession(docLines))
-			session = @getSession()
-			session.setUseWrapMode(true)
-			session.setMode(new LatexMode())
-			@undoManager = new UndoManager(@)
-			session.setUndoManager @undoManager
-
-		_setReadWritePermission: () ->
-			if !@ide.isAllowedToDoIt 'readAndWrite'
-				@makeReadOnly()
-			else
-				@makeWritable()
-
-		onDocChange: (change) ->
-			@lastUpdated = new Date()
-			@trigger "update:doc", change
-			
-		onScrollTopChange: (event) ->
-			@trigger "scroll", event
-			if !@ignoreCursorPositionChanges
-				docPosition = $.localStorage("doc.position.#{@current_doc_id}") || {}
-				docPosition.scrollTop = @getScrollTop()
-				$.localStorage("doc.position.#{@current_doc_id}", docPosition)
-			
-		onCursorChange: (event) ->
-			@trigger "cursor:change", event
-			if !@ignoreCursorPositionChanges
-				docPosition = $.localStorage("doc.position.#{@current_doc_id}") || {}
-				docPosition.cursorPosition = @getCursorPosition()
-				$.localStorage("doc.position.#{@current_doc_id}", docPosition)
-
-		makeReadOnly: () ->
-			@aceEditor.setReadOnly true
-
-		makeWritable: () ->
-			@aceEditor.setReadOnly false
-
-		getSession: () -> @aceEditor.getSession()
-
-		getDocument: () -> @getSession().getDocument()
-
-		gotoLine: (line) ->
-			@aceEditor.gotoLine(line)
-
-		getCurrentLine: () ->
-			@aceEditor.selection?.getCursor()?.row
-
-		getCurrentColumn: () ->
-			@aceEditor.selection?.getCursor()?.column
-
-		getLines: (from, to) ->
-			if from? and to?
-				@getSession().doc.getLines(from, to)
-			else
-				@getSession().doc.getAllLines()
-
-		addMarker: (position, klass, type, inFront) ->
-			range = new Range(
-				position.row, position.column,
-				position.row, position.column + position.length
-			)
-			@getSession().addMarker range, klass, type, inFront
-
-		removeMarker: (markerId) ->
-			@getSession().removeMarker markerId
-
-		getCursorPosition: () -> @aceEditor.getCursorPosition()
-		setCursorPosition: (pos) -> @aceEditor.moveCursorToPosition(pos)
-
-		getScrollTop: () -> @getSession().getScrollTop()
-		setScrollTop: (pos) -> @getSession().setScrollTop(pos)
-
-		replaceText: (range, text) ->
-			@getSession().replace(new Range(
-				range.start.row, range.start.column,
-				range.end.row, range.end.column
-			), text)
-
-		getContainerElement: () ->
-			$(@aceEditor.renderer.getContainerElement())
-
-		getCursorElement: () ->
-			@getContainerElement().find(".ace_cursor")
-
-		textToEditorCoordinates: (x, y) ->
-			editorAreaOffset = @getContainerElement().offset()
-			{pageX, pageY} = @aceEditor.renderer.textToScreenCoordinates(x, y)
-			return {
-				x: pageX - editorAreaOffset.left
-				y: pageY - editorAreaOffset.top
-			}
-
-		chaosMonkey: (line = 0, char = "a") ->
-			@_cm = setInterval () =>
-				@aceEditor.session.insert({row: line, column: 0}, char)
-			, 100
-
-		clearChaosMonkey: () ->
-			clearInterval @_cm
-
-		getCurrentDocId: () ->
-			@current_doc_id
-
-		enable: () ->
-			@enabled = true
-
-		disable: () ->
-			@enabled = false
-
-		hasUnsavedChanges: () ->
-			Document.hasUnsavedChanges()

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

@@ -1,3 +0,0 @@
-WEB = true
-window.sharejs = exports = {}
-types = exports.types = {}

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

@@ -1,127 +0,0 @@
-define [
-	"libs/sharejs"
-	"libs/backbone"
-], (ShareJs) ->
-	class ShareJsDoc
-		constructor: (@doc_id, docLines, version, @socket) ->
-			# Dencode any binary bits of data
-			# See http://ecmanaut.blogspot.co.uk/2006/07/encoding-decoding-utf8-in-javascript.html
-			@type = "text"
-			docLines = for line in docLines
-				if line.text?
-					@type = "json"
-					line.text = decodeURIComponent(escape(line.text))
-				else
-					@type = "text"
-					line = decodeURIComponent(escape(line))
-				line
-
-			if @type == "text"
-				snapshot = docLines.join("\n")
-			else if @type == "json"
-				snapshot = { lines: docLines }
-			else
-				throw new Error("Unknown type: #{@type}")
-
-			@connection = {
-				send: (update) =>
-					@_startInflightOpTimeout(update)
-					@socket.emit "applyOtUpdate", @doc_id, update
-				state: "ok"
-				id:    @socket.socket.sessionid
-			}
-
-			@_doc = new ShareJs.Doc @connection, @doc_id,
-				type: @type
-			@_doc.on "change", () =>
-				@trigger "change"
-			@_doc.on "acknowledge", () =>
-				@trigger "acknowledge"
-			@_doc.on "remoteop", () =>
-				@trigger "remoteop"
-
-			@_bindToDocChanges(@_doc)
-
-			@processUpdateFromServer
-				open: true
-				v: version
-				snapshot: snapshot
-
-		submitOp: (args...) -> @_doc.submitOp(args...)
-
-		processUpdateFromServer: (message) ->
-			try
-				@_doc._onMessage message
-			catch error
-				# Version mismatches are thrown as errors
-				@_handleError(error)
-
-			if message?.meta?.type == "external"
-				@trigger "externalUpdate"
-
-		catchUp: (updates) ->
-			for update, i in updates
-				update.v   = @_doc.version
-				update.doc = @doc_id
-				@processUpdateFromServer(update)
-
-		getSnapshot: () -> @_doc.snapshot
-		getVersion: () -> @_doc.version
-		getType: () -> @type
-
-		clearInflightAndPendingOps: () ->
-			@_doc.inflightOp = null
-			@_doc.inflightCallbacks = []
-			@_doc.pendingOp = null
-			@_doc.pendingCallbacks = []
-
-		flushPendingOps: () ->
-			# This will flush any ops that are pending.
-			# If there is an inflight op it will do nothing.
-			@_doc.flush()
-
-		updateConnectionState: (state) ->
-			@connection.state = state
-			@connection.id = @socket.socket.sessionid
-			@_doc.autoOpen = false
-			@_doc._connectionStateChanged(state)
-
-		hasBufferedOps: () ->
-			@_doc.inflightOp? or @_doc.pendingOp?
-
-		getInflightOp: () -> @_doc.inflightOp
-		getPendingOp: () -> @_doc.pendingOp
-
-		attachToAce: (ace) -> @_doc.attach_ace(ace)
-		detachFromAce: () -> @_doc.detach_ace?()
-	
-		INFLIGHT_OP_TIMEOUT: 10000
-		_startInflightOpTimeout: (update) ->
-			meta =
-				v: update.v
-				op_sent_at: new Date()
-			timer = setTimeout () =>
-				@trigger "op:timeout", update
-			, @INFLIGHT_OP_TIMEOUT
-			@_doc.inflightCallbacks.push () =>
-				clearTimeout timer
-
-		_handleError: (error, meta = {}) ->
-			@trigger "error", error, meta
-
-		_bindToDocChanges: (doc) ->
-			submitOp = doc.submitOp
-			doc.submitOp = (args...) =>
-				@trigger "op:sent", args...
-				doc.pendingCallbacks.push () =>
-					@trigger "op:acknowledged", args...
-				submitOp.apply(doc, args)
-
-			flush = doc.flush
-			doc.flush = (args...) =>
-				@trigger "flush", doc.inflightOp, doc.pendingOp, doc.version
-				flush.apply(doc, args)
-
-	_.extend(ShareJsDoc::, Backbone.Events)
-
-	return ShareJsDoc

+ 0 - 130
services/web/public/coffee/editor/sharejs/client/ace.coffee

@@ -1,130 +0,0 @@
-# This is some utility code to connect an ace editor to a sharejs document.
-
-Range = require("ace/range").Range
-
-# Convert an ace delta into an op understood by share.js
-applyToShareJS = (editorDoc, delta, doc) ->
-  # Get the start position of the range, in no. of characters
-  getStartOffsetPosition = (range) ->
-    # This is quite inefficient - getLines makes a copy of the entire
-    # lines array in the document. It would be nice if we could just
-    # access them directly.
-    lines = editorDoc.getLines 0, range.start.row
-      
-    offset = 0
-
-    for line, i in lines
-      offset += if i < range.start.row
-        line.length
-      else
-        range.start.column
-
-    # Add the row number to include newlines.
-    offset + range.start.row
-
-  pos = getStartOffsetPosition(delta.range)
-
-  switch delta.action
-    when 'insertText' then doc.insert pos, delta.text
-    when 'removeText' then doc.del pos, delta.text.length
-    
-    when 'insertLines'
-      text = delta.lines.join('\n') + '\n'
-      doc.insert pos, text
-      
-    when 'removeLines'
-      text = delta.lines.join('\n') + '\n'
-      doc.del pos, text.length
-
-    else throw new Error "unknown action: #{delta.action}"
-  
-  return
-
-# Attach an ace editor to the document. The editor's contents are replaced
-# with the document's contents unless keepEditorContents is true. (In which case the document's
-# contents are nuked and replaced with the editor's).
-window.sharejs.extendDoc 'attach_ace', (editor, keepEditorContents) ->
-  throw new Error 'Only text documents can be attached to ace' unless @provides['text']
-
-  doc = this
-  editorDoc = editor.getSession().getDocument()
-  editorDoc.setNewLineMode 'unix'
-
-  check = ->
-    window.setTimeout ->
-        editorText = editorDoc.getValue()
-        otText = doc.getText()
-
-        if editorText != otText
-          console.error "Text does not match!"
-          console.error "editor: #{editorText}"
-          console.error "ot:     #{otText}"
-          # Should probably also replace the editor text with the doc snapshot.
-      , 0
-
-  # MODIFIED by James: We will set the doc contents ourselves to
-  # avoid an extra entry in the undo stack.
-  # if keepEditorContents
-  #   doc.del 0, doc.getText().length
-  #   doc.insert 0, editorDoc.getValue()
-  # else
-  #   editorDoc.setValue doc.getText()
-
-  check()
-
-  # When we apply ops from sharejs, ace emits edit events. We need to ignore those
-  # to prevent an infinite typing loop.
-  suppress = false
-  
-  # Listen for edits in ace
-  editorListener = (change) ->
-    return if suppress
-    applyToShareJS editorDoc, change.data, doc
-
-    check()
-
-  editorDoc.on 'change', editorListener
-
-  # Listen for remote ops on the sharejs document
-  docListener = (op) ->
-    suppress = true
-    applyToDoc editorDoc, op
-    suppress = false
-
-    check()
-
-
-  # Horribly inefficient.
-  offsetToPos = (offset) ->
-    # Again, very inefficient.
-    lines = editorDoc.getAllLines()
-
-    row = 0
-    for line, row in lines
-      break if offset <= line.length
-
-      # +1 for the newline.
-      offset -= lines[row].length + 1
-
-    row:row, column:offset
-
-  doc.on 'insert', (pos, text) ->
-    suppress = true
-    editorDoc.insert offsetToPos(pos), text
-    suppress = false
-    check()
-
-  doc.on 'delete', (pos, text) ->
-    suppress = true
-    range = Range.fromPoints offsetToPos(pos), offsetToPos(pos + text.length)
-    editorDoc.remove range
-    suppress = false
-    check()
-
-  doc.detach_ace = ->
-    doc.removeListener 'remoteop', docListener
-    editorDoc.removeListener 'change', editorListener
-    delete doc.detach_ace
-
-  return
-

+ 0 - 119
services/web/public/coffee/editor/sharejs/client/ace.js

@@ -1,119 +0,0 @@
-// Generated by CoffeeScript 1.4.0
-(function() {
-  var Range, applyToShareJS;
-
-  Range = require("ace/range").Range;
-
-  applyToShareJS = function(editorDoc, delta, doc) {
-    var getStartOffsetPosition, pos, text;
-    getStartOffsetPosition = function(range) {
-      var i, line, lines, offset, _i, _len;
-      lines = editorDoc.getLines(0, range.start.row);
-      offset = 0;
-      for (i = _i = 0, _len = lines.length; _i < _len; i = ++_i) {
-        line = lines[i];
-        offset += i < range.start.row ? line.length : range.start.column;
-      }
-      return offset + range.start.row;
-    };
-    pos = getStartOffsetPosition(delta.range);
-    switch (delta.action) {
-      case 'insertText':
-        doc.insert(pos, delta.text);
-        break;
-      case 'removeText':
-        doc.del(pos, delta.text.length);
-        break;
-      case 'insertLines':
-        text = delta.lines.join('\n') + '\n';
-        doc.insert(pos, text);
-        break;
-      case 'removeLines':
-        text = delta.lines.join('\n') + '\n';
-        doc.del(pos, text.length);
-        break;
-      default:
-        throw new Error("unknown action: " + delta.action);
-    }
-  };
-
-  window.sharejs.extendDoc('attach_ace', function(editor, keepEditorContents) {
-    var check, doc, docListener, editorDoc, editorListener, offsetToPos, suppress;
-    if (!this.provides['text']) {
-      throw new Error('Only text documents can be attached to ace');
-    }
-    doc = this;
-    editorDoc = editor.getSession().getDocument();
-    editorDoc.setNewLineMode('unix');
-    check = function() {
-      return window.setTimeout(function() {
-        var editorText, otText;
-        editorText = editorDoc.getValue();
-        otText = doc.getText();
-        if (editorText !== otText) {
-          console.error("Text does not match!");
-          console.error("editor: " + editorText);
-          return console.error("ot:     " + otText);
-        }
-      }, 0);
-    };
-    if (keepEditorContents) {
-      doc.del(0, doc.getText().length);
-      doc.insert(0, editorDoc.getValue());
-    } else {
-      editorDoc.setValue(doc.getText());
-    }
-    check();
-    suppress = false;
-    editorListener = function(change) {
-      if (suppress) {
-        return;
-      }
-      applyToShareJS(editorDoc, change.data, doc);
-      return check();
-    };
-    editorDoc.on('change', editorListener);
-    docListener = function(op) {
-      suppress = true;
-      applyToDoc(editorDoc, op);
-      suppress = false;
-      return check();
-    };
-    offsetToPos = function(offset) {
-      var line, lines, row, _i, _len;
-      lines = editorDoc.getAllLines();
-      row = 0;
-      for (row = _i = 0, _len = lines.length; _i < _len; row = ++_i) {
-        line = lines[row];
-        if (offset <= line.length) {
-          break;
-        }
-        offset -= lines[row].length + 1;
-      }
-      return {
-        row: row,
-        column: offset
-      };
-    };
-    doc.on('insert', function(pos, text) {
-      suppress = true;
-      editorDoc.insert(offsetToPos(pos), text);
-      suppress = false;
-      return check();
-    });
-    doc.on('delete', function(pos, text) {
-      var range;
-      suppress = true;
-      range = Range.fromPoints(offsetToPos(pos), offsetToPos(pos + text.length));
-      editorDoc.remove(range);
-      suppress = false;
-      return check();
-    });
-    doc.detach_ace = function() {
-      doc.removeListener('remoteop', docListener);
-      editorDoc.removeListener('change', editorListener);
-      return delete doc.detach_ace;
-    };
-  });
-
-}).call(this);

+ 0 - 94
services/web/public/coffee/editor/sharejs/client/cm.coffee

@@ -1,94 +0,0 @@
-# This is some utility code to connect a CodeMirror editor
-# to a sharejs document.
-# It is heavily inspired from the Ace editor hook.
-
-# Convert a CodeMirror delta into an op understood by share.js
-applyToShareJS = (editorDoc, delta, doc) ->
-  # CodeMirror deltas give a text replacement.
-  # I tuned this operation a little bit, for speed.
-  startPos = 0  # Get character position from # of chars in each line.
-  i = 0         # i goes through all lines.
-
-  while i < delta.from.line
-    startPos += editorDoc.lineInfo(i).text.length + 1   # Add 1 for '\n'
-    i++
-
-  startPos += delta.from.ch
-
-  if delta.to.line == delta.from.line &&
-     delta.to.ch == delta.from.ch # Then nothing was removed.
-    doc.insert startPos, delta.text.join '\n'
-  else
-    delLen = delta.to.ch - delta.from.ch
-    while i < delta.to.line
-      delLen += editorDoc.lineInfo(i).text.length + 1   # Add 1 for '\n'
-      i++
-    doc.del startPos, delLen
-    doc.insert startPos, delta.text.join '\n' if delta.text
-
-  applyToShareJS editorDoc, delta.next, doc if delta.next
-
-# Attach a CodeMirror editor to the document. The editor's contents are replaced
-# with the document's contents unless keepEditorContents is true. (In which case
-# the document's contents are nuked and replaced with the editor's).
-window.sharejs.extendDoc 'attach_cm', (editor, keepEditorContents) ->
-  unless @provides.text
-    throw new Error 'Only text documents can be attached to CodeMirror2'
-
-  sharedoc = @
-  check = ->
-    window.setTimeout ->
-        editorText = editor.getValue()
-        otText = sharedoc.getValue()
-
-        if editorText != otText
-          console.error "Text does not match!"
-          console.error "editor: #{editorText}"
-          console.error "ot:     #{otText}"
-          # Replace the editor text with the doc snapshot.
-          editor.setValue sharedoc.getValue()
-      , 0
-
-  if keepEditorContents
-    @del 0, sharedoc.getText().length
-    @insert 0, editor.getValue()
-  else
-    editor.setValue sharedoc.getText()
-
-  check()
-
-  # When we apply ops from sharejs, CodeMirror emits edit events.
-  # We need to ignore those to prevent an infinite typing loop.
-  suppress = false
-
-  # Listen for edits in CodeMirror.
-  editorListener = (ed, change) ->
-    return if suppress
-    applyToShareJS editor, change, sharedoc
-
-    check()
-
-  editor.setOption 'onChange',  editorListener
-
-  @on 'insert', (pos, text) ->
-    suppress = true
-    # All the primitives we need are already in CM's API.
-    editor.replaceRange text, editor.posFromIndex(pos)
-    suppress = false
-    check()
-
-  @on 'delete', (pos, text) ->
-    suppress = true
-    from = editor.posFromIndex pos
-    to = editor.posFromIndex (pos + text.length)
-    editor.replaceRange '', from, to
-    suppress = false
-    check()
-
-  @detach_cm = ->
-    # TODO: can we remove the insert and delete event callbacks?
-    editor.setOption 'onChange', null
-    delete @detach_cm
-
-  return
-

+ 0 - 167
services/web/public/coffee/editor/sharejs/client/connection.coffee

@@ -1,167 +0,0 @@
-# A Connection wraps a persistant BC connection to a sharejs server.
-#
-# This class implements the client side of the protocol defined here:
-# https://github.com/josephg/ShareJS/wiki/Wire-Protocol
-#
-# The equivalent server code is in src/server/browserchannel.coffee.
-#
-# This file is a bit of a mess. I'm dreadfully sorry about that. It passes all the tests,
-# so I have hope that its *correct* even if its not clean.
-#
-# Most of Connection exists to support the open() method, which creates a new document
-# reference.
-
-if WEB?
-  types = exports.types
-  throw new Error 'Must load browserchannel before this library' unless window.BCSocket
-  {BCSocket} = window
-else
-  types = require '../types'
-  {BCSocket} = require 'browserchannel'
-  Doc = require('./doc').Doc
-
-class Connection
-  constructor: (host) ->
-    # Map of docname -> doc
-    @docs = {}
-
-    # States:
-    # - 'connecting': The connection is being established
-    # - 'handshaking': The connection has been established, but we don't have the auth ID yet
-    # - 'ok': We have connected and recieved our client ID. Ready for data.
-    # - 'disconnected': The connection is closed, but it will not reconnect automatically.
-    # - 'stopped': The connection is closed, and will not reconnect.
-    @state = 'connecting'
-    @socket = new BCSocket host, reconnect:true
-
-    @socket.onmessage = (msg) =>
-      if msg.auth is null
-        # Auth failed.
-        @lastError = msg.error # 'forbidden'
-        @disconnect()
-        return @emit 'connect failed', msg.error
-      else if msg.auth
-        # Our very own client id.
-        @id = msg.auth
-        @setState 'ok'
-        return
-
-      docName = msg.doc
-
-      if docName isnt undefined
-        @lastReceivedDoc = docName
-      else
-        msg.doc = docName = @lastReceivedDoc
-
-      if @docs[docName]
-        @docs[docName]._onMessage msg
-      else
-        console?.error 'Unhandled message', msg
-
-    @connected = false
-    @socket.onclose = (reason) =>
-      #console.warn 'onclose', reason
-      @setState 'disconnected', reason
-      if reason in ['Closed', 'Stopped by server']
-        @setState 'stopped', @lastError or reason
-
-    @socket.onerror = (e) =>
-      #console.warn 'onerror', e
-      @emit 'error', e
-
-    @socket.onopen = =>
-      #console.warn 'onopen'
-      @lastError = @lastReceivedDoc = @lastSentDoc = null
-      @setState 'handshaking'
-
-    @socket.onconnecting = =>
-      #console.warn 'connecting'
-      @setState 'connecting'
-
-  setState: (state, data) ->
-    return if @state is state
-    @state = state
-
-    delete @id if state is 'disconnected'
-    @emit state, data
-
-    # Documents could just subscribe to the state change events, but there's less state to
-    # clean up when you close a document if I just notify the doucments directly.
-    for docName, doc of @docs
-      doc._connectionStateChanged state, data
-
-  send: (data) ->
-    docName = data.doc
-
-    if docName is @lastSentDoc
-      delete data.doc
-    else
-      @lastSentDoc = docName
-
-    #console.warn 'c->s', data
-    @socket.send data
-
-  disconnect: ->
-    # This will call @socket.onclose(), which in turn will emit the 'disconnected' event.
-    #console.warn 'calling close on the socket'
-    @socket.close()
-
-  # *** Doc management
- 
-  makeDoc: (name, data, callback) ->
-    throw new Error("Doc #{name} already open") if @docs[name]
-    doc = new Doc(@, name, data)
-    @docs[name] = doc
-
-    doc.open (error) =>
-      delete @docs[name] if error
-      callback error, (doc unless error)
-
-  # Open a document that already exists
-  # callback(error, doc)
-  openExisting: (docName, callback) ->
-    return callback 'connection closed' if @state is 'stopped'
-    return callback null, @docs[docName] if @docs[docName]
-    doc = @makeDoc docName, {}, callback
-
-  # Open a document. It will be created if it doesn't already exist.
-  # Callback is passed a document or an error
-  # type is either a type name (eg 'text' or 'simple') or the actual type object.
-  # Types must be supported by the server.
-  # callback(error, doc)
-  open: (docName, type, callback) ->
-    return callback 'connection closed' if @state is 'stopped'
-
-    if typeof type is 'function'
-      callback = type
-      type = 'text'
-
-    callback ||= ->
-
-    type = types[type] if typeof type is 'string'
-
-    throw new Error "OT code for document type missing" unless type
-
-    throw new Error 'Server-generated random doc names are not currently supported' unless docName?
-
-    if @docs[docName]
-      doc = @docs[docName]
-      if doc.type == type
-        callback null, doc
-      else
-        callback 'Type mismatch', doc
-      return
-
-    @makeDoc docName, {create:true, type:type.name}, callback
-
-# Not currently working.
-#  create: (type, callback) ->
-#    open null, type, callback
-
-# Make connections event emitters.
-unless WEB?
-  MicroEvent = require './microevent'
-
-MicroEvent.mixin Connection
-
-exports.Connection = Connection

+ 0 - 327
services/web/public/coffee/editor/sharejs/client/doc.coffee

@@ -1,327 +0,0 @@
-unless WEB?
-  types = require '../types'
-
-if WEB?
-  exports.extendDoc = (name, fn) ->
-    Doc::[name] = fn
-
-# A Doc is a client's view on a sharejs document.
-#
-# Documents are created by calling Connection.open().
-#
-# Documents are event emitters - use doc.on(eventname, fn) to subscribe.
-#
-# Documents get mixed in with their type's API methods. So, you can .insert('foo', 0) into
-# a text document and stuff like that.
-#
-# Events:
-#  - remoteop (op)
-#  - changed (op)
-#  - acknowledge (op)
-#  - error
-#  - open, closing, closed. 'closing' is not guaranteed to fire before closed.
-class Doc
-  # connection is a Connection object.
-  # name is the documents' docName.
-  # data can optionally contain known document data, and initial open() call arguments:
-  # {v[erson], snapshot={...}, type, create=true/false/undefined}
-  # callback will be called once the document is first opened.
-  constructor: (@connection, @name, openData) ->
-    # Any of these can be null / undefined at this stage.
-    openData ||= {}
-    @version = openData.v
-    @snapshot = openData.snaphot
-    @_setType openData.type if openData.type
-
-    @state = 'closed'
-    @autoOpen = false
-
-    # Has the document already been created?
-    @_create = openData.create
-
-    # The op that is currently roundtripping to the server, or null.
-    #
-    # When the connection reconnects, the inflight op is resubmitted.
-    @inflightOp = null
-    @inflightCallbacks = []
-    # The auth ids which the client has previously used to attempt to send inflightOp. This is
-    # usually empty.
-    @inflightSubmittedIds = []
-
-    # All ops that are waiting for the server to acknowledge @inflightOp
-    @pendingOp = null
-    @pendingCallbacks = []
-
-    # Some recent ops, incase submitOp is called with an old op version number.
-    @serverOps = {}
-
-  # Transform a server op by a client op, and vice versa.
-  _xf: (client, server) ->
-    if @type.transformX
-      @type.transformX(client, server)
-    else
-      client_ = @type.transform client, server, 'left'
-      server_ = @type.transform server, client, 'right'
-      return [client_, server_]
-  
-  _otApply: (docOp, isRemote) ->
-    oldSnapshot = @snapshot
-    @snapshot = @type.apply(@snapshot, docOp)
-
-    # Its important that these event handlers are called with oldSnapshot.
-    # The reason is that the OT type APIs might need to access the snapshots to
-    # determine information about the received op.
-    @emit 'change', docOp, oldSnapshot
-    @emit 'remoteop', docOp, oldSnapshot if isRemote
-  
-  _connectionStateChanged: (state, data) ->
-    switch state
-      when 'disconnected'
-        @state = 'closed'
-        # This is used by the server to make sure that when an op is resubmitted it
-        # doesn't end up getting applied twice.
-        @inflightSubmittedIds.push @connection.id if @inflightOp
-
-        @emit 'closed'
-
-      when 'ok' # Might be able to do this when we're connecting... that would save a roundtrip.
-        @open() if @autoOpen
-
-      when 'stopped'
-        @_openCallback? data
-
-    @emit state, data
-
-  _setType: (type) ->
-    if typeof type is 'string'
-      type = types[type]
-
-    throw new Error 'Support for types without compose() is not implemented' unless type and type.compose
-
-    @type = type
-    if type.api
-      this[k] = v for k, v of type.api
-      @_register?()
-    else
-      @provides = {}
-
-  _onMessage: (msg) ->
-    #console.warn 's->c', msg
-    if msg.open == true
-      # The document has been successfully opened.
-      @state = 'open'
-      @_create = false # Don't try and create the document again next time open() is called.
-      unless @created?
-        @created = !!msg.create
-
-      @_setType msg.type if msg.type
-      if msg.create
-        @created = true
-        @snapshot = @type.create()
-      else
-        @created = false unless @created is true
-        @snapshot = msg.snapshot if msg.snapshot isnt undefined
-
-      @version = msg.v if msg.v?
-
-      # Resend any previously queued operation.
-      if @inflightOp
-        response =
-          doc: @name
-          op: @inflightOp
-          v: @version
-        response.dupIfSource = @inflightSubmittedIds if @inflightSubmittedIds.length
-        @connection.send response
-      else
-        @flush()
-
-      @emit 'open'
-      
-      @_openCallback? null
- 
-    else if msg.open == false
-      # The document has either been closed, or an open request has failed.
-      if msg.error
-        # An error occurred opening the document.
-        console?.error "Could not open document: #{msg.error}"
-        @emit 'error', msg.error
-        @_openCallback? msg.error
-
-      @state = 'closed'
-      @emit 'closed'
-
-      @_closeCallback?()
-      @_closeCallback = null
-
-    else if msg.op is null and error is 'Op already submitted'
-      # We've tried to resend an op to the server, which has already been received successfully. Do nothing.
-      # The op will be confirmed normally when we get the op itself was echoed back from the server
-      # (handled below).
-
-    else if (msg.op is undefined and msg.v isnt undefined) or (msg.op and msg.meta.source in @inflightSubmittedIds)
-      # Our inflight op has been acknowledged.
-      oldInflightOp = @inflightOp
-      @inflightOp = null
-      @inflightSubmittedIds.length = 0
-
-      error = msg.error
-      if error
-        # The server has rejected an op from the client for some reason.
-        # We'll send the error message to the user and roll back the change.
-        #
-        # If the server isn't going to allow edits anyway, we should probably
-        # figure out some way to flag that (readonly:true in the open request?)
-
-        if @type.invert
-          undo = @type.invert oldInflightOp
-
-          # Now we have to transform the undo operation by any server ops & pending ops
-          if @pendingOp
-            [@pendingOp, undo] = @_xf @pendingOp, undo
-
-          # ... and apply it locally, reverting the changes.
-          # 
-          # This call will also call @emit 'remoteop'. I'm still not 100% sure about this
-          # functionality, because its really a local op. Basically, the problem is that
-          # if the client's op is rejected by the server, the editor window should update
-          # to reflect the undo.
-          @_otApply undo, true
-        else
-          @emit 'error', "Op apply failed (#{error}) and the op could not be reverted"
-
-        callback error for callback in @inflightCallbacks
-      else
-        # The op applied successfully.
-        throw new Error('Invalid version from server') unless msg.v == @version
-
-        @serverOps[@version] = oldInflightOp
-        @version++
-        @emit 'acknowledge', oldInflightOp
-        callback null, oldInflightOp for callback in @inflightCallbacks
-
-      # Send the next op.
-      @flush()
-
-    else if msg.op
-      # We got a new op from the server.
-      # msg is {doc:, op:, v:}
-
-      # There is a bug in socket.io (produced on firefox 3.6) which causes messages
-      # to be duplicated sometimes.
-      # We'll just silently drop subsequent messages.
-      return if msg.v < @version
-
-      return @emit 'error', "Expected docName '#{@name}' but got #{msg.doc}" unless msg.doc == @name
-      return @emit 'error', "Expected version #{@version} but got #{msg.v}" unless msg.v == @version
-
-  #    p "if: #{i @inflightOp} pending: #{i @pendingOp} doc '#{@snapshot}' op: #{i msg.op}"
-
-      op = msg.op
-      @serverOps[@version] = op
-
-      docOp = op
-      if @inflightOp != null
-        [@inflightOp, docOp] = @_xf @inflightOp, docOp
-      if @pendingOp != null
-        [@pendingOp, docOp] = @_xf @pendingOp, docOp
-        
-      @version++
-      # Finally, apply the op to @snapshot and trigger any event listeners
-      @_otApply docOp, true
-
-    else if msg.meta
-      {path, value} = msg.meta
-
-      switch path?[0]
-        when 'shout'
-          return @emit 'shout', value
-        else
-          console?.warn 'Unhandled meta op:', msg
-
-    else
-      console?.warn 'Unhandled document message:', msg
-
-
-  # Send ops to the server, if appropriate.
-  #
-  # Only one op can be in-flight at a time, so if an op is already on its way then
-  # this method does nothing.
-  flush: =>
-    return unless @connection.state == 'ok' and @inflightOp == null and @pendingOp != null
-
-    # Rotate null -> pending -> inflight
-    @inflightOp = @pendingOp
-    @inflightCallbacks = @pendingCallbacks
-
-    @pendingOp = null
-    @pendingCallbacks = []
-
-    @connection.send {doc:@name, op:@inflightOp, v:@version}
-
-  # Submit an op to the server. The op maybe held for a little while before being sent, as only one
-  # op can be inflight at any time.
-  submitOp: (op, callback) ->
-    op = @type.normalize(op) if @type.normalize?
-
-    # If this throws an exception, no changes should have been made to the doc
-    @snapshot = @type.apply @snapshot, op
-
-    if @pendingOp != null
-      @pendingOp = @type.compose(@pendingOp, op)
-    else
-      @pendingOp = op
-
-    @pendingCallbacks.push callback if callback
-
-    @emit 'change', op
-
-    # A timeout is used so if the user sends multiple ops at the same time, they'll be composed
-    # & sent together.
-    setTimeout @flush, 0
-  
-  shout: (msg) =>
-    # Meta ops don't have to queue, they can go direct. Good/bad idea?
-    @connection.send {doc:@name, meta: { path: ['shout'], value: msg } }
-  
-  # Open a document. The document starts closed.
-  open: (callback) ->
-    @autoOpen = true
-    return unless @state is 'closed'
-
-    message =
-      doc: @name
-      open: true
-
-    message.snapshot = null if @snapshot is undefined
-    message.type = @type.name if @type
-    message.v = @version if @version?
-    message.create = true if @_create
-
-    @connection.send message
-
-    @state = 'opening'
-
-    @_openCallback = (error) =>
-      @_openCallback = null
-      callback? error
-
-  # Close a document.
-  close: (callback) ->
-    @autoOpen = false
-    return callback?() if @state is 'closed'
-
-    @connection.send {doc:@name, open:false}
-
-    # Should this happen immediately or when we get open:false back from the server?
-    @state = 'closed'
-
-    @emit 'closing'
-    @_closeCallback = callback
- 
-# Make documents event emitters
-unless WEB?
-  MicroEvent = require './microevent'
-
-MicroEvent.mixin Doc
-
-exports.Doc = Doc

+ 0 - 332
services/web/public/coffee/editor/sharejs/client/doc.js

@@ -1,332 +0,0 @@
-// Generated by CoffeeScript 1.4.0
-(function() {
-  var Doc, MicroEvent, types,
-    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
-    __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
-
-  if (typeof WEB === "undefined" || WEB === null) {
-    types = require('../types');
-  }
-
-  if (typeof WEB !== "undefined" && WEB !== null) {
-    exports.extendDoc = function(name, fn) {
-      return Doc.prototype[name] = fn;
-    };
-  }
-
-  Doc = (function() {
-
-    function Doc(connection, name, openData) {
-      this.connection = connection;
-      this.name = name;
-      this.shout = __bind(this.shout, this);
-
-      this.flush = __bind(this.flush, this);
-
-      openData || (openData = {});
-      this.version = openData.v;
-      this.snapshot = openData.snaphot;
-      if (openData.type) {
-        this._setType(openData.type);
-      }
-      this.state = 'closed';
-      this.autoOpen = false;
-      this._create = openData.create;
-      this.inflightOp = null;
-      this.inflightCallbacks = [];
-      this.inflightSubmittedIds = [];
-      this.pendingOp = null;
-      this.pendingCallbacks = [];
-      this.serverOps = {};
-    }
-
-    Doc.prototype._xf = function(client, server) {
-      var client_, server_;
-      if (this.type.transformX) {
-        return this.type.transformX(client, server);
-      } else {
-        client_ = this.type.transform(client, server, 'left');
-        server_ = this.type.transform(server, client, 'right');
-        return [client_, server_];
-      }
-    };
-
-    Doc.prototype._otApply = function(docOp, isRemote) {
-      var oldSnapshot;
-      oldSnapshot = this.snapshot;
-      this.snapshot = this.type.apply(this.snapshot, docOp);
-      this.emit('change', docOp, oldSnapshot);
-      if (isRemote) {
-        return this.emit('remoteop', docOp, oldSnapshot);
-      }
-    };
-
-    Doc.prototype._connectionStateChanged = function(state, data) {
-      switch (state) {
-        case 'disconnected':
-          this.state = 'closed';
-          if (this.inflightOp) {
-            this.inflightSubmittedIds.push(this.connection.id);
-          }
-          this.emit('closed');
-          break;
-        case 'ok':
-          if (this.autoOpen) {
-            this.open();
-          }
-          break;
-        case 'stopped':
-          if (typeof this._openCallback === "function") {
-            this._openCallback(data);
-          }
-      }
-      return this.emit(state, data);
-    };
-
-    Doc.prototype._setType = function(type) {
-      var k, v, _ref;
-      if (typeof type === 'string') {
-        type = types[type];
-      }
-      if (!(type && type.compose)) {
-        throw new Error('Support for types without compose() is not implemented');
-      }
-      this.type = type;
-      if (type.api) {
-        _ref = type.api;
-        for (k in _ref) {
-          v = _ref[k];
-          this[k] = v;
-        }
-        return typeof this._register === "function" ? this._register() : void 0;
-      } else {
-        return this.provides = {};
-      }
-    };
-
-    Doc.prototype._onMessage = function(msg) {
-      var callback, docOp, error, oldInflightOp, op, path, response, undo, value, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6;
-      if (msg.open === true) {
-        this.state = 'open';
-        this._create = false;
-        if (this.created == null) {
-          this.created = !!msg.create;
-        }
-        if (msg.type) {
-          this._setType(msg.type);
-        }
-        if (msg.create) {
-          this.created = true;
-          this.snapshot = this.type.create();
-        } else {
-          if (this.created !== true) {
-            this.created = false;
-          }
-          if (msg.snapshot !== void 0) {
-            this.snapshot = msg.snapshot;
-          }
-        }
-        if (msg.v != null) {
-          this.version = msg.v;
-        }
-        if (this.inflightOp) {
-          response = {
-            doc: this.name,
-            op: this.inflightOp,
-            v: this.version
-          };
-          if (this.inflightSubmittedIds.length) {
-            response.dupIfSource = this.inflightSubmittedIds;
-          }
-          this.connection.send(response);
-        } else {
-          this.flush();
-        }
-        this.emit('open');
-        return typeof this._openCallback === "function" ? this._openCallback(null) : void 0;
-      } else if (msg.open === false) {
-        if (msg.error) {
-          if (typeof console !== "undefined" && console !== null) {
-            console.error("Could not open document: " + msg.error);
-          }
-          this.emit('error', msg.error);
-          if (typeof this._openCallback === "function") {
-            this._openCallback(msg.error);
-          }
-        }
-        this.state = 'closed';
-        this.emit('closed');
-        if (typeof this._closeCallback === "function") {
-          this._closeCallback();
-        }
-        return this._closeCallback = null;
-      } else if (msg.op === null && error === 'Op already submitted') {
-
-      } else if ((msg.op === void 0 && msg.v !== void 0) || (msg.op && (_ref = msg.meta.source, __indexOf.call(this.inflightSubmittedIds, _ref) >= 0))) {
-        oldInflightOp = this.inflightOp;
-        this.inflightOp = null;
-        this.inflightSubmittedIds.length = 0;
-        error = msg.error;
-        if (error) {
-          if (this.type.invert) {
-            undo = this.type.invert(oldInflightOp);
-            if (this.pendingOp) {
-              _ref1 = this._xf(this.pendingOp, undo), this.pendingOp = _ref1[0], undo = _ref1[1];
-            }
-            this._otApply(undo, true);
-          } else {
-            this.emit('error', "Op apply failed (" + error + ") and the op could not be reverted");
-          }
-          _ref2 = this.inflightCallbacks;
-          for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
-            callback = _ref2[_i];
-            callback(error);
-          }
-        } else {
-          if (msg.v !== this.version) {
-            throw new Error('Invalid version from server');
-          }
-          this.serverOps[this.version] = oldInflightOp;
-          this.version++;
-          this.emit('acknowledge', oldInflightOp);
-          _ref3 = this.inflightCallbacks;
-          for (_j = 0, _len1 = _ref3.length; _j < _len1; _j++) {
-            callback = _ref3[_j];
-            callback(null, oldInflightOp);
-          }
-        }
-        return this.flush();
-      } else if (msg.op) {
-        if (msg.v < this.version) {
-          return;
-        }
-        if (msg.doc !== this.name) {
-          return this.emit('error', "Expected docName '" + this.name + "' but got " + msg.doc);
-        }
-        if (msg.v !== this.version) {
-          return this.emit('error', "Expected version " + this.version + " but got " + msg.v);
-        }
-        op = msg.op;
-        this.serverOps[this.version] = op;
-        docOp = op;
-        if (this.inflightOp !== null) {
-          _ref4 = this._xf(this.inflightOp, docOp), this.inflightOp = _ref4[0], docOp = _ref4[1];
-        }
-        if (this.pendingOp !== null) {
-          _ref5 = this._xf(this.pendingOp, docOp), this.pendingOp = _ref5[0], docOp = _ref5[1];
-        }
-        this.version++;
-        return this._otApply(docOp, true);
-      } else if (msg.meta) {
-        _ref6 = msg.meta, path = _ref6.path, value = _ref6.value;
-        switch (path != null ? path[0] : void 0) {
-          case 'shout':
-            return this.emit('shout', value);
-          default:
-            return typeof console !== "undefined" && console !== null ? console.warn('Unhandled meta op:', msg) : void 0;
-        }
-      } else {
-        return typeof console !== "undefined" && console !== null ? console.warn('Unhandled document message:', msg) : void 0;
-      }
-    };
-
-    Doc.prototype.flush = function() {
-      if (!(this.connection.state === 'ok' && this.inflightOp === null && this.pendingOp !== null)) {
-        return;
-      }
-      this.inflightOp = this.pendingOp;
-      this.inflightCallbacks = this.pendingCallbacks;
-      this.pendingOp = null;
-      this.pendingCallbacks = [];
-      return this.connection.send({
-        doc: this.name,
-        op: this.inflightOp,
-        v: this.version
-      });
-    };
-
-    Doc.prototype.submitOp = function(op, callback) {
-      if (this.type.normalize != null) {
-        op = this.type.normalize(op);
-      }
-      this.snapshot = this.type.apply(this.snapshot, op);
-      if (this.pendingOp !== null) {
-        this.pendingOp = this.type.compose(this.pendingOp, op);
-      } else {
-        this.pendingOp = op;
-      }
-      if (callback) {
-        this.pendingCallbacks.push(callback);
-      }
-      this.emit('change', op);
-      return setTimeout(this.flush, 0);
-    };
-
-    Doc.prototype.shout = function(msg) {
-      return this.connection.send({
-        doc: this.name,
-        meta: {
-          path: ['shout'],
-          value: msg
-        }
-      });
-    };
-
-    Doc.prototype.open = function(callback) {
-      var message,
-        _this = this;
-      this.autoOpen = true;
-      if (this.state !== 'closed') {
-        return;
-      }
-      message = {
-        doc: this.name,
-        open: true
-      };
-      if (this.snapshot === void 0) {
-        message.snapshot = null;
-      }
-      if (this.type) {
-        message.type = this.type.name;
-      }
-      if (this.version != null) {
-        message.v = this.version;
-      }
-      if (this._create) {
-        message.create = true;
-      }
-      this.connection.send(message);
-      this.state = 'opening';
-      return this._openCallback = function(error) {
-        _this._openCallback = null;
-        return typeof callback === "function" ? callback(error) : void 0;
-      };
-    };
-
-    Doc.prototype.close = function(callback) {
-      this.autoOpen = false;
-      if (this.state === 'closed') {
-        return typeof callback === "function" ? callback() : void 0;
-      }
-      this.connection.send({
-        doc: this.name,
-        open: false
-      });
-      this.state = 'closed';
-      this.emit('closing');
-      return this._closeCallback = callback;
-    };
-
-    return Doc;
-
-  })();
-
-  if (typeof WEB === "undefined" || WEB === null) {
-    MicroEvent = require('./microevent');
-  }
-
-  MicroEvent.mixin(Doc);
-
-  exports.Doc = Doc;
-
-}).call(this);

+ 0 - 73
services/web/public/coffee/editor/sharejs/client/index.coffee

@@ -1,73 +0,0 @@
-# This file implements the sharejs client, as defined here:
-# https://github.com/josephg/ShareJS/wiki/Client-API
-#
-# It works from both a node.js context and a web context (though in the latter case,
-# it needs to be compiled to work.)
-#
-# It should become a little nicer once I start using more of the new RPC features added
-# in socket.io 0.7.
-#
-# Note that anything declared in the global scope here is shared with other files
-# built by closure. Be careful what you put in this namespace.
-
-unless WEB?
-  Connection = require('./connection').Connection
-
-# Open a document with the given name. The connection is created implicitly and reused.
-#
-# This function uses a local (private) set of connections to support .open().
-#
-# Open returns the connection its using to access the document.
-exports.open = do ->
-  # This is a private connection pool for implicitly created connections.
-  connections = {}
-
-  getConnection = (origin) ->
-    if WEB?
-      location = window.location
-      origin ?= "#{location.protocol}//#{location.host}/channel"
-    
-    unless connections[origin]
-      c = new Connection origin
-
-      del = -> delete connections[origin]
-      c.on 'disconnecting', del
-      c.on 'connect failed', del
-      connections[origin] = c
-    
-    connections[origin]
-
-  # If you're using the bare API, connections are cleaned up as soon as there's no
-  # documents using them.
-  maybeClose = (c) ->
-    numDocs = 0
-    for name, doc of c.docs
-      numDocs++ if doc.state isnt 'closed' || doc.autoOpen
-
-    if numDocs == 0
-      c.disconnect()
- 
-  (docName, type, origin, callback) ->
-    if typeof origin == 'function'
-      callback = origin
-      origin = null
-
-    c = getConnection origin
-    c.numDocs++
-    c.open docName, type, (error, doc) ->
-      if error
-        callback error
-        maybeClose c
-      else
-        doc.on 'closed', -> maybeClose c
-       
-        callback null, doc
-    
-    c.on 'connect failed'
-    return c
-
-
-unless WEB?
-  exports.Doc = require('./doc').Doc
-  exports.Connection = require('./connection').Connection
-

+ 0 - 46
services/web/public/coffee/editor/sharejs/client/microevent.coffee

@@ -1,46 +0,0 @@
-# This is a simple port of microevent.js to Coffeescript. I've changed the
-# function names to be consistent with node.js EventEmitter.
-#
-# microevent.js is copyright Jerome Etienne, and licensed under the MIT license:
-# https://github.com/jeromeetienne/microevent.js
-
-nextTick = if WEB? then (fn) -> setTimeout fn, 0 else process['nextTick']
-
-class MicroEvent
-  on: (event, fct) ->
-    @_events ||= {}
-    @_events[event] ||= []
-    @_events[event].push(fct)
-    this
-
-  removeListener: (event, fct) ->
-    @_events ||= {}
-    listeners = (@_events[event] ||= [])
-    
-    # Sadly, there's no IE8- support for indexOf.
-    i = 0
-    while i < listeners.length
-      listeners[i] = undefined if listeners[i] == fct
-      i++
-
-    nextTick => @_events[event] = (x for x in @_events[event] when x)
-
-    this
-
-  emit: (event, args...) ->
-    return this unless @_events?[event]
-    fn.apply this, args for fn in @_events[event] when fn
-    this
-
-# mixin will delegate all MicroEvent.js function in the destination object
-MicroEvent.mixin = (obj) ->
-  proto = obj.prototype || obj
-
-  # Damn closure compiler :/
-  proto.on = MicroEvent.prototype.on
-  proto.removeListener = MicroEvent.prototype.removeListener
-  proto.emit = MicroEvent.prototype.emit
-  obj
-
-module.exports = MicroEvent unless WEB?
-

+ 0 - 85
services/web/public/coffee/editor/sharejs/client/microevent.js

@@ -1,85 +0,0 @@
-// Generated by CoffeeScript 1.4.0
-(function() {
-  var MicroEvent, nextTick,
-    __slice = [].slice;
-
-  nextTick = typeof WEB !== "undefined" && WEB !== null ? function(fn) {
-    return setTimeout(fn, 0);
-  } : process['nextTick'];
-
-  MicroEvent = (function() {
-
-    function MicroEvent() {}
-
-    MicroEvent.prototype.on = function(event, fct) {
-      var _base;
-      this._events || (this._events = {});
-      (_base = this._events)[event] || (_base[event] = []);
-      this._events[event].push(fct);
-      return this;
-    };
-
-    MicroEvent.prototype.removeListener = function(event, fct) {
-      var i, listeners, _base,
-        _this = this;
-      this._events || (this._events = {});
-      listeners = ((_base = this._events)[event] || (_base[event] = []));
-      i = 0;
-      while (i < listeners.length) {
-        if (listeners[i] === fct) {
-          listeners[i] = void 0;
-        }
-        i++;
-      }
-      nextTick(function() {
-        var x;
-        return _this._events[event] = (function() {
-          var _i, _len, _ref, _results;
-          _ref = this._events[event];
-          _results = [];
-          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
-            x = _ref[_i];
-            if (x) {
-              _results.push(x);
-            }
-          }
-          return _results;
-        }).call(_this);
-      });
-      return this;
-    };
-
-    MicroEvent.prototype.emit = function() {
-      var args, event, fn, _i, _len, _ref, _ref1;
-      event = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
-      if (!((_ref = this._events) != null ? _ref[event] : void 0)) {
-        return this;
-      }
-      _ref1 = this._events[event];
-      for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
-        fn = _ref1[_i];
-        if (fn) {
-          fn.apply(this, args);
-        }
-      }
-      return this;
-    };
-
-    return MicroEvent;
-
-  })();
-
-  MicroEvent.mixin = function(obj) {
-    var proto;
-    proto = obj.prototype || obj;
-    proto.on = MicroEvent.prototype.on;
-    proto.removeListener = MicroEvent.prototype.removeListener;
-    proto.emit = MicroEvent.prototype.emit;
-    return obj;
-  };
-
-  if (typeof WEB === "undefined" || WEB === null) {
-    module.exports = MicroEvent;
-  }
-
-}).call(this);

+ 0 - 69
services/web/public/coffee/editor/sharejs/client/textarea.coffee

@@ -1,69 +0,0 @@
-# Create an op which converts oldval -> newval.
-#
-# This function should be called every time the text element is changed. Because changes are
-# always localised, the diffing is quite easy.
-#
-# This algorithm is O(N), but I suspect you could speed it up somehow using regular expressions.
-applyChange = (doc, oldval, newval) ->
-  return if oldval == newval
-  commonStart = 0
-  commonStart++ while oldval.charAt(commonStart) == newval.charAt(commonStart)
-
-  commonEnd = 0
-  commonEnd++ while oldval.charAt(oldval.length - 1 - commonEnd) == newval.charAt(newval.length - 1 - commonEnd) and
-    commonEnd + commonStart < oldval.length and commonEnd + commonStart < newval.length
-
-  doc.del commonStart, oldval.length - commonStart - commonEnd unless oldval.length == commonStart + commonEnd
-  doc.insert commonStart, newval[commonStart ... newval.length - commonEnd] unless newval.length == commonStart + commonEnd
-
-window.sharejs.extendDoc 'attach_textarea', (elem) ->
-  doc = this
-  elem.value = @getText()
-  prevvalue = elem.value
-
-  replaceText = (newText, transformCursor) ->
-    newSelection = [
-      transformCursor elem.selectionStart
-      transformCursor elem.selectionEnd
-    ]
-
-    scrollTop = elem.scrollTop
-    elem.value = newText
-    elem.scrollTop = scrollTop if elem.scrollTop != scrollTop
-    [elem.selectionStart, elem.selectionEnd] = newSelection
-
-  @on 'insert', (pos, text) ->
-    transformCursor = (cursor) ->
-      if pos < cursor
-        cursor + text.length
-      else
-        cursor
-    #for IE8 and Opera that replace \n with \r\n.
-    prevvalue = elem.value.replace /\r\n/g, '\n'
-    replaceText prevvalue[...pos] + text + prevvalue[pos..], transformCursor
-  
-  @on 'delete', (pos, text) ->
-    transformCursor = (cursor) ->
-      if pos < cursor
-        cursor - Math.min(text.length, cursor - pos)
-      else
-        cursor
-    #for IE8 and Opera that replace \n with \r\n.
-    prevvalue = elem.value.replace /\r\n/g, '\n'
-    replaceText prevvalue[...pos] + prevvalue[pos + text.length..], transformCursor
-
-  genOp = (event) ->
-    onNextTick = (fn) -> setTimeout fn, 0
-    onNextTick ->
-      if elem.value != prevvalue
-        # IE constantly replaces unix newlines with \r\n. ShareJS docs
-        # should only have unix newlines.
-        prevvalue = elem.value
-        applyChange doc, doc.getText(), elem.value.replace /\r\n/g, '\n'
-
-  for event in ['textInput', 'keydown', 'keyup', 'select', 'cut', 'paste']
-    if elem.addEventListener
-      elem.addEventListener event, genOp, false
-    else
-      elem.attachEvent 'on'+event, genOp
-

+ 0 - 13
services/web/public/coffee/editor/sharejs/client/web-prelude.coffee

@@ -1,13 +0,0 @@
-# This file is included at the top of the compiled client-side javascript
-
-# This way all the modules can add stuff to exports, and for the web client they'll all get exported.
-window.sharejs = exports =
-  'version': '0.5.0'
-
-# This is compiled out when compiled with uglifyjs, but its important for the share.uncompressed.js.
-#
-# Maybe I should rename WEB to __SHAREJS_WEB or something, but its only relevant for testing
-# anyway.
-if typeof WEB == 'undefined'
-  # This will put WEB in the global scope in a browser.
-  window.WEB = true

+ 0 - 5
services/web/public/coffee/editor/sharejs/index.coffee

@@ -1,5 +0,0 @@
-exports.server = require './server'
-exports.client = require './client'
-exports.types = require './types'
-
-exports.version = '0.5.0'

+ 0 - 333
services/web/public/coffee/editor/sharejs/server/browserchannel.coffee

@@ -1,333 +0,0 @@
-# This implements the network API for ShareJS.
-#
-# The wire protocol is speccced out here:
-# https://github.com/josephg/ShareJS/wiki/Wire-Protocol
-#
-# When a client connects the server first authenticates it and sends:
-#
-# S: {auth:<agent session id>}
-#  or
-# S: {auth:null, error:'forbidden'}
-#
-# After that, the client can open documents:
-#
-# C: {doc:'foo', open:true, snapshot:null, create:true, type:'text'}
-# S: {doc:'foo', open:true, snapshot:{snapshot:'hi there', v:5, meta:{}}, create:false}
-#
-# ...
-#
-# The client can send open requests as soon as the socket has opened - it doesn't need to
-# wait for auth.
-#
-# The wire protocol is documented here:
-# https://github.com/josephg/ShareJS/wiki/Wire-Protocol
-
-browserChannel = require('browserchannel').server
-util = require 'util'
-hat = require 'hat'
-
-syncQueue = require './syncqueue'
-
-# Attach the streaming protocol to the supplied http.Server.
-#
-# Options = {}
-module.exports = (createAgent, options) ->
-  options or= {}
-
-  browserChannel options, (session) ->
-    #console.log "New BC session from #{session.address} with id #{session.id}"
-    data =
-      headers: session.headers
-      remoteAddress: session.address
-
-    # This is the user agent through which a connecting client acts. It is set when the
-    # session is authenticated. The agent is responsible for making sure client requests are
-    # properly authorized, and metadata is kept up to date.
-    agent = null
-
-    # To save on network traffic, the agent & server can leave out the docName with each message to mean
-    # 'same as the last message'
-    lastSentDoc = null
-    lastReceivedDoc = null
-
-    # Map from docName -> {queue, listener if open}
-    docState = {}
-
-    # We'll only handle one message from each client at a time.
-    handleMessage = (query) ->
-      #console.log "Message from #{session.id}", query
-
-      error = null
-      error = 'Invalid docName' unless query.doc is null or typeof query.doc is 'string' or (query.doc is undefined and lastReceivedDoc)
-      error = "'create' must be true or missing" unless query.create in [true, undefined]
-      error = "'open' must be true, false or missing" unless query.open in [true, false, undefined]
-      error = "'snapshot' must be null or missing" unless query.snapshot in [null, undefined]
-      error = "'type' invalid" unless query.type is undefined or typeof query.type is 'string'
-      error = "'v' invalid" unless query.v is undefined or (typeof query.v is 'number' and query.v >= 0)
-      
-      if error
-        console.warn "Invalid query #{JSON.stringify query} from #{agent.sessionId}: #{error}"
-        session.abort()
-        return callback()
-
-      # The agent can specify null as the docName to get a random doc name.
-      if query.doc is null
-        query.doc = lastReceivedDoc = hat()
-      else if query.doc != undefined
-        lastReceivedDoc = query.doc
-      else
-        unless lastReceivedDoc
-          console.warn "msg.doc missing in query #{JSON.stringify query} from #{agent.sessionId}"
-          # The disconnect handler will be called when we do this, which will clean up the open docs.
-          return session.abort()
-
-        query.doc = lastReceivedDoc
-
-      docState[query.doc] or= queue: syncQueue (query, callback) ->
-        # When the session is closed, we'll nuke docState. When that happens, no more messages
-        # should be handled.
-        return callback() unless docState
-
-        # Close messages are {open:false}
-        if query.open == false
-          handleClose query, callback
-   
-        # Open messages are {open:true}. There's a lot of shared logic with getting snapshots
-        # and creating documents. These operations can be done together; and I'll handle them
-        # together.
-        else if query.open or query.snapshot is null or query.create
-          # You can open, request a snapshot and create all in the same
-          # request. They're all handled together.
-          handleOpenCreateSnapshot query, callback
-
-        # The socket is submitting an op.
-        else if query.op? or query.meta?.path?
-          handleOp query, callback
-
-        else
-          console.warn "Invalid query #{JSON.stringify query} from #{agent.sessionId}"
-          session.abort()
-          callback()
-
-      # ... And add the message to the queue.
-      docState[query.doc].queue query
-
-
-    # # Some utility methods for message handlers
-
-    # Send a message to the socket.
-    # msg _must_ have the doc:DOCNAME property set. We'll remove it if its the same as lastReceivedDoc.
-    send = (response) ->
-      if response.doc is lastSentDoc
-        delete response.doc
-      else
-        lastSentDoc = response.doc
-
-      # Its invalid to send a message to a closed session. We'll silently drop messages if the
-      # session has closed.
-      if session.state isnt 'closed'
-        #console.log "Sending", response
-        session.send response
-
-    # Open the given document name, at the requested version.
-    # callback(error, version)
-    open = (docName, version, callback) ->
-      return callback 'Session closed' unless docState
-      return callback 'Document already open' if docState[docName].listener
-      #p "Registering listener on #{docName} by #{socket.id} at #{version}"
-
-      docState[docName].listener = listener = (opData) ->
-        throw new Error 'Consistency violation - doc listener invalid' unless docState[docName].listener == listener
-
-        #p "listener doc:#{docName} opdata:#{i opData} v:#{version}"
-
-        # Skip the op if this socket sent it.
-        return if opData.meta.source is agent.sessionId
-
-        opMsg =
-          doc: docName
-          op: opData.op
-          v: opData.v
-          meta: opData.meta
-
-        send opMsg
-      
-      # Tell the socket the doc is open at the requested version
-      agent.listen docName, version, listener, (error, v) ->
-        delete docState[docName].listener if error
-        callback error, v
-
-    # Close the named document.
-    # callback([error])
-    close = (docName, callback) ->
-      #p "Closing #{docName}"
-      return callback 'Session closed' unless docState
-      listener = docState[docName].listener
-      return callback 'Doc already closed' unless listener?
-
-      agent.removeListener docName
-      delete docState[docName].listener
-      callback()
-
-    # Handles messages with any combination of the open:true, create:true and snapshot:null parameters
-    handleOpenCreateSnapshot = (query, finished) ->
-      docName = query.doc
-      msg = doc:docName
-
-      callback = (error) ->
-        if error
-          close(docName) if msg.open == true
-          msg.open = false if query.open == true
-          msg.snapshot = null if query.snapshot != undefined
-          delete msg.create
-
-          msg.error = error
-
-        send msg
-        finished()
-
-      return callback 'No docName specified' unless query.doc?
-
-      if query.create == true
-        if typeof query.type != 'string'
-          return callback 'create:true requires type specified'
-
-      if query.meta != undefined
-        unless typeof query.meta == 'object' and Array.isArray(query.meta) == false
-          return callback 'meta must be an object'
-
-      docData = undefined
-
-      # This is implemented with a series of cascading methods for each different type of
-      # thing this method can handle. This would be so much nicer with an async library. Welcome to
-      # callback hell.
-
-      step1Create = ->
-        return step2Snapshot() if query.create != true
-
-        # The document obviously already exists if we have a snapshot.
-        if docData
-          msg.create = false
-          step2Snapshot()
-        else
-          agent.create docName, query.type, query.meta || {}, (error) ->
-            if error is 'Document already exists'
-              # We've called getSnapshot (-> null), then create (-> already exists). Its possible
-              # another agent has called create() between our getSnapshot and create() calls.
-              agent.getSnapshot docName, (error, data) ->
-                return callback error if error
-
-                docData = data
-                msg.create = false
-                step2Snapshot()
-            else if error
-              callback error
-            else
-              msg.create = true
-              step2Snapshot()
-
-      # The socket requested a document snapshot
-      step2Snapshot = ->
-#        if query.create or query.open or query.snapshot == null
-#          msg.meta = docData.meta
-
-        # Skip inserting a snapshot if the document was just created.
-        if query.snapshot != null or msg.create == true
-          step3Open()
-          return
-
-        if docData
-          msg.v = docData.v
-          msg.type = docData.type.name unless query.type == docData.type.name
-          msg.snapshot = docData.snapshot
-        else
-          return callback 'Document does not exist'
-
-        step3Open()
-
-      # Attempt to open a document with a given name. Version is optional.
-      # callback(opened at version) or callback(null, errormessage)
-      step3Open = ->
-        return callback() if query.open != true
-
-        # Verify the type matches
-        return callback 'Type mismatch' if query.type and docData and query.type != docData.type.name
-
-        open docName, query.v, (error, version) ->
-          return callback error if error
-
-          # + Should fail if the type is wrong.
-
-          #p "Opened #{docName} at #{version} by #{socket.id}"
-          msg.open = true
-          msg.v = version
-          callback()
-
-      # Technically, we don't need a snapshot if the user called create but not open or createSnapshot,
-      # but no clients do that yet anyway.
-      if query.snapshot == null or query.open == true #and query.type
-        agent.getSnapshot query.doc, (error, data) ->
-          return callback error if error and error != 'Document does not exist'
-
-          docData = data
-          step1Create()
-      else
-        step1Create()
-
-    # The socket closes a document
-    handleClose = (query, callback) ->
-      close query.doc, (error) ->
-        if error
-          # An error closing still results in the doc being closed.
-          send {doc:query.doc, open:false, error:error}
-        else
-          send {doc:query.doc, open:false}
-
-        callback()
-
-    # We received an op from the socket
-    handleOp = (query, callback) ->
-      # ...
-      #throw new Error 'No version specified' unless query.v?
-
-      opData = {v:query.v, op:query.op, meta:query.meta, dupIfSource:query.dupIfSource}
-
-      # If it's a metaOp don't send a response
-      agent.submitOp query.doc, opData, if (not opData.op? and opData.meta?.path?) then callback else (error, appliedVersion) ->
-        msg = if error
-          #p "Sending error to socket: #{error}"
-          {doc:query.doc, v:null, error:error}
-        else
-          {doc:query.doc, v:appliedVersion}
-
-        send msg
-        callback()
-
-    # We don't process any messages from the agent until they've authorized. Instead,
-    # they are stored in this buffer.
-    buffer = []
-    session.on 'message', bufferMsg = (msg) -> buffer.push msg
-
-    createAgent data, (error, agent_) ->
-      if error
-        # The client is not authorized, so they shouldn't try and reconnect.
-        session.send {auth:null, error}
-        session.stop()
-      else
-        agent = agent_
-        session.send auth:agent.sessionId
-
-        # Ok. Now we can handle all the messages in the buffer. They'll go straight to
-        # handleMessage from now on.
-        session.removeListener 'message', bufferMsg
-        handleMessage msg for msg in buffer
-        buffer = null
-        session.on 'message', handleMessage
-
-    session.on 'close', ->
-      return unless agent
-      #console.log "Client #{agent.sessionId} disconnected"
-      for docName, {listener} of docState
-        agent.removeListener docName if listener
-      docState = null
-

+ 0 - 149
services/web/public/coffee/editor/sharejs/server/db/couchdb.coffee

@@ -1,149 +0,0 @@
-# OT storage for CouchDB
-# Author: Max Ogden (@maxogden)
-#
-# The couchdb database contains two kinds of documents:
-#
-# - Document snapshots have a key which is doc:the document name
-# - Document ops have a random key, but docName: defined.
-
-request = require('request').defaults json: true
-
-# Helper method to parse errors out of couchdb. There's way more ways
-# things can go wrong, but I think this catches all the ones I care about.
-#
-# callback(error) or callback()
-parseError = (err, resp, body, callback) ->
-  body = body[0] if Array.isArray body and body.length >= 1
-
-  if err
-    # This indicates an HTTP error
-    callback err
-  else if resp.statusCode is 404
-    callback 'Document does not exist'
-  else if resp.statusCode is 403
-    callback 'forbidden'
-  else if typeof body is 'object'
-    if body.error is 'conflict'
-      callback 'Document already exists'
-    else if body.error
-      callback "#{body.error} reason: #{body.reason}"
-    else
-      callback()
-  else
-    callback()
-
-module.exports = (options) ->
-  options ?= {}
-  db = options.uri or "http://localhost:5984/sharejs"
-
-  uriForDoc = (docName) -> "#{db}/doc:#{encodeURIComponent docName}"
-  uriForOps = (docName, start, end, include_docs) ->
-    startkey = encodeURIComponent(JSON.stringify [docName, start])
-    # {} is sorted after all numbers - so this will get all ops in the case that end is null.
-    endkey = encodeURIComponent(JSON.stringify [docName, end ? {}])
-
-    # Another way to write this method would be to use node's builtin uri-encoder.
-    extra = if include_docs then '&include_docs=true' else ''
-    "#{db}/_design/sharejs/_view/operations?startkey=#{startkey}&endkey=#{endkey}&inclusive_end=false#{extra}"
-
-  # Helper method to get the revision of a document snapshot.
-  getRev = (docName, dbMeta, callback) ->
-    if dbMeta?.rev
-      callback null, dbMeta.rev
-    else
-      # JSON defaults to true, and that makes request think I'm trying to sneak a request
-      # body in. Ugh.
-      request.head {uri:uriForDoc(docName), json:false}, (err, resp, body) ->
-        parseError err, resp, body, (error) ->
-          if error
-            callback error
-          else
-            # The etag is the rev in quotes.
-            callback null, JSON.parse(resp.headers.etag)
-  
-  writeSnapshotInternal = (docName, data, rev, callback) ->
-    body = data
-    body.fieldType = 'Document'
-    body._rev = rev if rev?
-
-    request.put uri:(uriForDoc docName), body:body, (err, resp, body) ->
-      parseError err, resp, body, (error) ->
-        if error
-          #console.log 'create error'
-          # This will send write conflicts as 'document already exists'. Thats kinda wierd, but
-          # it shouldn't happen anyway
-          callback? error
-        else
-          # We pass the document revision back to the db cache so it can give it back to couchdb on subsequent requests.
-          callback? null, {rev: body.rev}
-
-  # getOps returns all ops between start and end. end can be null.
-  getOps: (docName, start, end, callback) ->
-    return callback null, [] if start == end
-    
-    # Its a bit gross having this end parameter here....
-    endkey = if end? then [docName, end - 1]
-    
-    request uriForOps(docName, start, end), (err, resp, body) ->
-      # Rows look like this:
-      # {"id":"<uuid>","key":["doc name",0],"value":{"op":[{"p":0,"i":"hi"}],"meta":{}}}
-      data = ({op: row.value.op, meta: row.value.meta} for row in body.rows)
-      callback null, data
-  
-  # callback(error, db metadata)
-  create: (docName, data, callback) ->
-    writeSnapshotInternal docName, data, null, callback
- 
-  delete: del = (docName, dbMeta, callback) ->
-    getRev docName, dbMeta, (error, rev) ->
-      return callback? error if error
-
-      docs = [{_id:"doc:#{docName}", _rev:rev, _deleted:true}]
-      # Its annoying, but we need to get the revision from the document. I don't think there's a simple way to do this.
-      # This request will get all the ops twice.
-      request uriForOps(docName, 0, null, true), (err, resp, body) ->
-        # Rows look like this:
-        # {"id":"<uuid>","key":["doc name",0],"value":{"op":[{"p":0,"i":"hi"}],"meta":{}},
-        #  "doc":{"_id":"<uuid>","_rev":"1-21a40c56ebd5d424ffe56950e77bc847","op":[{"p":0,"i":"hi"}],"v":0,"meta":{},"docName":"doc6"}}
-        for row in body.rows
-          row.doc._deleted = true
-          docs.push row.doc
-
-        request.post url: "#{db}/_bulk_docs", body: {docs}, (err, resp, body) ->
-          if body[0].error is 'conflict'
-            # Somebody has edited the document since we did a GET on the revision information. Recurse.
-            # By passing null to dbMeta I'm forcing the revision information to be reacquired.
-            del docName, null, callback
-          else
-            parseError err, resp, body, (error) -> callback? error
- 
-  writeOp: (docName, opData, callback) ->
-    body =
-      docName: docName
-      op: opData.op
-      v: opData.v
-      meta: opData.meta
-
-    request.post url:db, body:body, (err, resp, body) ->
-      parseError err, resp, body, callback
-
-  writeSnapshot: (docName, docData, dbMeta, callback) ->
-    getRev docName, dbMeta, (error, rev) ->
-      return callback? error if error
-
-      writeSnapshotInternal docName, docData, rev, callback
-
-  getSnapshot: (docName, callback) ->
-    request uriForDoc(docName), (err, resp, body) ->
-      parseError err, resp, body, (error) ->
-        if error
-          callback error
-        else
-          callback null,
-              snapshot: body.snapshot
-              type: body.type
-              meta: body.meta
-              v: body.v
-            , {rev: body._rev} # dbMeta
-
-  close: ->

+ 0 - 28
services/web/public/coffee/editor/sharejs/server/db/index.coffee

@@ -1,28 +0,0 @@
-# This is a simple switch for the different database implementations.
-#
-# The interface is the same as the regular database implementations, except
-# the options object can have another type:<TYPE> parameter which specifies
-# which type of database to use.
-#
-# Example usage:
-#  require('server/db').create {type:'redis'}
-
-defaultType = 'redis'
-
-module.exports = (options) ->
-  options ?= {}
-  type = options.type ? defaultType
-
-  console.warn "Database type: 'memory' detected. This has been deprecated and will
- be removed in a future version. Use 'none' instead, or just remove the db:{} block
- from your options." if type is 'memory'
-
-  if type in ['none', 'memory']
-    null
-  else
-    Db = switch type
-      when 'redis' then require './redis'
-      when 'couchdb' then require './couchdb'
-      when 'pg' then require './pg'
-      else throw new Error "Invalid or unsupported database type: '#{type}'"
-    new Db options

+ 0 - 198
services/web/public/coffee/editor/sharejs/server/db/pg.coffee

@@ -1,198 +0,0 @@
-# This is an implementation of the OT data backend for PostgreSQL. It requires
-# that you have two tables defined in your schema: one for the snapshots
-# and one for the operations. You must also install the 'pg' package.
-#
-#
-# Example usage:
-#
-#     var connect = require('connect');
-#     var share   = require('share').server;
-#
-#     var server = connect(connect.logger());
-#
-#     var options = {
-#       db: {
-#         type: 'pg',
-#         uri:  'tcp://josh:@localhost/sharejs',
-#         create_tables_automatically: true
-#       }
-#     };
-#
-#     share.attach(server, options);
-#     server.listen(9000);
-#
-# You can run bin/setup_pg to create the SQL tables initially.
-
-pg = require('pg').native
-
-defaultOptions =
-  schema: 'sharejs'
-  create_tables_automatically: true
-  operations_table: 'ops'
-  snapshot_table: 'snapshots'
-
-module.exports = PgDb = (options) ->
-  return new Db if !(this instanceof PgDb)
-
-  options ?= {}
-  options[k] ?= v for k, v of defaultOptions
-
-  client = new pg.Client options.uri
-  client.connect()
-
-  snapshot_table = options.schema and "#{options.schema}.#{options.snapshot_table}" or options.snapshot_table
-  operations_table = options.schema and "#{options.schema}.#{options.operations_table}" or options.operations_table
-
-  @close = ->
-    client.end()
-
-  @initialize = (callback) ->
-    console.warn 'Creating postgresql database tables'
-
-    sql = """
-      CREATE SCHEMA #{options.schema};
-
-      CREATE TABLE #{snapshot_table} (
-        doc text NOT NULL,
-        v int4 NOT NULL,
-        type text NOT NULL,
-        snapshot text NOT NULL,
-        meta text NOT NULL,
-        created_at timestamp(6) NOT NULL,
-        CONSTRAINT snapshots_pkey PRIMARY KEY (doc, v)
-      );
-
-      CREATE TABLE #{operations_table} (
-        doc text NOT NULL,
-        v int4 NOT NULL,
-        op text NOT NULL,
-        meta text NOT NULL,
-        CONSTRAINT operations_pkey PRIMARY KEY (doc, v)
-      );
-    """
-    client.query sql, (error, result) ->
-      callback? error?.message
-
-  # This will perminantly delete all data in the database.
-  @dropTables = (callback) ->
-    sql = "DROP SCHEMA #{options.schema} CASCADE;"
-    client.query sql, (error, result) ->
-      callback? error.message
-
-  @create = (docName, docData, callback) ->
-    sql = """
-      INSERT INTO #{snapshot_table} ("doc", "v", "snapshot", "meta", "type", "created_at")
-        VALUES ($1, $2, $3, $4, $5, now())
-    """
-    values = [docName, docData.v, JSON.stringify(docData.snapshot), JSON.stringify(docData.meta), docData.type]
-    client.query sql, values, (error, result) ->
-      if !error?
-        callback?()
-      else if error.toString().match "duplicate key value violates unique constraint"
-        callback? "Document already exists"
-      else
-        callback? error?.message
-
-  @delete = (docName, dbMeta, callback) ->
-    sql = """
-      DELETE FROM #{operations_table}
-      WHERE "doc" = $1
-      RETURNING *
-    """
-    values = [docName]
-    client.query sql, values, (error, result) ->
-      if !error?
-        sql = """
-          DELETE FROM #{snapshot_table}
-          WHERE "doc" = $1
-          RETURNING *
-        """
-        client.query sql, values, (error, result) ->
-          if !error? and result.rows.length > 0
-            callback?()
-          else if !error?
-            callback? "Document does not exist"
-          else
-            callback? error?.message
-      else
-        callback? error?.message
-
-  @getSnapshot = (docName, callback) ->
-    sql = """
-      SELECT *
-      FROM #{snapshot_table}
-      WHERE "doc" = $1
-      ORDER BY "v" DESC
-      LIMIT 1
-    """
-    values = [docName]
-    client.query sql, values, (error, result) ->
-      if !error? and result.rows.length > 0
-        row = result.rows[0]
-        data =
-          v:        row.v
-          snapshot: JSON.parse(row.snapshot)
-          meta:     JSON.parse(row.meta)
-          type:     row.type
-        callback? null, data
-      else if !error?
-        callback? "Document does not exist"
-      else
-        callback? error?.message
-
-  @writeSnapshot = (docName, docData, dbMeta, callback) ->
-    sql = """
-      UPDATE #{snapshot_table}
-      SET "v" = $2, "snapshot" = $3, "meta" = $4
-      WHERE "doc" = $1
-    """
-    values = [docName, docData.v, JSON.stringify(docData.snapshot), JSON.stringify(docData.meta)]
-    client.query sql, values, (error, result) ->
-      if !error?
-        callback?()
-      else
-        callback? error?.message
-
-  @getOps = (docName, start, end, callback) ->
-    end = if end? then end - 1 else 2147483647
-    sql = """
-      SELECT *
-      FROM #{operations_table}
-      WHERE "v" BETWEEN $1 AND $2
-      AND "doc" = $3
-      ORDER BY "v" ASC
-    """
-    values = [start, end, docName]
-    client.query sql, values, (error, result) ->
-      if !error?
-        data = result.rows.map (row) ->
-          return {
-            op:   JSON.parse row.op
-            # v:    row.version
-            meta: JSON.parse row.meta
-          }
-        callback? null, data
-      else
-        callback? error?.message
-
-  @writeOp = (docName, opData, callback) ->
-    sql = """
-      INSERT INTO #{operations_table} ("doc", "op", "v", "meta")
-        VALUES ($1, $2, $3, $4)
-    """
-    values = [docName, JSON.stringify(opData.op), opData.v, JSON.stringify(opData.meta)]
-    client.query sql, values, (error, result) ->
-      if !error?
-        callback?()
-      else
-        callback? error?.message
-
-  # Immediately try and create the database tables if need be. Its possible that a query
-  # which happens immediately will happen before the database has been initialized.
-  #
-  # But, its not really a big problem.
-  if options.create_tables_automatically
-    client.query "SELECT * from #{snapshot_table} LIMIT 0", (error, result) =>
-      @initialize() if error?.message.match "does not exist"
-
-  this

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä