Merge branch 'master-redesign' into master-redesign-templates-and-blog

This commit is contained in:
Henry Oswald 2014-07-04 13:06:47 +01:00
commit 86a9d08e5c
32 changed files with 446 additions and 201 deletions

View file

@ -33,6 +33,14 @@ block content
.alert.alert-warning.small(ng-if="connection.reconnecting") .alert.alert-warning.small(ng-if="connection.reconnecting")
strong Reconnecting... strong Reconnecting...
.div(ng-controller="SavingNotificationController")
.alert.alert-warning.small(
ng-repeat="(doc_id, state) in docSavingStatus"
ng-if="state.unsavedSeconds > 3"
)
| Saving {{ state.doc.name }}... ({{ state.unsavedSeconds }} seconds of unsaved changes)
include ./editor/left-menu include ./editor/left-menu
#chat-wrapper( #chat-wrapper(
@ -56,11 +64,23 @@ block content
.ui-layout-center .ui-layout-center
include ./editor/editor include ./editor/editor
include ./editor/binary-file
include ./editor/track-changes include ./editor/track-changes
.ui-layout-east .ui-layout-east
include ./editor/chat include ./editor/chat
script(type="text/ng-template", id="genericMessageModalTemplate")
.modal-header
button.close(
type="button"
data-dismiss="modal"
ng-click="done()"
) ×
h3 {{ title }}
.modal-body {{ message }}
.modal-footer
button.btn.btn-info(ng-click="done()") OK
script(src='/socket.io/socket.io.js') script(src='/socket.io/socket.io.js')

View file

@ -0,0 +1,19 @@
div.binary-file.full-size(
ng-controller="BinaryFileController"
ng-show="ui.view == 'file'"
ng-if="openFile"
)
img(
ng-src="/project/{{ project_id }}/file/{{ openFile.id }}"
ng-if="['png', 'jpg', 'jpeg', 'gif'].indexOf(extension(openFile)) > -1"
)
img(
ng-src="/project/{{ project_id }}/file/{{ openFile.id }}?format=png"
ng-if="['pdf', 'eps'].indexOf(extension(openFile)) > -1"
)
p.no-preview(
ng-if="['png', 'jpg', 'jpeg', 'gif', 'pdf', 'eps'].indexOf(extension(openFile)) == -1"
) Sorry, no preview is available.
a.btn.btn-info(
ng-href="/project/{{ project_id }}/file/{{ openFile.id }}"
) Download {{ openFile.name }}

View file

@ -25,7 +25,8 @@ div.full-size(
cursor-position="editor.cursorPosition", cursor-position="editor.cursorPosition",
goto-line="editor.gotoLine", goto-line="editor.gotoLine",
resize-on="layout:main:resize,layout:pdf:resize", resize-on="layout:main:resize,layout:pdf:resize",
annotations="pdf.logEntryAnnotations[editor.open_doc_id]" annotations="pdf.logEntryAnnotations[editor.open_doc_id]",
read-only="!permissions.write"
) )
.ui-layout-east .ui-layout-east

View file

@ -1,5 +1,5 @@
aside#file-tree(ng-controller="FileTreeController") aside#file-tree(ng-controller="FileTreeController")
.toolbar.toolbar-small .toolbar.toolbar-small(ng-if="permissions.write")
a( a(
href, href,
ng-click="openNewDocModal()", ng-click="openNewDocModal()",
@ -39,10 +39,19 @@ aside#file-tree(ng-controller="FileTreeController")
) )
i.fa.fa-trash-o i.fa.fa-trash-o
.file-tree-inner .file-tree-inner(
ul.list-unstyled.file-tree-list ng-if="rootFolder",
ng-controller="FileTreeRootFolderController",
ng-class="{ 'no-toolbar': !permissions.write }"
)
ul.list-unstyled.file-tree-list(
droppable="permissions.write"
accept=".entity-name"
on-drop-callback="onDrop"
)
file-entity( file-entity(
entity="entity", entity="entity",
permissions="permissions",
ng-repeat="entity in rootFolder.children | orderBy:[orderByFoldersFirst, 'name']" ng-repeat="entity in rootFolder.children | orderBy:[orderByFoldersFirst, 'name']"
) )
@ -72,11 +81,12 @@ script(type='text/ng-template', id='entityListItemTemplate')
.entity(ng-if="entity.type != 'folder'") .entity(ng-if="entity.type != 'folder'")
.entity-name( .entity-name(
ng-click="select()" ng-click="select()"
ng-dblclick="startRenaming()" ng-dblclick="permissions.write && startRenaming()"
draggable draggable="permissions.write"
context-menu context-menu
data-target="context-menu-{{ entity.id }}" data-target="context-menu-{{ entity.id }}"
context-menu-container="body" context-menu-container="body"
context-menu-disabled="!permissions.write"
) )
//- Just a spacer to align with folders //- Just a spacer to align with folders
i.fa.fa-fw.toggle(ng-if="entity.type != 'folder'") i.fa.fa-fw.toggle(ng-if="entity.type != 'folder'")
@ -88,6 +98,7 @@ script(type='text/ng-template', id='entityListItemTemplate')
ng-hide="entity.renaming" ng-hide="entity.renaming"
) {{ entity.name }} ) {{ entity.name }}
input( input(
ng-if="permissions.write",
ng-show="entity.renaming", ng-show="entity.renaming",
ng-model="inputs.name", ng-model="inputs.name",
ng-blur="finishRenaming()", ng-blur="finishRenaming()",
@ -95,7 +106,10 @@ script(type='text/ng-template', id='entityListItemTemplate')
on-enter="finishRenaming()" on-enter="finishRenaming()"
) )
span.dropdown(ng-show="entity.selected") span.dropdown(
ng-show="entity.selected",
ng-if="permissions.write"
)
a.dropdown-toggle(href) a.dropdown-toggle(href)
i.fa.fa-chevron-down i.fa.fa-chevron-down
@ -113,7 +127,10 @@ script(type='text/ng-template', id='entityListItemTemplate')
ng-click="openDeleteModal()" ng-click="openDeleteModal()"
) Delete ) Delete
div.dropdown.context-menu(id="context-menu-{{ entity.id }}") div.dropdown.context-menu(
id="context-menu-{{ entity.id }}",
ng-if="permissions.write"
)
ul.dropdown-menu ul.dropdown-menu
li li
a( a(
@ -134,9 +151,9 @@ script(type='text/ng-template', id='entityListItemTemplate')
.entity(ng-if="entity.type == 'folder'", ng-controller="FileTreeFolderController") .entity(ng-if="entity.type == 'folder'", ng-controller="FileTreeFolderController")
.entity-name( .entity-name(
ng-click="select()" ng-click="select()"
ng-dblclick="startRenaming()" ng-dblclick="permissions.write && startRenaming()"
draggable draggable="permissions.write"
droppable droppable="permissions.write"
accept=".entity-name" accept=".entity-name"
on-drop-callback="onDrop" on-drop-callback="onDrop"
) )
@ -144,6 +161,7 @@ script(type='text/ng-template', id='entityListItemTemplate')
context-menu context-menu
data-target="context-menu-{{ entity.id }}" data-target="context-menu-{{ entity.id }}"
context-menu-container="body" context-menu-container="body"
context-menu-disabled="!permissions.write"
) )
i.fa.fa-fw.toggle( i.fa.fa-fw.toggle(
ng-if="entity.type == 'folder'" ng-if="entity.type == 'folder'"
@ -164,6 +182,7 @@ script(type='text/ng-template', id='entityListItemTemplate')
ng-hide="entity.renaming" ng-hide="entity.renaming"
) {{ entity.name }} ) {{ entity.name }}
input( input(
ng-if="permissions.write",
ng-show="entity.renaming", ng-show="entity.renaming",
ng-model="inputs.name", ng-model="inputs.name",
ng-blur="finishRenaming()", ng-blur="finishRenaming()",
@ -171,7 +190,10 @@ script(type='text/ng-template', id='entityListItemTemplate')
on-enter="finishRenaming()" on-enter="finishRenaming()"
) )
span.dropdown(ng-show="entity.selected") span.dropdown(
ng-if="permissions.write"
ng-show="entity.selected"
)
a.dropdown-toggle(href) a.dropdown-toggle(href)
i.fa.fa-chevron-down i.fa.fa-chevron-down
@ -208,7 +230,10 @@ script(type='text/ng-template', id='entityListItemTemplate')
ng-click="openUploadFileModal()" ng-click="openUploadFileModal()"
) Upload File ) Upload File
.dropdown.context-menu(id="context-menu-{{ entity.id }}") .dropdown.context-menu(
ng-if="permissions.write"
id="context-menu-{{ entity.id }}"
)
ul.dropdown-menu ul.dropdown-menu
li li
a( a(
@ -250,12 +275,13 @@ script(type='text/ng-template', id='entityListItemTemplate')
ul.list-unstyled( ul.list-unstyled(
ng-if="entity.type == 'folder'" ng-if="entity.type == 'folder'"
ng-show="expanded" ng-show="expanded"
droppable droppable="permissions.write"
accept=".entity-name" accept=".entity-name"
on-drop-callback="onDrop" on-drop-callback="onDrop"
) )
file-entity( file-entity(
entity="child", entity="child",
permissions="permissions",
ng-repeat="child in entity.children | orderBy:[orderByFoldersFirst, 'name']" ng-repeat="child in entity.children | orderBy:[orderByFoldersFirst, 'name']"
) )

View file

@ -7,14 +7,35 @@ header.toolbar.toolbar-header(ng-cloak, ng-hide="state.loading")
) )
i.fa.fa-fw.fa-bars i.fa.fa-fw.fa-bars
span.name {{ project.name }} .toolbar-center.project-name(ng-controller="ProjectNameController")
span.name(
ng-dblclick="startRenaming()",
ng-show="!state.renaming"
) {{ project.name }}
a(href='#', data-toggle="tooltip", title="Rename") input.form-control(
type="text"
ng-model="inputs.name",
ng-show="state.renaming",
on-enter="finishRenaming()",
ng-blur="finishRenaming()",
select-name-when="state.renaming"
)
a.rename(
href='#',
tooltip-placement="bottom",
tooltip="Rename",
tooltip-append-to-body="true",
ng-click="startRenaming()",
ng-show="!state.renaming"
)
i.fa.fa-pencil i.fa.fa-pencil
.toolbar-right .toolbar-right
a.btn.btn-full-height( a.btn.btn-full-height(
href, href,
ng-if="permissions.admin",
tooltip="Share", tooltip="Share",
tooltip-placement="bottom", tooltip-placement="bottom",
ng-click="openShareProjectModal()", ng-click="openShareProjectModal()",

View file

@ -9,7 +9,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
.modal-body.modal-body-share .modal-body.modal-body-share
.container-fluid .container-fluid
.row.public-access-level(ng-show="project.publicAccesLevel == 'private'") .row.public-access-level(ng-show="project.publicAccesLevel == 'private'")
.col-md-12.text-center .col-xs-12.text-center
| This project is private and can only be accessed by the people below. | This project is private and can only be accessed by the people below.
|    |   
a( a(
@ -17,7 +17,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
ng-click="openMakePublicModal()" ng-click="openMakePublicModal()"
) Make Public ) Make Public
.row.public-access-level(ng-show="project.publicAccesLevel != 'private'") .row.public-access-level(ng-show="project.publicAccesLevel != 'private'")
.col-md-12.text-center .col-xs-12.text-center
strong(ng-if="project.publicAccesLevel == 'readAndWrite'") This project is public and can be edited by anyone with the URL. strong(ng-if="project.publicAccesLevel == 'readAndWrite'") This project is public and can be edited by anyone with the URL.
strong(ng-if="project.publicAccesLevel == 'readOnly'") This project is public and can be viewed by anyone with the URL. strong(ng-if="project.publicAccesLevel == 'readOnly'") This project is public and can be viewed by anyone with the URL.
|    |   
@ -26,16 +26,16 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
ng-click="openMakePrivateModal()" ng-click="openMakePrivateModal()"
) Make Private ) Make Private
.row.project-member .row.project-member
.col-md-8 {{ project.owner.email }} .col-xs-8 {{ project.owner.email }}
.text-right( .text-right(
ng-class="{'col-md-3': project.members.length > 0, 'col-md-4': project.members.length == 0}" ng-class="{'col-xs-3': project.members.length > 0, 'col-xs-4': project.members.length == 0}"
) Owner ) Owner
.row.project-member(ng-repeat="member in project.members") .row.project-member(ng-repeat="member in project.members")
.col-md-8 {{ member.email }} .col-xs-8 {{ member.email }}
.col-md-3.text-right .col-xs-3.text-right
span(ng-show="member.privileges == 'readAndWrite'") Can Edit span(ng-show="member.privileges == 'readAndWrite'") Can Edit
span(ng-show="member.privileges == 'readOnly'") Read Only span(ng-show="member.privileges == 'readOnly'") Read Only
.col-md-1 .col-xs-1
a( a(
href href
tooltip="Remove collaborator" tooltip="Remove collaborator"

View file

@ -1,4 +1,17 @@
div#trackChanges(ng-show="ui.view == 'track-changes'") div#trackChanges(ng-show="ui.view == 'track-changes'")
.upgrade-prompt(ng-show="!project.features.versioning")
.message(ng-show="project.owner._id == user.id")
p You need to upgrade your account to use the History feature.
p
a.btn.btn-info(
href="/user/subscription/plans"
target="_blank"
ng-click="startedFreeTrial = true"
) Start Free Trial
p.small(ng-show="startedFreeTrial") Please refresh the page after starting your free trial.
.message(ng-show="project.owner._id != user.id")
p Please ask the project owner to upgrade to use the History feature.
aside.change-list( aside.change-list(
ng-controller="TrackChangesListController" ng-controller="TrackChangesListController"
infinite-scroll="loadMore()" infinite-scroll="loadMore()"
@ -57,8 +70,11 @@ div#trackChanges(ng-show="ui.view == 'track-changes'")
div.users div.users
div.user(ng-repeat="update_user in update.meta.users") div.user(ng-repeat="update_user in update.meta.users")
.color-square(ng-style="{'background-color': 'hsl({{ update_user.hue }}, 100%, 50%)'}") .color-square(ng-style="{'background-color': 'hsl({{ update_user.hue }}, 100%, 50%)'}")
span(ng-if="update_user.id != user.id") {{user.first_name}} {{user.last_name}} span(ng-if="update_user.id != user.id") {{update_user.first_name}} {{update_user.last_name}}
span(ng-if="update_user.id == user.id") You span(ng-if="update_user.id == user.id") You
div.user(ng-if="update.meta.users.length == 0")
.color-square(style="background-color: hsl(100, 100%, 50%)")
span Anonymous
.loading(ng-show="trackChanges.loading") .loading(ng-show="trackChanges.loading")
i.fa.fa-spin.fa-refresh i.fa.fa-spin.fa-refresh

View file

@ -5,7 +5,9 @@ define [
"ide/editor/EditorManager" "ide/editor/EditorManager"
"ide/online-users/OnlineUsersManager" "ide/online-users/OnlineUsersManager"
"ide/track-changes/TrackChangesManager" "ide/track-changes/TrackChangesManager"
"ide/permissions/PermissionsManager"
"ide/pdf/PdfManager" "ide/pdf/PdfManager"
"ide/binary-files/BinaryFilesManager"
"ide/settings/index" "ide/settings/index"
"ide/share/index" "ide/share/index"
"ide/chat/index" "ide/chat/index"
@ -22,7 +24,9 @@ define [
EditorManager EditorManager
OnlineUsersManager OnlineUsersManager
TrackChangesManager TrackChangesManager
PermissionsManager
PdfManager PdfManager
BinaryFilesManager
) -> ) ->
App.controller "IdeController", ["$scope", "$timeout", "ide", ($scope, $timeout, ide) -> App.controller "IdeController", ["$scope", "$timeout", "ide", ($scope, $timeout, ide) ->
# Don't freak out if we're already in an apply callback # Don't freak out if we're already in an apply callback
@ -59,6 +63,8 @@ define [
ide.onlineUsersManager = new OnlineUsersManager(ide, $scope) ide.onlineUsersManager = new OnlineUsersManager(ide, $scope)
ide.trackChangesManager = new TrackChangesManager(ide, $scope) ide.trackChangesManager = new TrackChangesManager(ide, $scope)
ide.pdfManager = new PdfManager(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"]) angular.bootstrap(document.body, ["SharelatexApp"])

View file

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

View file

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

View file

@ -57,6 +57,7 @@ define [], () ->
@$scope.$apply () => @$scope.$apply () =>
@$scope.protocolVersion = protocolVersion @$scope.protocolVersion = protocolVersion
@$scope.project = project @$scope.project = project
@$scope.permissionsLevel = permissionsLevel
@$scope.state.load_progress = 100 @$scope.state.load_progress = 100
@$scope.state.loading = false @$scope.state.loading = false
@$scope.$emit "project:joined" @$scope.$emit "project:joined"
@ -79,6 +80,7 @@ define [], () ->
countdown = 3 + Math.floor(Math.random() * 7) countdown = 3 + Math.floor(Math.random() * 7)
@$scope.$apply () => @$scope.$apply () =>
@$scope.connection.reconnecting = false
@$scope.connection.reconnection_countdown = countdown @$scope.connection.reconnection_countdown = countdown
setTimeout(=> setTimeout(=>

View file

@ -149,6 +149,10 @@ define [
@ide.connectionManager.disconnect() @ide.connectionManager.disconnect()
return return
if Math.random() < (@ide.ignoreRate or 0)
console.log "Simulating lost update"
return
if update?.doc == @doc_id and @doc? if update?.doc == @doc_id and @doc?
@doc.processUpdateFromServer update @doc.processUpdateFromServer update

View file

@ -1,6 +1,7 @@
define [ define [
"ide/editor/Document" "ide/editor/Document"
"ide/editor/directives/aceEditor" "ide/editor/directives/aceEditor"
"ide/editor/controllers/SavingNotificationController"
], (Document) -> ], (Document) ->
class EditorManager class EditorManager
constructor: (@ide, @$scope) -> constructor: (@ide, @$scope) ->
@ -14,7 +15,7 @@ define [
} }
@$scope.$on "entity:selected", (event, entity) => @$scope.$on "entity:selected", (event, entity) =>
if (@$scope.ui.view == "editor" and entity.type == "doc") if (@$scope.ui.view != "track-changes" and entity.type == "doc")
@openDoc(entity) @openDoc(entity)
initialized = false initialized = false
@ -77,25 +78,17 @@ define [
_bindToDocumentEvents: (doc, sharejs_doc) -> _bindToDocumentEvents: (doc, sharejs_doc) ->
sharejs_doc.on "error", (error) => sharejs_doc.on "error", (error) =>
console.error "DOC ERROR", error
@openDoc(doc, forceReopen: true) @openDoc(doc, forceReopen: true)
@ide.showGenericMessageModal(
#TODO!!! "Out of sync"
# Modal.createModal "Sorry, this file has gone out of sync and we need to do a full refresh. Please let us know if this happens frequently."
# 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"
# ]
sharejs_doc.on "externalUpdate", () => sharejs_doc.on "externalUpdate", () =>
#TODO!!! @ide.showGenericMessageModal(
# Modal.createModal "Document Updated Externally"
# title: "Document Updated Externally" "This document was just updated externally. Any recent changes you have made may have been overwritten. To see previous versions please look in the history."
# 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) -> _unbindFromDocumentEvents: (document) ->
document.off() document.off()

View file

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

View file

@ -204,16 +204,16 @@ define [
_getColorScheme: (hue) -> _getColorScheme: (hue) ->
if @_isDarkTheme() if @_isDarkTheme()
return { return {
cursor: "hsl(#{hue}, 100%, 50%)" cursor: "hsl(#{hue}, 70%, 50%)"
labelBackgroundColor: "hsl(#{hue}, 100%, 50%)" labelBackgroundColor: "hsl(#{hue}, 70%, 50%)"
highlightBackgroundColor: "hsl(#{hue}, 100%, 28%);" highlightBackgroundColor: "hsl(#{hue}, 100%, 28%);"
strikeThroughBackgroundColor: "hsl(#{hue}, 100%, 20%);" strikeThroughBackgroundColor: "hsl(#{hue}, 100%, 20%);"
strikeThroughForegroundColor: "hsl(#{hue}, 100%, 60%);" strikeThroughForegroundColor: "hsl(#{hue}, 100%, 60%);"
} }
else else
return { return {
cursor: "hsl(#{hue}, 100%, 50%)" cursor: "hsl(#{hue}, 70%, 50%)"
labelBackgroundColor: "hsl(#{hue}, 100%, 50%)" labelBackgroundColor: "hsl(#{hue}, 70%, 50%)"
highlightBackgroundColor: "hsl(#{hue}, 70%, 85%);" highlightBackgroundColor: "hsl(#{hue}, 70%, 85%);"
strikeThroughBackgroundColor: "hsl(#{hue}, 70%, 95%);" strikeThroughBackgroundColor: "hsl(#{hue}, 70%, 95%);"
strikeThroughForegroundColor: "hsl(#{hue}, 70%, 40%);" strikeThroughForegroundColor: "hsl(#{hue}, 70%, 40%);"

View file

@ -5,6 +5,7 @@ define [
"ide/file-tree/controllers/FileTreeController" "ide/file-tree/controllers/FileTreeController"
"ide/file-tree/controllers/FileTreeEntityController" "ide/file-tree/controllers/FileTreeEntityController"
"ide/file-tree/controllers/FileTreeFolderController" "ide/file-tree/controllers/FileTreeFolderController"
"ide/file-tree/controllers/FileTreeRootFolderController"
], () -> ], () ->
class FileTreeManager class FileTreeManager
constructor: (@ide, @$scope) -> constructor: (@ide, @$scope) ->
@ -59,6 +60,7 @@ define [
@ide.socket.on "reciveEntityMove", (entity_id, folder_id) => @ide.socket.on "reciveEntityMove", (entity_id, folder_id) =>
entity = @findEntityById(entity_id) entity = @findEntityById(entity_id)
folder = @findEntityById(folder_id) folder = @findEntityById(folder_id)
console.log "Got recive ENTITY", entity_id, folder_id, entity, folder
@$scope.$apply () => @$scope.$apply () =>
@_moveEntityInScope(entity, folder) @_moveEntityInScope(entity, folder)
@ -68,7 +70,15 @@ define [
entity.selected = false entity.selected = false
entity.selected = true entity.selected = true
findSelectedEntity: () ->
selected = null
@forEachEntity (entity) ->
selected = entity if entity.selected
return selected
findEntityById: (id, options = {}) -> findEntityById: (id, options = {}) ->
return @$scope.rootFolder if @$scope.rootFolder.id == id
entity = @_findEntityByIdInFolder @$scope.rootFolder, id entity = @_findEntityByIdInFolder @$scope.rootFolder, id
return entity if entity? return entity if entity?
@ -250,7 +260,7 @@ define [
_csrf: window.csrfToken _csrf: window.csrfToken
} }
_deleteEntityFromScope: (entity) -> _deleteEntityFromScope: (entity, options = { moveToDeleted: true }) ->
parent_folder = null parent_folder = null
@forEachEntity (possible_entity, folder) -> @forEachEntity (possible_entity, folder) ->
if possible_entity == entity if possible_entity == entity
@ -261,11 +271,11 @@ define [
if index > -1 if index > -1
parent_folder.children.splice(index, 1) parent_folder.children.splice(index, 1)
if entity.type == "doc" if entity.type == "doc" and options.moveToDeleted
entity.deleted = true entity.deleted = true
@$scope.deletedDocs.push entity @$scope.deletedDocs.push entity
_moveEntityInScope: (entity, parent_folder) -> _moveEntityInScope: (entity, parent_folder) ->
return if entity in parent_folder.children return if entity in parent_folder.children
@_deleteEntityFromScope(entity) @_deleteEntityFromScope(entity, moveToDeleted: false)
parent_folder.children.push(entity) parent_folder.children.push(entity)

View file

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

View file

@ -4,6 +4,8 @@ define [
App.directive "draggable", () -> App.directive "draggable", () ->
return { return {
link: (scope, element, attrs) -> link: (scope, element, attrs) ->
scope.$watch attrs.draggable, (draggable) ->
if draggable
element.draggable element.draggable
delay: 250 delay: 250
opacity: 0.7 opacity: 0.7

View file

@ -7,6 +7,8 @@ define [
onDropCallback: "=" onDropCallback: "="
} }
link: (scope, element, attrs) -> link: (scope, element, attrs) ->
scope.$watch attrs.droppable, (droppable) ->
if droppable
element.droppable element.droppable
greedy: true greedy: true
hoverClass: "droppable-hover" hoverClass: "droppable-hover"

View file

@ -6,10 +6,18 @@ define [
restrict: "E" restrict: "E"
scope: { scope: {
entity: "=" entity: "="
permissions: "="
} }
templateUrl: "entityListItemTemplate" templateUrl: "entityListItemTemplate"
compile: (element) -> compile: (element) ->
RecursionHelper.compile element, (scope, element, attrs, ctrl) -> RecursionHelper.compile element, (scope, element, attrs, ctrl) ->
# Link function here if needed # Don't freak out if we're already in an apply callback
scope.$originalApply = scope.$apply
scope.$apply = (fn = () ->) ->
phase = @$root.$$phase
if (phase == '$apply' || phase == '$digest')
fn()
else
@$originalApply(fn);
} }
] ]

View file

@ -56,7 +56,7 @@ define [
OWN_HUE: 200 # We will always appear as this color to ourselves OWN_HUE: 200 # We will always appear as this color to ourselves
ANONYMOUS_HUE: 100 ANONYMOUS_HUE: 100
getHueForUserId: (user_id) -> getHueForUserId: (user_id) ->
if !user_id? if !user_id? or user_id == "anonymous-user"
return @ANONYMOUS_HUE return @ANONYMOUS_HUE
if window.user.id == user_id if window.user.id == user_id

View file

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

View file

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

View file

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

View file

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

View file

@ -9,10 +9,9 @@ define [
@$scope.toggleTrackChanges = () => @$scope.toggleTrackChanges = () =>
if @$scope.ui.view == "track-changes" if @$scope.ui.view == "track-changes"
@$scope.ui.view = "editor" @hide()
else else
@$scope.ui.view = "track-changes" @show()
@onShow()
@$scope.$watch "trackChanges.selection.updates", (updates) => @$scope.$watch "trackChanges.selection.updates", (updates) =>
if updates? and updates.length > 0 if updates? and updates.length > 0
@ -24,11 +23,14 @@ define [
@$scope.trackChanges.selection.doc = entity @$scope.trackChanges.selection.doc = entity
@reloadDiff() @reloadDiff()
onShow: () -> show: () ->
@$scope.ui.view = "track-changes"
@reset() @reset()
# @fetchNextBatchOfChanges()
# .success () => hide: () ->
# @autoSelectRecentUpdates() @$scope.ui.view = "editor"
# Make sure we run the 'open' logic for whatever is currently selected
@$scope.$emit "entity:selected", @ide.fileTreeManager.findSelectedEntity()
reset: () -> reset: () ->
@$scope.trackChanges = { @$scope.trackChanges = {
@ -157,21 +159,24 @@ define [
} }
if entry.i? or entry.d? if entry.i? or entry.d?
if entry.meta.user?
name = "#{entry.meta.user.first_name} #{entry.meta.user.last_name}" name = "#{entry.meta.user.first_name} #{entry.meta.user.last_name}"
if entry.meta.user.id == @$scope.user.id else
name = "Anonymous"
if entry.meta.user?.id == @$scope.user.id
name = "you" name = "you"
date = moment(entry.meta.end_ts).format("Do MMM YYYY, h:mm a") date = moment(entry.meta.end_ts).format("Do MMM YYYY, h:mm a")
if entry.i? if entry.i?
highlights.push { highlights.push {
label: "Added by #{name} on #{date}" label: "Added by #{name} on #{date}"
highlight: range highlight: range
hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user.id) hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user?.id)
} }
else if entry.d? else if entry.d?
highlights.push { highlights.push {
label: "Deleted by #{name} on #{date}" label: "Deleted by #{name} on #{date}"
strikeThrough: range strikeThrough: range
hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user.id) hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user?.id)
} }
return {text, highlights} return {text, highlights}

View file

@ -5,6 +5,7 @@
@import "./editor/pdf.less"; @import "./editor/pdf.less";
@import "./editor/share.less"; @import "./editor/share.less";
@import "./editor/chat.less"; @import "./editor/chat.less";
@import "./editor/binary-file.less";
.full-size { .full-size {
position: absolute; position: absolute;
@ -17,10 +18,15 @@
.global-alerts { .global-alerts {
position: absolute; position: absolute;
z-index: 20; z-index: 20;
top: (@line-height-computed / 4); top: 2px;
width: 400px; width: 400px;
left: 50%; left: 50%;
margin-left: -200px; margin-left: -200px;
.alert {
padding: (@line-height-computed / 4);
font-size: 14px;
margin-bottom: (@line-height-computed / 4);
}
} }
#chat-wrapper { #chat-wrapper {
@ -57,6 +63,35 @@
} }
} }
.project-name {
.name {
display: inline-block;
max-width: 250px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: top;
padding: 6px;
color: @gray;
font-weight: 700;
white-space: nowrap;
}
input {
height: 30px;
margin-top: 4px;
text-align: center;
padding: 6px;
font-weight: 700;
}
a.rename {
visibility: hidden;
}
&:hover {
a.rename {
visibility: visible;
}
}
}
// The internal components of the aceEditor directive // The internal components of the aceEditor directive
.ace-editor-wrapper { .ace-editor-wrapper {
.full-size; .full-size;

View file

@ -0,0 +1,20 @@
.binary-file {
padding: @line-height-computed / 2;
background-color: @gray-lightest;
text-align: center;
img {
max-width: 100%;
max-height: 90%;
display: block;
margin: auto;
margin-bottom: @line-height-computed / 2;
border: 1px solid @gray;
.box-shadow(0 2px 3px @gray;);
background-color: white;
}
p.no-preview {
font-size: 24px;
color: @gray;
}
}

View file

@ -9,6 +9,10 @@ aside#file-tree {
left: 0; left: 0;
right: 0; right: 0;
overflow-y: auto; overflow-y: auto;
&.no-toolbar {
top: 0;
}
} }
h3 { h3 {

View file

@ -73,6 +73,7 @@
} }
} }
.toolbar {
.log-btn { .log-btn {
position: relative; position: relative;
.label { .label {
@ -86,6 +87,13 @@
.label { .label {
display: none; display: none;
} }
color: white;
background-color: @link-color;
.box-shadow(inset 0 3px 5px rgba(0, 0, 0, 0.225));
&:hover {
color: white;
}
}
} }
} }

View file

@ -13,11 +13,6 @@
color: @gray-dark; color: @gray-dark;
text-decoration: none; text-decoration: none;
} }
&.active, &:active {
color: white;
background-color: @link-color;
.box-shadow(inset 0 3px 5px rgba(0, 0, 0, 0.225));
}
} }
.btn-full-height { .btn-full-height {
@ -47,6 +42,17 @@
} }
} }
.toolbar-center {
width: 300px;
position: absolute;
top: 0;
left: 50%;
margin-left: -150px;
text-align: center;
text-overflow: ellipsis;
overflow: hidden;
}
&.toolbar-header { &.toolbar-header {
box-shadow: 0 0 2px #ccc; box-shadow: 0 0 2px #ccc;
position: absolute; position: absolute;

View file

@ -9,117 +9,25 @@
@range-bar-selected-offset: 14px; @range-bar-selected-offset: 14px;
#trackChanges { #trackChanges {
// .track-changes-diff { .upgrade-prompt {
// position: absolute; position: absolute;
// right: @changesListWidth + 1px; top: 0;
// left: 0; bottom: 0;
// top: 0; left: 0;
// bottom: 0; right: 0;
// height: 100%; z-index: 100;
// .ace_editor { background-color: rgba(128,128,128,0.4);
// position: absolute; .message {
// top: 42px; margin: auto;
// left: 0; margin-top: 100px;
// right: 0; padding: 10px 10px 14px 10px;
// bottom: 0; width: 500px;
// .ace_active-line, .ace_cursor-layer, .ace_gutter-active-line { font-weight: bold;
// display: none; text-align: center;
// } background-color: white;
// } border-radius: 8px;
// .track-changes-diff-toolbar { }
// position: absolute; }
// top: 0;
// left: 0;
// right: -1px;
// height: 32px;
// padding: 5px 5px 5px 5px;
// margin: 0;
// background-color: #282828;
// color: white;
// border-right: 1px solid white;
// .number-of-changes, .restore {
// position: absolute;
// }
// .number-of-changes {
// left: 10px;
// bottom: 7px;
// }
// .restore {
// right: 10px;
// bottom: 5px;
// padding: 3px 9px;
// }
// }
// }
// .track-changes-upgrade-control, .track-changes-upgrade-popup {
// position: absolute;
// top: 0;
// bottom: 0;
// left: 0;
// right: 0;
// z-index: 100;
// }
// .track-changes-upgrade-popup {
// background-color: rgba(128,128,128,0.4);
// .message {
// margin: auto;
// margin-top: 200px;
// padding: 10px 10px 14px 10px;
// width: 400px;
// font-weight: bold;
// text-align: center;
// background-color: white;
// .border-radius(8px);
// }
// }
// .track-changes-upgrade-control {
// background-color: #eeeeee;
// text-align: center;
// .message {
// font-size: 18px;
// margin: 12px;
// margin-top: 36px;
// }
// }
// .deleted-background,
// .deleted-foreground,
// .inserted-background,
// .name-marker,
// .changes-before,
// .changes-after {
// position: absolute;
// z-index: 2;
// }
// .name-marker {
// font-size: 0.8em;
// padding: 2px 6px;
// .border-radius(3px 3px 3px 3px);
// position: absolute;
// border: 1px solid #999;
// left: 0;
// white-space: pre;
// }
// .changes-before {
// top: 6px;
// right: 6px;
// }
// .changes-after {
// bottom: 6px;
// right: 6px;
// }
// .changes-before, .changes-after {
// padding: 4px 8px;
// background-color: #eee;
// border: 1px solid #999;
// .border-radius(3px);
// }
.diff { .diff {
margin-right: @changesListWidth; margin-right: @changesListWidth;