Merge pull request #547 from sharelatex/ja-per-user-track-changes

Per-user track changes
This commit is contained in:
James Allen 2017-08-09 14:06:54 +02:00 committed by GitHub
commit c928935865
10 changed files with 251 additions and 50 deletions

View file

@ -230,6 +230,24 @@ module.exports = ProjectController =
return cb(null, false)
else
return cb(null, true)
showPerUserTCNotice: (cb) ->
cb = underscore.once(cb)
if !user_id?
return cb()
timestamp = user_id.toString().substring(0,8)
userSignupDate = new Date( parseInt( timestamp, 16 ) * 1000 )
if userSignupDate > new Date("2017-08-09")
# Don't show for users who registered after it was released
return cb(null, false)
timeout = setTimeout cb, 500
AnalyticsManager.getLastOccurance user_id, "shown-per-user-tc-notice", (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"
@ -237,7 +255,7 @@ module.exports = ProjectController =
project = results.project
user = results.user
subscription = results.subscription
showTrackChangesOnboarding = results.showTrackChangesOnboarding
{ showTrackChangesOnboarding, showPerUserTCNotice } = results
daysSinceLastUpdated = (new Date() - project.lastUpdated) /86400000
logger.log project_id:project_id, daysSinceLastUpdated:daysSinceLastUpdated, "got db results for loading editor"
@ -279,8 +297,9 @@ module.exports = ProjectController =
pdfViewer : user.ace.pdfViewer
syntaxValidation: user.ace.syntaxValidation
}
trackChangesEnabled: !!project.track_changes
trackChangesState: project.track_changes
showTrackChangesOnboarding: !!showTrackChangesOnboarding
showPerUserTCNotice: !!showPerUserTCNotice
privilegeLevel: privilegeLevel
chatUrl: Settings.apis.chat.url
anonymous: anonymous

View file

@ -32,7 +32,7 @@ ProjectSchema = new Schema
archived : { type: Boolean }
deletedDocs : [DeletedDocSchema]
imageName : { type: String }
track_changes : { type: Boolean }
track_changes : { type: Object }
ProjectSchema.statics.getProject = (project_or_id, fields, callback)->
if project_or_id._id?

View file

@ -106,7 +106,7 @@ block requirejs
//- We need to do .replace(/\//g, '\\/') do that '</script>' -> '<\/script>'
//- and doesn't prematurely end the script tag.
script#data(type="application/json").
!{JSON.stringify({userSettings: userSettings, user: user}).replace(/\//g, '\\/')}
!{JSON.stringify({userSettings: userSettings, user: user, trackChangesState: trackChangesState}).replace(/\//g, '\\/')}
script(type="text/javascript").
window.data = JSON.parse($("#data").text());
@ -118,8 +118,9 @@ block requirejs
window.csrfToken = "!{csrfToken}";
window.anonymous = #{anonymous};
window.maxDocLength = #{maxDocLength};
window.trackChangesEnabled = #{trackChangesEnabled};
window.trackChangesState = data.trackChangesState;
window.showTrackChangesOnboarding = #{!!showTrackChangesOnboarding};
window.showPerUserTCNotice = #{!!showPerUserTCNotice};
window.wikiEnabled = #{!!(settings.apis.wiki && settings.apis.wiki.url)};
window.requirejs = {
"paths" : {

View file

@ -46,21 +46,40 @@
is-loading="reviewPanel.dropdown.loading"
permissions="permissions"
)
span.review-panel-toolbar-label(ng-if="permissions.write")
span(ng-click="toggleTrackChanges(true)", ng-if="editor.wantTrackChanges === false") !{translate("track_changes_is_off")}
span(ng-click="toggleTrackChanges(false)", ng-if="editor.wantTrackChanges === true") !{translate("track_changes_is_on")}
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
span.review-panel-toolbar-icon-on(
ng-if="editor.wantTrackChanges === true"
)
span.review-panel-toolbar-label.review-panel-toolbar-label-disabled(ng-if="!permissions.write")
span(ng-if="editor.wantTrackChanges === false") !{translate("track_changes_is_off")}
span(ng-if="editor.wantTrackChanges === true") !{translate("track_changes_is_on")}
span.review-panel-toolbar-spinner(ng-if="editor.wantTrackChanges != editor.trackChanges")
i.fa.fa-spin.fa-spinner
i.fa.fa-circle
span(ng-click="toggleFullTCStateCollapse();")
span(ng-if="editor.wantTrackChanges === false") !{translate("track_changes_is_off")}
span(ng-if="editor.wantTrackChanges === true") !{translate("track_changes_is_on")}
span.rp-tc-state-collapse(
ng-class="{ 'rp-tc-state-collapse-on': reviewPanel.fullTCStateCollapsed }"
)
i.fa.fa-angle-down
ul.rp-tc-state(
review-panel-collapse-height="reviewPanel.fullTCStateCollapsed"
)
li.rp-tc-state-item.rp-tc-state-item-everyone
span.rp-tc-state-item-name !{translate("tc_everyone")}
review-panel-toggle(
ng-model="reviewPanel.trackChangesOnForEveryone"
on-toggle="toggleTrackChangesForEveryone(isOn);"
disabled="!project.features.trackChanges || !permissions.write"
)
li.rp-tc-state-item(
ng-repeat="member in reviewPanel.formattedProjectMembers"
)
span.rp-tc-state-item-name(
ng-class="{ 'rp-tc-state-item-name-disabled' : reviewPanel.trackChangesOnForEveryone}"
style="color: hsl({{ member.hue }}, 70%, 40%);"
) {{ member.name }}
review-panel-toggle(
ng-model="reviewPanel.trackChangesState[member.id].value"
on-toggle="toggleTrackChangesForUser(isOn, member.id);"
disabled="reviewPanel.trackChangesOnForEveryone || !project.features.trackChanges || !permissions.write"
)
.rp-entry-list(
review-panel-sorted
@ -494,7 +513,7 @@ script(type="text/ng-template", id="trackChangesUpgradeModalTemplate")
.modal-body
.teaser-video-container
video.teaser-video(autoplay, loop)
source(src="/img/teasers/track-changes/teaser-track-changes.mp4", type="video/mp4")
source(ng-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 #{translate("see_changes_in_your_documents_live")}
@ -532,6 +551,37 @@ script(type="text/ng-template", id="trackChangesUpgradeModalTemplate")
)
span #{translate("close")}
script(type="text/ng-template", id="perUserTCNoticeModalTemplate")
.modal-header
button.close(
type="button"
data-dismiss="modal"
ng-click="$close()"
) &times;
h3 #{translate("per_user_tc_title")}
.modal-body
.teaser-video-container
video.teaser-video(autoplay, loop)
source(ng-src="{{ '/img/teasers/track-changes/per-user-track-changes.mp4' }}", type="video/mp4")
img(src="/img/teasers/track-changes/per-user-track-changes.gif")
h4.teaser-title #{translate("you_can_use_per_user_tc")}
.row
.col-md-8.col-md-offset-2
ul.list-unstyled
li
i.fa.fa-check &nbsp;
| #{translate("turn_tc_on_individuals")}
li
i.fa.fa-check &nbsp;
| #{translate("keep_tc_on_like_before")}
.modal-footer()
button.btn.btn-default(
ng-click="$close()"
)
span #{translate("close")}
script(type="text/ng-template", id="bulkActionsModalTemplate")
.modal-header
button.close(

View file

@ -37,10 +37,6 @@ 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

@ -11,7 +11,13 @@ define [
CUR_FILE : "cur_file"
OVERVIEW : "overview"
$scope.UserTCSyncState = UserTCSyncState =
SYNCED : "synced"
PENDING : "pending"
$scope.reviewPanel =
trackChangesState: {}
trackChangesOnForEveryone: false
entries: {}
resolvedComments: {}
hasEntries: false
@ -25,6 +31,8 @@ define [
commentThreads: {}
resolvedThreadIds: {}
rendererData: {}
formattedProjectMembers: {}
fullTCStateCollapsed: true
loadingThreads: false
# All selected changes. If a aggregated change (insertion + deletion) is selection, the two ids
# will be present. The length of this array will differ from the count below (see explanation).
@ -32,6 +40,7 @@ define [
# A count of user-facing selected changes. An aggregated change (insertion + deletion) will count
# as only one.
nVisibleSelectedChanges: 0
showPerUserTCNotice: window.showPerUserTCNotice
window.addEventListener "beforeunload", () ->
collapsedStates = {}
@ -59,6 +68,15 @@ define [
if !visible
$scope.ui.reviewPanelOpen = false
$scope.$watch "project.members", (members) ->
$scope.reviewPanel.formattedProjectMembers = {}
if $scope.project?.owner?
$scope.reviewPanel.formattedProjectMembers[$scope.project.owner._id] = formatUser($scope.project.owner)
if $scope.project?.members?
for member in members
if member.privileges == "readAndWrite"
$scope.reviewPanel.formattedProjectMembers[member._id] = formatUser(member)
$scope.commentState =
adding: false
content: ""
@ -567,26 +585,83 @@ define [
$scope.gotoEntry = (doc_id, entry) ->
ide.editorManager.openDocId(doc_id, { gotoOffset: entry.offset })
$scope.toggleTrackChanges = (value) ->
$scope.toggleFullTCStateCollapse = () ->
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 }
if $scope.reviewPanel.showPerUserTCNotice
$scope.openPerUserTCNoticeModal()
$scope.reviewPanel.fullTCStateCollapsed = !$scope.reviewPanel.fullTCStateCollapsed
else
$scope.openTrackChangesUpgradeModal()
_setUserTCState = (userId, newValue, isLocal = false) ->
$scope.reviewPanel.trackChangesState[userId] ?= {}
state = $scope.reviewPanel.trackChangesState[userId]
if !state.syncState? or state.syncState == UserTCSyncState.SYNCED
state.value = newValue
state.syncState = UserTCSyncState.SYNCED
else if state.syncState == UserTCSyncState.PENDING and state.value == newValue
state.syncState = UserTCSyncState.SYNCED
else if isLocal
state.value = newValue
state.syncState = UserTCSyncState.PENDING
if userId == ide.$scope.user.id
$scope.editor.wantTrackChanges = newValue
_setEveryoneTCState = (newValue, isLocal = false) ->
$scope.reviewPanel.trackChangesOnForEveryone = newValue
for member in $scope.project.members
_setUserTCState(member._id, newValue, isLocal)
_setUserTCState($scope.project.owner._id, newValue, isLocal)
applyClientTrackChangesStateToServer = () ->
if $scope.reviewPanel.trackChangesOnForEveryone
data = {on : true}
else
data = {on_for: {}}
for userId, userState of $scope.reviewPanel.trackChangesState
data.on_for[userId] = userState.value
data._csrf = window.csrfToken
$http.post "/project/#{$scope.project_id}/track_changes", data
applyTrackChangesStateToClient = (state) ->
if typeof state is "boolean"
_setEveryoneTCState state
else
$scope.reviewPanel.trackChangesOnForEveryone = false
for member in $scope.project.members
_setUserTCState(member._id, state[member._id] ? false)
_setUserTCState($scope.project.owner._id, state[$scope.project.owner._id] ? false)
$scope.toggleTrackChangesForEveryone = (onForEveryone) ->
_setEveryoneTCState onForEveryone, true
applyClientTrackChangesStateToServer()
$scope.toggleTrackChangesForUser = (onForUser, userId) ->
_setUserTCState userId, onForUser, true
applyClientTrackChangesStateToServer()
ide.socket.on "toggle-track-changes", (state) ->
$scope.$apply () ->
applyTrackChangesStateToClient(state)
$scope.toggleTrackChangesFromKbdShortcut = () ->
if !$scope.project.features.trackChangesVisible
return
if $scope.editor.wantTrackChanges
$scope.toggleTrackChanges false
else
$scope.toggleTrackChanges true
ide.socket.on "toggle-track-changes", (value) ->
$scope.$apply () ->
$scope.editor.wantTrackChanges = value
$scope.toggleTrackChangesForUser !$scope.reviewPanel.trackChangesState[ide.$scope.user.id].value, ide.$scope.user.id
_inited = false
ide.$scope.$on "project:joined", () ->
return if _inited
project = ide.$scope.project
if project.features.trackChanges
window.trackChangesState ?= false
applyTrackChangesStateToClient(window.trackChangesState)
else
applyTrackChangesStateToClient(false)
_inited = true
_refreshingRangeUsers = false
_refreshedForUserIds = {}
@ -680,3 +755,11 @@ define [
controller: "TrackChangesUpgradeModalController"
scope: $scope.$new()
}
$scope.openPerUserTCNoticeModal = () ->
$scope.reviewPanel.showPerUserTCNotice = false
$modal.open({
templateUrl: "perUserTCNoticeModalTemplate"
}).result.finally () ->
event_tracking.sendMB "shown-per-user-tc-notice"

View file

@ -4,20 +4,23 @@ define [
App.directive "reviewPanelToggle", () ->
restrict: "E"
scope:
onToggle: '='
onToggle: '&'
ngModel: '='
valWhenUndefined: '=?'
disabled: '=?'
onDisabledClick: '=?'
onDisabledClick: '&?'
link: (scope) ->
if !scope.disabled?
scope.disabled = false
scope.onChange = (args...) ->
scope.onToggle(scope.localModel)
scope.onToggle({ isOn: scope.localModel })
scope.handleClick = () ->
if scope.disabled
if scope.disabled and scope.onDisabledClick?
scope.onDisabledClick()
scope.localModel = scope.ngModel
scope.$watch "ngModel", (value) ->
if scope.valWhenUndefined? and !value?
value = scope.valWhenUndefined
scope.localModel = value
template: """

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

View file

@ -57,6 +57,15 @@
}
}
.rp-collapse-arrow() {
display: inline-block;
transform: rotateZ(0deg);
transition: transform 0.15s ease;
&-on {
transform: rotateZ(-90deg);
}
}
.triangle(@_, @width, @height, @color) {
position: absolute;
border-color: transparent;
@ -131,7 +140,6 @@
}
position: relative;
height: @rp-toolbar-height;
border-bottom: 1px solid @rp-border-grey;
background-color: @rp-bg-dim-blue;
text-align: center;
@ -144,6 +152,10 @@
text-align: right;
flex-grow: 1;
}
.review-panel-toolbar-icon-on {
margin-right: 5px;
color: @red;
}
.review-panel-toolbar-label-disabled {
cursor: auto;
margin-right: 5px;
@ -151,6 +163,45 @@
.review-panel-toolbar-spinner {
margin-left: 5px;
}
.rp-tc-state {
position: absolute;
top: 100%;
left: 0;
right: 0;
overflow: hidden;
list-style: none;
padding: 0 5px;
margin: 0;
border-bottom: 1px solid @rp-border-grey;
background-color: @rp-bg-dim-blue;
text-align: left;
}
.rp-tc-state-collapse {
.rp-collapse-arrow;
margin-left: 5px;
}
.rp-tc-state-item {
display: flex;
align-items: center;
padding: 3px 0;
&:last-of-type {
padding-bottom: 5px;
}
}
.rp-tc-state-item-everyone {
border-bottom: 1px solid @rp-border-grey;
color: @red;
}
.rp-tc-state-item-name {
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: @rp-semibold-weight;
}
.rp-tc-state-item-name-disabled {
opacity: .35;
}
.rp-entry-list {
display: none;
@ -629,16 +680,9 @@
font-size: 0.9em;
}
.rp-overview-file-header-collapse {
display: inline-block;
.rp-collapse-arrow;
float: left;
transform: rotateZ(0deg);
transition: transform 0.15s ease
}
.rp-overview-file-header-collapse-on {
transform: rotateZ(-90deg);
}
.rp-overview-file-entries {
overflow: hidden;
}
@ -771,6 +815,11 @@
background-color: #FFF;
}
}
&:disabled + .rp-toggle-btn {
cursor: default;
opacity: .35;
}
}
.ace-editor-wrapper {