mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge branch 'master-redesign' into master-redesign-templates-and-blog
Conflicts: app/views/project/editor.jade
This commit is contained in:
commit
000be22f16
34 changed files with 1000 additions and 173 deletions
|
@ -26,7 +26,8 @@ module.exports =
|
|||
|
||||
addUserToProject: (project_id, email, privilegeLevel, callback)->
|
||||
emails = mimelib.parseAddresses(email)
|
||||
email = emails[0].address?.toLowerCase()
|
||||
email = emails[0]?.address?.toLowerCase()
|
||||
return callback(new Error("no valid email provided")) if !email?
|
||||
self = @
|
||||
User.findOne {'email':email}, (err, user)->
|
||||
async.waterfall [
|
||||
|
|
|
@ -138,7 +138,7 @@ module.exports = EditorController =
|
|||
CollaboratorsHandler.addUserToProject project_id, email, privileges, (err, user)=>
|
||||
ProjectEntityHandler.flushProjectToThirdPartyDataStore project_id, "", ->
|
||||
EditorRealTimeController.emitToRoom(project_id, 'userAddedToProject', user, privileges)
|
||||
callback null, true
|
||||
callback null, ProjectEditorHandler.buildUserModelView(user, privileges)
|
||||
|
||||
removeUserFromProject: (project_id, user_id, callback)->
|
||||
CollaboratorsHandler.removeUserFromProject project_id, user_id, =>
|
||||
|
|
|
@ -37,6 +37,8 @@ block content
|
|||
|
||||
include ./editor/header
|
||||
|
||||
include ./editor/share
|
||||
|
||||
#ide-body(ng-cloak, layout="main", ng-hide="state.loading")
|
||||
.ui-layout-west
|
||||
include ./editor/file-tree
|
||||
|
@ -45,34 +47,6 @@ block content
|
|||
include ./editor/editor
|
||||
include ./editor/track-changes
|
||||
|
||||
//- #loadingScreen
|
||||
//- h3 Loading...
|
||||
//- p#loadingMessage Loading editor
|
||||
|
||||
//- #errorMessages
|
||||
//- #connectionLostMessage(style="display: none;")
|
||||
//- | Lost connection.
|
||||
//- span#trying-reconnect
|
||||
//- | Reconnecting in
|
||||
//- span#reconnection-countdown ?
|
||||
//- | seconds.
|
||||
//- a(href='#')#try-reconnect-now Try now.
|
||||
//- span#reconnecting
|
||||
//- | Reconnecting...
|
||||
|
||||
//- #savingProblems(style="display: none")
|
||||
//- | Saving...
|
||||
|
||||
//- div#toolbar.sidebar-navigation
|
||||
//- ul#tabs
|
||||
//- #toolbar-footer
|
||||
|
||||
//- #tab-content.tab-content
|
||||
|
||||
//- include ../templates
|
||||
//- include ../templates/dropbox
|
||||
|
||||
|
||||
script(src='/socket.io/socket.io.js')
|
||||
|
||||
script(type='text/javascript').
|
||||
|
|
|
@ -16,13 +16,35 @@ div.full-size(
|
|||
font-size="settings.fontSize",
|
||||
auto-complete="settings.autoComplete",
|
||||
spell-check-language="project.spellCheckLanguage",
|
||||
annotations="onlineUserCursorAnnotations[editor.open_doc_id]"
|
||||
highlights="onlineUserCursorHighlights[editor.open_doc_id]"
|
||||
show-print-margin="false",
|
||||
sharejs-doc="editor.sharejs_doc",
|
||||
last-updated="editor.last_updated",
|
||||
cursor-position="editor.cursorPosition",
|
||||
resize-on="layout:main:resize,layout:pdf:resize"
|
||||
goto-line="editor.gotoLine",
|
||||
resize-on="layout:main:resize,layout:pdf:resize",
|
||||
annotations="pdf.logEntryAnnotations[editor.open_doc_id]"
|
||||
)
|
||||
|
||||
.ui-layout-east
|
||||
include ./pdf
|
||||
include ./pdf
|
||||
|
||||
.ui-layout-resizer-controls.synctex-controls(
|
||||
ng-show="!!pdf.url && settings.pdfViewer == 'pdfjs' && showControls"
|
||||
ng-controller="PdfSynctexController"
|
||||
)
|
||||
a.btn.btn-default.btn-xs(
|
||||
tooltip="Go to code location in PDF"
|
||||
tooltip-placement="right"
|
||||
tooltip-append-to-body="true"
|
||||
ng-click="syncToPdf()"
|
||||
)
|
||||
i.fa.fa-long-arrow-right
|
||||
br
|
||||
a.btn.btn-default.btn-xs(
|
||||
tooltip-html-unsafe="Go to PDF location in code<br/>(or double click PDF)"
|
||||
tooltip-placement="right"
|
||||
tooltip-append-to-body="true"
|
||||
ng-click="syncToCode()"
|
||||
)
|
||||
i.fa.fa-long-arrow-left
|
|
@ -13,7 +13,13 @@ header.toolbar.toolbar-header(ng-cloak, ng-hide="state.loading")
|
|||
i.fa.fa-pencil
|
||||
|
||||
.toolbar-right
|
||||
a.btn.btn-full-height(href='#', tooltip="Share", tooltip-placement="bottom")
|
||||
a.btn.btn-full-height(
|
||||
href,
|
||||
tooltip="Share",
|
||||
tooltip-placement="bottom",
|
||||
ng-click="openShareProjectModal()",
|
||||
ng-controller="ShareController"
|
||||
)
|
||||
i.fa.fa-fw.fa-group
|
||||
a.btn.btn-full-height(
|
||||
href,
|
||||
|
|
|
@ -3,7 +3,7 @@ aside#left-menu.full-size(
|
|||
ng-cloak
|
||||
)
|
||||
h4 Settings
|
||||
form
|
||||
form(ng-controller="SettingsController")
|
||||
.form-controls
|
||||
label(for="compiler") Compiler
|
||||
select.form-control(
|
||||
|
@ -64,6 +64,15 @@ aside#left-menu.full-size(
|
|||
each size in ['10','11','12','13','14','16','20','24']
|
||||
option(value=size) #{size}px
|
||||
|
||||
.form-controls
|
||||
label(for="pdfViewer") PDF Viewer
|
||||
select.form-control(
|
||||
name="pdfViewer"
|
||||
ng-model="settings.pdfViewer"
|
||||
)
|
||||
option(value="pdfjs") Built-In
|
||||
option(value="native") Native
|
||||
|
||||
#left-menu-mask(
|
||||
ng-show="ui.leftMenuShown",
|
||||
ng-click="ui.leftMenuShown = false"
|
||||
|
|
|
@ -14,32 +14,67 @@ div.full-size(ng-controller="PdfController")
|
|||
a.log-btn(
|
||||
href
|
||||
ng-click="toggleLogs()"
|
||||
ng-class="{ 'active': pdf.view == 'logs' }"
|
||||
ng-class="{ 'active': (pdf.view == 'logs' || pdf.failure) && !pdf.error && !pdf.timeout && !pdf.uncompiled }"
|
||||
tooltip="Logs"
|
||||
tooltip-placement="bottom"
|
||||
)
|
||||
i.fa.fa-file-text-o
|
||||
span.label.label-danger(ng-show="pdf.logEntries.errors.length > 0")
|
||||
| {{ pdf.logEntries.errors.length }}
|
||||
span.label.label-warning(ng-show="pdf.logEntries.warnings.length > 0")
|
||||
| {{ pdf.logEntries.warnings.length }}
|
||||
span.label(
|
||||
ng-show="pdf.logEntries.warnings.length + pdf.logEntries.errors.length > 0"
|
||||
ng-class="{\
|
||||
'label-warning': pdf.logEntries.errors.length == 0,\
|
||||
'label-danger': pdf.logEntries.errors.length > 0\
|
||||
}"
|
||||
) {{ pdf.logEntries.errors.length + pdf.logEntries.warnings.length }}
|
||||
|
||||
.pdf-viewer(ng-show="pdf.url && pdf.view == 'pdf'")
|
||||
.pdf-viewer(ng-show="pdf.url && pdf.view == 'pdf' && !pdf.failure && !pdf.timeout && !pdf.error")
|
||||
div(
|
||||
pdfjs
|
||||
ng-if="settings.pdfViewer == 'pdfjs'"
|
||||
pdf-src="pdf.url"
|
||||
key="project_id"
|
||||
resize-on="layout:main:resize,layout:pdf:resize"
|
||||
highlights="pdf.highlights"
|
||||
position="pdf.position"
|
||||
dbl-click-callback="syncToCode"
|
||||
)
|
||||
iframe(
|
||||
ng-src="{{ pdf.url }}"
|
||||
ng-if="settings.pdfViewer == 'native'"
|
||||
)
|
||||
|
||||
.logs(ng-show="pdf.view == 'logs'")
|
||||
div(ng-repeat="entry in pdf.logEntries.all")
|
||||
.pdf-uncompiled(ng-show="pdf.uncompiled && !pdf.compiling")
|
||||
|
|
||||
i.fa.fa-level-up.fa-flip-horizontal.fa-2x
|
||||
| Click here to preview your work as a PDF.
|
||||
|
||||
.pdf-errors(ng-show="pdf.timeout || pdf.error")
|
||||
.alert.alert-danger(ng-show="pdf.error")
|
||||
strong Server Error.
|
||||
span Sorry, something went wrong and your project could not be compiled. Please try again in a few moments.
|
||||
|
||||
.alert.alert-danger(ng-show="pdf.timeout")
|
||||
strong Timed out.
|
||||
span Sorry, your compile was taking too long and timed out.
|
||||
| This may be due to a large number of high-res images, or lots of complicated diagrams.
|
||||
| Please try to make your document simpler, or contact support for help.
|
||||
|
||||
.pdf-logs(ng-show="(pdf.view == 'logs' || pdf.failure) && !pdf.error && !pdf.timeout && !pdf.uncompiled")
|
||||
.alert.alert-success(ng-show="pdf.logEntries.all.length == 0")
|
||||
| No errors, good job!
|
||||
|
||||
.alert.alert-danger(ng-show="pdf.failure")
|
||||
strong Compile Error.
|
||||
span Sorry, your LaTeX code couldn't compile for some reason. Please check the errors below for details, or view the raw log.
|
||||
|
||||
div(ng-repeat="entry in pdf.logEntries.all", ng-controller="PdfLogEntryController")
|
||||
.alert(
|
||||
ng-class="{\
|
||||
'alert-danger': entry.level == 'error',\
|
||||
'alert-warning': entry.level == 'warning',\
|
||||
'alert-info': entry.level == 'typesetting'\
|
||||
}"
|
||||
ng-click="openInEditor(entry)"
|
||||
)
|
||||
span.line-no
|
||||
span(ng-show="entry.file") {{ entry.file }}
|
||||
|
@ -49,9 +84,50 @@ div.full-size(ng-controller="PdfController")
|
|||
|
||||
p
|
||||
.pull-right
|
||||
a.btn.btn-default.btn-sm(href) Other logs & files
|
||||
|
|
||||
a.btn.btn-default.btn-sm(href, tooltip="Clear cached files", tooltip-placement="top")
|
||||
a.btn.btn-default.btn-sm(
|
||||
href,
|
||||
tooltip="Clear cached files",
|
||||
tooltip-placement="top",
|
||||
tooltip-append-to-body="true",
|
||||
ng-click="openClearCacheModal()"
|
||||
)
|
||||
i.fa.fa-trash-o
|
||||
a.btn.btn-info.btn-sm(href) View Raw Logs
|
||||
|
|
||||
div.dropdown(style="display: inline-block;")
|
||||
a.btn.btn-default.btn-sm(
|
||||
href
|
||||
data-toggle="dropdown"
|
||||
)
|
||||
| Other logs & files
|
||||
span.caret
|
||||
ul.dropdown-menu.dropdown-menu-right
|
||||
li(ng-repeat="file in pdf.outputFiles")
|
||||
a(
|
||||
href="/project/{{project_id}}/output/{{file.path}}"
|
||||
target="_blank"
|
||||
ng-click="openOutputFile(file)"
|
||||
) {{ file.name }}
|
||||
a.btn.btn-info.btn-sm(href, ng-click="toggleRawLog()")
|
||||
span(ng-show="!pdf.showRawLog") View Raw Logs
|
||||
span(ng-show="pdf.showRawLog") Hide Raw Logs
|
||||
|
||||
pre(ng-bind="pdf.rawLog", ng-show="pdf.showRawLog")
|
||||
|
||||
script(type='text/ng-template', id='clearCacheModalTemplate')
|
||||
.modal-header
|
||||
h3 Clear cache?
|
||||
.modal-body
|
||||
p This will clear all hidden LaTeX files (.aux, .bbl, etc) from our compile server.
|
||||
| You generally don't need to do this unless you're having trouble with references.
|
||||
p Your project files will not be deleted or changed.
|
||||
.modal-footer
|
||||
button.btn.btn-default(
|
||||
ng-click="cancel()"
|
||||
ng-disabled="state.inflight"
|
||||
) Cancel
|
||||
button.btn.btn-info(
|
||||
ng-click="clear()"
|
||||
ng-disabled="state.inflight"
|
||||
)
|
||||
span(ng-show="!state.inflight") Clear cache
|
||||
span(ng-show="state.inflight") Clearing...
|
||||
|
|
125
services/web/app/views/project/editor/share.jade
Normal file
125
services/web/app/views/project/editor/share.jade
Normal file
|
@ -0,0 +1,125 @@
|
|||
script(type='text/ng-template', id='shareProjectModalTemplate')
|
||||
.modal-header
|
||||
button.close(
|
||||
type="button"
|
||||
data-dismiss="modal"
|
||||
ng-click="cancel()"
|
||||
) ×
|
||||
h3 Share Project
|
||||
.modal-body.modal-body-share
|
||||
.container-fluid
|
||||
.row.public-access-level(ng-show="project.publicAccesLevel == 'private'")
|
||||
.col-md-12.text-center
|
||||
| This project is private and can only be accessed by the people below.
|
||||
|
|
||||
a(
|
||||
href
|
||||
ng-click="openMakePublicModal()"
|
||||
) Make Public
|
||||
.row.public-access-level(ng-show="project.publicAccesLevel != 'private'")
|
||||
.col-md-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 == 'readOnly'") This project is public and can be viewed by anyone with the URL.
|
||||
|
|
||||
a(
|
||||
href
|
||||
ng-click="openMakePrivateModal()"
|
||||
) Make Private
|
||||
.row.project-member
|
||||
.col-md-8 {{ project.owner.email }}
|
||||
.text-right(
|
||||
ng-class="{'col-md-3': project.members.length > 0, 'col-md-4': project.members.length == 0}"
|
||||
) Owner
|
||||
.row.project-member(ng-repeat="member in project.members")
|
||||
.col-md-8 {{ member.email }}
|
||||
.col-md-3.text-right
|
||||
span(ng-show="member.privileges == 'readAndWrite'") Can Edit
|
||||
span(ng-show="member.privileges == 'readOnly'") Read Only
|
||||
.col-md-1
|
||||
a(
|
||||
href
|
||||
tooltip="Remove collaborator"
|
||||
tooltip-placement="bottom"
|
||||
ng-click="removeMember(member)"
|
||||
)
|
||||
i.fa.fa-times
|
||||
.row.invite-controls
|
||||
form(ng-show="canAddCollaborators")
|
||||
.small Share with your collaborators
|
||||
.form-group
|
||||
input.form-control(
|
||||
type="email"
|
||||
placeholder="Enter email address..."
|
||||
ng-model="inputs.email"
|
||||
focus-on="open"
|
||||
)
|
||||
.form-group
|
||||
.pull-right
|
||||
select.privileges.form-control(
|
||||
ng-model="inputs.privileges"
|
||||
name="privileges"
|
||||
)
|
||||
option(value="readAndWrite") Can Edit
|
||||
option(value="readOnly") Read Only
|
||||
|
|
||||
button.btn.btn-info(
|
||||
type="submit"
|
||||
ng-click="addMember()"
|
||||
) Share
|
||||
div.text-center(ng-hide="canAddCollaborators")
|
||||
p You need to upgrade your account to add more collaborators.
|
||||
p
|
||||
a.btn.btn-info(href, ng-click="startFreeTrial()") Start Free Trial
|
||||
p.small(ng-show="state.startedFreeTrial")
|
||||
| Please refresh this page after starting your free trial.
|
||||
|
||||
.modal-footer
|
||||
.modal-footer-left
|
||||
i.fa.fa-refresh.fa-spin(ng-show="state.inflight")
|
||||
span.text-danger.error(ng-show="state.error") {{ state.error }}
|
||||
button.btn.btn-primary(
|
||||
ng-click="done()"
|
||||
) Done
|
||||
|
||||
script(type="text/ng-template", id="makePublicModalTemplate")
|
||||
.modal-header
|
||||
button.close(
|
||||
type="button"
|
||||
data-dismiss="modal"
|
||||
ng-click="cancel()"
|
||||
) ×
|
||||
h3 Make project public?
|
||||
.modal-body.modal-body-share
|
||||
p If you make your project public then anyone with the URL will be able to access it.
|
||||
p
|
||||
select.form-control(
|
||||
ng-model="inputs.privileges"
|
||||
name="privileges"
|
||||
)
|
||||
option(value="readAndWrite") Allow public editing
|
||||
option(value="readOnly") Allow public read only access
|
||||
.modal-footer
|
||||
button.btn.btn-default(
|
||||
ng-click="cancel()"
|
||||
) Cancel
|
||||
button.btn.btn-info(
|
||||
ng-click="makePublic()"
|
||||
) Make public
|
||||
|
||||
script(type="text/ng-template", id="makePrivateModalTemplate")
|
||||
.modal-header
|
||||
button.close(
|
||||
type="button"
|
||||
data-dismiss="modal"
|
||||
ng-click="cancel()"
|
||||
) ×
|
||||
h3 Make project private?
|
||||
.modal-body.modal-body-share
|
||||
p If you make your project public then only the people you choose to share it with will have access.
|
||||
.modal-footer
|
||||
button.btn.btn-default(
|
||||
ng-click="cancel()"
|
||||
) Cancel
|
||||
button.btn.btn-info(
|
||||
ng-click="makePrivate()"
|
||||
) Make private
|
|
@ -71,7 +71,7 @@ div#trackChanges(ng-show="ui.view == 'track-changes'")
|
|||
theme="settings.theme",
|
||||
font-size="settings.fontSize",
|
||||
text="trackChanges.diff.text",
|
||||
annotations="trackChanges.diff.annotations",
|
||||
highlights="trackChanges.diff.highlights",
|
||||
read-only="true",
|
||||
resize-on="layout:main:resize"
|
||||
)
|
||||
|
|
|
@ -3,10 +3,11 @@ define [
|
|||
"ide/file-tree/FileTreeManager"
|
||||
"ide/connection/ConnectionManager"
|
||||
"ide/editor/EditorManager"
|
||||
"ide/settings/SettingsManager"
|
||||
"ide/online-users/OnlineUsersManager"
|
||||
"ide/track-changes/TrackChangesManager"
|
||||
"ide/pdf/PdfManager"
|
||||
"ide/settings/index"
|
||||
"ide/share/index"
|
||||
"ide/directives/layout"
|
||||
"ide/services/ide"
|
||||
"directives/focus"
|
||||
|
@ -18,7 +19,6 @@ define [
|
|||
FileTreeManager
|
||||
ConnectionManager
|
||||
EditorManager
|
||||
SettingsManager
|
||||
OnlineUsersManager
|
||||
TrackChangesManager
|
||||
PdfManager
|
||||
|
@ -42,6 +42,7 @@ define [
|
|||
view: "editor"
|
||||
}
|
||||
$scope.user = window.user
|
||||
$scope.settings = window.userSettings
|
||||
|
||||
window._ide = ide
|
||||
|
||||
|
@ -51,7 +52,6 @@ define [
|
|||
ide.connectionManager = new ConnectionManager(ide, $scope)
|
||||
ide.fileTreeManager = new FileTreeManager(ide, $scope)
|
||||
ide.editorManager = new EditorManager(ide, $scope)
|
||||
ide.settingsManager = new SettingsManager(ide, $scope)
|
||||
ide.onlineUsersManager = new OnlineUsersManager(ide, $scope)
|
||||
ide.trackChangesManager = new TrackChangesManager(ide, $scope)
|
||||
ide.pdfManager = new PdfManager(ide, $scope)
|
||||
|
|
|
@ -10,9 +10,15 @@ define [
|
|||
spacing_open: 24
|
||||
spacing_closed: 24
|
||||
onresize: () =>
|
||||
console.log "Triggering", "layout:#{name}:resize", name
|
||||
scope.$broadcast "layout:#{name}:resize"
|
||||
#maskIframesOnResize: true
|
||||
onResize()
|
||||
maskIframesOnResize: scope.$eval(
|
||||
attrs.maskIframesOnResize or "false"
|
||||
)
|
||||
|
||||
onResize = () ->
|
||||
state = element.layout().readState()
|
||||
scope.$broadcast "layout:#{name}:resize", state
|
||||
repositionControls()
|
||||
|
||||
# Restore previously recorded state
|
||||
if (state = $.localStorage("layout.#{name}"))?
|
||||
|
@ -28,4 +34,13 @@ define [
|
|||
# Save state when exiting
|
||||
$(window).unload () ->
|
||||
$.localStorage("layout.#{name}", element.layout().readState())
|
||||
|
||||
repositionControls = () ->
|
||||
state = element.layout().readState()
|
||||
if state.east?
|
||||
element.find(".ui-layout-resizer-controls").css({
|
||||
position: "absolute"
|
||||
right: state.east.size
|
||||
"z-index": 10
|
||||
})
|
||||
}
|
|
@ -10,6 +10,7 @@ define [
|
|||
open_doc_id: null
|
||||
opening: true
|
||||
cursorPosition: null
|
||||
gotoLine: null
|
||||
}
|
||||
|
||||
@$scope.$on "entity:selected", (event, entity) =>
|
||||
|
@ -33,25 +34,33 @@ define [
|
|||
|
||||
openDoc: (doc, options = {}) ->
|
||||
@$scope.ui.view = "editor"
|
||||
|
||||
done = () =>
|
||||
if options.gotoLine?
|
||||
@$scope.editor.gotoLine = options.gotoLine
|
||||
|
||||
console.log "Trying to open doc", doc.id
|
||||
return if doc.id == @$scope.editor.open_doc_id and !options.forceReopen
|
||||
if doc.id == @$scope.editor.open_doc_id and !options.forceReopen
|
||||
@$scope.$apply () =>
|
||||
done()
|
||||
return
|
||||
|
||||
@$scope.editor.open_doc_id = doc.id
|
||||
console.log "Actually opening doc", doc.id
|
||||
|
||||
$.localStorage "doc.open_id.#{@$scope.project_id}", doc.id
|
||||
@ide.fileTreeManager.selectEntity(doc)
|
||||
|
||||
@$scope.editor.opening = true
|
||||
@_openNewDocument doc, (error, sharejs_doc) =>
|
||||
console.log "OPENED DOC", error, sharejs_doc
|
||||
if error?
|
||||
@ide.showGenericServerErrorMessage()
|
||||
return
|
||||
|
||||
@$scope.$broadcast "doc:opened"
|
||||
|
||||
@$scope.$apply () =>
|
||||
@$scope.editor.opening = false
|
||||
@$scope.editor.sharejs_doc = sharejs_doc
|
||||
done()
|
||||
|
||||
_openNewDocument: (doc, callback = (error, sharejs_doc) ->) ->
|
||||
current_sharejs_doc = @$scope.editor.sharejs_doc
|
||||
|
@ -93,3 +102,9 @@ define [
|
|||
|
||||
lastUpdated: () ->
|
||||
@$scope.editor.last_updated
|
||||
|
||||
getCurrentDocValue: () ->
|
||||
@$scope.editor.sharejs_doc?.getSnapshot()
|
||||
|
||||
getCurrentDocId: () ->
|
||||
@$scope.editor.open_doc_id
|
||||
|
|
|
@ -11,6 +11,21 @@ define [], () ->
|
|||
|
||||
@gotoStoredPosition()
|
||||
|
||||
@editor.on "changeSelection", () =>
|
||||
cursor = @editor.getCursorPosition()
|
||||
console.log "Updating cursor position", cursor
|
||||
@$scope.$apply () =>
|
||||
@$scope.cursorPosition = cursor
|
||||
|
||||
@$scope.$watch "gotoLine", (value) =>
|
||||
console.log "Going to line", value
|
||||
if value?
|
||||
setTimeout () =>
|
||||
@gotoLine(value)
|
||||
@$scope.$apply () =>
|
||||
@$scope.gotoLine = null
|
||||
, 0
|
||||
|
||||
onScrollTopChange: (event) ->
|
||||
if !@ignoreCursorPositionChanges and doc_id = @$scope.sharejsDoc?.doc_id
|
||||
docPosition = $.localStorage("doc.position.#{doc_id}") || {}
|
||||
|
@ -30,4 +45,8 @@ define [], () ->
|
|||
@ignoreCursorPositionChanges = true
|
||||
@editor.moveCursorToPosition(pos.cursorPosition or {row: 0, column: 0})
|
||||
@editor.getSession().setScrollTop(pos.scrollTop or 0)
|
||||
delete @ignoreCursorPositionChanges
|
||||
delete @ignoreCursorPositionChanges
|
||||
|
||||
gotoLine: (line) ->
|
||||
@editor.gotoLine(line)
|
||||
@editor.focus()
|
|
@ -4,7 +4,7 @@ define [
|
|||
"ide/editor/undo/UndoManager"
|
||||
"ide/editor/auto-complete/AutoCompleteManager"
|
||||
"ide/editor/spell-check/SpellCheckManager"
|
||||
"ide/editor/annotations/AnnotationsManager"
|
||||
"ide/editor/highlights/HighlightsManager"
|
||||
"ide/editor/cursor-position/CursorPositionManager"
|
||||
"ace/keyboard/vim"
|
||||
"ace/keyboard/emacs"
|
||||
|
@ -26,9 +26,11 @@ define [
|
|||
lastUpdated: "="
|
||||
spellCheckLanguage: "="
|
||||
cursorPosition: "="
|
||||
annotations: "="
|
||||
highlights: "="
|
||||
text: "="
|
||||
readOnly: "="
|
||||
gotoLine: "="
|
||||
annotations: "="
|
||||
}
|
||||
link: (scope, element, attrs) ->
|
||||
# Don't freak out if we're already in an apply callback
|
||||
|
@ -63,12 +65,6 @@ define [
|
|||
scope.$on event, () ->
|
||||
editor.resize()
|
||||
|
||||
editor.on "changeSelection", () ->
|
||||
cursor = editor.getCursorPosition()
|
||||
scope.$apply () ->
|
||||
if scope.cursorPosition?
|
||||
scope.cursorPosition = cursor
|
||||
|
||||
scope.$watch "theme", (value) ->
|
||||
editor.setTheme("ace/theme/#{value}")
|
||||
|
||||
|
@ -100,15 +96,26 @@ define [
|
|||
session.setUseWrapMode(true)
|
||||
session.setMode(new LatexMode())
|
||||
|
||||
scope.$watch "annotations", (annotations) ->
|
||||
console.log "SETTING ANNOTATIONS", annotations
|
||||
if annotations?
|
||||
session = editor.getSession()
|
||||
session.setAnnotations annotations
|
||||
|
||||
scope.$watch "readOnly", (value) ->
|
||||
editor.setReadOnly !!value
|
||||
|
||||
resetSession = () ->
|
||||
session = editor.getSession()
|
||||
session.setUseWrapMode(true)
|
||||
session.setMode(new LatexMode())
|
||||
session.setAnnotations scope.annotations
|
||||
|
||||
attachToAce = (sharejs_doc) ->
|
||||
lines = sharejs_doc.getSnapshot().split("\n")
|
||||
editor.setSession(new EditSession(lines))
|
||||
resetSession()
|
||||
session = editor.getSession()
|
||||
session.setUseWrapMode(true)
|
||||
session.setMode(new LatexMode())
|
||||
|
||||
autoCompleteManager.bindToSession(session)
|
||||
annotationsManager.redrawAnnotations()
|
||||
|
|
|
@ -3,7 +3,7 @@ define [
|
|||
], () ->
|
||||
Range = require("ace/range").Range
|
||||
|
||||
class AnnotationsManager
|
||||
class HighlightsManager
|
||||
constructor: (@$scope, @editor, @element) ->
|
||||
@markerIds = []
|
||||
@labels = []
|
||||
|
@ -18,7 +18,7 @@ define [
|
|||
text: ""
|
||||
}
|
||||
|
||||
@$scope.$watch "annotations", (value) =>
|
||||
@$scope.$watch "highlights", (value) =>
|
||||
@redrawAnnotations()
|
||||
|
||||
@$scope.$watch "theme", (value) =>
|
||||
|
@ -33,12 +33,13 @@ define [
|
|||
@_clearMarkers()
|
||||
@_clearLabels()
|
||||
|
||||
for annotation in @$scope.annotations or []
|
||||
for annotation in @$scope.highlights or []
|
||||
do (annotation) =>
|
||||
colorScheme = @_getColorScheme(annotation.hue)
|
||||
if annotation.cursor?
|
||||
console.log "DRAWING CURSOR", annotation
|
||||
@labels.push {
|
||||
text: annotation.text
|
||||
text: annotation.label
|
||||
range: new Range(
|
||||
annotation.cursor.row, annotation.cursor.column,
|
||||
annotation.cursor.row, annotation.cursor.column + 1
|
|
@ -88,6 +88,25 @@ define [
|
|||
|
||||
return null
|
||||
|
||||
findEntityByPath: (path) ->
|
||||
@_findEntityByPathInFolder @$scope.rootFolder, path
|
||||
|
||||
_findEntityByPathInFolder: (folder, path) ->
|
||||
parts = path.split("/")
|
||||
name = parts.shift()
|
||||
rest = parts.join("/")
|
||||
|
||||
if name == "."
|
||||
return @_findEntityByPathInFolder(folder, rest)
|
||||
|
||||
for entity in folder.children
|
||||
if entity.name == name
|
||||
if rest == ""
|
||||
return entity
|
||||
else if entity.type == "folder"
|
||||
return @_findEntityByPathInFolder(entity, rest)
|
||||
return null
|
||||
|
||||
forEachEntity: (callback = (entity, parent_folder) ->) ->
|
||||
@_forEachEntityInFolder(@$scope.rootFolder, callback)
|
||||
|
||||
|
@ -100,6 +119,26 @@ define [
|
|||
if entity.children?
|
||||
@_forEachEntityInFolder(entity, callback)
|
||||
|
||||
getEntityPath: (entity) ->
|
||||
@_getEntityPathInFolder @$scope.rootFolder, entity
|
||||
|
||||
_getEntityPathInFolder: (folder, entity) ->
|
||||
for child in folder.children or []
|
||||
if child == entity
|
||||
return entity.name
|
||||
else if child.type == "folder"
|
||||
path = @_getEntityPathInFolder(child, entity)
|
||||
if path?
|
||||
return child.name + "/" + path
|
||||
return null
|
||||
|
||||
getRootDocDirname: () ->
|
||||
rootDoc = @findEntityById @$scope.project.rootDoc_id
|
||||
return if !rootDoc?
|
||||
path = @getEntityPath(rootDoc)
|
||||
return if !path?
|
||||
return path.split("/").slice(0, -1).join("/")
|
||||
|
||||
# forEachFolder: (callback) ->
|
||||
# @forEachEntity (entity) ->
|
||||
# if entity.type == "folder"
|
||||
|
|
|
@ -4,7 +4,7 @@ define [
|
|||
class OnlineUsersManager
|
||||
constructor: (@ide, @$scope) ->
|
||||
@$scope.onlineUsers = {}
|
||||
@$scope.onlineUserCursorAnnotations = {}
|
||||
@$scope.onlineUserCursorHighlights = {}
|
||||
|
||||
@$scope.$watch "editor.cursorPosition", (position) =>
|
||||
if position?
|
||||
|
@ -23,12 +23,12 @@ define [
|
|||
|
||||
updateCursorHighlights: () ->
|
||||
console.log "UPDATING CURSOR HIGHLIGHTS"
|
||||
@$scope.onlineUserCursorAnnotations = {}
|
||||
@$scope.onlineUserCursorHighlights = {}
|
||||
for client_id, client of @$scope.onlineUsers
|
||||
doc_id = client.doc_id
|
||||
continue if !doc_id?
|
||||
@$scope.onlineUserCursorAnnotations[doc_id] ||= []
|
||||
@$scope.onlineUserCursorAnnotations[doc_id].push {
|
||||
@$scope.onlineUserCursorHighlights[doc_id] ||= []
|
||||
@$scope.onlineUserCursorHighlights[doc_id].push {
|
||||
label: client.name
|
||||
cursor:
|
||||
row: client.row
|
||||
|
@ -38,6 +38,7 @@ define [
|
|||
|
||||
UPDATE_INTERVAL: 500
|
||||
sendCursorPositionUpdate: () ->
|
||||
console.log "SENDING CURSOR POSITION UPDATE", @$scope.editor.cursorPosition
|
||||
if !@cursorUpdateTimeout?
|
||||
@cursorUpdateTimeout = setTimeout ()=>
|
||||
position = @$scope.editor.cursorPosition
|
||||
|
|
|
@ -4,5 +4,17 @@ define [
|
|||
], () ->
|
||||
class PdfManager
|
||||
constructor: (@ide, @$scope) ->
|
||||
|
||||
# All the logic actually happens in the controller
|
||||
@$scope.pdf =
|
||||
url: null # Pdf Url
|
||||
error: false # Server error
|
||||
timeout: false # Server timed out
|
||||
failure: false # PDF failed to compile
|
||||
compiling: false
|
||||
uncompiled: true
|
||||
logEntries: []
|
||||
logEntryAnnotations: {}
|
||||
rawLog: ""
|
||||
view: null # 'pdf' 'logs'
|
||||
showRawLog: false
|
||||
highlights: []
|
||||
position: null
|
||||
|
|
|
@ -2,36 +2,35 @@ define [
|
|||
"base"
|
||||
"libs/latex-log-parser"
|
||||
], (App, LogParser) ->
|
||||
App.controller "PdfController", ["$scope", "$http", ($scope, $http) ->
|
||||
$scope.pdf =
|
||||
url: null # Pdf Url
|
||||
view: null # 'pdf' 'logs'
|
||||
error: false # Server error
|
||||
timeout: false # Server timed out
|
||||
failure: false # PDF failed to compile
|
||||
compiling: false
|
||||
logEntries: []
|
||||
App.controller "PdfController", ["$scope", "$http", "ide", "$modal", "synctex", ($scope, $http, ide, $modal, synctex) ->
|
||||
autoCompile = true
|
||||
$scope.$on "doc:opened", () ->
|
||||
return if !autoCompile
|
||||
autoCompile = false
|
||||
$scope.recompile(isAutoCompile: true)
|
||||
|
||||
sendCompileRequest = (options = {}) ->
|
||||
url = "/project/#{$scope.project_id}/compile"
|
||||
if options.isAutoCompile
|
||||
url += "?auto_compile=true"
|
||||
return $http.post url, {
|
||||
rootDoc_id: options.rootDocOverride_id or null
|
||||
settingsOverride:
|
||||
rootDoc_id: options.rootDocOverride_id or null
|
||||
_csrf: window.csrfToken
|
||||
}
|
||||
|
||||
parseCompileResponse = (response) ->
|
||||
# Reset everything
|
||||
$scope.pdf.error = false
|
||||
$scope.pdf.timedout = false
|
||||
$scope.pdf.failure = false
|
||||
$scope.pdf.url = null
|
||||
$scope.pdf.error = false
|
||||
$scope.pdf.timedout = false
|
||||
$scope.pdf.failure = false
|
||||
$scope.pdf.uncompiled = false
|
||||
$scope.pdf.url = null
|
||||
|
||||
if response.status == "timedout"
|
||||
$scope.pdf.timedout = true
|
||||
else if response.status == "autocompile-backoff"
|
||||
# Nothing to do
|
||||
$scope.pdf.uncompiled = true
|
||||
else if response.status == "failure"
|
||||
$scope.pdf.failure = true
|
||||
fetchLogs()
|
||||
|
@ -39,35 +38,241 @@ define [
|
|||
$scope.pdf.url = "/project/#{$scope.project_id}/output/output.pdf?cache_bust=#{Date.now()}"
|
||||
fetchLogs()
|
||||
|
||||
IGNORE_FILES = ["output.fls", "output.fdb_latexmk"]
|
||||
$scope.pdf.outputFiles = []
|
||||
for file in response.outputFiles
|
||||
if IGNORE_FILES.indexOf(file.path) == -1
|
||||
# Turn 'output.blg' into 'blg file'.
|
||||
if file.path.match(/^output\./)
|
||||
file.name = "#{file.path.replace(/^output\./, "")} file"
|
||||
else
|
||||
file.name = file.path
|
||||
$scope.pdf.outputFiles.push file
|
||||
|
||||
fetchLogs = () ->
|
||||
$http.get "/project/#{$scope.project_id}/output/output.log"
|
||||
.success (log) ->
|
||||
$scope.pdf.rawLog = log
|
||||
logEntries = LogParser.parse(log, ignoreDuplicates: true)
|
||||
$scope.pdf.logEntries = logEntries
|
||||
$scope.pdf.logEntries.all = logEntries.errors.concat(logEntries.warnings).concat(logEntries.typesetting)
|
||||
for entry in logEntries.all
|
||||
entry.file = entry.file.replace(/^(.*)\/compiles\/[0-9a-f]{24}\/(\.\/)?/, "")
|
||||
entry.file = entry.file.replace(/^\/compile\//, "")
|
||||
console.log "LOG", logEntries
|
||||
|
||||
$scope.recompile = () ->
|
||||
console.log "Recompiling"
|
||||
$scope.pdf.logEntryAnnotations = {}
|
||||
for entry in logEntries.all
|
||||
entry.file = normalizeFilePath(entry.file)
|
||||
|
||||
entity = ide.fileTreeManager.findEntityByPath(entry.file)
|
||||
if entity?
|
||||
$scope.pdf.logEntryAnnotations[entity.id] ||= []
|
||||
$scope.pdf.logEntryAnnotations[entity.id].push {
|
||||
row: entry.line - 1
|
||||
type: if entry.level == "error" then "error" else "warning"
|
||||
text: entry.message
|
||||
}
|
||||
|
||||
.error () ->
|
||||
$scope.pdf.logEntries = []
|
||||
$scope.pdf.rawLog = ""
|
||||
|
||||
getRootDocOverride_id = () ->
|
||||
doc = ide.editorManager.getCurrentDocValue()
|
||||
return null if !doc?
|
||||
for line in doc.split("\n")
|
||||
match = line.match /(.*)\\documentclass/
|
||||
if match and !match[1].match /%/
|
||||
return ide.editorManager.getCurrentDocId()
|
||||
return null
|
||||
|
||||
normalizeFilePath = (path) ->
|
||||
path = path.replace(/^(.*)\/compiles\/[0-9a-f]{24}\/(\.\/)?/, "")
|
||||
path = path.replace(/^\/compile\//, "")
|
||||
|
||||
rootDocDirname = ide.fileTreeManager.getRootDocDirname()
|
||||
if rootDocDirname?
|
||||
path = path.replace(/^\.\//, rootDocDirname + "/")
|
||||
|
||||
return path
|
||||
|
||||
$scope.recompile = (options = {}) ->
|
||||
console.log "Recompiling", options
|
||||
return if $scope.pdf.compiling
|
||||
$scope.pdf.compiling = true
|
||||
sendCompileRequest()
|
||||
|
||||
options.rootDocOverride_id = getRootDocOverride_id()
|
||||
|
||||
sendCompileRequest(options)
|
||||
.success (data) ->
|
||||
$scope.pdf.view = "pdf"
|
||||
$scope.pdf.compiling = false
|
||||
parseCompileResponse(data)
|
||||
.error () ->
|
||||
$scope.pdf.compiling = false
|
||||
$scope.pdf.error = true
|
||||
|
||||
$scope.clearCache = () ->
|
||||
$http {
|
||||
url: "/project/#{$scope.project_id}/output"
|
||||
method: "DELETE"
|
||||
headers:
|
||||
"X-Csrf-Token": window.csrfToken
|
||||
}
|
||||
|
||||
$scope.toggleLogs = () ->
|
||||
if $scope.pdf.view == "pdf"
|
||||
if !$scope.pdf.view? or $scope.pdf.view == "pdf"
|
||||
$scope.pdf.view = "logs"
|
||||
else
|
||||
$scope.pdf.view = "pdf"
|
||||
|
||||
$scope.showPdf = () ->
|
||||
$scope.pdf.view = "pdf"
|
||||
|
||||
$scope.toggleRawLog = () ->
|
||||
$scope.pdf.showRawLog = !$scope.pdf.showRawLog
|
||||
|
||||
$scope.openOutputFile = (file) ->
|
||||
window.open("/project/#{$scope.project_id}/output/#{file.path}")
|
||||
|
||||
$scope.openClearCacheModal = () ->
|
||||
modalInstance = $modal.open(
|
||||
templateUrl: "clearCacheModalTemplate"
|
||||
controller: "ClearCacheModalController"
|
||||
scope: $scope
|
||||
)
|
||||
|
||||
$scope.syncToCode = (position) ->
|
||||
console.log "SYNCING VIA DBL CLICK", position
|
||||
synctex
|
||||
.syncToCode(position)
|
||||
.then (data) ->
|
||||
{doc, line} = data
|
||||
ide.editorManager.openDoc(doc, gotoLine: line)
|
||||
|
||||
]
|
||||
|
||||
App.factory "synctex", ["ide", "$http", "$q", (ide, $http, $q) ->
|
||||
synctex =
|
||||
syncToPdf: (cursorPosition) ->
|
||||
deferred = $q.defer()
|
||||
|
||||
doc_id = ide.editorManager.getCurrentDocId()
|
||||
if !doc_id?
|
||||
deferred.reject()
|
||||
return deferred.promise
|
||||
doc = ide.fileTreeManager.findEntityById(doc_id)
|
||||
if !doc?
|
||||
deferred.reject()
|
||||
return deferred.promise
|
||||
path = ide.fileTreeManager.getEntityPath(doc)
|
||||
if !path?
|
||||
deferred.reject()
|
||||
return deferred.promise
|
||||
|
||||
# If the root file is folder/main.tex, then synctex sees the
|
||||
# path as folder/./main.tex
|
||||
rootDocDirname = ide.fileTreeManager.getRootDocDirname()
|
||||
if rootDocDirname? and rootDocDirname != ""
|
||||
path = path.replace(RegExp("^#{rootDocDirname}"), "#{rootDocDirname}/.")
|
||||
|
||||
{row, column} = cursorPosition
|
||||
|
||||
$http({
|
||||
url: "/project/#{ide.project_id}/sync/code",
|
||||
method: "GET",
|
||||
params: {
|
||||
file: path
|
||||
line: row + 1
|
||||
column: column
|
||||
}
|
||||
})
|
||||
.success (data) ->
|
||||
deferred.resolve(data.pdf or [])
|
||||
.error (error) ->
|
||||
deferred.reject(error)
|
||||
|
||||
return deferred.promise
|
||||
|
||||
syncToCode: (position, options = {}) ->
|
||||
deferred = $q.defer()
|
||||
if !position?
|
||||
deferred.reject()
|
||||
return deferred.promise
|
||||
|
||||
# It's not clear exactly where we should sync to if it wasn't directly
|
||||
# clicked on, but a little bit down from the very top seems best.
|
||||
if options.includeVisualOffset
|
||||
position.offset.top = position.offset.top + 80
|
||||
|
||||
$http({
|
||||
url: "/project/#{ide.project_id}/sync/pdf",
|
||||
method: "GET",
|
||||
params: {
|
||||
page: position.page + 1
|
||||
h: position.offset.left.toFixed(2)
|
||||
v: position.offset.top.toFixed(2)
|
||||
}
|
||||
})
|
||||
.success (data) ->
|
||||
if data.code? and data.code.length > 0
|
||||
doc = ide.fileTreeManager.findEntityByPath(data.code[0].file)
|
||||
return if !doc?
|
||||
deferred.resolve({doc: doc, line: data.code[0].line})
|
||||
.error (error) ->
|
||||
deferred.reject(error)
|
||||
|
||||
return deferred.promise
|
||||
|
||||
return synctex
|
||||
]
|
||||
|
||||
App.controller "PdfSynctexController", ["$scope", "synctex", "ide", ($scope, synctex, ide) ->
|
||||
$scope.showControls = true
|
||||
$scope.$on "layout:pdf:resize", (event, data) ->
|
||||
console.log "RESIZE DATA", data.east
|
||||
if data.east.initClosed
|
||||
$scope.showControls = false
|
||||
else
|
||||
$scope.showControls = true
|
||||
setTimeout () ->
|
||||
$scope.$digest()
|
||||
, 0
|
||||
|
||||
$scope.syncToPdf = () ->
|
||||
synctex
|
||||
.syncToPdf($scope.editor.cursorPosition)
|
||||
.then (highlights) ->
|
||||
$scope.pdf.highlights = highlights
|
||||
|
||||
$scope.syncToCode = () ->
|
||||
synctex
|
||||
.syncToCode($scope.pdf.position, includeVisualOffset: true)
|
||||
.then (data) ->
|
||||
{doc, line} = data
|
||||
console.log "OPENING DOC", doc, line
|
||||
ide.editorManager.openDoc(doc, gotoLine: line)
|
||||
]
|
||||
|
||||
App.controller "PdfLogEntryController", ["$scope", "ide", ($scope, ide) ->
|
||||
$scope.openInEditor = (entry) ->
|
||||
console.log "OPENING", entry.file, entry.line
|
||||
entity = ide.fileTreeManager.findEntityByPath(entry.file)
|
||||
return if !entity? or entity.type != "doc"
|
||||
if entry.line?
|
||||
line = entry.line
|
||||
ide.editorManager.openDoc(entity, gotoLine: line)
|
||||
]
|
||||
|
||||
App.controller 'ClearCacheModalController', ["$scope", "$modalInstance", ($scope, $modalInstance) ->
|
||||
$scope.state =
|
||||
inflight: false
|
||||
|
||||
$scope.clear = () ->
|
||||
$scope.state.inflight = true
|
||||
$scope
|
||||
.clearCache()
|
||||
.then () ->
|
||||
$scope.state.inflight = false
|
||||
$modalInstance.close()
|
||||
|
||||
$scope.cancel = () ->
|
||||
$modalInstance.dismiss('cancel')
|
||||
]
|
|
@ -28,12 +28,16 @@ define [
|
|||
return {
|
||||
scope: {
|
||||
"pdfSrc": "="
|
||||
"highlights": "="
|
||||
"position": "="
|
||||
"dblClickCallback": "="
|
||||
}
|
||||
link: (scope, element, attrs) ->
|
||||
pdfListView = new PDFListView element.find(".pdfjs-viewer")[0],
|
||||
textLayerBuilder: TextLayerBuilder
|
||||
annotationsLayerBuilder: AnnotationsLayerBuilder
|
||||
highlightsLayerBuilder: HighlightsLayerBuilder
|
||||
ondblclick: (e) -> onDoubleClick(e)
|
||||
logLevel: PDFListView.Logger.DEBUG
|
||||
pdfListView.listView.pageWidthOffset = 20
|
||||
pdfListView.listView.pageHeightOffset = 20
|
||||
|
@ -58,6 +62,8 @@ define [
|
|||
if (position = $.localStorage("pdf.position.#{attrs.key}"))
|
||||
pdfListView.setPdfPosition(position)
|
||||
|
||||
scope.position = pdfListView.getPdfPosition(true)
|
||||
|
||||
$(window).unload () =>
|
||||
$.localStorage "pdf.scale", {
|
||||
scaleMode: pdfListView.getScaleMode()
|
||||
|
@ -69,7 +75,14 @@ define [
|
|||
scope.flashControls = true
|
||||
$timeout () ->
|
||||
scope.flashControls = false
|
||||
, 1000
|
||||
, 1000
|
||||
|
||||
element.find(".pdfjs-viewer").scroll () ->
|
||||
console.log "UPDATING POSITION", pdfListView.getPdfPosition(true)
|
||||
scope.position = pdfListView.getPdfPosition(true)
|
||||
|
||||
onDoubleClick = (e) ->
|
||||
scope.dblClickCallback?(page: e.page, offset: { top: e.y, left: e.x })
|
||||
|
||||
scope.$watch "pdfSrc", (url) ->
|
||||
if url
|
||||
|
@ -85,6 +98,35 @@ define [
|
|||
initializePosition()
|
||||
flashControls()
|
||||
|
||||
scope.$watch "highlights", (areas) ->
|
||||
console.log "UPDATING HIGHLIGHTS", areas
|
||||
return if !areas?
|
||||
highlights = for area in areas or []
|
||||
{
|
||||
page: area.page - 1
|
||||
highlight:
|
||||
left: area.h
|
||||
top: area.v
|
||||
height: area.height
|
||||
width: area.width
|
||||
}
|
||||
|
||||
if highlights.length > 0
|
||||
first = highlights[0]
|
||||
pdfListView.setPdfPosition({
|
||||
page: first.page
|
||||
offset:
|
||||
left: first.highlight.left
|
||||
top: first.highlight.top - 80
|
||||
}, true)
|
||||
|
||||
pdfListView.clearHighlights()
|
||||
pdfListView.setHighlights(highlights, true)
|
||||
|
||||
setTimeout () =>
|
||||
pdfListView.clearHighlights()
|
||||
, 1000
|
||||
|
||||
scope.fitToHeight = () ->
|
||||
pdfListView.setToFitHeight()
|
||||
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
define [], () ->
|
||||
class SettingsManager
|
||||
constructor: (@ide, @$scope) ->
|
||||
@$scope.settings = window.userSettings
|
||||
|
||||
if @$scope.settings.mode not in ["default", "vim", "emacs"]
|
||||
@$scope.settings.mode = "default"
|
||||
|
||||
@$scope.$watch "settings.theme", (theme, oldTheme) =>
|
||||
if theme != oldTheme
|
||||
@saveSettings({theme: theme})
|
||||
|
||||
@$scope.$watch "settings.fontSize", (fontSize, oldFontSize) =>
|
||||
if fontSize != oldFontSize
|
||||
@saveSettings({fontSize: parseInt(fontSize, 10)})
|
||||
|
||||
@$scope.$watch "settings.mode", (mode, oldMode) =>
|
||||
if mode != oldMode
|
||||
@saveSettings({mode: mode})
|
||||
|
||||
@$scope.$watch "settings.autoComplete", (autoComplete, oldAutoComplete) =>
|
||||
if autoComplete != oldAutoComplete
|
||||
@saveSettings({autoComplete: autoComplete})
|
||||
|
||||
@$scope.$watch "project.spellCheckLanguage", (language, oldLanguage) =>
|
||||
return if @ignoreUpdates
|
||||
if oldLanguage? and language != oldLanguage
|
||||
@saveProjectSettings({spellCheckLanguage: language})
|
||||
# Also set it as the default for the user
|
||||
@saveSettings({spellCheckLanguage: language})
|
||||
|
||||
@$scope.$watch "project.compiler", (compiler, oldCompiler) =>
|
||||
return if @ignoreUpdates
|
||||
if oldCompiler? and compiler != oldCompiler
|
||||
@saveProjectSettings({compiler: compiler})
|
||||
|
||||
@ide.socket.on "compilerUpdated", (compiler) =>
|
||||
@ignoreUpdates = true
|
||||
@$scope.$apply () =>
|
||||
@$scope.project.compiler = compiler
|
||||
delete @ignoreUpdates
|
||||
|
||||
@ide.socket.on "spellCheckLanguageUpdated", (languageCode) =>
|
||||
@ignoreUpdates = true
|
||||
@$scope.$apply () =>
|
||||
@$scope.project.spellCheckLanguage = languageCode
|
||||
delete @ignoreUpdates
|
||||
|
||||
saveSettings: (data) ->
|
||||
data._csrf = window.csrfToken
|
||||
@ide.$http.post "/user/settings", data
|
||||
|
||||
saveProjectSettings: (data) ->
|
||||
data._csrf = window.csrfToken
|
||||
@ide.$http.post "/project/#{@ide.project_id}/settings", data
|
|
@ -0,0 +1,51 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.controller "SettingsController", ["$scope", "settings", "ide", ($scope, settings, ide) ->
|
||||
if $scope.settings.mode not in ["default", "vim", "emacs"]
|
||||
$scope.settings.mode = "default"
|
||||
|
||||
$scope.$watch "settings.theme", (theme, oldTheme) =>
|
||||
if theme != oldTheme
|
||||
settings.saveSettings({theme: theme})
|
||||
|
||||
$scope.$watch "settings.fontSize", (fontSize, oldFontSize) =>
|
||||
if fontSize != oldFontSize
|
||||
settings.saveSettings({fontSize: parseInt(fontSize, 10)})
|
||||
|
||||
$scope.$watch "settings.mode", (mode, oldMode) =>
|
||||
if mode != oldMode
|
||||
settings.saveSettings({mode: mode})
|
||||
|
||||
$scope.$watch "settings.autoComplete", (autoComplete, oldAutoComplete) =>
|
||||
if autoComplete != oldAutoComplete
|
||||
settings.saveSettings({autoComplete: autoComplete})
|
||||
|
||||
$scope.$watch "settings.pdfViewer", (pdfViewer, oldPdfViewer) =>
|
||||
if pdfViewer != oldPdfViewer
|
||||
settings.saveSettings({pdfViewer: pdfViewer})
|
||||
|
||||
$scope.$watch "project.spellCheckLanguage", (language, oldLanguage) =>
|
||||
return if @ignoreUpdates
|
||||
if oldLanguage? and language != oldLanguage
|
||||
settings.saveProjectSettings({spellCheckLanguage: language})
|
||||
# Also set it as the default for the user
|
||||
settings.saveSettings({spellCheckLanguage: language})
|
||||
|
||||
$scope.$watch "project.compiler", (compiler, oldCompiler) =>
|
||||
return if @ignoreUpdates
|
||||
if oldCompiler? and compiler != oldCompiler
|
||||
settings.saveProjectSettings({compiler: compiler})
|
||||
|
||||
ide.socket.on "compilerUpdated", (compiler) =>
|
||||
@ignoreUpdates = true
|
||||
$scope.$apply () =>
|
||||
$scope.project.compiler = compiler
|
||||
delete @ignoreUpdates
|
||||
|
||||
ide.socket.on "spellCheckLanguageUpdated", (languageCode) =>
|
||||
@ignoreUpdates = true
|
||||
$scope.$apply () =>
|
||||
$scope.project.spellCheckLanguage = languageCode
|
||||
delete @ignoreUpdates
|
||||
]
|
5
services/web/public/coffee/app/ide/settings/index.coffee
Normal file
5
services/web/public/coffee/app/ide/settings/index.coffee
Normal file
|
@ -0,0 +1,5 @@
|
|||
define [
|
||||
"ide/settings/services/settings"
|
||||
"ide/settings/controllers/SettingsController"
|
||||
], () ->
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.factory "settings", ["ide", (ide) ->
|
||||
return {
|
||||
saveSettings: (data) ->
|
||||
data._csrf = window.csrfToken
|
||||
ide.$http.post "/user/settings", data
|
||||
|
||||
saveProjectSettings: (data) ->
|
||||
data._csrf = window.csrfToken
|
||||
ide.$http.post "/project/#{ide.project_id}/settings", data
|
||||
}
|
||||
]
|
|
@ -0,0 +1,11 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.controller "ShareController", ["$scope", "$modal", ($scope, $modal) ->
|
||||
$scope.openShareProjectModal = () ->
|
||||
$modal.open(
|
||||
templateUrl: "shareProjectModalTemplate"
|
||||
controller: "ShareProjectModalController"
|
||||
scope: $scope
|
||||
)
|
||||
]
|
|
@ -0,0 +1,104 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.controller "ShareProjectModalController", ["$scope", "$modalInstance", "$timeout", "projectMembers", "$modal", ($scope, $modalInstance, $timeout, projectMembers, $modal) ->
|
||||
$scope.inputs = {
|
||||
privileges: "readAndWrite"
|
||||
email: ""
|
||||
}
|
||||
$scope.state = {
|
||||
error: null
|
||||
inflight: false
|
||||
startedFreeTrial: false
|
||||
}
|
||||
|
||||
$modalInstance.opened.then () ->
|
||||
$timeout () ->
|
||||
$scope.$broadcast "open"
|
||||
, 200
|
||||
|
||||
INFINITE_COLLABORATORS = -1
|
||||
$scope.$watch "project.members.length", (noOfMembers) ->
|
||||
allowedNoOfMembers = $scope.project.features.collaborators
|
||||
$scope.canAddCollaborators = noOfMembers < allowedNoOfMembers or allowedNoOfMembers == INFINITE_COLLABORATORS
|
||||
|
||||
$scope.addMember = () ->
|
||||
console.log "EMAIL", $scope.inputs.email
|
||||
return if !$scope.inputs.email? or $scope.inputs.email == ""
|
||||
$scope.state.error = null
|
||||
$scope.state.inflight = true
|
||||
projectMembers
|
||||
.addMember($scope.inputs.email, $scope.inputs.privileges)
|
||||
.then (user) ->
|
||||
$scope.state.inflight = false
|
||||
$scope.inputs.email = ""
|
||||
console.log "GOT USER", user
|
||||
$scope.project.members.push user
|
||||
.catch () ->
|
||||
$scope.state.inflight = false
|
||||
$scope.state.error = "Sorry, something went wrong :("
|
||||
|
||||
|
||||
$scope.removeMember = (member) ->
|
||||
$scope.state.error = null
|
||||
$scope.state.inflight = true
|
||||
projectMembers
|
||||
.removeMember(member)
|
||||
.then () ->
|
||||
$scope.state.inflight = false
|
||||
index = $scope.project.members.indexOf(member)
|
||||
return if index == -1
|
||||
$scope.project.members.splice(index, 1)
|
||||
.catch () ->
|
||||
$scope.state.inflight = false
|
||||
$scope.state.error = "Sorry, something went wrong :("
|
||||
|
||||
$scope.startFreeTrial = () ->
|
||||
ga?('send', 'event', 'subscription-funnel', 'upgraded-free-trial', "projectMembers")
|
||||
window.open("/user/subscription/plans")
|
||||
$scope.state.startedFreeTrial = true
|
||||
|
||||
$scope.openMakePublicModal = () ->
|
||||
$modal.open {
|
||||
templateUrl: "makePublicModalTemplate"
|
||||
controller: "MakePublicModalController"
|
||||
scope: $scope
|
||||
}
|
||||
|
||||
$scope.openMakePrivateModal = () ->
|
||||
$modal.open {
|
||||
templateUrl: "makePrivateModalTemplate"
|
||||
controller: "MakePrivateModalController"
|
||||
scope: $scope
|
||||
}
|
||||
|
||||
$scope.done = () ->
|
||||
$modalInstance.close()
|
||||
|
||||
$scope.cancel = () ->
|
||||
$modalInstance.dismiss()
|
||||
]
|
||||
|
||||
App.controller "MakePublicModalController", ["$scope", "$modalInstance", "settings", ($scope, $modalInstance, settings) ->
|
||||
$scope.inputs = {
|
||||
privileges: "readAndWrite"
|
||||
}
|
||||
|
||||
$scope.makePublic = () ->
|
||||
$scope.project.publicAccesLevel = $scope.inputs.privileges
|
||||
settings.saveProjectSettings({publicAccessLevel: $scope.inputs.privileges})
|
||||
$modalInstance.close()
|
||||
|
||||
$scope.cancel = () ->
|
||||
$modalInstance.dismiss()
|
||||
]
|
||||
|
||||
App.controller "MakePrivateModalController", ["$scope", "$modalInstance", "settings", ($scope, $modalInstance, settings) ->
|
||||
$scope.makePrivate = () ->
|
||||
$scope.project.publicAccesLevel = "private"
|
||||
settings.saveProjectSettings({publicAccessLevel: "private"})
|
||||
$modalInstance.close()
|
||||
|
||||
$scope.cancel = () ->
|
||||
$modalInstance.dismiss()
|
||||
]
|
5
services/web/public/coffee/app/ide/share/index.coffee
Normal file
5
services/web/public/coffee/app/ide/share/index.coffee
Normal file
|
@ -0,0 +1,5 @@
|
|||
define [
|
||||
"ide/share/controllers/ShareController"
|
||||
"ide/share/controllers/ShareProjectModalController"
|
||||
"ide/share/services/projectMembers"
|
||||
], () ->
|
|
@ -0,0 +1,30 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.factory "projectMembers", ["ide", "$q", (ide, $q) ->
|
||||
return {
|
||||
removeMember: (member) ->
|
||||
deferred = $q.defer()
|
||||
|
||||
ide.socket.emit "removeUserFromProject", member._id, (error) =>
|
||||
if error?
|
||||
return deferred.reject(error)
|
||||
deferred.resolve()
|
||||
|
||||
return deferred.promise
|
||||
|
||||
addMember: (email, privileges) ->
|
||||
deferred = $q.defer()
|
||||
|
||||
ide.socket.emit "addUserToProject", email, privileges, (error, user) =>
|
||||
if error?
|
||||
return deferred.reject(error)
|
||||
|
||||
if !user
|
||||
deferred.reject()
|
||||
else
|
||||
deferred.resolve(user)
|
||||
|
||||
return deferred.promise
|
||||
}
|
||||
]
|
|
@ -110,9 +110,9 @@ define [
|
|||
.get(url)
|
||||
.success (data) =>
|
||||
diff.loading = false
|
||||
{text, annotations} = @_parseDiff(data)
|
||||
{text, highlights} = @_parseDiff(data)
|
||||
diff.text = text
|
||||
diff.annotations = annotations
|
||||
diff.highlights = highlights
|
||||
.error () ->
|
||||
diff.loading = false
|
||||
diff.error = true
|
||||
|
@ -129,7 +129,7 @@ define [
|
|||
_parseDiff: (diff) ->
|
||||
row = 0
|
||||
column = 0
|
||||
annotations = []
|
||||
highlights = []
|
||||
text = ""
|
||||
for entry, i in diff.diff or []
|
||||
content = entry.u or entry.i or entry.d
|
||||
|
@ -162,19 +162,19 @@ define [
|
|||
name = "you"
|
||||
date = moment(entry.meta.end_ts).format("Do MMM YYYY, h:mm a")
|
||||
if entry.i?
|
||||
annotations.push {
|
||||
highlights.push {
|
||||
label: "Added by #{name} on #{date}"
|
||||
highlight: range
|
||||
hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user.id)
|
||||
}
|
||||
else if entry.d?
|
||||
annotations.push {
|
||||
highlights.push {
|
||||
label: "Deleted by #{name} on #{date}"
|
||||
strikeThrough: range
|
||||
hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user.id)
|
||||
}
|
||||
|
||||
return {text, annotations}
|
||||
return {text, highlights}
|
||||
|
||||
_loadUpdates: (updates = []) ->
|
||||
previousUpdate = @$scope.trackChanges.updates[@$scope.trackChanges.updates.length - 1]
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
@import "./editor/toolbar.less";
|
||||
@import "./editor/left-menu.less";
|
||||
@import "./editor/pdf.less";
|
||||
@import "./editor/share.less";
|
||||
|
||||
.full-size {
|
||||
position: absolute;
|
||||
|
@ -69,6 +70,7 @@
|
|||
background-position: bottom left;
|
||||
}
|
||||
.remote-cursor {
|
||||
position: absolute;
|
||||
border-left: 2px solid transparent;
|
||||
.nubbin {
|
||||
height: 5px;
|
||||
|
|
|
@ -1,9 +1,24 @@
|
|||
.pdf-viewer, .logs {
|
||||
.pdf-viewer, .pdf-logs, .pdf-errors, .pdf-uncompiled {
|
||||
.full-size;
|
||||
top: 58px;
|
||||
}
|
||||
|
||||
.pdf-logs, .pdf-errors, .pdf-uncompiled {
|
||||
padding: @line-height-computed / 2;
|
||||
}
|
||||
|
||||
.pdf-uncompiled {
|
||||
.fa {
|
||||
color: @blue;
|
||||
}
|
||||
}
|
||||
|
||||
.pdf-viewer {
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
.pdfjs-viewer {
|
||||
.full-size;
|
||||
background-color: @gray-lighter;
|
||||
|
@ -59,20 +74,27 @@
|
|||
}
|
||||
|
||||
.log-btn {
|
||||
position: relative;
|
||||
.label {
|
||||
margin-left: .4em;
|
||||
padding: .2em .4em .3em;
|
||||
vertical-align: text-bottom;
|
||||
line-height: inherit;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: .15em .6em .2em;
|
||||
font-size: 60%;
|
||||
}
|
||||
&.active, &:active {
|
||||
.label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logs {
|
||||
padding: @line-height-computed / 2;
|
||||
.pdf-logs {
|
||||
overflow: auto;
|
||||
.alert {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: @line-height-computed / 2;
|
||||
cursor: pointer;
|
||||
.line-no {
|
||||
float: right;
|
||||
color: @gray;
|
||||
|
@ -88,5 +110,19 @@
|
|||
//font-family: @font-family-monospace;
|
||||
}
|
||||
}
|
||||
pre {
|
||||
font-size: 12px;
|
||||
}
|
||||
.dropdown {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.synctex-controls {
|
||||
padding: 68px 2px 0;
|
||||
.btn-xs {
|
||||
line-height: 1.3;
|
||||
padding: 0 2px 0;
|
||||
}
|
||||
}
|
||||
|
50
services/web/public/stylesheets/app/editor/share.less
Normal file
50
services/web/public/stylesheets/app/editor/share.less
Normal file
|
@ -0,0 +1,50 @@
|
|||
.modal-body-share {
|
||||
h3 {
|
||||
border-bottom: 1px solid @gray-lighter;
|
||||
padding-bottom: @line-height-computed / 4;
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.project-member, .public-access-level {
|
||||
padding: (@line-height-computed / 2) 0;
|
||||
border-bottom: 1px solid @gray-lighter;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.public-access-level {
|
||||
color: @gray;
|
||||
padding-top: 0;
|
||||
font-size: 12px;
|
||||
padding-bottom: @line-height-computed;
|
||||
}
|
||||
|
||||
.project-member {
|
||||
&:hover {
|
||||
background-color: @gray-lightest;
|
||||
}
|
||||
}
|
||||
|
||||
.invite-controls {
|
||||
.small {
|
||||
padding: 2px;
|
||||
}
|
||||
padding: @line-height-computed / 2;
|
||||
background-color: @gray-lightest;
|
||||
margin-top: @line-height-computed / 2;
|
||||
form {
|
||||
.form-group {
|
||||
margin-bottom: @line-height-computed / 2;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.privileges {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -126,6 +126,10 @@
|
|||
.btn-block + .btn-block {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.modal-footer-left {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
// Scale up the modal
|
||||
|
|
|
@ -296,6 +296,7 @@ describe "EditorController", ->
|
|||
@email = "Jane.Doe@example.com"
|
||||
@priveleges = "readOnly"
|
||||
@addedUser = { _id: "added-user" }
|
||||
@ProjectEditorHandler.buildUserModelView = sinon.stub().returns(@addedUser)
|
||||
@CollaboratorsHandler.addUserToProject = sinon.stub().callsArgWith(3, null, @addedUser)
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
@callback = sinon.stub()
|
||||
|
@ -314,9 +315,9 @@ describe "EditorController", ->
|
|||
@EditorRealTimeController.emitToRoom.calledWith(@project_id, "userAddedToProject", @addedUser).should.equal true
|
||||
done()
|
||||
|
||||
it "should return true to the callback", (done)->
|
||||
it "should return the user to the callback", (done)->
|
||||
@EditorController.addUserToProject @project_id, @email, @priveleges, (err, result)=>
|
||||
result.should.equal true
|
||||
result.should.equal @addedUser
|
||||
done()
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue