Merge branch 'master' into pr-review-panel-onboarding

This commit is contained in:
James Allen 2017-02-13 16:19:56 +01:00
commit 6a4f0ace3b
24 changed files with 185 additions and 42 deletions

View file

@ -53,7 +53,11 @@ module.exports =
if req.body.login_after
UserGetter.getUser user_id, {email: 1}, (err, user) ->
return next(err) if err?
AuthenticationController.doLogin {email:user.email, password: password}, req, res, next
AuthenticationController.afterLoginSessionSetup req, user, (err) ->
if err?
logger.err {err, email: user.email}, "Error setting up session after setting password"
return next(err)
res.json {redir: AuthenticationController._getRedirectFromSession(req) || "/project"}
else
res.sendStatus 200
else

View file

@ -20,10 +20,10 @@ module.exports = ProjectEditorHandler =
if !result.invites?
result.invites = []
hasTrackChanges = false
trackChangesVisible = false
for member in members
if member.privilegeLevel == "owner" and member.user?.featureSwitches?.track_changes
hasTrackChanges = true
trackChangesVisible = true
{owner, ownerFeatures, members} = @buildOwnerAndMembersViews(members)
result.owner = owner
@ -37,7 +37,8 @@ module.exports = ProjectEditorHandler =
compileGroup:"standard"
templates: false
references: false
trackChanges: hasTrackChanges
trackChanges: false
trackChangesVisible: trackChangesVisible
})
return result

View file

@ -37,6 +37,7 @@ UserSchema = new Schema
compileGroup: { type:String, default: Settings.defaultFeatures.compileGroup }
templates: { type:Boolean, default: Settings.defaultFeatures.templates }
references: { type:Boolean, default: Settings.defaultFeatures.references }
trackChanges: { type:Boolean, default: Settings.defaultFeatures.trackChanges }
}
featureSwitches : {
track_changes: { type: Boolean }

View file

@ -53,7 +53,7 @@ div.full-size(
syntax-validation="settings.syntaxValidation",
review-panel="reviewPanel",
events-bridge="reviewPanelEventsBridge"
track-changes-enabled="project.features.trackChanges",
track-changes-enabled="project.features.trackChangesVisible",
track-changes= "editor.trackChanges",
doc-id="editor.open_doc_id"
renderer-data="reviewPanel.rendererData"

View file

@ -87,7 +87,7 @@ header.toolbar.toolbar-header.toolbar-with-labels(
a.btn.btn-full-height(
href,
ng-if="project.features.trackChanges",
ng-if="project.features.trackChangesVisible",
ng-class="{ active: ui.reviewPanelOpen }"
ng-click="toggleReviewPanel()"
)

View file

@ -25,7 +25,13 @@
strong off
span(ng-click="toggleTrackChanges(false)", ng-if="editor.wantTrackChanges === true") Track Changes is
strong on
review-panel-toggle(ng-if="editor.wantTrackChanges == editor.trackChanges", ng-model="editor.wantTrackChanges", on-toggle="toggleTrackChanges")
review-panel-toggle(
ng-if="editor.wantTrackChanges == editor.trackChanges"
ng-model="editor.wantTrackChanges"
on-toggle="toggleTrackChanges"
disabled="!project.features.trackChanges"
on-disabled-click="openTrackChangesUpgradeModal"
)
span.review-panel-toolbar-label.review-panel-toolbar-label-disabled(ng-if="!permissions.write")
span(ng-if="editor.wantTrackChanges === false") Track Changes is
strong off
@ -201,7 +207,7 @@ script(type='text/ng-template', id='commentEntryTemplate')
span.rp-entry-user(
style="color: hsl({{ comment.user.hue }}, 70%, 40%);",
) {{ comment.user.name }}: 
| {{ comment.content }}
span(ng-bind-html="comment.content | linky:'_blank'")
textarea.rp-comment-input(
expandable-text-area
ng-if="comment.editing"
@ -272,7 +278,7 @@ script(type='text/ng-template', id='resolvedCommentEntryTemplate')
style="color: hsl({{ comment.user.hue }}, 70%, 40%);"
ng-if="$first || comment.user.id !== thread.messages[$index - 1].user.id"
) {{ comment.user.name }}: 
| {{ comment.content }}
span(ng-bind-html="comment.content | linky:'_blank'")
.rp-entry-metadata
| {{ comment.timestamp | date : 'MMM d, y h:mm a' }}
.rp-comment.rp-comment-resolver
@ -372,3 +378,49 @@ script(type='text/ng-template', id='resolvedCommentsDropdownTemplate')
.rp-loading(ng-if="!resolvedComments.length")
| No resolved threads.
script(type="text/ng-template", id="trackChangesUpgradeModalTemplate")
.modal-header
button.close(
type="button"
data-dismiss="modal"
ng-click="cancel()"
) ×
h3 Upgrade to Track Changes
.modal-body
.teaser-video-container
video.teaser-video(autoplay, loop)
source(src="/img/teasers/track-changes/teaser-track-changes.mp4", type="video/mp4")
img(src="/img/teasers/track-changes/teaser-track-changes.gif")
h4.teaser-title See changes in your documents, live
p.small(ng-show="startedFreeTrial")
| #{translate("refresh_page_after_starting_free_trial")}
.row
.col-md-10.col-md-offset-1
ul.list-unstyled
li
i.fa.fa-check  
| Track any change, in real-time
li
i.fa.fa-check  
| Review your peers' work
li
i.fa.fa-check  
| Accept or reject each change individually
.row.text-center(ng-controller="FreeTrialModalController")
a.btn.btn-success(
href
ng-click="startFreeTrial('track-changes')"
) Try it for free
.modal-footer()
button.btn.btn-default(
ng-click="cancel()"
)
span #{translate("close")}

View file

@ -2,7 +2,7 @@
span(ng-controller="TranslationsPopupController", ng-cloak)
.translations-message(ng-hide="hidei18nNotification")
a(href=recomendSubdomain.url+currentUrl) !{translate("click_here_to_view_sl_in_lng", {lngName:"<strong>" + translate(recomendSubdomain.lngCode) + "</strong>"})}
img(src=buildImgPath("flags/24/#{recomendSubdomain.lngCode}.png"))
img(src=buildImgPath("flags/24/" + recomendSubdomain.lngCode + ".png"))
button(ng-click="dismiss()").close.pull-right
span(aria-hidden="true") &times;
span.sr-only #{translate("close")}

View file

@ -179,6 +179,7 @@ module.exports = settings =
compileGroup: "standard"
references: true
templates: true
trackChanges: true
plans: plans = [{
planCode: "personal"

View file

@ -5,12 +5,13 @@ define [
restrict: "A"
link: (scope, el) ->
resetHeight = () ->
el.css("height", "auto")
el.css("height", el.prop("scrollHeight"))
curHeight = el.outerHeight()
fitHeight = el.prop("scrollHeight")
if fitHeight > curHeight and el.val() != ""
scope.$emit "expandable-text-area:resize"
el.css("height", fitHeight)
scope.$watch (() -> el.val()), resetHeight
resetHeight()

View file

@ -1,3 +1,4 @@
define [], () ->
ONEHOUR = 1000 * 60 * 60
class ConnectionManager

View file

@ -11,7 +11,7 @@ define [
open_doc_name: null
opening: true
trackChanges: false
wantTrackChanges: window.trackChangesEnabled
wantTrackChanges: false
}
@$scope.$on "entity:selected", (event, entity) =>
@ -37,6 +37,10 @@ define [
@$scope.$watch "editor.wantTrackChanges", (value) =>
return if !value?
@_syncTrackChangesState(@$scope.editor.sharejs_doc)
@$scope.$watch "project.features.trackChanges", (trackChangesFeature) =>
return if !trackChangesFeature?
@$scope.editor.wantTrackChanges = window.trackChangesEnabled and trackChangesFeature
autoOpenDoc: () ->
open_doc_id =

View file

@ -167,10 +167,22 @@ define [
if arg == "/"
ace.require("ace/ext/searchbox").Search(editor, true)
getCursorScreenPosition = () ->
session = editor.getSession()
cursorPosition = session.selection.getCursor()
sessionPos = session.documentToScreenPosition(cursorPosition.row, cursorPosition.column)
screenPos = editor.renderer.textToScreenCoordinates(sessionPos.row, sessionPos.column)
return sessionPos.row * editor.renderer.lineHeight - session.getScrollTop()
if attrs.resizeOn?
for event in attrs.resizeOn.split(",")
scope.$on event, () ->
previousScreenPosition = getCursorScreenPosition()
editor.resize()
# Put cursor back to same vertical position on screen
newScreenPosition = getCursorScreenPosition()
session = editor.getSession()
session.setScrollTop(session.getScrollTop() + newScreenPosition - previousScreenPosition)
scope.$watch "theme", (value) ->
editor.setTheme("ace/theme/#{value}")

View file

@ -12,6 +12,9 @@ define [
class HighlightedWordManager
constructor: (@editor) ->
@reset()
reset: () ->
@highlights = rows: []
addHighlight: (highlight) ->
@ -21,7 +24,7 @@ define [
highlight.row, highlight.column,
highlight.row, highlight.column + highlight.word.length
)
highlight.markerId = @editor.getSession().addMarker range, "spelling-highlight", null, true
highlight.markerId = @editor.getSession().addMarker range, "spelling-highlight", 'text', false
@highlights.rows[highlight.row] ||= []
@highlights.rows[highlight.row].push highlight

View file

@ -22,6 +22,10 @@ define [
@closeContextMenu()
@editor.on "changeSession", (e) =>
@highlightedWordManager.reset()
if @inProgressRequest?
@inProgressRequest.abort()
if @$scope.spellCheckEnabled and @$scope.spellCheckLanguage and @$scope.spellCheckLanguage != ""
@runSpellCheckSoon(200)
@ -183,7 +187,8 @@ define [
if not words.length
displayResult highlights
else
@apiRequest "/check", {language: language, words: words}, (error, result) =>
@inProgressRequest = @apiRequest "/check", {language: language, words: words}, (error, result) =>
delete @inProgressRequest
if error? or !result? or !result.misspellings?
return null
mispelled = []
@ -240,4 +245,4 @@ define [
callback null, data
error: (xhr, status, error) ->
callback error
$.ajax options
return $.ajax options

View file

@ -1,5 +1,6 @@
define [
"ide/review-panel/controllers/ReviewPanelController"
"ide/review-panel/controllers/TrackChangesUpgradeModalController"
"ide/review-panel/directives/reviewPanelSorted"
"ide/review-panel/directives/reviewPanelToggle"
"ide/review-panel/directives/changeEntry"

View file

@ -4,7 +4,7 @@ define [
"ide/colors/ColorManager"
"ide/review-panel/RangesTracker"
], (App, EventEmitter, ColorManager, RangesTracker) ->
App.controller "ReviewPanelController", ($scope, $element, ide, $timeout, $http, event_tracking) ->
App.controller "ReviewPanelController", ($scope, $element, ide, $timeout, $http, $modal, event_tracking) ->
$reviewPanelEl = $element.find "#review-panel"
$scope.SubViews =
@ -29,9 +29,15 @@ define [
$scope.$on "layout:pdf:linked", (event, state) ->
$scope.reviewPanel.layoutToLeft = (state.east?.size < 220 || state.east?.initClosed)
$scope.$broadcast "review-panel:layout"
$scope.$on "layout:pdf:resize", (event, state) ->
$scope.reviewPanel.layoutToLeft = (state.east?.size < 220 || state.east?.initClosed)
$scope.$broadcast "review-panel:layout", false
$scope.$on "expandable-text-area:resize", (event) ->
$timeout () ->
$scope.$broadcast "review-panel:layout"
$scope.$watch "ui.pdfLayout", (layout) ->
$scope.reviewPanel.layoutToLeft = (layout == "flat")
@ -142,13 +148,13 @@ define [
entries = $scope.reviewPanel.entries[$scope.editor.open_doc_id] or {}
Object.keys(entries).length
), (nEntries) ->
$scope.reviewPanel.hasEntries = nEntries > 0 and $scope.project.features.trackChanges
$scope.reviewPanel.hasEntries = nEntries > 0 and $scope.project.features.trackChangesVisible
$scope.$watch "ui.reviewPanelOpen", (reviewPanelOpen) ->
return if !reviewPanelOpen?
$timeout () ->
$scope.$broadcast "review-panel:toggle"
$scope.$broadcast "review-panel:layout"
$scope.$broadcast "review-panel:layout", false
regenerateTrackChangesId = (doc) ->
old_id = getChangeTracker(doc.doc_id).getIdSeed()
@ -432,9 +438,12 @@ define [
ide.editorManager.openDocId(doc_id, { gotoOffset: entry.offset })
$scope.toggleTrackChanges = (value) ->
$scope.editor.wantTrackChanges = value
$http.post "/project/#{$scope.project_id}/track_changes", {_csrf: window.csrfToken, on: value}
event_tracking.sendMB "rp-trackchanges-toggle", { value }
if $scope.project.features.trackChanges
$scope.editor.wantTrackChanges = value
$http.post "/project/#{$scope.project_id}/track_changes", {_csrf: window.csrfToken, on: value}
event_tracking.sendMB "rp-trackchanges-toggle", { value }
else
$scope.openTrackChangesUpgradeModal()
ide.socket.on "toggle-track-changes", (value) ->
$scope.$apply () ->
@ -523,3 +532,10 @@ define [
hue: ColorManager.getHueForUserId(id)
avatar_text: [user.first_name, user.last_name].filter((n) -> n?).map((n) -> n[0]).join ""
}
$scope.openTrackChangesUpgradeModal = () ->
$modal.open {
templateUrl: "trackChangesUpgradeModalTemplate"
controller: "TrackChangesUpgradeModalController"
scope: $scope.$new()
}

View file

@ -0,0 +1,11 @@
define [
"base"
], (App) ->
App.controller "TrackChangesUpgradeModalController", ($scope, $modalInstance) ->
$scope.cancel = () ->
$modalInstance.dismiss()
$scope.startFreeTrial = (source) ->
ga?('send', 'event', 'subscription-funnel', 'upgraded-free-trial', source)
window.open("/user/subscription/new?planCode=student_free_trial_7_days")
$scope.startedFreeTrial = true

View file

@ -6,7 +6,11 @@ define [
link: (scope, element, attrs) ->
previous_focused_entry_index = 0
layout = () ->
layout = (animate = true) ->
if animate
element.removeClass("no-animate")
else
element.addClass("no-animate")
sl_console.log "LAYOUT"
if scope.ui.reviewPanelOpen
PADDING = 8
@ -101,9 +105,9 @@ define [
scope.$applyAsync () ->
layout()
scope.$on "review-panel:layout", () ->
scope.$on "review-panel:layout", (e, animate = true) ->
scope.$applyAsync () ->
layout()
layout(animate)
scope.$watch "reviewPanel.rendererData.lineHeight", () ->
layout()

View file

@ -6,16 +6,23 @@ define [
scope:
onToggle: '='
ngModel: '='
disabled: '=?'
onDisabledClick: '=?'
link: (scope) ->
if !scope.disabled?
scope.disabled = false
scope.onChange = (args...) ->
scope.onToggle(scope.localModel)
scope.handleClick = () ->
if scope.disabled
scope.onDisabledClick()
scope.localModel = scope.ngModel
scope.$watch "ngModel", (value) ->
scope.localModel = value
template: """
<div class="rp-toggle">
<input id="rp-toggle-{{$id}}" type="checkbox" class="rp-toggle-hidden-input" ng-model="localModel" ng-change="onChange()" />
<div class="rp-toggle" ng-click="handleClick();">
<input id="rp-toggle-{{$id}}" ng-disabled="disabled" type="checkbox" class="rp-toggle-hidden-input" ng-model="localModel" ng-change="onChange()" />
<label for="rp-toggle-{{$id}}" class="rp-toggle-btn"></label>
</div>
"""

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 KiB

View file

@ -184,6 +184,9 @@
color: #FFF;
cursor: pointer;
transition: top 0.3s, left 0.1s, right 0.1s;
.no-animate & {
transition: none;
}
&-focused {
left: 0px;
@ -277,6 +280,9 @@
border-radius: 3px;
background-color: #FFF;
transition: top 0.3s, left 0.1s, right 0.1s;
.no-animate & {
transition: none;
}
&-insert {
border-color: @rp-green;
@ -339,6 +345,7 @@
// We need to set any low-enough flex base size (0px), making it growable (1) and non-shrinkable (0).
// This is needed to ensure that IE makes the element fill the available space.
flex: 1 0 1px;
overflow-x: auto;
.rp-state-overview & {
margin-left: 0;
@ -417,6 +424,7 @@
.rp-comment-content {
margin: 0;
color: @rp-type-darkgrey;
overflow-x: auto; // Long words, like links can overflow without this.
}
.rp-comment-resolver {
@ -451,6 +459,8 @@
resize: vertical;
color: @rp-type-darkgrey;
margin-top: 3px;
overflow-x: hidden;
min-height: 3em;
}
.rp-icon-delete {
@ -643,7 +653,7 @@
.rp-toggle {
display: inline-block;
vertical-align: middle;
margin-left: 5px;
padding-left: 5px;
}
.rp-toggle-hidden-input {
display: none;

View file

@ -145,18 +145,27 @@ describe "PasswordResetController", ->
done()
@PasswordResetController.setNewUserPassword @req, @res
it "should login user if login_after is set", (done) ->
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, { email: "joe@example.com" })
@PasswordResetHandler.setNewUserPassword.callsArgWith(2, null, true, @user_id = "user-id-123")
@req.body.login_after = "true"
@AuthenticationController.doLogin = (options, req, res, next)=>
@UserGetter.getUser.calledWith(@user_id).should.equal true
expect(options).to.deep.equal {
email: "joe@example.com",
password: @password
}
describe 'when login_after is set', ->
beforeEach ->
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, { email: "joe@example.com" })
@PasswordResetHandler.setNewUserPassword.callsArgWith(2, null, true, @user_id = "user-id-123")
@req.body.login_after = "true"
@res.json = sinon.stub()
@AuthenticationController.afterLoginSessionSetup = sinon.stub().callsArgWith(2, null)
@AuthenticationController._getRedirectFromSession = sinon.stub().returns('/some/path')
it "should login user if login_after is set", (done) ->
@PasswordResetController.setNewUserPassword @req, @res
@AuthenticationController.afterLoginSessionSetup.callCount.should.equal 1
@AuthenticationController.afterLoginSessionSetup.calledWith(
@req,
{email: 'joe@example.com'}
).should.equal true
@AuthenticationController._getRedirectFromSession.callCount.should.equal 1
@res.json.callCount.should.equal 1
@res.json.calledWith({redir: '/some/path'}).should.equal true
done()
@PasswordResetController.setNewUserPassword @req, @res
describe "renderSetPasswordForm", ->