Просмотр исходного кода

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

Henry Oswald 12 лет назад
Родитель
Сommit
5093820a5f

+ 3 - 7
services/web/app/coffee/Features/Authentication/AuthenticationController.coffee

@@ -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
-		
-		
-	
-		

+ 3 - 3
services/web/app/views/project/editor/file-tree.jade

@@ -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

+ 53 - 12
services/web/app/views/project/editor/track-changes.jade

@@ -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...

+ 5 - 3
services/web/app/views/user/login.jade

@@ -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?

+ 8 - 7
services/web/app/views/user/register.jade

@@ -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...

+ 8 - 3
services/web/public/coffee/directives/asyncForm.coffee

@@ -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"
 			}
 
-		}
+		}

+ 89 - 7
services/web/public/coffee/ide/editor/directives/aceEditor.coffee

@@ -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(@)

+ 3 - 2
services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee

@@ -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"
 				}
 

+ 65 - 4
services/web/public/coffee/ide/editor/directives/aceEditor/highlights/HighlightsManager.coffee

@@ -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

+ 13 - 9
services/web/public/coffee/ide/track-changes/TrackChangesManager.coffee

@@ -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

+ 27 - 2
services/web/public/coffee/ide/track-changes/controllers/TrackChangesDiffController.coffee

@@ -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()

+ 11 - 1
services/web/public/stylesheets/app/editor.less

@@ -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;
 }
-

+ 1 - 3
services/web/public/stylesheets/app/editor/file-tree.less

@@ -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;
 	}
-}
+}

+ 2 - 2
services/web/public/stylesheets/app/editor/pdf.less

@@ -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;
 	}
 }
-				

+ 57 - 0
services/web/public/stylesheets/app/editor/search.less

@@ -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;
+}

+ 4 - 0
services/web/public/stylesheets/app/editor/toolbar.less

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

+ 19 - 2
services/web/public/stylesheets/app/editor/track-changes.less

@@ -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 @@
 			}
 		}
 	}
-}
+}

+ 1 - 0
services/web/public/stylesheets/core/mixins.less

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