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

This commit is contained in:
Henry Oswald 2014-07-14 12:42:03 +01:00
commit 5093820a5f
18 changed files with 372 additions and 67 deletions

View file

@ -16,9 +16,9 @@ module.exports = AuthenticationController =
if !isAllowed
logger.log email:email, "too many login requests"
res.statusCode = 429
return res.send
return res.send
message:
text: 'This account has had too many login requests, <br> please wait 2 minutes before trying to log in again',
text: 'This account has had too many login requests. Please wait 2 minutes before trying to log in again',
type: 'error'
AuthenticationManager.authenticate email: email, password, (error, user) ->
return next(error) if error?
@ -100,7 +100,7 @@ module.exports = AuthenticationController =
callback()
_establishUserSession: (req, user, callback = (error) ->) ->
lightUser =
lightUser =
_id: user._id
first_name: user.first_name
last_name: user.last_name
@ -110,7 +110,3 @@ module.exports = AuthenticationController =
req.session.user = lightUser
req.session.justLoggedIn = true
req.session.save callback

View file

@ -1,5 +1,5 @@
aside#file-tree(ng-controller="FileTreeController")
.toolbar.toolbar-small(ng-if="permissions.write")
.toolbar.toolbar-small.toolbar-alt(ng-if="permissions.write")
a(
href,
ng-click="openNewDocModal()",
@ -40,7 +40,7 @@ aside#file-tree(ng-controller="FileTreeController")
i.fa.fa-trash-o
.file-tree-inner(
ng-if="rootFolder",
ng-if="rootFolder",
ng-controller="FileTreeRootFolderController",
ng-class="{ 'no-toolbar': !permissions.write }"
)
@ -284,7 +284,7 @@ script(type='text/ng-template', id='entityListItemTemplate')
permissions="permissions",
ng-repeat="child in entity.children | orderBy:[orderByFoldersFirst, 'name']"
)
script(type='text/ng-template', id='newDocModalTemplate')
.modal-header
h3 New File

View file

@ -80,22 +80,41 @@ div#trackChanges(ng-show="ui.view == 'track-changes'")
i.fa.fa-spin.fa-refresh
| &nbsp;&nbsp;Loading...
.diff.full-size(ng-controller="TrackChangesDiffController")
.diff-editor.hide-ace-cursor(
ace-editor="track-changes",
ng-show="!!trackChanges.diff && !trackChanges.diff.loading && !trackChanges.diff.deleted && !trackChanges.diff.error",
theme="settings.theme",
font-size="settings.fontSize",
text="trackChanges.diff.text",
highlights="trackChanges.diff.highlights",
read-only="true",
resize-on="layout:main:resize"
.diff-panel.full-size(ng-controller="TrackChangesDiffController")
.diff(
ng-show="!!trackChanges.diff && !trackChanges.diff.loading && !trackChanges.diff.deleted && !trackChanges.diff.error"
)
.toolbar.toolbar-alt
span.name
| <strong>{{trackChanges.diff.highlights.length}} </strong>
ng-pluralize(
count="trackChanges.diff.highlights.length",
when="{\
'one': 'change',\
'other': 'changes'\
}"
)
| in <strong>{{trackChanges.diff.doc.name}}</strong>
.toolbar-right
a.btn.btn-danger.btn-sm(
href,
ng-click="openRestoreDiffModal()"
) Restore to before these changes
.diff-editor.hide-ace-cursor(
ace-editor="track-changes",
theme="settings.theme",
font-size="settings.fontSize",
text="trackChanges.diff.text",
highlights="trackChanges.diff.highlights",
read-only="true",
resize-on="layout:main:resize",
navigate-highlights="true"
)
.diff-deleted.text-centered(
ng-show="trackChanges.diff.deleted"
)
p.text-serif {{ trackChanges.diff.doc.name }} has been deleted.
p
p
a.btn.btn-primary.btn-lg(
href,
ng-click="restoreDeletedDoc()"
@ -104,4 +123,26 @@ div#trackChanges(ng-show="ui.view == 'track-changes'")
i.fa.fa-spin.fa-refresh
| &nbsp;&nbsp;Loading...
.error-panel(ng-show="trackChanges.diff.error")
.alert.alert-danger Sorry, something went wrong :(
.alert.alert-danger Sorry, something went wrong :(
script(type="text/ng-template", id="trackChangesRestoreDiffModalTemplate")
.modal-header
button.close(
type="button"
data-dismiss="modal"
ng-click="cancel()"
) &times;
h3 Restore {{diff.doc.name}}
.modal-body.modal-body-share
p Are you sure you want to restore <strong>{{diff.doc.name}}</strong> to before the changes on {{diff.start_ts | formatDate}}?
.modal-footer
button.btn.btn-default(
ng-click="cancel()",
ng-disabled="state.inflight"
) Cancel
button.btn.btn-danger(
ng-click="restore()",
ng-disabled="state.inflight"
)
span(ng-show="!state.inflight") Restore
span(ng-show="state.inflight") Restoring...

View file

@ -35,7 +35,9 @@ block content
| Required
.actions
button.btn-primary.btn(
type='submit'
ng-disabled="loginForm.$invalid"
) Login
type='submit',
ng-disabled="loginForm.inflight"
)
span(ng-show="!loginForm.inflight") Login
span(ng-show="loginForm.inflight") Logging in...
a.pull-right(href='/user/password/reset') Forgot your password?

View file

@ -10,7 +10,7 @@ block content
div Join ShareLaTeX to view this project
else if newTemplateData.templateName !== undefined
h1 Please register to edit the '#{newTemplateData.templateName}' template
div Already have a ShareLaTeX account?
div Already have a ShareLaTeX account?
a(href="/login") Login here
.row
@ -18,12 +18,12 @@ block content
.card
.page-header
h1 Register
form(async-form="register", name="registerForm", action="/register", novalidate, ng-cloak)
form(async-form="register", name="registerForm", action="/register", ng-cloak)
input(name='_csrf', type='hidden', value=csrfToken)
input(name='redir', type='hidden', value=redir)
form-messages(for="registerForm")
.form-group
label(for='email') Email
label(for='email') Email
input.form-control(
type='email',
name='email',
@ -36,7 +36,7 @@ block content
span.small.text-primary(ng-show="registerForm.email.$invalid && registerForm.email.$dirty")
| Must be an email address
.form-group
label(for='password') Password
label(for='password') Password
input.form-control(
type='password',
name='password',
@ -49,6 +49,7 @@ block content
.actions
button.btn-primary.btn(
type='submit'
ng-disabled="register.$invalid"
) Register
ng-disabled="registerForm.inflight"
)
span(ng-show="!registerForm.inflight") Register
span(ng-show="registerForm.inflight") Registering...

View file

@ -7,6 +7,7 @@ define [
formName = attrs.asyncForm
scope[attrs.name].response = response = {}
scope[attrs.name].inflight = false
element.on "submit", (e) ->
e.preventDefault()
@ -15,9 +16,12 @@ define [
for data in element.serializeArray()
formData[data.name] = data.value
scope[attrs.name].inflight = true
$http
.post(element.attr('action'), formData)
.success (data, status, headers, config) ->
scope[attrs.name].inflight = false
response.success = true
response.error = false
@ -33,12 +37,13 @@ define [
ga('send', 'event', formName, 'failure', data.message)
else
ga('send', 'event', formName, 'success')
.error (data, status, headers, config) ->
scope[attrs.name].inflight = false
response.success = false
response.error = true
response.message =
text: data.message or "Something went wrong talking to the server :(. Please try again."
text: data.message?.text or "Something went wrong talking to the server :(. Please try again."
type: 'error'
ga('send', 'event', formName, 'failure', data.message)
}
@ -60,4 +65,4 @@ define [
form: "=for"
}
}
}

View file

@ -1,15 +1,18 @@
define [
"base"
"ace/ace"
"ace/ext-searchbox"
"ide/editor/directives/aceEditor/undo/UndoManager"
"ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager"
"ide/editor/directives/aceEditor/spell-check/SpellCheckManager"
"ide/editor/directives/aceEditor/highlights/HighlightsManager"
"ide/editor/directives/aceEditor/cursor-position/CursorPositionManager"
], (App, Ace, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager) ->
], (App, Ace, SearchBox, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager) ->
EditSession = require('ace/edit_session').EditSession
App.directive "aceEditor", ["$timeout", ($timeout) ->
App.directive "aceEditor", ["$timeout", "$compile", "$rootScope", ($timeout, $compile, $rootScope) ->
monkeyPatchSearch($rootScope, $compile)
return {
scope: {
theme: "="
@ -24,6 +27,7 @@ define [
readOnly: "="
gotoLine: "="
annotations: "="
navigateHighlights: "="
}
link: (scope, element, attrs) ->
# Don't freak out if we're already in an apply callback
@ -57,6 +61,20 @@ define [
editor.commands.removeCommand "showSettingsMenu"
editor.commands.removeCommand "foldall"
# Trigger search AND replace on CMD+F
editor.commands.addCommand
name: "find",
bindKey: win: "Ctrl-F", mac: "Command-F"
exec: (editor) ->
Ace.require("ace/ext/searchbox").Search(editor, true)
readOnly: true
editor.commands.removeCommand "replace"
# Make '/' work for search in vim mode.
editor.showCommandLine = (arg) =>
if arg == "/"
Ace.require("ace/ext/searchbox").Search(editor, true)
if attrs.resizeOn?
for event in attrs.resizeOn.split(",")
scope.$on event, () ->
@ -95,7 +113,7 @@ define [
scope.$watch "annotations", (annotations) ->
if annotations?
session = editor.getSession()
session = editor.getSession()
session.setAnnotations annotations
scope.$watch "readOnly", (value) ->
@ -107,11 +125,11 @@ define [
session.setMode("ace/mode/latex")
session.setAnnotations scope.annotations
emitChange = () ->
emitChange = () ->
scope.$emit "#{scope.name}:change"
attachToAce = (sharejs_doc) ->
lines = sharejs_doc.getSnapshot().split("\n")
lines = sharejs_doc.getSnapshot().split("\n")
editor.setSession(new EditSession(lines))
resetSession()
session = editor.getSession()
@ -149,7 +167,7 @@ define [
>Dismiss</a>
</div>
<div class="ace-editor-body"></div>
<div
<div
id="spellCheckMenu"
class="dropdown context-menu"
ng-show="spellingMenu.open"
@ -180,7 +198,71 @@ define [
>
{{ annotationLabel.text }}
</div>
<a
href
class="highlights-before-label btn btn-info btn-xs"
ng-show="updateLabels.highlightsBefore > 0"
ng-click="gotoHighlightAbove()"
>
<i class="fa fa-fw fa-arrow-up"></i>
{{ updateLabels.highlightsBefore }} more update{{ updateLabels.highlightsBefore > 1 && "" || "s" }} above
</a>
<a
href
class="highlights-after-label btn btn-info btn-xs"
ng-show="updateLabels.highlightsAfter > 0"
ng-click="gotoHighlightBelow()"
>
<i class="fa fa-fw fa-arrow-down"></i>
{{ updateLabels.highlightsAfter }} more update{{ updateLabels.highlightsAfter > 1 && "" || "s" }} below
</a>
</div>
"""
}
]
]
monkeyPatchSearch = ($rootScope, $compile) ->
SearchBox = Ace.require("ace/ext/searchbox").SearchBox
searchHtml = """
<div class="ace_search right">
<a href type="button" action="hide" class="ace_searchbtn_close">
<i class="fa fa-fw fa-times"></i>
</a>
<div class="ace_search_form">
<input class="ace_search_field form-control input-sm" placeholder="Search for" spellcheck="false"></input>
<div class="btn-group">
<button type="button" action="findNext" class="ace_searchbtn next btn btn-default btn-sm">
<i class="fa fa-chevron-down fa-fw"></i>
</button>
<button type="button" action="findPrev" class="ace_searchbtn prev btn btn-default btn-sm">
<i class="fa fa-chevron-up fa-fw"></i>
</button>
</div>
</div>
<div class="ace_replace_form">
<input class="ace_search_field form-control input-sm" placeholder="Replace with" spellcheck="false"></input>
<div class="btn-group">
<button type="button" action="replaceAndFindNext" class="ace_replacebtn btn btn-default btn-sm">Replace</button>
<button type="button" action="replaceAll" class="ace_replacebtn btn btn-default btn-sm">All</button>
</div>
</div>
<div class="ace_search_options">
<div class="btn-group">
<span action="toggleRegexpMode" class="btn btn-default btn-sm" tooltip-placement="bottom" tooltip-append-to-body="true" tooltip="RegExp Search">.*</span>
<span action="toggleCaseSensitive" class="btn btn-default btn-sm" tooltip-placement="bottom" tooltip-append-to-body="true" tooltip="CaseSensitive Search">Aa</span>
<span action="toggleWholeWords" class="btn btn-default btn-sm" tooltip-placement="bottom" tooltip-append-to-body="true" tooltip="Whole Word Search">"..."</span>
</div>
</div>
</div>
"""
# Remove Ace CSS
$("#ace_searchbox").remove()
$init = SearchBox::$init
SearchBox::$init = () ->
@element = $compile(searchHtml)($rootScope.$new())[0];
$init.apply(@)

View file

@ -86,16 +86,17 @@ define [
Autocomplete::_insertMatch.call this, data
# Overwrite this to set autoInsert = false
# Overwrite this to set autoInsert = false and set font size
Autocomplete.startCommand = {
name: "startAutocomplete",
exec: (editor) ->
exec: (editor) =>
if (!editor.completer)
editor.completer = new Autocomplete()
editor.completer.autoInsert = false
editor.completer.autoSelect = true
editor.completer.showPopup(editor)
editor.completer.cancelContextMenu()
$(editor.completer.popup.container).css({'font-size': @$scope.fontSize + 'px'})
bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space"
}

View file

@ -18,6 +18,11 @@ define [
text: ""
}
@$scope.updateLabels = {
updatesAbove: 0
updatesBelow: 0
}
@$scope.$watch "highlights", (value) =>
@redrawAnnotations()
@ -29,9 +34,30 @@ define [
e.position = position
@showAnnotationLabels(position)
@editor.on "changeSession", () =>
onChangeScrollTop = () =>
@updateShowMoreLabels()
@editor.getSession().on "changeScrollTop", onChangeScrollTop
@$scope.$watch "text", () =>
if @$scope.navigateHighlights
setTimeout () =>
@scrollToFirstHighlight()
, 0
@editor.on "changeSession", (e) =>
e.oldSession?.off "changeScrollTop", onChangeScrollTop
e.session.on "changeScrollTop", onChangeScrollTop
@redrawAnnotations()
@$scope.gotoHighlightBelow = () =>
return if !@firstHiddenHighlightAfter?
@editor.scrollToLine(@firstHiddenHighlightAfter.end.row, true, false)
@$scope.gotoHighlightAbove = () =>
return if !@lastHiddenHighlightBefore?
@editor.scrollToLine(@lastHiddenHighlightBefore.start.row, true, false)
redrawAnnotations: () ->
@_clearMarkers()
@_clearLabels()
@ -71,6 +97,8 @@ define [
}
@_drawStrikeThrough(annotation, colorScheme)
@updateShowMoreLabels()
showAnnotationLabels: (position) ->
labelToShow = null
for label in @labels or []
@ -127,6 +155,39 @@ define [
text: labelToShow.text
}
updateShowMoreLabels: () ->
return if !@$scope.navigateHighlights
setTimeout () =>
firstRow = @editor.getFirstVisibleRow()
lastRow = @editor.getLastVisibleRow()
highlightsBefore = 0
highlightsAfter = 0
@lastHiddenHighlightBefore = null
@firstHiddenHighlightAfter = null
for annotation in @$scope.highlights or []
range = annotation.highlight or annotation.strikeThrough
continue if !range?
if range.start.row < firstRow
highlightsBefore += 1
@lastHiddenHighlightBefore = range
if range.end.row > lastRow
highlightsAfter += 1
@firstHiddenHighlightAfter ||= range
@$scope.$apply =>
@$scope.updateLabels = {
highlightsBefore: highlightsBefore
highlightsAfter: highlightsAfter
}
, 100
scrollToFirstHighlight: () ->
for annotation in @$scope.highlights or []
range = annotation.highlight or annotation.strikeThrough
continue if !range?
@editor.scrollToLine(range.start.row, true, false)
break
_clearMarkers: () ->
for marker_id in @markerIds
@editor.getSession().removeMarker(marker_id)
@ -177,9 +238,9 @@ define [
new Range(
annotation.strikeThrough.start.row, annotation.strikeThrough.start.column,
annotation.strikeThrough.end.row, annotation.strikeThrough.end.column + 1
),
),
"annotation strike-through-foreground",
true,
true,
"""
height: #{Math.round(lineHeight/2) + 2}px;
border-bottom: 2px solid #{colorScheme.strikeThroughForegroundColor};
@ -223,4 +284,4 @@ define [
r = parseInt(r, 10)
g = parseInt(g, 10)
b = parseInt(b, 10)
return r + g + b < 3 * 128
return r + g + b < 3 * 128

View file

@ -81,7 +81,7 @@ define [
reloadDiff: () ->
diff = @$scope.trackChanges.diff
{updates, doc} = @$scope.trackChanges.selection
{fromV, toV} = @_calculateRangeFromSelection()
{fromV, toV, start_ts, end_ts} = @_calculateRangeFromSelection()
return if !doc?
@ -91,10 +91,12 @@ define [
diff.toV == toV
@$scope.trackChanges.diff = diff = {
fromV: fromV
toV: toV
doc: doc
error: false
fromV: fromV
toV: toV
start_ts: start_ts
end_ts: end_ts
doc: doc
error: false
}
if !doc.deleted
@ -117,10 +119,12 @@ define [
diff.deleted = true
restoreDeletedDoc: (doc) ->
@ide.$http.post "/project/#{@$scope.project_id}/doc/#{doc.id}/restore", {
name: doc.name
_csrf: window.csrfToken
}
url = "/project/#{@$scope.project_id}/doc/#{doc.id}/restore"
@ide.$http.post(url, name: doc.name, _csrf: window.csrfToken)
restoreDiff: (diff) ->
url = "/project/#{@$scope.project_id}/doc/#{diff.doc.id}/version/#{diff.fromV}/restore"
@ide.$http.post(url, _csrf: window.csrfToken)
_parseDiff: (diff) ->
row = 0

View file

@ -1,9 +1,34 @@
define [
"base"
], (App) ->
App.controller "TrackChangesDiffController", ["$scope", "ide", ($scope, ide) ->
App.controller "TrackChangesDiffController", ($scope, $modal, ide) ->
$scope.restoreDeletedDoc = () ->
ide.trackChangesManager.restoreDeletedDoc(
$scope.trackChanges.diff.doc
)
]
$scope.openRestoreDiffModal = () ->
$modal.open {
templateUrl: "trackChangesRestoreDiffModalTemplate"
controller: "TrackChangesRestoreDiffModalController"
resolve:
diff: () -> $scope.trackChanges.diff
}
App.controller "TrackChangesRestoreDiffModalController", ($scope, $modalInstance, diff, ide) ->
$scope.state =
inflight: false
$scope.diff = diff
$scope.restore = () ->
$scope.state.inflight = true
ide.trackChangesManager
.restoreDiff(diff)
.success () ->
$scope.state.inflight = false
$modalInstance.close()
ide.editorManager.openDoc(diff.doc)
$scope.cancel = () ->
$modalInstance.dismiss()

View file

@ -6,6 +6,7 @@
@import "./editor/share.less";
@import "./editor/chat.less";
@import "./editor/binary-file.less";
@import "./editor/search.less";
.full-size {
position: absolute;
@ -152,6 +153,16 @@
position: absolute;
z-index: 2;
}
.highlights-before-label {
position: absolute;
top: @line-height-computed / 2;
right: @line-height-computed;
}
.highlights-after-label {
position: absolute;
bottom: @line-height-computed / 2;
right: @line-height-computed;
}
}
.ui-layout-resizer {
@ -197,4 +208,3 @@
position: fixed;
z-index: 100;
}

View file

@ -1,7 +1,5 @@
aside#file-tree {
background-color: #fafafa;
.file-tree-inner {
position: absolute;
top: 32px;
@ -93,4 +91,4 @@ aside#file-tree {
ul.droppable-hover {
background-color: @file-tree-droppable-background-color;
}
}
}

View file

@ -127,10 +127,10 @@
}
.synctex-controls {
padding: 68px 2px 0;
top: 68px;
padding: 0px 2px;
.btn-xs {
line-height: 1.3;
padding: 0 2px 0;
}
}

View file

@ -0,0 +1,57 @@
.ace_search {
background-color: @gray-lightest;
border: 1px solid @toolbar-border-color;
border-top: 0 none;
width: 350px;
overflow: hidden;
position: absolute;
top: 0px;
z-index: 99;
white-space: normal;
padding: @line-height-computed / 4;
font-family: @font-family-sans-serif;
a, button {
i {
pointer-events: none;
}
}
.ace_searchbtn_close {
position: absolute;
top: 6px;
right: 12px;
color: @gray;
&:hover {
color: @gray-dark;
}
}
.ace_search_form, .ace_replace_form {
margin-bottom: @line-height-computed / 4;
input {
width: 210px;
display: inline-block;
}
.btn-group {
display: inline-block;
}
}
.ace_nomatch {
input {
border-color: @red;
}
}
}
.ace_search.left {
border-left: 0 none;
border-radius: 0px 0px @border-radius-base 0px;
left: 0;
}
.ace_search.right {
border-radius: 0px 0px 0px @border-radius-base;
border-right: 0 none;
right: 0;
}

View file

@ -84,4 +84,8 @@
padding: 4px 10px 5px;
}
}
&.toolbar-alt {
background-color: #fafafa;
}
}

View file

@ -29,8 +29,25 @@
}
}
.diff {
.diff-panel {
.full-size;
margin-right: @changesListWidth;
}
.diff {
.full-size;
.toolbar {
padding: 3px;
.name {
float: left;
padding: 3px @line-height-computed / 4;
display: inline-block;
}
}
.diff-editor {
.full-size;
top: 40px;
}
.hide-ace-cursor {
.ace_active-line, .ace_cursor-layer, .ace_gutter-active-line {
display: none;
@ -245,4 +262,4 @@
}
}
}
}
}

View file

@ -505,6 +505,7 @@
&:focus,
&:active,
&.active,
&.checked,
.open .dropdown-toggle& {
color: @color;
background-color: darken(@background, 8%);