Merge branch 'master' into ja-track-changes

This commit is contained in:
James Allen 2017-02-16 17:07:12 +01:00
commit 04b9f8d249
23 changed files with 302 additions and 139 deletions

View file

@ -19,6 +19,9 @@ module.exports = AnnouncementsHandler =
if !user? and !user._id?
return callback("user not supplied")
timestamp = user._id.toString().substring(0,8)
userSignupDate = new Date( parseInt( timestamp, 16 ) * 1000 )
async.parallel {
lastEvent: (cb)->
AnalyticsManager.getLastOccurance user._id, "announcement-alert-dismissed", cb
@ -48,7 +51,9 @@ module.exports = AnnouncementsHandler =
announcement.id == lastSeenBlogId
announcements = _.map announcements, (announcement, index)->
if announcementIndex == -1
if announcement.date < userSignupDate
read = true
else if announcementIndex == -1
read = false
else if index >= announcementIndex
read = true

View file

@ -10,7 +10,7 @@ TagsHandler = require("../Tags/TagsHandler")
SubscriptionLocator = require("../Subscription/SubscriptionLocator")
NotificationsHandler = require("../Notifications/NotificationsHandler")
LimitationsManager = require("../Subscription/LimitationsManager")
_ = require("underscore")
underscore = require("underscore")
Settings = require("settings-sharelatex")
AuthorizationManager = require("../Authorization/AuthorizationManager")
fs = require "fs"
@ -20,6 +20,7 @@ ProjectGetter = require("./ProjectGetter")
PrivilegeLevels = require("../Authorization/PrivilegeLevels")
AuthenticationController = require("../Authentication/AuthenticationController")
PackageVersions = require("../../infrastructure/PackageVersions")
AnalyticsManager = require "../Analytics/AnalyticsManager"
module.exports = ProjectController =
@ -219,6 +220,19 @@ module.exports = ProjectController =
#don't need to wait for this to complete
ProjectUpdateHandler.markAsOpened project_id, ->
cb()
showTrackChangesOnboarding: (cb) ->
cb = underscore.once(cb)
if !user_id?
return cb()
timeout = setTimeout cb, 500
AnalyticsManager.getLastOccurance user_id, "shown-track-changes-onboarding-2", (error, event) ->
clearTimeout timeout
if error?
return cb(null, false)
else if event?
return cb(null, false)
else
return cb(null, true)
}, (err, results)->
if err?
logger.err err:err, "error getting details for project page"
@ -226,7 +240,7 @@ module.exports = ProjectController =
project = results.project
user = results.user
subscription = results.subscription
showTrackChangesOnboarding = results.showTrackChangesOnboarding
daysSinceLastUpdated = (new Date() - project.lastUpdated) /86400000
logger.log project_id:project_id, daysSinceLastUpdated:daysSinceLastUpdated, "got db results for loading editor"
@ -268,6 +282,7 @@ module.exports = ProjectController =
syntaxValidation: user.ace.syntaxValidation
}
trackChangesEnabled: !!project.track_changes
showTrackChangesOnboarding: !!showTrackChangesOnboarding
privilegeLevel: privilegeLevel
chatUrl: Settings.apis.chat.url
anonymous: anonymous

View file

@ -22,7 +22,7 @@ module.exports = ProjectEditorHandler =
trackChangesVisible = false
for member in members
if member.privilegeLevel == "owner" and member.user?.featureSwitches?.track_changes
if member.privilegeLevel == "owner" and (member.user?.featureSwitches?.track_changes or member.user?.betaProgram)
trackChangesVisible = true
{owner, ownerFeatures, members} = @buildOwnerAndMembersViews(members)

View file

@ -108,6 +108,7 @@ block requirejs
window.anonymous = #{anonymous};
window.maxDocLength = #{maxDocLength};
window.trackChangesEnabled = #{trackChangesEnabled};
window.showTrackChangesOnboarding = #{!!showTrackChangesOnboarding};
window.wikiEnabled = #{!!(settings.apis.wiki && settings.apis.wiki.url)};
window.requirejs = {
"paths" : {

View file

@ -1,54 +1,112 @@
.feat-onboard(
ng-controller="FeatureOnboardingController"
ng-class="('feat-onboard-step' + innerStep)"
ng-if="!state.loading && ui.showCodeCheckerOnboarding"
ng-class="('feat-onboard-step' + onboarding.innerStep)"
ng-if="!state.loading && ui.showCollabFeaturesOnboarding"
ng-cloak
stop-propagation="click"
)
a.feat-onboard-dismiss(
href
ng-click="dismiss();"
) &times;
.feat-onboard-wrapper
h1.feat-onboard-title
| Introducing&nbsp;
span.feat-onboard-title-name Code check
div(ng-if="innerStep === 1;")
p.feat-onboard-description
span.feat-onboard-description-name Code check&nbsp;
| will highlight potential problems in your LaTeX code, allowing you to handle errors earlier and become more productive.
.row
video.feat-onboard-video(autoplay, loop)
source(src="/img/teasers/code-checker/code-checker.mp4", type="video/mp4")
img(src="/img/teasers/code-checker/code-checker.gif")
.row.feat-onboard-adv-wrapper
.col-xs-4
h2.feat-onboard-adv-title
| Missing&nbsp;
span.feat-onboard-adv-title-highlight brackets
p Forgot to place a closing bracket? We'll warn you.
.col-xs-4
h2.feat-onboard-adv-title
| Unclosed&nbsp;
span.feat-onboard-adv-title-highlight environments
p
| Know when you are missing an&nbsp;
code \end{...}
| &nbsp;command.
.col-xs-4
h2.feat-onboard-adv-title
| Incorrect&nbsp;
span.feat-onboard-adv-title-highlight nesting
p
| Order matters. Get notified when you use an&nbsp;
code \end{...}
| &nbsp; too soon.
.feat-onboard-btn-wrapper
button.btn.btn-primary(ng-click="turnCodeCheckOn();") Yes, turn Code check on
.feat-onboard-btn-wrapper
button.btn.btn-default(ng-click="turnCodeCheckOff();") No, disable it for now
div(ng-if="innerStep === 2;")
p.feat-onboard-description
| Remember: you can always turn&nbsp;
span.feat-onboard-description-name Code check&nbsp;
em on&nbsp;
| or&nbsp;
em off&nbsp;
|, in the settings menu.
.feat-onboard-btn-wrapper
button.btn.btn-primary(ng-click="dismiss();") OK, got it
span.feat-onboard-highlight Commenting
| &amp;
span.feat-onboard-highlight Track Changes
p.feat-onboard-description
span.feat-onboard-highlight Commenting
| and
span.feat-onboard-highlight Track Changes
| will make it easier for you to work with peers in your documents.
.feat-onboard-tutorial-wrapper
button.btn.btn-primary.feat-onboard-nav-btn(
ng-click="gotoPrevStep();"
ng-disabled="onboarding.innerStep === 1;")
i.fa.fa-arrow-left
div(ng-show="onboarding.innerStep === 1;")
video.feat-onboard-video(
video-play-state="onboarding.innerStep === 1;"
autoplay
loop
)
source(src="/img/onboarding/review-panel/open-review.mp4", type="video/mp4")
img(src="/img/onboarding/review-panel/open-review.gif")
div(ng-show="onboarding.innerStep === 2;")
video.feat-onboard-video(
video-play-state="onboarding.innerStep === 2;"
autoplay
loop
)
source(src="/img/onboarding/review-panel/commenting.mp4", type="video/mp4")
img(src="/img/onboarding/review-panel/commenting.gif")
div(ng-show="onboarding.innerStep === 3;")
video.feat-onboard-video(
video-play-state="onboarding.innerStep === 3;"
autoplay
loop
)
source(src="/img/onboarding/review-panel/add-changes.mp4", type="video/mp4")
img(src="/img/onboarding/review-panel/add-changes.gif")
div(ng-show="onboarding.innerStep === 4;")
video.feat-onboard-video(
video-play-state="onboarding.innerStep === 4;"
autoplay
loop
)
source(src="/img/onboarding/review-panel/accept-changes.mp4", type="video/mp4")
img(src="/img/onboarding/review-panel/accept-changes.gif")
button.btn.btn-primary.feat-onboard-nav-btn(
ng-click="gotoNextStep();"
ng-disabled="onboarding.innerStep === onboarding.nSteps;")
i.fa.fa-arrow-right
div(ng-switch="onboarding.innerStep")
.row(ng-switch-when="1")
.col-xs-6
h2.feat-onboard-adv-title Commenting
p.feat-onboard-description Want to discuss specific parts of the text?
p.feat-onboard-description Use our brand-new commenting system.
.col-xs-6
h2.feat-onboard-adv-title Track Changes
p.feat-onboard-description See changes in your documents, live.
p.feat-onboard-description Track, accept and reject changes individually.
.row(ng-switch-when="2")
.col-xs-12
h2.feat-onboard-adv-title Commenting
p.feat-onboard-description Just select a span of text and click on
span.feat-onboard-highlight &ldquo;Add comment&rdquo;
| .
p.feat-onboard-description
span.feat-onboard-highlight Comments
| can be
span.feat-onboard-highlight replied
| to,
span.feat-onboard-highlight resolved
| and permanently
span.feat-onboard-highlight deleted
| .
.row(ng-switch-when="3")
.col-xs-12
h2.feat-onboard-adv-title Track Changes
p.feat-onboard-description
| Let your peers know what you've been up to.
p.feat-onboard-description
| Click on the
span.feat-onboard-highlight &ldquo;Track Changes&rdquo;
| toggle to start marking your insertions, as well as your deletions.
.row(ng-switch-when="4")
.col-xs-12
h2.feat-onboard-adv-title Track Changes
p.feat-onboard-description Upon reviewing,
span.feat-onboard-highlight changes
| can be accepted or undone.
p.feat-onboard-description
| Click&nbsp;
span.feat-onboard-highlight &ldquo;Accept&rdquo;
| or&nbsp;
span.feat-onboard-highlight &ldquo;Reject&rdquo;
| to incorporate or discard an individual change.

View file

@ -6,7 +6,7 @@
ng-class="{ 'rp-track-changes-indicator-on-dark' : darkTheme }"
) Track changes is
strong on
.review-panel-toolbar
resolved-comments-dropdown(
class="rp-flex-block"
@ -418,11 +418,13 @@ script(type="text/ng-template", id="trackChangesUpgradeModalTemplate")
.row.text-center(ng-controller="FreeTrialModalController")
a.btn.btn-success(
href
ng-click="startFreeTrial('track-changes')"
ng-click="startFreeTrial('real-time-track-changes')"
ng-show="project.owner._id == user.id"
) Try it for free
p(ng-show="project.owner._id != user.id"): strong Please ask the project owner to upgrade to use track changes
.modal-footer()
button.btn.btn-default(
ng-click="cancel()"
)
span #{translate("close")}
span #{translate("close")}

View file

@ -50,6 +50,7 @@ block content
p.announcement-date {{ announcement.date | date:"longDate" }}
a.announcement-link(
ng-href="{{ announcement.url }}"
ng-click="logAnnouncementClick()",
target="_blank"
) Read more
div.text-center(

View file

@ -0,0 +1,15 @@
define [
"base"
], (App) ->
App.directive "videoPlayState", ($parse) ->
return {
restrict: "A",
link: (scope, element, attrs) ->
videoDOMEl = element[0]
scope.$watch (() -> $parse(attrs.videoPlayState)(scope)), (shouldPlay) ->
if shouldPlay
videoDOMEl.currentTime = 0
videoDOMEl.play()
else
videoDOMEl.pause()
}

View file

@ -29,6 +29,7 @@ define [
"directives/stopPropagation"
"directives/rightClick"
"directives/expandableTextArea"
"directives/videoPlayState"
"services/queued-http"
"filters/formatDate"
"main/event"
@ -69,9 +70,12 @@ define [
chatOpen: false
pdfLayout: 'sideBySide'
reviewPanelOpen: localStorage("ui.reviewPanelOpen.#{window.project_id}")
showCodeCheckerOnboarding: !window.userSettings.syntaxValidation?
}
$scope.user = window.user
$scope.$watch "project.features.trackChangesVisible", (visible) ->
return if !visible?
$scope.ui.showCollabFeaturesOnboarding = window.showTrackChangesOnboarding and visible
$scope.shouldABTestPlans = false
if $scope.user.signUpDate >= '2016-10-27'

View file

@ -1,35 +1,32 @@
define [
"base"
], (App) ->
App.controller "FeatureOnboardingController", ($scope, settings) ->
$scope.innerStep = 1
$scope.turnCodeCheckOn = () ->
settings.saveSettings({ syntaxValidation: true })
$scope.settings.syntaxValidation = true
navToInnerStep2()
$scope.turnCodeCheckOff = () ->
settings.saveSettings({ syntaxValidation: false })
$scope.settings.syntaxValidation = false
navToInnerStep2()
App.controller "FeatureOnboardingController", ($scope, settings, event_tracking) ->
$scope.onboarding =
innerStep: 1
nSteps: 4
$scope.dismiss = () ->
$scope.ui.leftMenuShown = false
$scope.ui.showCodeCheckerOnboarding = false
event_tracking.sendMB "shown-track-changes-onboarding-2"
$scope.$applyAsync(() -> $scope.ui.showCollabFeaturesOnboarding = false)
navToInnerStep2 = () ->
$scope.innerStep = 2
$scope.ui.leftMenuShown = true
$scope.gotoPrevStep = () ->
if $scope.onboarding.innerStep > 1
$scope.$applyAsync(() -> $scope.onboarding.innerStep--)
handleKeypress = (e) ->
if e.keyCode == 13
if $scope.innerStep == 1
$scope.turnCodeCheckOn()
else
$scope.dismiss()
$scope.gotoNextStep = () ->
if $scope.onboarding.innerStep < 4
$scope.$applyAsync(() -> $scope.onboarding.innerStep++)
$(document).on "keypress", handleKeypress
handleKeydown = (e) ->
switch e.keyCode
when 37 then $scope.gotoPrevStep() # left directional key
when 39, 13 then $scope.gotoNextStep() # right directional key, enter
when 27 then $scope.dismiss() # escape
$(document).on "keydown", handleKeydown
$(document).on "click", $scope.dismiss
$scope.$on "$destroy", () ->
$(document).off "keypress", handleKeypress
$(document).off "keydown", handleKeydown
$(document).off "click", $scope.dismiss

View file

@ -15,6 +15,9 @@ define [
markAnnouncementsAsRead = ->
event_tracking.sendMB "announcement-alert-dismissed", { blogPostId: $scope.announcements[0].id }
$scope.logAnnouncementClick = ->
event_tracking.sendMB "announcement-read-more-clicked", { blogPostId: $scope.announcements[0].id }
refreshAnnouncements()
$scope.toggleAnnouncementsUI = ->

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View file

@ -1,78 +1,101 @@
@feat-onboard-wrapper-width: 820px;
@feat-onboard-max-text-width: 750px;
@feat-onboard-width: 900px;
.feat-onboard {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
top: 50px;
bottom: 50px;
left: 50%;
width: @feat-onboard-width;
margin-left: -(@feat-onboard-width / 2);
display: flex;
justify-content: center;
align-items: center;
background-image: linear-gradient(rgba(0, 0, 0, .85), rgba(0, 0, 0, .85));
align-items: baseline;
background-color: rgba(0, 0, 0, .85);
background-repeat: no-repeat;
background-position-x: 0;
color: #FFF;
text-align: center;
border-radius: 1em;
z-index: 102;
transition: background-position ease-in-out @left-menu-animation-duration;
overflow: auto;
}
.feat-onboard-step2 {
background-position-x: @left-menu-width;
~ #left-menu {
pointer-events: none;
.code-check-setting {
box-shadow: 0 0 300px 0 #000;
}
}
}
.feat-onboard-wrapper {
width: @feat-onboard-wrapper-width;
padding: 30px 0;
}
.feat-onboard-title {
color: @brand-primary;
margin-bottom: 40px;
.feat-onboard-title {
color: #FFF;
margin-bottom: 30px;
}
.feat-onboard-title-name {
color: #FFF;
font-weight: bold;
}
.feat-onboard-description {
max-width: @feat-onboard-max-text-width;
margin: 0 auto 30px;
padding: 0 80px;
max-width: 35em;
margin: 0 auto 5px;
}
.feat-onboard-description-name {
.feat-onboard-highlight {
font-weight: bold;
white-space: nowrap;
}
.feat-onboard-adv-title {
font-weight: bold;
white-space: nowrap;
color: #FFF;
font-size: 23px;
margin-top: 0;
}
.feat-onboard-video {
box-shadow: 0 0 70px 0 rgba(255, 255, 255, 0.3);
.feat-onboard-tutorial-wrapper {
display: flex;
align-items: center;
padding: 30px 0 15px;
}
.feat-onboard-adv-wrapper {
text-align: left;
margin-bottom: 30px;
}
.feat-onboard-adv-title {
color: #FFF;
font-size: 23px;
}
.feat-onboard-adv-title-highlight {
font-weight: bold;
}
.feat-onboard-btn-wrapper {
margin-bottom: 10px;
> .btn {
.feat-onboard-video {
width: 616px;
margin: 0 30px;
box-shadow: 0 0 70px 0 rgba(255, 255, 255, 0.3);
}
}
.feat-onboard-nav-btn {
border-radius: 1em;
width: 2em;
height: 2em;
text-align: center;
padding: 0;
font-size: 1.3em;
line-height: 1em;
box-shadow: 0 0 70px 0 rgba(255, 255, 255, 0.3);
&[disabled] {
opacity: 0.2;
}
&:focus,
&:active:focus {
outline: 0;
box-shadow: 0 0 70px 0 rgba(255, 255, 255, 0.3);
}
}
a.feat-onboard-dismiss {
position: absolute;
top: 10px;
right: 10px;
width: 1em;
height: 1em;
line-height: 1em;
font-size: 2.5em;
color: #FFF;
background-color: rgba(0,0,0, .25);
opacity: 0.7;
border-radius: 0.5em;
transition: opacity .15s ease-in-out;
&:hover,
&:focus {
text-decoration: none;
color: #FFF;
opacity: 1;
}
}

View file

@ -391,7 +391,7 @@
border-right-width: 0;
}
.rp-layout-left & {
.rp-state-current-file-mini.rp-layout-left & {
&:first-child {
border-bottom-left-radius: 3px;
}

View file

@ -11,7 +11,7 @@ describe 'AnnouncementsHandler', ->
beforeEach ->
@user =
_id:"some_id"
_id:"3c6afe000000000000000000" #2002-02-14T00:00:00.000Z
email: "someone@gmail.com"
@AnalyticsManager =
getLastOccurance: sinon.stub()
@ -36,10 +36,10 @@ describe 'AnnouncementsHandler', ->
id: '/2013/08/02/thesis-series-pt1'
}, {
date: new Date(1108369600000),
id: '/2011/08/04/somethingelse'
id: '/2005/08/04/somethingelse'
}, {
date: new Date(1208369600000),
id: '/2014/04/12/title-date-irrelivant'
id: '/2008/04/12/title-date-irrelivant'
}
]
@BlogHandler.getLatestAnnouncements.callsArgWith(0, null, @stubbedAnnouncements)
@ -64,7 +64,7 @@ describe 'AnnouncementsHandler', ->
done()
it "should return older ones marked as read as well", (done)->
@AnalyticsManager.getLastOccurance.callsArgWith(2, null, {segmentation:{blogPostId:"/2014/04/12/title-date-irrelivant"}})
@AnalyticsManager.getLastOccurance.callsArgWith(2, null, {segmentation:{blogPostId:"/2008/04/12/title-date-irrelivant"}})
@handler.getUnreadAnnouncements @user, (err, announcements)=>
announcements[0].id.should.equal @stubbedAnnouncements[0].id
announcements[0].read.should.equal false
@ -89,6 +89,21 @@ describe 'AnnouncementsHandler', ->
announcements[3].read.should.equal true
done()
it "should return posts older than signup date as read", (done)->
@stubbedAnnouncements.push({
date: new Date(978836800000),
id: '/2001/04/12/title-date-irrelivant'
})
@AnalyticsManager.getLastOccurance.callsArgWith(2, null, [])
@handler.getUnreadAnnouncements @user, (err, announcements)=>
announcements[0].read.should.equal false
announcements[1].read.should.equal false
announcements[2].read.should.equal false
announcements[3].read.should.equal false
announcements[4].read.should.equal true
announcements[4].id.should.equal '/2001/04/12/title-date-irrelivant'
done()
describe "with custom domain announcements", ->
beforeEach ->

View file

@ -58,6 +58,8 @@ describe "ProjectController", ->
getLoggedInUserId: sinon.stub().returns(@user._id)
getSessionUser: sinon.stub().returns(@user)
isUserLoggedIn: sinon.stub().returns(true)
@AnalyticsManager =
getLastOccurance: sinon.stub()
@ProjectController = SandboxedModule.require modulePath, requires:
"settings-sharelatex":@settings
"logger-sharelatex":
@ -82,6 +84,7 @@ describe "ProjectController", ->
"../ReferencesSearch/ReferencesSearchHandler": @ReferencesSearchHandler
"./ProjectGetter": @ProjectGetter
'../Authentication/AuthenticationController': @AuthenticationController
"../Analytics/AnalyticsManager": @AnalyticsManager
@projectName = "£12321jkj9ujkljds"
@req =
@ -310,9 +313,9 @@ describe "ProjectController", ->
@AuthorizationManager.getPrivilegeLevelForProject.callsArgWith 2, null, "owner"
@ProjectDeleter.unmarkAsDeletedByExternalSource = sinon.stub()
@InactiveProjectManager.reactivateProjectIfRequired.callsArgWith(1)
@AnalyticsManager.getLastOccurance.yields(null, {"mock": "event"})
@ProjectUpdateHandler.markAsOpened.callsArgWith(1)
it "should render the project/editor page", (done)->
@res.render = (pageName, opts)=>
pageName.should.equal "project/editor"
@ -357,3 +360,24 @@ describe "ProjectController", ->
@ProjectUpdateHandler.markAsOpened.calledWith(@project_id).should.equal true
done()
@ProjectController.loadEditor @req, @res
it "should set showTrackChangesOnboarding = false if there is an event", (done) ->
@AnalyticsManager.getLastOccurance.yields(null, {"mock": "event"})
@res.render = (pageName, opts)=>
opts.showTrackChangesOnboarding.should.equal false
done()
@ProjectController.loadEditor @req, @res
it "should set showTrackChangesOnboarding = true if there is no event", (done) ->
@AnalyticsManager.getLastOccurance.yields(null, null)
@res.render = (pageName, opts)=>
opts.showTrackChangesOnboarding.should.equal true
done()
@ProjectController.loadEditor @req, @res
it "should set showTrackChangesOnboarding = false if there is an error", (done) ->
@AnalyticsManager.getLastOccurance.yields(new Error("oops"), null)
@res.render = (pageName, opts)=>
opts.showTrackChangesOnboarding.should.equal false
done()
@ProjectController.loadEditor @req, @res