diff --git a/services/web/app/coffee/Features/Editor/EditorController.coffee b/services/web/app/coffee/Features/Editor/EditorController.coffee
index 19e9d98da5..a5dffc961f 100644
--- a/services/web/app/coffee/Features/Editor/EditorController.coffee
+++ b/services/web/app/coffee/Features/Editor/EditorController.coffee
@@ -163,7 +163,7 @@ module.exports = EditorController =
logger.log sl_req_id:sl_req_id, "sending new doc to project #{project_id}"
Metrics.inc "editor.add-doc"
ProjectEntityHandler.addDoc project_id, folder_id, docName, docLines, sl_req_id, (err, doc, folder_id)=>
- EditorRealTimeController.emitToRoom(project_id, 'reciveNewDoc', folder_id, doc)
+ EditorRealTimeController.emitToRoom(project_id, 'docCreated', folder_id, doc)
callback(err, doc)
addFile: (project_id, folder_id, fileName, path, sl_req_id, callback = (error, file)->)->
diff --git a/services/web/app/coffee/Features/Editor/EditorHttpController.coffee b/services/web/app/coffee/Features/Editor/EditorHttpController.coffee
index 57b65d7355..94daaf340f 100644
--- a/services/web/app/coffee/Features/Editor/EditorHttpController.coffee
+++ b/services/web/app/coffee/Features/Editor/EditorHttpController.coffee
@@ -1,6 +1,7 @@
ProjectEntityHandler = require "../Project/ProjectEntityHandler"
logger = require "logger-sharelatex"
EditorRealTimeController = require "./EditorRealTimeController"
+EditorController = require "./EditorController"
module.exports = EditorHttpController =
restoreDoc: (req, res, next) ->
@@ -19,3 +20,10 @@ module.exports = EditorHttpController =
doc_id: doc._id
}
+ addDoc: (req, res, next) ->
+ project_id = req.params.Project_id
+ name = req.body.name
+ parent_folder_id = req.body.parent_folder_id
+ EditorController.addDoc project_id, parent_folder_id, name, [], (error, doc) ->
+ return next(error) if error?
+ res.json doc
diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee
index 2edb5fb2d2..965ff54fba 100644
--- a/services/web/app/coffee/router.coffee
+++ b/services/web/app/coffee/router.coffee
@@ -87,6 +87,8 @@ module.exports = class Router
app.get '/Project/:Project_id', SecurityManager.requestCanAccessProject, ProjectController.loadEditor
app.get '/Project/:Project_id/file/:File_id', SecurityManager.requestCanAccessProject, FileStoreController.getFile
+ app.post '/project/:Project_id/doc', SecurityManager.requestCanModifyProject, EditorHttpController.addDoc
+
app.post '/project/:Project_id/compile', SecurityManager.requestCanAccessProject, CompileController.compile
app.get '/Project/:Project_id/output/output.pdf', SecurityManager.requestCanAccessProject, CompileController.downloadPdf
app.get /^\/project\/([^\/]*)\/output\/(.*)$/,
diff --git a/services/web/app/views/project/editor.jade b/services/web/app/views/project/editor.jade
index 2522c30f1a..e2603473b0 100644
--- a/services/web/app/views/project/editor.jade
+++ b/services/web/app/views/project/editor.jade
@@ -40,9 +40,14 @@ block content
i.fa.fa-comment
#editor-content(ng-cloak, layout="main", ng-hide="state.loading")
- aside#file-tree.ui-layout-west
+ aside#file-tree.ui-layout-west(ng-controller="FileTreeController")
.toolbar.toolbar-small
- a(href, tooltip-html-unsafe="New
File", tooltip-placement="bottom")
+ a(
+ href,
+ ng-click="openNewDocModal()",
+ tooltip-html-unsafe="New
File",
+ tooltip-placement="bottom"
+ )
i.fa.fa-file
a(href, tooltip="New Folder", tooltip-placement="bottom")
i.fa.fa-folder
@@ -58,7 +63,7 @@ block content
ul.list-unstyled.file-tree-list
file-entity(
entity="entity",
- ng-repeat="entity in rootFolder.children"
+ ng-repeat="entity in rootFolder.children | orderBy:'name'"
)
.ui-layout-center
@@ -97,8 +102,32 @@ block content
span(ng-click="select()") {{ entity.name }}
ul.list-unstyled(ng-show="expanded")
- file-entity(entity="child", ng-repeat="child in entity.children")
+ file-entity(entity="child", ng-repeat="child in entity.children | orderBy:'name'")
+ script(type='text/ng-template', id='newDocModalTemplate')
+ .modal-header
+ h3 New File
+ .modal-body
+ form(novalidate, name="newDocForm")
+ input.form-control(
+ type="text",
+ placeholder="File Name",
+ required,
+ ng-model="inputs.name",
+ ng-enter="create()",
+ focus-on="open"
+ )
+ .modal-footer
+ button.btn.btn-default(
+ ng-disabled="state.inflight"
+ ng-click="cancel()"
+ ) Cancel
+ button.btn.btn-primary(
+ ng-disabled="newDocForm.$invalid || state.inflight"
+ ng-click="create()"
+ )
+ span(ng-hide="state.inflight") Create
+ span(ng-show="state.inflight") Creating...
//- #loadingScreen
//- h3 Loading...
diff --git a/services/web/public/coffee/app/ide.coffee b/services/web/public/coffee/app/ide.coffee
index 3c06039117..5e3b398c43 100644
--- a/services/web/public/coffee/app/ide.coffee
+++ b/services/web/public/coffee/app/ide.coffee
@@ -2,35 +2,35 @@ define [
"base"
"ide/file-tree/FileTreeManager"
"ide/directives/layout"
+ "ide/services/ide"
+ "directives/focusOn"
], (
App
FileTreeManager
) ->
- App.controller "IdeController", ["$scope", "$timeout", ($scope, $timeout) ->
+ App.controller "IdeController", ["$scope", "$timeout", "ide", ($scope, $timeout, ide) ->
$scope.state = {
loading: true
load_progress: 40
}
- window.ide = ide = {
- '$scope': $scope
- }
+ window._ide = ide
+
+ ide.project_id = window.project_id
+ ide.$scope = $scope
+ ide.socket = io.connect null,
+ reconnect: false
+ "force new connection": true
+
ide.fileTreeManager = new FileTreeManager(ide, $scope)
- $scope.project_id = window.project_id
-
- ioOptions =
- reconnect: false
- "force new connection": true
- $scope.socket = io.connect null, ioOptions
-
- $scope.socket.on "connect", () ->
+ ide.socket.on "connect", () ->
$scope.$apply () ->
$scope.state.load_progress = 80
joinProject = () =>
- $scope.socket.emit 'joinProject', {
- project_id: $scope.project_id
+ ide.socket.emit 'joinProject', {
+ project_id: ide.project_id
}, (err, project, permissionsLevel, protocolVersion) =>
if $scope.protocolVersion? and $scope.protocolVersion != protocolVersion
location.reload(true)
@@ -43,8 +43,6 @@ define [
$scope.$emit "project:joined"
- console.log "Project", $scope.project, $scope.rootFolder
-
setTimeout(joinProject, 100) ]
angular.bootstrap(document.body, ["SharelatexApp"])
\ No newline at end of file
diff --git a/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee b/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee
index fa412cddf0..4958fc1665 100644
--- a/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee
+++ b/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee
@@ -1,30 +1,61 @@
define [
"ide/file-tree/directives/fileEntity"
+ "ide/file-tree/controllers/FileTreeController"
"ide/file-tree/controllers/FileTreeFolderController"
"ide/file-tree/controllers/FileTreeEntityController"
], () ->
class FileTreeManager
constructor: (@ide, @$scope) ->
@$scope.$on "project:joined", =>
- console.log "Joined"
@loadRootFolder()
+ @_bindToSocketEvents()
+
+ _bindToSocketEvents: () ->
+ @ide.socket.on "docCreated", (parent_folder_id, doc) =>
+ console.log "Doc created", parent_folder_id, doc
+ parent_folder = @findEntityById(parent_folder_id) or @$scope.rootFolder
+ @$scope.$apply () ->
+ parent_folder.children.push {
+ name: doc.name
+ id: doc._id
+ type: "doc"
+ }
+
+ findEntityById: (id) ->
+ @_findEntityByIdInFolder @$scope.rootFolder, id
+
+ _findEntityByIdInFolder: (folder, id) ->
+ for entity in folder.children or []
+ if entity.id == id
+ return entity
+ else if entity.children?
+ result = @_findEntityByIdInFolder(entity, id)
+ return result if result?
+
+ return null
+
forEachEntity: (callback) ->
@_forEachEntityInFolder(@$scope.rootFolder, callback)
_forEachEntityInFolder: (folder, callback) ->
- for entity in folder.children
+ for entity in folder.children or []
callback(entity)
if entity.children?
@_forEachEntityInFolder(entity, callback)
+ # forEachFolder: (callback) ->
+ # @forEachEntity (entity) ->
+ # if entity.type == "folder"
+ # callback(entity)
+
loadRootFolder: () ->
@$scope.rootFolder = @_parseFolder(@$scope.project.rootFolder[0])
_parseFolder: (rawFolder) ->
folder = {
name: rawFolder.name
- id: rawFolder.id
+ id: rawFolder._id
type: "folder"
children: []
}
@@ -32,18 +63,54 @@ define [
for doc in rawFolder.docs or []
folder.children.push {
name: doc.name
- type: "doc"
id: doc._id
+ type: "doc"
}
for file in rawFolder.fileRefs or []
folder.children.push {
name: file.name
- type: "file"
id: file._id
+ type: "file"
}
for childFolder in rawFolder.folders or []
folder.children.push @_parseFolder(childFolder)
return folder
+
+ getCurrentFolder: (startFolder = @$scope.rootFolder) ->
+ for entity in startFolder.children or []
+ # The 'current' folder is either the one selected, or
+ # the one containing the selected doc/file
+ if entity.selected
+ if entity.type == "folder"
+ return entity
+ else
+ return startFolder
+
+ if entity.type == "folder"
+ result = @getCurrentFolder(entity)
+ return result if result?
+
+ return null
+
+ createDocInCurrentFolder: (name, callback = (error, doc) ->) ->
+ # We'll wait for the socket.io 'createDoc' notification to actually
+ # add the doc for us.
+ parent_folder = @getCurrentFolder()
+ $.ajax {
+ url: "/project/#{@ide.project_id}/doc"
+ type: "POST"
+ contentType: "application/json; charset=utf-8"
+ data: JSON.stringify {
+ name: name,
+ parent_folder_id: parent_folder?.id
+ _csrf: window.csrfToken
+ }
+ dataType: "json"
+ success: (doc) ->
+ console.log "Got doc from POST", doc
+ callback(null, doc)
+ failure: (error) -> callback(error)
+ }
diff --git a/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeController.coffee b/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeController.coffee
new file mode 100644
index 0000000000..efdb769a77
--- /dev/null
+++ b/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeController.coffee
@@ -0,0 +1,33 @@
+define [
+ "base"
+], (App) ->
+ App.controller "FileTreeController", ["$scope", "$modal", ($scope, $modal) ->
+ $scope.openNewDocModal = () ->
+ $modal.open(
+ templateUrl: "newDocModalTemplate"
+ controller: "NewDocModalController"
+ )
+ ]
+
+ App.controller "NewDocModalController", [
+ "$scope", "ide", "$modalInstance", "$timeout",
+ ($scope, ide, $modalInstance, $timeout) ->
+ $scope.inputs =
+ name: "name.tex"
+ $scope.state =
+ inflight: false
+
+ $modalInstance.opened.then () ->
+ $timeout () ->
+ $scope.$broadcast "open"
+ , 700
+
+ $scope.create = () ->
+ $scope.state.inflight = true
+ ide.fileTreeManager.createDocInCurrentFolder $scope.inputs.name, (error, doc) ->
+ $scope.state.inflight = false
+ $modalInstance.close()
+
+ $scope.cancel = () ->
+ $modalInstance.dismiss('cancel')
+ ]
\ No newline at end of file
diff --git a/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeEntityController.coffee b/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeEntityController.coffee
index 21b5359658..b22a14c47a 100644
--- a/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeEntityController.coffee
+++ b/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeEntityController.coffee
@@ -1,7 +1,7 @@
define [
"base"
], (App) ->
- App.controller "FileTreeEntityController", ["$scope", ($scope) ->
+ App.controller "FileTreeEntityController", ["$scope", "ide", ($scope, ide) ->
$scope.select = ($event) ->
ide.fileTreeManager.forEachEntity (entity) ->
entity.selected = false
diff --git a/services/web/public/coffee/app/ide/services/ide.coffee b/services/web/public/coffee/app/ide/services/ide.coffee
new file mode 100644
index 0000000000..2a3b448370
--- /dev/null
+++ b/services/web/public/coffee/app/ide/services/ide.coffee
@@ -0,0 +1,7 @@
+define [
+ "base"
+], (App) ->
+ # We create and provide this as service so that we can access the global ide
+ # from within other parts of the angular app.
+ App.factory "ide", () ->
+ return {}
diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less
index 95d64c6e14..35cff2dc31 100644
--- a/services/web/public/stylesheets/app/editor.less
+++ b/services/web/public/stylesheets/app/editor.less
@@ -71,7 +71,14 @@
ul.file-tree-list {
font-size: 0.8rem;
- margin-top: (@line-height-computed / 4);
+ margin: 0;
+ padding: (@line-height-computed / 4) 0;
+ position: absolute;
+ top: 32px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ overflow-y: scroll;
ul {
margin-left: (@line-height-computed / 2);
diff --git a/services/web/test/UnitTests/coffee/Editor/EditorHttpControllerTests.coffee b/services/web/test/UnitTests/coffee/Editor/EditorHttpControllerTests.coffee
index 706476a6c4..3dc2d33a26 100644
--- a/services/web/test/UnitTests/coffee/Editor/EditorHttpControllerTests.coffee
+++ b/services/web/test/UnitTests/coffee/Editor/EditorHttpControllerTests.coffee
@@ -9,8 +9,10 @@ describe "EditorHttpController", ->
'../Project/ProjectEntityHandler' : @ProjectEntityHandler = {}
"./EditorRealTimeController": @EditorRealTimeController = {}
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
+ "./EditorController": @EditorController = {}
@project_id = "mock-project-id"
@doc_id = "mock-doc-id"
+ @parent_folder_id = "mock-folder-id"
@req = {}
@res =
send: sinon.stub()
@@ -44,3 +46,25 @@ describe "EditorHttpController", ->
@res.json
.calledWith(doc_id: @new_doc_id)
.should.equal true
+
+ describe "addDoc", ->
+ beforeEach ->
+ @doc = { "mock": "doc" }
+ @req.params =
+ Project_id: @project_id
+ @req.body =
+ name: @name = "doc-name"
+ parent_folder_id: @parent_folder_id
+ @EditorController.addDoc = sinon.stub().callsArgWith(4, null, @doc)
+ @EditorHttpController.addDoc @req, @res
+
+ it "should call EditorController.addDoc", ->
+ @EditorController.addDoc
+ .calledWith(@project_id, @parent_folder_id, @name, [])
+ .should.equal true
+
+ it "should send the doc back as JSON", ->
+ @res.json
+ .calledWith(@doc)
+ .should.equal true
+