diff --git a/services/web/app/coffee/Features/Announcements/AnnouncementsHandler.coffee b/services/web/app/coffee/Features/Announcements/AnnouncementsHandler.coffee index 9934a8bf69..96bb7568ee 100644 --- a/services/web/app/coffee/Features/Announcements/AnnouncementsHandler.coffee +++ b/services/web/app/coffee/Features/Announcements/AnnouncementsHandler.coffee @@ -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 diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index 3e6beaa0fb..e674b2b63a 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -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", (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 diff --git a/services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee index 5da582aa4c..3af40bf162 100644 --- a/services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee @@ -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) diff --git a/services/web/app/views/beta_program/opt_in.pug b/services/web/app/views/beta_program/opt_in.pug index f54766f30d..c740b894a6 100644 --- a/services/web/app/views/beta_program/opt_in.pug +++ b/services/web/app/views/beta_program/opt_in.pug @@ -18,11 +18,9 @@ block content | #{translate("beta_program_badge_description")} span.beta-feature-badge p.text-centered - | #{translate("beta_program_current_beta_features_description")} - ul.list-unstyled.text-center - li - i.fa.fa-fw.fa-book - |  #{translate("syntax_checking")} + strong We're currently testing track changes and commenting: + p.text-centered + img(src="/img/teasers/track-changes/track-changes-beta.png", style="max-width: 100%; border-bottom: 1px solid #ddd") .row.text-centered .col-md-12 if user.betaProgram diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug index 54c742fd15..eb8bdc684c 100644 --- a/services/web/app/views/project/editor.pug +++ b/services/web/app/views/project/editor.pug @@ -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" : { diff --git a/services/web/app/views/project/editor/feature-onboarding.pug b/services/web/app/views/project/editor/feature-onboarding.pug index 1a8d769088..8bcdcbdbd2 100644 --- a/services/web/app/views/project/editor/feature-onboarding.pug +++ b/services/web/app/views/project/editor/feature-onboarding.pug @@ -1,54 +1,112 @@ -.feat-onboard( - ng-controller="FeatureOnboardingController" - ng-class="('feat-onboard-step' + innerStep)" - ng-if="!state.loading && ui.showCodeCheckerOnboarding" - ng-cloak -) - .feat-onboard-wrapper - h1.feat-onboard-title - | Introducing  - span.feat-onboard-title-name Code check - div(ng-if="innerStep === 1;") +div(ng-controller="FeatureOnboardingController") + .feat-onboard( + ng-class="('feat-onboard-step' + onboarding.innerStep)" + ng-if="!state.loading && showCollabFeaturesOnboarding" + ng-cloak + stop-propagation="click" + ) + a.feat-onboard-dismiss( + href + ng-click="dismiss();" + ) × + .feat-onboard-wrapper + h1.feat-onboard-title + | Introducing  + span.feat-onboard-highlight Commenting + | & + span.feat-onboard-highlight Track Changes p.feat-onboard-description - span.feat-onboard-description-name Code check  - | 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  - 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  - span.feat-onboard-adv-title-highlight environments - p - | Know when you are missing an  - code \end{...} - |  command. - .col-xs-4 - h2.feat-onboard-adv-title - | Incorrect  - span.feat-onboard-adv-title-highlight nesting - p - | Order matters. Get notified when you use an  - code \end{...} - |   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  - span.feat-onboard-description-name Code check  - em on  - | or  - em off  - |, in the settings menu. - .feat-onboard-btn-wrapper - button.btn.btn-primary(ng-click="dismiss();") OK, got it \ No newline at end of file + 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 “Add comment” + | . + 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 “Track Changes” + | 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  + span.feat-onboard-highlight “Accept” + | or  + span.feat-onboard-highlight “Reject” + | to incorporate or discard an individual change. diff --git a/services/web/app/views/project/editor/header.pug b/services/web/app/views/project/editor/header.pug index 85397fa83f..2817efa814 100644 --- a/services/web/app/views/project/editor/header.pug +++ b/services/web/app/views/project/editor/header.pug @@ -92,7 +92,9 @@ header.toolbar.toolbar-header.toolbar-with-labels( ng-click="toggleReviewPanel()" ) i.review-icon - p.toolbar-label Review + p.toolbar-label + | Review + span(style="vertical-align: 20%; margin-left: 4px; padding: 2px 4px;").beta-feature-badge a.btn.btn-full-height( href, ng-if="permissions.admin", diff --git a/services/web/app/views/project/editor/review-panel.pug b/services/web/app/views/project/editor/review-panel.pug index 25ac9afd79..79e8a93144 100644 --- a/services/web/app/views/project/editor/review-panel.pug +++ b/services/web/app/views/project/editor/review-panel.pug @@ -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" @@ -332,6 +332,7 @@ script(type='text/ng-template', id='addCommentEntryTemplate') ng-keypress="handleCommentKeyPress($event);" placeholder="Add your comment here" focus-on="comment:new:open" + ng-blur="submitNewComment()" ) .rp-entry-actions button.rp-entry-button( @@ -417,10 +418,12 @@ script(type="text/ng-template", id="trackChangesUpgradeModalTemplate") a.btn.btn-success( href ng-click="startFreeTrial('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")} \ No newline at end of file + span #{translate("close")} diff --git a/services/web/public/coffee/directives/videoPlayState.coffee b/services/web/public/coffee/directives/videoPlayState.coffee new file mode 100644 index 0000000000..5dbf470bee --- /dev/null +++ b/services/web/public/coffee/directives/videoPlayState.coffee @@ -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() + } diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee index 08531f993a..9bf86d576e 100644 --- a/services/web/public/coffee/ide.coffee +++ b/services/web/public/coffee/ide.coffee @@ -29,6 +29,7 @@ define [ "directives/stopPropagation" "directives/rightClick" "directives/expandableTextArea" + "directives/videoPlayState" "services/queued-http" "filters/formatDate" "main/event" @@ -69,7 +70,6 @@ define [ chatOpen: false pdfLayout: 'sideBySide' reviewPanelOpen: localStorage("ui.reviewPanelOpen.#{window.project_id}") - showCodeCheckerOnboarding: !window.userSettings.syntaxValidation? } $scope.user = window.user diff --git a/services/web/public/coffee/ide/FeatureOnboardingController.coffee b/services/web/public/coffee/ide/FeatureOnboardingController.coffee index a5687cb42f..30db829b56 100644 --- a/services/web/public/coffee/ide/FeatureOnboardingController.coffee +++ b/services/web/public/coffee/ide/FeatureOnboardingController.coffee @@ -1,35 +1,36 @@ 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.$watch "project.features.trackChangesVisible", (visible) -> + return if !visible? + $scope.showCollabFeaturesOnboarding = window.showTrackChangesOnboarding and visible $scope.dismiss = () -> - $scope.ui.leftMenuShown = false - $scope.ui.showCodeCheckerOnboarding = false + event_tracking.sendMB "shown-track-changes-onboarding" + $scope.$applyAsync(() -> $scope.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 \ No newline at end of file + $(document).off "keydown", handleKeydown + $(document).off "click", $scope.dismiss \ No newline at end of file diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee index ed15da2958..b75cb07dba 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee @@ -20,8 +20,8 @@ define [ @rangesTracker = doc.ranges @connectToRangesTracker() - @$scope.$on "comment:add", (e, thread_id) => - @addCommentToSelection(thread_id) + @$scope.$on "comment:add", (e, thread_id, offset, length) => + @addCommentToSelection(thread_id, offset, length) @$scope.$on "comment:select_line", (e) => @selectLineIfNoSelection() @@ -45,7 +45,7 @@ define [ @recalculateReviewEntriesScreenPositions() changingSelection = false - onChangeSelection = (args...) => + onChangeSelection = () => # Deletes can send about 5 changeSelection events, so # just act on the last one. if !changingSelection @@ -53,7 +53,6 @@ define [ @$scope.$evalAsync () => changingSelection = false @updateFocus() - @recalculateReviewEntriesScreenPositions() onResize = () => @recalculateReviewEntriesScreenPositions() @@ -64,11 +63,13 @@ define [ bindToAce = () => @editor.on "changeSelection", onChangeSelection + @editor.on "change", onChangeSelection # Selection also moves with updates elsewhere in the document @editor.on "changeSession", onChangeSession @editor.renderer.on "resize", onResize unbindFromAce = () => @editor.off "changeSelection", onChangeSelection + @editor.off "change", onChangeSelection @editor.off "changeSession", onChangeSession @editor.renderer.off "resize", onResize @@ -174,10 +175,11 @@ define [ # @rangesTracker.applyOp op # Will apply via sharejs @$scope.sharejsDoc.submitOp op - addCommentToSelection: (thread_id) -> - range = @editor.getSelectionRange() - content = @editor.getSelectedText() - offset = @_aceRangeToShareJs(range.start) + addCommentToSelection: (thread_id, offset, length) -> + start = @_shareJsOffsetToAcePosition(offset) + end = @_shareJsOffsetToAcePosition(offset + length) + range = new Range(start.row, start.column, end.row, end.column) + content = @editor.session.getTextRange(range) @addComment(offset, content, thread_id) selectLineIfNoSelection: () -> diff --git a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee index 5cdf7c672c..88a3233934 100644 --- a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee +++ b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee @@ -280,6 +280,7 @@ define [ entries["add-comment"] = { type: "add-comment" offset: selection_offset_start + length: selection_offset_end - selection_offset_start } for id, entry of entries @@ -310,10 +311,15 @@ define [ $scope.$broadcast "review-panel:layout" $scope.submitNewComment = (content) -> + return if !content? or content == "" + doc_id = $scope.editor.open_doc_id + entries = getDocEntries(doc_id) + return if !entries["add-comment"]? + {offset, length} = entries["add-comment"] thread_id = RangesTracker.generateId() thread = getThread(thread_id) thread.submitting = true - $scope.$broadcast "comment:add", thread_id + $scope.$broadcast "comment:add", thread_id, offset, length $http.post("/project/#{$scope.project_id}/thread/#{thread_id}/messages", {content, _csrf: window.csrfToken}) .error (error) -> ide.showGenericMessageModal("Error submitting comment", "Sorry, there was a problem submitting your comment") diff --git a/services/web/public/coffee/ide/review-panel/directives/addCommentEntry.coffee b/services/web/public/coffee/ide/review-panel/directives/addCommentEntry.coffee index 124794e7b8..c23c37b674 100644 --- a/services/web/public/coffee/ide/review-panel/directives/addCommentEntry.coffee +++ b/services/web/public/coffee/ide/review-panel/directives/addCommentEntry.coffee @@ -28,10 +28,9 @@ define [ if ev.keyCode == 13 and !ev.shiftKey and !ev.ctrlKey and !ev.metaKey ev.preventDefault() if scope.state.content.length > 0 - ev.target.blur() scope.submitNewComment() scope.submitNewComment = () -> scope.onSubmit { content: scope.state.content } scope.state.isAdding = false - scope.state.content = "" \ No newline at end of file + scope.state.content = "" diff --git a/services/web/public/img/onboarding/review-panel/accept-changes.gif b/services/web/public/img/onboarding/review-panel/accept-changes.gif new file mode 100644 index 0000000000..eca888f908 Binary files /dev/null and b/services/web/public/img/onboarding/review-panel/accept-changes.gif differ diff --git a/services/web/public/img/onboarding/review-panel/accept-changes.mp4 b/services/web/public/img/onboarding/review-panel/accept-changes.mp4 new file mode 100644 index 0000000000..7a486c9363 Binary files /dev/null and b/services/web/public/img/onboarding/review-panel/accept-changes.mp4 differ diff --git a/services/web/public/img/onboarding/review-panel/add-changes.gif b/services/web/public/img/onboarding/review-panel/add-changes.gif new file mode 100644 index 0000000000..4a46a45b6e Binary files /dev/null and b/services/web/public/img/onboarding/review-panel/add-changes.gif differ diff --git a/services/web/public/img/onboarding/review-panel/add-changes.mp4 b/services/web/public/img/onboarding/review-panel/add-changes.mp4 new file mode 100644 index 0000000000..a73fbd1eab Binary files /dev/null and b/services/web/public/img/onboarding/review-panel/add-changes.mp4 differ diff --git a/services/web/public/img/onboarding/review-panel/commenting.gif b/services/web/public/img/onboarding/review-panel/commenting.gif new file mode 100644 index 0000000000..648c7dd4c3 Binary files /dev/null and b/services/web/public/img/onboarding/review-panel/commenting.gif differ diff --git a/services/web/public/img/onboarding/review-panel/commenting.mp4 b/services/web/public/img/onboarding/review-panel/commenting.mp4 new file mode 100644 index 0000000000..9f0f50eb0d Binary files /dev/null and b/services/web/public/img/onboarding/review-panel/commenting.mp4 differ diff --git a/services/web/public/img/onboarding/review-panel/open-review.gif b/services/web/public/img/onboarding/review-panel/open-review.gif new file mode 100644 index 0000000000..603a05933d Binary files /dev/null and b/services/web/public/img/onboarding/review-panel/open-review.gif differ diff --git a/services/web/public/img/onboarding/review-panel/open-review.mp4 b/services/web/public/img/onboarding/review-panel/open-review.mp4 new file mode 100644 index 0000000000..c8288b8d41 Binary files /dev/null and b/services/web/public/img/onboarding/review-panel/open-review.mp4 differ diff --git a/services/web/public/img/teasers/track-changes/track-changes-beta.png b/services/web/public/img/teasers/track-changes/track-changes-beta.png new file mode 100644 index 0000000000..a9efead2cc Binary files /dev/null and b/services/web/public/img/teasers/track-changes/track-changes-beta.png differ diff --git a/services/web/public/stylesheets/app/editor/feature-onboarding.less b/services/web/public/stylesheets/app/editor/feature-onboarding.less index fa815f1d8d..7289ed4fb7 100644 --- a/services/web/public/stylesheets/app/editor/feature-onboarding.less +++ b/services/web/public/stylesheets/app/editor/feature-onboarding.less @@ -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); } - } \ No newline at end of file + + .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; + } +} \ No newline at end of file diff --git a/services/web/public/stylesheets/app/editor/review-panel.less b/services/web/public/stylesheets/app/editor/review-panel.less index 0d4c0032e8..1d1900c90c 100644 --- a/services/web/public/stylesheets/app/editor/review-panel.less +++ b/services/web/public/stylesheets/app/editor/review-panel.less @@ -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; } diff --git a/services/web/test/UnitTests/coffee/Announcement/AnnouncementsHandlerTests.coffee b/services/web/test/UnitTests/coffee/Announcement/AnnouncementsHandlerTests.coffee index daa0da0531..ece2ddac46 100644 --- a/services/web/test/UnitTests/coffee/Announcement/AnnouncementsHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/Announcement/AnnouncementsHandlerTests.coffee @@ -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 -> diff --git a/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee b/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee index bcb2354b55..ba92046c28 100644 --- a/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee @@ -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