mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-08 18:40:35 +00:00
Merge pull request #547 from sharelatex/ja-per-user-track-changes
Per-user track changes
This commit is contained in:
commit
c928935865
10 changed files with 251 additions and 50 deletions
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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" : {
|
||||
|
|
|
@ -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()"
|
||||
) ×
|
||||
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
|
||||
| #{translate("turn_tc_on_individuals")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{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(
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 |
Binary file not shown.
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue