diff --git a/services/web/Gruntfile.coffee b/services/web/Gruntfile.coffee
index cb58399d80..9d7e8c3b14 100644
--- a/services/web/Gruntfile.coffee
+++ b/services/web/Gruntfile.coffee
@@ -37,15 +37,13 @@ module.exports = (grunt) ->
join: true
files:
"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:
@@ -89,12 +87,8 @@ module.exports = (grunt) ->
inlineText: false
preserveLicenseComments: false
paths:
- "underscore": "libs/underscore"
- "jquery": "libs/jquery"
- "moment": "libs/moment"
+ "moment": "libs/moment-2.7.0"
shim:
- "libs/backbone":
- deps: ["libs/underscore"]
"libs/pdfListView/PdfListView":
deps: ["libs/pdf"]
"libs/pdf":
@@ -104,16 +98,12 @@ module.exports = (grunt) ->
modules: [
{
name: "main",
- exclude: ["jquery"]
+ exclude: ["libs"]
}, {
name: "ide",
- exclude: ["jquery"]
+ exclude: ["libs", "libs/jquery-layout"]
}, {
- name: "home",
- exclude: ["jquery"]
- }, {
- name: "list",
- exclude: ["jquery"]
+ name: "libs"
}
]
diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade
index 581c1b8cd3..2fed8059bf 100644
--- a/services/web/app/views/layout.jade
+++ b/services/web/app/views/layout.jade
@@ -29,15 +29,10 @@ html(itemscope, itemtype='http://schema.org/Product')
script(type="text/javascript").
window.csrfToken = "#{csrfToken}";
-
- script(src=jsPath+'libs/jquery.js')
- script(src=jsPath+'libs/angular-1.2.17.js')
- script(src=jsPath+'libs/angular-sanitize1.2.17.js')
- script(src=jsPath+'libs/moment-2.4.0.js')
- script(src=jsPath+'libs/underscore-1.3.3.js')
- script(src=jsPath+'libs/algolia-2.5.2.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
- if(typeof(suppressNavbar) == "undefined")
@@ -64,10 +59,13 @@ html(itemscope, itemtype='http://schema.org/Product')
- if(typeof(suppressFooter) == "undefined")
script(type='text/javascript').
window.requirejs = {
- "urlArgs" : "fingerprint=#{fingerprint(jsPath + 'app/main.js')}"
+ "urlArgs" : "fingerprint=#{fingerprint(jsPath + 'app/main.js')}",
+ "paths" : {
+ "moment": "libs/moment-2.7.0"
+ }
};
script(
- data-main=jsPath+'app/main.js',
+ data-main=jsPath+'main.js',
baseurl=jsPath,
src=jsPath+'libs/require.js?fingerprint='+fingerprint(jsPath + 'libs/require.js')
)
diff --git a/services/web/app/views/project/editor.jade b/services/web/app/views/project/editor.jade
index 622ec76cab..39fa793b4f 100644
--- a/services/web/app/views/project/editor.jade
+++ b/services/web/app/views/project/editor.jade
@@ -5,13 +5,6 @@ block vars
- var suppressFooter = 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
.editor(ng-controller="IdeController")
.loading-screen(ng-show="state.loading")
@@ -92,19 +85,12 @@ block content
window.csrfToken = "!{csrfToken}";
window.requirejs = {
"paths" : {
- "underscore": "../libs/underscore-1.3.3",
"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')}",
"waitSeconds": 0,
"shim": {
- "libs/backbone": {
- deps: ["libs/underscore-1.3.3"]
- },
"libs/pdfListView/PdfListView": {
deps: ["libs/pdf"]
},
@@ -125,7 +111,7 @@ block content
window.sharelatex.pdfJsWorkerPath = "#{pdfJsWorkerPath}"
script(
- data-main=jsPath+'app/ide.js',
+ data-main=jsPath+'ide.js',
baseurl=jsPath,
data-ace-base=jsPath+'ace',
src=jsPath+'libs/require.js?fingerprint='+fingerprint(jsPath + 'libs/require.js')
diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade
index e75b5091fb..821e9e7ee6 100644
--- a/services/web/app/views/project/list.jade
+++ b/services/web/app/views/project/list.jade
@@ -1,6 +1,6 @@
extends ../layout
-block scripts
+block content
script(type="text/javascript").
window.data = {
projects: !{JSON.stringify(projects)},
@@ -13,7 +13,6 @@ block scripts
}
};
-block content
.content.content-alt(ng-controller="ProjectPageController")
.container
.row
diff --git a/services/web/public/coffee/SubscriptionGroupsManager.coffee b/services/web/public/coffee/SubscriptionGroupsManager.coffee
deleted file mode 100644
index 053eca14b6..0000000000
--- a/services/web/public/coffee/SubscriptionGroupsManager.coffee
+++ /dev/null
@@ -1,88 +0,0 @@
-require [
- "libs/mustache"
- "./main"
- "underscore"
-], (m)->
- $(document).ready ->
-
- tableRowTemplate = '''
-
- |
- {{ email }} |
- {{ first_name }} {{ last_name }} |
- {{ !holdingAccount }} |
-
-
- |
-
- '''
-
- 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 )
-
diff --git a/services/web/public/coffee/account/AccountManager.coffee b/services/web/public/coffee/account/AccountManager.coffee
deleted file mode 100644
index 5329d36de3..0000000000
--- a/services/web/public/coffee/account/AccountManager.coffee
+++ /dev/null
@@ -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
- }]
diff --git a/services/web/public/coffee/admin.coffee b/services/web/public/coffee/admin.coffee
deleted file mode 100644
index 364d179f9c..0000000000
--- a/services/web/public/coffee/admin.coffee
+++ /dev/null
@@ -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)->
diff --git a/services/web/public/coffee/analytics/AnalyticsManager.coffee b/services/web/public/coffee/analytics/AnalyticsManager.coffee
deleted file mode 100644
index e06cf264c3..0000000000
--- a/services/web/public/coffee/analytics/AnalyticsManager.coffee
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/services/web/public/coffee/app/ide.coffee b/services/web/public/coffee/app/ide.coffee
deleted file mode 100644
index 87b47abb49..0000000000
--- a/services/web/public/coffee/app/ide.coffee
+++ /dev/null
@@ -1,71 +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"
- "directives/scroll"
- "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"])
\ No newline at end of file
diff --git a/services/web/public/coffee/auto-complete/Snippets.coffee b/services/web/public/coffee/auto-complete/Snippets.coffee
deleted file mode 100644
index 53e0f57fa1..0000000000
--- a/services/web/public/coffee/auto-complete/Snippets.coffee
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/services/web/public/coffee/auto-complete/SuggestionManager.coffee b/services/web/public/coffee/auto-complete/SuggestionManager.coffee
deleted file mode 100644
index 559a2c5981..0000000000
--- a/services/web/public/coffee/auto-complete/SuggestionManager.coffee
+++ /dev/null
@@ -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
- }
-
diff --git a/services/web/public/coffee/app/base.coffee b/services/web/public/coffee/base.coffee
similarity index 67%
rename from services/web/public/coffee/app/base.coffee
rename to services/web/public/coffee/base.coffee
index 553f791704..95301da521 100644
--- a/services/web/public/coffee/app/base.coffee
+++ b/services/web/public/coffee/base.coffee
@@ -1,8 +1,6 @@
define [
- "../libs/angular-autocomplete/angular-autocomplete"
- "../libs/ui-bootstrap"
+ "libs"
"modules/recursionHelper"
- "../libs/ng-context-menu-0.1.4"
"utils/underscore"
], () ->
diff --git a/services/web/public/coffee/cursors/CursorManager.coffee b/services/web/public/coffee/cursors/CursorManager.coffee
deleted file mode 100644
index 9ba1879ccf..0000000000
--- a/services/web/public/coffee/cursors/CursorManager.coffee
+++ /dev/null
@@ -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 = """
-
-
-
#{$('
').text(clientData.name).html()}
-
- """
- 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]
-
diff --git a/services/web/public/coffee/debug/DebugManager.coffee b/services/web/public/coffee/debug/DebugManager.coffee
deleted file mode 100644
index 7776fdd000..0000000000
--- a/services/web/public/coffee/debug/DebugManager.coffee
+++ /dev/null
@@ -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:
-
- user-agent: #{useragent}
- server-id: #{server_id}
- transport: #{transport}
-
- """
- buttons: [
- text: "OK"
- ]
- )
\ No newline at end of file
diff --git a/services/web/public/coffee/app/directives/asyncForm.coffee b/services/web/public/coffee/directives/asyncForm.coffee
similarity index 100%
rename from services/web/public/coffee/app/directives/asyncForm.coffee
rename to services/web/public/coffee/directives/asyncForm.coffee
diff --git a/services/web/public/coffee/app/directives/equals.coffee b/services/web/public/coffee/directives/equals.coffee
similarity index 100%
rename from services/web/public/coffee/app/directives/equals.coffee
rename to services/web/public/coffee/directives/equals.coffee
diff --git a/services/web/public/coffee/app/directives/fineUpload.coffee b/services/web/public/coffee/directives/fineUpload.coffee
similarity index 98%
rename from services/web/public/coffee/app/directives/fineUpload.coffee
rename to services/web/public/coffee/directives/fineUpload.coffee
index f30219c5a0..b4572bf040 100644
--- a/services/web/public/coffee/app/directives/fineUpload.coffee
+++ b/services/web/public/coffee/directives/fineUpload.coffee
@@ -1,6 +1,5 @@
define [
"base"
- "../../libs/fineuploader"
], (App) ->
App.directive 'fineUpload', ($timeout) ->
console.log "7777777777"
diff --git a/services/web/public/coffee/app/directives/focus.coffee b/services/web/public/coffee/directives/focus.coffee
similarity index 100%
rename from services/web/public/coffee/app/directives/focus.coffee
rename to services/web/public/coffee/directives/focus.coffee
diff --git a/services/web/public/coffee/app/directives/onEnter.coffee b/services/web/public/coffee/directives/onEnter.coffee
similarity index 100%
rename from services/web/public/coffee/app/directives/onEnter.coffee
rename to services/web/public/coffee/directives/onEnter.coffee
diff --git a/services/web/public/coffee/app/directives/scroll.coffee b/services/web/public/coffee/directives/scroll.coffee
similarity index 100%
rename from services/web/public/coffee/app/directives/scroll.coffee
rename to services/web/public/coffee/directives/scroll.coffee
diff --git a/services/web/public/coffee/app/directives/selectAll.coffee b/services/web/public/coffee/directives/selectAll.coffee
similarity index 100%
rename from services/web/public/coffee/app/directives/selectAll.coffee
rename to services/web/public/coffee/directives/selectAll.coffee
diff --git a/services/web/public/coffee/app/directives/stopPropagation.coffee b/services/web/public/coffee/directives/stopPropagation.coffee
similarity index 100%
rename from services/web/public/coffee/app/directives/stopPropagation.coffee
rename to services/web/public/coffee/directives/stopPropagation.coffee
diff --git a/services/web/public/coffee/editor/AceUpdateManager.coffee b/services/web/public/coffee/editor/AceUpdateManager.coffee
deleted file mode 100644
index 5496206961..0000000000
--- a/services/web/public/coffee/editor/AceUpdateManager.coffee
+++ /dev/null
@@ -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
diff --git a/services/web/public/coffee/editor/Document.coffee b/services/web/public/coffee/editor/Document.coffee
deleted file mode 100644
index b0fec4d231..0000000000
--- a/services/web/public/coffee/editor/Document.coffee
+++ /dev/null
@@ -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
-
-
-
-
diff --git a/services/web/public/coffee/editor/Editor.coffee b/services/web/public/coffee/editor/Editor.coffee
deleted file mode 100644
index 62fa4daa28..0000000000
--- a/services/web/public/coffee/editor/Editor.coffee
+++ /dev/null
@@ -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()
diff --git a/services/web/public/coffee/editor/ShareJsDoc.coffee b/services/web/public/coffee/editor/ShareJsDoc.coffee
deleted file mode 100644
index f264fe6a9c..0000000000
--- a/services/web/public/coffee/editor/ShareJsDoc.coffee
+++ /dev/null
@@ -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
diff --git a/services/web/public/coffee/event_tracking.coffee b/services/web/public/coffee/event_tracking.coffee
deleted file mode 100644
index 546ad30bbe..0000000000
--- a/services/web/public/coffee/event_tracking.coffee
+++ /dev/null
@@ -1,71 +0,0 @@
-require [
-], ()->
- #plans page
- $('a.sign_up_now').on 'click', (e)->
- ga_PlanType = $(@).attr("ga_PlanType")
- ga 'send', 'event', 'subscription-funnel', 'sign_up_now_button', ga_PlanType
-
- $('#annual-pricing').on 'click', ->
- ga 'send', 'event', 'subscription-funnel', 'plans-page', 'annual-prices'
- $('#student-pricing').on 'click', ->
- ga('send', 'event', 'subscription-funnel', 'plans-page', 'student-prices')
-
- $('#plansLink').on 'click', ->
- ga 'send', 'event', 'subscription-funnel', 'go-to-plans-page', 'from menu bar'
-
-
- #list page
- $('#newProject a').on 'click', (e)->
- ga 'send', 'event', 'project-list-page-interaction', 'new-project', $(@).text().trim()
-
- $('#projectFilter').on 'keydown', (e)->
- ga 'send', 'event', 'project-list-page-interaction', 'project-search', 'keydown'
-
- $('#projectList .project-actions li a').on 'click', (e)->
- ga 'send', 'event', 'project-list-page-interaction', 'project action', $(@).text().trim()
-
-
-
- #left menu navigation
-
- $('.tab-link.account-settings-tab').on 'click', ->
- ga 'send', 'event', 'navigation', 'left menu bar', 'user settings link'
-
- $('.tab-link.subscription-tab').on 'click', ->
- ga 'send', 'event', 'navigation', 'left menu bar', 'subscription managment link'
-
-
-
- #menu bar navigation
-
- $('.userSettingsLink').on 'click', ->
- ga 'send', 'event', 'navigation', 'top menu bar', 'user settings link'
-
- $('.subscriptionLink').on 'click', ->
- ga 'send', 'event', 'navigation', 'top menu bar', 'subscription managment link'
-
- $('.logoutLink').on 'click', ->
- ga 'send', 'event', 'navigation', 'top menu bar', 'logout'
-
- $('#templatesLink').on 'click', ->
- ga 'send', 'event', 'navigation', 'top menu bar', 'templates'
-
- $('#blogLink').on 'click', ->
- ga 'send', 'event', 'navigation', 'top menu bar', 'blog'
-
- $('#learnLink').on 'click', ->
- ga 'send', 'event', 'navigation', 'top menu bar', 'learn link'
-
- $('#resourcesLink').on 'click', ->
- ga 'send', 'event', 'navigation', 'top menu bar', 'resources link'
-
- $('#aboutUsLink').on 'click', ->
- ga 'send', 'event', 'navigation', 'top menu bar', 'about us link'
-
- # editor
-
- $('#hotkeysLink').on 'click', ->
- ga 'send', 'event', 'navigation', 'editor', 'show hot keys link'
-
- $('#editorTourLink').on 'click', ->
- ga 'send', 'event', 'navigation', 'editor', 'editor tour link'
diff --git a/services/web/public/coffee/file-tree/DeletedDocsFolderView.coffee b/services/web/public/coffee/file-tree/DeletedDocsFolderView.coffee
deleted file mode 100644
index 48217e24c5..0000000000
--- a/services/web/public/coffee/file-tree/DeletedDocsFolderView.coffee
+++ /dev/null
@@ -1,33 +0,0 @@
-define [
- "file-tree/FolderView"
-], (FolderView) ->
- DeletedDocsFolderView = FolderView.extend
- template: $("#deletedDocsFolderTemplate").html()
-
- render: () ->
- @$el.append(Mustache.to_html @template, @model.attributes)
- @_bindToDomElements()
- @hideRenameBox()
- @hideToggle()
- @renderEntries()
- @showEntries()
- return @
-
- onClick: () ->
- e.preventDefault()
-
- onToggle: () ->
- e.preventDefault()
-
- getContextMenuEntries: () -> null
-
- hideToggle: () ->
- @$(".js-toggle").hide()
-
- makeReadOnly: () ->
-
- makeReadWrite: () ->
-
-
-
-
diff --git a/services/web/public/coffee/file-tree/DocView.coffee b/services/web/public/coffee/file-tree/DocView.coffee
deleted file mode 100644
index db360b9970..0000000000
--- a/services/web/public/coffee/file-tree/DocView.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-define [
- "file-tree/EntityView"
- "libs/mustache"
-], (EntityView) ->
- DocView = EntityView.extend
- onClick: (e) ->
- e.preventDefault()
- @options.manager.openDoc(@model)
-
diff --git a/services/web/public/coffee/file-tree/EntityView.coffee b/services/web/public/coffee/file-tree/EntityView.coffee
deleted file mode 100644
index b22f2910aa..0000000000
--- a/services/web/public/coffee/file-tree/EntityView.coffee
+++ /dev/null
@@ -1,179 +0,0 @@
-define [
- "utils/ContextMenu"
- "libs/backbone"
- "libs/mustache"
-], (ContextMenu) ->
- EntityView = Backbone.View.extend
- entityTemplate: $("#entityTemplate").html()
-
- initialize: () ->
- @ide = @options.manager.ide
- @manager = @options.manager
- @manager.registerView(@model.id, @)
- @bindToModel()
-
- events: () ->
- events = {}
- events["click ##{@model.id} > .js-clickable"] = "parentOnClick"
- events["click ##{@model.id} > .entity-label"] = "parentOnClick"
- events["click .dropdown-caret"] = "showContextMenuFromCaret"
- events["contextmenu"] = "showContextMenuFromRightClick"
- return events
-
- render: () ->
- @$el.append(Mustache.to_html @entityTemplate, @model.attributes)
- @_bindToDomElements()
- @_initializeRenameBox()
- @_initializeDrag()
- return @
-
- _bindToDomElements: () ->
- @$nameEl = @$(".name")
- @$inputEl = @$("input.js-rename")
- @$entityListItemEl = @$el.children(".entity-list-item")
- @$labelEl = @$entityListItemEl.children(".entity-label")
-
- bindToModel: () ->
- @model.on "change:name", (model) =>
- @$nameEl.text(model.get("name"))
-
- hideRenameBox: () ->
- @$nameEl.show()
- @$inputEl.hide()
-
- showRenameBox: () ->
- @$nameEl.hide()
- @$inputEl.show()
-
- setLabels: (labels) ->
- label = labels[@model.get("id")]
- if label?
- @$entityListItemEl.addClass("show-label")
- @$labelEl.text("±")
- return true
- else
- @$entityListItemEl.removeClass("show-label")
- @$labelEl.text("")
- return false
-
- select: () ->
- @selected = true
- @$entityListItemEl.addClass("selected")
-
- deselect: () ->
- @selected = false
- @$entityListItemEl.removeClass("selected")
-
- isSelected: () ->
- @selected
-
- parentOnClick: (e) ->
- doubleClickInterval = 600
- e.preventDefault()
- if @lastClick and new Date() - @lastClick < doubleClickInterval
- @onDoubleClick(e)
- else
- @lastClick = new Date()
- @onClick(e)
-
- onDoubleClick: (e) ->
- e.preventDefault()
- e.stopPropagation()
- if !@readonly
- @startRename()
-
- showContextMenuFromCaret: (e) ->
- e.stopPropagation()
- caret = @$(".dropdown-caret")
- offset = caret.offset()
- position =
- top: offset.top + caret.outerHeight()
- right: $(document.body).width() - (offset.left + caret.outerWidth())
- @toggleContextMenu(position)
-
- showContextMenuFromRightClick: (e) ->
- e.preventDefault()
- e.stopPropagation()
- position =
- left: e.pageX
- top: e.pageY
- @showContextMenu(position)
-
- toggleContextMenu: (position) ->
- if @contextMenu?
- @contextMenu.destroy()
- else
- @showContextMenu(position)
-
- showContextMenu: (position) ->
- entries = @getContextMenuEntries()
- return if !entries?
-
- @manager.trigger "contextmenu:beforeshow", @model, entries
-
- @contextMenu = new ContextMenu(position, entries)
- @contextMenu.on "destroy", () =>
- delete @contextMenu
-
- getContextMenuEntries: () ->
- return null if @readonly
- return [{
- text: "Rename"
- onClick: () =>
- @startRename()
- ga('send', 'event', 'editor-interaction', 'renameEntity', "entityView")
- }, {
- text: "Delete"
- onClick: () =>
- @manager.confirmDelete(@model)
- ga('send', 'event', 'editor-interaction', 'deleteEntity', "entityView")
- }]
-
- _initializeDrag: () ->
- @$entityListItemEl.draggable
- delay: 250
- opacity: 0.7
- helper: "clone"
- scroll: true
-
- _initializeRenameBox: () ->
- @$inputEl.click (e) -> e.stopPropagation() # Don't stop rename on click in input
- @$inputEl.keydown (event) =>
- code = event.keyCode || event.which
- if code == 13
- @_finishRename()
- @hideRenameBox()
-
- startRename: () ->
- if !@renaming
- @renaming = true
- @showRenameBox()
- name = @model.get("name")
- @$inputEl.val(name).focus()
- if @$inputEl[0].setSelectionRange?
- selectionEnd = name.lastIndexOf(".")
- if selectionEnd == -1
- selectionEnd = name.length
- @$inputEl[0].setSelectionRange(0, selectionEnd)
- setTimeout =>
- $(document.body).on "click.entity-rename", () =>
- @_finishRename()
- , 0
-
- _finishRename: () ->
- $(document.body).off "click.entity-rename"
- @renaming = false
- name = @$inputEl.val()
- @manager.renameEntity(@model, name)
- @hideRenameBox()
-
- makeReadOnly: () ->
- @$entityListItemEl.draggable("disable")
- @readonly = true
-
- makeReadWrite: () ->
- @$entityListItemEl.draggable("enable")
- delete @readonly
-
-
-
diff --git a/services/web/public/coffee/file-tree/FileTreeManager.coffee b/services/web/public/coffee/file-tree/FileTreeManager.coffee
deleted file mode 100644
index 27b7d82d2f..0000000000
--- a/services/web/public/coffee/file-tree/FileTreeManager.coffee
+++ /dev/null
@@ -1,329 +0,0 @@
-define [
- "models/Doc"
- "models/File"
- "models/Folder"
- "file-tree/FileTreeView"
- "file-tree/FolderView"
- "file-tree/DeletedDocsFolderView"
- "utils/Effects"
- "utils/Modal"
- "libs/backbone"
- "libs/jquery.storage"
-], (Doc, File, Folder, FileTreeView, FolderView, DeletedDocsFolderView, Effects, Modal) ->
- class FileTreeManager
- constructor: (@ide) ->
- _.extend(@, Backbone.Events)
- @views = {}
- @multiSelectedEntities = []
- @ide.on "afterJoinProject", (@project) =>
- @populateFileTree()
- @makeReadWriteIfAllowed()
- @project_id = @project.id
- if @ide.editor?.current_doc_id?
- @openDoc(@ide.editor.current_doc_id)
- else if location.hash.length > 1
- fileName = location.hash.slice(1)
- @openDocByPath(fileName)
- else if openDoc_id = $.localStorage("doc.open_id.#{@project_id}") and @getEntity(openDoc_id)
- @openDoc(openDoc_id)
- else if @project.get("rootDoc_id")?
- @openDoc(project.get("rootDoc_id"))
- else
- $('#settings').click()
- @view = new FileTreeView(@)
- @ide.sideBarView.addLink
- identifier: "file-tree"
- element: @view.$el
- prepend: true
- @view.render()
- @listenForUpdates()
-
- populateFileTree: () ->
- @view.bindToRootFolder(@project.get("rootFolder"))
-
- if @deletedDocsView?
- @deletedDocsView.$el.remove()
- @deletedDocsView = new DeletedDocsFolderView(model: @project.get("deletedDocs"), manager: @)
- @deletedDocsView.render()
- $("#sections").append(@deletedDocsView.$el)
- @hideDeletedDocs()
-
- listenForUpdates: () ->
- @ide.socket.on 'reciveNewDoc', (folder_id, doc) =>
- @addEntityToFolder(
- new Doc(id: doc._id, name: doc.name)
- folder_id
- )
-
- @ide.socket.on 'reciveNewFolder', (folder_id, folder) =>
- @addEntityToFolder(
- new Folder(id: folder._id, name: folder.name)
- folder_id
- )
-
- @ide.socket.on 'reciveNewFile', (folder_id, file) =>
- @addEntityToFolder(
- new File(id: file._id, name: file.name)
- folder_id
- )
-
- @ide.socket.on 'removeEntity', (entity_id) =>
- @onDeleteEntity(entity_id)
-
- @ide.socket.on 'reciveEntityRename', (entity_id, newName) =>
- @onRenameEntity(entity_id, newName)
-
- @ide.socket.on 'reciveEntityMove', (entity_id, folder_id) =>
- @onMoveEntity(entity_id, folder_id)
-
- registerView: (entity_id, view) ->
- @views[entity_id] = view
-
- addEntityToFolder: (entity, folder_id) ->
- folder = @views[folder_id].model
- children = folder.get("children")
- children.add(entity)
-
- openDoc: (doc, line) ->
- return if !doc?
- doc_id = doc.id or doc
- @trigger "open:doc", doc_id, line: line
- @selectEntity(doc_id)
- $.localStorage "doc.open_id.#{@project_id}", doc_id
-
- openDocByPath: (path, line) ->
- doc_id = @getDocIdOfPath(path)
- return null if !doc_id?
- @openDoc(doc_id, line)
-
- openFile: (file) ->
- @trigger "open:file", file
- @selectEntity(file.id)
-
- openFolder: (folder) ->
- @selectEntity(folder.id)
-
- selectEntity: (entity_id) ->
- if @views[@selected_entity_id]?
- @views[@selected_entity_id].deselect()
- @selected_entity_id = entity_id
- @ide.sideBarView.deselectAll()
- @views[entity_id]?.select()
-
- getEntity: (entity_id, options = {include_deleted: false}) ->
- model = @views[entity_id]?.model
- if !model? or (model.get("deleted") and !options.include_deleted)
- return
- else
- return model
-
- getSelectedEntity: () -> @getEntity(@selected_entity_id)
- getSelectedEntityId: () -> @getSelectedEntity()?.id
-
- getCurrentFolder: () ->
- selected_entity = @getSelectedEntity()
- if !selected_entity?
- return @project.get("rootFolder")
- else if selected_entity instanceof Folder
- return selected_entity
- else
- return selected_entity.collection.parentFolder
-
- getDocIdOfPath: (path) ->
- parts = path.split("/")
- folder = @project.get("rootFolder")
- lastPart = parts.pop()
-
- getChildWithName = (folder, name) ->
- return folder if name == "."
- foundChild = null
- for child in folder.get("children").models
- if child.get("name") == name
- foundChild = child
- return foundChild
-
- for part in parts
- folder = getChildWithName(folder, part)
- return null if !folder or !(folder instanceof Folder)
-
- doc = getChildWithName(folder, lastPart)
- return null if !doc or !(doc instanceof Doc)
- return doc.id
-
- getPathOfEntityId: (entity_id) ->
- entity = @getEntity(entity_id)
- return if !entity?
- path = entity.get("name")
- while (entity = entity.collection?.parentFolder)
- if entity.collection?
- # it's not the root folder so keep going
- path = entity.get("name") + "/" + path
- return path
-
- getRootFolderPath: () ->
- rootFilePath = @getPathOfEntityId(@project.get("rootDoc_id"))
- return rootFilePath.split("/").slice(0, -1).join("/")
-
- getNameOfEntityId: (entity_id) ->
- entity = @getEntity(entity_id)
- return if !entity?
- return entity.get("name")
-
- # RENAMING
- renameSelected: () ->
- entity_id = @getSelectedEntityId()
- return if !entity_id?
- @views[entity_id]?.startRename()
- ga('send', 'event', 'editor-interaction', 'renameEntity', "topMenu")
-
-
- renameEntity: (entity, name) ->
- name = name?.trim()
- @ide.socket.emit 'renameEntity', entity.id, entity.get("type"), name
- entity.set("name", name)
-
- onRenameEntity: (entity_id, name) ->
- @getEntity(entity_id)?.set("name", name)
-
- # MOVING
- onMoveEntity: (entity_id, folder_id) ->
- entity = @getEntity(entity_id)
- destFolder = @getEntity(folder_id)
- return if !entity? or !destFolder?
- if entity.collection == destFolder.get("children")
- # Already in parent folder
- return
- return if @_isParent(entity_id, folder_id)
-
- entity.collection.remove(entity)
- destFolder.get("children").add(entity)
-
- _isParent: (parent_folder_id, child_folder_id) ->
- childFolder = @getEntity(child_folder_id)
- return false unless childFolder? and childFolder instanceof Folder
- parentIds = childFolder.getParentFolderIds()
- if parentIds.indexOf(parent_folder_id) > -1
- return true
- else
- return false
-
- moveEntity: (entity_id, folder_id, type) ->
- return if @_isParent(entity_id, folder_id)
- @ide.socket.emit 'moveEntity', entity_id, folder_id, type
- @onMoveEntity(entity_id, folder_id)
-
- # CREATING
- showNewEntityModal: (type, defaultName, callback = (name) ->) ->
- el = $($("#newEntityModalTemplate").html())
- input = el.find("input")
- create = _.once () =>
- name = input.val()?.trim()
- if name != ""
- callback(name)
- modal = new Modal
- title: "New #{type}"
- el: el
- buttons: [{
- text: "Cancel"
- }, {
- text: "Create"
- callback: create
- class: "btn-primary"
- }]
- input.on "keydown", (e) ->
- if e.keyCode == 13 # Enter
- create()
- modal.remove()
-
- input.val(defaultName.replace("|", ""))
- if input[0].setSelectionRange?
- # value is "name.tex"
- input[0].setSelectionRange(0, defaultName.indexOf("|"))
-
- showNewDocModal: (parentFolder = @getCurrentFolder()) ->
- return if !parentFolder?
- @showNewEntityModal "Document", "name|.tex", (name) =>
- @addDocToFolder parentFolder, name
-
- showNewFolderModal: (parentFolder = @getCurrentFolder()) ->
- return if !parentFolder?
- @showNewEntityModal "Folder", "name|", (name) =>
- @addFolderToFolder parentFolder, name
-
- showUploadFileModal: (parentFolder = @getCurrentFolder()) ->
- return if !parentFolder?
- @ide.fileUploadManager.showUploadDialog parentFolder.id
-
- addDoc: (folder_id, name) ->
- @ide.socket.emit 'addDoc', folder_id, name
-
- addDocToFolder: (parentFolder, name) ->
- @addDoc parentFolder.id, name
-
- addFolder: (parent_folder_id, name) ->
- @ide.socket.emit 'addFolder', parent_folder_id, name
-
- addFolderToFolder: (parentFolder, name) ->
- return if !parentFolder?
- @addFolder parentFolder.id, name
-
- # DELETING
- confirmDelete: (entity) ->
- ga('send', 'event', 'editor-interaction', 'deleteEntity', "topMenu")
- Modal.createModal
- title: "Confirm Deletion"
- message: "Are you sure you want to delete #{entity.get("name")}?"
- buttons: [{
- text: "Cancel"
- class: "btn"
- },{
- text: "Delete"
- class: "btn btn-danger"
- callback: () => @_doDelete(entity)
- }]
-
- confirmDeleteOfSelectedEntity: () ->
- entity = @getSelectedEntity()
- return if !entity?
- @confirmDelete(entity)
-
- _doDelete: (entity) ->
- @ide.socket.emit 'deleteEntity', entity.id, entity.get("type")
- @onDeleteEntity entity.id
-
- onDeleteEntity: (entity_id) ->
- entity = @getEntity(entity_id)
- return if !entity?
- entity.set("deleted", true)
- entity.collection?.remove(entity)
- delete @views[entity_id]
-
- # Do this after the remove so that it's never in two places at once
- # and so that it doesn't get reset by deleting from @views
- if entity.get("type") == "doc"
- @project.get("deletedDocs").get("children").add entity
-
- setLabels: (labels) ->
- @view.setLabels(labels)
- @deletedDocsView.setLabels(labels)
-
- showDeletedDocs: () ->
- if @project.get("deletedDocs").get("children").length > 0
- @deletedDocsView.$el.show()
-
- hideDeletedDocs: () ->
- @deletedDocsView.$el.hide()
-
- makeReadOnly: () ->
- for id, view of @views or []
- view.makeReadOnly?()
-
- makeReadWrite: () ->
- for id, view of @views or []
- view.makeReadWrite?()
-
- makeReadWriteIfAllowed: () ->
- if @ide.isAllowedToDoIt("readAndWrite")
- @makeReadWrite()
- else
- @makeReadOnly()
diff --git a/services/web/public/coffee/file-tree/FileTreeView.coffee b/services/web/public/coffee/file-tree/FileTreeView.coffee
deleted file mode 100644
index d06dfe52d4..0000000000
--- a/services/web/public/coffee/file-tree/FileTreeView.coffee
+++ /dev/null
@@ -1,25 +0,0 @@
-define [
- "file-tree/RootFolderView"
- "libs/backbone"
-], (RootFolderView) ->
- FileTreeView = Backbone.View.extend
- initialize: (@manager) ->
-
- template: $("#fileTreeTemplate").html()
-
- render: () ->
- @$el.append($(@template))
- return @
-
- bindToRootFolder: (rootFolder) ->
- entities = @$('.js-file-tree')
- # This is hacky, we're doing nothing to clean up the old folder tree
- # from memory, just removing it from the DOM.
- entities.empty()
- @rootFolderView = new RootFolderView(model: rootFolder, manager: @manager)
- entities.append(@rootFolderView.$el)
- @rootFolderView.render()
-
- setLabels: (labels) ->
- @rootFolderView.setLabels(labels)
-
diff --git a/services/web/public/coffee/file-tree/FileView.coffee b/services/web/public/coffee/file-tree/FileView.coffee
deleted file mode 100644
index 42078691d6..0000000000
--- a/services/web/public/coffee/file-tree/FileView.coffee
+++ /dev/null
@@ -1,8 +0,0 @@
-define [
- "file-tree/EntityView"
- "libs/mustache"
-], (EntityView) ->
- FileView = EntityView.extend
- onClick: (e) ->
- e.preventDefault()
- @options.manager.openFile(@model)
diff --git a/services/web/public/coffee/file-tree/FolderView.coffee b/services/web/public/coffee/file-tree/FolderView.coffee
deleted file mode 100644
index e62140a538..0000000000
--- a/services/web/public/coffee/file-tree/FolderView.coffee
+++ /dev/null
@@ -1,165 +0,0 @@
-define [
- "models/Folder"
- "models/Doc"
- "models/File"
- "file-tree/EntityView"
- "file-tree/DocView"
- "file-tree/FileView"
- "utils/Modal"
- "utils/Effects"
- "libs/mustache"
-], (Folder, Doc, File, EntityView, DocView, FileView, Modal, Effects) ->
- FolderView = EntityView.extend
- templates:
- childList: $("#entityListTemplate").html()
-
- entityTemplate: $("#folderTemplate").html()
-
- events: () ->
- events = EntityView::events.apply(this)
- events["click ##{@model.id} > .js-toggle"] = "onToggle"
- return events
-
- render: () ->
- EntityView::render.apply(this, arguments)
- @renderEntries()
- return @
-
- renderEntries: () ->
- @$el.append(Mustache.to_html @templates.childList, @model.attributes)
- @$contents = @$(".contents")
- @$childList = @$(".entity-list")
- @$menu = @$(".js-new-entity-menu")
- @$deleteButton = @$(".js-delete-btn")
- @$toggle = @$entityListItemEl.children(".js-toggle")
- @_renderChildViews()
- @_initializeDrop()
- @hideEntries()
-
- _renderChildViews: () ->
- throw "Already rendered children" unless !@views?
- @views = []
- @model.get("children").each (child) =>
- view = @_buildViewForModel(child)
- @views.push view
- @$childList.append(view.$el)
- view.render()
- @bindToCollection()
-
- renderNewEntry: (model, index) ->
- view = @_buildViewForModel(model)
- @views.splice(index, 0, view)
- if index == 0
- @$childList.prepend(view.$el)
- else
- view.$el.insertAfter(@views[index-1].$el)
- view.render()
- Effects.fadeElementIn view.$el
-
- removeEntry: (model, index) ->
- view = @views[index]
- @views.splice(index,1)
- if model.get("deleted")
- Effects.fadeElementOut view.$el, () ->
- view.remove()
- else
- view.remove()
-
- _buildViewForModel: (model) ->
- attrs = model: model, manager: @options.manager
- if model instanceof Folder
- view = new FolderView(attrs)
- else if model instanceof Doc
- view = new DocView(attrs)
- else
- view = new FileView(attrs)
- return view
-
- _initializeDrop: () ->
- onDrop = (event, ui) =>
- if event.target == @$childList[0] or event.target == @$entityListItemEl[0]
- entity = ui.draggable
- entity_id = entity.attr("id")
- entity_type = entity.attr("entity-type")
- @manager.moveEntity entity_id, @model.id, entity_type
-
- @$entityListItemEl.droppable
- greedy: true
- hoverClass: "droppable-folder-hover"
- drop: onDrop
-
- @$childList.droppable
- greedy: true
- hoverClass: "droppable-folder-hover"
- drop: onDrop
-
- bindToCollection: () ->
- @model.get("children").on "add", (model, folderCollection, data) =>
- @renderNewEntry(model, data.index)
- @model.get("children").on "remove", (model, folderCollection, data) =>
- @removeEntry(model, data.index)
-
- onClick: (e) ->
- e.preventDefault()
- @options.manager.openFolder(@model)
-
- hideEntries: () ->
- @$contents.hide()
- @$toggle.find(".js-open").hide()
- @$toggle.find(".js-closed").show()
- @$entityListItemEl.removeClass("folder-open")
-
- showEntries: () ->
- @$contents.show()
- @$toggle.find(".js-open").show()
- @$toggle.find(".js-closed").hide()
- @$entityListItemEl.addClass("folder-open")
-
- onToggle: (e) ->
- e.preventDefault()
- if @$contents.is(":visible")
- @hideEntries()
- else
- @showEntries()
-
- getContextMenuEntries: (args...) ->
- return null if @readonly
- entries = EntityView::getContextMenuEntries.apply(this, args)
- entries.push {
- divider: true
- }
- entries.push @getFolderContextMenuEntries()...
- return entries
-
- getFolderContextMenuEntries: () ->
- return [{
- text: "New file"
- onClick: () =>
- ga('send', 'event', 'editor-interaction', 'newFile', "folderView")
- @manager.showNewDocModal(@model)
- }, {
- text: "New folder"
- onClick: () =>
- ga('send', 'event', 'editor-interaction', 'newFolder', "folderView")
- @manager.showNewFolderModal(@model)
- }, {
- text: "Upload file"
- onClick: () =>
- ga('send', 'event', 'editor-interaction', 'uploadFile', "folderView")
- @manager.showUploadFileModal(@model)
- }]
-
- setLabels: (labels) ->
- showLabel = false
- for entity in @views
- if entity.setLabels(labels)
- showLabel = true
-
- if showLabel
- @$entityListItemEl.addClass("show-label")
- @$labelEl.text("±")
- return true
- else
- @$entityListItemEl.removeClass("show-label")
- @$labelEl.text("")
- return false
diff --git a/services/web/public/coffee/file-tree/RootFolderView.coffee b/services/web/public/coffee/file-tree/RootFolderView.coffee
deleted file mode 100644
index ff5c1c20c3..0000000000
--- a/services/web/public/coffee/file-tree/RootFolderView.coffee
+++ /dev/null
@@ -1,71 +0,0 @@
-define [
- "file-tree/FolderView"
-], (FolderView) ->
- RootFolderView = FolderView.extend
- actionsTemplate: $("#fileTreeActionsTemplate").html()
-
- events: () ->
- events = FolderView::events.apply(this)
- if @ide.isAllowedToDoIt("readAndWrite")
- _.extend(events,
- "click .js-new-file" : (e) ->
- e.preventDefault()
- @manager.showNewDocModal()
- ga('send', 'event', 'editor-interaction', 'newFile', "topMenu")
- "click .js-new-folder" : (e) ->
- e.preventDefault()
- @manager.showNewFolderModal()
- ga('send', 'event', 'editor-interaction', 'newFolder', "topMenu")
- "click .js-upload-file" : (e) ->
- e.preventDefault()
- @manager.showUploadFileModal()
- ga('send', 'event', 'editor-interaction', 'uploadFile', "topMenu")
- "click .js-delete-btn" : (e) ->
- e.preventDefault()
- @manager.confirmDeleteOfSelectedEntity()
- ga('send', 'event', 'editor-interaction', 'deleteEntity', "topMenu")
- "click .js-rename-btn" : (e) ->
- e.preventDefault()
- @manager.renameSelected()
- ga('send', 'event', 'editor-interaction', 'renameEntity', "topMenu")
- )
-
- render: () ->
- @$el.append(Mustache.to_html @entityTemplate, {
- name: @manager.project.get("name")
- type: "project"
- })
- @_bindToDomElements()
- @renderActions()
- @hideRenameBox()
- @hideToggle()
- @renderEntries()
- @showEntries()
- return @
-
- renderActions: () ->
- @$actions = $(@actionsTemplate)
- @$actions.insertAfter(@$entityListItemEl)
- @$(".js-new-entity-menu > a").dropdown()
-
- onClick: () ->
- e.preventDefault()
-
- onToggle: () ->
- e.preventDefault()
-
- getContextMenuEntries: () ->
- @getFolderContextMenuEntries()
-
- hideToggle: () ->
- @$(".js-toggle").hide()
-
- makeReadOnly: () ->
- @$actions.hide()
-
- makeReadWrite: () ->
- @$actions.show()
-
-
-
-
diff --git a/services/web/public/coffee/file-view/FileView.coffee b/services/web/public/coffee/file-view/FileView.coffee
deleted file mode 100644
index 51d1e33959..0000000000
--- a/services/web/public/coffee/file-view/FileView.coffee
+++ /dev/null
@@ -1,29 +0,0 @@
-define [
- "libs/backbone"
- "libs/mustache"
-], () ->
- FileViewManager = Backbone.View.extend
- template: $("#fileViewTemplate").html()
- className: "fullEditorArea"
- id: "fileViewArea"
-
- render: () ->
- extension = @model.get("name").split(".").pop().toLowerCase()
- image = (["jpg", "jpeg", "png", "gif", "eps", "pdf"].indexOf(extension) != -1)
- html = Mustache.to_html(@template, {
- name: @model.get("name")
- downloadUrl: @model.downloadUrl()
- previewUrl: @model.previewUrl()
- image: image
- })
- @$el.html(html)
-
- setModel: (model) ->
- @model = model
- @render()
- @onResize()
-
- onResize: () ->
- @$("img").css
- "max-width": ($("#fileViewArea").width() - 40) + "px"
- "max-height": ($("#fileViewArea").height() - 140) + "px"
diff --git a/services/web/public/coffee/file-view/FileViewManager.coffee b/services/web/public/coffee/file-view/FileViewManager.coffee
deleted file mode 100644
index 4f414493b9..0000000000
--- a/services/web/public/coffee/file-view/FileViewManager.coffee
+++ /dev/null
@@ -1,32 +0,0 @@
-define [
- "file-view/FileView"
-], (FileView) ->
- class FileViewManager
- constructor: (@ide) ->
- @view = new FileView()
-
- @ide.mainAreaManager.addArea
- identifier: "file"
- element: @view.$el
-
- $(window).resize () => @view.onResize()
- @ide.layoutManager.on "resize", () => @view.onResize()
- @view.onResize()
-
- @bindToFileTreeEvents()
- @enable()
-
- bindToFileTreeEvents: () ->
- @ide.fileTreeManager.on "open:file", (file) =>
- if @enabled
- @showFile(file)
-
- showFile: (file) ->
- @ide.mainAreaManager.change('file')
- @view.setModel(file)
-
- enable: () ->
- @enabled = true
-
- disable: () ->
- @enabled = false
diff --git a/services/web/public/coffee/app/filters/formatDate.coffee b/services/web/public/coffee/filters/formatDate.coffee
similarity index 100%
rename from services/web/public/coffee/app/filters/formatDate.coffee
rename to services/web/public/coffee/filters/formatDate.coffee
diff --git a/services/web/public/coffee/help/HelpManager.coffee b/services/web/public/coffee/help/HelpManager.coffee
deleted file mode 100644
index 07416f81fe..0000000000
--- a/services/web/public/coffee/help/HelpManager.coffee
+++ /dev/null
@@ -1,13 +0,0 @@
-define [], () ->
- class HelpManager
- template: $("#helpLinkTemplate").html()
-
- constructor: (@ide) ->
- @$el = $(@template)
- $("#toolbar-footer").append(@$el)
- @$el.on "click", (e) ->
- e.preventDefault()
- window.open("/learn", "_latex_help")
-
-
-
diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee
index 7b8961901e..8b5913a18e 100644
--- a/services/web/public/coffee/ide.coffee
+++ b/services/web/public/coffee/ide.coffee
@@ -1,210 +1,71 @@
define [
- "ide/ConnectionManager"
- "history/HistoryManager"
- "auto-complete/AutoCompleteManager"
- "project-members/ProjectMembersManager"
- "settings/SettingsManager"
- "editor/Editor"
- "pdf/PdfManager"
- "ide/MainAreaManager"
- "ide/SideBarManager"
- "ide/TabManager"
- "ide/LayoutManager"
- "ide/FileUploadManager"
- "ide/SavingAreaManager"
- "spelling/SpellingManager"
- "search/SearchManager"
- "models/Project"
- "models/User"
- "utils/Modal"
- "file-tree/FileTreeManager"
- "messages/MessageManager"
- "help/HelpManager"
- "cursors/CursorManager"
- "keys/HotkeysManager"
- "keys/BackspaceHighjack"
- "file-view/FileViewManager"
- "tour/IdeTour"
- "analytics/AnalyticsManager"
- "track-changes/TrackChangesManager"
- "debug/DebugManager"
- "ace/ace"
- "libs/jquery.color"
- "libs/jquery-layout"
- "libs/backbone"
+ "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/scroll"
+ "directives/onEnter"
+ "filters/formatDate"
], (
- ConnectionManager,
- HistoryManager,
- AutoCompleteManager,
- ProjectMembers,
- SettingsManager,
- Editor,
- PdfManager,
- MainAreaManager,
- SideBarManager,
- TabManager,
- LayoutManager,
- FileUploadManager,
- SavingAreaManager,
- SpellingManager,
- SearchManager,
- Project,
- User,
- Modal,
- FileTreeManager,
- MessageManager,
- HelpManager,
- CursorManager,
- HotkeysManager,
- BackspaceHighjack,
- FileViewManager,
- IdeTour,
- AnalyticsManager,
+ App
+ FileTreeManager
+ ConnectionManager
+ EditorManager
+ OnlineUsersManager
TrackChangesManager
- DebugManager
+ PermissionsManager
+ PdfManager
+ BinaryFilesManager
) ->
-
-
-
- ProjectMembersManager = ProjectMembers.ProjectMembersManager
-
- mainAreaManager = undefined
- socket = undefined
- currentDoc_id = undefined
- selectElement = undefined
- security = undefined
- _.templateSettings =
- interpolate : /\{\{(.+?)\}\}/g
-
- isAllowedToDoIt = (permissionsLevel)->
-
- if permissionsLevel == "owner" && _.include ["owner"], security.permissionsLevel
- return true
- else if permissionsLevel == "readAndWrite" && _.include ["readAndWrite", "owner"], security.permissionsLevel
- return true
- else if permissionsLevel == "readOnly" && _.include ["readOnly", "readAndWrite", "owner"], security.permissionsLevel
- return true
- else
- return false
-
- Ide = class Ide
- constructor: () ->
- @userSettings = window.userSettings
- @project_id = @userSettings.project_id
-
- @user = User.findOrBuild window.user.id, window.user
-
- ide = this
- @isAllowedToDoIt = isAllowedToDoIt
-
- ioOptions =
- reconnect: false
- "force new connection": true
- @socket = socket = io.connect null, ioOptions
-
- @messageManager = new MessageManager(@)
- @connectionManager = new ConnectionManager(@)
- @tabManager = new TabManager(@)
- @layoutManager = new LayoutManager(@)
- @sideBarView = new SideBarManager(@, $("#sections"))
- selectElement = @sideBarView.selectElement
- mainAreaManager = @mainAreaManager = new MainAreaManager(@, $("#content"))
- @fileTreeManager = new FileTreeManager(@)
- @editor = new Editor(@)
- @pdfManager = new PdfManager(@)
- if @userSettings.autoComplete
- @autoCompleteManager = new AutoCompleteManager(@)
- @spellingManager = new SpellingManager(@)
- @fileUploadManager = new FileUploadManager(@)
- @searchManager = new SearchManager(@)
- @cursorManager = new CursorManager(@)
- @fileViewManager = new FileViewManager(@)
- @analyticsManager = new AnalyticsManager(@)
- if @userSettings.oldHistory
- @historyManager = new HistoryManager(@)
+ 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
- @trackChangesManager = new TrackChangesManager(@)
+ this.$originalApply(fn);
- @setLoadingMessage("Connecting")
- firstConnect = true
- socket.on "connect", () =>
- @setLoadingMessage("Joining project")
- joinProject = () =>
- socket.emit 'joinProject', {project_id: @project_id}, (err, project, permissionsLevel, protocolVersion) =>
- @hideLoadingScreen()
- if @protocolVersion? and @protocolVersion != protocolVersion
- location.reload(true)
- @protocolVersion = protocolVersion
- Security = {}
- Security.permissionsLevel = permissionsLevel
- @security = security = Object.freeze(Security)
- @project = new Project project, parse: true
- @project.set("ide", ide)
- ide.trigger "afterJoinProject", @project
+ $scope.state = {
+ loading: true
+ load_progress: 40
+ }
+ $scope.ui = {
+ leftMenuShown: false
+ view: "editor"
+ chatOpen: false
+ }
+ $scope.user = window.user
+ $scope.settings = window.userSettings
- if firstConnect
- @pdfManager.refreshPdf(isAutoCompile:true)
- firstConnect = false
+ $scope.chat = {}
- setTimeout(joinProject, 100)
-
- showErrorModal: (title, message)->
- new Modal {
- title: title
- message: message
- buttons: [ text: "OK" ]
- }
+ window._ide = ide
- showGenericServerErrorMessage: ()->
- new Modal {
- title: "There was a problem talking to the server"
- message: "Sorry, we couldn't complete your request right now. Please wait a few moments and try again. If the problem persists, please let us know."
- buttons: [ text: "OK" ]
- }
+ ide.project_id = $scope.project_id = window.project_id
+ ide.$scope = $scope
- recentEvents: []
-
- pushEvent: (type, meta = {}) ->
- @recentEvents.push type: type, meta: meta, date: new Date()
- if @recentEvents.length > 40
- @recentEvents.shift()
-
- reportError: (error, meta = {}) ->
- meta.client_id = @socket?.socket?.sessionid
- meta.transport = @socket?.socket?.transport?.name
- meta.client_now = new Date()
- meta.recent_events = @recentEvents
- errorObj = {}
- if typeof error == "object"
- for key in Object.getOwnPropertyNames(error)
- errorObj[key] = error[key]
- else if typeof error == "string"
- errorObj.message = error
- $.ajax
- url: "/error/client"
- type: "POST"
- data: JSON.stringify
- error: errorObj
- meta: meta
- contentType: "application/json; charset=utf-8"
- headers:
- "X-Csrf-Token": window.csrfToken
-
- setLoadingMessage: (message) ->
- $("#loadingMessage").text(message)
-
- hideLoadingScreen: () ->
- $("#loadingScreen").remove()
-
- _.extend(Ide::, Backbone.Events)
- window.ide = ide = new Ide()
- ide.projectMembersManager = new ProjectMembersManager ide
- ide.settingsManager = new SettingsManager ide
- ide.helpManager = new HelpManager ide
- ide.hotkeysManager = new HotkeysManager ide
- ide.layoutManager.resizeAllSplitters()
- ide.tourManager = new IdeTour ide
- ide.debugManager = new DebugManager(ide)
-
- ide.savingAreaManager = new SavingAreaManager(ide)
+ 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"])
\ No newline at end of file
diff --git a/services/web/public/coffee/ide/ConnectionManager.coffee b/services/web/public/coffee/ide/ConnectionManager.coffee
deleted file mode 100644
index ae54c7946b..0000000000
--- a/services/web/public/coffee/ide/ConnectionManager.coffee
+++ /dev/null
@@ -1,87 +0,0 @@
-define [
- "utils/Modal"
- "libs/backbone"
-], (Modal) ->
- class ConnectionManager
- constructor: (@ide) ->
- @connected = false
- @socket = @ide.socket
- @socket.on "connect", () =>
- @connected = true
- @ide.pushEvent("connected")
- @hideModal()
- @cancelReconnect()
-
- @socket.on 'disconnect', () =>
- @connected = false
- @ide.pushEvent("disconnected")
- @ide.trigger "disconnect"
- setTimeout(=>
- ga('send', 'event', 'editor-interaction', 'disconnect')
- , 2000)
-
- if !@forcedDisconnect
- @showModalAndStartAutoReconnect()
-
- @socket.on 'forceDisconnect', (message) =>
- @showModal(message)
- @forcedDisconnect = true
- @socket.disconnect()
-
- @messageEl = $("#connectionLostMessage")
- $('#try-reconnect-now').on 'click', (e) =>
- e.preventDefault()
- @tryReconnect()
- @hideModal()
-
- reconnectImmediately: () ->
- @disconnect()
- @tryReconnect()
-
- disconnect: () ->
- @socket.disconnect()
-
- showModalAndStartAutoReconnect: () ->
- @hideModal()
- twoMinutes = 2 * 60 * 1000
- if @ide.editor? and @ide.editor.lastUpdated? and new Date() - @ide.editor.lastUpdated > twoMinutes
- # between 1 minute and 3 minutes
- @countdown = 60 + Math.floor(Math.random() * 120)
- else
- @countdown = 3 + Math.floor(Math.random() * 7)
-
- $("#reconnection-countdown").text(@countdown)
-
- setTimeout(=>
- if !@connected
- @showModal()
- @timeoutId = setTimeout (=> @decreaseCountdown()), 1000
- , 200)
-
- showModal: () =>
- @messageEl.show()
- $("#reconnecting").hide()
- $("#trying-reconnect").show()
-
- hideModal: () ->
- @messageEl.hide()
-
- cancelReconnect: () ->
- clearTimeout @timeoutId if @timeoutId?
-
- decreaseCountdown: () ->
- @countdown--
- $("#reconnection-countdown").text(@countdown)
-
- if @countdown <= 0
- @tryReconnect()
- else
- @timeoutId = setTimeout (=> @decreaseCountdown()), 1000
-
- tryReconnect: () ->
- @cancelReconnect()
- $("#reconnecting").show()
- $("#trying-reconnect").hide()
- @socket.socket.reconnect()
- setTimeout (=> @showModalAndStartAutoReconnect() if !@connected), 1000
-
diff --git a/services/web/public/coffee/ide/FileUploadManager.coffee b/services/web/public/coffee/ide/FileUploadManager.coffee
deleted file mode 100644
index f8f2fd16b2..0000000000
--- a/services/web/public/coffee/ide/FileUploadManager.coffee
+++ /dev/null
@@ -1,63 +0,0 @@
-define [
- "utils/Modal"
- "libs/fineuploader"
-], (Modal) ->
- class FileUploadManager
- constructor: (@ide) ->
- @ide.on "afterJoinProject", () =>
- if @ide.isAllowedToDoIt "readAndWrite"
- $('button#upload-file').click (e)=>
- @showUploadDialog()
-
- showUploadDialog: (folder_id, callback = (error, entity_ids) ->) ->
- uploaderEl = $("")
- modal = new Modal
- title: "Upload file"
- el: uploaderEl
- buttons: [{
- text: "Close"
- }]
-
- uploadCount = 0
- entity_ids = []
- new qq.FineUploader
- element: uploaderEl[0]
- disabledCancelForFormUploads: true
- maxConnections: 1
- request:
- endpoint: "/Project/#{@ide.project.id}/upload"
- params:
- folder_id: folder_id
- _csrf: csrfToken
- paramsInBody: false
- forceMultipart: true
- callbacks:
- onUpload: () -> uploadCount++
- onComplete: (error, name, response) ->
- setTimeout (() ->
- uploadCount--
- entity_ids.push response.entity_id
- if uploadCount == 0 and response? and response.success
- modal.remove()
- callback null, entity_ids
- ), 250
- text:
- waitingForResponse: "Inserting file..."
- failUpload: "Upload failed, sorry :("
- uploadButton: "Select file(s)"
- template: """
-
-
{dragZoneText}
-
-
or
-
drag file(s)
-
{dropProcessingText}
-
Hint: Press and hold the Control (Ctrl) key to select multiple files
-
-
- """
- $(".qq-uploader input").addClass("js-file-uploader")
-
-
diff --git a/services/web/public/coffee/ide/LayoutManager.coffee b/services/web/public/coffee/ide/LayoutManager.coffee
deleted file mode 100644
index e7a09e36d7..0000000000
--- a/services/web/public/coffee/ide/LayoutManager.coffee
+++ /dev/null
@@ -1,76 +0,0 @@
-define [
- "underscore",
- "libs/backbone",
- "libs/jquery-layout"
- "libs/jquery.storage"
-], () ->
- class LayoutManager
- constructor: (@ide) ->
- _.extend @, Backbone.Events
-
- template = $("#editorLayoutTemplate").html()
- el = $(template)
- @ide.tabManager.addTab {
- id: "code"
- name: "Code"
- content: el
- active: true
- contract: true
- onShown: () =>
- @resizeAllSplitters()
- }
-
- $(window).resize () =>
- @refreshHeights()
-
- @refreshHeights()
-
- @initLayout()
-
- $(window).keypress (event)->
- if (!(event.which == 115 && event.ctrlKey) && !(event.which == 19))
- return true
- event.preventDefault()
- return false
-
- @refreshHeights()
-
- initLayout: () ->
- options =
- spacing_open: 8
- spacing_closed: 16
- onresize: () =>
- @.trigger("resize")
-
- if (state = $.localStorage("layout.main"))?
- options.west =
- state.west
-
- $("#mainSplitter").layout options
-
- $(window).unload () ->
- $.localStorage("layout.main", $("#mainSplitter").layout().readState())
-
- refreshHeights: ->
- @setSplitterHeight()
- @setSectionsHeight()
- @setTopOffset()
-
- setSplitterHeight: () ->
- $("#mainSplitter").height($(window).height() - $(".navbar").outerHeight())
-
- setTopOffset: () ->
- $("#toolbar").css(top: $(".navbar").outerHeight())
- $("#tab-content").css(top: $(".navbar").outerHeight())
-
- setSectionsHeight: ()->
- $sections = $('#sections')
- $chatArea = $('#chatArea')
- availableSpace = $(window).height() - 40 - 20 - 10
- if $chatArea.is(':visible')
- availableSpace -= 200
- $sections.height(availableSpace)
-
- resizeAllSplitters : ->
- $("#mainSplitter").layout().resizeAll()
- $("#editorSplitter").layout().resizeAll()
diff --git a/services/web/public/coffee/ide/MainAreaManager.coffee b/services/web/public/coffee/ide/MainAreaManager.coffee
deleted file mode 100644
index a28ebb559c..0000000000
--- a/services/web/public/coffee/ide/MainAreaManager.coffee
+++ /dev/null
@@ -1,52 +0,0 @@
-define () ->
- class MainAreaManager
- constructor: (@ide, @el) ->
- @$iframe = $('#imageArea')
- @$loading = $('#loading')
- @$currentArea = $('#loading')
- @areas = {}
-
- addArea: (options) ->
- @areas ||= {}
- @areas[options.identifier] = options.element
- options.element.hide()
- @el.append(options.element)
-
- removeArea: (identifier) ->
- @areas ||= {}
- if @areas[identifier]?
- if @$currentArea == @areas[identifier]
- delete @$currentArea
- @areas[identifier].remove()
- delete @areas[identifier]
-
- getAreaElement: (identifier) ->
- @areas[identifier]
-
- setIframeSrc: (src)->
- $('#imageArea iframe').attr 'src', src
-
- change : (type, complete)->
- if @areas[type]?
- @$currentArea.hide() if @$currentArea?
- @areas[type].show 0, =>
- @ide.layoutManager.refreshHeights()
- if complete?
- complete()
- @$currentArea = @areas[type]
- else
- # Deprecated system
- switch type
- when 'iframe'
- if(@$iframe.attr('id')!=@$currentArea.attr('id'))
- @$iframe.show()
- @$currentArea.hide()
- @$currentArea = @$iframe
- break
- when 'loading'
- if(@$loading.attr('id')!=@$currentArea.attr('id'))
- @$currentArea.hide()
- @$loading.show()
- @$currentArea = @$loading
- break
-
diff --git a/services/web/public/coffee/ide/SavingAreaManager.coffee b/services/web/public/coffee/ide/SavingAreaManager.coffee
deleted file mode 100644
index 285995a3ef..0000000000
--- a/services/web/public/coffee/ide/SavingAreaManager.coffee
+++ /dev/null
@@ -1,32 +0,0 @@
-define [
-], () ->
- class SavingAreaManager
- $el: $('#saving-area')
-
- constructor: (@ide) ->
- @unsavedSeconds = 0
- setInterval () =>
- @pollSavedStatus()
- , 1000
-
- $(window).bind 'beforeunload', () =>
- @warnAboutUnsavedChanges()
-
- pollSavedStatus: () ->
- doc = @ide.editor.document
- return if !doc?
- saved = doc.pollSavedStatus()
- if saved
- @unsavedSeconds = 0
- else
- @unsavedSeconds += 1
-
- if @unsavedSeconds >= 4
- $("#savingProblems").text("Saving... (#{@unsavedSeconds} seconds of unsaved changes)")
- $("#savingProblems").show()
- else
- $("#savingProblems").hide()
-
- warnAboutUnsavedChanges: () ->
- if @ide.editor.hasUnsavedChanges()
- return "You have unsaved changes. If you leave now they will not be saved."
diff --git a/services/web/public/coffee/ide/SideBarManager.coffee b/services/web/public/coffee/ide/SideBarManager.coffee
deleted file mode 100644
index 17674fcdf1..0000000000
--- a/services/web/public/coffee/ide/SideBarManager.coffee
+++ /dev/null
@@ -1,38 +0,0 @@
-define () ->
- class SideBarManager
- constructor: (@ide, @el) ->
-
- addLink: (options) ->
- @elements ||= {}
- @elements[options.identifier] = options.element
-
- if options.before and @elements[options.before]
- options.element.insertBefore @elements[options.before]
- else if options.after and @elements[options.after]
- options.element.insertAfter @elements[options.after]
- else if options.prepend
- @el.prepend options.element
- else
- @el.append options.element
-
- removeLink: (identifier) ->
- @elements ||= {}
- if @elements[identifier]?
- @elements[identifier].remove()
- delete @elements[identifier]
-
- selectLink: (identifier) ->
- @selectElement(@elements[identifier].find("li"))
-
- selectElement: (selector, callback)->
- # This method is deprecated and will eventually be replaced
- # by selectLink
- if $(selector).length
- @deselectAll()
- $(selector).addClass('selected')
-
- deselectAll: () ->
- $('.selected').removeClass('selected')
-
-
-
diff --git a/services/web/public/coffee/ide/TabManager.coffee b/services/web/public/coffee/ide/TabManager.coffee
deleted file mode 100644
index e4c1b3ca70..0000000000
--- a/services/web/public/coffee/ide/TabManager.coffee
+++ /dev/null
@@ -1,109 +0,0 @@
-define [
- "libs/mustache"
-], () ->
- class TabManager
- templates:
- tab: $("#tabTemplate").html()
- content: $("#tabContentTemplate").html()
-
- constructor: () ->
- @locked_open = false
- @locked_closed = false
- @state = "closed"
- $("#toolbar").on "mouseenter", () => @onMouseOver()
- $("#toolbar").on "mouseleave", (e) => @onMouseOut(e)
- @tabs = []
-
- addTab: (options) ->
- @tabs.push options
-
- options.show ||= options.id
- tabEl = $(Mustache.to_html @templates.tab, options)
- tabEl.find("a").attr("href", "#" + options.show)
-
- if options.content?
- contentEl = $(Mustache.to_html @templates.content, options)
- contentEl.append(options.content)
- $("#tab-content").append(contentEl)
-
- if options.active
- tabEl.addClass("active")
- contentEl?.addClass("active")
-
- if options.after?
- tabEl.insertAfter($("##{options.after}-tab-li"))
- else
- $("#tabs").append(tabEl)
-
- $("body").scrollTop(0)
- tabEl.on "shown", () =>
- $("body").scrollTop(0)
-
- options.onShown() if options.onShown?
- for other_tab in @tabs
- if other_tab.id != options.id and other_tab.active and other_tab.onHidden?
- other_tab.onHidden()
- other_tab.active = false
- options.active = true
-
- if options.lock
- @lockOpen()
- else
- @unlockOpen()
-
- if options.contract
- @contract()
-
- show: (tab) ->
- $("##{tab}-tab-li > a").tab("show")
-
- lockOpen: () ->
- @locked_open = true
- $("#toolbar").css({
- width: 180
- })
-
- unlockOpen: () ->
- @locked_open = false
-
- contract: () ->
- $("#toolbar").css({
- width: 40
- })
- # cooldown so we don't immediately reopen
- original_locked_closed = @locked_closed
- @locked_closed = true
- setTimeout () =>
- @locked_closed = original_locked_closed
- , 200
-
- onMouseOver: () ->
- if !@locked_closed and @state == "closed"
- @openMenu()
-
- onMouseOut: (e) ->
- @cancelOpen()
- if !@locked_open and @state == "open"
- @closeMenu()
-
- cancelOpen: () ->
- if @openTimeout
- clearTimeout @openTimeout
- @state = "closed"
-
- openMenu: () ->
- @openTimeout = setTimeout () =>
- @state = "open"
- $("#toolbar").animate({
- width: 180
- }, "fast")
- delete @openTimeout
- , 500
-
- closeMenu: () ->
- @state = "closed"
- $("#toolbar").animate({
- width: 40
- }, "fast")
-
-
diff --git a/services/web/public/coffee/app/ide/binary-files/BinaryFilesManager.coffee b/services/web/public/coffee/ide/binary-files/BinaryFilesManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/binary-files/BinaryFilesManager.coffee
rename to services/web/public/coffee/ide/binary-files/BinaryFilesManager.coffee
diff --git a/services/web/public/coffee/app/ide/binary-files/controllers/BinaryFileController.coffee b/services/web/public/coffee/ide/binary-files/controllers/BinaryFileController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/binary-files/controllers/BinaryFileController.coffee
rename to services/web/public/coffee/ide/binary-files/controllers/BinaryFileController.coffee
diff --git a/services/web/public/coffee/app/ide/chat/controllers/ChatButtonController.coffee b/services/web/public/coffee/ide/chat/controllers/ChatButtonController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/chat/controllers/ChatButtonController.coffee
rename to services/web/public/coffee/ide/chat/controllers/ChatButtonController.coffee
diff --git a/services/web/public/coffee/app/ide/chat/controllers/ChatController.coffee b/services/web/public/coffee/ide/chat/controllers/ChatController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/chat/controllers/ChatController.coffee
rename to services/web/public/coffee/ide/chat/controllers/ChatController.coffee
diff --git a/services/web/public/coffee/app/ide/chat/controllers/ChatMessageController.coffee b/services/web/public/coffee/ide/chat/controllers/ChatMessageController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/chat/controllers/ChatMessageController.coffee
rename to services/web/public/coffee/ide/chat/controllers/ChatMessageController.coffee
diff --git a/services/web/public/coffee/app/ide/chat/index.coffee b/services/web/public/coffee/ide/chat/index.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/chat/index.coffee
rename to services/web/public/coffee/ide/chat/index.coffee
diff --git a/services/web/public/coffee/app/ide/connection/ConnectionManager.coffee b/services/web/public/coffee/ide/connection/ConnectionManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/connection/ConnectionManager.coffee
rename to services/web/public/coffee/ide/connection/ConnectionManager.coffee
diff --git a/services/web/public/coffee/app/ide/directives/layout.coffee b/services/web/public/coffee/ide/directives/layout.coffee
similarity index 99%
rename from services/web/public/coffee/app/ide/directives/layout.coffee
rename to services/web/public/coffee/ide/directives/layout.coffee
index 36528e63f6..a7b9d81019 100644
--- a/services/web/public/coffee/app/ide/directives/layout.coffee
+++ b/services/web/public/coffee/ide/directives/layout.coffee
@@ -1,5 +1,6 @@
define [
"base"
+ "libs/jquery-layout"
], (App) ->
App.directive "layout", ["$parse", ($parse) ->
return {
diff --git a/services/web/public/coffee/app/ide/editor/Document.coffee b/services/web/public/coffee/ide/editor/Document.coffee
similarity index 99%
rename from services/web/public/coffee/app/ide/editor/Document.coffee
rename to services/web/public/coffee/ide/editor/Document.coffee
index 4b7347b520..fe396f19b2 100644
--- a/services/web/public/coffee/app/ide/editor/Document.coffee
+++ b/services/web/public/coffee/ide/editor/Document.coffee
@@ -1,7 +1,6 @@
define [
"utils/EventEmitter"
"ide/editor/ShareJsDoc"
- "underscore"
], (EventEmitter, ShareJsDoc) ->
class Document extends EventEmitter
@getDocument: (ide, doc_id) ->
diff --git a/services/web/public/coffee/app/ide/editor/EditorManager.coffee b/services/web/public/coffee/ide/editor/EditorManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/editor/EditorManager.coffee
rename to services/web/public/coffee/ide/editor/EditorManager.coffee
diff --git a/services/web/public/coffee/app/ide/editor/ShareJsDoc.coffee b/services/web/public/coffee/ide/editor/ShareJsDoc.coffee
similarity index 99%
rename from services/web/public/coffee/app/ide/editor/ShareJsDoc.coffee
rename to services/web/public/coffee/ide/editor/ShareJsDoc.coffee
index fe61580041..3a1728c9c6 100644
--- a/services/web/public/coffee/app/ide/editor/ShareJsDoc.coffee
+++ b/services/web/public/coffee/ide/editor/ShareJsDoc.coffee
@@ -1,6 +1,6 @@
define [
"utils/EventEmitter"
- "../../../libs/sharejs"
+ "libs/sharejs"
], (EventEmitter, ShareJs) ->
class ShareJsDoc extends EventEmitter
constructor: (@doc_id, docLines, version, @socket) ->
diff --git a/services/web/public/coffee/app/ide/editor/controllers/SavingNotificationController.coffee b/services/web/public/coffee/ide/editor/controllers/SavingNotificationController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/editor/controllers/SavingNotificationController.coffee
rename to services/web/public/coffee/ide/editor/controllers/SavingNotificationController.coffee
diff --git a/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee
similarity index 94%
rename from services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee
rename to services/web/public/coffee/ide/editor/directives/aceEditor.coffee
index ae4dec106f..4a91d4ee54 100644
--- a/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee
+++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee
@@ -1,11 +1,11 @@
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"
+ "ide/editor/directives/aceEditor/undo/UndoManager"
+ "ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager"
+ "ide/editor/directives/aceEditor/spell-check/SpellCheckManager"
+ "ide/editor/directives/aceEditor/highlights/HighlightsManager"
+ "ide/editor/directives/aceEditor/cursor-position/CursorPositionManager"
"ace/keyboard/vim"
"ace/keyboard/emacs"
"ace/mode/latex"
diff --git a/services/web/public/coffee/app/ide/editor/auto-complete/AutoCompleteManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/editor/auto-complete/AutoCompleteManager.coffee
rename to services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
diff --git a/services/web/public/coffee/app/ide/editor/auto-complete/Snippets.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/Snippets.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/editor/auto-complete/Snippets.coffee
rename to services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/Snippets.coffee
diff --git a/services/web/public/coffee/app/ide/editor/auto-complete/SuggestionManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/SuggestionManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/editor/auto-complete/SuggestionManager.coffee
rename to services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/SuggestionManager.coffee
diff --git a/services/web/public/coffee/app/ide/editor/cursor-position/CursorPositionManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/cursor-position/CursorPositionManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/editor/cursor-position/CursorPositionManager.coffee
rename to services/web/public/coffee/ide/editor/directives/aceEditor/cursor-position/CursorPositionManager.coffee
diff --git a/services/web/public/coffee/app/ide/editor/highlights/HighlightsManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/highlights/HighlightsManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/editor/highlights/HighlightsManager.coffee
rename to services/web/public/coffee/ide/editor/directives/aceEditor/highlights/HighlightsManager.coffee
diff --git a/services/web/public/coffee/app/ide/editor/spell-check/HighlightedWordManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/editor/spell-check/HighlightedWordManager.coffee
rename to services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee
diff --git a/services/web/public/coffee/app/ide/editor/spell-check/SpellCheckManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/editor/spell-check/SpellCheckManager.coffee
rename to services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee
diff --git a/services/web/public/coffee/app/ide/editor/spell-check/SpellingMenuView.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellingMenuView.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/editor/spell-check/SpellingMenuView.coffee
rename to services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellingMenuView.coffee
diff --git a/services/web/public/coffee/app/ide/editor/undo/UndoManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/undo/UndoManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/editor/undo/UndoManager.coffee
rename to services/web/public/coffee/ide/editor/directives/aceEditor/undo/UndoManager.coffee
diff --git a/services/web/public/coffee/editor/ShareJSHeader.coffee b/services/web/public/coffee/ide/editor/sharejs/header.coffee
similarity index 100%
rename from services/web/public/coffee/editor/ShareJSHeader.coffee
rename to services/web/public/coffee/ide/editor/sharejs/header.coffee
diff --git a/services/web/public/coffee/editor/sharejs/client/ace.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/client/ace.coffee
similarity index 92%
rename from services/web/public/coffee/editor/sharejs/client/ace.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/client/ace.coffee
index f85a1c9982..a55eb85c82 100644
--- a/services/web/public/coffee/editor/sharejs/client/ace.coffee
+++ b/services/web/public/coffee/ide/editor/sharejs/vendor/client/ace.coffee
@@ -62,13 +62,11 @@ window.sharejs.extendDoc 'attach_ace', (editor, keepEditorContents) ->
# 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()
+ if keepEditorContents
+ doc.del 0, doc.getText().length
+ doc.insert 0, editorDoc.getValue()
+ else
+ editorDoc.setValue doc.getText()
check()
diff --git a/services/web/public/coffee/editor/sharejs/client/ace.js b/services/web/public/coffee/ide/editor/sharejs/vendor/client/ace.js
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/client/ace.js
rename to services/web/public/coffee/ide/editor/sharejs/vendor/client/ace.js
diff --git a/services/web/public/coffee/editor/sharejs/client/cm.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/client/cm.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/client/cm.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/client/cm.coffee
diff --git a/services/web/public/coffee/editor/sharejs/client/connection.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/client/connection.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/client/connection.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/client/connection.coffee
diff --git a/services/web/public/coffee/editor/sharejs/client/doc.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/client/doc.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.coffee
diff --git a/services/web/public/coffee/editor/sharejs/client/doc.js b/services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.js
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/client/doc.js
rename to services/web/public/coffee/ide/editor/sharejs/vendor/client/doc.js
diff --git a/services/web/public/coffee/editor/sharejs/client/index.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/client/index.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/client/index.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/client/index.coffee
diff --git a/services/web/public/coffee/editor/sharejs/client/microevent.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/client/microevent.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/client/microevent.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/client/microevent.coffee
diff --git a/services/web/public/coffee/editor/sharejs/client/microevent.js b/services/web/public/coffee/ide/editor/sharejs/vendor/client/microevent.js
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/client/microevent.js
rename to services/web/public/coffee/ide/editor/sharejs/vendor/client/microevent.js
diff --git a/services/web/public/coffee/editor/sharejs/client/textarea.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/client/textarea.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/client/textarea.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/client/textarea.coffee
diff --git a/services/web/public/coffee/editor/sharejs/client/web-prelude.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/client/web-prelude.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/client/web-prelude.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/client/web-prelude.coffee
diff --git a/services/web/public/coffee/editor/sharejs/index.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/index.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/index.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/index.coffee
diff --git a/services/web/public/coffee/editor/sharejs/server/browserchannel.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/server/browserchannel.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/server/browserchannel.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/server/browserchannel.coffee
diff --git a/services/web/public/coffee/editor/sharejs/server/db/couchdb.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/server/db/couchdb.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/server/db/couchdb.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/server/db/couchdb.coffee
diff --git a/services/web/public/coffee/editor/sharejs/server/db/index.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/server/db/index.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/server/db/index.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/server/db/index.coffee
diff --git a/services/web/public/coffee/editor/sharejs/server/db/pg.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/server/db/pg.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/server/db/pg.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/server/db/pg.coffee
diff --git a/services/web/public/coffee/editor/sharejs/server/db/redis.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/server/db/redis.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/server/db/redis.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/server/db/redis.coffee
diff --git a/services/web/public/coffee/editor/sharejs/server/index.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/server/index.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/server/index.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/server/index.coffee
diff --git a/services/web/public/coffee/editor/sharejs/server/model.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/server/model.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/server/model.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/server/model.coffee
diff --git a/services/web/public/coffee/editor/sharejs/server/rest.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/server/rest.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/server/rest.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/server/rest.coffee
diff --git a/services/web/public/coffee/editor/sharejs/server/socketio.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/server/socketio.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/server/socketio.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/server/socketio.coffee
diff --git a/services/web/public/coffee/editor/sharejs/server/syncqueue.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/server/syncqueue.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/server/syncqueue.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/server/syncqueue.coffee
diff --git a/services/web/public/coffee/editor/sharejs/server/useragent.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/server/useragent.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/server/useragent.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/server/useragent.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/README.md b/services/web/public/coffee/ide/editor/sharejs/vendor/types/README.md
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/README.md
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/README.md
diff --git a/services/web/public/coffee/editor/sharejs/types/count.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/count.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/count.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/count.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/helpers.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/helpers.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/helpers.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/helpers.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/index.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/index.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/index.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/index.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/json-api.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/json-api.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/json-api.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/json-api.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/json.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/json.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/json.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/json.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/simple.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/simple.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/simple.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/simple.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/text-api.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/text-api.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/text-api.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/text-api.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/text-api.js b/services/web/public/coffee/ide/editor/sharejs/vendor/types/text-api.js
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/text-api.js
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/text-api.js
diff --git a/services/web/public/coffee/editor/sharejs/types/text-composable-api.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/text-composable-api.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/text-composable-api.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/text-composable-api.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/text-composable.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/text-composable.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/text-composable.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/text-composable.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/text-tp2-api.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/text-tp2-api.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/text-tp2-api.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/text-tp2-api.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/text-tp2.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/text-tp2.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/text-tp2.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/text-tp2.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/text.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/text.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/text.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/text.coffee
diff --git a/services/web/public/coffee/editor/sharejs/types/text.js b/services/web/public/coffee/ide/editor/sharejs/vendor/types/text.js
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/text.js
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/text.js
diff --git a/services/web/public/coffee/editor/sharejs/types/web-prelude.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/types/web-prelude.coffee
similarity index 100%
rename from services/web/public/coffee/editor/sharejs/types/web-prelude.coffee
rename to services/web/public/coffee/ide/editor/sharejs/vendor/types/web-prelude.coffee
diff --git a/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee b/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee
rename to services/web/public/coffee/ide/file-tree/FileTreeManager.coffee
diff --git a/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeController.coffee b/services/web/public/coffee/ide/file-tree/controllers/FileTreeController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/file-tree/controllers/FileTreeController.coffee
rename to services/web/public/coffee/ide/file-tree/controllers/FileTreeController.coffee
diff --git a/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeEntityController.coffee b/services/web/public/coffee/ide/file-tree/controllers/FileTreeEntityController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/file-tree/controllers/FileTreeEntityController.coffee
rename to services/web/public/coffee/ide/file-tree/controllers/FileTreeEntityController.coffee
diff --git a/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeFolderController.coffee b/services/web/public/coffee/ide/file-tree/controllers/FileTreeFolderController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/file-tree/controllers/FileTreeFolderController.coffee
rename to services/web/public/coffee/ide/file-tree/controllers/FileTreeFolderController.coffee
diff --git a/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeRootFolderController.coffee b/services/web/public/coffee/ide/file-tree/controllers/FileTreeRootFolderController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/file-tree/controllers/FileTreeRootFolderController.coffee
rename to services/web/public/coffee/ide/file-tree/controllers/FileTreeRootFolderController.coffee
diff --git a/services/web/public/coffee/app/ide/file-tree/directives/draggable.coffee b/services/web/public/coffee/ide/file-tree/directives/draggable.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/file-tree/directives/draggable.coffee
rename to services/web/public/coffee/ide/file-tree/directives/draggable.coffee
diff --git a/services/web/public/coffee/app/ide/file-tree/directives/droppable.coffee b/services/web/public/coffee/ide/file-tree/directives/droppable.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/file-tree/directives/droppable.coffee
rename to services/web/public/coffee/ide/file-tree/directives/droppable.coffee
diff --git a/services/web/public/coffee/app/ide/file-tree/directives/fileEntity.coffee b/services/web/public/coffee/ide/file-tree/directives/fileEntity.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/file-tree/directives/fileEntity.coffee
rename to services/web/public/coffee/ide/file-tree/directives/fileEntity.coffee
diff --git a/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee b/services/web/public/coffee/ide/online-users/OnlineUsersManager.coffee
similarity index 98%
rename from services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee
rename to services/web/public/coffee/ide/online-users/OnlineUsersManager.coffee
index e170a58de9..b4631a9c31 100644
--- a/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee
+++ b/services/web/public/coffee/ide/online-users/OnlineUsersManager.coffee
@@ -1,5 +1,5 @@
define [
- "../../../libs/md5"
+ "libs/md5"
], () ->
class OnlineUsersManager
constructor: (@ide, @$scope) ->
diff --git a/services/web/public/coffee/app/ide/pdf/PdfManager.coffee b/services/web/public/coffee/ide/pdf/PdfManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/pdf/PdfManager.coffee
rename to services/web/public/coffee/ide/pdf/PdfManager.coffee
diff --git a/services/web/public/coffee/app/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/pdf/controllers/PdfController.coffee
rename to services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
diff --git a/services/web/public/coffee/app/ide/pdf/directives/pdfJs.coffee b/services/web/public/coffee/ide/pdf/directives/pdfJs.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/pdf/directives/pdfJs.coffee
rename to services/web/public/coffee/ide/pdf/directives/pdfJs.coffee
diff --git a/services/web/public/coffee/app/ide/permissions/PermissionsManager.coffee b/services/web/public/coffee/ide/permissions/PermissionsManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/permissions/PermissionsManager.coffee
rename to services/web/public/coffee/ide/permissions/PermissionsManager.coffee
diff --git a/services/web/public/coffee/app/ide/services/ide.coffee b/services/web/public/coffee/ide/services/ide.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/services/ide.coffee
rename to services/web/public/coffee/ide/services/ide.coffee
diff --git a/services/web/public/coffee/app/ide/settings/controllers/ProjectNameController.coffee b/services/web/public/coffee/ide/settings/controllers/ProjectNameController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/settings/controllers/ProjectNameController.coffee
rename to services/web/public/coffee/ide/settings/controllers/ProjectNameController.coffee
diff --git a/services/web/public/coffee/app/ide/settings/controllers/SettingsController.coffee b/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/settings/controllers/SettingsController.coffee
rename to services/web/public/coffee/ide/settings/controllers/SettingsController.coffee
diff --git a/services/web/public/coffee/app/ide/settings/index.coffee b/services/web/public/coffee/ide/settings/index.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/settings/index.coffee
rename to services/web/public/coffee/ide/settings/index.coffee
diff --git a/services/web/public/coffee/app/ide/settings/services/settings.coffee b/services/web/public/coffee/ide/settings/services/settings.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/settings/services/settings.coffee
rename to services/web/public/coffee/ide/settings/services/settings.coffee
diff --git a/services/web/public/coffee/app/ide/share/controllers/ShareController.coffee b/services/web/public/coffee/ide/share/controllers/ShareController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/share/controllers/ShareController.coffee
rename to services/web/public/coffee/ide/share/controllers/ShareController.coffee
diff --git a/services/web/public/coffee/app/ide/share/controllers/ShareProjectModalController.coffee b/services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/share/controllers/ShareProjectModalController.coffee
rename to services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee
diff --git a/services/web/public/coffee/app/ide/share/index.coffee b/services/web/public/coffee/ide/share/index.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/share/index.coffee
rename to services/web/public/coffee/ide/share/index.coffee
diff --git a/services/web/public/coffee/app/ide/share/services/projectMembers.coffee b/services/web/public/coffee/ide/share/services/projectMembers.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/share/services/projectMembers.coffee
rename to services/web/public/coffee/ide/share/services/projectMembers.coffee
diff --git a/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee b/services/web/public/coffee/ide/track-changes/TrackChangesManager.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee
rename to services/web/public/coffee/ide/track-changes/TrackChangesManager.coffee
diff --git a/services/web/public/coffee/app/ide/track-changes/controllers/TrackChangesDiffController.coffee b/services/web/public/coffee/ide/track-changes/controllers/TrackChangesDiffController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/track-changes/controllers/TrackChangesDiffController.coffee
rename to services/web/public/coffee/ide/track-changes/controllers/TrackChangesDiffController.coffee
diff --git a/services/web/public/coffee/app/ide/track-changes/controllers/TrackChangesListController.coffee b/services/web/public/coffee/ide/track-changes/controllers/TrackChangesListController.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/track-changes/controllers/TrackChangesListController.coffee
rename to services/web/public/coffee/ide/track-changes/controllers/TrackChangesListController.coffee
diff --git a/services/web/public/coffee/app/ide/track-changes/directives/infiniteScroll.coffee b/services/web/public/coffee/ide/track-changes/directives/infiniteScroll.coffee
similarity index 100%
rename from services/web/public/coffee/app/ide/track-changes/directives/infiniteScroll.coffee
rename to services/web/public/coffee/ide/track-changes/directives/infiniteScroll.coffee
diff --git a/services/web/public/coffee/keys/BackspaceHighjack.coffee b/services/web/public/coffee/keys/BackspaceHighjack.coffee
deleted file mode 100644
index b3fcf1cce1..0000000000
--- a/services/web/public/coffee/keys/BackspaceHighjack.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-define [
-], () ->
- rx = /INPUT|SELECT|TEXTAREA/i
-
- $(document).bind "keydown keypress", (e)->
- if e.which == 8
- # 8 == backspace
- if !rx.test(e.target.tagName) || e.target.disabled || e.target.readOnly
- e.preventDefault()
\ No newline at end of file
diff --git a/services/web/public/coffee/keys/HotkeysManager.coffee b/services/web/public/coffee/keys/HotkeysManager.coffee
deleted file mode 100644
index 529eabeb6f..0000000000
--- a/services/web/public/coffee/keys/HotkeysManager.coffee
+++ /dev/null
@@ -1,30 +0,0 @@
-define [
- "utils/Modal"
- "ace/lib/useragent"
-], (Modal) ->
- useragent = require("ace/lib/useragent")
-
- class HotKeysManager
- template: $("#hotKeysLinkTemplate").html()
-
- constructor: (@ide) ->
- @$el = $(@template)
- $("#toolbar-footer").append(@$el)
- @$el.on "click", (e) =>
- e.preventDefault()
- @showHotKeys()
-
- showHotKeys: () ->
- el = $($("#hotKeysListTemplate").html())
- if useragent.isMac
- el.find(".win").hide()
- else
- el.find(".mac").hide()
-
- new Modal
- title: "Hot keys"
- el: el
- buttons: [{
- text: "Hide"
- class: "btn-primary"
- }]
diff --git a/services/web/public/coffee/libs.coffee b/services/web/public/coffee/libs.coffee
new file mode 100644
index 0000000000..b25e273586
--- /dev/null
+++ b/services/web/public/coffee/libs.coffee
@@ -0,0 +1,11 @@
+define [
+ "moment"
+ "libs/angular-autocomplete/angular-autocomplete"
+ "libs/ui-bootstrap"
+ "libs/ng-context-menu-0.1.4"
+ "libs/underscore-1.3.3"
+ "libs/algolia-2.5.2"
+ "libs/jquery.storage"
+ "libs/fineuploader"
+ "libs/angular-sanitize-1.2.17"
+], () ->
diff --git a/services/web/public/coffee/app/main.coffee b/services/web/public/coffee/main.coffee
similarity index 100%
rename from services/web/public/coffee/app/main.coffee
rename to services/web/public/coffee/main.coffee
diff --git a/services/web/public/coffee/app/main/account-settings.coffee b/services/web/public/coffee/main/account-settings.coffee
similarity index 100%
rename from services/web/public/coffee/app/main/account-settings.coffee
rename to services/web/public/coffee/main/account-settings.coffee
diff --git a/services/web/public/coffee/app/main/group-members.coffee b/services/web/public/coffee/main/group-members.coffee
similarity index 100%
rename from services/web/public/coffee/app/main/group-members.coffee
rename to services/web/public/coffee/main/group-members.coffee
diff --git a/services/web/public/coffee/app/main/plans.coffee b/services/web/public/coffee/main/plans.coffee
similarity index 100%
rename from services/web/public/coffee/app/main/plans.coffee
rename to services/web/public/coffee/main/plans.coffee
diff --git a/services/web/public/coffee/app/main/project-list.coffee b/services/web/public/coffee/main/project-list.coffee
similarity index 100%
rename from services/web/public/coffee/app/main/project-list.coffee
rename to services/web/public/coffee/main/project-list.coffee
diff --git a/services/web/public/coffee/app/main/templates.coffee b/services/web/public/coffee/main/templates.coffee
similarity index 100%
rename from services/web/public/coffee/app/main/templates.coffee
rename to services/web/public/coffee/main/templates.coffee
diff --git a/services/web/public/coffee/app/main/user-details.coffee b/services/web/public/coffee/main/user-details.coffee
similarity index 98%
rename from services/web/public/coffee/app/main/user-details.coffee
rename to services/web/public/coffee/main/user-details.coffee
index 03a180a6f0..9f781a5a2a 100644
--- a/services/web/public/coffee/app/main/user-details.coffee
+++ b/services/web/public/coffee/main/user-details.coffee
@@ -1,6 +1,6 @@
define [
"base"
- "../../libs/algolia-2.5.2"
+ "libs/algolia-2.5.2"
], (App, algolia)->
app.factory "Institutions", ->
new AlgoliaSearch(window.algolia.institutions.app_id, window.algolia.institutions.api_key).initIndex("institutions")
diff --git a/services/web/public/coffee/messages/MessageManager.coffee b/services/web/public/coffee/messages/MessageManager.coffee
deleted file mode 100644
index b740ee971f..0000000000
--- a/services/web/public/coffee/messages/MessageManager.coffee
+++ /dev/null
@@ -1,25 +0,0 @@
-define [
- "utils/Modal"
-], (Modal) ->
- class MessageManager
- constructor: (@ide) ->
- if @ide?
- @ide.on "afterJoinProject", (@project) =>
- @checkIfProjectHasBeenDeleted()
- @ide.socket.on "projectRenamedOrDeletedByExternalSource", @notifyUsersProjectHasBeenDeletedOrRenamed
-
- checkIfProjectHasBeenDeleted: ->
- if @project.get('deletedByExternalDataSource')
- @notifyUsersProjectHasBeenDeletedOrRenamed()
-
- notifyUsersProjectHasBeenDeletedOrRenamed: ->
- Modal.createModal
- isStatic: false
- title: "Project Renamed or Deleted"
- message: "This project has either been renamed or deleted by an external data source such as Dropbox. We don't want to delete your data on ShareLaTeX, so this project still contains your history and collaborators. If the project has been renamed please look in your project list for a new project under the new name."
- buttons: [{
- text : "Ok",
- class : "btn",
- }]
- @ide.mainAreaManager.addArea identifier:"project_deleted", element:$('#projectDeleted')
- @ide.mainAreaManager.change "project_deleted"
diff --git a/services/web/public/coffee/models/Doc.coffee b/services/web/public/coffee/models/Doc.coffee
deleted file mode 100644
index a80b3b6acf..0000000000
--- a/services/web/public/coffee/models/Doc.coffee
+++ /dev/null
@@ -1,12 +0,0 @@
-define [
- "libs/backbone"
-], () ->
- Doc = Backbone.Model.extend
- initialize: () ->
- @set("type", "doc")
-
- parse: (rawAttributes) ->
- attributes =
- id: rawAttributes._id
- name: rawAttributes.name
- deleted: !!rawAttributes.deleted
diff --git a/services/web/public/coffee/models/File.coffee b/services/web/public/coffee/models/File.coffee
deleted file mode 100644
index d4fc8b006f..0000000000
--- a/services/web/public/coffee/models/File.coffee
+++ /dev/null
@@ -1,25 +0,0 @@
-define [
- "libs/backbone"
-], () ->
- File = Backbone.Model.extend
- initialize: () ->
- @set("type", "file")
-
-
- previewUrl: () ->
- extension = @get("name").split(".").pop()?.toLowerCase()
- needsConverting = (["eps", "pdf"].indexOf(extension) != -1)
- queryString = if needsConverting then "?format=png" else ""
- url = "#{@downloadUrl()}#{queryString}"
- return url
-
- downloadUrl: ->
- url = "/project/#{userSettings.project_id}/file/#{@id}"
- return url
-
-
- parse: (rawAttributes) ->
- attributes =
- id: rawAttributes._id
- name: rawAttributes.name
-
diff --git a/services/web/public/coffee/models/Folder.coffee b/services/web/public/coffee/models/Folder.coffee
deleted file mode 100644
index d9c3df13ec..0000000000
--- a/services/web/public/coffee/models/Folder.coffee
+++ /dev/null
@@ -1,36 +0,0 @@
-define [
- "models/FolderChildren"
- "models/File"
- "models/Doc"
- "libs/backbone"
-], (FolderChildren, File, Doc) ->
- Folder = Backbone.Model.extend
- initialize: () ->
- if !@get("children")?
- @set("children", new FolderChildren())
-
- @get("children").parentFolder = @
- @on "change:children", () =>
- @get("children").parentFolder = @
-
- @set("type", "folder")
-
- parse: (rawAttributes) ->
- attributes =
- id: rawAttributes._id
- name: rawAttributes.name
- children = []
- for childFolder in rawAttributes.folders or []
- children.push new Folder(childFolder, parse: true)
- for file in rawAttributes.fileRefs or []
- children.push new File(file, parse: true)
- for doc in rawAttributes.docs or []
- children.push new Doc(doc, parse: true)
- attributes.children = new FolderChildren(children)
- return attributes
-
- getParentFolderIds: () ->
- ids = @collection?.parentFolder?.getParentFolderIds() or []
- ids.push @id
- return ids
-
diff --git a/services/web/public/coffee/models/FolderChildren.coffee b/services/web/public/coffee/models/FolderChildren.coffee
deleted file mode 100644
index 082e9d2dbe..0000000000
--- a/services/web/public/coffee/models/FolderChildren.coffee
+++ /dev/null
@@ -1,29 +0,0 @@
-define [
- "models/Folder"
- "libs/backbone"
-], (Folder) ->
- FolderChildren = Backbone.Collection.extend
- comparator: (a,b) ->
- # Group folders at the top and then sort by name
- Folder = require("models/Folder") # recursive includes
-
- aName = a.get("name")
- if a instanceof Folder
- aName = "0" + aName
- else
- aName = "1" + aName
-
- bName = b.get("name")
- if b instanceof Folder
- bName = "0" + bName
- else
- bName = "1" + bName
-
- if aName < bName
- return -1
- else if aName > bName
- return 1
- else
- return 0
-
-
diff --git a/services/web/public/coffee/models/Project.coffee b/services/web/public/coffee/models/Project.coffee
deleted file mode 100644
index ab3ed089ca..0000000000
--- a/services/web/public/coffee/models/Project.coffee
+++ /dev/null
@@ -1,111 +0,0 @@
-define [
- "models/User"
- "models/ProjectMemberList"
- "models/Folder"
- "models/Doc"
- "libs/backbone"
-], (User, ProjectMemberList, Folder, Doc) ->
- Project = Backbone.Model.extend
- initialize: ->
- @on "change:ide", (project, ide) =>
- @bindToRootDocId()
- @bindToProjectName()
- @bindToPublicAccessLevel()
- @bindToCompiler()
- @bindToSpellingPreference()
- @bindToProjectDescription()
-
- parse: (rawAttributes) ->
- attributes =
- id : rawAttributes._id
- name : rawAttributes.name
- rootFolder : new Folder(rawAttributes.rootFolder[0], parse: true)
- rootDoc_id : rawAttributes.rootDoc_id
- publicAccesLevel : rawAttributes.publicAccesLevel
- features : rawAttributes.features
- compiler : rawAttributes.compiler
- spellCheckLanguage: rawAttributes.spellCheckLanguage
- dropboxEnabled : rawAttributes.dropboxEnabled
- description : rawAttributes.description
- deletedByExternalDataSource: rawAttributes.deletedByExternalDataSource
- useOt : rawAttributes.useOt
-
- attributes.members = members = new ProjectMemberList()
-
- attributes.owner = owner = User.findOrBuild rawAttributes.owner._id, rawAttributes.owner
- owner.set("privileges", "owner")
- members.add owner
-
- for rawMember in rawAttributes.members or []
- member = User.findOrBuild rawMember._id, rawMember
- members.add member
-
- for doc in rawAttributes.deletedDocs or []
- doc.deleted = true
-
- attributes.deletedDocs = new Folder({
- _id: "deleted-docs-folder"
- name: "Deleted documents"
- docs: rawAttributes.deletedDocs
- }, parse: true)
-
- return attributes
-
- bindToRootDocId: ->
- remoteChange = false
- @on "change:rootDoc_id", (project, docId) =>
- if !remoteChange
- @get("ide").socket.emit "setRootDoc", docId
- @get("ide").socket.on "rootDocUpdated", (docId) =>
- remoteChange = true
- @set("rootDoc_id", docId)
- remoteChange = false
-
- bindToProjectName: ->
- remoteChange = false
- @on "change:name", (project, name) =>
- if !remoteChange
- @get("ide").socket.emit "setProjectName", name
- @get("ide").socket.on "projectNameUpdated", (name) =>
- remoteChange = true
- @set("name", name)
- remoteChange = false
-
- bindToCompiler: ->
- remoteChange = false
- @on "change:compiler", (project, name) =>
- if !remoteChange
- @get("ide").socket.emit "setCompiler", name
- @get("ide").socket.on "compilerUpdated", (compiler) =>
- remoteChange = true
- @set("compiler", compiler)
- remoteChange = false
-
- bindToSpellingPreference: ->
- @on "change:spellCheckLanguage", (project, languageCode) =>
- @get("ide").socket.emit "setSpellCheckLanguage", languageCode
-
- bindToPublicAccessLevel: ->
- remoteChange = false
- @on "change:publicAccesLevel", (project, level) =>
- if !remoteChange
- @get("ide").socket.emit "setPublicAccessLevel", level
- @get("ide").socket.on 'publicAccessLevelUpdated', (level) =>
- remoteChange = true
- @set("publicAccesLevel", level)
- remoteChange = false
-
- bindToProjectDescription: ->
- remoteChange = false
- @on "change:description", (project, description) =>
- if !remoteChange
- @get("ide").socket.emit "updateProjectDescription", description
- @get("ide").socket.on 'projectDescriptionUpdated', (description) =>
- remoteChange = true
- @set("description", description)
- remoteChange = false
-
- getRootDocumentsList: (callback) ->
- @get("ide").socket.emit "getRootDocumentsList", (err, listOfDocs) ->
- callback(listOfDocs)
-
diff --git a/services/web/public/coffee/models/ProjectMemberList.coffee b/services/web/public/coffee/models/ProjectMemberList.coffee
deleted file mode 100644
index 3f3770256a..0000000000
--- a/services/web/public/coffee/models/ProjectMemberList.coffee
+++ /dev/null
@@ -1,14 +0,0 @@
-define [
- "models/User"
- "libs/backbone"
-], (User) ->
- ProjectMemberList = Backbone.Collection.extend
- model: User
-
- numberOfCollaborators: () ->
- collaborators = 0
- for member in @models
- if member.get("privileges") != "owner"
- collaborators += 1
- return collaborators
-
diff --git a/services/web/public/coffee/models/User.coffee b/services/web/public/coffee/models/User.coffee
deleted file mode 100644
index f83df16139..0000000000
--- a/services/web/public/coffee/models/User.coffee
+++ /dev/null
@@ -1,55 +0,0 @@
-define [
- "libs/md5"
- "libs/backbone"
-], () ->
- User = Backbone.Model.extend {
- gravatarUrl: (size = 32) ->
- email = @get("email").trim().toLowerCase()
- hash = CryptoJS.MD5(email)
- return "//www.gravatar.com/avatar/#{hash}.jpg?size=#{size}&d=mm"
-
- OWNER_HUE: 200
- hue: () ->
- if window.user.id == @get("id")
- hue = @OWNER_HUE
- else
- hash = CryptoJS.MD5(@get("id"))
- hue = parseInt(hash.toString().slice(0,8), 16) % 320
- # Avoid 20 degrees either side of the owner
- if hue > @OWNER_HUE - 20
- hue = hue + 40
- return hue
-
- name: () ->
- if window.user.id == @get("id")
- return "you"
- parts = []
- first_name = @get("first_name")
- if first_name? and first_name.length > 0
- parts.push first_name
- last_name = @get("last_name")
- if last_name? and last_name.length > 0
- parts.push last_name
- return parts.join(" ")
-
- }, {
- findOrBuild : (id, attributes) ->
- model = @find id
- if !model?
- model = @build id
- model.set model.parse attributes
- return model
-
- build: (id) ->
- model = new this(id : id)
- @loadedModel ||= {}
- @loadedModel[id] = model
- return model
-
- find: (id) ->
- @loadedModel ||= {}
- return @loadedModel[id]
-
- getAnonymousUser: () ->
- return User.findOrBuild("anonymous", { first_name: "Anonymous", email: "anon@sharelatex.com" })
- }
diff --git a/services/web/public/coffee/app/modules/recursionHelper.coffee b/services/web/public/coffee/modules/recursionHelper.coffee
similarity index 100%
rename from services/web/public/coffee/app/modules/recursionHelper.coffee
rename to services/web/public/coffee/modules/recursionHelper.coffee
diff --git a/services/web/public/coffee/pdf/CompiledView.coffee b/services/web/public/coffee/pdf/CompiledView.coffee
deleted file mode 100755
index 7176e63999..0000000000
--- a/services/web/public/coffee/pdf/CompiledView.coffee
+++ /dev/null
@@ -1,233 +0,0 @@
-define [
- "pdf/PDFjsView"
- "pdf/NativePdfView"
- "libs/backbone"
- "libs/mustache"
- "libs/jquery.storage"
-], (PdfjsView, NativePdfView) ->
- LatexErrorView = Backbone.View.extend
- constructor: (@ide, @error) ->
-
- template: $('#compileLogEntryTemplate').html()
-
- events:
- "click": "onClick"
-
- render: () ->
- if @error.level == "error"
- @error.type = "error"
- @error.title = "Error in #{@error.file}"
- else if @error.level == "warning"
- @error.type = "warning"
- @error.title = "Warning in #{@error.file}"
- else
- @error.type = "info"
- @error.title = "Typesetting problem in #{@error.file}"
- if @error.line?
- @error.title += " (line #{@error.line})"
- @setElement($(Mustache.to_html(@template, @error)))
-
- onClick: (event) ->
- @ide.fileTreeManager.openDocByPath(@error.file, @error.line)
-
- PdfView = Backbone.View.extend
- templates:
- pdfPanel: $("#pdfPanelTemplate").html()
- compileSuccess: $('#compileSuccessTemplate').html()
- compileFailed: $('#compileFailedTemplate').html()
- compileError: $('#compileErrorTemplate').html()
- compileTimeout: $('#compileTimeoutTemplate').html()
- outputFileLink: $('#outputFileLinkTemplate').html()
-
- events:
- "click #recompilePdf": -> @recompilePdf()
- "click #showLog": -> @showLog()
- "click #showRawLog": -> @showRawLog()
- "click #showPdf": -> @showPdf()
- "click #downloadPdf": -> @options.manager.downloadPdf()
- "click #flatViewButton": () ->
- $.localStorage("layout.pdf", "flat")
- @options.manager.switchToFlatView(showPdf: true)
- "click #splitViewButton": ->
- $.localStorage("layout.pdf", "split")
- @options.manager.switchToSplitView()
- "click .delete-cached-files > a": -> @options.manager.deleteCachedFiles()
-
- initialize: (@options) ->
- @ide = @options.ide
- @ide.layoutManager.on "resize", => @resize()
- @ide.editor.on "resize", => @resize()
- if @ide.userSettings.pdfViewer == "native"
- PdfView = NativePdfView
- @pdfjs = false
- else
- PdfView = PdfjsView
- @pdfjs = true
- @pdfView = new PdfView(manager: @)
- @pdfView.on "dblclick", (e) => @trigger "dblclick", e
-
- render: () ->
- @setElement(@templates.pdfPanel)
- @$("#pdfAreaContent").append @pdfView.render().$el
- @showBeforeCompile()
- return this
-
- resize: () ->
- toolbarHeight = @$("#pdfToolBar").outerHeight()
- areaHeight = @$el.outerHeight()
- @$("#pdfAreaContent").height(areaHeight - toolbarHeight)
- @pdfView.onResize?()
-
- updateLog: (options) ->
- {pdfExists, logExists, compileErrors, rawLog, timedOut, systemError} = options
-
- if @errorViews?
- for errorView in @errorViews
- errorView.remove()
- @errorViews = []
-
- errorLogs = @$("#logArea").find('ul')
- errorLogs.empty()
-
- logButtonHtml = "Logs"
-
- if compileErrors?
- for error in compileErrors.errors.concat(compileErrors.warnings).concat(compileErrors.typesetting)
- errorView = new LatexErrorView(@options.manager.ide, error)
- errorView.render()
- @errorViews.push(errorView)
- # TODO: The event handlers introduced by LatexError are never freed
- errorLogs.append(errorView.el)
-
- if compileErrors.errors.length > 0
- logButtonHtml += " #{compileErrors.errors.length}"
-
- if compileErrors.warnings.length > 0
- logButtonHtml += " #{compileErrors.warnings.length}"
-
- if compileErrors.typesetting.length > 0
- logButtonHtml += " #{compileErrors.typesetting.length}"
-
- @$("#showLog").html(logButtonHtml)
-
- if timedOut
- errorLogs.prepend($(@templates.compileTimeout))
- else if systemError
- errorLogs.prepend($(@templates.compileError))
- else if !pdfExists
- errorLogs.prepend($(@templates.compileFailed))
- else if pdfExists && compileErrors.all.length == 0
- errorLogs.prepend($(@templates.compileSuccess))
-
- errorLogs.find(".js-clear-cache").on "click", () =>
- @options.manager.deleteCachedFiles()
-
- @$("#rawLogArea").find("pre").text(rawLog)
-
- setPdf: (pdfUrl) ->
- @pdfUrl = pdfUrl
- @pdfView.setPdf pdfUrl
- @$("#downloadPdf").removeAttr("disabled")
- @$("#downloadLinksButton").removeAttr("disabled")
-
- unsetPdf: () ->
- delete @pdfUrl
- @pdfView.unsetPdf()
- @$("#downloadPdf").attr("disabled", "disabled")
- @$("#downloadLinksButton").attr("disabled", "disabled")
- return @
-
- hasPdf: () -> !!@pdfUrl
-
- IGNORE_FILES: ["output.fls", "output.fdb_latexmk"]
- showOutputFileDownloadLinks: (outputFiles) ->
- @$("#downloadLinks").empty()
- for file in outputFiles
- if @IGNORE_FILES.indexOf(file.path) == -1
- if file.path.match(/^output\./)
- name = "#{file.path.replace(/^output\./, "")} file"
- else
- name = file.path
- link = $ Mustache.to_html(@templates.outputFileLink, {
- project_id: @ide.project_id
- name: name
- path: file.path
- })
- @$("#downloadLinks").append(link)
-
- afterSwitchView: ()->
- if !@pdfjs
- @pdfView.unsetPdf()
- @pdfView.hide()
- @$(".not-compiled-yet-message").show()
-
- showBeforeCompile: () ->
- @pdfView.hide()
- @$("#showPdfGroup").hide()
- @$("#showLog").attr("disabled", "disabled")
- @$("#downloadPdf").attr("disabled", "disabled")
- @$("#downloadLinksButton").attr("disabled", "disabled")
- @$(".compiling-message").hide()
- @$(".not-compiled-yet-message").show()
-
- showPdf: () ->
- @$("#logArea").hide()
- @$("#rawLogArea").hide()
- @pdfView.show()
- @$("#showPdfGroup").hide()
- @$("#showLogGroup").show()
-
- showLog: () ->
- @$("#rawLogArea").hide()
- @pdfView.hide()
- @$("#logArea").show()
- @$("#showPdfGroup").show()
- @$("#showLogGroup").hide()
-
- showRawLog: () ->
- @pdfView.hide()
- @$("#logArea").hide()
- @$("#rawLogArea").show()
- @$("#showPdfGroup").show()
- @$("#showLogGroup").hide()
-
- onCompiling: () ->
- @$(".not-compiled-yet-message").hide()
- @$(".compiling-message").show()
- @$("#recompilePdf").attr("disabled", "disabled")
-
- doneCompiling: () ->
- @$(".compiling-message").hide()
- @$("#recompilePdf").removeAttr("disabled")
- @$("#showLog").removeAttr("disabled")
-
- recompilePdf: () ->
- @options.manager.trigger "compile:pdf"
- rootDocOverride_id = null
- for line in @ide.editor.getLines()
- match = line.match /(.*)\\documentclass/
- if match and !match[1].match /%/
- rootDocOverride_id = @ide.editor.getCurrentDocId()
- @options.manager.refreshPdf {rootDocOverride_id}
-
- toggleFlatViewButton: () -> @$("#flatViewButton").button("toggle")
- toggleSplitViewButton: () -> @$("#splitViewButton").button("toggle")
-
- downloadPdf: () ->
- @options.manager.downloadPdf()
-
- delegateEvents: () ->
- Backbone.View::delegateEvents.apply(this, arguments)
- @pdfView.delegateEvents()
-
- undelegateEvents: () ->
- Backbone.View::undelegateEvents.apply(this, arguments)
- @pdfView.undelegateEvents()
-
- highlightInPdf: (args...) ->
- @pdfView.highlightInPdf?(args...)
-
- getPdfPosition: () ->
- @pdfView.getPdfPosition?()
-
-
diff --git a/services/web/public/coffee/pdf/NativePdfView.coffee b/services/web/public/coffee/pdf/NativePdfView.coffee
deleted file mode 100644
index 045d889556..0000000000
--- a/services/web/public/coffee/pdf/NativePdfView.coffee
+++ /dev/null
@@ -1,22 +0,0 @@
-define [
- "libs/backbone"
-], () ->
- NativePdfView = Backbone.View.extend
- tagName: "iframe"
-
- render: () ->
- @$el.css
- border: "none"
- width: "100%"
- height: "100%"
- return @
-
- setPdf: (url) ->
- @$el.attr "src", url
-
- unsetPdf: () ->
- @$el.removeAttr "src"
-
- hide: () -> @$el.hide()
- show: () -> @$el.show()
-
diff --git a/services/web/public/coffee/pdf/PDFjsView.coffee b/services/web/public/coffee/pdf/PDFjsView.coffee
deleted file mode 100644
index e69ede132b..0000000000
--- a/services/web/public/coffee/pdf/PDFjsView.coffee
+++ /dev/null
@@ -1,168 +0,0 @@
-define [
- "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"
- "libs/backbone"
- "libs/jquery.storage"
-], (PDFListView, TextLayerBuilder, AnnotationsLayerBuilder, HighlightsLayerBuilder, textLayerCss, annotationsLayerCss, highlightsLayerCss) ->
- if PDFJS?
- PDFJS.workerSrc = "#{window.sharelatex.pdfJsWorkerPath}"
-
- style = $("")
- style.text(textLayerCss + "\n" + annotationsLayerCss + "\n" + highlightsLayerCss)
- $("body").append(style)
-
- PDFjsView = Backbone.View.extend
- template: $("#pdfjsViewerTemplate").html()
-
- events:
- "mousemove" : "onMousemove"
- "mouseout" : "onMouseout"
- "click .js-fit-height" : "fitToHeight"
- "click .js-fit-width" : "fitToWidth"
- "click .js-zoom-out" : "zoomOut"
- "click .js-zoom-in" : "zoomIn"
-
- initialize: () ->
- @ide = @options.manager.ide
-
- render: () ->
- @setElement $(@template)
- @pdfListView = new PDFListView @$(".pdfjs-list-view")[0],
- textLayerBuilder: TextLayerBuilder
- annotationsLayerBuilder: AnnotationsLayerBuilder
- highlightsLayerBuilder: HighlightsLayerBuilder
- ondblclick: (e) =>
- @trigger "dblclick", e
- #logLevel: PDFListView.Logger.DEBUG
- @pdfListView.listView.pageWidthOffset = 20
- @pdfListView.listView.pageHeightOffset = 20
- @toolbar = @$(".btn-group")
- @toolbar.hide()
- @progress_bar = @$(".progress")
- @hideProgressBar()
- return @
-
- show: () ->
- @$el.show()
- @pdfListView.onResize()
-
- hide: () -> @$el.hide()
-
- setPdf: (url) ->
- @setProgressBarTo(0)
- onProgress = (progress) =>
- @setProgressBarTo(progress.loaded/progress.total)
- @pdfListView
- .loadPdf(url, onProgress)
- .then () =>
- @hideProgressBar()
- if !@setInitialPosition
- @_initInitialPosition()
- @setInitialPosition = true
- @flashToolbar()
-
- unsetPdf: () -> @hide()
-
- _initInitialPosition: () ->
- if (scale = $.localStorage("pdf.scale"))?
- @pdfListView.setScaleMode(scale.scaleMode, scale.scale)
- else
- @pdfListView.setToFitWidth()
-
- if (position = $.localStorage("pdf.position.#{@ide.project.get("id")}"))
- @pdfListView.setPdfPosition(position)
-
- $(window).unload () =>
- $.localStorage "pdf.scale", {
- scaleMode: @pdfListView.getScaleMode()
- scale: @pdfListView.getScale()
- }
- $.localStorage "pdf.position.#{@ide.project.get("id")}", @pdfListView.getPdfPosition()
-
- setProgressBarTo: (value) ->
- @progress_bar.show()
- @progress_bar.find(".bar").css width: "#{value * 100}%"
-
- hideProgressBar: () ->
- @progress_bar.hide()
-
- fitToHeight: () ->
- @pdfListView.setToFitHeight()
-
- fitToWidth: () ->
- @pdfListView.setToFitWidth()
-
- zoomIn: () ->
- scale = @pdfListView.getScale()
- @pdfListView.setScale(scale * 1.2)
-
- zoomOut: () ->
- scale = @pdfListView.getScale()
- @pdfListView.setScale(scale / 1.2)
-
- flashToolbar: () ->
- @toolbar.show()
- setTimeout =>
- @toolbar.fadeOut "slow"
- , 1000
-
- onMousemove: (e) ->
- viewerOffset = @$el.offset()
- offsetY = e.pageY - viewerOffset.top
- offsetX = e.pageX - viewerOffset.left
- if offsetY < 70 and offsetX < 250
- @toolbar.show()
- else
- @toolbar.fadeOut "slow"
-
- onMouseout: (e) ->
- @toolbar.hide()
-
- onResize: () ->
- @pdfListView.onResize()
-
- highlightInPdf: (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 () =>
- @$(".pdfjs-list-view .plv-highlights-layer > div").fadeOut "slow", () =>
- @pdfListView.clearHighlights()
- , 1000
-
- getPdfPosition: () ->
- position = @pdfListView.getPdfPosition(true)
- return if !position?
- return {
- page: position.page
- x: position.offset.left
- y: position.offset.top
- }
-
-
-
-
diff --git a/services/web/public/coffee/pdf/PdfManager.coffee b/services/web/public/coffee/pdf/PdfManager.coffee
deleted file mode 100644
index 5c2738444c..0000000000
--- a/services/web/public/coffee/pdf/PdfManager.coffee
+++ /dev/null
@@ -1,302 +0,0 @@
-define [
- "utils/Modal"
- "pdf/CompiledView"
- "pdf/SyncButtonsView"
- "libs/latex-log-parser"
- "libs/jquery.storage"
- "underscore"
- "libs/backbone"
-], (Modal, CompiledView, SyncButtonsView, LogParser) ->
- class PdfManager
- templates:
- pdfLink: $("#pdfSideBarLinkTemplate").html()
-
- constructor: (@ide) ->
- _.extend @, Backbone.Events
- @createSyncButtons()
- @createPdfPanel()
- @ide.editor.aceEditor.commands.addCommand
- name: "compile",
- bindKey: win: "Ctrl-Enter", mac: "Command-Enter"
- exec: (editor) =>
- @refreshPdf()
- readOnly: true
-
- @ide.editor.aceEditor.commands.removeCommand "replace"
-
- createPdfPanel: () ->
- @view = new CompiledView manager: @, ide: @ide
- @view.on "dblclick", (e) => @syncToCode(e)
- @view.render()
- if $.localStorage("layout.pdf") == "flat"
- @switchToFlatView()
- else if $.localStorage("layout.pdf") == "split"
- @switchToSplitView()
- else if $(window).width() < 1024
- @switchToFlatView()
- else
- @switchToSplitView()
-
- createSyncButtons: () ->
- unless @ide.userSettings.pdfViewer == "native"
- @syncButtonsView = new SyncButtonsView(ide: @ide)
- @syncButtonsView.on "click:sync-code-to-pdf", () =>
- @syncToPdf()
- @syncButtonsView.on "click:sync-pdf-to-code", () =>
- @syncToCode()
- @syncButtonsView.hide()
-
- switchToFlatView: (options = {showPdf: false}) ->
- @teardownSplitView()
- @setupFlatView()
- @view.toggleFlatViewButton()
- if options.showPdf
- @ide.sideBarView.selectLink "pdf"
- @ide.mainAreaManager.change "pdf"
- @view.resize()
-
- switchToSplitView: () ->
- @teardownFlatView()
- @setupSplitView()
- @view.toggleSplitViewButton()
- @view.resize()
- @ide.editor.setIdeToEditorPanel()
-
- setupFlatView: () ->
- @teardownFlatView()
- @ide.editor.switchToFlatView()
- pdfLink = $(@templates.pdfLink)
- @ide.sideBarView.addLink
- identifier : "pdf"
- before : "history"
- element : pdfLink
- pdfLink.on "click", (e) => @showPdfPanel()
-
- @ide.mainAreaManager.addArea
- identifier: "pdf"
- element: @view.$el
- @view.resize()
-
- @view.undelegateEvents()
- @view.delegateEvents()
-
-
- teardownFlatView: () ->
- @ide.sideBarView.removeLink("pdf")
- @ide.mainAreaManager.removeArea("pdf")
- @view.afterSwitchView()
-
- setupSplitView: () ->
- @ide.editor.switchToSplitView()
- @ide.editor.rightPanel.append(
- @view.$el
- )
- @view.$el.show()
- @view.resize()
-
- @view.undelegateEvents()
- @view.delegateEvents()
-
- @ide.editor.$splitter.append(
- @syncButtonsView?.$el
- )
-
- setTimeout(@ide.layoutManager.resizeAllSplitters, 100)
-
- teardownSplitView: () ->
- @view.afterSwitchView()
-
- showPdfPanel: () ->
- @ide.sideBarView.selectLink 'pdf'
- @ide.mainAreaManager.change 'pdf'
- @view.resize()
-
- if !@view.hasPdf()
- @refreshPdf()
-
- showRawLogPanel: () ->
- @ide.mainAreaManager.change 'rawLog'
-
- refreshPdf: (opts) ->
- if @ide.project?
- @_refreshPdfWhenProjectIsLoaded(opts)
- else
- @ide.on "afterJoinProject", () =>
- @_refreshPdfWhenProjectIsLoaded(opts)
-
- _refreshPdfWhenProjectIsLoaded: (opts = {}) ->
-
- if !@ide.project.get("rootDoc_id")?
- new Modal
- title: "No root document selected"
- message: "First you need to choose a root document via the settings menu. This tells ShareLaTeX which file to run LaTeX on."
- buttons: [{
- text: "OK",
- class: "btn-primary"
- }]
- else if !@compiling
- @view.onCompiling()
- @syncButtonsView?.hide()
- @compiling = true
- @_doCompile opts, (error, status, outputFiles) =>
- @compiling = false
- @view.doneCompiling()
- @syncButtonsView?.show()
-
- if error?
- @view.updateLog(systemError: true)
- @view.unsetPdf()
- @view.showLog()
- else if status == "timedout"
- @view.updateLog(timedOut: true)
- @view.unsetPdf()
- @view.showLog()
- else if status == "autocompile-backoff"
- @view.showBeforeCompile()
- else
- pdfExists = (status == "success")
- @fetchLogAndUpdateView(pdfExists)
-
- if pdfExists
- @view.setPdf("/project/#{@ide.project_id}/output/output.pdf?cache_bust=#{Date.now()}")
- @view.showPdf()
- else
- @view.unsetPdf()
- @view.showLog()
- @syncButtonsView?.hide()
-
- if outputFiles?
- @view.showOutputFileDownloadLinks(outputFiles)
-
- _doCompile: (opts, callback = (error, status, outputFiles) ->) ->
- url = "/project/#{@ide.project_id}/compile"
- if opts.isAutoCompile
- url += "?auto_compile=true"
- $.ajax(
- url: url
- type: "POST"
- headers:
- "X-CSRF-Token": window.csrfToken
- contentType: "application/json; charset=utf-8"
- dataType: 'json'
- data: JSON.stringify settingsOverride:
- rootDoc_id: opts.rootDocOverride_id ? null
- success: (body, status, response) ->
- callback null, body.status, body.outputFiles
- error: (error) ->
- callback error
- )
-
- fetchLogAndUpdateView: (pdfExists) ->
- $.ajax(
- url: "/project/#{@ide.project_id}/output/output.log"
- success: (body, status, response) =>
- @parseLogAndUpdateView(pdfExists, body)
- error: () =>
- @view.updateLog(pdfExists: pdfExists, logExists: false)
- )
-
- parseLogAndUpdateView: (pdfExists, log) ->
- errors = LogParser.parse(log, ignoreDuplicates: true)
- lastCompileErrors = {}
- for error in errors.all
- error.file = @_normalizeFilePath(error.file)
-
- doc_id = @ide.fileTreeManager.getDocIdOfPath(error.file)
- if doc_id?
- lastCompileErrors[doc_id] ||= []
- lastCompileErrors[doc_id].push
- row: error.line - 1
- type: if error.level == "error" then "error" else "warning"
- text: error.message
- @ide.editor.compilationErrors = lastCompileErrors
- @ide.editor.refreshCompilationErrors()
-
- @view.updateLog(pdfExists: pdfExists, logExists: true, compileErrors: errors, rawLog: log)
-
- _normalizeFilePath: (path) ->
- path = path.replace(/^compiles\/[0-9a-f]{32}\/(\.\/)?/, "")
- path = path.replace(/^\/compile\//, "")
-
- rootDoc_id = @ide.project.get("rootDoc_id")
- if rootDoc_id?
- rootDocPath = @ide.fileTreeManager.getPathOfEntityId(rootDoc_id)
- if rootDocPath?
- rootDocDir = rootDocPath.split("/").slice(0,-1).map( (part) -> part + "/" ).join("")
- path = path.replace(/^\.\//, rootDocDir)
-
- return path
-
- downloadPdf: () ->
- @ide.mainAreaManager.setIframeSrc "/project/#{@ide.project_id}/output/output.pdf?popupDownload=true"
-
- deleteCachedFiles: () ->
- modal = new Modal
- title: "Clear cache?"
- message: "This will clear all hidden LaTeX files like .aux, .bbl, etc, from our compile server. You generally don't need to do this unless you're having trouble with references. Your project files will not be deleted or changed."
- buttons: [{
- text: "Cancel"
- }, {
- text: "Clear from cache",
- class: "btn-primary",
- close: false
- callback: ($button) =>
- $button.text("Clearing...")
- $button.prop("disabled", true)
- $.ajax({
- url: "/project/#{@ide.project_id}/output"
- type: "DELETE"
- headers:
- "X-CSRF-Token": window.csrfToken
- complete: () -> modal.remove()
- })
-
- }]
-
- syncToCode: (e) ->
- if !e?
- e = @view.getPdfPosition()
- return if !e?
- # It's not clear exactly where we should sync to if it was directly
- # clicked on, but a little bit down from the very top seems best.
- e.y = e.y + 80
-
- $.ajax {
- url: "/project/#{@ide.project_id}/sync/pdf"
- data:
- page: e.page + 1
- h: e.x.toFixed(2)
- v: e.y.toFixed(2)
- type: "GET"
- success: (response) =>
- data = JSON.parse(response)
- if data.code and data.code.length > 0
- file = data.code[0].file
- line = data.code[0].line
- @ide.fileTreeManager.openDocByPath(file, line)
- }
-
- syncToPdf: () ->
- entity_id = @ide.editor.getCurrentDocId()
- file = @ide.fileTreeManager.getPathOfEntityId(entity_id)
-
- # If the root file is folder/main.tex, then synctex sees the
- # path as folder/./main.tex
- rootFolderPath = @ide.fileTreeManager.getRootFolderPath()
- if rootFolderPath != ""
- file = file.replace(RegExp("^#{rootFolderPath}"), "#{rootFolderPath}/.")
-
- line = @ide.editor.getCurrentLine()
- column = @ide.editor.getCurrentColumn()
-
- $.ajax {
- url: "/project/#{@ide.project_id}/sync/code"
- data:
- file: file
- line: line + 1
- column: column
- type: "GET"
- success: (response) =>
- data = JSON.parse(response)
- @view.highlightInPdf(data.pdf or [])
- }
diff --git a/services/web/public/coffee/pdf/SyncButtonsView.coffee b/services/web/public/coffee/pdf/SyncButtonsView.coffee
deleted file mode 100644
index 0f8de8690f..0000000000
--- a/services/web/public/coffee/pdf/SyncButtonsView.coffee
+++ /dev/null
@@ -1,67 +0,0 @@
-define [
- "libs/backbone"
- "libs/mustache"
-], () ->
- SyncButtonsView = Backbone.View.extend
- template: $("#syncButtonsTemplate").html()
-
- events:
- "click .sync-code-to-pdf": () ->
- ga('send', 'event', 'editor-interaction', 'sync-code-to-pdf')
- @trigger "click:sync-code-to-pdf"
- "click .sync-pdf-to-code": () ->
- ga('send', 'event', 'editor-interaction', 'sync-pdf-to-code')
- @trigger "click:sync-pdf-to-code"
-
- initialize: (options) ->
- @render()
- @ide = options.ide
- @ide.editor.on "resize", => @repositionLeft()
-
- render: () ->
- @setElement($(@template))
-
- ### These keep screwing up in the UI :(
- @$(".sync-code-to-pdf").tooltip({
- title: "Go to code location in the output"
- animate: false
- placement: "top"
- trigger: "hover"
- delay:
- show: 800
- hide: 0
- })
- @$(".sync-pdf-to-code").tooltip({
- html: true
- title: "Go to output location in the code
(Or double click the output)"
- animate: false
- placement: "bottom"
- trigger: "hover"
- delay:
- show: 800
- hide: 0
- })
- ###
- return @
-
- hide: () ->
- @hidden = true
- @$el.hide()
-
- show: () ->
- @hidden = false
- @showIfNotHiddenAndOpen()
-
- showIfNotHiddenAndOpen: () ->
- state = @ide.editor.$splitter.layout().readState()
- if !@hidden and !state.east?.initClosed
- @$el.show()
- else
- @$el.hide()
-
- repositionLeft: () ->
- state = @ide.editor.$splitter.layout().readState()
- if state.east?
- @$el.css({right: state.east.size - 8})
- @showIfNotHiddenAndOpen();
-
diff --git a/services/web/public/coffee/project-members/ProjectMembersManager.coffee b/services/web/public/coffee/project-members/ProjectMembersManager.coffee
deleted file mode 100644
index a93b8c09e9..0000000000
--- a/services/web/public/coffee/project-members/ProjectMembersManager.coffee
+++ /dev/null
@@ -1,366 +0,0 @@
-define [
- "models/User"
- "models/ProjectMemberList"
- "account/AccountManager"
- "utils/Modal"
- "moment"
- "libs/backbone"
- "libs/mustache"
-], (User, ProjectMemberList, AccountManager, Modal, moment) ->
- INFINITE_COLLABORATORS = -1
-
- class ProjectMembersManager
- templates:
- userPanel: $("#userPanelTemplate").html()
-
- constructor: (@ide, options) ->
- options || = {}
- setupArea = _.once =>
- @ide.tabManager.addTab
- id : "collaborators"
- name: "Share"
- content : $(@templates.userPanel)
- lock: true
- onShown: () =>
- @publishProjectView?.refreshPublishStatus()
-
- setupPublish = _.once =>
- if @ide.security? and @ide.security.permissionsLevel == "owner"
- @publishProjectView = new PublishProjectView
- ide: @ide
- el: $("#publishProject")
- @publishProjectView.render()
-
- setupArea()
- if @ide?
- @ide.on "afterJoinProject", (project) =>
- @project = project
- if !@view?
- @members = @project.get("members")
- @view = new ProjectMemberListView
- ide: @ide
- collection: @members
- el: $('#projectMembersList')
- manager: this
-
- allowedCollaborators = @project.get("features").collaborators
- allowedMembers = allowedCollaborators + 1 # include owner
- if @members.length > allowedMembers and allowedCollaborators != INFINITE_COLLABORATORS
- AccountManager.showUpgradeDialog @ide,
- message: "This project has too many collaborators for your plan. Please upgrade your account or remove some collaborators"
-
- if @ide.security? and @ide.security.permissionsLevel == "owner"
- @view.options.showAdminControls = true
- else
- @view.options.showAdminControls = false
- @view.render()
-
- if @ide.project.get("owner") == @ide.user and @ide.user.get("id") != "openUser"
- setupPublish()
-
- if @ide.project.get("owner") == @ide.user or @ide.project.get("publicAccesLevel") != "private"
- if !@socialView?
- @socialView = new SocialSharingView
- ide: @ide
- el: $("#socialSharing")
- @socialView.render()
- else
- $("#socialSharing").hide()
-
- if @ide.socket?
- @ide.socket.on "userRemovedFromProject", (userId) =>
- @afterMemberRemoved(userId)
- @ide.socket.on "userAddedToProject", (user, privileges) =>
- @afterMemberAdded(user, privileges)
-
- removeMember: (member) ->
- @ide.socket.emit "removeUserFromProject", member.id
-
- addMember: (email, privileges) ->
- console.log "Adding member", email
- @ide.socket.emit "addUserToProject", email, privileges, (error, added) =>
- if error?
- @ide.showGenericServerErrorMessage()
- return
- if !added
- console.log "got response", error, added
- ga('send', 'event', 'subscription-funnel', 'askToUpgrade', "projectMemebrs")
- AccountManager.askToUpgrade @ide,
- why: "to add additional collaborators"
- onUpgrade: () =>
- ga('send', 'event', 'subscription-funnel', 'upgraded-free-trial', "projectMemebrs")
-
- afterMemberRemoved: (memberId) ->
- for member in @members.models
- if member.id == memberId
- toRemove = member
- @members.remove(toRemove) if toRemove?
-
- afterMemberAdded: (member, privileges) ->
- @members.add new User
- email : member.email
- id : member._id
- privileges : privileges
-
- ProjectMemberListView = Backbone.View.extend
- template : $("#projectMemberListTemplate").html()
-
- events:
- "click .addUser" : "addMember"
-
- initialize: ->
- @itemViews = {}
- @options.showAdminControls ||= false
- @collection.on "add", (model) =>
- @addItem model
- @collection.on "reset", (collection) =>
- @addItem model for model in collection.models
- @collection.on "remove", (model) =>
- @removeItem model
-
- render: ->
- $(@el).html Mustache.to_html(@template, showAdminControls: @options.showAdminControls)
- @addItem model for model in @collection.models
- return this
-
- addItem: (model) ->
- view = new ProjectMemberListItemView
- model : model
- showRemove : @options.showAdminControls and model.get("privileges") != "owner"
- manager: @options.manager
- @itemViews[model.id] = view
- @$("tbody").append view.$el
-
- removeItem: (model) ->
- if @itemViews[model.id]?
- @itemViews[model.id].remove()
- delete @itemViews[model.id]
-
- addMember: (e) ->
- e.preventDefault()
- email = @$(".addUserForm .email").val()
- privileges= @$(".addUserForm .privileges").val()
- if email.indexOf('@') == -1
- @options.ide.showErrorModal("Invalid Email", "#{email} is not a valid email address")
- else
- @options.manager.addMember email, privileges
- @$(".addUserForm .email").val("")
-
-
- ProjectMemberListItemView = Backbone.View.extend
- template : $("#projectMemberListItemTemplate").html()
-
- events:
- "click .removeUser": "removeMember"
-
- initialize: -> @render()
-
- render: ->
- @setElement $(Mustache.to_html(@template, @modelView()))
- return this
-
- modelView: ->
- modelView = @model.toJSON()
- modelView.privileges = {
- readOnly : "Read Only"
- readAndWrite : "Read and Write"
- owner : "Owner"
- }[modelView.privileges]
- modelView.showRemove = @options.showRemove
- return modelView
-
- removeMember: (e) ->
- e.preventDefault()
- @options.manager.removeMember(@model)
-
- PublishProjectView = Backbone.View.extend
- template: $("#publishProjectTemplate").html()
-
- events:
- "click #publishProjectAsTemplate": "publishProjectAsTemplate"
- "click #republishProjectAsTemplate": "publishProjectAsTemplate"
- "click #unPublishProjectAsTemplate": "unPublishProjectAsTemplate"
- "blur #projectDescription" : "updateDescription"
-
- initialize: () ->
- @ide = @options.ide
- @model = @ide.project
- @render()
-
- render: ->
- viewModel =
- description: @model.get("description")
- canonicalUrl: @model.get("template.canonicalUrl")
- isPublished: @model.get("template.isPublished")
- publishedDate: moment(@model.get("template.publishedDate")).format("Do MMM YYYY, h:mm a")
-
- @$el.html $(Mustache.to_html(@template, viewModel))
- @publishedArea = $('.show-when-published')
- @unpublishedArea = $('.show-when-unpublished')
- $('#problemWithPublishingArea').hide()
- $('#publishWorkingArea').hide()
-
- refreshView: () ->
- if @model.get("template.isPublished")
- @$("a#templateLink").attr("href", @model.get("template.canonicalUrl"))
- @publishedArea.show()
- else
- @unpublishedArea.show()
-
- refreshPublishStatus: ->
- @showWorking()
- @ide.socket.emit "getPublishedDetails", @ide.user.get("id"), (err, details)=>
- @hideWorking()
- if err?
- return @showError()
-
- @model.set("template.isPublished", details.exists)
- if details.exists
- @model.set("template.canonicalUrl", details.canonicalUrl)
- @model.set("template.publishedDate", details.publishedDate)
-
- @refreshView()
-
- showError: ->
- @publishedArea.hide()
- @unpublishedArea.hide()
- $('#problemWithPublishingArea').show()
-
- showWorking: ->
- @publishedArea.hide()
- @unpublishedArea.hide()
- $('#publishWorkingArea').show()
-
- hideWorking: ->
- $('#publishWorkingArea').hide()
-
- publishProjectAsTemplate: ->
- @showWorking()
- @unpublishedArea.hide()
- @publishedArea.hide()
- @ide.socket.emit "publishProjectAsTemplate", @ide.user.get("id"), (err)=>
- @hideWorking()
- if err?
- @showError()
- else
- @refreshPublishStatus()
-
- unPublishProjectAsTemplate: ->
- @showWorking()
- @publishedArea.hide()
- @ide.socket.emit "unPublishProjectAsTemplate", @ide.user.get("id"), (err)=>
- @hideWorking()
- if err?
- @showError()
- else
- @refreshPublishStatus()
-
- updateDescription: ->
- newDescription = $('#projectDescription').val()
- @model.set("description", newDescription)
-
- SocialSharingView = Backbone.View.extend
- template: $("#socialSharingTemplate").html()
-
- events:
- "click .btn-facebook": "postToFacebook"
- "click .btn-twitter": "postToTwitter"
- "click .btn-google-plus": "postToGoogle"
- "click .btn-url": "shareUrl"
-
- initialize: () ->
- @ide = @options.ide
-
- url: (medium) ->
- "#{window.sharelatex.siteUrl}/project/#{@ide.project.get("id")}" +
- "?r=#{@ide.user.get("referal_id")}&rs=ps&rm=#{medium}" # Referal source = public share
-
- render: ->
- $(@el).html $(@template)
-
- postToFacebook: () ->
- @ensurePublic (error, success) =>
- if success
-
- url = "https://www.facebook.com/dialog/feed?link=#{encodeURIComponent(@url("fb"))}&" +
- "app_id=148710621956179&" +
- "picture=#{window.sharelatex.siteUrl}/brand/logo/logo-128.png&" +
- "name=#{@ide.project.get("name")}&" +
- "caption=My LaTeX project (#{@ide.project.get("name")}) is available online on ShareLaTeX&" +
- "redirect_uri=#{window.sharelatex.siteUrl}&" +
- "display=popup"
- ga('send', 'event', 'editor-interaction', 'project-shared', "facebook")
- window.open(
- url
- ""
- 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600'
- )
-
- postToTwitter: () ->
- @ensurePublic (error, success) =>
- if success
- ga('send', 'event', 'editor-interaction', 'project-shared', "twitter")
- window.open(
- "https://www.twitter.com/share/?text=Check out my online LaTeX Project: #{@ide.project.get("name")}&url=#{encodeURIComponent(@url("t"))}"
- ""
- 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600'
- )
-
- postToGoogle: () ->
- @ensurePublic (error, success) =>
- if success
- ga('send', 'event', 'editor-interaction', 'project-shared', "google-plus")
- window.open(
- "https://plus.google.com/share?url=#{encodeURIComponent(@url("gp"))}"
- ""
- 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600'
- )
-
- shareUrl: () ->
- @ensurePublic (error, success) =>
- if success
- ga('send', 'event', 'editor-interaction', 'project-shared', "url")
- Modal.createModal
- el: $(
- "You can share you project with your friends and colleagues via this URL:
" +
- "#{@url("d")}
"
- )
- title: "Share your project"
- buttons: [
- text: "OK"
- class: "btn btn-primary"
- ]
-
- ensurePublic: (callback = (error, success) ->) ->
- accessLevel = @ide.project.get("publicAccesLevel")
- if accessLevel == "private"
- Modal.createModal
- title: "Make project public?"
- message: "Your project needs to be public before you can share it. This means anyone with the URL will be able to access it. Would you like to make your project public?"
- buttons: [{
- text: "Cancel"
- class: "btn"
- callback: () =>
- callback null, false
- }, {
- text: "Yes, make publicly readable"
- class: "btn btn-primary"
- callback: () =>
- @ide.project.set("publicAccesLevel", "readOnly")
- callback null, true
- }, {
- text: "Yes, make publicly editable"
- class: "btn btn-primary"
- callback: () =>
- @ide.project.set("publicAccesLevel", "readAndWrite")
- callback null, true
- }]
- else
- callback null, true
-
- return {
- ProjectMembersManager : ProjectMembersManager
- ProjectMemberList : ProjectMemberList
- ProjectMemberListView : ProjectMemberListView
- }
-
diff --git a/services/web/public/coffee/search/SearchManager.coffee b/services/web/public/coffee/search/SearchManager.coffee
deleted file mode 100644
index fc44cfaf26..0000000000
--- a/services/web/public/coffee/search/SearchManager.coffee
+++ /dev/null
@@ -1,18 +0,0 @@
-define [
- 'search/searchbox'
-], (searchbox) ->
- class SearchManager
- constructor: (@ide) ->
- @ide.editor.aceEditor.commands.addCommand
- name: "find",
- bindKey: win: "Ctrl-F", mac: "Command-F"
- exec: (editor) ->
- searchbox.Search(editor)
- readOnly: true
-
- @ide.editor.on "showCommandLine", (editor, arg) =>
- if arg == "/"
- searchbox.Search(editor)
-
- @ide.editor.aceEditor.commands.removeCommand "replace"
-
diff --git a/services/web/public/coffee/settings/DropboxSettingsManager.coffee b/services/web/public/coffee/settings/DropboxSettingsManager.coffee
deleted file mode 100644
index b4fd801bd3..0000000000
--- a/services/web/public/coffee/settings/DropboxSettingsManager.coffee
+++ /dev/null
@@ -1,55 +0,0 @@
-define [
- "utils/Modal"
- "account/AccountManager"
- "underscore"
-], (Modal, accountManager, _) ->
- _.templateSettings = interpolate : /\{\{(.+?)\}\}/g
- POLLING_INTERVAL = 15
- ONE_MIN_MILI = 1000*60
- USER_LINKED_TEMPLATE = _.template($('#userLinkedToDropboxTemplate').html())
-
- class DropboxSettingsManger
- constructor: (@ide)->
-
- setup = _.once =>
- @tab = $("#dropboxProjectSettings")
- tabLink = $('#manageDropboxSettiingsTabLink').on 'click', (event)=>
- @tab.empty()
- if !@ide.isAllowedToDoIt "owner"
- else if !@project.get('features').dropbox
- ga('send', 'event', 'subscription-funnel', 'askToUpgrade', "dropbox")
- accountManager.askToUpgrade @ide,
- onUpgrade: =>
- @checkIfUserIsLinkedToDropbox()
- ga('send', 'event', 'subscription-funnel', 'upgraded-free-trial', "dropbox")
- else
- @checkIfUserIsLinkedToDropbox()
-
- @ide.on "afterJoinProject", (project) =>
- @project = project
- setup()
-
- checkIfUserIsLinkedToDropbox : =>
- @ide.socket.emit "getUserDropboxLinkStatus", @project.get("owner").id, (err, status)=>
- if status.registered
- @buildLinkedView()
- else
- @buildNonLinkedView()
-
- buildLinkedView: ->
- self = @
- run = ->
- @ide.socket.emit "getLastTimePollHappned", (err, lastTimePollHappened)=>
- milisecondsSinceLastPoll = new Date().getTime() - lastTimePollHappened
- roundedMinsSinceLastPoll = Math.round(milisecondsSinceLastPoll / ONE_MIN_MILI)
-
- minsTillNextPoll = POLLING_INTERVAL - roundedMinsSinceLastPoll
- percentageLeftTillNextPoll = 100 - ((roundedMinsSinceLastPoll / POLLING_INTERVAL) * 100)
-
- html = $(USER_LINKED_TEMPLATE(minsTillNextPoll:minsTillNextPoll, percentageLeftTillNextPoll:percentageLeftTillNextPoll))
- self.tab.empty()
- self.tab.append html
- setTimeout run, ONE_MIN_MILI
- run()
- buildNonLinkedView: ->
- @tab.append $('#userNotLinkedToDropboxTemplate').html()
diff --git a/services/web/public/coffee/settings/SettingsManager.coffee b/services/web/public/coffee/settings/SettingsManager.coffee
deleted file mode 100644
index 47791833de..0000000000
--- a/services/web/public/coffee/settings/SettingsManager.coffee
+++ /dev/null
@@ -1,219 +0,0 @@
-define [
- "utils/Modal"
- "settings/DropboxSettingsManager"
-], (Modal, DropboxSettingsManager) ->
- class SettingsManager
- templates:
- settingsPanel: $("#settingsPanelTemplate").html()
-
- constructor: (@ide, options) ->
- options || = {}
- setup = _.once =>
- @bindToProjectName()
- @bindToPublicAccessLevel()
- @bindToCompiler()
- @bindToRootDocument()
- @bindToSpellingPreference()
-
- new DropboxSettingsManager @ide
-
- @setFontSize()
-
- if @ide?
- @ide.on "afterJoinProject", (project) =>
- @project = project
- setup()
-
- settingsPanel = $(@templates.settingsPanel)
- @ide.tabManager.addTab
- id: "settings"
- name: "Settings"
- content: settingsPanel
- lock: true
-
- $('#DownloadZip').click (event)=>
- event.preventDefault()
- @ide.mainAreaManager.setIframeSrc "/project/#{@ide.project_id}/download/zip"
-
- $("#deleteProject").click (event)=>
- event.preventDefault()
- self = @
- deleteProject = =>
- $.ajax
- url: "/Project/#{@ide.project_id}",
- type: 'DELETE'
- data:
- _csrf: window.csrfToken
- success: ->
- window.location = '/'
- modalOptions =
- templateId:'deleteEntityModal'
- isStatic: false
- title:'Delete Project'
- message: "Are you sure you want to delete this project?"
- buttons: [{
- text : "Cancel",
- class : "btn",
- }, {
- text : "Delete Forever",
- class : "btn-danger confirm",
- callback : deleteProject
- }]
- Modal.createModal modalOptions
-
- $('.cloneProject').click (event)=>
- event.stopPropagation()
- event.preventDefault()
- goToRegPage = ->
- window.location = "/register"
-
- modalOptions =
- isStatic: false
- title:'Registration Required'
- message: "You need to register to clone a project"
- buttons: [{
- text : "Cancel",
- class : "btn",
- }, {
- text : "Register Now",
- class : "btn-success confirm",
- callback : goToRegPage
- }]
-
- user = @project.get("ide").user
- if user.id == "openUser"
- return Modal.createModal modalOptions
-
- $modal = $('#cloneProjectModal')
- $confirm = $('#confirmCloneProject')
- $modal.modal({backdrop:true, show:true, keyboard:true})
- $modal.find('input').val('').focus()
- self = @
- $confirm.click (e)->
- $confirm.attr("disabled", true)
- $confirm.text("Cloning...")
- projectName = $modal.find('input').val()
- $.ajax
-
- url: "/project/#{self.ide.project_id}/clone"
- type:'POST'
- data:
- projectName: projectName
- _csrf: window.csrfToken
- success: (data)->
- if data.redir?
- window.location = data.redir
- else if data.project_id?
- window.location = '/project/'+data.project_id
- $modal.on 'hide', ->
- $confirm.off 'click'
- $modal.find('.cancel').click (e)->
- $modal.modal('hide')
-
- setFontSize: () ->
- @fontSizeCss = $("")
- @fontSizeCss.text """
- .ace_editor, .ace_content {
- font-size: #{window.userSettings.fontSize}px;
- }
- """
- $(document.body).append(@fontSizeCss)
-
- bindToProjectName: () ->
- @project.on "change:name", (project, newName) ->
- $element = $('.projectName')
- $element.text(newName)
- window.document.title = newName
- $("input.projectName").val(newName)
-
- $("input.projectName").on "change", (e)=>
- # Check if event was triggered by the user, re:
- # http://stackoverflow.com/questions/6692031/check-if-event-is-triggered-by-a-human
- if e.originalEvent?
- if @ide.isAllowedToDoIt "readAndWrite"
- newName = e.target.value?.trim()
- $("input.projectName").val(newName)
- @project.set("name", newName)
-
-
- bindToCompiler: ->
- $('select#compilers').val(@project.get("compiler"))
-
- $('select#compilers').change (e)=>
- if @ide.isAllowedToDoIt "readAndWrite"
- @project.set("compiler", e.target.value)
-
-
-
- bindToSpellingPreference: ->
-
- $('select#spellCheckLanguageSelection').on "change", (e)=>
- languageCode = e.target.value
- @project.set("spellCheckLanguage", languageCode)
-
-
- bindToRootDocument: () ->
- $('#rootDocList').change (event)=>
- docId = $(event.target).val()
- @project.set("rootDoc_id", docId)
-
- # Repopulate the root document list when the settings page is shown. Updating
- # it in real time is just a little too complicated
- do refreshDocList = =>
- $docList = $('select#rootDocList')
- $docList.empty()
- @project.getRootDocumentsList (listOfDocs) =>
- template = _.template($('#rootDocListEntity').html())
- _.each listOfDocs, (doc)=>
- option = $(template(name:doc.path))
- option.attr('value', doc._id)
- $docList.append(option)
- hasRootDoc = _.find listOfDocs, (doc)=>
- if doc._id == @project.get("rootDoc_id")
- return true
- if hasRootDoc
- $docList.val(@project.get("rootDoc_id"))
- else
- option = $(template(name:"No Root Document Selected!"))
- option.attr('value', 'blank')
- $docList.append(option)
- $docList.val('blank')
-
- $('#settings-tab-li').on "click", refreshDocList
-
- bindToPublicAccessLevel: () ->
- $('select#publicAccessLevel').val(@project.get("publicAccesLevel"))
-
- @project.on "change:publicAccesLevel", (project, level) ->
- $('select#publicAccessLevel').val(level)
-
- $("select#publicAccessLevel").on "change", (event)=>
- newSetting = event.target.value
- cancelChange = =>
- @project.set("publicAccesLevel", "private")
- doChange = () =>
- @project.set("publicAccesLevel", newSetting)
- modalOptions =
- buttons: [{
- text : "Cancel",
- class : "btn",
- callback : cancelChange
- },{
- text : "OK",
- class : "btn-danger confirm",
- callback : doChange
- }]
-
- if newSetting == 'readOnly'
- modalOptions.title = 'Make Project Public - Read Only'
- modalOptions.message = 'Are you sure you want make this project public to the world? Google and search engines will be able to see it. Public users will not be able to edit the project'
- else if newSetting == 'readAndWrite'
- modalOptions.title = 'Make Project Public - Read and Write'
- modalOptions.message = 'Are you sure you want make this project public to the world? Google and search engines will be able to see it. Public users will be able to write and modify the project'
- if newSetting == 'private'
- modalOptions.title = 'Make Project Private'
- modalOptions.message = 'Are you sure you want make this project private? Only registered users who are given permission below will be able to view the project'
-
- Modal.createModal modalOptions
-
-
diff --git a/services/web/public/coffee/spelling/HighlightedWordManager.coffee b/services/web/public/coffee/spelling/HighlightedWordManager.coffee
deleted file mode 100644
index 1ae058960b..0000000000
--- a/services/web/public/coffee/spelling/HighlightedWordManager.coffee
+++ /dev/null
@@ -1,146 +0,0 @@
-define () ->
- class Highlight
- constructor: (options) ->
- @row = options.row
- @column = options.column
- @word = options.word
- @suggestions = options.suggestions
-
- class HighlightedWordManager
- constructor: (@ide) ->
- @highlights = rows: []
-
- addHighlight: (highlight) ->
- unless highlight instanceof Highlight
- highlight = new Highlight(highlight)
- highlight.markerId = @ide.editor.addMarker {
- row: highlight.row
- column: highlight.column
- length: highlight.word.length
- }, "sharelatex-spelling-highlight"
- @highlights.rows[highlight.row] ||= []
- @highlights.rows[highlight.row].push highlight
-
- removeHighlight: (highlight) ->
- @ide.editor.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
-
-
-
-
-
-
-
diff --git a/services/web/public/coffee/spelling/SpellingManager.coffee b/services/web/public/coffee/spelling/SpellingManager.coffee
deleted file mode 100644
index d21b408ceb..0000000000
--- a/services/web/public/coffee/spelling/SpellingManager.coffee
+++ /dev/null
@@ -1,159 +0,0 @@
-define [
- "spelling/HighlightedWordManager"
- "spelling/SpellingMenuView"
-], (HighlightedWordManager, SpellingMenuView) ->
- class SpellingManager
- constructor: (@ide) ->
- setup = _.once =>
- @updatedLines = []
- @highlightedWordManager = new HighlightedWordManager(@ide)
- @menu = new SpellingMenuView(ide: @ide)
- @menu.on "click:suggestion", (suggestion, highlight) =>
- @menu.hide()
- @replaceWord(highlight, suggestion)
- @menu.on "click:learn", (highlight) =>
- @menu.hide()
- @learnWord highlight
- @ide.editor.on "change:doc", () => @changeOpenDoc()
- @ide.editor.on "update:doc", (e) => @onDocUpdated(e)
- @ide.editor.on "mousemove", (e) => @onMouseMove(e)
-
- @ide.on "afterJoinProject", (project) =>
- @project = project
- @language = @ide.project.get("spellCheckLanguage") || window.userSettings.spellCheckLanguage
- if @language? and @language != ""
- setup()
-
- @project.on "change:spellCheckLanguage", =>
- @changeOpenDoc()
-
- changeOpenDoc: () ->
- @highlightedWordManager.clearRows()
- @runSpellCheck()
-
- onDocUpdated: (e) ->
- @highlightedWordManager.applyChange(e.data)
- @markLinesAsUpdated(e.data)
- @runSpellCheckSoon()
-
- onMouseMove: (e) ->
- @showMenuIfOverMisspelling(e.position)
-
- showMenuIfOverMisspelling: (position) ->
- highlight = @highlightedWordManager.findHighlightWithinRange
- start: position
- end: position
- if highlight
- @menu.showForHighlight(highlight)
- else
- @menu.hideIfAppropriate(position)
-
- replaceWord: (highlight, suggestion) ->
- @ide.editor.replaceText {
- start:
- row: highlight.row
- column: highlight.column
- end:
- row: highlight.row
- column: highlight.column + highlight.word.length
- }, suggestion
-
- learnWord: (highlight) ->
- @apiRequest "/learn", word: highlight.word
- @highlightedWordManager.removeWord highlight.word
-
- getHighlightedWordAtCursor: () ->
- cursor = @ide.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 = @ide.project.get("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 = @ide.editor.getLines()
- 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 = @ide.user.get("id")
- data._csrf = 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
diff --git a/services/web/public/coffee/spelling/SpellingMenuView.coffee b/services/web/public/coffee/spelling/SpellingMenuView.coffee
deleted file mode 100644
index 3ba9fd1d96..0000000000
--- a/services/web/public/coffee/spelling/SpellingMenuView.coffee
+++ /dev/null
@@ -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
-
-
diff --git a/services/web/public/coffee/tests/unit/UndoManagerTests.coffee b/services/web/public/coffee/tests/unit/UndoManagerTests.coffee
deleted file mode 100644
index 6181c2ef6e..0000000000
--- a/services/web/public/coffee/tests/unit/UndoManagerTests.coffee
+++ /dev/null
@@ -1,455 +0,0 @@
-define [
- "undo/UndoManager"
-], (UndoManager) ->
- describe "UndoManager", ->
- beforeEach ->
- @undoManager = new UndoManager()
-
- describe "Regression Tests", ->
- describe "block commenting", ->
- it "should convert correctly", ->
- lines = ["a", "a", "a", "a"]
- simpleDeltas = [
- deltas: [
- { insert: "%", position: 0 }
- { insert: "%", position: 3 }
- { insert: "%", position: 6 }
- { insert: "%", position: 9 }
- ]
- group: "doc"
- ]
- aceDeltas = @undoManager._simpleDeltaSetsToAceDeltaSets(simpleDeltas, lines)
- expectedAceDeltas = [
- deltas: [{
- action: "insertText"
- text: "%"
- range:
- start: column: 0, row: 0
- end: column: 1, row: 0
- }, {
- action: "insertText"
- text: "%"
- range:
- start: column: 0, row: 1
- end: column: 1, row: 1
- }, {
- action: "insertText"
- text: "%"
- range:
- start: column: 0, row: 2
- end: column: 1, row: 2
- }, {
- action: "insertText"
- text: "%"
- range:
- start: column: 0, row: 3
- end: column: 1, row: 3
- }]
- group: "doc"
- ]
- console.log aceDeltas, expectedAceDeltas
- aceDeltas.should.deep.equal expectedAceDeltas
-
- describe "_shiftLocalChangeToTopOfUndoStack", ->
- describe "with no local undos", ->
- beforeEach ->
- @undoManager.undoStack = [
- { deltaSets: [], remote: true }
- { deltaSets: [], remote: true }
- ]
- @return = @undoManager._shiftLocalChangeToTopOfUndoStack()
-
- it "should return false", ->
- @return.should.equal false
-
- describe "with a local undo that can be shifted to the top", ->
- beforeEach ->
- @undoManager.undoStack = [
- { deltaSets: [deltas: [{ position: 0, insert: "banana"}], group: "doc"], remote: true }
- { deltaSets: [deltas: [{ position: 5, insert: "baz" }], group: "doc"], remote: false }
- { deltaSets: [deltas: [{ position: 20, insert: "bar" }], group: "doc"], remote: true }
- { deltaSets: [deltas: [{ position: 10, insert: "foo" }], group: "doc"], remote: true }
- ]
- @return = @undoManager._shiftLocalChangeToTopOfUndoStack()
-
- it "should bring the local change to the top of the stack", ->
- @expected = [
- { deltaSets: [deltas: [{ position: 0, insert: "banana"}], group: "doc"], remote: true }
- { deltaSets: [deltas: [{ position: 17, insert: "bar" }], group: "doc"], remote: true }
- { deltaSets: [deltas: [{ position: 7, insert: "foo" }], group: "doc"], remote: true }
- { deltaSets: [deltas: [{ position: 5, insert: "baz" }], group: "doc"], remote: false }
- ]
- @undoManager.undoStack.should.deep.equal @expected
-
- it "should return true", ->
- @return.should.equal true
-
- describe "with a local undo that cannot be brought all the way to the top", ->
- beforeEach ->
- @undoManager.undoStack = [
- { deltaSets: [deltas: [{ position: 0, insert: "banana"}], group: "doc"], remote: true }
- { deltaSets: [deltas: [{ position: 5, insert: "baz" }], group: "doc"], remote: false }
- { deltaSets: [deltas: [{ position: 20, insert: "bar" }], group: "doc"], remote: true }
- { deltaSets: [deltas: [{ position: 6, insert: "foo" }], group: "doc"], remote: true }
- ]
- @return = @undoManager._shiftLocalChangeToTopOfUndoStack()
-
- it "should bring the change as far up the stack as possible", ->
- @expected = [
- { deltaSets: [deltas: [{ position: 0, insert: "banana"}], group: "doc"], remote: true }
- { deltaSets: [deltas: [{ position: 17, insert: "bar" }], group: "doc"], remote: true }
- { deltaSets: [deltas: [{ position: 5, insert: "baz" }], group: "doc"], remote: false }
- { deltaSets: [deltas: [{ position: 6, insert: "foo" }], group: "doc"], remote: true }
- ]
- @undoManager.undoStack.should.deep.equal @expected
-
- it "should return true", ->
- @return.should.equal true
-
- describe "_aceDeltaToSimpleDelta", ->
- beforeEach ->
- @docLines = [
- "one",
- "two",
- "three",
- "four"
- ]
-
- describe "insertText", ->
- it "should convert correctly", ->
- @aceDelta =
- action: "insertText"
- range:
- start:
- row: 2
- column: 2
- end:
- row: 2
- column: 5
- text: "foo"
- @simpleDelta = @undoManager._aceDeltaToSimpleDelta(@aceDelta, @docLines)
- @simpleDelta.should.deep.equal
- insert: "foo"
- position: 10
-
- describe "insertLines", ->
- it "should convert correctly", ->
- @aceDelta =
- action: "insertLines"
- lines: [
- "two and half"
- "two and three quarters"
- ]
- range:
- start:
- row: 2
- column: 0
- end:
- row: 4
- column: 0
- @simpleDelta = @undoManager._aceDeltaToSimpleDelta(@aceDelta, @docLines)
- @simpleDelta.should.deep.equal
- insert: "two and half\ntwo and three quarters\n"
- position: 8
-
- describe "removeText", ->
- it "should convert correctly", ->
- @aceDelta =
- action: "removeLines"
- range:
- start:
- row: 1
- column: 0
- end:
- row: 3
- column: 0
- lines: [
- "two",
- "three"
- ]
- @simpleDelta = @undoManager._aceDeltaToSimpleDelta(@aceDelta, @docLines)
- @simpleDelta.should.deep.equal
- remove: "two\nthree\n"
- position: 4
-
- describe "_simpleDeltaToAceDelta", ->
- describe "insert", ->
- beforeEach ->
- @docLines = [
- "one",
- "two"
- "three"
- ]
- describe "with leading and trailing partial lines", ->
- it "should return insertText and insertLines ace updates", ->
- @simpleDelta =
- position: 7
- insert: "after\ntwo and a half\nbefore"
- @aceDeltas = @undoManager._simpleDeltaToAceDeltas(@simpleDelta, @docLines)
- @expected = [{
- text: "after"
- range:
- start: row: 1, column: 3
- end: row:1, column: 8
- action: "insertText"
- }, {
- text: "\n"
- range:
- start: row: 1, column: 8
- end: row:2, column: 0
- action: "insertText"
- }, {
- lines: [ "two and a half" ]
- range:
- start: row: 2, column:0
- end: row:3, column: 0
- action: "insertLines"
- }, {
- text: "before"
- range:
- start: row: 3, column:0
- end: row: 3, column: 6
- action: "insertText"
- }]
- @aceDeltas.should.deep.equal @expected
-
- describe "remove", ->
- beforeEach ->
- @docLines = [
- "one",
- "two"
- "three"
- ]
- describe "with leading and trailing partial lines", ->
- it "should return insertText and insertLines ace updates", ->
- @simpleDelta =
- position: 2
- remove: "e\ntwo\nth"
- @aceDeltas = @undoManager._simpleDeltaToAceDeltas(@simpleDelta, @docLines)
- @expected = [{
- text: "th"
- range:
- start: row: 2, column:0
- end: row: 2, column: 2
- action: "removeText"
- }, {
- lines: [ "two" ]
- range:
- start: row: 1, column:0
- end: row:2, column: 0
- action: "removeLines"
- }, {
- text: "\n"
- range:
- start: row: 0, column: 3
- end: row:1, column: 0
- action: "removeText"
- }, {
- text: "e"
- range:
- start: row: 0, column: 2
- end: row: 0, column: 3
- action: "removeText"
- }]
- @aceDeltas.should.deep.equal @expected
-
- describe "_concatSimpleDeltas", ->
- it "should concat adjacent simple deltas", ->
- @result = @undoManager._concatSimpleDeltas [{
- insert: "foo"
- position: 5
- }, {
- insert: "bar"
- position: 8
- }, {
- insert: "baz"
- position: 11
- }, {
- insert: "one"
- position: 20
- }, {
- insert: "two"
- position: 23
- }, {
- remove: "three"
- position: 26
- }]
-
- @result.should.deep.equal [{
- insert: "foobarbaz"
- position: 5
- }, {
- insert: "onetwo"
- position: 20
- }, {
- remove: "three"
- position: 26
- }]
-
- it "should concat removes", ->
- @result = @undoManager._concatSimpleDeltas [{
- remove: "foo"
- position: 5
- }, {
- remove: "bar"
- position: 5
- }, {
- remove: "baz"
- position: 5
- }]
-
- @result.should.deep.equal [{
- remove: "foobarbaz"
- position: 5
- }]
-
- describe "_swapSimpleDeltaOrder", ->
- describe "insert - insert", ->
- describe "when the first delta is before the second", ->
- beforeEach ->
- # result: "**fooba"
- [@newFirstDelta, @newSecondDelta] = @undoManager._swapSimpleDeltaOrder(
- { insert: "foo", position: 2 }
- { insert: "ba", position: 5 }
- )
-
- it "should remove the length of the first insert from the second position", ->
- @newFirstDelta.should.deep.equal position: 2, insert: "ba"
- @newSecondDelta.should.deep.equal position: 2, insert: "foo"
-
- describe "when the second delta is inserted into the first", ->
- beforeEach ->
- # result "**fobao*"
- @result = @undoManager._swapSimpleDeltaOrder(
- { insert: "foo", position: 2 }
- { insert: "ba", position: 4 }
- )
-
- it "should return null", ->
- (@result?).should.equal false
-
- describe "when the second delta is before the first", ->
- beforeEach ->
- # result: "**bafoo"
- [@newFirstDelta, @newSecondDelta] = @undoManager._swapSimpleDeltaOrder(
- { insert: "foo", position: 2 }
- { insert: "ba", position: 2 }
- )
-
- it "should add the length of the second delta on to the first position", ->
- @newFirstDelta.should.deep.equal insert: "ba", position: 2
- @newSecondDelta.should.deep.equal insert: "foo", position: 4
-
- describe "remove - remove", ->
- describe "when the first delta is before the second", ->
- beforeEach ->
- # start "**fooba*
- [@newFirstDelta, @newSecondDelta] = @undoManager._swapSimpleDeltaOrder(
- { remove: "foo", position: 2 }
- { remove: "ba", position: 2 }
- )
-
- it "should add the length of the first remove onto the second position", ->
- @newFirstDelta.should.deep.equal position: 5, remove: "ba"
- @newSecondDelta.should.deep.equal position: 2, remove: "foo"
-
- describe "when the first and second delta overlap", ->
- beforeEach ->
- # start "**bfooa*
- @result = @undoManager._swapSimpleDeltaOrder(
- { remove: "foo", position: 3 }
- { remove: "ba", position: 2 }
- )
-
- it "should return null", ->
- (@result?).should.equal false
-
- describe "when the second delta is before the first", ->
- beforeEach ->
- # start "**bafoo*
- [@newFirstDelta, @newSecondDelta] = @undoManager._swapSimpleDeltaOrder(
- { remove: "foo", position: 4 }
- { remove: "ba", position: 2 }
- )
-
- it "should remove the length of the second delta from the first position", ->
- @newFirstDelta.should.deep.equal position: 2, remove: "ba"
- @newSecondDelta.should.deep.equal position: 2, remove: "foo"
-
- describe "insert - remove", ->
- describe "when the first delta is before the second", ->
- beforeEach ->
- # "**ba" -> "**fooba" -> "**foo"
- [@newFirstDelta, @newSecondDelta] = @undoManager._swapSimpleDeltaOrder(
- { insert: "foo", position: 2 }
- { remove: "ba", position: 5 }
- )
-
- it "should remove the length of the first delta from the second position", ->
- @newFirstDelta.should.deep.equal position: 2, remove: "ba"
- @newSecondDelta.should.deep.equal position: 2, insert: "foo"
-
- describe "when the deltas overlap", ->
- beforeEach ->
- # "**ba" -> "**bfooa" -> "**ooa"
- @result = @undoManager._swapSimpleDeltaOrder(
- { insert: "foo", position: 3 }
- { remove: "bf", position: 2 }
- )
-
- it "should return null", ->
- (@result?).should.equal false
-
- describe "when the second delta is before the first", ->
- beforeEach ->
- # "**ba" -> "**bafoo" -> "**foo"
- [@newFirstDelta, @newSecondDelta] = @undoManager._swapSimpleDeltaOrder(
- { insert: "foo", position: 4 }
- { remove: "ba", position: 2 }
- )
-
- it "should remove the length of the second delta from the first position", ->
- @newFirstDelta.should.deep.equal position: 2, remove: "ba"
- @newSecondDelta.should.deep.equal position: 2, insert: "foo"
-
- describe "remove - insert", ->
- describe "when the first delta is before the second", ->
- beforeEach ->
- # "**foo" -> "**" -> "**ba"
- [@newFirstDelta, @newSecondDelta] = @undoManager._swapSimpleDeltaOrder(
- { remove: "foo", position: 2 }
- { insert: "ba", position: 2 }
- )
-
- it "should add the length of the first delta to the second position", ->
- @newFirstDelta.should.deep.equal position: 5, insert: "ba"
- @newSecondDelta.should.deep.equal position: 2, remove: "foo"
-
- # I don't think the deltas can overlap in this case!
-
- describe "when the first delta is after the second", ->
- beforeEach ->
- # "**foo" -> "**" -> "*ba*"
- [@newFirstDelta, @newSecondDelta] = @undoManager._swapSimpleDeltaOrder(
- { remove: "foo", position: 2 }
- { insert: "ba", position: 1 }
- )
-
- it "should add the length of the second delta on to the first position", ->
- @newFirstDelta.should.deep.equal position: 1, insert: "ba"
- @newSecondDelta.should.deep.equal position: 4, remove: "foo"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/services/web/public/coffee/tests/unit/auto-complete/SuggestionManager.coffee b/services/web/public/coffee/tests/unit/auto-complete/SuggestionManager.coffee
deleted file mode 100644
index 895226a83d..0000000000
--- a/services/web/public/coffee/tests/unit/auto-complete/SuggestionManager.coffee
+++ /dev/null
@@ -1,68 +0,0 @@
-define [
- "auto-complete/SuggestionManager"
- "libs/chai"
-], (SuggestionManager, chai) ->
- should = chai.should()
-
- describe "SuggestionManager", ->
- beforeEach ->
- @suggestionManager = new SuggestionManager()
-
- describe "loadCommandsFromDoc", ->
- it "should return commands with no arguments", ->
- @suggestionManager.loadCommandsFromDoc """
- \\alpha
- \\beta \\gamma
- """
- @suggestionManager.commands.should.deep.equal [
- ["alpha", 0, 0]
- ["beta", 0, 0]
- ["gamma", 0, 0]
- ]
-
- it "should return commands with arguments", ->
- @suggestionManager.loadCommandsFromDoc """
- \\begin{document}
- \\includegraphics[width=10pt]{foo.png}
- \\frac{1}{2}
- """
- @suggestionManager.commands.should.deep.equal [
- ["begin", 0, 1]
- ["includegraphics", 1, 1]
- ["frac", 0, 2]
- ]
-
- it "should not care about whitespace between arguments", ->
- @suggestionManager.loadCommandsFromDoc """
- \\includegraphics
- \t [blah]
- \t\t {woo}
- """
- @suggestionManager.commands.should.deep.equal [
- ["includegraphics", 1, 1]
- ]
-
- it "should parse nested commands", ->
- @suggestionManager.loadCommandsFromDoc """
- \\frac{
- \\overbrace{1}{2}
- }{2}
- """
- @suggestionManager.commands.should.deep.equal [
- ["frac", 0, 2]
- ["overbrace", 0, 2]
- ]
-
- it "should not duplicate commands", ->
- @suggestionManager.loadCommandsFromDoc """
- \\frac{2}{3}
- \\frac{4}{5}
- """
- @suggestionManager.commands.should.deep.equal [
- ["frac", 0, 2]
- ]
-
-
-
-
-
diff --git a/services/web/public/coffee/tests/unit/editor/DocumentTests.coffee b/services/web/public/coffee/tests/unit/editor/DocumentTests.coffee
deleted file mode 100644
index 2ac939227b..0000000000
--- a/services/web/public/coffee/tests/unit/editor/DocumentTests.coffee
+++ /dev/null
@@ -1,233 +0,0 @@
-define [
- "libs/chai"
- "editor/Document"
- "editor/ShareJsDoc"
- "libs/sinon"
-], (
- chai
- Document
- ShareJsDoc
-) ->
- should = chai.should()
-
- describe "Document", ->
- beforeEach ->
- @ide =
- socket: @socket =
- socket:
- sessionid: @session_id = "mock-session-id"
- connected: true
- emit: (name, args..., callback = () ->) ->
- if @handlers[name]?
- @handlers[name].call(@, args..., callback)
- handlers: {}
- sinon.spy @ide.socket, "emit"
- _.extend(@ide, Backbone.Events)
- _.extend(@socket, Backbone.Events)
- @socket.removeListener = @socket.off
-
- sinon.spy ShareJsDoc::, "flushPendingOps"
- sinon.spy ShareJsDoc::, "updateConnectionState"
-
- @doc_id = "mock-doc-id"
- @docLines = ["hello", "world"]
- @version = 5
- @remote_session_id = "remote-session-id"
-
- @socket.handlers["joinDoc"] = (doc_id, args...) =>
- doc_id.should.equal @doc_id
- callback = args.pop()
- callback null, @docLines, @version, []
- @socket.handlers["leaveDoc"] = (doc_id, args...) =>
- doc_id.should.equal @doc_id
- callback = args.pop()
- callback null
-
-
- @doc = new Document(@ide, @doc_id)
- sinon.spy @doc, "trigger"
-
- afterEach ->
- ShareJsDoc::flushPendingOps.restore()
- ShareJsDoc::updateConnectionState.restore()
-
- # This is a little pattern I'm trying out to make the tests below much
- # more readable when they have a lot of repetition
- CONDITIONS =
- "connected": () ->
- @doc.connected = true
-
- "not connected": () ->
- @doc.connected = false
-
- "reconnected": () ->
- @ide.trigger "afterJoinProject"
-
- "disconnected": () ->
- @ide.socket.trigger "disconnect"
-
- "joining": () ->
- @callback = sinon.stub()
- @doc.join @callback
-
- "joined": () ->
- @callback = sinon.stub()
- @doc.join @callback
-
- "leaving": () ->
- @callback = sinon.stub()
- @doc.leave @callback
-
- "there are buffered ops": () ->
- sinon.stub @doc.doc, "hasBufferedOps", () -> return true
-
- "the buffered ops have been sent and acknowledged": () ->
- sinon.stub @doc.doc, "processUpdateFromServer"
- @doc.doc.hasBufferedOps.restore()
- @ide.socket.trigger "otUpdateApplied", { doc: @doc_id }
- @doc.doc.processUpdateFromServer.restore()
-
- TESTS =
- "emit joinDoc": () ->
- @socket.emit.calledWith("joinDoc", @doc_id).should.equal true
-
- "not emit joinDoc": () ->
- @socket.emit.calledWith("joinDoc").should.equal false
-
- "emit leaveDoc": () ->
- @socket.emit.calledWith("leaveDoc", @doc_id).should.equal true
-
- "not emit leaveDoc": () ->
- @socket.emit.calledWith("leaveDoc").should.equal false
-
- "be joined": () ->
- @doc.joined.should.equal true
-
- "not be joined": () ->
- @doc.joined.should.equal false
-
- "be wanting to be joined": () ->
- @doc.wantToBeJoined.should.equal true
-
- "not be wanting to be joined": () ->
- @doc.wantToBeJoined.should.equal false
-
- "call the callback": () ->
- @callback.called.should.equal true
-
- "not call the callback": () ->
- @callback.called.should.equal false
-
- "flush any pending ops": () ->
- ShareJsDoc::flushPendingOps.called.should.equal true
-
- "update the connection state to ok": () ->
- ShareJsDoc::updateConnectionState.calledWith("ok").should.equal true
-
- WHEN = (condition, callback) -> [
- "when " + condition,
- () ->
- beforeEach CONDITIONS[condition]
- callback()
- ]
-
- SHOULD = (test) -> ["should " + test, TESTS[test]]
-
-
- describe WHEN("connected", ->
- describe WHEN("joining", ->
- it SHOULD("emit joinDoc")...
- it SHOULD("be joined")...
- it SHOULD("be wanting to be joined")...
- it SHOULD("call the callback")...
- )...
-
- describe WHEN("leaving", ->
- it SHOULD("emit leaveDoc")...
- it SHOULD("not be joined")...
- it SHOULD("not be wanting to be joined")...
- it SHOULD("call the callback")...
- )...
-
- describe WHEN("joined", ->
- describe WHEN("there are buffered ops", ->
- describe WHEN("leaving", ->
- it SHOULD("not emit leaveDoc")...
- it SHOULD("be joined")...
- it SHOULD("not be wanting to be joined")...
- it SHOULD("not call the callback")...
-
- describe WHEN("the buffered ops have been sent and acknowledged", ->
- it SHOULD("emit leaveDoc")...
- it SHOULD("not be joined")...
- it SHOULD("not be wanting to be joined")...
- it SHOULD("call the callback")...
- )...
- )...
- )...
-
- describe WHEN("disconnected", ->
- describe WHEN("leaving", ->
- it SHOULD("not be wanting to be joined")...
- it SHOULD("not emit leaveDoc")...
- it SHOULD("call the callback")...
-
- describe WHEN("reconnected", ->
- it SHOULD("not emit leaveDoc")...
- it SHOULD("not be joined")...
- it SHOULD("not be wanting to be joined")...
- )...
- )...
-
- describe WHEN("there are buffered ops", ->
- describe WHEN("leaving", ->
- it SHOULD("not be wanting to be joined")...
- it SHOULD("not emit leaveDoc")...
- it SHOULD("not call the callback")...
-
- describe WHEN("reconnected", ->
- it SHOULD("emit joinDoc")...
- it SHOULD("flush any pending ops")...
- describe WHEN("the buffered ops have been sent and acknowledged", ->
- it SHOULD("emit leaveDoc")...
- it SHOULD("not be joined")...
- it SHOULD("not be wanting to be joined")...
- it SHOULD("call the callback")...
- )...
- )...
- )...
- )...
- )...
- )...
- )...
-
- describe WHEN("not connected", ->
- describe WHEN("joining", ->
- it SHOULD("be wanting to be joined")...
- it SHOULD("not emit joinDoc")...
- it SHOULD("not be joined")...
- it SHOULD("not call the callback")...
- describe WHEN("reconnected", ->
- it SHOULD("emit joinDoc")...
- it SHOULD("be joined")...
- it SHOULD("be wanting to be joined")...
- it SHOULD("flush any pending ops")...
- it SHOULD("update the connection state to ok")...
- )...
- )...
- )...
-
- describe "leaving a doc", ->
- describe "when not connected", ->
- describe "with buffered ops", ->
- it "should not be wanting to be joined"
-
- describe "when reconnected", ->
- it "should emit joinDoc"
- it "should flush the bufferedOps"
- it "should emit leaveDoc"
- it "should not be joined"
- it "should be wanting to be joined"
- it "should call the callback"
-
-
diff --git a/services/web/public/coffee/tests/unit/editor/ShareJsDocTests.coffee b/services/web/public/coffee/tests/unit/editor/ShareJsDocTests.coffee
deleted file mode 100644
index 6d53e63f4a..0000000000
--- a/services/web/public/coffee/tests/unit/editor/ShareJsDocTests.coffee
+++ /dev/null
@@ -1,251 +0,0 @@
-define [
- "libs/chai"
- "editor/ShareJsDoc"
- "libs/sharejs"
- "libs/sinon"
-], (
- chai
- ShareJsDoc
- ShareJs
-) ->
- should = chai.should()
- expect = chai.expect
-
- describe "ShareJsDoc", ->
- beforeEach ->
- @lines = ["hello", "world"]
- @snapshot = @lines.join("\n")
- @doc_id = "mock-doc-id"
- @version = 42
-
- socket: @socket =
- socket:
- sessionid: @session_id = "mock-session-id"
- connected: true
- emit: sinon.stub()
-
- sinon.spy ShareJs.Doc::, "_onMessage"
- sinon.spy ShareJs.Doc::, "on"
- sinon.spy ShareJs, "Doc"
-
- afterEach ->
- ShareJs.Doc::_onMessage.restore()
- ShareJs.Doc::on.restore()
- ShareJs.Doc.restore()
-
- describe "Creating a ShareJsDoc", ->
- beforeEach ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
-
- it "should create a new ShareJs.Doc instance", ->
- expect(@shareJsDoc._doc instanceof ShareJs.Doc).to.be.true
-
- it "should set the ShareJs doc connection", ->
- ShareJs.Doc
- .calledWith(@shareJsDoc.connection)
- .should.equal true
-
- it "should set the ShareJs doc name", ->
- ShareJs.Doc
- .calledWith(sinon.match.any, @doc_id)
- .should.equal true
-
- it "should set the type of the ShareJs doc to 'text'", ->
- ShareJs.Doc
- .calledWith(sinon.match.any, sinon.match.any, type: "text")
- .should.equal true
-
- it "should open the ShareJs doc so that it is in an active state", ->
- ShareJs.Doc::_onMessage
- .calledWith({
- open: true
- snapshot: @snapshot
- v: @version
- })
- .should.equal true
-
- it "should bind to the ShareJs doc events", ->
- ShareJs.Doc::on.calledWith("change").should.equal true
- ShareJs.Doc::on.calledWith("acknowledge").should.equal true
- ShareJs.Doc::on.calledWith("remoteop").should.equal true
-
- describe "Sending an op", ->
- describe "when the server responds", ->
- beforeEach (done) ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
- @shareJsDoc.submitOp @op = [p: 5, i: "foo"], () -> done()
-
- # Send the acknowledgement so that the callback is called
- setTimeout () =>
- @shareJsDoc.processUpdateFromServer {
- v: @version
- doc: @doc_id
- }
- , 10
-
-
- it "should send the op to the server", ->
- @socket.emit
- .calledWith("applyOtUpdate", @doc_id, {
- doc: @doc_id,
- op: @op
- v: @version
- })
- .should.equal true
-
- it "should update the document snapshot", ->
- @shareJsDoc.getSnapshot().should.equal "hellofoo\nworld"
-
- describe "when the server does not respond", ->
- beforeEach (done) ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
- sinon.spy @shareJsDoc, "trigger"
- @shareJsDoc.INFLIGHT_OP_TIMEOUT = 50
- @shareJsDoc.submitOp @op = [p: 5, i: "foo"]
-
- setTimeout () ->
- done()
- , 100
-
- it "should trigger an error", ->
- @shareJsDoc.trigger
- .calledWith("error", "Doc op was not acknowledged in time")
- .should.equal true
-
- describe "clearInflightAndPendingOps", ->
- beforeEach ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
- @shareJsDoc._doc.inflightOp = "mock-op-1"
- @shareJsDoc._doc.inflightCallbacks = ["mock-callback-1"]
- @shareJsDoc._doc.pendingOp = "mock-op-2"
- @shareJsDoc._doc.pendingCallbacks = ["mock-callback-2"]
-
- @shareJsDoc.clearInflightAndPendingOps()
-
- it "should clear any inflight or pendings ops", ->
- expect(@shareJsDoc._doc.inflightOp).to.be.null
- @shareJsDoc._doc.inflightCallbacks.should.deep.equal []
- expect(@shareJsDoc._doc.pendingOp).to.be.null
- @shareJsDoc._doc.pendingCallbacks.should.deep.equal []
-
- describe "flushPendingOps", ->
- beforeEach ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
- @shareJsDoc._doc.flush = sinon.stub()
- @shareJsDoc.flushPendingOps()
-
- it "should call _doc.flush", ->
- @shareJsDoc._doc.flush.called.should.equal true
-
- describe "updateConnectionState", ->
- beforeEach ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
- @shareJsDoc._doc._connectionStateChanged = sinon.stub()
- @shareJsDoc._doc.autoOpen = true
- @socket.socket.sessionid = "new-connection-id"
-
- @shareJsDoc.updateConnectionState "mock-state"
-
- it "should set autoOpen to false so that ShareJs doesn't try to send an open message", ->
- @shareJsDoc._doc.autoOpen.should.equal false
-
- it "should update the connection state", ->
- @shareJsDoc.connection.state.should.equal "mock-state"
-
- it "should notify the ShareJs doc of the state change", ->
- @shareJsDoc._doc._connectionStateChanged
- .calledWith("mock-state")
- .should.equal true
-
- it "should set the connection id to the latest socket id", ->
- @shareJsDoc.connection.id.should.equal "new-connection-id"
-
- describe "hasBufferedOps", ->
- describe "with inflight ops", ->
- beforeEach ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
- @shareJsDoc._doc.inflightOp = "mock-op-1"
-
- it "should return true", ->
- @shareJsDoc.hasBufferedOps().should.equal true
-
- describe "with pending ops", ->
- beforeEach ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
- @shareJsDoc._doc.pendingOp = "mock-op-1"
-
- it "should return true", ->
- @shareJsDoc.hasBufferedOps().should.equal true
-
- describe "with no buffered ops", ->
- beforeEach ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
-
- it "should return false", ->
- @shareJsDoc.hasBufferedOps().should.equal false
-
- describe "processUpdateFromServer", ->
- describe "successfully", ->
- beforeEach ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
- @shareJsDoc._doc._onMessage = sinon.stub()
- @shareJsDoc.processUpdateFromServer "mock-message"
-
- it "should pass the message onto the ShareJs Doc", ->
- @shareJsDoc._doc._onMessage
- .calledWith( "mock-message" )
- .should.equal true
-
- describe "with an error", ->
- beforeEach ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
- sinon.spy @shareJsDoc, "trigger"
- @shareJsDoc._doc._onMessage = sinon.stub().throws(@error = {message: "Mock error"})
- @shareJsDoc.processUpdateFromServer "mock-message"
-
- it "should trigger the error handler", ->
- @shareJsDoc.trigger
- .calledWith("error", @error)
- .should.equal true
-
- describe "with an external update", ->
- beforeEach ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
- @shareJsDoc._doc._onMessage = sinon.stub()
- sinon.spy @shareJsDoc, "trigger"
- @shareJsDoc.processUpdateFromServer { op: "mock-op", meta: { type: "external" } }
-
- it "should trigger an externalUpdate event", ->
- @shareJsDoc.trigger
- .calledWith("externalUpdate")
- .should.equal true
-
-
- describe "catchUp", ->
- beforeEach ->
- @shareJsDoc = new ShareJsDoc(@doc_id, @lines, @version, @socket)
- sinon.stub @shareJsDoc, "processUpdateFromServer", () ->
- @_doc.version++
-
- @shareJsDoc.catchUp [{
- ops: ["mock-op-1"]
- }, {
- ops: ["mock-op-2"]
- }]
-
- it "should apply each update", ->
- @shareJsDoc.processUpdateFromServer
- .calledWith({
- doc: @doc_id
- v: @version
- ops: ["mock-op-1"]
- })
- .should.equal true
-
- @shareJsDoc.processUpdateFromServer
- .calledWith({
- doc: @doc_id
- v: @version + 1
- ops: ["mock-op-2"]
- })
- .should.equal true
diff --git a/services/web/public/coffee/tests/unit/helpers.coffee b/services/web/public/coffee/tests/unit/helpers.coffee
deleted file mode 100644
index 5ddbe6794a..0000000000
--- a/services/web/public/coffee/tests/unit/helpers.coffee
+++ /dev/null
@@ -1,10 +0,0 @@
-define [
- "underscore"
- "libs/backbone"
-], () ->
- class SocketIoMock
- constructor: () ->
- _.extend(SocketIoMock::, Backbone.Events)
- SocketIoMock::emit = () -> @trigger.apply(@, arguments)
-
- return SocketIoMock: SocketIoMock
diff --git a/services/web/public/coffee/tests/unit/history/FileDiff.coffee b/services/web/public/coffee/tests/unit/history/FileDiff.coffee
deleted file mode 100644
index 3b97b04016..0000000000
--- a/services/web/public/coffee/tests/unit/history/FileDiff.coffee
+++ /dev/null
@@ -1,140 +0,0 @@
-define [
- "libs/chai"
- "history/FileDiff"
- "history/FileDiffView"
- "libs/sinon"
-], (chai, FileDiff, FileDiffView) ->
- should = chai.should()
-
- describe "FileDiffView", ->
- describe "Updated text", ->
- beforeEach ->
- @model = new FileDiff
- path: "dir/file"
- type: "updated"
- sections: [{
- old_start_line: 5,
- new_start_line: 7,
- lines : [
- { type: "unchanged", content: "line 5" },
- { type: "removed", content: "removed line" },
- { type: "added", content: "added line" },
- { type: "unchanged", content: "line 7" }
- ]
- }, {
- old_start_line: 12,
- new_start_line: 12,
- lines : [
- { type: "unchanged", content: "line 12" },
- { type: "removed", content: "removed line" },
- { type: "added", content: "added line" },
- { type: "unchanged", content: "line 14" }
- ]
- }]
- @view = new FileDiffView
- model : @model
- @view.render()
-
- it "should render a pretty version of the diff", ->
- htmlLines = @view.$(".line")
- lines = for line in htmlLines
- old_line_number: $(line).find(".old_line_number").text()
- new_line_number: $(line).find(".new_line_number").text()
- symbol: $(line).find(".symbol").text()
- content: $(line).find(".content").text()
-
- lines[0].should.deep.equal(old_line_number: "5", new_line_number: "7", symbol: "", content: "line 5")
- lines[1].should.deep.equal(old_line_number: "6", new_line_number: "", symbol: "-", content: "removed line")
- lines[2].should.deep.equal(old_line_number: "", new_line_number: "8", symbol: "+", content: "added line")
- lines[3].should.deep.equal(old_line_number: "7", new_line_number: "9", symbol: "", content: "line 7")
-
- lines[4].should.deep.equal(old_line_number: "12", new_line_number: "12", symbol: "", content: "line 12")
- lines[5].should.deep.equal(old_line_number: "13", new_line_number: "", symbol: "-", content: "removed line")
- lines[6].should.deep.equal(old_line_number: "", new_line_number: "13", symbol: "+", content: "added line")
- lines[7].should.deep.equal(old_line_number: "14", new_line_number: "14", symbol: "", content: "line 14")
-
- it "should load the raw file when clicked", ->
- model = @model
- @model.fetch = (options) ->
- @set("content", "Test content")
- options.success(model) if options.success?
-
- @view.$(".rawFileContent").text().should.equal "Loading..."
- @view.$(".nav .raw").click()
- @view.$(".rawFileContent").text().should.equal "Test content"
-
- describe "Deleted text", ->
- beforeEach ->
- @model = new FileDiff
- path: "dir/file"
- type: "deleted"
- sections: [{
- old_start_line: 12,
- new_start_line: 12,
- lines : [
- { type: "removed", content: "line 12" }
- ]
- }]
- @view = new FileDiffView
- model : @model
- @view.render()
-
- it "shouldn't show a link to the raw file", ->
- @view.$(".nav .raw").length.should.equal 0
-
- describe "Blank diff", ->
- beforeEach ->
- @model = new FileDiff
- path: "dir/file"
- type: "deleted"
- @view = new FileDiffView
- model : @model
- @view.render()
-
- it "shouldn't show a diff", ->
- @view.$(".tab-diff").length.should.equal 0
-
-
- describe "Updated binary file", ->
- beforeEach ->
- @model = new FileDiff
- binary: true
- path: "dir/file"
- version_id: "123"
- type: "updated"
- @view = new FileDiffView
- model : @model
- @view.render()
-
- it "should display a link to the file", ->
- @view.$("a.rawFileLink").attr("href").should.equal "/project/#{userSettings.project_id}/version/123/file/dir/file"
-
- describe "Deleted binary file", ->
- beforeEach ->
- @model = new FileDiff
- binary: true
- type: "deleted"
- @view = new FileDiffView
- model : @model
- @view.render()
-
- it "should not display a link to the file", ->
- @view.$("a.rawFileLink").length.should.equal 0
-
-
- describe "Moved file", ->
- beforeEach ->
- @model = new FileDiff
- path: "new-path"
- oldPath: "old-path"
- type: "renamed"
- @view = new FileDiffView
- model : @model
- @view.render()
-
- it "should say the file was moved", ->
- @view.$("h3").text().should.equal("old-path")
- @view.$(".fileMoved").text().should.equal("Moved to new-path")
-
-
-
diff --git a/services/web/public/coffee/tests/unit/history/HistoryManager.coffee b/services/web/public/coffee/tests/unit/history/HistoryManager.coffee
deleted file mode 100644
index 9130c1a614..0000000000
--- a/services/web/public/coffee/tests/unit/history/HistoryManager.coffee
+++ /dev/null
@@ -1,158 +0,0 @@
-define [
- "libs/chai"
- "history/HistoryManager"
- "models/Project"
- "account/AccountManager"
- "libs/sinon"
-], (chai, HistoryManager, Project, AccountManager) ->
- should = chai.should()
-
- describe "HistoryManager", ->
- beforeEach ->
- @editor =
- sideBarView:
- addLink: sinon.stub()
- selectLink: (identifier) ->
- mainAreaManager:
- change: (selector) ->
- addArea: (options) ->
- $("#test-area").append(options.element)
- options.element.show()
- _.extend(@editor, Backbone.Events)
- @historyManager = new HistoryManager @editor
- sinon.stub @historyManager.versionListView, "loadUntilFull", () ->
- sinon.stub @historyManager.versionList, "fetchNewVersions", () ->
-
- afterEach -> @historyManager.historyPanel.remove()
-
- describe "takeSnapshot", ->
- beforeEach ->
- @error = false
- sinon.stub $, "ajax", (options) =>
- if @error
- options.error() if options.error?
- else
- options.success() if options.success?
- @project = new Project id: userSettings.project_id
- @message = "what a wonderful message"
- @editor.trigger("afterJoinProject", @project)
-
- afterEach ->
- $.ajax.restore()
-
- describe "success", ->
- beforeEach ->
- @error = false
- @callback = sinon.stub()
- @historyManager.takeSnapshot(@message, @callback)
-
- it "should POST to /project//snapshot", ->
- $.ajax.called.should.equal true
- options = $.ajax.args[0][0]
- options.type.should.equal "POST"
- options.url.should.equal "/project/#{userSettings.project_id}/snapshot"
- options.data.should.deep.equal message: @message
-
- it "should call the callback without an error", ->
- @callback.called.should.equal true
- @callback.args[0].length.should.equal 0
-
- it "should call fetchNewVersions on collection", ->
- @historyManager.versionList.fetchNewVersions
- .called.should.equal true
-
- describe "error", ->
- beforeEach ->
- @error = true
- @callback = sinon.stub()
- @historyManager.takeSnapshot(@message, @callback)
-
- it "should call the callback with an error", ->
- @callback.called.should.equal true
- (@callback.args[0][0] instanceof Error).should.equal true
-
- describe "with versioning to be shown", ->
- beforeEach ->
- @project = new Project
- features : {}
- @editor.project = @project
- @editor.trigger("afterJoinProject", @project)
-
- it "should insert the History link into the side bar", ->
- @editor.sideBarView.addLink.called.should.equal true
- args = @editor.sideBarView.addLink.args[0][0]
- args.identifier.should.equal "history"
- args.before.should.equal "settings"
- should.exist args.element
- args.element.text().should.equal "History"
-
- describe "when clicking on the history link", ->
- beforeEach ->
- @element = @editor.sideBarView.addLink.args[0][0].element
-
- describe "generally", ->
- beforeEach ->
- sinon.spy @historyManager, "showHistoryArea"
- sinon.spy @editor.sideBarView, "selectLink"
- sinon.spy @editor.mainAreaManager, "change"
- @element.click()
-
- it "should call showHistoryArea", ->
- @historyManager.showHistoryArea.called.should.equal true
-
- it "should select the history link", ->
- @editor.sideBarView.selectLink.called.should.equal true
- @editor.sideBarView.selectLink.calledWith("history").should.equal true
-
- it "should show the history area", ->
- @editor.mainAreaManager.change.called.should.equal true
- @editor.mainAreaManager.change.calledWith("history").should.equal true
-
- describe "with versioning enabled", ->
- beforeEach ->
- @project.get("features").versioning = true
- @element.click()
-
- it "should display the history", ->
- @historyManager.view.$("#versionListArea").is(":visible").should.equal true
- @historyManager.view.$("#diffViewArea").is(":visible").should.equal true
- @historyManager.view.$("#enableVersioningMessage").is(":visible").should.equal false
-
- it "should load versions when the history area is shown", ->
- @historyManager.showHistoryArea()
- @historyManager.versionListView.loadUntilFull.called.should.equal true
- @historyManager.versionList.fetchNewVersions.called.should.equal true
-
- describe "with versioning disabled", ->
- beforeEach ->
- @project.get("features").versioning = false
- @element.click()
-
- it "should display the prompt to enable versioning", ->
- @historyManager.view.$("#versionListArea").is(":visible").should.equal false
- @historyManager.view.$("#diffViewArea").is(":visible").should.equal false
- @historyManager.view.$("#enableVersioningMessage").is(":visible").should.equal true
-
- it "should not attempt to load versions when the history area is shown", ->
- @historyManager.showHistoryArea()
- @historyManager.versionListView.loadUntilFull.called.should.equal false
- @historyManager.versionList.fetchNewVersions.called.should.equal false
-
- describe "when the user clicks 'Enable now'", ->
- describe "when enabled succesfully", ->
- beforeEach ->
- sinon.stub AccountManager, "askToUpgrade", (editor, options) ->
- editor.project.set("features", versioning: true)
- options.onUpgrade()
- @historyManager.view.$("#enableVersioning").click()
-
- afterEach ->
- AccountManager.askToUpgrade.restore()
-
- it "should start the user on their free trial", ->
- AccountManager.askToUpgrade.called.should.equal true
-
- it "should display the history interface", ->
- @historyManager.view.$("#versionListArea").is(":visible").should.equal true
- @historyManager.view.$("#diffViewArea").is(":visible").should.equal true
- @historyManager.view.$("#enableVersioningMessage").is(":visible").should.equal false
diff --git a/services/web/public/coffee/tests/unit/history/HistoryView.coffee b/services/web/public/coffee/tests/unit/history/HistoryView.coffee
deleted file mode 100644
index d2d48fb00e..0000000000
--- a/services/web/public/coffee/tests/unit/history/HistoryView.coffee
+++ /dev/null
@@ -1,47 +0,0 @@
-define [
- "libs/chai"
- "history/HistoryView"
- "utils/Modal"
- "libs/sinon"
-], (chai, HistoryView, Modal) ->
- should = chai.should()
-
- describe "HistoryView", ->
- beforeEach ->
- @el = $("")
- $("#test-area").append(@el)
- @historyManager =
- takeSnapshot: sinon.stub()
- @historyView = new HistoryView el : @el, manager: @historyManager
-
- describe "takeSnapshot", ->
- beforeEach ->
- sinon.spy Modal, "createModal"
- @historyView.takeSnapshot()
-
- afterEach ->
- $(".modal").remove()
- Modal.createModal.restore()
-
- it "should display a modal asking for a comment", ->
- Modal.createModal.called.should.equal true
- options = Modal.createModal.args[0][0]
- should.exist options.title
- should.exist options.message
- options.buttons[0].text.should.equal "Cancel"
- options.buttons[1].text.should.equal "Take Snapshot"
-
- describe "user clicks ok", ->
- beforeEach ->
- @message = "what a wonderful message"
- $("#snapshotComment").val(@message)
- $(".modal-footer a.btn-primary").click()
-
- it "should call takeSnapshot with the message", ->
- @historyManager.takeSnapshot.calledWith(@message)
- .should.equal true
-
-
-
-
-
diff --git a/services/web/public/coffee/tests/unit/history/VersionListView.coffee b/services/web/public/coffee/tests/unit/history/VersionListView.coffee
deleted file mode 100644
index 4fe0b91a4a..0000000000
--- a/services/web/public/coffee/tests/unit/history/VersionListView.coffee
+++ /dev/null
@@ -1,71 +0,0 @@
-define [
- "libs/chai"
- "history/VersionListView"
- "history/VersionList"
- "history/Version"
- "libs/sinon"
-], (chai, VersionListView, VersionList, Version) ->
- should = chai.should()
-
- describe "VersionListView", ->
- beforeEach ->
- collection = @collection = new VersionList()
- versionCounter = 1
- @collection.fetchNextBatch = (options) ->
- for i in [1..3]
- collection.add new Version message: "Test Snapshot #{versionCounter}"
- versionCounter++
- options.success() if options.success?
-
- @view = new VersionListView
- collection : @collection
- @view.$el.css height: "200px"
- $("#test-area").append(@view.$el)
-
- afterEach ->
- @view.$el.remove()
-
- it "should add versions to the list as they are added to the collection", ->
- @collection.add new Version message : "Test Snapshot 1"
- @collection.add new Version message : "Test Snapshot 2"
- should.equal $(@view.$(".version-message")[0]).text(), "Test Snapshot 1"
- should.equal $(@view.$(".version-message")[1]).text(), "Test Snapshot 2"
-
- it "should load more versions when scrolled to the end", (done) ->
- # Add enough versions to make the list long enough to scroll
- originalVersions = 10
- for i in [1..originalVersions]
- @collection.add new Version message: "Test Snapshot"
-
- @view.$el.scrollTop(10000) # Should get us to the bottom
-
- collection = @collection
- setTimeout (() ->
- should.equal collection.models.length > originalVersions, true
- done()
- ), 0
-
- it "should load more versions when the list does not take up the whole view", (done) ->
- view = @view
- @view.loadUntilFull null, () ->
- should.equal view.$("#version-list").height() > view.$el.height(), true
- should.equal view.collection.models.length > 0, true
- done()
-
- it "should stop loading versions if the collection returns an error", (done) ->
- @collection.fetchNextBatch = (options) ->
- options.error() if options.error?
- view = @view
- @view.loadUntilFull null, () ->
- should.equal view.collection.models.length, 0
- done()
-
- it "should show the empty message if the collection is empty after fetching", (done) ->
- @collection.pop() until @collection.isEmpty()
- @collection.fetchNextBatch = (options) ->
- # error callback is triggered on 404
- options.error() if options.error()
-
- @view.loadUntilFull null, () =>
- @view.$(".empty-message").is(":visible").should.equal true
- done()
diff --git a/services/web/public/coffee/tests/unit/modal.coffee b/services/web/public/coffee/tests/unit/modal.coffee
deleted file mode 100644
index 5d4af8f0bb..0000000000
--- a/services/web/public/coffee/tests/unit/modal.coffee
+++ /dev/null
@@ -1,56 +0,0 @@
-define [
- "utils/Modal"
-], (Modal) ->
- describe "Modal", ->
- describe "initialization", ->
- beforeEach ->
- @modal = Modal.createModal
- title: "Test modal"
- message : "Test modal message"
- buttons: [{
- text : "OK"
- class : "btn-primary"
- }, {
- text : "Cancel"
- class : "btn-danger"
- }]
- afterEach ->
- @modal.remove()
-
- it "should display the modal", ->
- @modal.$el.is(":visible").should.equal true
-
- it "should include the buttons in reverse order", ->
- buttons = @modal.$(".modal-footer a")
- $(buttons[0]).text().should.equal "Cancel"
- $(buttons[0]).hasClass("btn-danger").should.equal true
- $(buttons[1]).text().should.equal "OK"
- $(buttons[1]).hasClass("btn-primary").should.equal true
-
- it "should include the title", ->
- @modal.$("h3").text().should.equal "Test modal"
-
- it "should include the message", ->
- @modal.$(".message").text().should.equal "Test modal message"
-
- describe "clicking buttons", ->
- beforeEach ->
- @callbackCalled = false
- @modal = Modal.createModal
- title: "Test modal"
- message : "Test modal message"
- buttons: [{
- text : "OK"
- class : "btn-primary"
- callback : () =>
- @callbackCalled = true
- }]
- @modal.$(".modal-footer a").click()
-
- it "should call the callback", ->
- @callbackCalled.should.equal true
-
- it "should remove the modal", ->
- @modal.$el.is(":visible").should.equal false
- @modal.$el.parent().length.should.equal 0
-
diff --git a/services/web/public/coffee/tests/unit/project-members.coffee b/services/web/public/coffee/tests/unit/project-members.coffee
deleted file mode 100644
index 6d32ad4fac..0000000000
--- a/services/web/public/coffee/tests/unit/project-members.coffee
+++ /dev/null
@@ -1,275 +0,0 @@
-define [
- "libs/chai"
- "project-members/ProjectMembersManager"
- "models/User"
- "models/Project"
- "account/AccountManager"
- "tests/unit/helpers"
- "libs/sinon"
-], (chai, PM, User, Project, AccountManager, Helpers) ->
- should = chai.should()
-
- describe "ProjectMembersManager", ->
- beforeEach ->
- @socket = new Helpers.SocketIoMock()
- @ide =
- socket: @socket
- mainAreaManager:
- addArea: sinon.stub()
- sideBarView:
- addLink: sinon.stub()
- tabManager:
- addTab: sinon.stub()
- _.extend(@ide, Backbone.Events)
- @projectMembersManager = new PM.ProjectMembersManager @ide
- @project = new Project {
- _id : "project-1"
- owner:
- _id : 1
- email : "owner@example.com"
- privileges : "owner"
- members : [{
- _id : 2
- email : "readonly@example.com"
- privileges : "readOnly"
- },
- {
- _id : 3
- email : "readwrite@example.com"
- privileges : "readAndWrite"
- }]
- features:
- collaborators: -1
- rootFolder: [
- _id: "root-folder-id"
- name: "root-folder"
- docs: []
- folders: []
- fileRefs: []
- ]
- }, {
- parse: true
- }
- @ide.project = @project
- @project.set "ide", @ide
-
- describe "when the project loaded is owned by the user", ->
- beforeEach ->
- @ide.security =
- permissionsLevel: "owner"
- @ide.trigger "afterJoinProject", @project
-
- it "should show the admin controls on the member list", ->
- @projectMembersManager.view.options.showAdminControls.should.equal true
-
- describe "when the project loaded is not owned by the user", ->
- beforeEach ->
- @ide.security =
- permissionsLevel: "readAndWrite"
- @ide.project = @project
- @ide.trigger "afterJoinProject", @project
-
- it "should socket show the admin controls on the member list", ->
- @projectMembersManager.view.options.showAdminControls.should.equal false
-
- describe "when the project is loaded with too many collaborators", ->
- beforeEach ->
- @project.get("features").collaborators = 1
- sinon.stub AccountManager, "showUpgradeDialog"
- @ide.project = @project
- @ide.trigger "afterJoinProject", @project
-
- afterEach ->
- AccountManager.showUpgradeDialog.restore()
-
- it "should show the upgrade dialog", ->
- AccountManager.showUpgradeDialog.called.should.equal true
- options = AccountManager.showUpgradeDialog.args[0][1]
- options.message.should.equal "This project has too many collaborators for your plan. Please upgrade your account or remove some collaborators"
-
- describe "when the project is loaded", ->
- beforeEach ->
- @ide.project = @project
- @ide.trigger "afterJoinProject", @project
-
- describe "when a member is removed client-side", ->
- beforeEach ->
- @socket.on "removeUserFromProject", @removeUserFromProject = sinon.spy()
- @projectMembersManager.removeMember id: "member-123"
-
- it "should call socket.removeUserFromProject", ->
- @removeUserFromProject.calledWith("member-123").should.equal true
-
- describe "when a member is removed server-side", ->
- beforeEach ->
- @projectMembersManager.members.add new User
- id: "user-1"
- @projectMembersManager.members.add new User
- id: "user-2"
- @ide.socket.emit "userRemovedFromProject", "user-1"
-
- it "should remove the member", ->
- for member in @projectMembersManager.members.models
- member.id.should.not.equal "user-1"
-
- describe "when a member is added client-side", ->
- describe "successfully", ->
- beforeEach ->
- @socket.on "addUserToProject", @addUserToProject =
- sinon.stub().callsArgWith(2, null, true)
- @projectMembersManager.addMember "new-member@example.com", "readAndWrite"
-
- it "should call socket.addUserToProject", ->
- @addUserToProject
- .calledWith("new-member@example.com", "readAndWrite")
- .should.equal true
-
- describe "when the user needs to upgrade", ->
- beforeEach ->
- @socket.on "addUserToProject", @addUserToProject =
- sinon.stub().callsArgWith(2, null, false)
- sinon.stub AccountManager, "askToUpgrade", (ide, options) ->
- ide.project.set("features", collaborators: 5)
- @projectMembersManager.addMember "new-member@example.com", "readAndWrite"
-
- afterEach ->
- AccountManager.askToUpgrade.restore()
-
- it "should start the user on their free trial", ->
- AccountManager.askToUpgrade.called.should.equal true
-
- describe "when a member is added server-side", ->
- beforeEach ->
- @socket.emit "userAddedToProject",
- _id: "new-user-1"
- email: "new-user@example.com",
- "readOnly"
-
- it "should add a member", ->
- for possibleMember in @projectMembersManager.members.models
- if possibleMember.id == "new-user-1"
- member = possibleMember
- should.exist member
- member.get("email").should.equal "new-user@example.com"
- member.get("privileges").should.equal "readOnly"
-
- describe "ProjectMembersView", ->
- beforeEach ->
- @socket = new Helpers.SocketIoMock()
- @ide =
- socket: @socket
- mainAreaManager:
- addArea: sinon.stub()
- sideBarView:
- addLink: sinon.stub()
- tabManager:
- addTab: sinon.stub()
- _.extend(@ide, Backbone.Events)
- @projectMembersManager = new PM.ProjectMembersManager(@ide)
- @collection = new PM.ProjectMemberList()
- @view = new PM.ProjectMemberListView
- collection: @collection
- manager: @projectMembersManager
- @view.render()
- $("#test-area").append(@view.$el)
-
- afterEach ->
- @view.$el.remove()
-
- describe "when formatting privileges", ->
- it "should format the read-write privileges nicely", ->
- @collection.add new User email: "test1@example.com", privileges: "readAndWrite"
- @view.$(".projectMember").find(".privileges").text().should.equal "Read and Write"
-
- it "should format the read-only privileges nicely", ->
- @collection.add new User email: "test1@example.com", privileges: "readOnly"
- @view.$(".projectMember").find(".privileges").text().should.equal "Read Only"
-
- it "should format the owner privileges nicely", ->
- @collection.add new User email: "test1@example.com", privileges: "owner"
- @view.$(".projectMember").find(".privileges").text().should.equal "Owner"
-
- describe "when the collection already has entries when the view is rendered", ->
- beforeEach ->
- @collection.add new User email: "test1@example.com", privileges: "readAndWrite"
- @view.render()
-
- it "should render the member in the view", ->
- @view.$(".projectMember").find(".email").text().should.equal "test1@example.com"
-
- describe "when the collection is updated", ->
- describe "by adding a member", ->
- beforeEach ->
- @collection.add new User email: "test1@example.com", privileges: "readAndWrite"
-
- it "should add the member to view", ->
- @view.$(".projectMember").find(".email").text().should.equal "test1@example.com"
-
- describe "by removing a member", ->
- beforeEach ->
- @member = new User email: "test1@example.com", privileges: "readAndWrite"
- @collection.add @member
- @collection.remove @member
-
- it "should remove the user from the view", ->
- @view.$(".projectMember").length.should.equal 0
-
- describe "with admin controls", ->
- beforeEach ->
- @view.options.showAdminControls = true
- @view.render()
-
- it "should show the remove link for members", ->
- @collection.add new User email: "test1@example.com", privileges: "readAndWrite"
- @view.$(".projectMember").find(".removeUser").length.should.equal 1
-
- it "should not show the remove link for the owner", ->
- @collection.add new User email: "test1@example.com", privileges: "owner"
- @view.$(".projectMember").find(".removeUser").length.should.equal 0
-
- it "should show the form to add a member", ->
- @view.$(".addUser").length.should.equal 1
-
- describe "without admin controls", ->
- it "should not show the remove link for members", ->
- @collection.add new User email: "test1@example.com", privileges: "readAndWrite"
- @view.$(".projectMember").find(".removeUser").length.should.equal 0
-
- it "should not show the form to add a member", ->
- @view.$(".addUser").length.should.equal 0
-
- describe "when removing a user", ->
- beforeEach ->
- sinon.stub @projectMembersManager, "removeMember", (user) ->
- @view.options.showAdminControls = true
- @member = new User email: "test1@example.com", privileges: "readAndWrite"
- @collection.add @member
-
- describe "when the remove link is clicked", ->
- beforeEach -> @view.$(".projectMember").find(".removeUser").click()
-
- it "should remove the user", ->
- @projectMembersManager.removeMember.called.should.equal true
- @projectMembersManager.removeMember.calledWith(@member).should.equal true
-
- describe "when adding a user", ->
- beforeEach ->
- sinon.stub @projectMembersManager, "addMember", (email, privileges) ->
- @view.options.showAdminControls = true
- @view.render()
-
-
- describe "when the add user button is clicked", ->
- beforeEach ->
- @view.$(".email").val("new-user@example.com")
- @view.$(".privileges").val("readOnly")
- @view.$(".addUser").click()
-
- it "should add a user", ->
- @projectMembersManager.addMember.called.should.equal true
- @projectMembersManager.addMember.calledWith("new-user@example.com", "readOnly").should.equal true
-
- it "should reset the email address", ->
- @view.$(".email").val().should.equal ""
-
-
diff --git a/services/web/public/coffee/tests/unit/project.coffee b/services/web/public/coffee/tests/unit/project.coffee
deleted file mode 100644
index 6807e35a0e..0000000000
--- a/services/web/public/coffee/tests/unit/project.coffee
+++ /dev/null
@@ -1,164 +0,0 @@
-define [
- "libs/chai"
- "models/Project"
- "models/ProjectMemberList"
- "models/User"
- "tests/unit/helpers"
- "libs/sinon"
-], (chai, Project, ProjectMemberList, User, Helpers) ->
- should = chai.should()
-
-
- describe "Project", ->
- beforeEach ->
- @socket = new Helpers.SocketIoMock()
-
- describe "parsing", ->
- beforeEach ->
- @socket.on "setProjectName", @setProjectName = sinon.spy()
- @socket.on "setPublicAccessLevel", @setPublicAccessLevel = sinon.spy()
- @socket.on "setCompiler", @setCompiler = sinon.spy()
- @socket.on "setRootDoc", @setRootDoc = sinon.spy()
- @project = new Project {
- _id : "project-1"
- rootDoc_id : "root-doc-id"
- name : "Project Name"
- publicAccesLevel : "readOnly"
- owner:
- _id : 1
- email : "owner@example.com"
- privileges : "owner"
- members : [{
- _id : 2
- email : "readonly@example.com"
- privileges : "readOnly"
- },
- {
- _id : 3
- email : "readwrite@example.com"
- privileges : "readAndWrite"
- }]
- features:
- versioning: true
- collaborators: 3
- rootFolder: [
- _id: "root-folder-id"
- name: "root-folder"
- docs: []
- folders: []
- fileRefs: []
- ]
-
- }, {
- parse: true
- }
- @project.set "ide", socket: @socket
-
- it "should set the id", ->
- @project.id.should.equal "project-1"
-
- it "should set the name without calling socket.setProjectName", ->
- @project.get("name").should.equal "Project Name"
- @setProjectName.called.should.equal false
-
- it "should set the public access level without called socket.setPublicAccessLevel", ->
- @project.get("publicAccesLevel").should.equal "readOnly"
- @setPublicAccessLevel.called.should.equal false
-
- it "should create a collection of collaborators", ->
- should.exist @project.get("members")
- (@project.get("members") instanceof ProjectMemberList).should.equal true
-
- members = @project.get("members").models
- members[0].id.should.equal 1
- members[0].get("email").should.equal "owner@example.com"
- members[0].get("privileges").should.equal "owner"
- members[1].id.should.equal 2
- members[1].get("email").should.equal "readonly@example.com"
- members[1].get("privileges").should.equal "readOnly"
- members[2].id.should.equal 3
- members[2].get("email").should.equal "readwrite@example.com"
- members[2].get("privileges").should.equal "readAndWrite"
-
- it "should create an owner model", ->
- should.exist @project.get("owner")
- (@project.get("owner") instanceof User).should.equal true
- @project.get("owner").get("privileges").should.equal "owner"
-
- it "should include the features", ->
- @project.get("features").should.deep.equal
- versioning: true
- collaborators: 3
-
- it "should set the rootDoc_id without calling socket.setRootDoc", ->
- @project.get("rootDoc_id").should.equal "root-doc-id"
- @setRootDoc.called.should.equal false
-
- describe "updating the root document", ->
- beforeEach ->
- @project = new Project
- id: "project-1",
- rootDoc_id: "old-root-doc-id"
- @project.set "ide", socket: @socket
-
- it "should call socket.setRootDoc when the root doc is changed", ->
- @socket.on "setRootDoc", @setRootDoc = sinon.spy()
- @project.set("rootDoc_id", "new-root-doc-id")
- @setRootDoc.calledWith("new-root-doc-id").should.equal true
-
- it "should update the root doc when socket.rootDocUpdated is called", ->
- @socket.emit "rootDocUpdated", "new-root-doc-id"
- @project.get("rootDoc_id").should.equal "new-root-doc-id"
-
-
- describe "updating the compiler the project uses", ->
- beforeEach ->
- @project = new Project
- id : "project-1",
- compiler : "latex"
- @project.set "ide", socket: @socket
-
- it "should call socket.setCompiler when the compiler is changed", ->
- @socket.on "setCompiler", @setCompiler = sinon.spy()
- @project.set("compiler", "xetex")
- @setCompiler.calledWith("xetex").should.equal true
-
- it "should update the compiler when socket.compilerUpdated is called", ->
- @socket.emit "compilerUpdated", "xetex"
- @project.get("compiler").should.equal "xetex"
-
- describe "updating the project's name", ->
- beforeEach ->
- @project = new Project
- id : "project-1",
- name : "Old Name"
- @project.set "ide", socket: @socket
-
- it "should call socket.setProjectName when the name is changed", ->
- @socket.on "setProjectName", @setProjectName = sinon.spy()
- @project.set("name", "New Name")
- @setProjectName.calledWith(window.window_id, "New Name").should.equal true
-
- it "should update the name when socket.projectNameUpdated is called", ->
- @socket.emit "projectNameUpdated", "window-id-2", "New Name"
- @project.get("name").should.equal "New Name"
-
- describe "updating the project's public access level", ->
- beforeEach ->
- @project = new Project
- id : "project-1",
- publicAccesLevel : "readOnly"
- @project.set "ide", socket: @socket
-
- it "should call socket.setPublicAccessLevel when the level is changed", ->
- @socket.on "setPublicAccessLevel", @setPublicAccessLevel = sinon.spy()
- @project.set("publicAccesLevel", "readAndWrite")
- @setPublicAccessLevel.calledWith("readAndWrite").should.equal true
-
- it "should update the level when socket.publicAccessLevelUpdated is called", ->
- @socket.emit "publicAccessLevelUpdated", "readAndWrite"
- @project.get("publicAccesLevel").should.equal "readAndWrite"
-
-
-
-
diff --git a/services/web/public/coffee/tests/unit/run.coffee b/services/web/public/coffee/tests/unit/run.coffee
deleted file mode 100644
index 211a7af294..0000000000
--- a/services/web/public/coffee/tests/unit/run.coffee
+++ /dev/null
@@ -1,21 +0,0 @@
-require ["libs/mocha", "libs/underscore", "libs/jquery"], ->
- mocha.setup({
- ui: "bdd",
- globals: ["now", "socket"]
- })
- require [
- "tests/unit/UndoManagerTests"
- "tests/unit/history/FileDiff"
- "tests/unit/history/VersionListView"
- "tests/unit/history/HistoryView"
- "tests/unit/spelling/HighlightedWordManagerTests"
- "tests/unit/spelling/SpellingManagerTests"
- "tests/unit/auto-complete/SuggestionManager"
- "tests/unit/project-members"
- "tests/unit/user"
- "tests/unit/project"
- "tests/unit/modal"
- "tests/unit/editor/DocumentTests"
- "tests/unit/editor/ShareJsDocTests"
- ], ->
- mocha.run()
diff --git a/services/web/public/coffee/tests/unit/spelling/HighlightedWordManagerTests.coffee b/services/web/public/coffee/tests/unit/spelling/HighlightedWordManagerTests.coffee
deleted file mode 100644
index e2c3007fc4..0000000000
--- a/services/web/public/coffee/tests/unit/spelling/HighlightedWordManagerTests.coffee
+++ /dev/null
@@ -1,295 +0,0 @@
-define [
- "spelling/HighlightedWordManager"
- "libs/chai"
-], (HighlightedWordsManager, chai) ->
- should = chai.should()
-
- describe "HighlightedWordsManager", ->
- beforeEach ->
- @currentMarkerId = 0
- @ide =
- editor:
- addMarker: () => ++@currentMarkerId
- removeMarker: sinon.stub()
- sinon.spy @ide.editor, "addMarker"
- @highlightedWordsManager = new HighlightedWordsManager(@ide)
- @highlightedWordsManager.doesHighlightExist = (row, column, word) ->
- for highlight in @highlights.rows[row]
- if highlight.row == row and highlight.column == column and highlight.word == word
- return true
- return false
-
- describe "addHighlight", ->
- beforeEach ->
- @highlightedWordsManager.addHighlight
- row: 1
- column: 5
- word: "sharelatex"
-
- it "should add a marker into ace", ->
- @ide.editor.addMarker.called.should.equal true
- [options, klass] = @ide.editor.addMarker.args[0]
- options.should.deep.equal row: 1, column: 5, length: 10
- klass.should.equal "sharelatex-spelling-highlight"
-
- it "should record the highlight internally", ->
- row = @highlightedWordsManager.highlights.rows[1]
- highlight = row[0]
- highlight.word.should.equal "sharelatex"
- highlight.markerId.should.equal @currentMarkerId
-
- describe "removeHighlight", ->
- beforeEach ->
- @highlightedWordsManager.addHighlight row: 0, column: 5, word: "sharelatex"
- @highlightedWordsManager.addHighlight row: 0, column: 17, word: "sharelatex"
- @highlightedWordsManager.addHighlight row: 0, column: 42, word: "sharelatex"
- @highlight = @highlightedWordsManager.highlights.rows[0][1]
- @highlightedWordsManager.removeHighlight @highlight
-
- it "should remove the marker", ->
- @ide.editor.removeMarker.calledWith(@highlight.markerId).should.equal true
-
- it "should remove the highlight internally", ->
- @highlightedWordsManager.doesHighlightExist(0, 17, "sharelatex").should.equal false
-
- describe "removeWord", ->
- beforeEach ->
- @highlightedWordsManager.addHighlight row: 0, column: 5, word: "sharelatex"
- @highlightedWordsManager.addHighlight row: 1, column: 5, word: "banana"
- @highlightedWordsManager.addHighlight row: 3, column: 5, word: "sharelatex"
- @highlightedWordsManager.addHighlight row: 4, column: 5, word: "monkey"
- @highlightedWordsManager.removeWord "sharelatex"
-
- it "should remove all instances of the word", ->
- @highlightedWordsManager.doesHighlightExist(0, 5, "sharelatex").should.equal false
- @highlightedWordsManager.doesHighlightExist(3, 5, "sharelatex").should.equal false
-
- it "should not remove other highlights", ->
- @highlightedWordsManager.doesHighlightExist(1, 5, "banana").should.equal true
- @highlightedWordsManager.doesHighlightExist(4, 5, "monkey").should.equal true
-
- describe "moveHighlight", ->
- beforeEach ->
- @highlightedWordsManager.addHighlight row: 0, column: 5, word: "sharelatex"
- @highlight = @highlightedWordsManager.highlights.rows[0][0]
- @oldMarkerId = @highlight.markerId
- @highlightedWordsManager.moveHighlight @highlight, row: 1, column: 17
-
- it "should remove the old marker", ->
- @ide.editor.removeMarker.calledWith(@oldMarkerId).should.equal true
-
- it "should insert a new marker", ->
- @ide.editor.addMarker.calledWith(
- {row: 1, column: 17, length: 10},
- "sharelatex-spelling-highlight"
- ).should.equal true
-
- it "should move the highlight internally", ->
- @highlightedWordsManager.doesHighlightExist(0, 5, "sharelatex").should.equal false
- @highlightedWordsManager.doesHighlightExist(1, 17, "sharelatex").should.equal true
-
- describe "clearHighlights", ->
- beforeEach ->
- @highlightedWordsManager.addHighlight row: 0, column: 5, word: "sharelatex"
- @highlightedWordsManager.addHighlight row: 1, column: 5, word: "sharelatex"
- @highlightedWordsManager.addHighlight row: 3, column: 5, word: "sharelatex"
- @highlightedWordsManager.addHighlight row: 4, column: 5, word: "sharelatex"
-
- describe "with a range", ->
- beforeEach ->
- @highlightedWordsManager.clearRows 1, 3
-
- it "should clear the given rows", ->
- @highlightedWordsManager.doesHighlightExist(1, 5, "sharelatex").should.equal false
- @highlightedWordsManager.doesHighlightExist(3, 5, "sharelatex").should.equal false
-
- it "should not clear the other rows", ->
- @highlightedWordsManager.doesHighlightExist(0, 5, "sharelatex").should.equal true
- @highlightedWordsManager.doesHighlightExist(4, 5, "sharelatex").should.equal true
-
- describe "with no range", ->
- beforeEach ->
- @highlightedWordsManager.clearRows()
-
- it "should clear all the rows", ->
- @highlightedWordsManager.doesHighlightExist(0, 5, "sharelatex").should.equal false
- @highlightedWordsManager.doesHighlightExist(1, 5, "sharelatex").should.equal false
- @highlightedWordsManager.doesHighlightExist(3, 5, "sharelatex").should.equal false
- @highlightedWordsManager.doesHighlightExist(4, 5, "sharelatex").should.equal false
-
-
- describe "applyChange", ->
- beforeEach ->
- @highlightedWordsManager.addHighlight row: 0, column: 5, word: "sharelatex"
- @highlightedWordsManager.addHighlight row: 0, column: 17, word: "latex"
- @highlightedWordsManager.addHighlight row: 0, column: 25, word: "monkey"
- @highlightedWordsManager.addHighlight row: 1, column: 5, word: "banana"
-
- describe "inserting text into a single line", ->
- beforeEach ->
- @highlightedWordsManager.applyChange
- action: "insertText"
- range:
- start:
- row: 0
- column: 17
- end:
- row: 0
- column: 22
- text: "share"
-
- it "should move highlights after the change", ->
- @highlightedWordsManager.doesHighlightExist(0, 25, "monkey").should.equal false
- @highlightedWordsManager.doesHighlightExist(0, 30, "monkey").should.equal true
-
- it "should remove highlights affected by the change", ->
- @highlightedWordsManager.doesHighlightExist(0, 17, "latex").should.equal false
-
- it "should not affect highlights before the change", ->
- @highlightedWordsManager.doesHighlightExist(0, 5, "sharelatex").should.equal true
-
- describe "inserting text into multiple lines", ->
- beforeEach ->
- @highlightedWordsManager.applyChange
- action: "insertText"
- range:
- start:
- row: 0
- column: 18
- end:
- row: 1
- column: 0
- text: "\n"
-
- it "should move highlights after the change onto the new line", ->
- @highlightedWordsManager.doesHighlightExist(0, 25, "monkey").should.equal false
- @highlightedWordsManager.doesHighlightExist(1, 7, "monkey").should.equal true
-
- it "should remove highlights affected by the change", ->
- @highlightedWordsManager.doesHighlightExist(0, 17, "latex").should.equal false
-
- it "should not affect highlights before the change", ->
- @highlightedWordsManager.doesHighlightExist(0, 5, "sharelatex").should.equal true
-
- describe "inserting lines", ->
- beforeEach ->
- @highlightedWordsManager.applyChange
- action: "insertLines"
- range:
- start:
- row: 1
- column: 0
- end:
- row: 3
- column: 0
- lines: ["", ""]
-
- it "should move the highlights after the inserted lines", ->
- @highlightedWordsManager.doesHighlightExist(1, 5, "banana").should.equal false
- @highlightedWordsManager.doesHighlightExist(3, 5, "banana").should.equal true
-
- it "should not affect highlights before the inserted lines", ->
- @highlightedWordsManager.doesHighlightExist(0, 5, "sharelatex").should.equal true
-
- describe "deleting text from a single line", ->
- beforeEach ->
- @highlightedWordsManager.applyChange
- action: "removeText"
- range:
- start:
- row: 0
- column: 19
- end:
- row: 0
- column: 20
- text: "t"
-
- it "should move highlights after the change", ->
- @highlightedWordsManager.doesHighlightExist(0, 25, "monkey").should.equal false
- @highlightedWordsManager.doesHighlightExist(0, 24, "monkey").should.equal true
-
- it "should remove highlights affected by the change", ->
- @highlightedWordsManager.doesHighlightExist(0, 17, "latex").should.equal false
-
- it "should not affect highlights before the change", ->
- @highlightedWordsManager.doesHighlightExist(0, 5, "sharelatex").should.equal true
-
- describe "deleting text from a multiple lines", ->
- beforeEach ->
- @highlightedWordsManager.applyChange
- action: "removeText"
- range:
- start:
- row: 0
- column: 27
- end:
- row: 1
- column: 3
-
- it "should move highlights after the change onto the first line", ->
- @highlightedWordsManager.doesHighlightExist(1, 5, "banana").should.equal false
- @highlightedWordsManager.doesHighlightExist(0, 29, "banana").should.equal true
-
- it "should remove highlights affected by the change", ->
- @highlightedWordsManager.doesHighlightExist(0, 25, "monkey").should.equal false
-
- it "should not affect highlights before the change", ->
- @highlightedWordsManager.doesHighlightExist(0, 5, "sharelatex").should.equal true
-
- describe "deleting lines", ->
- beforeEach ->
- @highlightedWordsManager.applyChange
- action: "removeLines"
- range:
- start:
- row: 0
- column: 0
- end:
- row: 1
- column: 0
- lines: [""]
-
- it "should move the highlights after the inserted lines", ->
- @highlightedWordsManager.doesHighlightExist(1, 5, "banana").should.equal false
- @highlightedWordsManager.doesHighlightExist(0, 5, "banana").should.equal true
-
- it "should remove the highlights in the removed lines", ->
- @highlightedWordsManager.doesHighlightExist(0, 5, "sharelatex").should.equal false
-
- describe "findHighlightWithinRange", ->
- beforeEach ->
- @highlightedWordsManager.addHighlight row: 0, column: 5, word: "sharelatex"
- @highlightedWordsManager.addHighlight row: 0, column: 17, word: "latex"
- @highlightedWordsManager.addHighlight row: 0, column: 25, word: "monkey"
- @highlightedWordsManager.addHighlight row: 1, column: 5, word: "banana"
-
- describe "with range inside word", ->
- beforeEach ->
- @highlight = @highlightedWordsManager.findHighlightWithinRange
- start: row: 0, column: 6
- end: row: 0, column: 7
-
- it "should return the highlight", ->
- @highlight.row.should.equal 0
- @highlight.column.should.equal 5
- @highlight.word.should.equal "sharelatex"
-
- describe "with range outside word", ->
- beforeEach ->
- @highlight = @highlightedWordsManager.findHighlightWithinRange
- start: row: 0, column: 3
- end: row: 0, column: 4
-
- it "should return null", ->
- should.not.exist @highlight
-
- describe "with range equal to word", ->
- beforeEach ->
- @highlight = @highlightedWordsManager.findHighlightWithinRange
- start: row: 0, column: 5
- end: row: 0, column: 15
-
- it "should return null", ->
- @highlight.row.should.equal 0
- @highlight.column.should.equal 5
- @highlight.word.should.equal "sharelatex"
diff --git a/services/web/public/coffee/tests/unit/spelling/SpellingManagerTests.coffee b/services/web/public/coffee/tests/unit/spelling/SpellingManagerTests.coffee
deleted file mode 100644
index 1866785371..0000000000
--- a/services/web/public/coffee/tests/unit/spelling/SpellingManagerTests.coffee
+++ /dev/null
@@ -1,218 +0,0 @@
-define [
- "spelling/SpellingManager"
- "libs/chai"
-], (SpellingManager, chai) ->
- should = chai.should()
-
- describe "SpellingManager", ->
- beforeEach ->
- @el = $("")
- $("#test-area").append(@el)
- window.userSettings =
- spellCheckLanguage: "en"
- @lines = []
- @project =
- attributes: {}
- get: (attribute) -> @attributes[attribute]
- @ide =
- editor:
- on: () ->
- getLines: => @lines
- getContainerElement: () => @el
- project: @project
- _.extend(@ide, Backbone.Events)
- _.extend(@project, Backbone.Events)
- @spellingManager = new SpellingManager(@ide)
- @ide.trigger "afterJoinProject", @project
-
- describe "runSpellCheck", ->
- beforeEach ->
- @misspellings = [
- { row: 1, column: 2, word: "bussines", index: 1, suggestions: ["business"] }
- { row: 4, column: 0, word: "misteak", index: 2, suggestions: ["mistake"] }
- ]
- @words =
- words: ["sharelatex", "bussines", "misteak"]
- positions: [
- { row: 0, column: 5 },
- { row: 1, column: 2 },
- { row: 4, column: 0 }
- ]
- @spellingManager.getWords = () => @words
- sinon.spy @spellingManager, "getWords"
- @spellingManager.apiRequest = (endpoint, data, callback) => callback null, misspellings: @misspellings
- @spellingManager.highlightedWordManager.clearRows = sinon.stub()
- @spellingManager.highlightedWordManager.addHighlight = sinon.stub()
-
- describe "with no argument", ->
- beforeEach ->
- @spellingManager.runSpellCheck()
-
- it "should clear all of the existing highlights", ->
- @spellingManager.highlightedWordManager.clearRows.calledWithExactly().should.equal true
-
- it "should add in highlights for all misspellings", ->
- for misspelling, i in @misspellings
- highlight =
- row: misspelling.row
- column: misspelling.column
- word: misspelling.word
- suggestions: misspelling.suggestions
- @spellingManager.highlightedWordManager.addHighlight.args[i][0]
- .should.deep.equal highlight
-
- it "should get all words", ->
- @spellingManager.getWords.calledWithExactly(undefined).should.equal true
-
- describe "with specified lines", ->
- beforeEach ->
- @spellingManager.runSpellCheck(@lines = [false, true, false, true])
-
- it "should only get words on the given lines", ->
- @spellingManager.getWords.calledWithExactly(@lines).should.equal true
-
- it "should only clear the given rows of highlights", ->
- @spellingManager.highlightedWordManager.clearRows.calledWithExactly(1,1).should.equal true
- @spellingManager.highlightedWordManager.clearRows.calledWithExactly(3,3).should.equal true
-
- describe "markLinesAsUpdated", ->
- describe "inserts on a single line", ->
- beforeEach ->
- @spellingManager.markLinesAsUpdated
- action: "insertText"
- range:
- start: { row: 1, column: 3 }
- end: { row: 1, column: 8 }
-
- it "should mark the line as updated", ->
- @spellingManager.updatedLines[1].should.equal true
-
- describe "inserts on multiple lines", ->
- beforeEach ->
- @spellingManager.updatedLines = [false, false, false, false, true]
- @spellingManager.markLinesAsUpdated
- action: "insertText"
- range:
- start: { row: 1, column: 3 }
- end: { row: 3, column: 8 }
-
- it "should mark the lines as updated", ->
- @spellingManager.updatedLines[1].should.equal true
- @spellingManager.updatedLines[2].should.equal true
- @spellingManager.updatedLines[3].should.equal true
-
- it "should move existing lines", ->
- @spellingManager.updatedLines[4].should.equal false
- @spellingManager.updatedLines[6].should.equal true
-
- describe "deletes on a single line", ->
- beforeEach ->
- @spellingManager.markLinesAsUpdated
- action: "removeText"
- range:
- start: { row: 1, column: 3 }
- end: { row: 1, column: 8 }
-
- it "should mark the line as updated", ->
- @spellingManager.updatedLines[1].should.equal true
-
- describe "deletes on multiple lines", ->
- beforeEach ->
- @spellingManager.updatedLines = [false, false, false, false, false, true]
- @spellingManager.markLinesAsUpdated
- action: "removeText"
- range:
- start: { row: 1, column: 3 }
- end: { row: 3, column: 8 }
-
- it "should mark the lines as updated", ->
- @spellingManager.updatedLines[1].should.equal true
-
- it "should move existing lines", ->
- @spellingManager.updatedLines[3].should.equal true
-
- describe "insertLines", ->
- beforeEach ->
- @spellingManager.updatedLines = [false, false, false, false, true]
- @spellingManager.markLinesAsUpdated
- action: "insertLines"
- range:
- start: { row: 1, column: 0 }
- end: { row: 3, column: 0 }
-
- it "should mark the lines as updated", ->
- @spellingManager.updatedLines[1].should.equal true
- @spellingManager.updatedLines[2].should.equal true
-
- it "should move existing lines", ->
- @spellingManager.updatedLines[4].should.equal false
- @spellingManager.updatedLines[6].should.equal true
-
- describe "removeLines", ->
- beforeEach ->
- @spellingManager.updatedLines = [false, false, false, false, true]
- @spellingManager.markLinesAsUpdated
- action: "removeLines"
- range:
- start: { row: 1, column: 0 }
- end: { row: 3, column: 0 }
-
- it "should move existing lines", ->
- @spellingManager.updatedLines.length.should.equal 3
- @spellingManager.updatedLines[2].should.equal true
-
- describe "getWords", ->
- describe "with no argument", ->
- beforeEach ->
- @lines = [
- "\\documentclass{article}"
- "\\begin{document}"
- "Hello world"
- "\\end{document}"
- ]
- {@words, @positions} = @spellingManager.getWords()
-
- it "should return all of the words", ->
- @words.should.deep.equal [
- "\\documentclass", "article", "\\begin", "document", "Hello", "world", "\\end", "document"
- ]
-
- it "should return the correct positions", ->
- @positions.length.should.equal @words.length
- @positions[5].should.deep.equal row: 2, column: 6
-
- describe "with selective lines", ->
- beforeEach ->
- @lines = [
- "zero"
- "one uno"
- "two dos"
- "three"
- ]
- {@words, @positions} = @spellingManager.getWords([false, true, true, false])
-
- it "should only return words from the given lines", ->
- @words.should.deep.equal [
- "one", "uno", "two", "dos"
- ]
-
- describe "with accents", ->
- beforeEach ->
- @lines = ["accént"]
- {@words, @positions} = @spellingManager.getWords()
-
- it "should treat the accented character as a letter", ->
- @words.should.deep.equal ["accént"]
-
- describe "with single quote marks", ->
- beforeEach ->
- @lines = ["you'll 'words appear in quotes' and with apostrophe's"]
- {@words, @positions} = @spellingManager.getWords()
-
- it "should understand the difference between apostophes and quotes", ->
- @words.should.deep.equal [
- "you'll", "words", "appear", "in", "quotes", "and", "with", "apostrophe's"
- ]
-
-
-
diff --git a/services/web/public/coffee/tests/unit/user.coffee b/services/web/public/coffee/tests/unit/user.coffee
deleted file mode 100644
index 32b5210fb9..0000000000
--- a/services/web/public/coffee/tests/unit/user.coffee
+++ /dev/null
@@ -1,24 +0,0 @@
-define [
- "libs/chai"
- "models/User"
- "libs/sinon"
-], (chai, User) ->
- describe "User", ->
- describe "findOrBuild", ->
- describe "with an existing model", ->
- beforeEach ->
- @user = User.build "user-1"
- @user.set("email", "test@example.com")
-
- it "should return the same model", ->
- @newUser = User.findOrBuild "user-1", email: "new@example.com"
- @newUser.should.equal @user
- @newUser.get("email").should.equal "new@example.com"
-
- describe "without an existing model", ->
- it "should return new a model with the correct id", ->
- user = User.findOrBuild("user-2")
- user.id.should.equal "user-2"
-
- afterEach ->
- User.loadedModels = {}
diff --git a/services/web/public/coffee/tour/IdeTour.coffee b/services/web/public/coffee/tour/IdeTour.coffee
deleted file mode 100644
index 0779a3114c..0000000000
--- a/services/web/public/coffee/tour/IdeTour.coffee
+++ /dev/null
@@ -1,92 +0,0 @@
-
-define [
- "libs/intro"
- "utils/Modal"
- "libs/jquery.storage"
-], (introJs, Modal, storage)->
-
- key = "tour.hadIntroduction"
-
- saveTourHasBeenFinished = ->
- $.localStorage(key, true)
-
- getIfTourHasStarted = ->
- return $.localStorage(key)
-
- class IdeTour
- template: $("#editorTourTemplate").html()
-
- constructor: (@ide) ->
- @ide.on "afterJoinProject", () =>
- if !@inited
- @inited = true
- signUpDate = new Date(@ide.user.get("signUpDate")).getTime()
- oneDay = 24 * 60 * 60 * 1000
- yesterday = new Date().getTime() - oneDay
-
- signedUpToday = signUpDate > yesterday
-
- hadIntroduction = getIfTourHasStarted()
-
- if !hadIntroduction? and signedUpToday
- @run()
-
- @$el = $(@template)
- $("#toolbar-footer").append(@$el)
- @$el.on "click", (e) =>
- e.preventDefault()
- @run()
-
- run : ->
- $('#code-tab-li a').click()
-
- intro = introJs()
- intro.oncomplete saveTourHasBeenFinished
- intro.onexit saveTourHasBeenFinished
-
- isSplitView = $('#recompilePdf').is(":visible")
-
- if isSplitView
- pdfStep =
- element: '#pdfToolBar'
- intro: "Compile your project, check logs, download and change editor layout."
- else
- pdfStep =
- element: "li#pdf"
- intro: "Compile and preview your project."
-
- chatIsMinimized = $('.chat-window.minimized').is(":visible")
- if chatIsMinimized
- $('.js-minimize-toggle.minimize-toggle').click()
-
-
- steps = [
- {
- element: '.actions'
- intro: "Welcome to ShareLaTeX!
You can add, upload, rename and delete your documents here."
- },
- pdfStep,
- {
- element: '#history-tab-li'
- intro: "View what has changed in your project."
- position: 'right'
- }, {
- element: '#collaborators-tab-li'
- intro: "Add collaborators and share your project."
- position: 'right'
- }, {
- element: '#settings-tab-li'
- intro: "Change your project settings."
- position: 'right'
- }, {
- element: '.chat-window'
- intro: "Chat to your collaborators."
- position: 'top'
- }
- ]
-
- intro.setOptions(skipLabel: "Skip tour", steps:steps, showStepNumbers:false)
-
- intro.start()
-
-
diff --git a/services/web/public/coffee/track-changes/ChangeListView.coffee b/services/web/public/coffee/track-changes/ChangeListView.coffee
deleted file mode 100644
index 1212faa1fe..0000000000
--- a/services/web/public/coffee/track-changes/ChangeListView.coffee
+++ /dev/null
@@ -1,320 +0,0 @@
-define [
- "moment"
- "libs/mustache"
- "libs/backbone"
-], (moment)->
-
- 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'
-
- ChangeListView = Backbone.View.extend
- template: $("#changeListTemplate").html()
-
- events:
- "scroll" : () -> @loadUntilFull()
-
- initialize: () ->
- @itemViews = []
- @atEndOfCollection = false
-
- self = this
- @collection.on "add", (model) ->
- self.addItem model
- @collection.on "reset", (collection) ->
- self.addItem model for model in collection.models
-
- @selectedFromIndex = 0
- @selectedToIndex = 0
-
- @render()
- @hideLoading()
-
- render: ->
- @$el.html Mustache.to_html @template
- @$el.css
- overflow: "scroll"
- this
-
- remove: () ->
- @undelegateEvents()
-
- addItem: (model) ->
- index = @collection.indexOf(model)
- previousModel = @collection.models[index - 1]
- view = new ChangeListItemView(model : model, previousModel: previousModel)
- @itemViews.push view
- elementAtIndex = @$(".change-list").children()[index]
- view.$el.insertBefore(elementAtIndex)
-
- view.on "click", (e, v) =>
- if e.shiftKey
- @selectRangeTo(index)
- else
- @setSelectionRange(index, index)
-
- view.on "selected:to", (e, v) =>
- @setSelectionRange(@selectedFromIndex, index)
-
- view.on "selected:from", (e, v) =>
- @setSelectionRange(index, @selectedToIndex)
-
- view.on "mouseenter:to", (e) =>
- @hoverToIndex = index
- @resetHoverStates()
-
- view.on "mouseleave:to", (e) =>
- delete @hoverToIndex
- @resetHoverStates()
-
- view.on "mouseenter:from", (e) =>
- @hoverFromIndex = index
- @resetHoverStates()
-
- view.on "mouseleave:from", (e) =>
- delete @hoverFromIndex
- @resetHoverStates()
-
- view.resetSelector(index, @selectedFromIndex, @selectedToIndex)
-
- setSelectionRange: (fromIndex, toIndex) ->
- @selectedFromIndex = fromIndex
- @selectedToIndex = toIndex
- @resetAllSelectors()
- @triggerChangeDiff()
-
- selectRangeTo: (index) ->
- return unless @selectedFromIndex? and @selectedToIndex?
- if index < @selectedToIndex
- @setSelectionRange(@selectedFromIndex, index)
- else
- @setSelectionRange(index, @selectedToIndex)
-
- resetAllSelectors: () ->
- for view, i in @itemViews
- view.resetSelector(i, @selectedFromIndex, @selectedToIndex)
-
- resetHoverStates: () ->
- if @hoverToIndex? and @hoverToIndex != @selectedToIndex
- @$("ul").addClass("hover-state")
- for view, i in @itemViews
- view.resetHoverState(i, @selectedFromIndex, @hoverToIndex)
- else if @hoverFromIndex? and @hoverFromIndex != @selectedFromIndex
- @$("ul").addClass("hover-state")
- for view, i in @itemViews
- view.resetHoverState(i, @hoverFromIndex, @selectedToIndex)
- else
- @$("ul").removeClass("hover-state")
- for view, i in @itemViews
- view.setHoverUnselected()
-
- triggerChangeDiff: () ->
- @trigger "change_diff", @selectedFromIndex, @selectedToIndex
-
- listShorterThanContainer: ->
- @$el.height() > @$(".change-list").height()
-
- atEndOfListView: ->
- @$el.scrollTop() + @$el.height() >= @$(".change-list").height() - 30
-
- loadUntilFull: (callback = (error) ->) ->
- if (@listShorterThanContainer() or @atEndOfListView()) and not @atEndOfCollection and not @loading
- @showLoading()
- @hideEmptyMessage()
- @collection.fetchNextBatch
- error: (error) =>
- @hideLoading()
- @showEmptyMessageIfCollectionEmpty()
- callback(error)
- success: (collection, response) =>
- @hideLoading()
- if @collection.isAtEnd()
- @atEndOfCollection = true
- @showEmptyMessageIfCollectionEmpty()
- callback()
- else
- @loadUntilFull(callback)
-
- else
- callback() if callback?
-
- showEmptyMessageIfCollectionEmpty: ()->
- if @collection.isEmpty()
- @$(".empty-message").show()
- else
- @$(".empty-message").hide()
-
- hideEmptyMessage: () ->
- @$(".empty-message").hide()
-
- showLoading: ->
- @loading = true
- @$(".loading-changes").show()
-
- hideLoading: ->
- @loading = false
- @$(".loading-changes").hide()
-
- ChangeListItemView = Backbone.View.extend
- tagName: "li"
-
- events:
- "click .change-description" : "onClick"
- "click .change-selector-from" : "onFromSelectorClick"
- "click .change-selector-to" : "onToSelectorClick"
- "mouseenter .change-selector-to": (args...) ->
- @trigger "mouseenter:to", args...
- "mouseleave .change-selector-to": (args...) ->
- @trigger "mouseleave:to", args...
- "mouseenter .change-selector-from": (args...) ->
- @trigger "mouseenter:from", args...
- "mouseleave .change-selector-from": (args...) ->
- @trigger "mouseleave:from", args...
-
- templates:
- item: $("#changeListItemTemplate").html()
- user: $("#changeListItemUserTemplate").html()
-
- initialize: ->
- @render()
-
- render: ->
- userHtml = for user in @model.get("users")
- Mustache.to_html @templates.user, {
- hue: user.hue()
- name: user.name()
- }
- docNames = []
- for doc in @model.get("docs")
- if doc.entity?
- docNames.push doc.entity.get("name")
- else
- docNames.push "deleted"
- data = {
- day: moment(parseInt(@model.get("end_ts"), 10)).calendar()
- time: moment(parseInt(@model.get("end_ts"), 10)).format("h:mm a")
- users: userHtml.join("")
- docs: docNames.join(", ")
- }
-
- @$el.html Mustache.to_html(@templates.item, data)
-
- if @options.previousModel?
- prevDate = @options.previousModel.get("end_ts")
- date = @model.get("end_ts")
- if not moment(prevDate).isSame(date, "day")
- @$el.addClass("first-in-day")
- else
- @$el.addClass("first-in-day")
-
- @$(".change-selector-from").tooltip({
- title: "Show back to this change",
- placement: "left",
- animation: false
- })
- @$(".change-selector-to").tooltip({
- title: "Show up to this change",
- placement: "left",
- animation: false
- })
-
- return this
-
- onClick: (e) ->
- e.preventDefault()
- @trigger "click", e, @
-
- onToSelectorClick: (e) ->
- @trigger "selected:to", e, @
-
- onFromSelectorClick: (e) ->
- @trigger "selected:from", e, @
-
- isSelectedFrom: () ->
- @$(".change-selector-from").is(":checked")
-
- isSelectedTo: () ->
- @$(".change-selector-to").is(":checked")
-
- hideFromSelector: () ->
- @$(".change-selector-from").hide()
-
- showFromSelector: () ->
- @$(".change-selector-from").show()
-
- hideToSelector: () ->
- @$(".change-selector-to").hide()
-
- showToSelector: () ->
- @$(".change-selector-to").show()
-
- setFromChecked: (checked) ->
- @$(".change-selector-from").prop("checked", checked)
-
- setToChecked: (checked) ->
- @$(".change-selector-to").prop("checked", checked)
-
- setSelected: (first, last) ->
- @$el.addClass("selected-change")
- if first
- @$el.addClass("selected-change-to")
- else
- @$el.removeClass("selected-change-to")
- if last
- @$el.addClass("selected-change-from")
- else
- @$el.removeClass("selected-change-from")
-
- setUnselected: () ->
- @$el.removeClass("selected-change-to")
- @$el.removeClass("selected-change-from")
- @$el.removeClass("selected-change")
-
- setHoverSelected: (first, last) ->
- @$el.addClass("hover-selected")
- if first
- @$el.addClass("hover-selected-to")
- else
- @$el.removeClass("hover-selected-to")
- if last
- @$el.addClass("hover-selected-from")
- else
- @$el.removeClass("hover-selected-from")
-
- setHoverUnselected: () ->
- @$el.removeClass("hover-selected-to")
- @$el.removeClass("hover-selected-from")
- @$el.removeClass("hover-selected")
-
- resetSelector: (myIndex, selectedFromIndex, selectedToIndex) ->
- if myIndex >= selectedToIndex
- @showFromSelector()
- else
- @hideFromSelector()
-
- if myIndex <= selectedFromIndex
- @showToSelector()
- else
- @hideToSelector()
-
- if selectedToIndex <= myIndex <= selectedFromIndex
- @setSelected(selectedToIndex == myIndex, selectedFromIndex == myIndex)
- else
- @setUnselected()
-
- @setFromChecked(myIndex == selectedFromIndex)
- @setToChecked(myIndex == selectedToIndex)
-
- resetHoverState: (myIndex, hoverFromIndex, hoverToIndex) ->
- if hoverToIndex <= myIndex <= hoverFromIndex
- @setHoverSelected(hoverToIndex == myIndex, hoverFromIndex == myIndex)
- else
- @setHoverUnselected()
-
-
- return ChangeListView
-
diff --git a/services/web/public/coffee/track-changes/DiffView.coffee b/services/web/public/coffee/track-changes/DiffView.coffee
deleted file mode 100644
index b5c3269ce4..0000000000
--- a/services/web/public/coffee/track-changes/DiffView.coffee
+++ /dev/null
@@ -1,285 +0,0 @@
-define [
- "ace/ace"
- "ace/mode/latex"
- "ace/range"
- "moment"
- "libs/backbone"
- "libs/mustache"
-], (Ace, LatexMode, Range, moment)->
- DiffView = Backbone.View.extend
- template: $("#trackChangesDiffTemplate").html()
-
- events:
- "click .restore": (e) ->
- e.preventDefault()
- @trigger "restore"
- "click .restore-deleted": (e) ->
- e.preventDefault()
- @$("a.restore-deleted").attr("disabled", true)
- @$("a.restore-deleted").text("Restoring...")
- @trigger "restore-deleted"
-
- initialize: () ->
- if !@model.get("doc").get("deleted")
- @model.on "change:diff", () => @render()
- @model.fetch()
- else
- @render()
-
- render: ->
- changes = @getNumberOfChanges()
- html = Mustache.to_html @template, {
- changes: "#{changes} change#{if changes == 1 then "" else "s"}"
- name: @model.get("doc")?.get("name")
- }
- @$el.html(html)
-
- if @model.get("doc").get("deleted")
- @$(".change-info").hide()
- @$(".deleted-info").show()
- else
- diff = @model.get("diff")
- return unless diff?
-
- if !@model.get("from")? or !@model.get("to")? or changes == 0
- @$(".restore").hide()
-
- @createAceEditor()
- @aceEditor.setValue(@getPlainDiffContent())
- @aceEditor.clearSelection()
- @$ace = $(@aceEditor.renderer.container).find(".ace_scroller")
- @insertMarkers()
- @insertNameTag()
- @insertMoreChangeLabels()
- @bindToScrollEvents()
- @scrollToFirstChange()
-
- return @
-
- remove: () ->
- @$editor?.remove()
- @undelegateEvents()
-
- createAceEditor: () ->
- @$editor = @$(".track-changes-diff-editor")
- @$el.append(@$editor)
- @aceEditor = Ace.edit(@$editor[0])
- @aceEditor.setTheme("ace/theme/#{window.userSettings.theme}")
- @aceEditor.setReadOnly true
- @aceEditor.setShowPrintMargin(false)
- session = @aceEditor.getSession()
- session.setMode(new LatexMode.Mode())
- session.setUseWrapMode(true)
-
- @aceEditor.on "mousemove", (e) =>
- position = @aceEditor.renderer.screenToTextCoordinates(e.clientX, e.clientY)
- e.position = position
- @updateVisibleNames(e)
-
- bindToScrollEvents: () ->
- @aceEditor.getSession().on "changeScrollTop", (e) =>
- @updateMoreChangeLabels()
-
- getPlainDiffContent: () ->
- content = ""
- for entry in @model.get("diff") or []
- content += entry.u or entry.i or entry.d or ""
- return content
-
- getNumberOfChanges: () ->
- changes = 0
- for entry in @model.get("diff") or []
- changes += 1 if entry.i? or entry.d?
- return changes
-
- insertMarkers: () ->
- row = 0
- column = 0
- @entries = []
- for entry, i in @model.get("diff") or []
- content = entry.u or entry.i or entry.d
- 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 = new Range.Range(
- startRow, startColumn, endRow, endColumn
- )
- entry.range = range
- @addMarker(range, entry)
- if entry.i? or entry.d?
- @entries.push entry
-
- addMarker: (range, entry) ->
- session = @aceEditor.getSession()
- markerBackLayer = @aceEditor.renderer.$markerBack
- markerFrontLayer = @aceEditor.renderer.$markerFront
- lineHeight = @aceEditor.renderer.lineHeight
-
- dark = @_isDarkTheme()
-
- if entry.i? or entry.d?
- hue = entry.meta.user.hue()
- if entry.i?
- if dark
- style = "background-color : hsl(#{hue}, 100%, 28%);"
- else
- style = "background-color : hsl(#{hue}, 70%, 85%);"
- @_addMarkerWithCustomStyle session, markerBackLayer, range, "inserted-change-background", false, style
- if entry.d?
- if dark
- bgStyle = "background-color: hsl(#{hue}, 100%, 20%);"
- fgStyle = "border-bottom: 2px solid hsl(#{hue}, 100%, 60%);"
- else
- bgStyle = "background-color: hsl(#{hue}, 70%, 95%);"
- fgStyle = "border-bottom: 2px solid hsl(#{hue}, 70%, 40%);"
- fgStyle += "; height: #{Math.round(lineHeight/2) - 1}px;"
- @_addMarkerWithCustomStyle session, markerBackLayer, range, "deleted-change-background", false, bgStyle
- @_addMarkerWithCustomStyle session, markerBackLayer, range, "deleted-change-foreground", true, fgStyle
-
- _addMarkerWithCustomStyle: (session, markerLayer, range, klass, foreground, style) ->
- session.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
-
- _isDarkTheme: () ->
- rgb = $(".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
-
- insertNameTag: () ->
- @$nameTagEl = $("")
- @$nameTagEl.css({
- position: "absolute"
- })
- @$nameTagEl.hide()
- @$ace.append(@$nameTagEl)
-
- insertMoreChangeLabels: () ->
- @$changesBefore = $(" ")
- @$changesAfter = $(" ")
- @$ace.append(@$changesBefore)
- @$ace.append(@$changesAfter)
- @$changesBefore.on "click", () =>
- @gotoLastHiddenChangeBefore()
- @$changesAfter.on "click", () =>
- @gotoFirstHiddenChangeAfter()
- @updateMoreChangeLabels()
-
- scrollToFirstChange: () ->
- @aceEditor.scrollToLine(0)
- setTimeout () =>
- if @entries? and @entries[0]?
- row = @entries[0].range.start.row
- @aceEditor.scrollToLine(row, true, false)
- , 10
-
- _drawNameTag: (entry, position) ->
- @$nameTagEl.show()
-
- if entry.i?
- text = "Added by #{entry.meta.user.name()}"
- else if entry.d?
- text = "Deleted by #{entry.meta.user.name()}"
- date = moment(parseInt(entry.meta.end_ts, 10)).format("Do MMM YYYY, h:mm a")
- text += " on #{date}"
- @$nameTagEl.text(text)
-
- position = @aceEditor.renderer.textToScreenCoordinates(position.row, position.column)
- offset = @$ace.offset()
- position.pageX = position.pageX - offset.left
- position.pageY = position.pageY - offset.top
- height = @$ace.height()
-
- hue = entry.meta.user.hue()
- if @_isDarkTheme()
- css = { "background-color" : "hsl(#{hue}, 100%, 20%)"; }
- else
- css = { "background-color" : "hsl(#{hue}, 70%, 90%)"; }
-
- if position.pageX + @$nameTagEl.width() < @$ace.width()
- css["left"] = position.pageX
- css["right"] = "auto"
- else
- css["right"] = 0
- css["left"] = "auto"
-
- if position.pageY > 2 * @$nameTagEl.height()
- css["bottom"] = height - position.pageY
- css["top"] = "auto"
- else
- css["top"] = position.pageY + @aceEditor.renderer.lineHeight
- css["bottom"] = "auto"
-
- @$nameTagEl.css css
-
- _hideNameTag: () ->
- @$nameTagEl?.hide()
-
- updateVisibleNames: (e) ->
- visibleName = false
- for entry in @entries or []
- if entry.range.contains(e.position.row, e.position.column)
- @_drawNameTag(entry, e.position)
- visibleName = true
- break
- if !visibleName
- @_hideNameTag()
-
- updateMoreChangeLabels: () ->
- return if !@$changesBefore or !@$changesAfter
- setTimeout () =>
- firstRow = @aceEditor.getFirstVisibleRow()
- lastRow = @aceEditor.getLastVisibleRow()
- changesBefore = 0
- changesAfter = 0
- @lastHiddenChangeBefore = null
- @firstHiddenChangeAfter = null
- for entry in @entries or []
- if entry.range.start.row < firstRow
- changesBefore += 1
- @lastHiddenChangeBefore = entry
- if entry.range.end.row > lastRow
- changesAfter += 1
- @firstHiddenChangeAfter ||= entry
-
- if changesBefore > 0
- @$changesBefore.find("span").text("#{changesBefore} more change#{if changesBefore > 1 then "s" else ""} above")
- @$changesBefore.show()
- else
- @$changesBefore.hide()
- if changesAfter > 0
- @$changesAfter.find("span").text("#{changesAfter} more change#{if changesAfter > 1 then "s" else ""} below")
- @$changesAfter.show()
- else
- @$changesAfter.hide()
- , 100
-
- gotoLastHiddenChangeBefore: () ->
- return if !@lastHiddenChangeBefore
- @aceEditor.scrollToLine(@lastHiddenChangeBefore.range.start.row, true, false)
-
- gotoFirstHiddenChangeAfter: () ->
- return if !@firstHiddenChangeAfter
- @aceEditor.scrollToLine(@firstHiddenChangeAfter.range.end.row, true, false)
-
- resize: () ->
- @aceEditor?.resize()
-
- return DiffView
-
diff --git a/services/web/public/coffee/track-changes/TrackChangesManager.coffee b/services/web/public/coffee/track-changes/TrackChangesManager.coffee
deleted file mode 100644
index dfa6d52ead..0000000000
--- a/services/web/public/coffee/track-changes/TrackChangesManager.coffee
+++ /dev/null
@@ -1,264 +0,0 @@
-define [
- "track-changes/models/ChangeList"
- "track-changes/models/Diff"
- "track-changes/ChangeListView"
- "track-changes/DiffView"
- "account/AccountManager"
- "utils/Modal"
- "models/Doc"
- "moment"
-], (ChangeList, Diff, ChangeListView, DiffView, AccountManager, Modal, Doc, moment) ->
- class TrackChangesManager
- template: $("#trackChangesPanelTemplate").html()
-
- constructor: (@ide) ->
- @project_id = window.userSettings.project_id
- @$el = $(@template)
- @ide.mainAreaManager.addArea
- identifier: "trackChanges"
- element: @$el
-
- @ide.tabManager.addTab
- id: "history"
- name: "History"
- show: "code"
- after: "code"
- contract: true
- onShown: () => @show()
- onHidden: () => @hide()
-
- @ide.editor.on "resize", () =>
- @diffView?.resize()
-
- @$el.find(".track-changes-close").on "click", (e) =>
- e.preventDefault
- @hide()
-
- @bindToFileTreeEvents()
-
- @disable()
-
- bindToFileTreeEvents: () ->
- @ide.fileTreeManager.on "open:doc", (doc_id) =>
- @doc_id = doc_id
- if @enabled
- @updateDiff()
-
- AB_BUCKETS: ["control", "one-week", "pop-up"]
- show: () ->
- @changes = new ChangeList([], project_id: @project_id, ide: @ide)
-
- if @changeListView?
- @changeListView.remove()
- @changeListView = new ChangeListView(
- el: @$el.find(".change-list-area")
- collection: @changes
- )
- @changeListView.render()
- @changeListView.loadUntilFull (error) =>
- @autoSelectDiff()
-
- @changeListView.on "change_diff", (fromIndex, toIndex) =>
- @findDocsInChange(fromIndex, toIndex)
- @updateLabels()
- @updateDiff()
-
- @showUpgradeView()
-
- if @diffView?
- @diffView.remove()
-
- @ide.mainAreaManager.change "trackChanges"
- @ide.editor.disable()
- @ide.fileViewManager.disable()
-
- @ide.fileTreeManager.makeReadOnly()
- @ide.fileTreeManager.showDeletedDocs()
-
- @enable()
-
- showUpgradeView: () ->
- @$el.find("button.start-free-trial").off "click.track-changes"
- @$el.find("button.start-free-trial").on "click.track-changes", () => @gotoFreeTrial()
-
- if !@ide.project.get("features").versioning
- ga('send', 'event', 'subscription-funnel', 'askToUgrade', "trackchanges")
- @$el.find(".track-changes-upgrade-popup").show()
-
- if @ide.project.get("owner") == @ide.user
- @$el.find(".show-when-not-owner").hide()
- else
- @$el.find(".show-when-owner").hide()
-
- hide: () ->
- @ide.editor.enable()
- @ide.fileViewManager.enable()
- @disable()
-
- doc = @ide.fileTreeManager.getEntity(@doc_id, include_deleted: true)
- if doc? and doc.get("deleted")
- @ide.fileTreeManager.openDoc(@ide.project.get("rootDoc_id"))
- else
- @ide.fileTreeManager.openDoc(@doc_id)
-
- @ide.tabManager.show "code"
- @resetLabels()
- @ide.fileTreeManager.makeReadWriteIfAllowed()
- @ide.fileTreeManager.hideDeletedDocs()
-
- autoSelectDiff: () ->
- if @changes.models.length == 0
- return
-
- # Find all change until the last one we made
- fromIndex = null
- for change, i in @changes.models
- if ide.user in change.get("users")
- if i > 0
- fromIndex = i - 1
- else
- fromIndex = 0
- break
- fromIndex = 0 if !fromIndex
-
- toChange = @changes.models[0]
- fromChange = @changes.models[fromIndex]
- @changeListView.setSelectionRange(fromIndex, 0)
- @updateDiff()
-
- findDocsInChange: (fromIndex, toIndex) ->
- @changed_doc_ids = []
- for change in @changes.models.slice(toIndex, fromIndex + 1)
- for doc in change.get("docs") or []
- @changed_doc_ids.push doc.id if doc.id not in @changed_doc_ids
-
- if !@doc_id? or @doc_id not in @changed_doc_ids
- @doc_id = @changed_doc_ids[0]
-
- @updateDiff()
-
- updateLabels: () ->
- labels = {}
- for doc_id in @changed_doc_ids
- labels[doc_id] = true
- @ide.fileTreeManager.setLabels(labels)
-
- resetLabels: () ->
- @ide.fileTreeManager.setLabels({})
-
-
- updateDiff: () ->
- fromIndex = @changeListView.selectedFromIndex
- toIndex = @changeListView.selectedToIndex
-
- if !toIndex? or !fromIndex?
- console.log "No selection - what should we do!?"
- return
-
- {from, to, start_ts, end_ts} = @_findDocVersionsRangeInSelection(@doc_id, fromIndex, toIndex)
-
- @diff = new Diff({
- project_id: @project_id
- doc_id: @doc_id
- from: from
- to: to
- start_ts: start_ts
- end_ts: end_ts
- }, {
- ide: @ide
- })
-
- if @diffView?
- @diffView.remove()
-
- if !@diff.get("doc")?
- console.log "This document does not exist. What should we do?"
- return
-
- @diffView = new DiffView(
- model: @diff
- el: @$el.find(".track-changes-diff")
- )
-
- @diffView.on "restore", () =>
- @restoreDiff(@diff)
-
- @diffView.on "restore-deleted", () =>
- @restoreDeletedDoc @diff.get("doc"), (error, doc_id) =>
- return if error? or !doc_id?
- setTimeout () =>
- # Give doc a chance to appear in file tree via socket.io
- @hide()
- @ide.fileTreeManager.openDoc(doc_id)
- , 1000
-
- @ide.fileTreeManager.selectEntity(@doc_id)
-
-
- _findDocVersionsRangeInSelection: (doc_id, fromIndex, toIndex) ->
- from = to = start_ts = end_ts = null
-
- for change in @changes.models.slice(toIndex, fromIndex + 1)
- for doc in change.get("docs")
- if doc.id == doc_id
- if from? and to?
- from = Math.min(from, doc.fromV)
- to = Math.max(to, doc.toV)
- start_ts = Math.min(start_ts, change.get("start_ts"))
- end_ts = Math.max(end_ts, change.get("end_ts"))
- else
- from = doc.fromV
- to = doc.toV
- start_ts = change.get("start_ts")
- end_ts = change.get("end_ts")
- break
-
- return {from, to, start_ts, end_ts}
-
- restoreDiff: (diff) ->
- name = diff.get("doc")?.get("name")
- date = moment(diff.get("start_ts")).format("Do MMM YYYY, h:mm:ss a")
- modal = new Modal({
- title: "Restore document"
- message: "Are you sure you want to restore #{name} to before the changes on #{date}?"
- buttons: [{
- text: "Cancel"
- }, {
- text: "Restore"
- class: "btn-success"
- close: false
- callback: ($button) =>
- $button.text("Restoring...")
- $button.prop("disabled", true)
- diff.restore (error) =>
- modal.remove()
- @hide()
- }]
- })
-
- restoreDeletedDoc: (doc, callback) ->
- $.ajax {
- url: "/project/#{@project_id}/doc/#{doc.get("id")}/restore"
- type: "POST"
- dataType: "json"
- data:
- name: doc.get("name")
- headers:
- "X-CSRF-Token": window.csrfToken
- success: (body, status, response) ->
- callback(null, body?.doc_id)
- error: (error) ->
- callback(error)
- }
-
- enable: () ->
- @enabled = true
-
- disable: () ->
- @enabled = false
-
- gotoFreeTrial: () ->
- AccountManager.gotoSubscriptionsPage()
- ga('send', 'event', 'subscription-funnel', 'upgraded-free-trial', "trackchanges")
-
- return TrackChangesManager
diff --git a/services/web/public/coffee/track-changes/models/Change.coffee b/services/web/public/coffee/track-changes/models/Change.coffee
deleted file mode 100644
index ac48997583..0000000000
--- a/services/web/public/coffee/track-changes/models/Change.coffee
+++ /dev/null
@@ -1,26 +0,0 @@
-define [
- "models/User"
- "libs/backbone"
-], (User)->
- Change = Backbone.Model.extend
- parse: (change) ->
- model = {
- start_ts: change.meta.start_ts
- end_ts: change.meta.end_ts
- }
- model.users = []
- for user in change.meta.users or []
- model.users.push User.findOrBuild(user.id, user)
- if model.users.length == 0
- model.users.push User.getAnonymousUser()
- model.docs = []
- for doc_id, data of change.docs
- model.docs.push
- id: doc_id
- fromV: data.fromV
- toV: data.toV
- # TODO: We should not use a global reference here, but
- # it's hard to get @ide into Backbone at this point.
- entity: ide.fileTreeManager.getEntity(doc_id, include_deleted: true)
-
- return model
\ No newline at end of file
diff --git a/services/web/public/coffee/track-changes/models/ChangeList.coffee b/services/web/public/coffee/track-changes/models/ChangeList.coffee
deleted file mode 100644
index 82fd84fef7..0000000000
--- a/services/web/public/coffee/track-changes/models/ChangeList.coffee
+++ /dev/null
@@ -1,33 +0,0 @@
-define [
- "track-changes/models/Change"
- "libs/backbone"
-], (Change)->
- ChangeList = Backbone.Collection.extend
- model: Change
- batchSize: 10
-
- initialize: (models, @options) ->
- @ide = @options.ide
- @atEnd = false
-
- url: () ->
- url = "/project/#{@options.project_id}/updates?min_count=#{@batchSize}"
- if @nextBeforeTimestamp?
- url += "&before=#{@nextBeforeTimestamp}"
- return url
-
- isAtEnd: () -> @atEnd
-
- parse: (json) ->
- @nextBeforeTimestamp = json.nextBeforeTimestamp
- @atEnd = !@nextBeforeTimestamp
- return json.updates
-
- fetchNextBatch: (options = {}) ->
- if @isAtEnd()
- options.success?(@)
- return
- options.add = true
- @fetch options
-
-
diff --git a/services/web/public/coffee/track-changes/models/Diff.coffee b/services/web/public/coffee/track-changes/models/Diff.coffee
deleted file mode 100644
index e0c108ac66..0000000000
--- a/services/web/public/coffee/track-changes/models/Diff.coffee
+++ /dev/null
@@ -1,35 +0,0 @@
-define [
- "models/User"
- "libs/backbone"
-], (User) ->
- Diff = Backbone.Model.extend
- initialize: (attributes, options) ->
- @ide = options.ide
- @set "doc", @ide.fileTreeManager.getEntity(@get("doc_id"), include_deleted: true)
-
- url: () ->
- url = "/project/#{@get("project_id")}/doc/#{@get("doc_id")}/diff"
- if @get("from")? and @get("to")?
- url += "?from=#{@get("from")}&to=#{@get("to")}"
- return url
-
- parse: (diff) ->
- for entry in diff.diff
- if entry.meta?
- if entry.meta.user?
- entry.meta.user = User.findOrBuild(entry.meta.user.id, entry.meta.user)
- else
- entry.meta.user = User.getAnonymousUser()
- return diff
-
- restore: (callback = (error) ->) ->
- $.ajax {
- url: "/project/#{@get("project_id")}/doc/#{@get("doc_id")}/version/#{@get("from")}/restore"
- type: "POST"
- headers:
- "X-CSRF-Token": window.csrfToken
- success: () ->
- callback()
- error: (error) ->
- callback(error)
- }
diff --git a/services/web/public/coffee/undo/UndoManager.coffee b/services/web/public/coffee/undo/UndoManager.coffee
deleted file mode 100644
index db1c9f0880..0000000000
--- a/services/web/public/coffee/undo/UndoManager.coffee
+++ /dev/null
@@ -1,358 +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: (@manager) ->
- @reset()
- @nextUpdateIsRemote = false
-
- 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
- @manager.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
-
diff --git a/services/web/public/coffee/utils/ContextMenu.coffee b/services/web/public/coffee/utils/ContextMenu.coffee
deleted file mode 100644
index 10a62aabe2..0000000000
--- a/services/web/public/coffee/utils/ContextMenu.coffee
+++ /dev/null
@@ -1,62 +0,0 @@
-define [
- "libs/backbone"
- "libs/mustache"
-], () ->
- ContextMenuEntry = Backbone.View.extend
- template: $("#contextMenuEntryTemplate").html()
-
- events:
- "click a" : "onClick"
-
- render: () ->
- @setElement($(Mustache.to_html(@template, @options)))
- return @
-
- onClick: (e) ->
- e.preventDefault()
- if @options.onClick
- @options.onClick()
-
- ContextMenu = Backbone.View.extend
- templates:
- menu: $("#contextMenuTemplate").html()
- divider: $("#contextMenuDividerTemplate").html()
-
- initialize: (position, entries) ->
- if ContextMenu.currentMenu?
- ContextMenu.currentMenu.destroy()
- ContextMenu.currentMenu = @
- @render()
- for entry in entries
- @addEntry(entry)
- @show(position)
-
- render: () ->
- @setElement($(@templates.menu))
- $(document.body).append(@$el)
- return @
-
- destroy: () ->
- @$el.remove()
- @trigger "destroy"
-
- show: (position) ->
- page = $(document.body)
- page.on "click.hideContextMenu", (e) =>
- page.off "click.hideContextMenu"
- @destroy()
- @$el.css
- position: "absolute"
- "z-index": 10000
- @$el.css position
-
- addEntry: (options) ->
- if options.divider
- @$el.append $(@templates.divider)
- else
- entry = new ContextMenuEntry(options)
- @$el.append entry.render().el
-
-
-
-
diff --git a/services/web/public/coffee/utils/Effects.coffee b/services/web/public/coffee/utils/Effects.coffee
deleted file mode 100644
index 7d3d1c197f..0000000000
--- a/services/web/public/coffee/utils/Effects.coffee
+++ /dev/null
@@ -1,22 +0,0 @@
-define () ->
- Effects =
- highlight: (element, color, length, callback = () ->)->
- element.animate
- backgroundColor: color
- , length, -> callback()
-
- fadeElementIn: (element, callback = () -> )->
- element.hide()
- element.slideDown "slow"
- element.fadeIn "slow"
- @highlight element, "#BDDFB3", 1500, =>
- setTimeout ( () -> element.removeAttr('style') ), 0
- callback()
-
- fadeElementOut: (element, callback = () -> )->
- @highlight element, "#FF8E8A", 800, ->
- element.slideUp "slow"
- element.fadeOut "slow", ->
- callback()
-
-
diff --git a/services/web/public/coffee/app/utils/EventEmitter.coffee b/services/web/public/coffee/utils/EventEmitter.coffee
similarity index 100%
rename from services/web/public/coffee/app/utils/EventEmitter.coffee
rename to services/web/public/coffee/utils/EventEmitter.coffee
diff --git a/services/web/public/coffee/utils/Modal.coffee b/services/web/public/coffee/utils/Modal.coffee
deleted file mode 100644
index 66c5b317af..0000000000
--- a/services/web/public/coffee/utils/Modal.coffee
+++ /dev/null
@@ -1,65 +0,0 @@
-define [
- "libs/backbone"
- "libs/mustache"
-], () ->
- Modal = Backbone.View.extend {
- templates:
- modal: $("#genericModalTemplate").html()
- button: $("#genericModalButtonTemplate").html()
-
- initialize: () ->
- @render()
- @options.buttons ||= []
- self = @
- for buttonOptions in @options.buttons
- do (buttonOptions) ->
- button = $(Mustache.to_html self.templates.button, buttonOptions)
- self.$(".modal-footer").prepend button
- button.on "click", (e) ->
- e.preventDefault()
- if buttonOptions.callback?
- buttonOptions.callback(button)
- if !buttonOptions.close? or buttonOptions.close
- self.remove()
-
- @$el.modal
- # make sure we control when the modal is hidden
- keyboard: false
- backdrop: "static"
-
- @$el.on "hidden", () => @remove()
-
- if @options.clearBackdrop
- $(".modal-backdrop").addClass("clear-modal-backdrop")
-
- @$el.find('input').on "keydown", (event)->
- code = event.keyCode || event.which
- if code == 13
- self.$el.find('.btn-primary').click()
-
- @$el.find('input').focus()
-
- remove: () ->
- @$el.modal("hide")
- Backbone.View.prototype.remove.call(this)
-
- render: () ->
- @setElement $(
- Mustache.to_html @templates.modal, @options
- )
- if @options.el?
- @$(".message").append @options.el
- $(document.body).append(@$el)
- }, {
- createModal: (options) ->
- new Modal(options)
- }
-
- return Modal
-
-
-
-
-
-
-
diff --git a/services/web/public/coffee/app/utils/underscore.coffee b/services/web/public/coffee/utils/underscore.coffee
similarity index 100%
rename from services/web/public/coffee/app/utils/underscore.coffee
rename to services/web/public/coffee/utils/underscore.coffee
diff --git a/services/web/public/js/libs/angular-sanitize1.2.17.js b/services/web/public/js/libs/angular-sanitize-1.2.17.js
similarity index 100%
rename from services/web/public/js/libs/angular-sanitize1.2.17.js
rename to services/web/public/js/libs/angular-sanitize-1.2.17.js
diff --git a/services/web/public/js/libs/moment-2.4.0.js b/services/web/public/js/libs/moment-2.4.0.js
deleted file mode 100755
index 568ad05cec..0000000000
--- a/services/web/public/js/libs/moment-2.4.0.js
+++ /dev/null
@@ -1,6 +0,0 @@
-//! moment.js
-//! version : 2.4.0
-//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
-//! license : MIT
-//! momentjs.com
-(function(a){function b(a,b){return function(c){return i(a.call(this,c),b)}}function c(a,b){return function(c){return this.lang().ordinal(a.call(this,c),b)}}function d(){}function e(a){u(a),g(this,a)}function f(a){var b=o(a),c=b.year||0,d=b.month||0,e=b.week||0,f=b.day||0,g=b.hour||0,h=b.minute||0,i=b.second||0,j=b.millisecond||0;this._input=a,this._milliseconds=+j+1e3*i+6e4*h+36e5*g,this._days=+f+7*e,this._months=+d+12*c,this._data={},this._bubble()}function g(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return b.hasOwnProperty("toString")&&(a.toString=b.toString),b.hasOwnProperty("valueOf")&&(a.valueOf=b.valueOf),a}function h(a){return 0>a?Math.ceil(a):Math.floor(a)}function i(a,b){for(var c=a+"";c.lengthd;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function n(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=Kb[a]||Lb[b]||b}return a}function o(a){var b,c,d={};for(c in a)a.hasOwnProperty(c)&&(b=n(c),b&&(d[b]=a[c]));return d}function p(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}bb[b]=function(e,f){var g,h,i=bb.fn._lang[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=bb().utc().set(d,a);return i.call(bb.fn._lang,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function r(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function s(a){return t(a)?366:365}function t(a){return 0===a%4&&0!==a%100||0===a%400}function u(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[gb]<0||a._a[gb]>11?gb:a._a[hb]<1||a._a[hb]>r(a._a[fb],a._a[gb])?hb:a._a[ib]<0||a._a[ib]>23?ib:a._a[jb]<0||a._a[jb]>59?jb:a._a[kb]<0||a._a[kb]>59?kb:a._a[lb]<0||a._a[lb]>999?lb:-1,a._pf._overflowDayOfYear&&(fb>b||b>hb)&&(b=hb),a._pf.overflow=b)}function v(a){a._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function w(a){return null==a._isValid&&(a._isValid=!isNaN(a._d.getTime())&&a._pf.overflow<0&&!a._pf.empty&&!a._pf.invalidMonth&&!a._pf.nullInput&&!a._pf.invalidFormat&&!a._pf.userInvalidated,a._strict&&(a._isValid=a._isValid&&0===a._pf.charsLeftOver&&0===a._pf.unusedTokens.length)),a._isValid}function x(a){return a?a.toLowerCase().replace("_","-"):a}function y(a,b){return b.abbr=a,mb[a]||(mb[a]=new d),mb[a].set(b),mb[a]}function z(a){delete mb[a]}function A(a){var b,c,d,e,f=0,g=function(a){if(!mb[a]&&nb)try{require("./lang/"+a)}catch(b){}return mb[a]};if(!a)return bb.fn._lang;if(!k(a)){if(c=g(a))return c;a=[a]}for(;f0;){if(c=g(e.slice(0,b).join("-")))return c;if(d&&d.length>=b&&m(e,d,!0)>=b-1)break;b--}f++}return bb.fn._lang}function B(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function C(a){var b,c,d=a.match(rb);for(b=0,c=d.length;c>b;b++)d[b]=Pb[d[b]]?Pb[d[b]]:B(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function D(a,b){return a.isValid()?(b=E(b,a.lang()),Mb[b]||(Mb[b]=C(b)),Mb[b](a)):a.lang().invalidDate()}function E(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(sb.lastIndex=0;d>=0&&sb.test(a);)a=a.replace(sb,c),sb.lastIndex=0,d-=1;return a}function F(a,b){var c;switch(a){case"DDDD":return vb;case"YYYY":case"GGGG":case"gggg":return wb;case"YYYYY":case"GGGGG":case"ggggg":return xb;case"S":case"SS":case"SSS":case"DDD":return ub;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return zb;case"a":case"A":return A(b._l)._meridiemParse;case"X":return Cb;case"Z":case"ZZ":return Ab;case"T":return Bb;case"SSSS":return yb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"ww":case"W":case"WW":case"e":case"E":return tb;default:return c=new RegExp(N(M(a.replace("\\","")),"i"))}}function G(a){var b=(Ab.exec(a)||[])[0],c=(b+"").match(Hb)||["-",0,0],d=+(60*c[1])+q(c[2]);return"+"===c[0]?-d:d}function H(a,b,c){var d,e=c._a;switch(a){case"M":case"MM":null!=b&&(e[gb]=q(b)-1);break;case"MMM":case"MMMM":d=A(c._l).monthsParse(b),null!=d?e[gb]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[hb]=q(b));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=q(b));break;case"YY":e[fb]=q(b)+(q(b)>68?1900:2e3);break;case"YYYY":case"YYYYY":e[fb]=q(b);break;case"a":case"A":c._isPm=A(c._l).isPM(b);break;case"H":case"HH":case"h":case"hh":e[ib]=q(b);break;case"m":case"mm":e[jb]=q(b);break;case"s":case"ss":e[kb]=q(b);break;case"S":case"SS":case"SSS":case"SSSS":e[lb]=q(1e3*("0."+b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=G(b);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":a=a.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=b)}}function I(a){var b,c,d,e,f,g,h,i,j,k,l=[];if(!a._d){for(d=K(a),a._w&&null==a._a[hb]&&null==a._a[gb]&&(f=function(b){return b?b.length<3?parseInt(b,10)>68?"19"+b:"20"+b:b:null==a._a[fb]?bb().weekYear():a._a[fb]},g=a._w,null!=g.GG||null!=g.W||null!=g.E?h=X(f(g.GG),g.W||1,g.E,4,1):(i=A(a._l),j=null!=g.d?T(g.d,i):null!=g.e?parseInt(g.e,10)+i._week.dow:0,k=parseInt(g.w,10)||1,null!=g.d&&js(e)&&(a._pf._overflowDayOfYear=!0),c=S(e,0,a._dayOfYear),a._a[gb]=c.getUTCMonth(),a._a[hb]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=l[b]=d[b];for(;7>b;b++)a._a[b]=l[b]=null==a._a[b]?2===b?1:0:a._a[b];l[ib]+=q((a._tzm||0)/60),l[jb]+=q((a._tzm||0)%60),a._d=(a._useUTC?S:R).apply(null,l)}}function J(a){var b;a._d||(b=o(a._i),a._a=[b.year,b.month,b.day,b.hour,b.minute,b.second,b.millisecond],I(a))}function K(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function L(a){a._a=[],a._pf.empty=!0;var b,c,d,e,f,g=A(a._l),h=""+a._i,i=h.length,j=0;for(d=E(a._f,g).match(rb)||[],b=0;b0&&a._pf.unusedInput.push(f),h=h.slice(h.indexOf(c)+c.length),j+=c.length),Pb[e]?(c?a._pf.empty=!1:a._pf.unusedTokens.push(e),H(e,c,a)):a._strict&&!c&&a._pf.unusedTokens.push(e);a._pf.charsLeftOver=i-j,h.length>0&&a._pf.unusedInput.push(h),a._isPm&&a._a[ib]<12&&(a._a[ib]+=12),a._isPm===!1&&12===a._a[ib]&&(a._a[ib]=0),I(a),u(a)}function M(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function N(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function O(a){var b,c,d,e,f;if(0===a._f.length)return a._pf.invalidFormat=!0,a._d=new Date(0/0),void 0;for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function P(a){var b,c=a._i,d=Db.exec(c);if(d){for(a._pf.iso=!0,b=4;b>0;b--)if(d[b]){a._f=Fb[b-1]+(d[6]||" ");break}for(b=0;4>b;b++)if(Gb[b][1].exec(c)){a._f+=Gb[b][0];break}Ab.exec(c)&&(a._f+="Z"),L(a)}else a._d=new Date(c)}function Q(b){var c=b._i,d=ob.exec(c);c===a?b._d=new Date:d?b._d=new Date(+d[1]):"string"==typeof c?P(b):k(c)?(b._a=c.slice(0),I(b)):l(c)?b._d=new Date(+c):"object"==typeof c?J(b):b._d=new Date(c)}function R(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function S(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function T(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function U(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function V(a,b,c){var d=eb(Math.abs(a)/1e3),e=eb(d/60),f=eb(e/60),g=eb(f/24),h=eb(g/365),i=45>d&&["s",d]||1===e&&["m"]||45>e&&["mm",e]||1===f&&["h"]||22>f&&["hh",f]||1===g&&["d"]||25>=g&&["dd",g]||45>=g&&["M"]||345>g&&["MM",eb(g/30)]||1===h&&["y"]||["yy",h];return i[2]=b,i[3]=a>0,i[4]=c,U.apply({},i)}function W(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=bb(a).add("d",f),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function X(a,b,c,d,e){var f,g,h=new Date(Date.UTC(a,0)).getUTCDay();return c=null!=c?c:e,f=e-h+(h>d?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:s(a-1)+g}}function Y(a){var b=a._i,c=a._f;return"undefined"==typeof a._pf&&v(a),null===b?bb.invalid({nullInput:!0}):("string"==typeof b&&(a._i=b=A().preparse(b)),bb.isMoment(b)?(a=g({},b),a._d=new Date(+b._d)):c?k(c)?O(a):L(a):Q(a),new e(a))}function Z(a,b){bb.fn[a]=bb.fn[a+"s"]=function(a){var c=this._isUTC?"UTC":"";return null!=a?(this._d["set"+c+b](a),bb.updateOffset(this),this):this._d["get"+c+b]()}}function $(a){bb.duration.fn[a]=function(){return this._data[a]}}function _(a,b){bb.duration.fn["as"+a]=function(){return+this/b}}function ab(a){var b=!1,c=bb;"undefined"==typeof ender&&(this.moment=a?function(){return!b&&console&&console.warn&&(b=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),c.apply(null,arguments)}:bb)}for(var bb,cb,db="2.4.0",eb=Math.round,fb=0,gb=1,hb=2,ib=3,jb=4,kb=5,lb=6,mb={},nb="undefined"!=typeof module&&module.exports,ob=/^\/?Date\((\-?\d+)/i,pb=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,qb=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,rb=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,sb=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,tb=/\d\d?/,ub=/\d{1,3}/,vb=/\d{3}/,wb=/\d{1,4}/,xb=/[+\-]?\d{1,6}/,yb=/\d+/,zb=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Ab=/Z|[\+\-]\d\d:?\d\d/i,Bb=/T/i,Cb=/[\+\-]?\d+(\.\d{1,3})?/,Db=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d:?\d\d|Z)?)?$/,Eb="YYYY-MM-DDTHH:mm:ssZ",Fb=["YYYY-MM-DD","GGGG-[W]WW","GGGG-[W]WW-E","YYYY-DDD"],Gb=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],Hb=/([\+\-]|\d\d)/gi,Ib="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),Jb={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},Kb={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},Lb={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Mb={},Nb="DDD w W M D d".split(" "),Ob="M D H h m s w W".split(" "),Pb={M:function(){return this.month()+1},MMM:function(a){return this.lang().monthsShort(this,a)},MMMM:function(a){return this.lang().months(this,a)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(a){return this.lang().weekdaysMin(this,a)},ddd:function(a){return this.lang().weekdaysShort(this,a)},dddd:function(a){return this.lang().weekdays(this,a)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return i(this.year()%100,2)},YYYY:function(){return i(this.year(),4)},YYYYY:function(){return i(this.year(),5)},gg:function(){return i(this.weekYear()%100,2)},gggg:function(){return this.weekYear()},ggggg:function(){return i(this.weekYear(),5)},GG:function(){return i(this.isoWeekYear()%100,2)},GGGG:function(){return this.isoWeekYear()},GGGGG:function(){return i(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return q(this.milliseconds()/100)},SS:function(){return i(q(this.milliseconds()/10),2)},SSS:function(){return i(this.milliseconds(),3)},SSSS:function(){return i(this.milliseconds(),3)},Z:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(a/60),2)+":"+i(q(a)%60,2)},ZZ:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(10*a/6),4)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()}},Qb=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];Nb.length;)cb=Nb.pop(),Pb[cb+"o"]=c(Pb[cb],cb);for(;Ob.length;)cb=Ob.pop(),Pb[cb+cb]=b(Pb[cb],2);for(Pb.DDDD=b(Pb.DDD,3),g(d.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a){var b,c,d;for(this._monthsParse||(this._monthsParse=[]),b=0;12>b;b++)if(this._monthsParse[b]||(c=bb.utc([2e3,b]),d="^"+this.months(c,"")+"|^"+this.monthsShort(c,""),this._monthsParse[b]=new RegExp(d.replace(".",""),"i")),this._monthsParse[b].test(a))return b},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=bb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b){var c=this._calendar[a];return"function"==typeof c?c.apply(b):c},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",preparse:function(a){return a},postformat:function(a){return a},week:function(a){return W(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),bb=function(b,c,d,e){return"boolean"==typeof d&&(e=d,d=a),Y({_i:b,_f:c,_l:d,_strict:e,_isUTC:!1})},bb.utc=function(b,c,d,e){var f;return"boolean"==typeof d&&(e=d,d=a),f=Y({_useUTC:!0,_isUTC:!0,_l:d,_i:b,_f:c,_strict:e}).utc()},bb.unix=function(a){return bb(1e3*a)},bb.duration=function(a,b){var c,d,e,g=bb.isDuration(a),h="number"==typeof a,i=g?a._input:h?{}:a,j=null;return h?b?i[b]=a:i.milliseconds=a:(j=pb.exec(a))?(c="-"===j[1]?-1:1,i={y:0,d:q(j[hb])*c,h:q(j[ib])*c,m:q(j[jb])*c,s:q(j[kb])*c,ms:q(j[lb])*c}):(j=qb.exec(a))&&(c="-"===j[1]?-1:1,e=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*c},i={y:e(j[2]),M:e(j[3]),d:e(j[4]),h:e(j[5]),m:e(j[6]),s:e(j[7]),w:e(j[8])}),d=new f(i),g&&a.hasOwnProperty("_lang")&&(d._lang=a._lang),d},bb.version=db,bb.defaultFormat=Eb,bb.updateOffset=function(){},bb.lang=function(a,b){var c;return a?(b?y(x(a),b):null===b?(z(a),a="en"):mb[a]||A(a),c=bb.duration.fn._lang=bb.fn._lang=A(a),c._abbr):bb.fn._lang._abbr},bb.langData=function(a){return a&&a._lang&&a._lang._abbr&&(a=a._lang._abbr),A(a)},bb.isMoment=function(a){return a instanceof e},bb.isDuration=function(a){return a instanceof f},cb=Qb.length-1;cb>=0;--cb)p(Qb[cb]);for(bb.normalizeUnits=function(a){return n(a)},bb.invalid=function(a){var b=bb.utc(0/0);return null!=a?g(b._pf,a):b._pf.userInvalidated=!0,b},bb.parseZone=function(a){return bb(a).parseZone()},g(bb.fn=e.prototype,{clone:function(){return bb(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){return D(bb(this).utc(),"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var a=this;return[a.year(),a.month(),a.date(),a.hours(),a.minutes(),a.seconds(),a.milliseconds()]},isValid:function(){return w(this)},isDSTShifted:function(){return this._a?this.isValid()&&m(this._a,(this._isUTC?bb.utc(this._a):bb(this._a)).toArray())>0:!1},parsingFlags:function(){return g({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(a){var b=D(this,a||bb.defaultFormat);return this.lang().postformat(b)},add:function(a,b){var c;return c="string"==typeof a?bb.duration(+b,a):bb.duration(a,b),j(this,c,1),this},subtract:function(a,b){var c;return c="string"==typeof a?bb.duration(+b,a):bb.duration(a,b),j(this,c,-1),this},diff:function(a,b,c){var d,e,f=this._isUTC?bb(a).zone(this._offset||0):bb(a).local(),g=6e4*(this.zone()-f.zone());return b=n(b),"year"===b||"month"===b?(d=432e5*(this.daysInMonth()+f.daysInMonth()),e=12*(this.year()-f.year())+(this.month()-f.month()),e+=(this-bb(this).startOf("month")-(f-bb(f).startOf("month")))/d,e-=6e4*(this.zone()-bb(this).startOf("month").zone()-(f.zone()-bb(f).startOf("month").zone()))/d,"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:h(e)},from:function(a,b){return bb.duration(this.diff(a)).lang(this.lang()._abbr).humanize(!b)},fromNow:function(a){return this.from(bb(),a)},calendar:function(){var a=this.diff(bb().zone(this.zone()).startOf("day"),"days",!0),b=-6>a?"sameElse":-1>a?"lastWeek":0>a?"lastDay":1>a?"sameDay":2>a?"nextDay":7>a?"nextWeek":"sameElse";return this.format(this.lang().calendar(b,this))},isLeapYear:function(){return t(this.year())},isDST:function(){return this.zone()+bb(a).startOf(b)},isBefore:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)<+bb(a).startOf(b)},isSame:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)===+bb(a).startOf(b)},min:function(a){return a=bb.apply(null,arguments),this>a?this:a},max:function(a){return a=bb.apply(null,arguments),a>this?this:a},zone:function(a){var b=this._offset||0;return null==a?this._isUTC?b:this._d.getTimezoneOffset():("string"==typeof a&&(a=G(a)),Math.abs(a)<16&&(a=60*a),this._offset=a,this._isUTC=!0,b!==a&&j(this,bb.duration(b-a,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(a){return a=a?bb(a).zone():0,0===(this.zone()-a)%60},daysInMonth:function(){return r(this.year(),this.month())},dayOfYear:function(a){var b=eb((bb(this).startOf("day")-bb(this).startOf("year"))/864e5)+1;return null==a?b:this.add("d",a-b)},weekYear:function(a){var b=W(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==a?b:this.add("y",a-b)},isoWeekYear:function(a){var b=W(this,1,4).year;return null==a?b:this.add("y",a-b)},week:function(a){var b=this.lang().week(this);return null==a?b:this.add("d",7*(a-b))},isoWeek:function(a){var b=W(this,1,4).week;return null==a?b:this.add("d",7*(a-b))},weekday:function(a){var b=(this.day()+7-this.lang()._week.dow)%7;return null==a?b:this.add("d",a-b)},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},get:function(a){return a=n(a),this[a]()},set:function(a,b){return a=n(a),"function"==typeof this[a]&&this[a](b),this},lang:function(b){return b===a?this._lang:(this._lang=A(b),this)}}),cb=0;cb ["10", "00"] or "-1530" > ["-15", "30"]
+ parseTimezoneChunker = /([\+\-]|\d\d)/gi,
+
+ // getter and setter names
+ proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
+ unitMillisecondFactors = {
+ 'Milliseconds' : 1,
+ 'Seconds' : 1e3,
+ 'Minutes' : 6e4,
+ 'Hours' : 36e5,
+ 'Days' : 864e5,
+ 'Months' : 2592e6,
+ 'Years' : 31536e6
+ },
+
+ unitAliases = {
+ ms : 'millisecond',
+ s : 'second',
+ m : 'minute',
+ h : 'hour',
+ d : 'day',
+ D : 'date',
+ w : 'week',
+ W : 'isoWeek',
+ M : 'month',
+ Q : 'quarter',
+ y : 'year',
+ DDD : 'dayOfYear',
+ e : 'weekday',
+ E : 'isoWeekday',
+ gg: 'weekYear',
+ GG: 'isoWeekYear'
+ },
+
+ camelFunctions = {
+ dayofyear : 'dayOfYear',
+ isoweekday : 'isoWeekday',
+ isoweek : 'isoWeek',
+ weekyear : 'weekYear',
+ isoweekyear : 'isoWeekYear'
+ },
+
+ // format function strings
+ formatFunctions = {},
+
+ // default relative time thresholds
+ relativeTimeThresholds = {
+ s: 45, //seconds to minutes
+ m: 45, //minutes to hours
+ h: 22, //hours to days
+ dd: 25, //days to month (month == 1)
+ dm: 45, //days to months (months > 1)
+ dy: 345 //days to year
+ },
+
+ // tokens to ordinalize and pad
+ ordinalizeTokens = 'DDD w W M D d'.split(' '),
+ paddedTokens = 'M D H h m s w W'.split(' '),
+
+ formatTokenFunctions = {
+ M : function () {
+ return this.month() + 1;
+ },
+ MMM : function (format) {
+ return this.lang().monthsShort(this, format);
+ },
+ MMMM : function (format) {
+ return this.lang().months(this, format);
+ },
+ D : function () {
+ return this.date();
+ },
+ DDD : function () {
+ return this.dayOfYear();
+ },
+ d : function () {
+ return this.day();
+ },
+ dd : function (format) {
+ return this.lang().weekdaysMin(this, format);
+ },
+ ddd : function (format) {
+ return this.lang().weekdaysShort(this, format);
+ },
+ dddd : function (format) {
+ return this.lang().weekdays(this, format);
+ },
+ w : function () {
+ return this.week();
+ },
+ W : function () {
+ return this.isoWeek();
+ },
+ YY : function () {
+ return leftZeroFill(this.year() % 100, 2);
+ },
+ YYYY : function () {
+ return leftZeroFill(this.year(), 4);
+ },
+ YYYYY : function () {
+ return leftZeroFill(this.year(), 5);
+ },
+ YYYYYY : function () {
+ var y = this.year(), sign = y >= 0 ? '+' : '-';
+ return sign + leftZeroFill(Math.abs(y), 6);
+ },
+ gg : function () {
+ return leftZeroFill(this.weekYear() % 100, 2);
+ },
+ gggg : function () {
+ return leftZeroFill(this.weekYear(), 4);
+ },
+ ggggg : function () {
+ return leftZeroFill(this.weekYear(), 5);
+ },
+ GG : function () {
+ return leftZeroFill(this.isoWeekYear() % 100, 2);
+ },
+ GGGG : function () {
+ return leftZeroFill(this.isoWeekYear(), 4);
+ },
+ GGGGG : function () {
+ return leftZeroFill(this.isoWeekYear(), 5);
+ },
+ e : function () {
+ return this.weekday();
+ },
+ E : function () {
+ return this.isoWeekday();
+ },
+ a : function () {
+ return this.lang().meridiem(this.hours(), this.minutes(), true);
+ },
+ A : function () {
+ return this.lang().meridiem(this.hours(), this.minutes(), false);
+ },
+ H : function () {
+ return this.hours();
+ },
+ h : function () {
+ return this.hours() % 12 || 12;
+ },
+ m : function () {
+ return this.minutes();
+ },
+ s : function () {
+ return this.seconds();
+ },
+ S : function () {
+ return toInt(this.milliseconds() / 100);
+ },
+ SS : function () {
+ return leftZeroFill(toInt(this.milliseconds() / 10), 2);
+ },
+ SSS : function () {
+ return leftZeroFill(this.milliseconds(), 3);
+ },
+ SSSS : function () {
+ return leftZeroFill(this.milliseconds(), 3);
+ },
+ Z : function () {
+ var a = -this.zone(),
+ b = "+";
+ if (a < 0) {
+ a = -a;
+ b = "-";
+ }
+ return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2);
+ },
+ ZZ : function () {
+ var a = -this.zone(),
+ b = "+";
+ if (a < 0) {
+ a = -a;
+ b = "-";
+ }
+ return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2);
+ },
+ z : function () {
+ return this.zoneAbbr();
+ },
+ zz : function () {
+ return this.zoneName();
+ },
+ X : function () {
+ return this.unix();
+ },
+ Q : function () {
+ return this.quarter();
+ }
+ },
+
+ lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'];
+
+ // Pick the first defined of two or three arguments. dfl comes from
+ // default.
+ function dfl(a, b, c) {
+ switch (arguments.length) {
+ case 2: return a != null ? a : b;
+ case 3: return a != null ? a : b != null ? b : c;
+ default: throw new Error("Implement me");
+ }
+ }
+
+ function defaultParsingFlags() {
+ // We need to deep clone this object, and es5 standard is not very
+ // helpful.
+ return {
+ empty : false,
+ unusedTokens : [],
+ unusedInput : [],
+ overflow : -2,
+ charsLeftOver : 0,
+ nullInput : false,
+ invalidMonth : null,
+ invalidFormat : false,
+ userInvalidated : false,
+ iso: false
+ };
+ }
+
+ function deprecate(msg, fn) {
+ var firstTime = true;
+ function printMsg() {
+ if (moment.suppressDeprecationWarnings === false &&
+ typeof console !== 'undefined' && console.warn) {
+ console.warn("Deprecation warning: " + msg);
+ }
+ }
+ return extend(function () {
+ if (firstTime) {
+ printMsg();
+ firstTime = false;
+ }
+ return fn.apply(this, arguments);
+ }, fn);
+ }
+
+ function padToken(func, count) {
+ return function (a) {
+ return leftZeroFill(func.call(this, a), count);
+ };
+ }
+ function ordinalizeToken(func, period) {
+ return function (a) {
+ return this.lang().ordinal(func.call(this, a), period);
+ };
+ }
+
+ while (ordinalizeTokens.length) {
+ i = ordinalizeTokens.pop();
+ formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i);
+ }
+ while (paddedTokens.length) {
+ i = paddedTokens.pop();
+ formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
+ }
+ formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
+
+
+ /************************************
+ Constructors
+ ************************************/
+
+ function Language() {
+
+ }
+
+ // Moment prototype object
+ function Moment(config) {
+ checkOverflow(config);
+ extend(this, config);
+ }
+
+ // Duration Constructor
+ function Duration(duration) {
+ var normalizedInput = normalizeObjectUnits(duration),
+ years = normalizedInput.year || 0,
+ quarters = normalizedInput.quarter || 0,
+ months = normalizedInput.month || 0,
+ weeks = normalizedInput.week || 0,
+ days = normalizedInput.day || 0,
+ hours = normalizedInput.hour || 0,
+ minutes = normalizedInput.minute || 0,
+ seconds = normalizedInput.second || 0,
+ milliseconds = normalizedInput.millisecond || 0;
+
+ // representation for dateAddRemove
+ this._milliseconds = +milliseconds +
+ seconds * 1e3 + // 1000
+ minutes * 6e4 + // 1000 * 60
+ hours * 36e5; // 1000 * 60 * 60
+ // Because of dateAddRemove treats 24 hours as different from a
+ // day when working around DST, we need to store them separately
+ this._days = +days +
+ weeks * 7;
+ // It is impossible translate months into days without knowing
+ // which months you are are talking about, so we have to store
+ // it separately.
+ this._months = +months +
+ quarters * 3 +
+ years * 12;
+
+ this._data = {};
+
+ this._bubble();
+ }
+
+ /************************************
+ Helpers
+ ************************************/
+
+
+ function extend(a, b) {
+ for (var i in b) {
+ if (b.hasOwnProperty(i)) {
+ a[i] = b[i];
+ }
+ }
+
+ if (b.hasOwnProperty("toString")) {
+ a.toString = b.toString;
+ }
+
+ if (b.hasOwnProperty("valueOf")) {
+ a.valueOf = b.valueOf;
+ }
+
+ return a;
+ }
+
+ function cloneMoment(m) {
+ var result = {}, i;
+ for (i in m) {
+ if (m.hasOwnProperty(i) && momentProperties.hasOwnProperty(i)) {
+ result[i] = m[i];
+ }
+ }
+
+ return result;
+ }
+
+ function absRound(number) {
+ if (number < 0) {
+ return Math.ceil(number);
+ } else {
+ return Math.floor(number);
+ }
+ }
+
+ // left zero fill a number
+ // see http://jsperf.com/left-zero-filling for performance comparison
+ function leftZeroFill(number, targetLength, forceSign) {
+ var output = '' + Math.abs(number),
+ sign = number >= 0;
+
+ while (output.length < targetLength) {
+ output = '0' + output;
+ }
+ return (sign ? (forceSign ? '+' : '') : '-') + output;
+ }
+
+ // helper function for _.addTime and _.subtractTime
+ function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) {
+ var milliseconds = duration._milliseconds,
+ days = duration._days,
+ months = duration._months;
+ updateOffset = updateOffset == null ? true : updateOffset;
+
+ if (milliseconds) {
+ mom._d.setTime(+mom._d + milliseconds * isAdding);
+ }
+ if (days) {
+ rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding);
+ }
+ if (months) {
+ rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding);
+ }
+ if (updateOffset) {
+ moment.updateOffset(mom, days || months);
+ }
+ }
+
+ // check if is an array
+ function isArray(input) {
+ return Object.prototype.toString.call(input) === '[object Array]';
+ }
+
+ function isDate(input) {
+ return Object.prototype.toString.call(input) === '[object Date]' ||
+ input instanceof Date;
+ }
+
+ // compare two arrays, return the number of differences
+ function compareArrays(array1, array2, dontConvert) {
+ var len = Math.min(array1.length, array2.length),
+ lengthDiff = Math.abs(array1.length - array2.length),
+ diffs = 0,
+ i;
+ for (i = 0; i < len; i++) {
+ if ((dontConvert && array1[i] !== array2[i]) ||
+ (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
+ diffs++;
+ }
+ }
+ return diffs + lengthDiff;
+ }
+
+ function normalizeUnits(units) {
+ if (units) {
+ var lowered = units.toLowerCase().replace(/(.)s$/, '$1');
+ units = unitAliases[units] || camelFunctions[lowered] || lowered;
+ }
+ return units;
+ }
+
+ function normalizeObjectUnits(inputObject) {
+ var normalizedInput = {},
+ normalizedProp,
+ prop;
+
+ for (prop in inputObject) {
+ if (inputObject.hasOwnProperty(prop)) {
+ normalizedProp = normalizeUnits(prop);
+ if (normalizedProp) {
+ normalizedInput[normalizedProp] = inputObject[prop];
+ }
+ }
+ }
+
+ return normalizedInput;
+ }
+
+ function makeList(field) {
+ var count, setter;
+
+ if (field.indexOf('week') === 0) {
+ count = 7;
+ setter = 'day';
+ }
+ else if (field.indexOf('month') === 0) {
+ count = 12;
+ setter = 'month';
+ }
+ else {
+ return;
+ }
+
+ moment[field] = function (format, index) {
+ var i, getter,
+ method = moment.fn._lang[field],
+ results = [];
+
+ if (typeof format === 'number') {
+ index = format;
+ format = undefined;
+ }
+
+ getter = function (i) {
+ var m = moment().utc().set(setter, i);
+ return method.call(moment.fn._lang, m, format || '');
+ };
+
+ if (index != null) {
+ return getter(index);
+ }
+ else {
+ for (i = 0; i < count; i++) {
+ results.push(getter(i));
+ }
+ return results;
+ }
+ };
+ }
+
+ function toInt(argumentForCoercion) {
+ var coercedNumber = +argumentForCoercion,
+ value = 0;
+
+ if (coercedNumber !== 0 && isFinite(coercedNumber)) {
+ if (coercedNumber >= 0) {
+ value = Math.floor(coercedNumber);
+ } else {
+ value = Math.ceil(coercedNumber);
+ }
+ }
+
+ return value;
+ }
+
+ function daysInMonth(year, month) {
+ return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
+ }
+
+ function weeksInYear(year, dow, doy) {
+ return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week;
+ }
+
+ function daysInYear(year) {
+ return isLeapYear(year) ? 366 : 365;
+ }
+
+ function isLeapYear(year) {
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+ }
+
+ function checkOverflow(m) {
+ var overflow;
+ if (m._a && m._pf.overflow === -2) {
+ overflow =
+ m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
+ m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE :
+ m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR :
+ m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
+ m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
+ m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
+ -1;
+
+ if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
+ overflow = DATE;
+ }
+
+ m._pf.overflow = overflow;
+ }
+ }
+
+ function isValid(m) {
+ if (m._isValid == null) {
+ m._isValid = !isNaN(m._d.getTime()) &&
+ m._pf.overflow < 0 &&
+ !m._pf.empty &&
+ !m._pf.invalidMonth &&
+ !m._pf.nullInput &&
+ !m._pf.invalidFormat &&
+ !m._pf.userInvalidated;
+
+ if (m._strict) {
+ m._isValid = m._isValid &&
+ m._pf.charsLeftOver === 0 &&
+ m._pf.unusedTokens.length === 0;
+ }
+ }
+ return m._isValid;
+ }
+
+ function normalizeLanguage(key) {
+ return key ? key.toLowerCase().replace('_', '-') : key;
+ }
+
+ // Return a moment from input, that is local/utc/zone equivalent to model.
+ function makeAs(input, model) {
+ return model._isUTC ? moment(input).zone(model._offset || 0) :
+ moment(input).local();
+ }
+
+ /************************************
+ Languages
+ ************************************/
+
+
+ extend(Language.prototype, {
+
+ set : function (config) {
+ var prop, i;
+ for (i in config) {
+ prop = config[i];
+ if (typeof prop === 'function') {
+ this[i] = prop;
+ } else {
+ this['_' + i] = prop;
+ }
+ }
+ },
+
+ _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
+ months : function (m) {
+ return this._months[m.month()];
+ },
+
+ _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
+ monthsShort : function (m) {
+ return this._monthsShort[m.month()];
+ },
+
+ monthsParse : function (monthName) {
+ var i, mom, regex;
+
+ if (!this._monthsParse) {
+ this._monthsParse = [];
+ }
+
+ for (i = 0; i < 12; i++) {
+ // make the regex if we don't have it already
+ if (!this._monthsParse[i]) {
+ mom = moment.utc([2000, i]);
+ regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
+ this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
+ }
+ // test the regex
+ if (this._monthsParse[i].test(monthName)) {
+ return i;
+ }
+ }
+ },
+
+ _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
+ weekdays : function (m) {
+ return this._weekdays[m.day()];
+ },
+
+ _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
+ weekdaysShort : function (m) {
+ return this._weekdaysShort[m.day()];
+ },
+
+ _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"),
+ weekdaysMin : function (m) {
+ return this._weekdaysMin[m.day()];
+ },
+
+ weekdaysParse : function (weekdayName) {
+ var i, mom, regex;
+
+ if (!this._weekdaysParse) {
+ this._weekdaysParse = [];
+ }
+
+ for (i = 0; i < 7; i++) {
+ // make the regex if we don't have it already
+ if (!this._weekdaysParse[i]) {
+ mom = moment([2000, 1]).day(i);
+ regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
+ this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
+ }
+ // test the regex
+ if (this._weekdaysParse[i].test(weekdayName)) {
+ return i;
+ }
+ }
+ },
+
+ _longDateFormat : {
+ LT : "h:mm A",
+ L : "MM/DD/YYYY",
+ LL : "MMMM D YYYY",
+ LLL : "MMMM D YYYY LT",
+ LLLL : "dddd, MMMM D YYYY LT"
+ },
+ longDateFormat : function (key) {
+ var output = this._longDateFormat[key];
+ if (!output && this._longDateFormat[key.toUpperCase()]) {
+ output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
+ return val.slice(1);
+ });
+ this._longDateFormat[key] = output;
+ }
+ return output;
+ },
+
+ isPM : function (input) {
+ // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
+ // Using charAt should be more compatible.
+ return ((input + '').toLowerCase().charAt(0) === 'p');
+ },
+
+ _meridiemParse : /[ap]\.?m?\.?/i,
+ meridiem : function (hours, minutes, isLower) {
+ if (hours > 11) {
+ return isLower ? 'pm' : 'PM';
+ } else {
+ return isLower ? 'am' : 'AM';
+ }
+ },
+
+ _calendar : {
+ sameDay : '[Today at] LT',
+ nextDay : '[Tomorrow at] LT',
+ nextWeek : 'dddd [at] LT',
+ lastDay : '[Yesterday at] LT',
+ lastWeek : '[Last] dddd [at] LT',
+ sameElse : 'L'
+ },
+ calendar : function (key, mom) {
+ var output = this._calendar[key];
+ return typeof output === 'function' ? output.apply(mom) : output;
+ },
+
+ _relativeTime : {
+ future : "in %s",
+ past : "%s ago",
+ s : "a few seconds",
+ m : "a minute",
+ mm : "%d minutes",
+ h : "an hour",
+ hh : "%d hours",
+ d : "a day",
+ dd : "%d days",
+ M : "a month",
+ MM : "%d months",
+ y : "a year",
+ yy : "%d years"
+ },
+ relativeTime : function (number, withoutSuffix, string, isFuture) {
+ var output = this._relativeTime[string];
+ return (typeof output === 'function') ?
+ output(number, withoutSuffix, string, isFuture) :
+ output.replace(/%d/i, number);
+ },
+ pastFuture : function (diff, output) {
+ var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
+ return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
+ },
+
+ ordinal : function (number) {
+ return this._ordinal.replace("%d", number);
+ },
+ _ordinal : "%d",
+
+ preparse : function (string) {
+ return string;
+ },
+
+ postformat : function (string) {
+ return string;
+ },
+
+ week : function (mom) {
+ return weekOfYear(mom, this._week.dow, this._week.doy).week;
+ },
+
+ _week : {
+ dow : 0, // Sunday is the first day of the week.
+ doy : 6 // The week that contains Jan 1st is the first week of the year.
+ },
+
+ _invalidDate: 'Invalid date',
+ invalidDate: function () {
+ return this._invalidDate;
+ }
+ });
+
+ // Loads a language definition into the `languages` cache. The function
+ // takes a key and optionally values. If not in the browser and no values
+ // are provided, it will load the language file module. As a convenience,
+ // this function also returns the language values.
+ function loadLang(key, values) {
+ values.abbr = key;
+ if (!languages[key]) {
+ languages[key] = new Language();
+ }
+ languages[key].set(values);
+ return languages[key];
+ }
+
+ // Remove a language from the `languages` cache. Mostly useful in tests.
+ function unloadLang(key) {
+ delete languages[key];
+ }
+
+ // Determines which language definition to use and returns it.
+ //
+ // With no parameters, it will return the global language. If you
+ // pass in a language key, such as 'en', it will return the
+ // definition for 'en', so long as 'en' has already been loaded using
+ // moment.lang.
+ function getLangDefinition(key) {
+ var i = 0, j, lang, next, split,
+ get = function (k) {
+ if (!languages[k] && hasModule) {
+ try {
+ require('./lang/' + k);
+ } catch (e) { }
+ }
+ return languages[k];
+ };
+
+ if (!key) {
+ return moment.fn._lang;
+ }
+
+ if (!isArray(key)) {
+ //short-circuit everything else
+ lang = get(key);
+ if (lang) {
+ return lang;
+ }
+ key = [key];
+ }
+
+ //pick the language from the array
+ //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
+ //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
+ while (i < key.length) {
+ split = normalizeLanguage(key[i]).split('-');
+ j = split.length;
+ next = normalizeLanguage(key[i + 1]);
+ next = next ? next.split('-') : null;
+ while (j > 0) {
+ lang = get(split.slice(0, j).join('-'));
+ if (lang) {
+ return lang;
+ }
+ if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
+ //the next array item is better than a shallower substring of this one
+ break;
+ }
+ j--;
+ }
+ i++;
+ }
+ return moment.fn._lang;
+ }
+
+ /************************************
+ Formatting
+ ************************************/
+
+
+ function removeFormattingTokens(input) {
+ if (input.match(/\[[\s\S]/)) {
+ return input.replace(/^\[|\]$/g, "");
+ }
+ return input.replace(/\\/g, "");
+ }
+
+ function makeFormatFunction(format) {
+ var array = format.match(formattingTokens), i, length;
+
+ for (i = 0, length = array.length; i < length; i++) {
+ if (formatTokenFunctions[array[i]]) {
+ array[i] = formatTokenFunctions[array[i]];
+ } else {
+ array[i] = removeFormattingTokens(array[i]);
+ }
+ }
+
+ return function (mom) {
+ var output = "";
+ for (i = 0; i < length; i++) {
+ output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
+ }
+ return output;
+ };
+ }
+
+ // format date using native date object
+ function formatMoment(m, format) {
+
+ if (!m.isValid()) {
+ return m.lang().invalidDate();
+ }
+
+ format = expandFormat(format, m.lang());
+
+ if (!formatFunctions[format]) {
+ formatFunctions[format] = makeFormatFunction(format);
+ }
+
+ return formatFunctions[format](m);
+ }
+
+ function expandFormat(format, lang) {
+ var i = 5;
+
+ function replaceLongDateFormatTokens(input) {
+ return lang.longDateFormat(input) || input;
+ }
+
+ localFormattingTokens.lastIndex = 0;
+ while (i >= 0 && localFormattingTokens.test(format)) {
+ format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
+ localFormattingTokens.lastIndex = 0;
+ i -= 1;
+ }
+
+ return format;
+ }
+
+
+ /************************************
+ Parsing
+ ************************************/
+
+
+ // get the regex to find the next token
+ function getParseRegexForToken(token, config) {
+ var a, strict = config._strict;
+ switch (token) {
+ case 'Q':
+ return parseTokenOneDigit;
+ case 'DDDD':
+ return parseTokenThreeDigits;
+ case 'YYYY':
+ case 'GGGG':
+ case 'gggg':
+ return strict ? parseTokenFourDigits : parseTokenOneToFourDigits;
+ case 'Y':
+ case 'G':
+ case 'g':
+ return parseTokenSignedNumber;
+ case 'YYYYYY':
+ case 'YYYYY':
+ case 'GGGGG':
+ case 'ggggg':
+ return strict ? parseTokenSixDigits : parseTokenOneToSixDigits;
+ case 'S':
+ if (strict) { return parseTokenOneDigit; }
+ /* falls through */
+ case 'SS':
+ if (strict) { return parseTokenTwoDigits; }
+ /* falls through */
+ case 'SSS':
+ if (strict) { return parseTokenThreeDigits; }
+ /* falls through */
+ case 'DDD':
+ return parseTokenOneToThreeDigits;
+ case 'MMM':
+ case 'MMMM':
+ case 'dd':
+ case 'ddd':
+ case 'dddd':
+ return parseTokenWord;
+ case 'a':
+ case 'A':
+ return getLangDefinition(config._l)._meridiemParse;
+ case 'X':
+ return parseTokenTimestampMs;
+ case 'Z':
+ case 'ZZ':
+ return parseTokenTimezone;
+ case 'T':
+ return parseTokenT;
+ case 'SSSS':
+ return parseTokenDigits;
+ case 'MM':
+ case 'DD':
+ case 'YY':
+ case 'GG':
+ case 'gg':
+ case 'HH':
+ case 'hh':
+ case 'mm':
+ case 'ss':
+ case 'ww':
+ case 'WW':
+ return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits;
+ case 'M':
+ case 'D':
+ case 'd':
+ case 'H':
+ case 'h':
+ case 'm':
+ case 's':
+ case 'w':
+ case 'W':
+ case 'e':
+ case 'E':
+ return parseTokenOneOrTwoDigits;
+ case 'Do':
+ return parseTokenOrdinal;
+ default :
+ a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i"));
+ return a;
+ }
+ }
+
+ function timezoneMinutesFromString(string) {
+ string = string || "";
+ var possibleTzMatches = (string.match(parseTokenTimezone) || []),
+ tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [],
+ parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
+ minutes = +(parts[1] * 60) + toInt(parts[2]);
+
+ return parts[0] === '+' ? -minutes : minutes;
+ }
+
+ // function to convert string input to date
+ function addTimeToArrayFromToken(token, input, config) {
+ var a, datePartArray = config._a;
+
+ switch (token) {
+ // QUARTER
+ case 'Q':
+ if (input != null) {
+ datePartArray[MONTH] = (toInt(input) - 1) * 3;
+ }
+ break;
+ // MONTH
+ case 'M' : // fall through to MM
+ case 'MM' :
+ if (input != null) {
+ datePartArray[MONTH] = toInt(input) - 1;
+ }
+ break;
+ case 'MMM' : // fall through to MMMM
+ case 'MMMM' :
+ a = getLangDefinition(config._l).monthsParse(input);
+ // if we didn't find a month name, mark the date as invalid.
+ if (a != null) {
+ datePartArray[MONTH] = a;
+ } else {
+ config._pf.invalidMonth = input;
+ }
+ break;
+ // DAY OF MONTH
+ case 'D' : // fall through to DD
+ case 'DD' :
+ if (input != null) {
+ datePartArray[DATE] = toInt(input);
+ }
+ break;
+ case 'Do' :
+ if (input != null) {
+ datePartArray[DATE] = toInt(parseInt(input, 10));
+ }
+ break;
+ // DAY OF YEAR
+ case 'DDD' : // fall through to DDDD
+ case 'DDDD' :
+ if (input != null) {
+ config._dayOfYear = toInt(input);
+ }
+
+ break;
+ // YEAR
+ case 'YY' :
+ datePartArray[YEAR] = moment.parseTwoDigitYear(input);
+ break;
+ case 'YYYY' :
+ case 'YYYYY' :
+ case 'YYYYYY' :
+ datePartArray[YEAR] = toInt(input);
+ break;
+ // AM / PM
+ case 'a' : // fall through to A
+ case 'A' :
+ config._isPm = getLangDefinition(config._l).isPM(input);
+ break;
+ // 24 HOUR
+ case 'H' : // fall through to hh
+ case 'HH' : // fall through to hh
+ case 'h' : // fall through to hh
+ case 'hh' :
+ datePartArray[HOUR] = toInt(input);
+ break;
+ // MINUTE
+ case 'm' : // fall through to mm
+ case 'mm' :
+ datePartArray[MINUTE] = toInt(input);
+ break;
+ // SECOND
+ case 's' : // fall through to ss
+ case 'ss' :
+ datePartArray[SECOND] = toInt(input);
+ break;
+ // MILLISECOND
+ case 'S' :
+ case 'SS' :
+ case 'SSS' :
+ case 'SSSS' :
+ datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000);
+ break;
+ // UNIX TIMESTAMP WITH MS
+ case 'X':
+ config._d = new Date(parseFloat(input) * 1000);
+ break;
+ // TIMEZONE
+ case 'Z' : // fall through to ZZ
+ case 'ZZ' :
+ config._useUTC = true;
+ config._tzm = timezoneMinutesFromString(input);
+ break;
+ // WEEKDAY - human
+ case 'dd':
+ case 'ddd':
+ case 'dddd':
+ a = getLangDefinition(config._l).weekdaysParse(input);
+ // if we didn't get a weekday name, mark the date as invalid
+ if (a != null) {
+ config._w = config._w || {};
+ config._w['d'] = a;
+ } else {
+ config._pf.invalidWeekday = input;
+ }
+ break;
+ // WEEK, WEEK DAY - numeric
+ case 'w':
+ case 'ww':
+ case 'W':
+ case 'WW':
+ case 'd':
+ case 'e':
+ case 'E':
+ token = token.substr(0, 1);
+ /* falls through */
+ case 'gggg':
+ case 'GGGG':
+ case 'GGGGG':
+ token = token.substr(0, 2);
+ if (input) {
+ config._w = config._w || {};
+ config._w[token] = toInt(input);
+ }
+ break;
+ case 'gg':
+ case 'GG':
+ config._w = config._w || {};
+ config._w[token] = moment.parseTwoDigitYear(input);
+ }
+ }
+
+ function dayOfYearFromWeekInfo(config) {
+ var w, weekYear, week, weekday, dow, doy, temp, lang;
+
+ w = config._w;
+ if (w.GG != null || w.W != null || w.E != null) {
+ dow = 1;
+ doy = 4;
+
+ // TODO: We need to take the current isoWeekYear, but that depends on
+ // how we interpret now (local, utc, fixed offset). So create
+ // a now version of current config (take local/utc/offset flags, and
+ // create now).
+ weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year);
+ week = dfl(w.W, 1);
+ weekday = dfl(w.E, 1);
+ } else {
+ lang = getLangDefinition(config._l);
+ dow = lang._week.dow;
+ doy = lang._week.doy;
+
+ weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year);
+ week = dfl(w.w, 1);
+
+ if (w.d != null) {
+ // weekday -- low day numbers are considered next week
+ weekday = w.d;
+ if (weekday < dow) {
+ ++week;
+ }
+ } else if (w.e != null) {
+ // local weekday -- counting starts from begining of week
+ weekday = w.e + dow;
+ } else {
+ // default to begining of week
+ weekday = dow;
+ }
+ }
+ temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow);
+
+ config._a[YEAR] = temp.year;
+ config._dayOfYear = temp.dayOfYear;
+ }
+
+ // convert an array to a date.
+ // the array should mirror the parameters below
+ // note: all values past the year are optional and will default to the lowest possible value.
+ // [year, month, day , hour, minute, second, millisecond]
+ function dateFromConfig(config) {
+ var i, date, input = [], currentDate, yearToUse;
+
+ if (config._d) {
+ return;
+ }
+
+ currentDate = currentDateArray(config);
+
+ //compute day of the year from weeks and weekdays
+ if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
+ dayOfYearFromWeekInfo(config);
+ }
+
+ //if the day of the year is set, figure out what it is
+ if (config._dayOfYear) {
+ yearToUse = dfl(config._a[YEAR], currentDate[YEAR]);
+
+ if (config._dayOfYear > daysInYear(yearToUse)) {
+ config._pf._overflowDayOfYear = true;
+ }
+
+ date = makeUTCDate(yearToUse, 0, config._dayOfYear);
+ config._a[MONTH] = date.getUTCMonth();
+ config._a[DATE] = date.getUTCDate();
+ }
+
+ // Default to current date.
+ // * if no year, month, day of month are given, default to today
+ // * if day of month is given, default month and year
+ // * if month is given, default only year
+ // * if year is given, don't default anything
+ for (i = 0; i < 3 && config._a[i] == null; ++i) {
+ config._a[i] = input[i] = currentDate[i];
+ }
+
+ // Zero out whatever was not defaulted, including time
+ for (; i < 7; i++) {
+ config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
+ }
+
+ config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input);
+ // Apply timezone offset from input. The actual zone can be changed
+ // with parseZone.
+ if (config._tzm != null) {
+ config._d.setUTCMinutes(config._d.getUTCMinutes() + config._tzm);
+ }
+ }
+
+ function dateFromObject(config) {
+ var normalizedInput;
+
+ if (config._d) {
+ return;
+ }
+
+ normalizedInput = normalizeObjectUnits(config._i);
+ config._a = [
+ normalizedInput.year,
+ normalizedInput.month,
+ normalizedInput.day,
+ normalizedInput.hour,
+ normalizedInput.minute,
+ normalizedInput.second,
+ normalizedInput.millisecond
+ ];
+
+ dateFromConfig(config);
+ }
+
+ function currentDateArray(config) {
+ var now = new Date();
+ if (config._useUTC) {
+ return [
+ now.getUTCFullYear(),
+ now.getUTCMonth(),
+ now.getUTCDate()
+ ];
+ } else {
+ return [now.getFullYear(), now.getMonth(), now.getDate()];
+ }
+ }
+
+ // date from string and format string
+ function makeDateFromStringAndFormat(config) {
+
+ if (config._f === moment.ISO_8601) {
+ parseISO(config);
+ return;
+ }
+
+ config._a = [];
+ config._pf.empty = true;
+
+ // This array is used to make a Date, either with `new Date` or `Date.UTC`
+ var lang = getLangDefinition(config._l),
+ string = '' + config._i,
+ i, parsedInput, tokens, token, skipped,
+ stringLength = string.length,
+ totalParsedInputLength = 0;
+
+ tokens = expandFormat(config._f, lang).match(formattingTokens) || [];
+
+ for (i = 0; i < tokens.length; i++) {
+ token = tokens[i];
+ parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
+ if (parsedInput) {
+ skipped = string.substr(0, string.indexOf(parsedInput));
+ if (skipped.length > 0) {
+ config._pf.unusedInput.push(skipped);
+ }
+ string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
+ totalParsedInputLength += parsedInput.length;
+ }
+ // don't parse if it's not a known token
+ if (formatTokenFunctions[token]) {
+ if (parsedInput) {
+ config._pf.empty = false;
+ }
+ else {
+ config._pf.unusedTokens.push(token);
+ }
+ addTimeToArrayFromToken(token, parsedInput, config);
+ }
+ else if (config._strict && !parsedInput) {
+ config._pf.unusedTokens.push(token);
+ }
+ }
+
+ // add remaining unparsed input length to the string
+ config._pf.charsLeftOver = stringLength - totalParsedInputLength;
+ if (string.length > 0) {
+ config._pf.unusedInput.push(string);
+ }
+
+ // handle am pm
+ if (config._isPm && config._a[HOUR] < 12) {
+ config._a[HOUR] += 12;
+ }
+ // if is 12 am, change hours to 0
+ if (config._isPm === false && config._a[HOUR] === 12) {
+ config._a[HOUR] = 0;
+ }
+
+ dateFromConfig(config);
+ checkOverflow(config);
+ }
+
+ function unescapeFormat(s) {
+ return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
+ return p1 || p2 || p3 || p4;
+ });
+ }
+
+ // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+ function regexpEscape(s) {
+ return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+ }
+
+ // date from string and array of format strings
+ function makeDateFromStringAndArray(config) {
+ var tempConfig,
+ bestMoment,
+
+ scoreToBeat,
+ i,
+ currentScore;
+
+ if (config._f.length === 0) {
+ config._pf.invalidFormat = true;
+ config._d = new Date(NaN);
+ return;
+ }
+
+ for (i = 0; i < config._f.length; i++) {
+ currentScore = 0;
+ tempConfig = extend({}, config);
+ tempConfig._pf = defaultParsingFlags();
+ tempConfig._f = config._f[i];
+ makeDateFromStringAndFormat(tempConfig);
+
+ if (!isValid(tempConfig)) {
+ continue;
+ }
+
+ // if there is any input that was not parsed add a penalty for that format
+ currentScore += tempConfig._pf.charsLeftOver;
+
+ //or tokens
+ currentScore += tempConfig._pf.unusedTokens.length * 10;
+
+ tempConfig._pf.score = currentScore;
+
+ if (scoreToBeat == null || currentScore < scoreToBeat) {
+ scoreToBeat = currentScore;
+ bestMoment = tempConfig;
+ }
+ }
+
+ extend(config, bestMoment || tempConfig);
+ }
+
+ // date from iso format
+ function parseISO(config) {
+ var i, l,
+ string = config._i,
+ match = isoRegex.exec(string);
+
+ if (match) {
+ config._pf.iso = true;
+ for (i = 0, l = isoDates.length; i < l; i++) {
+ if (isoDates[i][1].exec(string)) {
+ // match[5] should be "T" or undefined
+ config._f = isoDates[i][0] + (match[6] || " ");
+ break;
+ }
+ }
+ for (i = 0, l = isoTimes.length; i < l; i++) {
+ if (isoTimes[i][1].exec(string)) {
+ config._f += isoTimes[i][0];
+ break;
+ }
+ }
+ if (string.match(parseTokenTimezone)) {
+ config._f += "Z";
+ }
+ makeDateFromStringAndFormat(config);
+ } else {
+ config._isValid = false;
+ }
+ }
+
+ // date from iso format or fallback
+ function makeDateFromString(config) {
+ parseISO(config);
+ if (config._isValid === false) {
+ delete config._isValid;
+ moment.createFromInputFallback(config);
+ }
+ }
+
+ function makeDateFromInput(config) {
+ var input = config._i,
+ matched = aspNetJsonRegex.exec(input);
+
+ if (input === undefined) {
+ config._d = new Date();
+ } else if (matched) {
+ config._d = new Date(+matched[1]);
+ } else if (typeof input === 'string') {
+ makeDateFromString(config);
+ } else if (isArray(input)) {
+ config._a = input.slice(0);
+ dateFromConfig(config);
+ } else if (isDate(input)) {
+ config._d = new Date(+input);
+ } else if (typeof(input) === 'object') {
+ dateFromObject(config);
+ } else if (typeof(input) === 'number') {
+ // from milliseconds
+ config._d = new Date(input);
+ } else {
+ moment.createFromInputFallback(config);
+ }
+ }
+
+ function makeDate(y, m, d, h, M, s, ms) {
+ //can't just apply() to create a date:
+ //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
+ var date = new Date(y, m, d, h, M, s, ms);
+
+ //the date constructor doesn't accept years < 1970
+ if (y < 1970) {
+ date.setFullYear(y);
+ }
+ return date;
+ }
+
+ function makeUTCDate(y) {
+ var date = new Date(Date.UTC.apply(null, arguments));
+ if (y < 1970) {
+ date.setUTCFullYear(y);
+ }
+ return date;
+ }
+
+ function parseWeekday(input, language) {
+ if (typeof input === 'string') {
+ if (!isNaN(input)) {
+ input = parseInt(input, 10);
+ }
+ else {
+ input = language.weekdaysParse(input);
+ if (typeof input !== 'number') {
+ return null;
+ }
+ }
+ }
+ return input;
+ }
+
+ /************************************
+ Relative Time
+ ************************************/
+
+
+ // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
+ function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) {
+ return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
+ }
+
+ function relativeTime(milliseconds, withoutSuffix, lang) {
+ var seconds = round(Math.abs(milliseconds) / 1000),
+ minutes = round(seconds / 60),
+ hours = round(minutes / 60),
+ days = round(hours / 24),
+ years = round(days / 365),
+ args = seconds < relativeTimeThresholds.s && ['s', seconds] ||
+ minutes === 1 && ['m'] ||
+ minutes < relativeTimeThresholds.m && ['mm', minutes] ||
+ hours === 1 && ['h'] ||
+ hours < relativeTimeThresholds.h && ['hh', hours] ||
+ days === 1 && ['d'] ||
+ days <= relativeTimeThresholds.dd && ['dd', days] ||
+ days <= relativeTimeThresholds.dm && ['M'] ||
+ days < relativeTimeThresholds.dy && ['MM', round(days / 30)] ||
+ years === 1 && ['y'] || ['yy', years];
+ args[2] = withoutSuffix;
+ args[3] = milliseconds > 0;
+ args[4] = lang;
+ return substituteTimeAgo.apply({}, args);
+ }
+
+
+ /************************************
+ Week of Year
+ ************************************/
+
+
+ // firstDayOfWeek 0 = sun, 6 = sat
+ // the day of the week that starts the week
+ // (usually sunday or monday)
+ // firstDayOfWeekOfYear 0 = sun, 6 = sat
+ // the first week is the week that contains the first
+ // of this day of the week
+ // (eg. ISO weeks use thursday (4))
+ function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
+ var end = firstDayOfWeekOfYear - firstDayOfWeek,
+ daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(),
+ adjustedMoment;
+
+
+ if (daysToDayOfWeek > end) {
+ daysToDayOfWeek -= 7;
+ }
+
+ if (daysToDayOfWeek < end - 7) {
+ daysToDayOfWeek += 7;
+ }
+
+ adjustedMoment = moment(mom).add('d', daysToDayOfWeek);
+ return {
+ week: Math.ceil(adjustedMoment.dayOfYear() / 7),
+ year: adjustedMoment.year()
+ };
+ }
+
+ //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
+ function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
+ var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear;
+
+ d = d === 0 ? 7 : d;
+ weekday = weekday != null ? weekday : firstDayOfWeek;
+ daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0);
+ dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
+
+ return {
+ year: dayOfYear > 0 ? year : year - 1,
+ dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear
+ };
+ }
+
+ /************************************
+ Top Level Functions
+ ************************************/
+
+ function makeMoment(config) {
+ var input = config._i,
+ format = config._f;
+
+ if (input === null || (format === undefined && input === '')) {
+ return moment.invalid({nullInput: true});
+ }
+
+ if (typeof input === 'string') {
+ config._i = input = getLangDefinition().preparse(input);
+ }
+
+ if (moment.isMoment(input)) {
+ config = cloneMoment(input);
+
+ config._d = new Date(+input._d);
+ } else if (format) {
+ if (isArray(format)) {
+ makeDateFromStringAndArray(config);
+ } else {
+ makeDateFromStringAndFormat(config);
+ }
+ } else {
+ makeDateFromInput(config);
+ }
+
+ return new Moment(config);
+ }
+
+ moment = function (input, format, lang, strict) {
+ var c;
+
+ if (typeof(lang) === "boolean") {
+ strict = lang;
+ lang = undefined;
+ }
+ // object construction must be done this way.
+ // https://github.com/moment/moment/issues/1423
+ c = {};
+ c._isAMomentObject = true;
+ c._i = input;
+ c._f = format;
+ c._l = lang;
+ c._strict = strict;
+ c._isUTC = false;
+ c._pf = defaultParsingFlags();
+
+ return makeMoment(c);
+ };
+
+ moment.suppressDeprecationWarnings = false;
+
+ moment.createFromInputFallback = deprecate(
+ "moment construction falls back to js Date. This is " +
+ "discouraged and will be removed in upcoming major " +
+ "release. Please refer to " +
+ "https://github.com/moment/moment/issues/1407 for more info.",
+ function (config) {
+ config._d = new Date(config._i);
+ });
+
+ // Pick a moment m from moments so that m[fn](other) is true for all
+ // other. This relies on the function fn to be transitive.
+ //
+ // moments should either be an array of moment objects or an array, whose
+ // first element is an array of moment objects.
+ function pickBy(fn, moments) {
+ var res, i;
+ if (moments.length === 1 && isArray(moments[0])) {
+ moments = moments[0];
+ }
+ if (!moments.length) {
+ return moment();
+ }
+ res = moments[0];
+ for (i = 1; i < moments.length; ++i) {
+ if (moments[i][fn](res)) {
+ res = moments[i];
+ }
+ }
+ return res;
+ }
+
+ moment.min = function () {
+ var args = [].slice.call(arguments, 0);
+
+ return pickBy('isBefore', args);
+ };
+
+ moment.max = function () {
+ var args = [].slice.call(arguments, 0);
+
+ return pickBy('isAfter', args);
+ };
+
+ // creating with utc
+ moment.utc = function (input, format, lang, strict) {
+ var c;
+
+ if (typeof(lang) === "boolean") {
+ strict = lang;
+ lang = undefined;
+ }
+ // object construction must be done this way.
+ // https://github.com/moment/moment/issues/1423
+ c = {};
+ c._isAMomentObject = true;
+ c._useUTC = true;
+ c._isUTC = true;
+ c._l = lang;
+ c._i = input;
+ c._f = format;
+ c._strict = strict;
+ c._pf = defaultParsingFlags();
+
+ return makeMoment(c).utc();
+ };
+
+ // creating with unix timestamp (in seconds)
+ moment.unix = function (input) {
+ return moment(input * 1000);
+ };
+
+ // duration
+ moment.duration = function (input, key) {
+ var duration = input,
+ // matching against regexp is expensive, do it on demand
+ match = null,
+ sign,
+ ret,
+ parseIso;
+
+ if (moment.isDuration(input)) {
+ duration = {
+ ms: input._milliseconds,
+ d: input._days,
+ M: input._months
+ };
+ } else if (typeof input === 'number') {
+ duration = {};
+ if (key) {
+ duration[key] = input;
+ } else {
+ duration.milliseconds = input;
+ }
+ } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) {
+ sign = (match[1] === "-") ? -1 : 1;
+ duration = {
+ y: 0,
+ d: toInt(match[DATE]) * sign,
+ h: toInt(match[HOUR]) * sign,
+ m: toInt(match[MINUTE]) * sign,
+ s: toInt(match[SECOND]) * sign,
+ ms: toInt(match[MILLISECOND]) * sign
+ };
+ } else if (!!(match = isoDurationRegex.exec(input))) {
+ sign = (match[1] === "-") ? -1 : 1;
+ parseIso = function (inp) {
+ // We'd normally use ~~inp for this, but unfortunately it also
+ // converts floats to ints.
+ // inp may be undefined, so careful calling replace on it.
+ var res = inp && parseFloat(inp.replace(',', '.'));
+ // apply sign while we're at it
+ return (isNaN(res) ? 0 : res) * sign;
+ };
+ duration = {
+ y: parseIso(match[2]),
+ M: parseIso(match[3]),
+ d: parseIso(match[4]),
+ h: parseIso(match[5]),
+ m: parseIso(match[6]),
+ s: parseIso(match[7]),
+ w: parseIso(match[8])
+ };
+ }
+
+ ret = new Duration(duration);
+
+ if (moment.isDuration(input) && input.hasOwnProperty('_lang')) {
+ ret._lang = input._lang;
+ }
+
+ return ret;
+ };
+
+ // version number
+ moment.version = VERSION;
+
+ // default format
+ moment.defaultFormat = isoFormat;
+
+ // constant that refers to the ISO standard
+ moment.ISO_8601 = function () {};
+
+ // Plugins that add properties should also add the key here (null value),
+ // so we can properly clone ourselves.
+ moment.momentProperties = momentProperties;
+
+ // This function will be called whenever a moment is mutated.
+ // It is intended to keep the offset in sync with the timezone.
+ moment.updateOffset = function () {};
+
+ // This function allows you to set a threshold for relative time strings
+ moment.relativeTimeThreshold = function(threshold, limit) {
+ if (relativeTimeThresholds[threshold] === undefined) {
+ return false;
+ }
+ relativeTimeThresholds[threshold] = limit;
+ return true;
+ };
+
+ // This function will load languages and then set the global language. If
+ // no arguments are passed in, it will simply return the current global
+ // language key.
+ moment.lang = function (key, values) {
+ var r;
+ if (!key) {
+ return moment.fn._lang._abbr;
+ }
+ if (values) {
+ loadLang(normalizeLanguage(key), values);
+ } else if (values === null) {
+ unloadLang(key);
+ key = 'en';
+ } else if (!languages[key]) {
+ getLangDefinition(key);
+ }
+ r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key);
+ return r._abbr;
+ };
+
+ // returns language data
+ moment.langData = function (key) {
+ if (key && key._lang && key._lang._abbr) {
+ key = key._lang._abbr;
+ }
+ return getLangDefinition(key);
+ };
+
+ // compare moment object
+ moment.isMoment = function (obj) {
+ return obj instanceof Moment ||
+ (obj != null && obj.hasOwnProperty('_isAMomentObject'));
+ };
+
+ // for typechecking Duration objects
+ moment.isDuration = function (obj) {
+ return obj instanceof Duration;
+ };
+
+ for (i = lists.length - 1; i >= 0; --i) {
+ makeList(lists[i]);
+ }
+
+ moment.normalizeUnits = function (units) {
+ return normalizeUnits(units);
+ };
+
+ moment.invalid = function (flags) {
+ var m = moment.utc(NaN);
+ if (flags != null) {
+ extend(m._pf, flags);
+ }
+ else {
+ m._pf.userInvalidated = true;
+ }
+
+ return m;
+ };
+
+ moment.parseZone = function () {
+ return moment.apply(null, arguments).parseZone();
+ };
+
+ moment.parseTwoDigitYear = function (input) {
+ return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
+ };
+
+ /************************************
+ Moment Prototype
+ ************************************/
+
+
+ extend(moment.fn = Moment.prototype, {
+
+ clone : function () {
+ return moment(this);
+ },
+
+ valueOf : function () {
+ return +this._d + ((this._offset || 0) * 60000);
+ },
+
+ unix : function () {
+ return Math.floor(+this / 1000);
+ },
+
+ toString : function () {
+ return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ");
+ },
+
+ toDate : function () {
+ return this._offset ? new Date(+this) : this._d;
+ },
+
+ toISOString : function () {
+ var m = moment(this).utc();
+ if (0 < m.year() && m.year() <= 9999) {
+ return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+ } else {
+ return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+ }
+ },
+
+ toArray : function () {
+ var m = this;
+ return [
+ m.year(),
+ m.month(),
+ m.date(),
+ m.hours(),
+ m.minutes(),
+ m.seconds(),
+ m.milliseconds()
+ ];
+ },
+
+ isValid : function () {
+ return isValid(this);
+ },
+
+ isDSTShifted : function () {
+
+ if (this._a) {
+ return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0;
+ }
+
+ return false;
+ },
+
+ parsingFlags : function () {
+ return extend({}, this._pf);
+ },
+
+ invalidAt: function () {
+ return this._pf.overflow;
+ },
+
+ utc : function () {
+ return this.zone(0);
+ },
+
+ local : function () {
+ this.zone(0);
+ this._isUTC = false;
+ return this;
+ },
+
+ format : function (inputString) {
+ var output = formatMoment(this, inputString || moment.defaultFormat);
+ return this.lang().postformat(output);
+ },
+
+ add : function (input, val) {
+ var dur;
+ // switch args to support add('s', 1) and add(1, 's')
+ if (typeof input === 'string' && typeof val === 'string') {
+ dur = moment.duration(isNaN(+val) ? +input : +val, isNaN(+val) ? val : input);
+ } else if (typeof input === 'string') {
+ dur = moment.duration(+val, input);
+ } else {
+ dur = moment.duration(input, val);
+ }
+ addOrSubtractDurationFromMoment(this, dur, 1);
+ return this;
+ },
+
+ subtract : function (input, val) {
+ var dur;
+ // switch args to support subtract('s', 1) and subtract(1, 's')
+ if (typeof input === 'string' && typeof val === 'string') {
+ dur = moment.duration(isNaN(+val) ? +input : +val, isNaN(+val) ? val : input);
+ } else if (typeof input === 'string') {
+ dur = moment.duration(+val, input);
+ } else {
+ dur = moment.duration(input, val);
+ }
+ addOrSubtractDurationFromMoment(this, dur, -1);
+ return this;
+ },
+
+ diff : function (input, units, asFloat) {
+ var that = makeAs(input, this),
+ zoneDiff = (this.zone() - that.zone()) * 6e4,
+ diff, output;
+
+ units = normalizeUnits(units);
+
+ if (units === 'year' || units === 'month') {
+ // average number of days in the months in the given dates
+ diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2
+ // difference in months
+ output = ((this.year() - that.year()) * 12) + (this.month() - that.month());
+ // adjust by taking difference in days, average number of days
+ // and dst in the given months.
+ output += ((this - moment(this).startOf('month')) -
+ (that - moment(that).startOf('month'))) / diff;
+ // same as above but with zones, to negate all dst
+ output -= ((this.zone() - moment(this).startOf('month').zone()) -
+ (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff;
+ if (units === 'year') {
+ output = output / 12;
+ }
+ } else {
+ diff = (this - that);
+ output = units === 'second' ? diff / 1e3 : // 1000
+ units === 'minute' ? diff / 6e4 : // 1000 * 60
+ units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
+ units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
+ units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
+ diff;
+ }
+ return asFloat ? output : absRound(output);
+ },
+
+ from : function (time, withoutSuffix) {
+ return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix);
+ },
+
+ fromNow : function (withoutSuffix) {
+ return this.from(moment(), withoutSuffix);
+ },
+
+ calendar : function (time) {
+ // We want to compare the start of today, vs this.
+ // Getting start-of-today depends on whether we're zone'd or not.
+ var now = time || moment(),
+ sod = makeAs(now, this).startOf('day'),
+ diff = this.diff(sod, 'days', true),
+ format = diff < -6 ? 'sameElse' :
+ diff < -1 ? 'lastWeek' :
+ diff < 0 ? 'lastDay' :
+ diff < 1 ? 'sameDay' :
+ diff < 2 ? 'nextDay' :
+ diff < 7 ? 'nextWeek' : 'sameElse';
+ return this.format(this.lang().calendar(format, this));
+ },
+
+ isLeapYear : function () {
+ return isLeapYear(this.year());
+ },
+
+ isDST : function () {
+ return (this.zone() < this.clone().month(0).zone() ||
+ this.zone() < this.clone().month(5).zone());
+ },
+
+ day : function (input) {
+ var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+ if (input != null) {
+ input = parseWeekday(input, this.lang());
+ return this.add({ d : input - day });
+ } else {
+ return day;
+ }
+ },
+
+ month : makeAccessor('Month', true),
+
+ startOf: function (units) {
+ units = normalizeUnits(units);
+ // the following switch intentionally omits break keywords
+ // to utilize falling through the cases.
+ switch (units) {
+ case 'year':
+ this.month(0);
+ /* falls through */
+ case 'quarter':
+ case 'month':
+ this.date(1);
+ /* falls through */
+ case 'week':
+ case 'isoWeek':
+ case 'day':
+ this.hours(0);
+ /* falls through */
+ case 'hour':
+ this.minutes(0);
+ /* falls through */
+ case 'minute':
+ this.seconds(0);
+ /* falls through */
+ case 'second':
+ this.milliseconds(0);
+ /* falls through */
+ }
+
+ // weeks are a special case
+ if (units === 'week') {
+ this.weekday(0);
+ } else if (units === 'isoWeek') {
+ this.isoWeekday(1);
+ }
+
+ // quarters are also special
+ if (units === 'quarter') {
+ this.month(Math.floor(this.month() / 3) * 3);
+ }
+
+ return this;
+ },
+
+ endOf: function (units) {
+ units = normalizeUnits(units);
+ return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1);
+ },
+
+ isAfter: function (input, units) {
+ units = typeof units !== 'undefined' ? units : 'millisecond';
+ return +this.clone().startOf(units) > +moment(input).startOf(units);
+ },
+
+ isBefore: function (input, units) {
+ units = typeof units !== 'undefined' ? units : 'millisecond';
+ return +this.clone().startOf(units) < +moment(input).startOf(units);
+ },
+
+ isSame: function (input, units) {
+ units = units || 'ms';
+ return +this.clone().startOf(units) === +makeAs(input, this).startOf(units);
+ },
+
+ min: deprecate(
+ "moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",
+ function (other) {
+ other = moment.apply(null, arguments);
+ return other < this ? this : other;
+ }
+ ),
+
+ max: deprecate(
+ "moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",
+ function (other) {
+ other = moment.apply(null, arguments);
+ return other > this ? this : other;
+ }
+ ),
+
+ // keepTime = true means only change the timezone, without affecting
+ // the local hour. So 5:31:26 +0300 --[zone(2, true)]--> 5:31:26 +0200
+ // It is possible that 5:31:26 doesn't exist int zone +0200, so we
+ // adjust the time as needed, to be valid.
+ //
+ // Keeping the time actually adds/subtracts (one hour)
+ // from the actual represented time. That is why we call updateOffset
+ // a second time. In case it wants us to change the offset again
+ // _changeInProgress == true case, then we have to adjust, because
+ // there is no such time in the given timezone.
+ zone : function (input, keepTime) {
+ var offset = this._offset || 0;
+ if (input != null) {
+ if (typeof input === "string") {
+ input = timezoneMinutesFromString(input);
+ }
+ if (Math.abs(input) < 16) {
+ input = input * 60;
+ }
+ this._offset = input;
+ this._isUTC = true;
+ if (offset !== input) {
+ if (!keepTime || this._changeInProgress) {
+ addOrSubtractDurationFromMoment(this,
+ moment.duration(offset - input, 'm'), 1, false);
+ } else if (!this._changeInProgress) {
+ this._changeInProgress = true;
+ moment.updateOffset(this, true);
+ this._changeInProgress = null;
+ }
+ }
+ } else {
+ return this._isUTC ? offset : this._d.getTimezoneOffset();
+ }
+ return this;
+ },
+
+ zoneAbbr : function () {
+ return this._isUTC ? "UTC" : "";
+ },
+
+ zoneName : function () {
+ return this._isUTC ? "Coordinated Universal Time" : "";
+ },
+
+ parseZone : function () {
+ if (this._tzm) {
+ this.zone(this._tzm);
+ } else if (typeof this._i === 'string') {
+ this.zone(this._i);
+ }
+ return this;
+ },
+
+ hasAlignedHourOffset : function (input) {
+ if (!input) {
+ input = 0;
+ }
+ else {
+ input = moment(input).zone();
+ }
+
+ return (this.zone() - input) % 60 === 0;
+ },
+
+ daysInMonth : function () {
+ return daysInMonth(this.year(), this.month());
+ },
+
+ dayOfYear : function (input) {
+ var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
+ return input == null ? dayOfYear : this.add("d", (input - dayOfYear));
+ },
+
+ quarter : function (input) {
+ return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
+ },
+
+ weekYear : function (input) {
+ var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year;
+ return input == null ? year : this.add("y", (input - year));
+ },
+
+ isoWeekYear : function (input) {
+ var year = weekOfYear(this, 1, 4).year;
+ return input == null ? year : this.add("y", (input - year));
+ },
+
+ week : function (input) {
+ var week = this.lang().week(this);
+ return input == null ? week : this.add("d", (input - week) * 7);
+ },
+
+ isoWeek : function (input) {
+ var week = weekOfYear(this, 1, 4).week;
+ return input == null ? week : this.add("d", (input - week) * 7);
+ },
+
+ weekday : function (input) {
+ var weekday = (this.day() + 7 - this.lang()._week.dow) % 7;
+ return input == null ? weekday : this.add("d", input - weekday);
+ },
+
+ isoWeekday : function (input) {
+ // behaves the same as moment#day except
+ // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
+ // as a setter, sunday should belong to the previous week.
+ return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
+ },
+
+ isoWeeksInYear : function () {
+ return weeksInYear(this.year(), 1, 4);
+ },
+
+ weeksInYear : function () {
+ var weekInfo = this._lang._week;
+ return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
+ },
+
+ get : function (units) {
+ units = normalizeUnits(units);
+ return this[units]();
+ },
+
+ set : function (units, value) {
+ units = normalizeUnits(units);
+ if (typeof this[units] === 'function') {
+ this[units](value);
+ }
+ return this;
+ },
+
+ // If passed a language key, it will set the language for this
+ // instance. Otherwise, it will return the language configuration
+ // variables for this instance.
+ lang : function (key) {
+ if (key === undefined) {
+ return this._lang;
+ } else {
+ this._lang = getLangDefinition(key);
+ return this;
+ }
+ }
+ });
+
+ function rawMonthSetter(mom, value) {
+ var dayOfMonth;
+
+ // TODO: Move this out of here!
+ if (typeof value === 'string') {
+ value = mom.lang().monthsParse(value);
+ // TODO: Another silent failure?
+ if (typeof value !== 'number') {
+ return mom;
+ }
+ }
+
+ dayOfMonth = Math.min(mom.date(),
+ daysInMonth(mom.year(), value));
+ mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
+ return mom;
+ }
+
+ function rawGetter(mom, unit) {
+ return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]();
+ }
+
+ function rawSetter(mom, unit, value) {
+ if (unit === 'Month') {
+ return rawMonthSetter(mom, value);
+ } else {
+ return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
+ }
+ }
+
+ function makeAccessor(unit, keepTime) {
+ return function (value) {
+ if (value != null) {
+ rawSetter(this, unit, value);
+ moment.updateOffset(this, keepTime);
+ return this;
+ } else {
+ return rawGetter(this, unit);
+ }
+ };
+ }
+
+ moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false);
+ moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false);
+ moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false);
+ // Setting the hour should keep the time, because the user explicitly
+ // specified which hour he wants. So trying to maintain the same hour (in
+ // a new timezone) makes sense. Adding/subtracting hours does not follow
+ // this rule.
+ moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true);
+ // moment.fn.month is defined separately
+ moment.fn.date = makeAccessor('Date', true);
+ moment.fn.dates = deprecate("dates accessor is deprecated. Use date instead.", makeAccessor('Date', true));
+ moment.fn.year = makeAccessor('FullYear', true);
+ moment.fn.years = deprecate("years accessor is deprecated. Use year instead.", makeAccessor('FullYear', true));
+
+ // add plural methods
+ moment.fn.days = moment.fn.day;
+ moment.fn.months = moment.fn.month;
+ moment.fn.weeks = moment.fn.week;
+ moment.fn.isoWeeks = moment.fn.isoWeek;
+ moment.fn.quarters = moment.fn.quarter;
+
+ // add aliased format methods
+ moment.fn.toJSON = moment.fn.toISOString;
+
+ /************************************
+ Duration Prototype
+ ************************************/
+
+
+ extend(moment.duration.fn = Duration.prototype, {
+
+ _bubble : function () {
+ var milliseconds = this._milliseconds,
+ days = this._days,
+ months = this._months,
+ data = this._data,
+ seconds, minutes, hours, years;
+
+ // The following code bubbles up values, see the tests for
+ // examples of what that means.
+ data.milliseconds = milliseconds % 1000;
+
+ seconds = absRound(milliseconds / 1000);
+ data.seconds = seconds % 60;
+
+ minutes = absRound(seconds / 60);
+ data.minutes = minutes % 60;
+
+ hours = absRound(minutes / 60);
+ data.hours = hours % 24;
+
+ days += absRound(hours / 24);
+ data.days = days % 30;
+
+ months += absRound(days / 30);
+ data.months = months % 12;
+
+ years = absRound(months / 12);
+ data.years = years;
+ },
+
+ weeks : function () {
+ return absRound(this.days() / 7);
+ },
+
+ valueOf : function () {
+ return this._milliseconds +
+ this._days * 864e5 +
+ (this._months % 12) * 2592e6 +
+ toInt(this._months / 12) * 31536e6;
+ },
+
+ humanize : function (withSuffix) {
+ var difference = +this,
+ output = relativeTime(difference, !withSuffix, this.lang());
+
+ if (withSuffix) {
+ output = this.lang().pastFuture(difference, output);
+ }
+
+ return this.lang().postformat(output);
+ },
+
+ add : function (input, val) {
+ // supports only 2.0-style add(1, 's') or add(moment)
+ var dur = moment.duration(input, val);
+
+ this._milliseconds += dur._milliseconds;
+ this._days += dur._days;
+ this._months += dur._months;
+
+ this._bubble();
+
+ return this;
+ },
+
+ subtract : function (input, val) {
+ var dur = moment.duration(input, val);
+
+ this._milliseconds -= dur._milliseconds;
+ this._days -= dur._days;
+ this._months -= dur._months;
+
+ this._bubble();
+
+ return this;
+ },
+
+ get : function (units) {
+ units = normalizeUnits(units);
+ return this[units.toLowerCase() + 's']();
+ },
+
+ as : function (units) {
+ units = normalizeUnits(units);
+ return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's']();
+ },
+
+ lang : moment.fn.lang,
+
+ toIsoString : function () {
+ // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
+ var years = Math.abs(this.years()),
+ months = Math.abs(this.months()),
+ days = Math.abs(this.days()),
+ hours = Math.abs(this.hours()),
+ minutes = Math.abs(this.minutes()),
+ seconds = Math.abs(this.seconds() + this.milliseconds() / 1000);
+
+ if (!this.asSeconds()) {
+ // this is the same as C#'s (Noda) and python (isodate)...
+ // but not other JS (goog.date)
+ return 'P0D';
+ }
+
+ return (this.asSeconds() < 0 ? '-' : '') +
+ 'P' +
+ (years ? years + 'Y' : '') +
+ (months ? months + 'M' : '') +
+ (days ? days + 'D' : '') +
+ ((hours || minutes || seconds) ? 'T' : '') +
+ (hours ? hours + 'H' : '') +
+ (minutes ? minutes + 'M' : '') +
+ (seconds ? seconds + 'S' : '');
+ }
+ });
+
+ function makeDurationGetter(name) {
+ moment.duration.fn[name] = function () {
+ return this._data[name];
+ };
+ }
+
+ function makeDurationAsGetter(name, factor) {
+ moment.duration.fn['as' + name] = function () {
+ return +this / factor;
+ };
+ }
+
+ for (i in unitMillisecondFactors) {
+ if (unitMillisecondFactors.hasOwnProperty(i)) {
+ makeDurationAsGetter(i, unitMillisecondFactors[i]);
+ makeDurationGetter(i.toLowerCase());
+ }
+ }
+
+ makeDurationAsGetter('Weeks', 6048e5);
+ moment.duration.fn.asMonths = function () {
+ return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12;
+ };
+
+
+ /************************************
+ Default Lang
+ ************************************/
+
+
+ // Set default language, other languages will inherit from English.
+ moment.lang('en', {
+ ordinal : function (number) {
+ var b = number % 10,
+ output = (toInt(number % 100 / 10) === 1) ? 'th' :
+ (b === 1) ? 'st' :
+ (b === 2) ? 'nd' :
+ (b === 3) ? 'rd' : 'th';
+ return number + output;
+ }
+ });
+
+ /* EMBED_LANGUAGES */
+
+ /************************************
+ Exposing Moment
+ ************************************/
+
+ function makeGlobal(shouldDeprecate) {
+ /*global ender:false */
+ if (typeof ender !== 'undefined') {
+ return;
+ }
+ oldGlobalMoment = globalScope.moment;
+ if (shouldDeprecate) {
+ globalScope.moment = deprecate(
+ "Accessing Moment through the global scope is " +
+ "deprecated, and will be removed in an upcoming " +
+ "release.",
+ moment);
+ } else {
+ globalScope.moment = moment;
+ }
+ }
+
+ // CommonJS module is defined
+ if (hasModule) {
+ module.exports = moment;
+ } else if (typeof define === "function" && define.amd) {
+ define("moment", function (require, exports, module) {
+ if (module.config && module.config() && module.config().noGlobal === true) {
+ // release the global variable
+ globalScope.moment = oldGlobalMoment;
+ }
+
+ return moment;
+ });
+ makeGlobal(true);
+ } else {
+ makeGlobal();
+ }
+}).call(this);
\ No newline at end of file