mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
merge/moving js stuff around half done
This commit is contained in:
commit
0080809489
198 changed files with 2717 additions and 9375 deletions
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -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')
|
||||
)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
require [
|
||||
"libs/mustache"
|
||||
"./main"
|
||||
"underscore"
|
||||
], (m)->
|
||||
$(document).ready ->
|
||||
|
||||
tableRowTemplate = '''
|
||||
<tr>
|
||||
<td> <input type="checkbox" class="select-one"></td>
|
||||
<td> {{ email }} </td>
|
||||
<td> {{ first_name }} {{ last_name }} </td>
|
||||
<td> {{ !holdingAccount }} </td>
|
||||
<td>
|
||||
<input type="hidden" name="user_id" value="{{_id}}" class="user_id">
|
||||
</td>
|
||||
</tr>
|
||||
'''
|
||||
|
||||
window.temp = tableRowTemplate
|
||||
|
||||
$form = $('form#addUserToGroup')
|
||||
|
||||
addUser = (e)->
|
||||
|
||||
parseEmails = (emailsString)->
|
||||
regexBySpaceOrComma = /[\s,]+/
|
||||
emails = emailsString.split(regexBySpaceOrComma)
|
||||
emails = _.map emails, (email)->
|
||||
email = email.trim()
|
||||
emails = _.select emails, (email)->
|
||||
email.indexOf("@") != -1
|
||||
return emails
|
||||
|
||||
|
||||
sendNewUserToServer = (email)->
|
||||
$.ajax
|
||||
url: "/subscription/group/user"
|
||||
type: 'POST'
|
||||
data:
|
||||
email: email
|
||||
_csrf: csrfToken
|
||||
success: (data)->
|
||||
if data.limitReached
|
||||
alert("You have reached your maximum number of members")
|
||||
else
|
||||
renderNewUserInList data.user
|
||||
|
||||
renderNewUserInList = (user)->
|
||||
html = Mustache.to_html(tableRowTemplate, user)
|
||||
$('#userList').append(html)
|
||||
|
||||
e.preventDefault()
|
||||
val = $form.find("input[name=email]").val()
|
||||
emails = parseEmails(val)
|
||||
emails.forEach (email)->
|
||||
sendNewUserToServer(email)
|
||||
$form.find("input").val('')
|
||||
|
||||
removeUsers = (e)->
|
||||
selectedUserRows = $('td input.select-one:checked').closest('tr').find(".user_id").toArray()
|
||||
do deleteNext = () ->
|
||||
row = selectedUserRows.pop()
|
||||
if row?
|
||||
user_id = $(row).val()
|
||||
$.ajax
|
||||
url: "/subscription/group/user/#{user_id}"
|
||||
type: 'DELETE'
|
||||
data:
|
||||
_csrf: csrfToken
|
||||
success: ->
|
||||
$(row).parents("tr").fadeOut(250)
|
||||
deleteNext()
|
||||
|
||||
$form.on 'keypress', (e)->
|
||||
if(e.keyCode == 13)
|
||||
addUser(e)
|
||||
|
||||
$form.find(".addUser").on 'click', addUser
|
||||
|
||||
$('#deleteUsers').on 'click', removeUsers
|
||||
|
||||
$('input.select-all').on "change", () ->
|
||||
if $(@).is(":checked")
|
||||
$("input.select-one").prop( "checked", true )
|
||||
else
|
||||
$("input.select-one").prop( "checked", false )
|
||||
|
|
@ -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
|
||||
}]
|
|
@ -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)->
|
|
@ -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
|
|
@ -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"])
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
], () ->
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
define () ->
|
||||
class CursorManager
|
||||
UPDATE_INTERVAL: 500
|
||||
|
||||
constructor: (@ide) ->
|
||||
@clients = {}
|
||||
@ide.socket.on "clientTracking.clientUpdated", (cursorUpdate) => @onRemoteClientUpdate(cursorUpdate)
|
||||
@ide.socket.on "clientTracking.clientDisconnected", (client_id) => @onRemoteClientDisconnect(client_id)
|
||||
@ide.editor.on "change:doc", (session) =>
|
||||
@bindToAceSession(session)
|
||||
@ide.editor.on "mousemove", (e) =>
|
||||
@mousePosition = e.position
|
||||
@updateVisibleNames()
|
||||
|
||||
bindToAceSession: (session) ->
|
||||
@clients = {}
|
||||
@ide.editor.aceEditor.on "changeSelection", => @onLocalCursorUpdate()
|
||||
|
||||
onLocalCursorUpdate: () ->
|
||||
if !@cursorUpdateTimeout?
|
||||
@cursorUpdateTimeout = setTimeout (=>
|
||||
@_sendLocalCursorUpdate()
|
||||
delete @cursorUpdateTimeout
|
||||
), @UPDATE_INTERVAL
|
||||
|
||||
_sendLocalCursorUpdate: () ->
|
||||
cursor = @ide.editor.getCursorPosition()
|
||||
if !@currentCursorPosition? or not (cursor.row == @currentCursorPosition.row and cursor.column == @currentCursorPosition.column)
|
||||
@currentCursorPosition = cursor
|
||||
@ide.socket.emit "clientTracking.updatePosition", {
|
||||
row: cursor.row
|
||||
column: cursor.column
|
||||
doc_id: @ide.editor.getCurrentDocId()
|
||||
}
|
||||
|
||||
onRemoteClientUpdate: (clientData) ->
|
||||
if clientData.id != ide.socket.socket.sessionid
|
||||
client = @clients[clientData.id] ||= {}
|
||||
client.row = clientData.row
|
||||
client.column = clientData.column
|
||||
client.name = clientData.name
|
||||
client.doc_id = clientData.doc_id
|
||||
@redrawCursors()
|
||||
|
||||
onRemoteClientDisconnect: (client_id) ->
|
||||
@removeCursor(client_id)
|
||||
delete @clients[client_id]
|
||||
|
||||
removeCursor: (client_id) ->
|
||||
client = @clients[client_id]
|
||||
return if !client?
|
||||
@ide.editor.removeMarker(client.cursorMarkerId)
|
||||
delete client.cursorMarkerId
|
||||
|
||||
redrawCursors: () ->
|
||||
for clientId, clientData of @clients
|
||||
do (clientId, clientData) =>
|
||||
if clientData.cursorMarkerId?
|
||||
@removeCursor(clientId)
|
||||
if clientData.doc_id == @ide.editor.getCurrentDocId()
|
||||
colorId = @getColorIdFromName(clientData.name)
|
||||
clientData.cursorMarkerId = @ide.editor.addMarker {
|
||||
row: clientData.row
|
||||
column: clientData.column
|
||||
length: 1
|
||||
}, "sharelatex-remote-cursor", (html, range, left, top, config) ->
|
||||
div = """
|
||||
<div
|
||||
id='cursor-#{clientId}'
|
||||
class='sharelatex-remote-cursor custom ace_start sharelatex-remote-cursor-#{colorId}'
|
||||
style='height: #{config.lineHeight}px; top:#{top}px; left:#{left}px;'
|
||||
>
|
||||
<div class="nubbin" style="bottom: #{config.lineHeight - 2}px"></div>
|
||||
<div class="name" style="display: none; bottom: #{config.lineHeight - 2}px">#{$('<div/>').text(clientData.name).html()}</div>
|
||||
</div>
|
||||
"""
|
||||
html.push div
|
||||
, true
|
||||
setTimeout =>
|
||||
@updateVisibleNames()
|
||||
, 0
|
||||
|
||||
updateVisibleNames: () ->
|
||||
for clientId, clientData of @clients
|
||||
if @mousePosition? and clientData.row == @mousePosition.row and clientData.column == @mousePosition.column
|
||||
$("#cursor-#{clientId}").find(".name").show()
|
||||
$("#cursor-#{clientId}").find(".nubbin").hide()
|
||||
else
|
||||
$("#cursor-#{clientId}").find(".name").hide()
|
||||
$("#cursor-#{clientId}").find(".nubbin").show()
|
||||
|
||||
getColorIdFromName: (name) ->
|
||||
@currentColorId ||= 0
|
||||
@colorIds ||= {}
|
||||
if !@colorIds[name]?
|
||||
@colorIds[name] = @currentColorId
|
||||
@currentColorId++
|
||||
return @colorIds[name]
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
define [
|
||||
"utils/Modal"
|
||||
], (Modal) ->
|
||||
class DebugManager
|
||||
template: $("#DebugLinkTemplate").html()
|
||||
|
||||
constructor: (@ide) ->
|
||||
@$el = $(@template)
|
||||
$("#toolbar-footer").append(@$el)
|
||||
@$el.on "click", (e) =>
|
||||
e.preventDefault()
|
||||
@showDebugModal()
|
||||
|
||||
showDebugModal: () ->
|
||||
useragent = navigator.userAgent
|
||||
server_id = document.cookie.match(/SERVERID=([^;]*)/)?[1]
|
||||
transport = @ide.socket.socket.transport.name
|
||||
|
||||
new Modal(
|
||||
title: "Debug info"
|
||||
message: """
|
||||
Please give this information to the ShareLaTeX team:
|
||||
<p><pre>
|
||||
user-agent: #{useragent}
|
||||
server-id: #{server_id}
|
||||
transport: #{transport}
|
||||
</pre></p>
|
||||
"""
|
||||
buttons: [
|
||||
text: "OK"
|
||||
]
|
||||
)
|
|
@ -1,6 +1,5 @@
|
|||
define [
|
||||
"base"
|
||||
"../../libs/fineuploader"
|
||||
], (App) ->
|
||||
App.directive 'fineUpload', ($timeout) ->
|
||||
console.log "7777777777"
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
@ -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()
|
|
@ -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
|
|
@ -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'
|
|
@ -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: () ->
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
define [
|
||||
"file-tree/EntityView"
|
||||
"libs/mustache"
|
||||
], (EntityView) ->
|
||||
DocView = EntityView.extend
|
||||
onClick: (e) ->
|
||||
e.preventDefault()
|
||||
@options.manager.openDoc(@model)
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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 <strong>#{entity.get("name")}</strong>?"
|
||||
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()
|
|
@ -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)
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
define [
|
||||
"file-tree/EntityView"
|
||||
"libs/mustache"
|
||||
], (EntityView) ->
|
||||
FileView = EntityView.extend
|
||||
onClick: (e) ->
|
||||
e.preventDefault()
|
||||
@options.manager.openFile(@model)
|
|
@ -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
|
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
|
@ -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"
|
|
@ -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
|
|
@ -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")
|
||||
|
||||
|
||||
|
|
@ -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"])
|
|
@ -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
|
||||
|
|
@ -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 = $("<div/>")
|
||||
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: """
|
||||
<div class="qq-uploader">
|
||||
<div class="qq-upload-drop-area"><span>{dragZoneText}</span></div>
|
||||
<div class="qq-upload-button btn btn-primary btn-large">
|
||||
<div>{uploadButtonText}</div>
|
||||
</div>
|
||||
<span class="or btn-large"> or </span>
|
||||
<span class="drag-here btn-large">drag file(s)</span>
|
||||
<span class="qq-drop-processing"><span>{dropProcessingText}</span><span class="qq-drop-processing-spinner"></span></span>
|
||||
<div class="help">Hint: Press and hold the Control (Ctrl) key to select multiple files</div>
|
||||
<ul class="qq-upload-list"></ul>
|
||||
</div>
|
||||
"""
|
||||
$(".qq-uploader input").addClass("js-file-uploader")
|
||||
|
||||
|
|
@ -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()
|
|
@ -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
|
||||
|
|
@ -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."
|
|
@ -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')
|
||||
|
||||
|
||||
|
|
@ -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")
|
||||
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
define [
|
||||
"base"
|
||||
"libs/jquery-layout"
|
||||
], (App) ->
|
||||
App.directive "layout", ["$parse", ($parse) ->
|
||||
return {
|
|
@ -1,7 +1,6 @@
|
|||
define [
|
||||
"utils/EventEmitter"
|
||||
"ide/editor/ShareJsDoc"
|
||||
"underscore"
|
||||
], (EventEmitter, ShareJsDoc) ->
|
||||
class Document extends EventEmitter
|
||||
@getDocument: (ide, doc_id) ->
|
|
@ -1,6 +1,6 @@
|
|||
define [
|
||||
"utils/EventEmitter"
|
||||
"../../../libs/sharejs"
|
||||
"libs/sharejs"
|
||||
], (EventEmitter, ShareJs) ->
|
||||
class ShareJsDoc extends EventEmitter
|
||||
constructor: (@doc_id, docLines, version, @socket) ->
|
|
@ -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"
|
|
@ -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()
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue