mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-11 07:00:47 +02:00
Merge branch 'master' into sk-server-side-labels-loading
This commit is contained in:
@@ -176,7 +176,7 @@ module.exports = EditorController =
|
||||
callback?()
|
||||
|
||||
renameProject: (project_id, newName, callback = (err) ->) ->
|
||||
ProjectDetailsHandler.renameProject project_id, newName, ->
|
||||
ProjectDetailsHandler.renameProject project_id, newName, (err) ->
|
||||
if err?
|
||||
logger.err err:err, project_id:project_id, newName:newName, "error renaming project"
|
||||
return callback(err)
|
||||
|
||||
@@ -25,6 +25,18 @@ module.exports = ErrorController =
|
||||
else if error instanceof Errors.TooManyRequestsError
|
||||
logger.warn {err: error, url: req.url}, "too many requests error"
|
||||
res.sendStatus(429)
|
||||
else if error instanceof Errors.InvalidNameError
|
||||
logger.warn {err: error, url: req.url}, "invalid name error"
|
||||
res.status(400)
|
||||
res.send(error.message)
|
||||
else
|
||||
logger.error err: error, url:req.url, method:req.method, user:user, "error passed to top level next middlewear"
|
||||
ErrorController.serverError req, res
|
||||
|
||||
handleApiError: (error, req, res, next) ->
|
||||
if error instanceof Errors.NotFoundError
|
||||
logger.warn {err: error, url: req.url}, "not found error"
|
||||
res.sendStatus(404)
|
||||
else
|
||||
logger.error err: error, url:req.url, method:req.method, user:user, "error passed to top level next middlewear"
|
||||
res.sendStatus(500)
|
||||
|
||||
@@ -5,7 +5,6 @@ NotFoundError = (message) ->
|
||||
return error
|
||||
NotFoundError.prototype.__proto__ = Error.prototype
|
||||
|
||||
|
||||
ServiceNotConfiguredError = (message) ->
|
||||
error = new Error(message)
|
||||
error.name = "ServiceNotConfiguredError"
|
||||
@@ -13,7 +12,6 @@ ServiceNotConfiguredError = (message) ->
|
||||
return error
|
||||
ServiceNotConfiguredError.prototype.__proto__ = Error.prototype
|
||||
|
||||
|
||||
TooManyRequestsError = (message) ->
|
||||
error = new Error(message)
|
||||
error.name = "TooManyRequestsError"
|
||||
@@ -21,8 +19,15 @@ TooManyRequestsError = (message) ->
|
||||
return error
|
||||
TooManyRequestsError.prototype.__proto__ = Error.prototype
|
||||
|
||||
InvalidNameError = (message) ->
|
||||
error = new Error(message)
|
||||
error.name = "InvalidNameError"
|
||||
error.__proto__ = InvalidNameError.prototype
|
||||
return error
|
||||
InvalidNameError.prototype.__proto__ = Error.prototype
|
||||
|
||||
module.exports = Errors =
|
||||
NotFoundError: NotFoundError
|
||||
ServiceNotConfiguredError: ServiceNotConfiguredError
|
||||
TooManyRequestsError: TooManyRequestsError
|
||||
InvalidNameError: InvalidNameError
|
||||
|
||||
@@ -101,7 +101,7 @@ module.exports = ProjectController =
|
||||
res.send(project_id:project._id)
|
||||
|
||||
|
||||
newProject: (req, res)->
|
||||
newProject: (req, res, next)->
|
||||
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||
projectName = req.body.projectName?.trim()
|
||||
template = req.body.template
|
||||
@@ -113,25 +113,17 @@ module.exports = ProjectController =
|
||||
else
|
||||
projectCreationHandler.createBasicProject user_id, projectName, cb
|
||||
], (err, project)->
|
||||
if err?
|
||||
logger.error err: err, project: project, user: user_id, name: projectName, templateType: template, "error creating project"
|
||||
res.sendStatus 500
|
||||
else
|
||||
logger.log project: project, user: user_id, name: projectName, templateType: template, "created project"
|
||||
res.send {project_id:project._id}
|
||||
return next(err) if err?
|
||||
logger.log project: project, user: user_id, name: projectName, templateType: template, "created project"
|
||||
res.send {project_id:project._id}
|
||||
|
||||
|
||||
renameProject: (req, res)->
|
||||
renameProject: (req, res, next)->
|
||||
project_id = req.params.Project_id
|
||||
newName = req.body.newProjectName
|
||||
if newName.length > 150
|
||||
return res.sendStatus 400
|
||||
editorController.renameProject project_id, newName, (err)->
|
||||
if err?
|
||||
logger.err err:err, project_id:project_id, newName:newName, "problem renaming project"
|
||||
res.sendStatus 500
|
||||
else
|
||||
res.sendStatus 200
|
||||
return next(err) if err?
|
||||
res.sendStatus 200
|
||||
|
||||
projectListPage: (req, res, next)->
|
||||
timer = new metrics.Timer("project-list")
|
||||
|
||||
@@ -6,6 +6,7 @@ ObjectId = require('mongoose').Types.ObjectId
|
||||
Project = require('../../models/Project').Project
|
||||
Folder = require('../../models/Folder').Folder
|
||||
ProjectEntityHandler = require('./ProjectEntityHandler')
|
||||
ProjectDetailsHandler = require('./ProjectDetailsHandler')
|
||||
User = require('../../models/User').User
|
||||
fs = require('fs')
|
||||
Path = require "path"
|
||||
@@ -15,19 +16,21 @@ module.exports = ProjectCreationHandler =
|
||||
|
||||
createBlankProject : (owner_id, projectName, callback = (error, project) ->)->
|
||||
metrics.inc("project-creation")
|
||||
logger.log owner_id:owner_id, projectName:projectName, "creating blank project"
|
||||
rootFolder = new Folder {'name':'rootFolder'}
|
||||
project = new Project
|
||||
owner_ref : new ObjectId(owner_id)
|
||||
name : projectName
|
||||
if Settings.currentImageName?
|
||||
project.imageName = Settings.currentImageName
|
||||
project.rootFolder[0] = rootFolder
|
||||
User.findById owner_id, "ace.spellCheckLanguage", (err, user)->
|
||||
project.spellCheckLanguage = user.ace.spellCheckLanguage
|
||||
project.save (err)->
|
||||
return callback(err) if err?
|
||||
callback err, project
|
||||
ProjectDetailsHandler.validateProjectName projectName, (error) ->
|
||||
return callback(error) if error?
|
||||
logger.log owner_id:owner_id, projectName:projectName, "creating blank project"
|
||||
rootFolder = new Folder {'name':'rootFolder'}
|
||||
project = new Project
|
||||
owner_ref : new ObjectId(owner_id)
|
||||
name : projectName
|
||||
if Settings.currentImageName?
|
||||
project.imageName = Settings.currentImageName
|
||||
project.rootFolder[0] = rootFolder
|
||||
User.findById owner_id, "ace.spellCheckLanguage", (err, user)->
|
||||
project.spellCheckLanguage = user.ace.spellCheckLanguage
|
||||
project.save (err)->
|
||||
return callback(err) if err?
|
||||
callback err, project
|
||||
|
||||
createBasicProject : (owner_id, projectName, callback = (error, project) ->)->
|
||||
self = @
|
||||
|
||||
@@ -7,8 +7,7 @@ _ = require("underscore")
|
||||
PublicAccessLevels = require("../Authorization/PublicAccessLevels")
|
||||
Errors = require("../Errors/Errors")
|
||||
|
||||
module.exports =
|
||||
|
||||
module.exports = ProjectDetailsHandler =
|
||||
getDetails: (project_id, callback)->
|
||||
ProjectGetter.getProject project_id, {name:true, description:true, compiler:true, features:true, owner_ref:true}, (err, project)->
|
||||
if err?
|
||||
@@ -39,16 +38,29 @@ module.exports =
|
||||
callback(err)
|
||||
|
||||
renameProject: (project_id, newName, callback = ->)->
|
||||
logger.log project_id: project_id, newName:newName, "renaming project"
|
||||
ProjectGetter.getProject project_id, {name:true}, (err, project)->
|
||||
if err? or !project?
|
||||
logger.err err:err, project_id:project_id, "error getting project or could not find it todo project rename"
|
||||
return callback(err)
|
||||
oldProjectName = project.name
|
||||
Project.update _id:project_id, {name: newName}, (err, project)=>
|
||||
if err?
|
||||
ProjectDetailsHandler.validateProjectName newName, (error) ->
|
||||
return callback(error) if error?
|
||||
logger.log project_id: project_id, newName:newName, "renaming project"
|
||||
ProjectGetter.getProject project_id, {name:true}, (err, project)->
|
||||
if err? or !project?
|
||||
logger.err err:err, project_id:project_id, "error getting project or could not find it todo project rename"
|
||||
return callback(err)
|
||||
tpdsUpdateSender.moveEntity {project_id:project_id, project_name:oldProjectName, newProjectName:newName}, callback
|
||||
oldProjectName = project.name
|
||||
Project.update _id:project_id, {name: newName}, (err, project)=>
|
||||
if err?
|
||||
return callback(err)
|
||||
tpdsUpdateSender.moveEntity {project_id:project_id, project_name:oldProjectName, newProjectName:newName}, callback
|
||||
|
||||
MAX_PROJECT_NAME_LENGTH: 150
|
||||
validateProjectName: (name, callback = (error) ->) ->
|
||||
if name.length == 0
|
||||
return callback(new Errors.InvalidNameError("Project name cannot be blank"))
|
||||
else if name.length > @MAX_PROJECT_NAME_LENGTH
|
||||
return callback(new Errors.InvalidNameError("Project name is too long"))
|
||||
else if name.indexOf("/") > -1
|
||||
return callback(new Errors.InvalidNameError("Project name cannot not contain / characters"))
|
||||
else
|
||||
return callback()
|
||||
|
||||
setPublicAccessLevel : (project_id, newAccessLevel, callback = ->)->
|
||||
logger.log project_id: project_id, level: newAccessLevel, "set public access level"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
Project = require('../../models/Project').Project
|
||||
logger = require('logger-sharelatex')
|
||||
Project = require("../../models/Project").Project
|
||||
|
||||
module.exports =
|
||||
markAsUpdated : (project_id, callback)->
|
||||
|
||||
@@ -64,8 +64,9 @@ module.exports =
|
||||
if !subscription?
|
||||
logger.err user_id:user_id, "no subscription found for user"
|
||||
return callback("no subscription found")
|
||||
limitReached = subscription.member_ids.length >= subscription.membersLimit
|
||||
logger.log user_id:user_id, limitReached:limitReached, currentTotal: subscription.member_ids.length, membersLimit: subscription.membersLimit, "checking if subscription members limit has been reached"
|
||||
currentTotal = (subscription.member_ids or []).length + (subscription.invited_emails or []).length
|
||||
limitReached = currentTotal >= subscription.membersLimit
|
||||
logger.log user_id:user_id, limitReached:limitReached, currentTotal: currentTotal, membersLimit: subscription.membersLimit, "checking if subscription members limit has been reached"
|
||||
callback(err, limitReached, subscription)
|
||||
|
||||
getOwnerIdOfProject = (project_id, callback)->
|
||||
|
||||
@@ -33,6 +33,14 @@ module.exports =
|
||||
return res.sendStatus 500
|
||||
res.send()
|
||||
|
||||
removeEmailInviteFromGroup: (req, res)->
|
||||
adminUserId = AuthenticationController.getLoggedInUserId(req)
|
||||
email = req.params.email
|
||||
logger.log {adminUserId, email}, "removing email invite from group subscription"
|
||||
SubscriptionGroupHandler.removeEmailInviteFromGroup adminUserId, email, (err)->
|
||||
return next(error) if error?
|
||||
res.send()
|
||||
|
||||
removeSelfFromGroup: (req, res)->
|
||||
adminUserId = req.query.admin_user_id
|
||||
userToRemove_id = AuthenticationController.getLoggedInUserId(req)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
async = require("async")
|
||||
_ = require("underscore")
|
||||
UserCreator = require("../User/UserCreator")
|
||||
SubscriptionUpdater = require("./SubscriptionUpdater")
|
||||
SubscriptionLocator = require("./SubscriptionLocator")
|
||||
UserLocator = require("../User/UserLocator")
|
||||
@@ -15,36 +14,40 @@ module.exports = SubscriptionGroupHandler =
|
||||
|
||||
addUserToGroup: (adminUserId, newEmail, callback)->
|
||||
logger.log adminUserId:adminUserId, newEmail:newEmail, "adding user to group"
|
||||
UserCreator.getUserOrCreateHoldingAccount newEmail, (err, user)->
|
||||
LimitationsManager.hasGroupMembersLimitReached adminUserId, (err, limitReached, subscription)->
|
||||
if err?
|
||||
logger.err err:err, adminUserId:adminUserId, newEmail:newEmail, "error creating user for holding account"
|
||||
logger.err err:err, adminUserId:adminUserId, newEmail:newEmail, "error checking if limit reached for group plan"
|
||||
return callback(err)
|
||||
if !user?
|
||||
msg = "no user returned whenc reating holidng account or getting user"
|
||||
logger.err adminUserId:adminUserId, newEmail:newEmail, msg
|
||||
return callback(msg)
|
||||
LimitationsManager.hasGroupMembersLimitReached adminUserId, (err, limitReached, subscription)->
|
||||
if err?
|
||||
logger.err err:err, adminUserId:adminUserId, newEmail:newEmail, "error checking if limit reached for group plan"
|
||||
return callback(err)
|
||||
if limitReached
|
||||
logger.err adminUserId:adminUserId, newEmail:newEmail, "group subscription limit reached not adding user to group"
|
||||
return callback(limitReached:limitReached)
|
||||
SubscriptionUpdater.addUserToGroup adminUserId, user._id, (err)->
|
||||
if err?
|
||||
logger.err err:err, "error adding user to group"
|
||||
return callback(err)
|
||||
NotificationsBuilder.groupPlan(user, {subscription_id:subscription._id}).read()
|
||||
userViewModel = buildUserViewModel(user)
|
||||
callback(err, userViewModel)
|
||||
if limitReached
|
||||
logger.err adminUserId:adminUserId, newEmail:newEmail, "group subscription limit reached not adding user to group"
|
||||
return callback(limitReached:limitReached)
|
||||
UserLocator.findByEmail newEmail, (err, user)->
|
||||
return callback(err) if err?
|
||||
if user?
|
||||
SubscriptionUpdater.addUserToGroup adminUserId, user._id, (err)->
|
||||
if err?
|
||||
logger.err err:err, "error adding user to group"
|
||||
return callback(err)
|
||||
NotificationsBuilder.groupPlan(user, {subscription_id:subscription._id}).read()
|
||||
userViewModel = buildUserViewModel(user)
|
||||
callback(err, userViewModel)
|
||||
else
|
||||
SubscriptionUpdater.addEmailInviteToGroup adminUserId, newEmail, (err) ->
|
||||
return callback(err) if err?
|
||||
userViewModel = buildEmailInviteViewModel(newEmail)
|
||||
callback(err, userViewModel)
|
||||
|
||||
removeUserFromGroup: (adminUser_id, userToRemove_id, callback)->
|
||||
SubscriptionUpdater.removeUserFromGroup adminUser_id, userToRemove_id, callback
|
||||
|
||||
|
||||
removeEmailInviteFromGroup: (adminUser_id, email, callback) ->
|
||||
SubscriptionUpdater.removeEmailInviteFromGroup adminUser_id, email, callback
|
||||
|
||||
getPopulatedListOfMembers: (adminUser_id, callback)->
|
||||
SubscriptionLocator.getUsersSubscription adminUser_id, (err, subscription)->
|
||||
users = []
|
||||
for email in subscription.invited_emails or []
|
||||
users.push buildEmailInviteViewModel(email)
|
||||
jobs = _.map subscription.member_ids, (user_id)->
|
||||
return (cb)->
|
||||
UserLocator.findById user_id, (err, user)->
|
||||
@@ -91,7 +94,21 @@ module.exports = SubscriptionGroupHandler =
|
||||
return callback()
|
||||
SubscriptionGroupHandler.addUserToGroup subscription?.admin_id, userEmail, callback
|
||||
|
||||
|
||||
convertEmailInvitesToMemberships: (email, user_id, callback = (err) ->) ->
|
||||
SubscriptionLocator.getGroupsWithEmailInvite email, (err, groups = []) ->
|
||||
return callback(err) if err?
|
||||
logger.log {email, user_id, groups}, "found groups to convert from email invite to member"
|
||||
jobs = []
|
||||
for group in groups
|
||||
do (group) ->
|
||||
jobs.push (cb) ->
|
||||
SubscriptionUpdater.removeEmailInviteFromGroup group.admin_id, email, (err) ->
|
||||
return cb(err) if err?
|
||||
SubscriptionUpdater.addUserToGroup group.admin_id, user_id, (err) ->
|
||||
return cb(err) if err?
|
||||
logger.log {group_id: group._id, user_id, email}, "converted email invite to group membership"
|
||||
return cb()
|
||||
async.series jobs, callback
|
||||
|
||||
buildUserViewModel = (user)->
|
||||
u =
|
||||
@@ -101,3 +118,9 @@ buildUserViewModel = (user)->
|
||||
holdingAccount: user.holdingAccount
|
||||
_id: user._id
|
||||
return u
|
||||
|
||||
buildEmailInviteViewModel = (email) ->
|
||||
return {
|
||||
email: email
|
||||
holdingAccount: true
|
||||
}
|
||||
@@ -30,3 +30,6 @@ module.exports =
|
||||
|
||||
getGroupSubscriptionMemberOf: (user_id, callback)->
|
||||
Subscription.findOne {member_ids: user_id}, {_id:1, planCode:1}, callback
|
||||
|
||||
getGroupsWithEmailInvite: (email, callback) ->
|
||||
Subscription.find { invited_emails: email }, callback
|
||||
@@ -25,6 +25,7 @@ module.exports =
|
||||
webRouter.post '/subscription/group/user', AuthenticationController.requireLogin(), SubscriptionGroupController.addUserToGroup
|
||||
webRouter.get '/subscription/group/export', AuthenticationController.requireLogin(), SubscriptionGroupController.exportGroupCsv
|
||||
webRouter.delete '/subscription/group/user/:user_id', AuthenticationController.requireLogin(), SubscriptionGroupController.removeUserFromGroup
|
||||
webRouter.delete '/subscription/group/email/:email', AuthenticationController.requireLogin(), SubscriptionGroupController.removeEmailInviteFromGroup
|
||||
webRouter.delete '/subscription/group/user', AuthenticationController.requireLogin(), SubscriptionGroupController.removeSelfFromGroup
|
||||
|
||||
|
||||
|
||||
@@ -35,6 +35,14 @@ module.exports = SubscriptionUpdater =
|
||||
logger.err err:err, searchOps:searchOps, insertOperation:insertOperation, "error findy and modify add user to group"
|
||||
return callback(err)
|
||||
UserFeaturesUpdater.updateFeatures user_id, subscription.planCode, callback
|
||||
|
||||
addEmailInviteToGroup: (adminUser_id, email, callback) ->
|
||||
logger.log {adminUser_id, email}, "adding email into mongo subscription"
|
||||
searchOps =
|
||||
admin_id: adminUser_id
|
||||
insertOperation =
|
||||
"$addToSet": {invited_emails: email}
|
||||
Subscription.findAndModify searchOps, insertOperation, callback
|
||||
|
||||
removeUserFromGroup: (adminUser_id, user_id, callback)->
|
||||
searchOps =
|
||||
@@ -47,6 +55,12 @@ module.exports = SubscriptionUpdater =
|
||||
return callback(err)
|
||||
SubscriptionUpdater._setUsersMinimumFeatures user_id, callback
|
||||
|
||||
removeEmailInviteFromGroup: (adminUser_id, email, callback)->
|
||||
Subscription.update {
|
||||
admin_id: adminUser_id
|
||||
}, "$pull": {
|
||||
invited_emails: email
|
||||
}, callback
|
||||
|
||||
_createNewSubscription: (adminUser_id, callback)->
|
||||
logger.log adminUser_id:adminUser_id, "creating new subscription"
|
||||
|
||||
@@ -7,19 +7,22 @@ logger = require("logger-sharelatex")
|
||||
module.exports = UserHandler =
|
||||
|
||||
populateGroupLicenceInvite: (user, callback = ->)->
|
||||
logger.log user_id:user._id, "populating any potential group licence invites"
|
||||
licence = SubscriptionDomainHandler.getLicenceUserCanJoin user
|
||||
if !licence?
|
||||
return callback()
|
||||
SubscriptionGroupHandler.convertEmailInvitesToMemberships user.email, user._id, (err) ->
|
||||
return callback(err) if err?
|
||||
|
||||
SubscriptionGroupHandler.isUserPartOfGroup user._id, licence.subscription_id, (err, alreadyPartOfGroup)->
|
||||
if err?
|
||||
return callback(err)
|
||||
else if alreadyPartOfGroup
|
||||
logger.log user_id:user._id, "user already part of group, not creating notifcation for them"
|
||||
logger.log user_id:user._id, "populating any potential group licence invites"
|
||||
licence = SubscriptionDomainHandler.getLicenceUserCanJoin user
|
||||
if !licence?
|
||||
return callback()
|
||||
else
|
||||
NotificationsBuilder.groupPlan(user, licence).create(callback)
|
||||
|
||||
SubscriptionGroupHandler.isUserPartOfGroup user._id, licence.subscription_id, (err, alreadyPartOfGroup)->
|
||||
if err?
|
||||
return callback(err)
|
||||
else if alreadyPartOfGroup
|
||||
logger.log user_id:user._id, "user already part of group, not creating notifcation for them"
|
||||
return callback()
|
||||
else
|
||||
NotificationsBuilder.groupPlan(user, licence).create(callback)
|
||||
|
||||
setupLoginData: (user, callback = ->)->
|
||||
@populateGroupLicenceInvite user, callback
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
version = {
|
||||
"pdfjs": "1.7.225"
|
||||
"pdfjs": "1.8.188"
|
||||
"moment": "2.9.0"
|
||||
"ace": "1.2.5"
|
||||
}
|
||||
|
||||
@@ -164,11 +164,12 @@ server = require('http').createServer(app)
|
||||
# process api routes first, if nothing matched fall though and use
|
||||
# web middlewear + routes
|
||||
app.use(apiRouter)
|
||||
app.use(ErrorController.handleApiError)
|
||||
app.use(webRouter)
|
||||
app.use(ErrorController.handleError)
|
||||
|
||||
router = new Router(webRouter, apiRouter)
|
||||
|
||||
app.use ErrorController.handleError
|
||||
|
||||
module.exports =
|
||||
app: app
|
||||
|
||||
@@ -6,7 +6,8 @@ ObjectId = Schema.ObjectId
|
||||
|
||||
SubscriptionSchema = new Schema
|
||||
admin_id : {type:ObjectId, ref:'User', index: {unique: true, dropDups: true}}
|
||||
member_ids : [ type:ObjectId, ref:'User' ]
|
||||
member_ids : [ type:ObjectId, ref:'User' ]
|
||||
invited_emails: [ String ]
|
||||
recurlySubscription_id : String
|
||||
planCode : {type: String}
|
||||
groupPlan : {type: Boolean, default: false}
|
||||
|
||||
@@ -167,6 +167,8 @@ script(type='text/ng-template', id='cloneProjectModalTemplate')
|
||||
.modal-header
|
||||
h3 #{translate("copy_project")}
|
||||
.modal-body
|
||||
.alert.alert-danger(ng-show="state.error.message") {{ state.error.message}}
|
||||
.alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")}
|
||||
form(name="cloneProjectForm", novalidate)
|
||||
.form-group
|
||||
label #{translate("new_name")}
|
||||
|
||||
@@ -8,20 +8,20 @@
|
||||
) !{translate("track_changes_is_on")}
|
||||
a.rp-bulk-actions-btn(
|
||||
href
|
||||
ng-if="reviewPanel.selectedEntryIds.length > 1"
|
||||
ng-if="reviewPanel.nVisibleSelectedChanges > 1"
|
||||
ng-click="showBulkAcceptDialog();"
|
||||
)
|
||||
i.fa.fa-check
|
||||
| #{translate("accept_all")}
|
||||
| ({{ reviewPanel.selectedEntryIds.length }})
|
||||
| ({{ reviewPanel.nVisibleSelectedChanges }})
|
||||
a.rp-bulk-actions-btn(
|
||||
href
|
||||
ng-if="reviewPanel.selectedEntryIds.length > 1"
|
||||
ng-if="reviewPanel.nVisibleSelectedChanges > 1"
|
||||
ng-click="showBulkRejectDialog();"
|
||||
)
|
||||
i.fa.fa-times
|
||||
| #{translate("reject_all")}
|
||||
| ({{ reviewPanel.selectedEntryIds.length }})
|
||||
| ({{ reviewPanel.nVisibleSelectedChanges }})
|
||||
a.rp-add-comment-btn(
|
||||
href
|
||||
ng-if="reviewPanel.entries[editor.open_doc_id]['add-comment'] != null"
|
||||
@@ -75,8 +75,19 @@
|
||||
change-entry(
|
||||
entry="entry"
|
||||
user="users[entry.metadata.user_id]"
|
||||
on-reject="rejectChange(entry_id);"
|
||||
on-accept="acceptChange(entry_id);"
|
||||
on-reject="rejectChanges(entry.entry_ids);"
|
||||
on-accept="acceptChanges(entry.entry_ids);"
|
||||
on-indicator-click="toggleReviewPanel();"
|
||||
on-body-click="gotoEntry(editor.open_doc_id, entry)"
|
||||
permissions="permissions"
|
||||
)
|
||||
|
||||
div(ng-if="entry.type === 'aggregate-change'")
|
||||
aggregate-change-entry(
|
||||
entry="entry"
|
||||
user="users[entry.metadata.user_id]"
|
||||
on-reject="rejectChanges(entry.entry_ids);"
|
||||
on-accept="acceptChanges(entry.entry_ids);"
|
||||
on-indicator-click="toggleReviewPanel();"
|
||||
on-body-click="gotoEntry(editor.open_doc_id, entry)"
|
||||
permissions="permissions"
|
||||
@@ -106,7 +117,7 @@
|
||||
bulk-actions-entry(
|
||||
on-bulk-accept="showBulkAcceptDialog();"
|
||||
on-bulk-reject="showBulkRejectDialog();"
|
||||
n-entries="reviewPanel.selectedEntryIds.length"
|
||||
n-entries="reviewPanel.nVisibleSelectedChanges"
|
||||
)
|
||||
|
||||
.rp-entry-list(
|
||||
@@ -142,11 +153,18 @@
|
||||
change-entry(
|
||||
entry="entry"
|
||||
user="users[entry.metadata.user_id]"
|
||||
on-indicator-click="toggleReviewPanel();"
|
||||
ng-click="gotoEntry(doc.doc.id, entry)"
|
||||
permissions="permissions"
|
||||
)
|
||||
|
||||
div(ng-if="entry.type === 'aggregate-change'")
|
||||
aggregate-change-entry(
|
||||
entry="entry"
|
||||
user="users[entry.metadata.user_id]"
|
||||
ng-click="gotoEntry(editor.open_doc_id, entry)"
|
||||
permissions="permissions"
|
||||
)
|
||||
|
||||
div(ng-if="entry.type === 'comment'")
|
||||
comment-entry(
|
||||
entry="entry"
|
||||
@@ -154,7 +172,6 @@
|
||||
on-reply="submitReply(entry, entry_id);"
|
||||
on-save-edit="saveEdit(entry.thread_id, comment)"
|
||||
on-delete="deleteComment(entry.thread_id, comment)"
|
||||
on-indicator-click="toggleReviewPanel();"
|
||||
ng-click="gotoEntry(doc.doc.id, entry)"
|
||||
permissions="permissions"
|
||||
)
|
||||
@@ -222,6 +239,42 @@ script(type='text/ng-template', id='changeEntryTemplate')
|
||||
i.fa.fa-check
|
||||
| #{translate("accept")}
|
||||
|
||||
script(type='text/ng-template', id='aggregateChangeEntryTemplate')
|
||||
div
|
||||
.rp-entry-callout.rp-entry-callout-aggregate
|
||||
.rp-entry-indicator(
|
||||
ng-class="{ 'rp-entry-indicator-focused': entry.focused }"
|
||||
ng-click="onIndicatorClick();"
|
||||
)
|
||||
i.fa.fa-pencil
|
||||
.rp-entry.rp-entry-aggregate(
|
||||
ng-class="{ 'rp-entry-focused': entry.focused }"
|
||||
)
|
||||
.rp-entry-body
|
||||
.rp-entry-action-icon
|
||||
i.fa.fa-pencil
|
||||
.rp-entry-details
|
||||
.rp-entry-description
|
||||
| #{translate("aggregate_changed")}
|
||||
del.rp-content-highlight {{ entry.metadata.replaced_content }}
|
||||
| #{translate("aggregate_to")}
|
||||
ins.rp-content-highlight {{ entry.content }}
|
||||
a.rp-collapse-toggle(
|
||||
href
|
||||
ng-if="needsCollapsing"
|
||||
ng-click="toggleCollapse();"
|
||||
) {{ isCollapsed ? '... (#{translate("show_all")})' : ' (#{translate("show_less")})' }}
|
||||
.rp-entry-metadata
|
||||
| {{ entry.metadata.ts | date : 'MMM d, y h:mm a' }} •
|
||||
span.rp-entry-user(style="color: hsl({{ user.hue }}, 70%, 40%);") {{ user.name }}
|
||||
.rp-entry-actions(ng-if="permissions.write")
|
||||
a.rp-entry-button(href, ng-click="onReject();")
|
||||
i.fa.fa-times
|
||||
| #{translate("reject")}
|
||||
a.rp-entry-button(href, ng-click="onAccept();")
|
||||
i.fa.fa-check
|
||||
| #{translate("accept")}
|
||||
|
||||
script(type='text/ng-template', id='commentEntryTemplate')
|
||||
.rp-comment-wrapper(
|
||||
ng-class="{ 'rp-comment-wrapper-resolving': state.animating }"
|
||||
|
||||
@@ -98,6 +98,8 @@ script(type='text/ng-template', id='renameProjectModalTemplate')
|
||||
) ×
|
||||
h3 #{translate("rename_project")}
|
||||
.modal-body
|
||||
.alert.alert-danger(ng-show="state.error.message") {{ state.error.message}}
|
||||
.alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")}
|
||||
form(name="renameProjectForm", novalidate)
|
||||
input.form-control(
|
||||
type="text",
|
||||
@@ -111,8 +113,10 @@ script(type='text/ng-template', id='renameProjectModalTemplate')
|
||||
button.btn.btn-default(ng-click="cancel()") #{translate("cancel")}
|
||||
button.btn.btn-primary(
|
||||
ng-click="rename()",
|
||||
ng-disabled="renameProjectForm.$invalid"
|
||||
) #{translate("rename")}
|
||||
ng-disabled="renameProjectForm.$invalid || state.inflight"
|
||||
)
|
||||
span(ng-show="!state.inflight") #{translate("rename")}
|
||||
span(ng-show="state.inflight") #{translate("renaming")}...
|
||||
|
||||
script(type='text/ng-template', id='cloneProjectModalTemplate')
|
||||
.modal-header
|
||||
@@ -123,6 +127,8 @@ script(type='text/ng-template', id='cloneProjectModalTemplate')
|
||||
) ×
|
||||
h3 #{translate("copy_project")}
|
||||
.modal-body
|
||||
.alert.alert-danger(ng-show="state.error.message") {{ state.error.message}}
|
||||
.alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")}
|
||||
form(name="cloneProjectForm", novalidate)
|
||||
.form-group
|
||||
label #{translate("new_name")}
|
||||
@@ -155,6 +161,8 @@ script(type='text/ng-template', id='newProjectModalTemplate')
|
||||
) ×
|
||||
h3 #{translate("new_project")}
|
||||
.modal-body
|
||||
.alert.alert-danger(ng-show="state.error.message") {{ state.error.message}}
|
||||
.alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")}
|
||||
form(novalidate, name="newProjectForm")
|
||||
input.form-control(
|
||||
type="text",
|
||||
|
||||
@@ -46,6 +46,7 @@ block content
|
||||
type='text',
|
||||
name='first_name',
|
||||
value=user.first_name
|
||||
ng-non-bindable
|
||||
)
|
||||
.form-group
|
||||
label(for='lastName').control-label #{translate("last_name")}
|
||||
@@ -53,6 +54,7 @@ block content
|
||||
type='text',
|
||||
name='last_name',
|
||||
value=user.last_name
|
||||
ng-non-bindable
|
||||
)
|
||||
.actions
|
||||
button.btn.btn-primary(
|
||||
|
||||
+10
-2
@@ -6,6 +6,7 @@ define [
|
||||
projectName: ide.$scope.project.name + " (Copy)"
|
||||
$scope.state =
|
||||
inflight: false
|
||||
error: false
|
||||
|
||||
$modalInstance.opened.then () ->
|
||||
$timeout () ->
|
||||
@@ -20,9 +21,16 @@ define [
|
||||
|
||||
$scope.clone = () ->
|
||||
$scope.state.inflight = true
|
||||
$scope.state.error = false
|
||||
cloneProject($scope.inputs.projectName)
|
||||
.then (data) ->
|
||||
window.location = "/project/#{data.data.project_id}"
|
||||
.success (data) ->
|
||||
window.location = "/project/#{data.project_id}"
|
||||
.error (body, statusCode) ->
|
||||
$scope.state.inflight = false
|
||||
if statusCode == 400
|
||||
$scope.state.error = { message: body }
|
||||
else
|
||||
$scope.state.error = true
|
||||
|
||||
$scope.cancel = () ->
|
||||
$modalInstance.dismiss('cancel')
|
||||
+59
-8
@@ -26,16 +26,10 @@ define [
|
||||
@$scope.$on "comment:select_line", (e) =>
|
||||
@selectLineIfNoSelection()
|
||||
|
||||
@$scope.$on "change:accept", (e, change_id) =>
|
||||
@acceptChangeIds([ change_id ])
|
||||
|
||||
@$scope.$on "change:reject", (e, change_id) =>
|
||||
@rejectChangeIds([ change_id ])
|
||||
|
||||
@$scope.$on "change:bulk-accept", (e, change_ids) =>
|
||||
@$scope.$on "changes:accept", (e, change_ids) =>
|
||||
@acceptChangeIds(change_ids)
|
||||
|
||||
@$scope.$on "change:bulk-reject", (e, change_ids) =>
|
||||
@$scope.$on "changes:reject", (e, change_ids) =>
|
||||
@rejectChangeIds(change_ids)
|
||||
|
||||
@$scope.$on "comment:remove", (e, comment_id) =>
|
||||
@@ -216,10 +210,66 @@ define [
|
||||
acceptChangeIds: (change_ids) ->
|
||||
@rangesTracker.removeChangeIds(change_ids)
|
||||
@updateAnnotations()
|
||||
@updateFocus()
|
||||
|
||||
rejectChangeIds: (change_ids) ->
|
||||
changes = @rangesTracker.getChanges(change_ids)
|
||||
return if changes.length == 0
|
||||
|
||||
# When doing bulk rejections, adjacent changes might interact with each other.
|
||||
# Consider an insertion with an adjacent deletion (which is a common use-case, replacing words):
|
||||
#
|
||||
# "foo bar baz" -> "foo quux baz"
|
||||
#
|
||||
# The change above will be modeled with two ops, with the insertion going first:
|
||||
#
|
||||
# foo quux baz
|
||||
# |--| -> insertion of "quux", op 1, at position 4
|
||||
# | -> deletion of "bar", op 2, pushed forward by "quux" to position 8
|
||||
#
|
||||
# When rejecting these changes at once, if the insertion is rejected first, we get unexpected
|
||||
# results. What happens is:
|
||||
#
|
||||
# 1) Rejecting the insertion deletes the added word "quux", i.e., it removes 4 chars
|
||||
# starting from position 4;
|
||||
#
|
||||
# "foo quux baz" -> "foo baz"
|
||||
# |--| -> 4 characters to be removed
|
||||
#
|
||||
# 2) Rejecting the deletion adds the deleted word "bar" at position 8 (i.e. it will act as if
|
||||
# the word "quuux" was still present).
|
||||
#
|
||||
# "foo baz" -> "foo bazbar"
|
||||
# | -> deletion of "bar" is reverted by reinserting "bar" at position 8
|
||||
#
|
||||
# While the intended result would be "foo bar baz", what we get is:
|
||||
#
|
||||
# "foo bazbar" (note "bar" readded at position 8)
|
||||
#
|
||||
# The issue happens because of step 1. To revert the insertion of "quux", 4 characters are deleted
|
||||
# from position 4. This includes the position where the deletion exists; when that position is
|
||||
# cleared, the RangesTracker considers that the deletion is gone and stops tracking/updating it.
|
||||
# As we still hold a reference to it, the code tries to revert it by readding the deleted text, but
|
||||
# does so at the outdated position (position 8, which was valid when "quux" was present).
|
||||
#
|
||||
# To avoid this kind of problem, we need to make sure that reverting operations doesn't affect
|
||||
# subsequent operations that come after. Reverse sorting the operations based on position will
|
||||
# achieve it; in the case above, it makes sure that the the deletion is reverted first:
|
||||
#
|
||||
# 1) Rejecting the deletion adds the deleted word "bar" at position 8
|
||||
#
|
||||
# "foo quux baz" -> "foo quuxbar baz"
|
||||
# | -> deletion of "bar" is reverted by
|
||||
# reinserting "bar" at position 8
|
||||
#
|
||||
# 2) Rejecting the insertion deletes the added word "quux", i.e., it removes 4 chars
|
||||
# starting from position 4 and achieves the expected result:
|
||||
#
|
||||
# "foo quuxbar baz" -> "foo bar baz"
|
||||
# |--| -> 4 characters to be removed
|
||||
|
||||
changes.sort((a, b) -> b.op.p - a.op.p)
|
||||
|
||||
session = @editor.getSession()
|
||||
for change in changes
|
||||
if change.op.d?
|
||||
@@ -239,6 +289,7 @@ define [
|
||||
session.$fromReject = false
|
||||
else
|
||||
throw new Error("unknown change: #{JSON.stringify(change)}")
|
||||
setTimeout () => @updateFocus()
|
||||
|
||||
removeCommentId: (comment_id) ->
|
||||
@rangesTracker.removeCommentId(comment_id)
|
||||
|
||||
@@ -6,22 +6,6 @@ define [
|
||||
|
||||
App.factory 'PDFRenderer', ['$q', '$timeout', 'pdfAnnotations', 'pdfTextLayer', 'pdfSpinner', ($q, $timeout, pdfAnnotations, pdfTextLayer, pdfSpinner) ->
|
||||
|
||||
# Have a single worker used by all rendering, to avoid reloading
|
||||
RenderThread = { worker: null, count: 0}
|
||||
|
||||
getRenderThread = () ->
|
||||
if RenderThread.count > 16 # recycle the worker periodically to avoid leaks
|
||||
RenderThread.readyToDestroy = true
|
||||
RenderThread = { worker: null, count: 0 }
|
||||
RenderThread.worker ||= new PDFJS.PDFWorker('pdfjsworker')
|
||||
RenderThread.count++
|
||||
return RenderThread
|
||||
|
||||
resetWorker = (thread) ->
|
||||
thread.worker.destroy() if thread.readyToDestroy
|
||||
|
||||
# The PDF page renderer
|
||||
|
||||
class PDFRenderer
|
||||
JOB_QUEUE_INTERVAL: 25
|
||||
PAGE_LOAD_TIMEOUT: 60*1000
|
||||
@@ -43,8 +27,7 @@ define [
|
||||
# PDFJS.disableStream
|
||||
# PDFJS.disableRange
|
||||
@scale = @options.scale || 1
|
||||
@thread = getRenderThread()
|
||||
@pdfjs = PDFJS.getDocument {url: @url, rangeChunkSize: 2*65536, worker: @thread.worker}
|
||||
@pdfjs = PDFJS.getDocument {url: @url, rangeChunkSize: 2*65536}
|
||||
@pdfjs.onProgress = @options.progressCallback
|
||||
@document = $q.when(@pdfjs)
|
||||
@navigateFn = @options.navigateFn
|
||||
@@ -353,9 +336,8 @@ define [
|
||||
destroy: () ->
|
||||
@shuttingDown = true
|
||||
@resetState()
|
||||
@pdfjs.then (document) =>
|
||||
@pdfjs.then (document) ->
|
||||
document.cleanup()
|
||||
document.destroy()
|
||||
resetWorker(@thread)
|
||||
|
||||
]
|
||||
|
||||
@@ -5,6 +5,7 @@ define [
|
||||
"ide/review-panel/directives/reviewPanelSorted"
|
||||
"ide/review-panel/directives/reviewPanelToggle"
|
||||
"ide/review-panel/directives/changeEntry"
|
||||
"ide/review-panel/directives/aggregateChangeEntry"
|
||||
"ide/review-panel/directives/commentEntry"
|
||||
"ide/review-panel/directives/addCommentEntry"
|
||||
"ide/review-panel/directives/bulkActionsEntry"
|
||||
|
||||
+84
-53
@@ -26,7 +26,12 @@ define [
|
||||
resolvedThreadIds: {}
|
||||
rendererData: {}
|
||||
loadingThreads: false
|
||||
selectedEntryIds: []
|
||||
# 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).
|
||||
selectedEntryIds: []
|
||||
# A count of user-facing selected changes. An aggregated change (insertion + deletion) will count
|
||||
# as only one.
|
||||
nVisibleSelectedChanges: 0
|
||||
|
||||
window.addEventListener "beforeunload", () ->
|
||||
collapsedStates = {}
|
||||
@@ -69,20 +74,12 @@ define [
|
||||
$scope.$apply()
|
||||
$timeout () ->
|
||||
$scope.$broadcast "review-panel:layout"
|
||||
|
||||
ide.socket.on "accept-change", (doc_id, change_id) ->
|
||||
if doc_id != $scope.editor.open_doc_id
|
||||
getChangeTracker(doc_id).removeChangeId(change_id)
|
||||
else
|
||||
$scope.$broadcast "change:accept", change_id
|
||||
updateEntries(doc_id)
|
||||
$scope.$apply () ->
|
||||
|
||||
ide.socket.on "accept-changes", (doc_id, change_ids) ->
|
||||
if doc_id != $scope.editor.open_doc_id
|
||||
getChangeTracker(doc_id).removeChangeIds(change_ids)
|
||||
else
|
||||
$scope.$broadcast "change:bulk-accept", change_ids
|
||||
$scope.$broadcast "changes:accept", change_ids
|
||||
updateEntries(doc_id)
|
||||
$scope.$apply () ->
|
||||
|
||||
@@ -227,28 +224,49 @@ define [
|
||||
|
||||
# Assume we'll delete everything until we see it, then we'll remove it from this object
|
||||
delete_changes = {}
|
||||
for change_id, change of entries
|
||||
if change_id != "add-comment"
|
||||
delete_changes[change_id] = true
|
||||
for change_id, change of resolvedComments
|
||||
delete_changes[change_id] = true
|
||||
for id, change of entries
|
||||
if id not in [ "add-comment", "bulk-actions" ]
|
||||
for entry_id in change.entry_ids
|
||||
delete_changes[entry_id] = true
|
||||
for id, change of resolvedComments
|
||||
for entry_id in change.entry_ids
|
||||
delete_changes[entry_id] = true
|
||||
|
||||
potential_aggregate = false
|
||||
prev_insertion = null
|
||||
|
||||
for change in rangesTracker.changes
|
||||
changed = true
|
||||
delete delete_changes[change.id]
|
||||
entries[change.id] ?= {}
|
||||
|
||||
# Update in place to avoid a full DOM redraw via angular
|
||||
metadata = {}
|
||||
metadata[key] = value for key, value of change.metadata
|
||||
new_entry = {
|
||||
type: if change.op.i then "insert" else "delete"
|
||||
content: change.op.i or change.op.d
|
||||
offset: change.op.p
|
||||
metadata: change.metadata
|
||||
}
|
||||
for key, value of new_entry
|
||||
entries[change.id][key] = value
|
||||
|
||||
if (
|
||||
potential_aggregate and
|
||||
change.op.d and
|
||||
change.op.p == prev_insertion.op.p + prev_insertion.op.i.length and
|
||||
change.metadata.user_id == prev_insertion.metadata.user_id
|
||||
)
|
||||
# An actual aggregate op.
|
||||
entries[prev_insertion.id].type = "aggregate-change"
|
||||
entries[prev_insertion.id].metadata.replaced_content = change.op.d
|
||||
entries[prev_insertion.id].entry_ids.push change.id
|
||||
else
|
||||
entries[change.id] ?= {}
|
||||
delete delete_changes[change.id]
|
||||
new_entry = {
|
||||
type: if change.op.i then "insert" else "delete"
|
||||
entry_ids: [ change.id ]
|
||||
content: change.op.i or change.op.d
|
||||
offset: change.op.p
|
||||
metadata: change.metadata
|
||||
}
|
||||
for key, value of new_entry
|
||||
entries[change.id][key] = value
|
||||
|
||||
if change.op.i
|
||||
potential_aggregate = true
|
||||
prev_insertion = change
|
||||
else
|
||||
potential_aggregate = false
|
||||
prev_insertion = null
|
||||
|
||||
if !$scope.users[change.metadata.user_id]?
|
||||
refreshChangeUsers(change.metadata.user_id)
|
||||
@@ -268,6 +286,7 @@ define [
|
||||
new_entry = {
|
||||
type: "comment"
|
||||
thread_id: comment.op.t
|
||||
entry_ids: [ comment.id ]
|
||||
content: comment.op.c
|
||||
offset: comment.op.p
|
||||
}
|
||||
@@ -295,8 +314,10 @@ define [
|
||||
$scope.$on "editor:focus:changed", (e, selection_offset_start, selection_offset_end, selection) ->
|
||||
doc_id = $scope.editor.open_doc_id
|
||||
entries = getDocEntries(doc_id)
|
||||
# All selected changes will be added to this array.
|
||||
$scope.reviewPanel.selectedEntryIds = []
|
||||
|
||||
# Count of user-visible changes, i.e. an aggregated change will count as one.
|
||||
$scope.reviewPanel.nVisibleSelectedChanges = 0
|
||||
delete entries["add-comment"]
|
||||
delete entries["bulk-actions"]
|
||||
|
||||
@@ -313,53 +334,63 @@ define [
|
||||
}
|
||||
|
||||
for id, entry of entries
|
||||
isChangeEntryAndWithinSelection = false
|
||||
if entry.type == "comment" and not $scope.reviewPanel.resolvedThreadIds[entry.thread_id]
|
||||
entry.focused = (entry.offset <= selection_offset_start <= entry.offset + entry.content.length)
|
||||
else if entry.type == "insert"
|
||||
isEntryWithinSelection = entry.offset >= selection_offset_start and entry.offset + entry.content.length <= selection_offset_end
|
||||
isChangeEntryAndWithinSelection = entry.offset >= selection_offset_start and entry.offset + entry.content.length <= selection_offset_end
|
||||
entry.focused = (entry.offset <= selection_offset_start <= entry.offset + entry.content.length)
|
||||
$scope.reviewPanel.selectedEntryIds.push id if isEntryWithinSelection
|
||||
else if entry.type == "delete"
|
||||
isEntryWithinSelection = selection_offset_start <= entry.offset <= selection_offset_end
|
||||
isChangeEntryAndWithinSelection = selection_offset_start <= entry.offset <= selection_offset_end
|
||||
entry.focused = (entry.offset == selection_offset_start)
|
||||
$scope.reviewPanel.selectedEntryIds.push id if isEntryWithinSelection
|
||||
else if entry.type == "aggregate-change"
|
||||
isChangeEntryAndWithinSelection = entry.offset >= selection_offset_start and entry.offset + entry.content.length <= selection_offset_end
|
||||
entry.focused = (entry.offset <= selection_offset_start <= entry.offset + entry.content.length)
|
||||
else if entry.type in [ "add-comment", "bulk-actions" ] and selection
|
||||
entry.focused = true
|
||||
|
||||
if isChangeEntryAndWithinSelection
|
||||
for entry_id in entry.entry_ids
|
||||
$scope.reviewPanel.selectedEntryIds.push entry_id
|
||||
$scope.reviewPanel.nVisibleSelectedChanges++
|
||||
|
||||
$scope.$broadcast "review-panel:recalculate-screen-positions"
|
||||
$scope.$broadcast "review-panel:layout"
|
||||
|
||||
$scope.acceptChange = (entry_id) ->
|
||||
$http.post "/project/#{$scope.project_id}/doc/#{$scope.editor.open_doc_id}/changes/#{entry_id}/accept", {_csrf: window.csrfToken}
|
||||
$scope.$broadcast "change:accept", entry_id
|
||||
event_tracking.sendMB "rp-change-accepted", { view: if $scope.ui.reviewPanelOpen then $scope.reviewPanel.subView else 'mini' }
|
||||
|
||||
$scope.rejectChange = (entry_id) ->
|
||||
$scope.$broadcast "change:reject", entry_id
|
||||
event_tracking.sendMB "rp-change-rejected", { view: if $scope.ui.reviewPanelOpen then $scope.reviewPanel.subView else 'mini' }
|
||||
|
||||
$scope.acceptChanges = (change_ids) ->
|
||||
_doAcceptChanges change_ids
|
||||
event_tracking.sendMB "rp-changes-accepted", { view: if $scope.ui.reviewPanelOpen then $scope.reviewPanel.subView else 'mini' }
|
||||
|
||||
$scope.rejectChanges = (change_ids) ->
|
||||
_doRejectChanges change_ids
|
||||
event_tracking.sendMB "rp-changes-rejected", { view: if $scope.ui.reviewPanelOpen then $scope.reviewPanel.subView else 'mini' }
|
||||
|
||||
_doAcceptChanges = (change_ids) ->
|
||||
$http.post "/project/#{$scope.project_id}/doc/#{$scope.editor.open_doc_id}/changes/accept", { change_ids, _csrf: window.csrfToken}
|
||||
$scope.$broadcast "changes:accept", change_ids
|
||||
|
||||
_doRejectChanges = (change_ids) ->
|
||||
$scope.$broadcast "changes:reject", change_ids
|
||||
|
||||
bulkAccept = () ->
|
||||
entry_ids = $scope.reviewPanel.selectedEntryIds.slice()
|
||||
$http.post "/project/#{$scope.project_id}/doc/#{$scope.editor.open_doc_id}/changes/accept", { change_ids: entry_ids, _csrf: window.csrfToken}
|
||||
$scope.$broadcast "change:bulk-accept", entry_ids
|
||||
$scope.reviewPanel.selectedEntryIds = []
|
||||
_doAcceptChanges $scope.reviewPanel.selectedEntryIds.slice()
|
||||
event_tracking.sendMB "rp-bulk-accept", {
|
||||
view: if $scope.ui.reviewPanelOpen then $scope.reviewPanel.subView else 'mini',
|
||||
nEntries: $scope.reviewPanel.selectedEntryIds.length
|
||||
nEntries: $scope.reviewPanel.nVisibleSelectedChanges
|
||||
}
|
||||
|
||||
bulkReject = () ->
|
||||
$scope.$broadcast "change:bulk-reject", $scope.reviewPanel.selectedEntryIds.slice()
|
||||
$scope.reviewPanel.selectedEntryIds = []
|
||||
_doRejectChanges $scope.reviewPanel.selectedEntryIds.slice()
|
||||
event_tracking.sendMB "rp-bulk-reject", {
|
||||
view: if $scope.ui.reviewPanelOpen then $scope.reviewPanel.subView else 'mini',
|
||||
nEntries: $scope.reviewPanel.selectedEntryIds.length
|
||||
nEntries: $scope.reviewPanel.nVisibleSelectedChanges
|
||||
}
|
||||
|
||||
$scope.showBulkAcceptDialog = () ->
|
||||
showBulkActionsDialog true
|
||||
|
||||
$scope.showBulkRejectDialog = () -> showBulkActionsDialog false
|
||||
$scope.showBulkRejectDialog = () ->
|
||||
showBulkActionsDialog false
|
||||
|
||||
showBulkActionsDialog = (isAccept) ->
|
||||
$modal.open({
|
||||
@@ -367,7 +398,7 @@ define [
|
||||
controller: "BulkActionsModalController"
|
||||
resolve:
|
||||
isAccept: () -> isAccept
|
||||
nChanges: () -> $scope.reviewPanel.selectedEntryIds.length
|
||||
nChanges: () -> $scope.reviewPanel.nVisibleSelectedChanges
|
||||
scope: $scope.$new()
|
||||
}).result.then (isAccept) ->
|
||||
if isAccept
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.directive "aggregateChangeEntry", ($timeout) ->
|
||||
restrict: "E"
|
||||
templateUrl: "aggregateChangeEntryTemplate"
|
||||
scope:
|
||||
entry: "="
|
||||
user: "="
|
||||
permissions: "="
|
||||
onAccept: "&"
|
||||
onReject: "&"
|
||||
onIndicatorClick: "&"
|
||||
onBodyClick: "&"
|
||||
link: (scope, element, attrs) ->
|
||||
scope.contentLimit = 35
|
||||
scope.isCollapsed = true
|
||||
scope.needsCollapsing = false
|
||||
|
||||
element.on "click", (e) ->
|
||||
if $(e.target).is('.rp-entry, .rp-entry-description, .rp-entry-body, .rp-entry-action-icon i')
|
||||
scope.onBodyClick()
|
||||
|
||||
scope.toggleCollapse = () ->
|
||||
scope.isCollapsed = !scope.isCollapsed
|
||||
$timeout () ->
|
||||
scope.$emit "review-panel:layout"
|
||||
|
||||
scope.$watch "entry.content.length + entry.metadata.agg_op.content.length", (contentLength) ->
|
||||
scope.needsCollapsing = contentLength > scope.contentLimit
|
||||
@@ -63,7 +63,12 @@ define [
|
||||
|
||||
# Put the focused entry as close to where it wants to be as possible
|
||||
focused_entry_top = Math.max(focused_entry.scope.entry.screenPos.y, TOOLBAR_HEIGHT)
|
||||
focused_entry.$box_el.css(top: focused_entry_top)
|
||||
focused_entry.$box_el.css(
|
||||
top: focused_entry_top
|
||||
# The entry element is invisible by default, to avoid flickering when positioning for
|
||||
# the first time. Here we make sure it becomes visible after having a "top" value.
|
||||
visibility: "visible"
|
||||
)
|
||||
focused_entry.$indicator_el.css(top: focused_entry_top)
|
||||
positionLayoutEl(focused_entry.$callout_el, focused_entry.scope.entry.screenPos.y, focused_entry_top)
|
||||
|
||||
@@ -73,7 +78,12 @@ define [
|
||||
height = entry.height
|
||||
top = Math.max(original_top, previousBottom + PADDING)
|
||||
previousBottom = top + height
|
||||
entry.$box_el.css(top: top)
|
||||
entry.$box_el.css(
|
||||
top: top
|
||||
# The entry element is invisible by default, to avoid flickering when positioning for
|
||||
# the first time. Here we make sure it becomes visible after having a "top" value.
|
||||
visibility: "visible"
|
||||
)
|
||||
entry.$indicator_el.css(top: top)
|
||||
positionLayoutEl(entry.$callout_el, original_top, top)
|
||||
sl_console.log "ENTRY", {entry: entry.scope.entry, top}
|
||||
@@ -89,7 +99,12 @@ define [
|
||||
bottom = Math.min(original_bottom, previousTop - PADDING)
|
||||
top = bottom - height
|
||||
previousTop = top
|
||||
entry.$box_el.css(top: top)
|
||||
entry.$box_el.css(
|
||||
top: top
|
||||
# The entry element is invisible by default, to avoid flickering when positioning for
|
||||
# the first time. Here we make sure it becomes visible after having a "top" value.
|
||||
visibility: "visible"
|
||||
)
|
||||
entry.$indicator_el.css(top: top)
|
||||
positionLayoutEl(entry.$callout_el, original_top, top)
|
||||
sl_console.log "ENTRY", {entry: entry.scope.entry, top}
|
||||
|
||||
@@ -19,12 +19,18 @@ define [
|
||||
$scope.finishRenaming = () ->
|
||||
$scope.state.renaming = false
|
||||
newName = $scope.inputs.name
|
||||
if !newName? or newName.length == 0 or newName.length > MAX_PROJECT_NAME_LENGTH
|
||||
return
|
||||
if $scope.project.name == newName
|
||||
return
|
||||
oldName = $scope.project.name
|
||||
$scope.project.name = newName
|
||||
settings.saveProjectSettings({name: $scope.project.name})
|
||||
.error (response, statusCode) ->
|
||||
$scope.project.name = oldName
|
||||
if statusCode == 400
|
||||
ide.showGenericMessageModal("Error renaming project", response)
|
||||
else
|
||||
ide.showGenericMessageModal("Error renaming project", "Please try again in a moment")
|
||||
console.log arguments
|
||||
|
||||
ide.socket.on "projectNameUpdated", (name) ->
|
||||
$scope.$apply () ->
|
||||
|
||||
@@ -12,8 +12,7 @@ define [
|
||||
# End of tracking code.
|
||||
|
||||
data._csrf = window.csrfToken
|
||||
ide.$http.post "/user/settings", data
|
||||
|
||||
return ide.$http.post "/user/settings", data
|
||||
|
||||
saveProjectSettings: (data) ->
|
||||
# Tracking code.
|
||||
@@ -24,9 +23,8 @@ define [
|
||||
# End of tracking code.
|
||||
|
||||
data._csrf = window.csrfToken
|
||||
ide.$http.post "/project/#{ide.project_id}/settings", data
|
||||
return ide.$http.post "/project/#{ide.project_id}/settings", data
|
||||
|
||||
|
||||
saveProjectAdminSettings: (data) ->
|
||||
# Tracking code.
|
||||
for key in Object.keys(data)
|
||||
@@ -36,8 +34,6 @@ define [
|
||||
# End of tracking code.
|
||||
|
||||
data._csrf = window.csrfToken
|
||||
ide.$http.post "/project/#{ide.project_id}/settings/admin", data
|
||||
|
||||
|
||||
return ide.$http.post "/project/#{ide.project_id}/settings/admin", data
|
||||
}
|
||||
]
|
||||
@@ -33,9 +33,13 @@ define [
|
||||
$scope.removeMembers = () ->
|
||||
for user in $scope.selectedUsers
|
||||
do (user) ->
|
||||
if user.holdingAccount and !user._id?
|
||||
url = "/subscription/group/email/#{encodeURIComponent(user.email)}"
|
||||
else
|
||||
url = "/subscription/group/user/#{user._id}"
|
||||
queuedHttp({
|
||||
method: "DELETE",
|
||||
url: "/subscription/group/user/#{user._id}"
|
||||
url: url
|
||||
headers:
|
||||
"X-Csrf-Token": window.csrfToken
|
||||
})
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.controller 'RenameProjectModalController', ($scope, $modalInstance, $timeout, projectName) ->
|
||||
App.controller 'RenameProjectModalController', ($scope, $modalInstance, $timeout, project, queuedHttp) ->
|
||||
$scope.inputs =
|
||||
projectName: projectName
|
||||
projectName: project.name
|
||||
|
||||
$scope.state =
|
||||
inflight: false
|
||||
error: false
|
||||
|
||||
$modalInstance.opened.then () ->
|
||||
$timeout () ->
|
||||
@@ -11,7 +15,20 @@ define [
|
||||
, 200
|
||||
|
||||
$scope.rename = () ->
|
||||
$modalInstance.close($scope.inputs.projectName)
|
||||
$scope.state.inflight = true
|
||||
$scope.state.error = false
|
||||
$scope
|
||||
.renameProject(project, $scope.inputs.projectName)
|
||||
.success () ->
|
||||
$scope.state.inflight = false
|
||||
$scope.state.error = false
|
||||
$modalInstance.close()
|
||||
.error (body, statusCode) ->
|
||||
$scope.state.inflight = false
|
||||
if statusCode == 400
|
||||
$scope.state.error = { message: body }
|
||||
else
|
||||
$scope.state.error = true
|
||||
|
||||
$scope.cancel = () ->
|
||||
$modalInstance.dismiss('cancel')
|
||||
@@ -21,6 +38,7 @@ define [
|
||||
projectName: project.name + " (Copy)"
|
||||
$scope.state =
|
||||
inflight: false
|
||||
error: false
|
||||
|
||||
$modalInstance.opened.then () ->
|
||||
$timeout () ->
|
||||
@@ -31,9 +49,16 @@ define [
|
||||
$scope.state.inflight = true
|
||||
$scope
|
||||
.cloneProject(project, $scope.inputs.projectName)
|
||||
.then (project_id) ->
|
||||
.success () ->
|
||||
$scope.state.inflight = false
|
||||
$modalInstance.close(project_id)
|
||||
$scope.state.error = false
|
||||
$modalInstance.close()
|
||||
.error (body, statusCode) ->
|
||||
$scope.state.inflight = false
|
||||
if statusCode == 400
|
||||
$scope.state.error = { message: body }
|
||||
else
|
||||
$scope.state.error = true
|
||||
|
||||
$scope.cancel = () ->
|
||||
$modalInstance.dismiss('cancel')
|
||||
@@ -43,6 +68,7 @@ define [
|
||||
projectName: ""
|
||||
$scope.state =
|
||||
inflight: false
|
||||
error: false
|
||||
|
||||
$modalInstance.opened.then () ->
|
||||
$timeout () ->
|
||||
@@ -51,11 +77,19 @@ define [
|
||||
|
||||
$scope.create = () ->
|
||||
$scope.state.inflight = true
|
||||
$scope.state.error = false
|
||||
$scope
|
||||
.createProject($scope.inputs.projectName, template)
|
||||
.then (project_id) ->
|
||||
.success (data) ->
|
||||
$scope.state.inflight = false
|
||||
$modalInstance.close(project_id)
|
||||
$scope.state.error = false
|
||||
$modalInstance.close(data.project_id)
|
||||
.error (body, statusCode) ->
|
||||
$scope.state.inflight = false
|
||||
if statusCode == 400
|
||||
$scope.state.error = { message: body }
|
||||
else
|
||||
$scope.state.error = true
|
||||
|
||||
$scope.cancel = () ->
|
||||
$modalInstance.dismiss('cancel')
|
||||
|
||||
@@ -256,9 +256,7 @@ define [
|
||||
)
|
||||
|
||||
$scope.createProject = (name, template = "none") ->
|
||||
deferred = $q.defer()
|
||||
|
||||
queuedHttp
|
||||
return queuedHttp
|
||||
.post("/project/new", {
|
||||
_csrf: window.csrfToken
|
||||
projectName: name
|
||||
@@ -273,13 +271,7 @@ define [
|
||||
# to the rest of the app
|
||||
}
|
||||
$scope.updateVisibleProjects()
|
||||
deferred.resolve(data.project_id)
|
||||
)
|
||||
.error((data, status, headers, config) ->
|
||||
deferred.reject()
|
||||
)
|
||||
|
||||
return deferred.promise
|
||||
|
||||
$scope.openCreateProjectModal = (template = "none") ->
|
||||
event_tracking.send 'project-list-page-interaction', 'new-project', template
|
||||
@@ -294,15 +286,13 @@ define [
|
||||
modalInstance.result.then (project_id) ->
|
||||
window.location = "/project/#{project_id}"
|
||||
|
||||
MAX_PROJECT_NAME_LENGTH = 150
|
||||
$scope.renameProject = (project, newName) ->
|
||||
if !newName? or newName.length == 0 or newName.length > MAX_PROJECT_NAME_LENGTH
|
||||
return
|
||||
project.name = newName
|
||||
queuedHttp.post "/project/#{project.id}/rename", {
|
||||
newProjectName: project.name
|
||||
return queuedHttp.post "/project/#{project.id}/rename", {
|
||||
newProjectName: newName,
|
||||
_csrf: window.csrfToken
|
||||
}
|
||||
.success () ->
|
||||
project.name = newName
|
||||
|
||||
$scope.openRenameProjectModal = () ->
|
||||
project = $scope.getFirstSelectedProject()
|
||||
@@ -312,16 +302,11 @@ define [
|
||||
templateUrl: "renameProjectModalTemplate"
|
||||
controller: "RenameProjectModalController"
|
||||
resolve:
|
||||
projectName: () -> project.name
|
||||
)
|
||||
|
||||
modalInstance.result.then(
|
||||
(newName) ->
|
||||
$scope.renameProject(project, newName)
|
||||
project: () -> project
|
||||
scope: $scope
|
||||
)
|
||||
|
||||
$scope.cloneProject = (project, cloneName) ->
|
||||
deferred = $q.defer()
|
||||
event_tracking.send 'project-list-page-interaction', 'project action', 'Clone'
|
||||
queuedHttp
|
||||
.post("/project/#{project.id}/clone", {
|
||||
@@ -337,13 +322,7 @@ define [
|
||||
# to the rest of the app
|
||||
}
|
||||
$scope.updateVisibleProjects()
|
||||
deferred.resolve(data.project_id)
|
||||
)
|
||||
.error((data, status, headers, config) ->
|
||||
deferred.reject()
|
||||
)
|
||||
|
||||
return deferred.promise
|
||||
|
||||
$scope.openCloneProjectModal = () ->
|
||||
project = $scope.getFirstSelectedProject()
|
||||
|
||||
@@ -19,24 +19,29 @@ define [
|
||||
processPendingRequests()
|
||||
|
||||
queuedHttp = (args...) ->
|
||||
deferred = $q.defer()
|
||||
promise = deferred.promise
|
||||
# We can't use Angular's $q.defer promises, because it only passes
|
||||
# a single argument on error, and $http passes multiple.
|
||||
promise = {}
|
||||
successCallbacks = []
|
||||
errorCallbacks = []
|
||||
|
||||
# Adhere to the $http promise conventions
|
||||
promise.success = (callback) ->
|
||||
promise.then(callback)
|
||||
successCallbacks.push callback
|
||||
return promise
|
||||
|
||||
promise.error = (callback) ->
|
||||
promise.catch(callback)
|
||||
errorCallbacks.push callback
|
||||
return promise
|
||||
|
||||
doRequest = () ->
|
||||
$http(args...)
|
||||
.success (successArgs...) ->
|
||||
deferred.resolve(successArgs...)
|
||||
.error (errorArgs...) ->
|
||||
deferred.reject(errorArgs...)
|
||||
.success (args...) ->
|
||||
for cb in successCallbacks
|
||||
cb(args...)
|
||||
.error (args...) ->
|
||||
for cb in errorCallbacks
|
||||
cb(args...)
|
||||
|
||||
pendingRequests.push doRequest
|
||||
processPendingRequests()
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
* binary
|
||||
@@ -1,596 +0,0 @@
|
||||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* globals VBArray, PDFJS */
|
||||
|
||||
(function compatibilityWrapper() {
|
||||
'use strict';
|
||||
|
||||
// Initializing PDFJS global object here, it case if we need to change/disable
|
||||
// some PDF.js features, e.g. range requests
|
||||
if (typeof PDFJS === 'undefined') {
|
||||
(typeof window !== 'undefined' ? window : this).PDFJS = {};
|
||||
}
|
||||
|
||||
// Checking if the typed arrays are supported
|
||||
// Support: iOS<6.0 (subarray), IE<10, Android<4.0
|
||||
(function checkTypedArrayCompatibility() {
|
||||
if (typeof Uint8Array !== 'undefined') {
|
||||
// Support: iOS<6.0
|
||||
if (typeof Uint8Array.prototype.subarray === 'undefined') {
|
||||
Uint8Array.prototype.subarray = function subarray(start, end) {
|
||||
return new Uint8Array(this.slice(start, end));
|
||||
};
|
||||
Float32Array.prototype.subarray = function subarray(start, end) {
|
||||
return new Float32Array(this.slice(start, end));
|
||||
};
|
||||
}
|
||||
|
||||
// Support: Android<4.1
|
||||
if (typeof Float64Array === 'undefined') {
|
||||
window.Float64Array = Float32Array;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function subarray(start, end) {
|
||||
return new TypedArray(this.slice(start, end));
|
||||
}
|
||||
|
||||
function setArrayOffset(array, offset) {
|
||||
if (arguments.length < 2) {
|
||||
offset = 0;
|
||||
}
|
||||
for (var i = 0, n = array.length; i < n; ++i, ++offset) {
|
||||
this[offset] = array[i] & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
function TypedArray(arg1) {
|
||||
var result, i, n;
|
||||
if (typeof arg1 === 'number') {
|
||||
result = [];
|
||||
for (i = 0; i < arg1; ++i) {
|
||||
result[i] = 0;
|
||||
}
|
||||
} else if ('slice' in arg1) {
|
||||
result = arg1.slice(0);
|
||||
} else {
|
||||
result = [];
|
||||
for (i = 0, n = arg1.length; i < n; ++i) {
|
||||
result[i] = arg1[i];
|
||||
}
|
||||
}
|
||||
|
||||
result.subarray = subarray;
|
||||
result.buffer = result;
|
||||
result.byteLength = result.length;
|
||||
result.set = setArrayOffset;
|
||||
|
||||
if (typeof arg1 === 'object' && arg1.buffer) {
|
||||
result.buffer = arg1.buffer;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
window.Uint8Array = TypedArray;
|
||||
window.Int8Array = TypedArray;
|
||||
|
||||
// we don't need support for set, byteLength for 32-bit array
|
||||
// so we can use the TypedArray as well
|
||||
window.Uint32Array = TypedArray;
|
||||
window.Int32Array = TypedArray;
|
||||
window.Uint16Array = TypedArray;
|
||||
window.Float32Array = TypedArray;
|
||||
window.Float64Array = TypedArray;
|
||||
})();
|
||||
|
||||
// URL = URL || webkitURL
|
||||
// Support: Safari<7, Android 4.2+
|
||||
(function normalizeURLObject() {
|
||||
if (!window.URL) {
|
||||
window.URL = window.webkitURL;
|
||||
}
|
||||
})();
|
||||
|
||||
// Object.defineProperty()?
|
||||
// Support: Android<4.0, Safari<5.1
|
||||
(function checkObjectDefinePropertyCompatibility() {
|
||||
if (typeof Object.defineProperty !== 'undefined') {
|
||||
var definePropertyPossible = true;
|
||||
try {
|
||||
// some browsers (e.g. safari) cannot use defineProperty() on DOM objects
|
||||
// and thus the native version is not sufficient
|
||||
Object.defineProperty(new Image(), 'id', { value: 'test' });
|
||||
// ... another test for android gb browser for non-DOM objects
|
||||
var Test = function Test() {};
|
||||
Test.prototype = { get id() { } };
|
||||
Object.defineProperty(new Test(), 'id',
|
||||
{ value: '', configurable: true, enumerable: true, writable: false });
|
||||
} catch (e) {
|
||||
definePropertyPossible = false;
|
||||
}
|
||||
if (definePropertyPossible) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty = function objectDefineProperty(obj, name, def) {
|
||||
delete obj[name];
|
||||
if ('get' in def) {
|
||||
obj.__defineGetter__(name, def['get']);
|
||||
}
|
||||
if ('set' in def) {
|
||||
obj.__defineSetter__(name, def['set']);
|
||||
}
|
||||
if ('value' in def) {
|
||||
obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
|
||||
this.__defineGetter__(name, function objectDefinePropertyGetter() {
|
||||
return value;
|
||||
});
|
||||
return value;
|
||||
});
|
||||
obj[name] = def.value;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
// No XMLHttpRequest#response?
|
||||
// Support: IE<11, Android <4.0
|
||||
(function checkXMLHttpRequestResponseCompatibility() {
|
||||
var xhrPrototype = XMLHttpRequest.prototype;
|
||||
var xhr = new XMLHttpRequest();
|
||||
if (!('overrideMimeType' in xhr)) {
|
||||
// IE10 might have response, but not overrideMimeType
|
||||
// Support: IE10
|
||||
Object.defineProperty(xhrPrototype, 'overrideMimeType', {
|
||||
value: function xmlHttpRequestOverrideMimeType(mimeType) {}
|
||||
});
|
||||
}
|
||||
if ('responseType' in xhr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The worker will be using XHR, so we can save time and disable worker.
|
||||
PDFJS.disableWorker = true;
|
||||
|
||||
Object.defineProperty(xhrPrototype, 'responseType', {
|
||||
get: function xmlHttpRequestGetResponseType() {
|
||||
return this._responseType || 'text';
|
||||
},
|
||||
set: function xmlHttpRequestSetResponseType(value) {
|
||||
if (value === 'text' || value === 'arraybuffer') {
|
||||
this._responseType = value;
|
||||
if (value === 'arraybuffer' &&
|
||||
typeof this.overrideMimeType === 'function') {
|
||||
this.overrideMimeType('text/plain; charset=x-user-defined');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Support: IE9
|
||||
if (typeof VBArray !== 'undefined') {
|
||||
Object.defineProperty(xhrPrototype, 'response', {
|
||||
get: function xmlHttpRequestResponseGet() {
|
||||
if (this.responseType === 'arraybuffer') {
|
||||
return new Uint8Array(new VBArray(this.responseBody).toArray());
|
||||
} else {
|
||||
return this.responseText;
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty(xhrPrototype, 'response', {
|
||||
get: function xmlHttpRequestResponseGet() {
|
||||
if (this.responseType !== 'arraybuffer') {
|
||||
return this.responseText;
|
||||
}
|
||||
var text = this.responseText;
|
||||
var i, n = text.length;
|
||||
var result = new Uint8Array(n);
|
||||
for (i = 0; i < n; ++i) {
|
||||
result[i] = text.charCodeAt(i) & 0xFF;
|
||||
}
|
||||
return result.buffer;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// window.btoa (base64 encode function) ?
|
||||
// Support: IE<10
|
||||
(function checkWindowBtoaCompatibility() {
|
||||
if ('btoa' in window) {
|
||||
return;
|
||||
}
|
||||
|
||||
var digits =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
|
||||
window.btoa = function windowBtoa(chars) {
|
||||
var buffer = '';
|
||||
var i, n;
|
||||
for (i = 0, n = chars.length; i < n; i += 3) {
|
||||
var b1 = chars.charCodeAt(i) & 0xFF;
|
||||
var b2 = chars.charCodeAt(i + 1) & 0xFF;
|
||||
var b3 = chars.charCodeAt(i + 2) & 0xFF;
|
||||
var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
|
||||
var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
|
||||
var d4 = i + 2 < n ? (b3 & 0x3F) : 64;
|
||||
buffer += (digits.charAt(d1) + digits.charAt(d2) +
|
||||
digits.charAt(d3) + digits.charAt(d4));
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
})();
|
||||
|
||||
// window.atob (base64 encode function)?
|
||||
// Support: IE<10
|
||||
(function checkWindowAtobCompatibility() {
|
||||
if ('atob' in window) {
|
||||
return;
|
||||
}
|
||||
|
||||
// https://github.com/davidchambers/Base64.js
|
||||
var digits =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
window.atob = function (input) {
|
||||
input = input.replace(/=+$/, '');
|
||||
if (input.length % 4 === 1) {
|
||||
throw new Error('bad atob input');
|
||||
}
|
||||
for (
|
||||
// initialize result and counters
|
||||
var bc = 0, bs, buffer, idx = 0, output = '';
|
||||
// get next character
|
||||
buffer = input.charAt(idx++);
|
||||
// character found in table?
|
||||
// initialize bit storage and add its ascii value
|
||||
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
|
||||
// and if not first of each 4 characters,
|
||||
// convert the first 8 bits to one ascii character
|
||||
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
|
||||
) {
|
||||
// try to find character in table (0-63, not found => -1)
|
||||
buffer = digits.indexOf(buffer);
|
||||
}
|
||||
return output;
|
||||
};
|
||||
})();
|
||||
|
||||
// Function.prototype.bind?
|
||||
// Support: Android<4.0, iOS<6.0
|
||||
(function checkFunctionPrototypeBindCompatibility() {
|
||||
if (typeof Function.prototype.bind !== 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
Function.prototype.bind = function functionPrototypeBind(obj) {
|
||||
var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
|
||||
var bound = function functionPrototypeBindBound() {
|
||||
var args = headArgs.concat(Array.prototype.slice.call(arguments));
|
||||
return fn.apply(obj, args);
|
||||
};
|
||||
return bound;
|
||||
};
|
||||
})();
|
||||
|
||||
// HTMLElement dataset property
|
||||
// Support: IE<11, Safari<5.1, Android<4.0
|
||||
(function checkDatasetProperty() {
|
||||
var div = document.createElement('div');
|
||||
if ('dataset' in div) {
|
||||
return; // dataset property exists
|
||||
}
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'dataset', {
|
||||
get: function() {
|
||||
if (this._dataset) {
|
||||
return this._dataset;
|
||||
}
|
||||
|
||||
var dataset = {};
|
||||
for (var j = 0, jj = this.attributes.length; j < jj; j++) {
|
||||
var attribute = this.attributes[j];
|
||||
if (attribute.name.substring(0, 5) !== 'data-') {
|
||||
continue;
|
||||
}
|
||||
var key = attribute.name.substring(5).replace(/\-([a-z])/g,
|
||||
function(all, ch) {
|
||||
return ch.toUpperCase();
|
||||
});
|
||||
dataset[key] = attribute.value;
|
||||
}
|
||||
|
||||
Object.defineProperty(this, '_dataset', {
|
||||
value: dataset,
|
||||
writable: false,
|
||||
enumerable: false
|
||||
});
|
||||
return dataset;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
})();
|
||||
|
||||
// HTMLElement classList property
|
||||
// Support: IE<10, Android<4.0, iOS<5.0
|
||||
(function checkClassListProperty() {
|
||||
var div = document.createElement('div');
|
||||
if ('classList' in div) {
|
||||
return; // classList property exists
|
||||
}
|
||||
|
||||
function changeList(element, itemName, add, remove) {
|
||||
var s = element.className || '';
|
||||
var list = s.split(/\s+/g);
|
||||
if (list[0] === '') {
|
||||
list.shift();
|
||||
}
|
||||
var index = list.indexOf(itemName);
|
||||
if (index < 0 && add) {
|
||||
list.push(itemName);
|
||||
}
|
||||
if (index >= 0 && remove) {
|
||||
list.splice(index, 1);
|
||||
}
|
||||
element.className = list.join(' ');
|
||||
return (index >= 0);
|
||||
}
|
||||
|
||||
var classListPrototype = {
|
||||
add: function(name) {
|
||||
changeList(this.element, name, true, false);
|
||||
},
|
||||
contains: function(name) {
|
||||
return changeList(this.element, name, false, false);
|
||||
},
|
||||
remove: function(name) {
|
||||
changeList(this.element, name, false, true);
|
||||
},
|
||||
toggle: function(name) {
|
||||
changeList(this.element, name, true, true);
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'classList', {
|
||||
get: function() {
|
||||
if (this._classList) {
|
||||
return this._classList;
|
||||
}
|
||||
|
||||
var classList = Object.create(classListPrototype, {
|
||||
element: {
|
||||
value: this,
|
||||
writable: false,
|
||||
enumerable: true
|
||||
}
|
||||
});
|
||||
Object.defineProperty(this, '_classList', {
|
||||
value: classList,
|
||||
writable: false,
|
||||
enumerable: false
|
||||
});
|
||||
return classList;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
})();
|
||||
|
||||
// Check console compatibility
|
||||
// In older IE versions the console object is not available
|
||||
// unless console is open.
|
||||
// Support: IE<10
|
||||
(function checkConsoleCompatibility() {
|
||||
if (!('console' in window)) {
|
||||
window.console = {
|
||||
log: function() {},
|
||||
error: function() {},
|
||||
warn: function() {}
|
||||
};
|
||||
} else if (!('bind' in console.log)) {
|
||||
// native functions in IE9 might not have bind
|
||||
console.log = (function(fn) {
|
||||
return function(msg) { return fn(msg); };
|
||||
})(console.log);
|
||||
console.error = (function(fn) {
|
||||
return function(msg) { return fn(msg); };
|
||||
})(console.error);
|
||||
console.warn = (function(fn) {
|
||||
return function(msg) { return fn(msg); };
|
||||
})(console.warn);
|
||||
}
|
||||
})();
|
||||
|
||||
// Check onclick compatibility in Opera
|
||||
// Support: Opera<15
|
||||
(function checkOnClickCompatibility() {
|
||||
// workaround for reported Opera bug DSK-354448:
|
||||
// onclick fires on disabled buttons with opaque content
|
||||
function ignoreIfTargetDisabled(event) {
|
||||
if (isDisabled(event.target)) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
function isDisabled(node) {
|
||||
return node.disabled || (node.parentNode && isDisabled(node.parentNode));
|
||||
}
|
||||
if (navigator.userAgent.indexOf('Opera') !== -1) {
|
||||
// use browser detection since we cannot feature-check this bug
|
||||
document.addEventListener('click', ignoreIfTargetDisabled, true);
|
||||
}
|
||||
})();
|
||||
|
||||
// Checks if possible to use URL.createObjectURL()
|
||||
// Support: IE
|
||||
(function checkOnBlobSupport() {
|
||||
// sometimes IE loosing the data created with createObjectURL(), see #3977
|
||||
if (navigator.userAgent.indexOf('Trident') >= 0) {
|
||||
PDFJS.disableCreateObjectURL = true;
|
||||
}
|
||||
})();
|
||||
|
||||
// Checks if navigator.language is supported
|
||||
(function checkNavigatorLanguage() {
|
||||
if ('language' in navigator) {
|
||||
return;
|
||||
}
|
||||
PDFJS.locale = navigator.userLanguage || 'en-US';
|
||||
})();
|
||||
|
||||
(function checkRangeRequests() {
|
||||
// Safari has issues with cached range requests see:
|
||||
// https://github.com/mozilla/pdf.js/issues/3260
|
||||
// Last tested with version 6.0.4.
|
||||
// Support: Safari 6.0+
|
||||
var isSafari = Object.prototype.toString.call(
|
||||
window.HTMLElement).indexOf('Constructor') > 0;
|
||||
|
||||
// Older versions of Android (pre 3.0) has issues with range requests, see:
|
||||
// https://github.com/mozilla/pdf.js/issues/3381.
|
||||
// Make sure that we only match webkit-based Android browsers,
|
||||
// since Firefox/Fennec works as expected.
|
||||
// Support: Android<3.0
|
||||
var regex = /Android\s[0-2][^\d]/;
|
||||
var isOldAndroid = regex.test(navigator.userAgent);
|
||||
|
||||
// Range requests are broken in Chrome 39 and 40, https://crbug.com/442318
|
||||
var isChromeWithRangeBug = /Chrome\/(39|40)\./.test(navigator.userAgent);
|
||||
|
||||
if (isSafari || isOldAndroid || isChromeWithRangeBug) {
|
||||
PDFJS.disableRange = true;
|
||||
PDFJS.disableStream = true;
|
||||
}
|
||||
})();
|
||||
|
||||
// Check if the browser supports manipulation of the history.
|
||||
// Support: IE<10, Android<4.2
|
||||
(function checkHistoryManipulation() {
|
||||
// Android 2.x has so buggy pushState support that it was removed in
|
||||
// Android 3.0 and restored as late as in Android 4.2.
|
||||
// Support: Android 2.x
|
||||
if (!history.pushState || navigator.userAgent.indexOf('Android 2.') >= 0) {
|
||||
PDFJS.disableHistory = true;
|
||||
}
|
||||
})();
|
||||
|
||||
// Support: IE<11, Chrome<21, Android<4.4, Safari<6
|
||||
(function checkSetPresenceInImageData() {
|
||||
// IE < 11 will use window.CanvasPixelArray which lacks set function.
|
||||
if (window.CanvasPixelArray) {
|
||||
if (typeof window.CanvasPixelArray.prototype.set !== 'function') {
|
||||
window.CanvasPixelArray.prototype.set = function(arr) {
|
||||
for (var i = 0, ii = this.length; i < ii; i++) {
|
||||
this[i] = arr[i];
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Old Chrome and Android use an inaccessible CanvasPixelArray prototype.
|
||||
// Because we cannot feature detect it, we rely on user agent parsing.
|
||||
var polyfill = false, versionMatch;
|
||||
if (navigator.userAgent.indexOf('Chrom') >= 0) {
|
||||
versionMatch = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
|
||||
// Chrome < 21 lacks the set function.
|
||||
polyfill = versionMatch && parseInt(versionMatch[2]) < 21;
|
||||
} else if (navigator.userAgent.indexOf('Android') >= 0) {
|
||||
// Android < 4.4 lacks the set function.
|
||||
// Android >= 4.4 will contain Chrome in the user agent,
|
||||
// thus pass the Chrome check above and not reach this block.
|
||||
polyfill = /Android\s[0-4][^\d]/g.test(navigator.userAgent);
|
||||
} else if (navigator.userAgent.indexOf('Safari') >= 0) {
|
||||
versionMatch = navigator.userAgent.
|
||||
match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//);
|
||||
// Safari < 6 lacks the set function.
|
||||
polyfill = versionMatch && parseInt(versionMatch[1]) < 6;
|
||||
}
|
||||
|
||||
if (polyfill) {
|
||||
var contextPrototype = window.CanvasRenderingContext2D.prototype;
|
||||
var createImageData = contextPrototype.createImageData;
|
||||
contextPrototype.createImageData = function(w, h) {
|
||||
var imageData = createImageData.call(this, w, h);
|
||||
imageData.data.set = function(arr) {
|
||||
for (var i = 0, ii = this.length; i < ii; i++) {
|
||||
this[i] = arr[i];
|
||||
}
|
||||
};
|
||||
return imageData;
|
||||
};
|
||||
// this closure will be kept referenced, so clear its vars
|
||||
contextPrototype = null;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// Support: IE<10, Android<4.0, iOS
|
||||
(function checkRequestAnimationFrame() {
|
||||
function fakeRequestAnimationFrame(callback) {
|
||||
window.setTimeout(callback, 20);
|
||||
}
|
||||
|
||||
var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
|
||||
if (isIOS) {
|
||||
// requestAnimationFrame on iOS is broken, replacing with fake one.
|
||||
window.requestAnimationFrame = fakeRequestAnimationFrame;
|
||||
return;
|
||||
}
|
||||
if ('requestAnimationFrame' in window) {
|
||||
return;
|
||||
}
|
||||
window.requestAnimationFrame =
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
fakeRequestAnimationFrame;
|
||||
})();
|
||||
|
||||
(function checkCanvasSizeLimitation() {
|
||||
var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
|
||||
var isAndroid = /Android/g.test(navigator.userAgent);
|
||||
if (isIOS || isAndroid) {
|
||||
// 5MP
|
||||
PDFJS.maxCanvasPixels = 5242880;
|
||||
}
|
||||
})();
|
||||
|
||||
// Disable fullscreen support for certain problematic configurations.
|
||||
// Support: IE11+ (when embedded).
|
||||
(function checkFullscreenSupport() {
|
||||
var isEmbeddedIE = (navigator.userAgent.indexOf('Trident') >= 0 &&
|
||||
window.parent !== window);
|
||||
if (isEmbeddedIE) {
|
||||
PDFJS.disableFullscreen = true;
|
||||
}
|
||||
})();
|
||||
|
||||
// Provides document.currentScript support
|
||||
// Support: IE, Chrome<29.
|
||||
(function checkCurrentScript() {
|
||||
if ('currentScript' in document) {
|
||||
return;
|
||||
}
|
||||
Object.defineProperty(document, 'currentScript', {
|
||||
get: function () {
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
return scripts[scripts.length - 1];
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
})();
|
||||
|
||||
}).call((typeof window === 'undefined') ? this : window);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user