Merge branch 'master' into ho-annom-user-events
@@ -1,9 +1,15 @@
|
||||
AnalyticsManager = require "./AnalyticsManager"
|
||||
AuthenticationController = require("../Authentication/AuthenticationController")
|
||||
Errors = require "../Errors/Errors"
|
||||
|
||||
|
||||
module.exports = AnalyticsController =
|
||||
recordEvent: (req, res, next) ->
|
||||
user_id = AuthenticationController.getLoggedInUserId(req) or req.sessionID
|
||||
AnalyticsManager.recordEvent user_id, req.params.event, req.body, (error) ->
|
||||
return next(error) if error?
|
||||
if error?
|
||||
if error instanceof Errors.ServiceNotConfiguredError
|
||||
# ignore, no-op
|
||||
return res.send(204)
|
||||
else
|
||||
return next(error)
|
||||
res.send 204
|
||||
|
||||
@@ -2,6 +2,7 @@ settings = require "settings-sharelatex"
|
||||
logger = require "logger-sharelatex"
|
||||
_ = require "underscore"
|
||||
request = require "request"
|
||||
Errors = require '../Errors/Errors'
|
||||
|
||||
|
||||
makeRequest = (opts, callback)->
|
||||
@@ -10,8 +11,7 @@ makeRequest = (opts, callback)->
|
||||
opts.url = "#{settings.apis.analytics.url}#{urlPath}"
|
||||
request opts, callback
|
||||
else
|
||||
callback()
|
||||
|
||||
callback(new Errors.ServiceNotConfiguredError('Analytics service not configured'))
|
||||
|
||||
|
||||
module.exports =
|
||||
|
||||
@@ -10,7 +10,7 @@ module.exports = BlogHandler =
|
||||
opts =
|
||||
url:blogUrl
|
||||
json:true
|
||||
timeout:500
|
||||
timeout:1000
|
||||
request.get opts, (err, res, announcements)->
|
||||
if err?
|
||||
return callback err
|
||||
@@ -18,7 +18,6 @@ module.exports = BlogHandler =
|
||||
return callback("blog announcement returned non 200")
|
||||
logger.log announcementsLength: announcements?.length, "announcements returned"
|
||||
announcements = _.map announcements, (announcement)->
|
||||
announcement.url = "/blog#{announcement.url}"
|
||||
announcement.date = new Date(announcement.date)
|
||||
return announcement
|
||||
callback(err, announcements)
|
||||
|
||||
@@ -4,9 +4,9 @@ logger = require("logger-sharelatex")
|
||||
AuthenticationController = require('../Authentication/AuthenticationController')
|
||||
UserInfoManager = require('../User/UserInfoManager')
|
||||
UserInfoController = require('../User/UserInfoController')
|
||||
CommentsController = require('../Comments/CommentsController')
|
||||
async = require "async"
|
||||
|
||||
module.exports =
|
||||
module.exports = ChatController =
|
||||
sendMessage: (req, res, next)->
|
||||
project_id = req.params.project_id
|
||||
content = req.body.content
|
||||
@@ -28,7 +28,38 @@ module.exports =
|
||||
logger.log project_id:project_id, query:query, "getting messages"
|
||||
ChatApiHandler.getGlobalMessages project_id, query.limit, query.before, (err, messages) ->
|
||||
return next(err) if err?
|
||||
CommentsController._injectUserInfoIntoThreads {global: { messages: messages }}, (err) ->
|
||||
ChatController._injectUserInfoIntoThreads {global: { messages: messages }}, (err) ->
|
||||
return next(err) if err?
|
||||
logger.log length: messages?.length, "sending messages to client"
|
||||
res.json messages
|
||||
|
||||
_injectUserInfoIntoThreads: (threads, callback = (error, threads) ->) ->
|
||||
userCache = {}
|
||||
getUserDetails = (user_id, callback = (error, user) ->) ->
|
||||
return callback(null, userCache[user_id]) if userCache[user_id]?
|
||||
UserInfoManager.getPersonalInfo user_id, (err, user) ->
|
||||
return callback(error) if error?
|
||||
user = UserInfoController.formatPersonalInfo user
|
||||
userCache[user_id] = user
|
||||
callback null, user
|
||||
|
||||
jobs = []
|
||||
for thread_id, thread of threads
|
||||
do (thread) ->
|
||||
if thread.resolved
|
||||
jobs.push (cb) ->
|
||||
getUserDetails thread.resolved_by_user_id, (error, user) ->
|
||||
cb(error) if error?
|
||||
thread.resolved_by_user = user
|
||||
cb()
|
||||
for message in thread.messages
|
||||
do (message) ->
|
||||
jobs.push (cb) ->
|
||||
getUserDetails message.user_id, (error, user) ->
|
||||
cb(error) if error?
|
||||
message.user = user
|
||||
cb()
|
||||
|
||||
async.series jobs, (error) ->
|
||||
return callback(error) if error?
|
||||
return callback null, threads
|
||||
@@ -1,111 +0,0 @@
|
||||
ChatApiHandler = require("../Chat/ChatApiHandler")
|
||||
EditorRealTimeController = require("../Editor/EditorRealTimeController")
|
||||
logger = require("logger-sharelatex")
|
||||
AuthenticationController = require('../Authentication/AuthenticationController')
|
||||
UserInfoManager = require('../User/UserInfoManager')
|
||||
UserInfoController = require('../User/UserInfoController')
|
||||
DocumentUpdaterHandler = require "../DocumentUpdater/DocumentUpdaterHandler"
|
||||
async = require "async"
|
||||
|
||||
module.exports = CommentsController =
|
||||
sendComment: (req, res, next) ->
|
||||
{project_id, thread_id} = req.params
|
||||
content = req.body.content
|
||||
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||
if !user_id?
|
||||
err = new Error('no logged-in user')
|
||||
return next(err)
|
||||
logger.log {project_id, thread_id, user_id, content}, "sending comment"
|
||||
ChatApiHandler.sendComment project_id, thread_id, user_id, content, (err, comment) ->
|
||||
return next(err) if err?
|
||||
UserInfoManager.getPersonalInfo comment.user_id, (err, user) ->
|
||||
return next(err) if err?
|
||||
comment.user = UserInfoController.formatPersonalInfo(user)
|
||||
EditorRealTimeController.emitToRoom project_id, "new-comment", thread_id, comment, (err) ->
|
||||
res.send 204
|
||||
|
||||
getThreads: (req, res, next) ->
|
||||
{project_id} = req.params
|
||||
logger.log {project_id}, "getting comment threads for project"
|
||||
ChatApiHandler.getThreads project_id, (err, threads) ->
|
||||
return next(err) if err?
|
||||
CommentsController._injectUserInfoIntoThreads threads, (error, threads) ->
|
||||
return next(err) if err?
|
||||
res.json threads
|
||||
|
||||
resolveThread: (req, res, next) ->
|
||||
{project_id, thread_id} = req.params
|
||||
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||
logger.log {project_id, thread_id, user_id}, "resolving comment thread"
|
||||
ChatApiHandler.resolveThread project_id, thread_id, user_id, (err) ->
|
||||
return next(err) if err?
|
||||
UserInfoManager.getPersonalInfo user_id, (err, user) ->
|
||||
return next(err) if err?
|
||||
EditorRealTimeController.emitToRoom project_id, "resolve-thread", thread_id, UserInfoController.formatPersonalInfo(user), (err)->
|
||||
res.send 204
|
||||
|
||||
reopenThread: (req, res, next) ->
|
||||
{project_id, thread_id} = req.params
|
||||
logger.log {project_id, thread_id}, "reopening comment thread"
|
||||
ChatApiHandler.reopenThread project_id, thread_id, (err, threads) ->
|
||||
return next(err) if err?
|
||||
EditorRealTimeController.emitToRoom project_id, "reopen-thread", thread_id, (err)->
|
||||
res.send 204
|
||||
|
||||
deleteThread: (req, res, next) ->
|
||||
{project_id, doc_id, thread_id} = req.params
|
||||
logger.log {project_id, doc_id, thread_id}, "deleting comment thread"
|
||||
DocumentUpdaterHandler.deleteThread project_id, doc_id, thread_id, (err) ->
|
||||
return next(err) if err?
|
||||
ChatApiHandler.deleteThread project_id, thread_id, (err, threads) ->
|
||||
return next(err) if err?
|
||||
EditorRealTimeController.emitToRoom project_id, "delete-thread", thread_id, (err)->
|
||||
res.send 204
|
||||
|
||||
editMessage: (req, res, next) ->
|
||||
{project_id, thread_id, message_id} = req.params
|
||||
{content} = req.body
|
||||
logger.log {project_id, thread_id, message_id}, "editing message thread"
|
||||
ChatApiHandler.editMessage project_id, thread_id, message_id, content, (err) ->
|
||||
return next(err) if err?
|
||||
EditorRealTimeController.emitToRoom project_id, "edit-message", thread_id, message_id, content, (err)->
|
||||
res.send 204
|
||||
|
||||
deleteMessage: (req, res, next) ->
|
||||
{project_id, thread_id, message_id} = req.params
|
||||
logger.log {project_id, thread_id, message_id}, "deleting message"
|
||||
ChatApiHandler.deleteMessage project_id, thread_id, message_id, (err, threads) ->
|
||||
return next(err) if err?
|
||||
EditorRealTimeController.emitToRoom project_id, "delete-message", thread_id, message_id, (err)->
|
||||
res.send 204
|
||||
|
||||
_injectUserInfoIntoThreads: (threads, callback = (error, threads) ->) ->
|
||||
userCache = {}
|
||||
getUserDetails = (user_id, callback = (error, user) ->) ->
|
||||
return callback(null, userCache[user_id]) if userCache[user_id]?
|
||||
UserInfoManager.getPersonalInfo user_id, (err, user) ->
|
||||
return callback(error) if error?
|
||||
user = UserInfoController.formatPersonalInfo user
|
||||
userCache[user_id] = user
|
||||
callback null, user
|
||||
|
||||
jobs = []
|
||||
for thread_id, thread of threads
|
||||
do (thread) ->
|
||||
if thread.resolved
|
||||
jobs.push (cb) ->
|
||||
getUserDetails thread.resolved_by_user_id, (error, user) ->
|
||||
cb(error) if error?
|
||||
thread.resolved_by_user = user
|
||||
cb()
|
||||
for message in thread.messages
|
||||
do (message) ->
|
||||
jobs.push (cb) ->
|
||||
getUserDetails message.user_id, (error, user) ->
|
||||
cb(error) if error?
|
||||
message.user = user
|
||||
cb()
|
||||
|
||||
async.series jobs, (error) ->
|
||||
return callback(error) if error?
|
||||
return callback null, threads
|
||||
@@ -5,5 +5,14 @@ NotFoundError = (message) ->
|
||||
return error
|
||||
NotFoundError.prototype.__proto__ = Error.prototype
|
||||
|
||||
|
||||
ServiceNotConfiguredError = (message) ->
|
||||
error = new Error(message)
|
||||
error.name = "ServiceNotConfiguredError"
|
||||
error.__proto__ = ServiceNotConfiguredError.prototype
|
||||
return error
|
||||
|
||||
|
||||
module.exports = Errors =
|
||||
NotFoundError: NotFoundError
|
||||
NotFoundError: NotFoundError
|
||||
ServiceNotConfiguredError: ServiceNotConfiguredError
|
||||
|
||||
@@ -25,10 +25,19 @@ module.exports = FileStoreHandler =
|
||||
timeout:fiveMinsInMs
|
||||
writeStream = request(opts)
|
||||
readStream.pipe writeStream
|
||||
writeStream.on "end", callback
|
||||
|
||||
writeStream.on 'response', (response) ->
|
||||
if response.statusCode not in [200, 201]
|
||||
err = new Error("non-ok response from filestore for upload: #{response.statusCode}")
|
||||
logger.err {err, statusCode: response.statusCode}, "error uploading to filestore"
|
||||
callback(err)
|
||||
else
|
||||
callback(null)
|
||||
|
||||
readStream.on "error", (err)->
|
||||
logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the read stream of uploadFileFromDisk"
|
||||
callback err
|
||||
|
||||
writeStream.on "error", (err)->
|
||||
logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the write stream of uploadFileFromDisk"
|
||||
callback err
|
||||
@@ -79,4 +88,4 @@ module.exports = FileStoreHandler =
|
||||
callback(err)
|
||||
|
||||
_buildUrl: (project_id, file_id)->
|
||||
return "#{settings.apis.filestore.url}/project/#{project_id}/file/#{file_id}"
|
||||
return "#{settings.apis.filestore.url}/project/#{project_id}/file/#{file_id}"
|
||||
|
||||
@@ -224,6 +224,11 @@ module.exports = ProjectController =
|
||||
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-03-09") # 8th March
|
||||
# Don't show for users who registered after it was released
|
||||
return cb(null, false)
|
||||
timeout = setTimeout cb, 500
|
||||
AnalyticsManager.getLastOccurance user_id, "shown-track-changes-onboarding-2", (error, event) ->
|
||||
clearTimeout timeout
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
_ = require("underscore")
|
||||
|
||||
module.exports = ProjectEditorHandler =
|
||||
trackChangesAvailable: false
|
||||
|
||||
buildProjectModelView: (project, members, invites) ->
|
||||
result =
|
||||
_id : project._id
|
||||
@@ -20,11 +22,6 @@ module.exports = ProjectEditorHandler =
|
||||
if !result.invites?
|
||||
result.invites = []
|
||||
|
||||
trackChangesVisible = false
|
||||
for member in members
|
||||
if member.privilegeLevel == "owner" and (member.user?.featureSwitches?.track_changes or member.user?.betaProgram)
|
||||
trackChangesVisible = true
|
||||
|
||||
{owner, ownerFeatures, members} = @buildOwnerAndMembersViews(members)
|
||||
result.owner = owner
|
||||
result.members = members
|
||||
@@ -38,7 +35,7 @@ module.exports = ProjectEditorHandler =
|
||||
templates: false
|
||||
references: false
|
||||
trackChanges: false
|
||||
trackChangesVisible: trackChangesVisible
|
||||
trackChangesVisible: ProjectEditorHandler.trackChangesAvailable
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -16,6 +16,8 @@ module.exports =
|
||||
webRouter.get '/style', HomeController.externalPage("style_guide", "Style Guide")
|
||||
webRouter.get '/jobs', HomeController.externalPage("jobs", "Jobs")
|
||||
|
||||
webRouter.get '/track-changes-and-comments-in-latex', HomeController.externalPage("review-features-page", "Review features")
|
||||
|
||||
webRouter.get '/dropbox', HomeController.externalPage("dropbox", "Dropbox and ShareLaTeX")
|
||||
|
||||
webRouter.get '/university', UniversityController.getIndexPage
|
||||
|
||||
@@ -23,7 +23,7 @@ module.exports = SystemMessageManager =
|
||||
clearCache: () ->
|
||||
delete @_cachedMessages
|
||||
|
||||
CACHE_TIMEOUT = 5 * 60 * 1000 # 5 minutes
|
||||
CACHE_TIMEOUT = 20 * 1000 # 20 seconds
|
||||
setInterval () ->
|
||||
SystemMessageManager.clearCache()
|
||||
, CACHE_TIMEOUT
|
||||
, CACHE_TIMEOUT
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
DocumentUpdaterHandler = require "../DocumentUpdater/DocumentUpdaterHandler"
|
||||
DocstoreManager = require "../Docstore/DocstoreManager"
|
||||
UserInfoManager = require "../User/UserInfoManager"
|
||||
async = require "async"
|
||||
|
||||
module.exports = RangesManager =
|
||||
getAllRanges: (project_id, callback = (error, docs) ->) ->
|
||||
DocumentUpdaterHandler.flushProjectToMongo project_id, (error) ->
|
||||
return callback(error) if error?
|
||||
DocstoreManager.getAllRanges project_id, callback
|
||||
|
||||
getAllChangesUsers: (project_id, callback = (error, users) ->) ->
|
||||
user_ids = {}
|
||||
RangesManager.getAllRanges project_id, (error, docs) ->
|
||||
return callback(error) if error?
|
||||
jobs = []
|
||||
for doc in docs
|
||||
for change in doc.ranges?.changes or []
|
||||
user_ids[change.metadata.user_id] = true
|
||||
|
||||
async.mapSeries Object.keys(user_ids), (user_id, cb) ->
|
||||
UserInfoManager.getPersonalInfo user_id, cb
|
||||
, callback
|
||||
@@ -1,42 +0,0 @@
|
||||
RangesManager = require "./RangesManager"
|
||||
logger = require "logger-sharelatex"
|
||||
UserInfoController = require "../User/UserInfoController"
|
||||
DocumentUpdaterHandler = require "../DocumentUpdater/DocumentUpdaterHandler"
|
||||
EditorRealTimeController = require("../Editor/EditorRealTimeController")
|
||||
TrackChangesManager = require "./TrackChangesManager"
|
||||
|
||||
module.exports = TrackChangesController =
|
||||
getAllRanges: (req, res, next) ->
|
||||
project_id = req.params.project_id
|
||||
logger.log {project_id}, "request for project ranges"
|
||||
RangesManager.getAllRanges project_id, (error, docs = []) ->
|
||||
return next(error) if error?
|
||||
docs = ({id: d._id, ranges: d.ranges} for d in docs)
|
||||
res.json docs
|
||||
|
||||
getAllChangesUsers: (req, res, next) ->
|
||||
project_id = req.params.project_id
|
||||
logger.log {project_id}, "request for project range users"
|
||||
RangesManager.getAllChangesUsers project_id, (error, users) ->
|
||||
return next(error) if error?
|
||||
users = (UserInfoController.formatPersonalInfo(user) for user in users)
|
||||
# Get rid of any anonymous/deleted user objects
|
||||
users = users.filter (u) -> u?.id?
|
||||
res.json users
|
||||
|
||||
acceptChange: (req, res, next) ->
|
||||
{project_id, doc_id, change_id} = req.params
|
||||
logger.log {project_id, doc_id, change_id}, "request to accept change"
|
||||
DocumentUpdaterHandler.acceptChange project_id, doc_id, change_id, (error) ->
|
||||
return next(error) if error?
|
||||
EditorRealTimeController.emitToRoom project_id, "accept-change", doc_id, change_id, (err)->
|
||||
res.send 204
|
||||
|
||||
toggleTrackChanges: (req, res, next) ->
|
||||
{project_id} = req.params
|
||||
track_changes_on = !!req.body.on
|
||||
logger.log {project_id, track_changes_on}, "request to toggle track changes"
|
||||
TrackChangesManager.toggleTrackChanges project_id, track_changes_on, (error) ->
|
||||
return next(error) if error?
|
||||
EditorRealTimeController.emitToRoom project_id, "toggle-track-changes", track_changes_on, (err)->
|
||||
res.send 204
|
||||
@@ -1,5 +0,0 @@
|
||||
Project = require("../../models/Project").Project
|
||||
|
||||
module.exports = TrackChangesManager =
|
||||
toggleTrackChanges: (project_id, track_changes_on, callback = (error) ->) ->
|
||||
Project.update {_id: project_id}, {track_changes: track_changes_on}, callback
|
||||
@@ -1,5 +1,5 @@
|
||||
version = {
|
||||
"pdfjs": "1.6.210p2"
|
||||
"pdfjs": "1.7.225"
|
||||
"moment": "2.9.0"
|
||||
"ace": "1.2.5"
|
||||
}
|
||||
|
||||
@@ -39,9 +39,6 @@ UserSchema = new Schema
|
||||
references: { type:Boolean, default: Settings.defaultFeatures.references }
|
||||
trackChanges: { type:Boolean, default: Settings.defaultFeatures.trackChanges }
|
||||
}
|
||||
featureSwitches : {
|
||||
track_changes: { type: Boolean }
|
||||
}
|
||||
referal_id : {type:String, default:() -> uuid.v4().split("-")[0]}
|
||||
refered_users: [ type:ObjectId, ref:'User' ]
|
||||
refered_user_count: { type:Number, default: 0 }
|
||||
|
||||
@@ -40,8 +40,6 @@ AuthorizationMiddlewear = require('./Features/Authorization/AuthorizationMiddlew
|
||||
BetaProgramController = require('./Features/BetaProgram/BetaProgramController')
|
||||
AnalyticsRouter = require('./Features/Analytics/AnalyticsRouter')
|
||||
AnnouncementsController = require("./Features/Announcements/AnnouncementsController")
|
||||
TrackChangesController = require("./Features/TrackChanges/TrackChangesController")
|
||||
CommentsController = require "./Features/Comments/CommentsController"
|
||||
|
||||
logger = require("logger-sharelatex")
|
||||
_ = require("underscore")
|
||||
@@ -177,11 +175,6 @@ module.exports = class Router
|
||||
webRouter.get "/project/:Project_id/doc/:doc_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.proxyToHistoryApi
|
||||
webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.proxyToHistoryApi
|
||||
|
||||
webRouter.get "/project/:project_id/ranges", AuthorizationMiddlewear.ensureUserCanReadProject, TrackChangesController.getAllRanges
|
||||
webRouter.get "/project/:project_id/changes/users", AuthorizationMiddlewear.ensureUserCanReadProject, TrackChangesController.getAllChangesUsers
|
||||
webRouter.post "/project/:project_id/doc/:doc_id/changes/:change_id/accept", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, TrackChangesController.acceptChange
|
||||
webRouter.post "/project/:project_id/track_changes", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, TrackChangesController.toggleTrackChanges
|
||||
|
||||
webRouter.get '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject
|
||||
webRouter.get '/project/download/zip', AuthorizationMiddlewear.ensureUserCanReadMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
|
||||
|
||||
@@ -232,15 +225,6 @@ module.exports = class Router
|
||||
|
||||
webRouter.get "/project/:project_id/messages", AuthorizationMiddlewear.ensureUserCanReadProject, ChatController.getMessages
|
||||
webRouter.post "/project/:project_id/messages", AuthorizationMiddlewear.ensureUserCanReadProject, ChatController.sendMessage
|
||||
|
||||
# Note: Read only users can still comment
|
||||
webRouter.post "/project/:project_id/thread/:thread_id/messages", AuthorizationMiddlewear.ensureUserCanReadProject, CommentsController.sendComment
|
||||
webRouter.get "/project/:project_id/threads", AuthorizationMiddlewear.ensureUserCanReadProject, CommentsController.getThreads
|
||||
webRouter.post "/project/:project_id/thread/:thread_id/resolve", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, CommentsController.resolveThread
|
||||
webRouter.post "/project/:project_id/thread/:thread_id/reopen", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, CommentsController.reopenThread
|
||||
webRouter.delete "/project/:project_id/doc/:doc_id/thread/:thread_id", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, CommentsController.deleteThread
|
||||
webRouter.post "/project/:project_id/thread/:thread_id/messages/:message_id/edit", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, CommentsController.editMessage
|
||||
webRouter.delete "/project/:project_id/thread/:thread_id/messages/:message_id", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, CommentsController.deleteMessage
|
||||
|
||||
webRouter.post "/project/:Project_id/references/index", AuthorizationMiddlewear.ensureUserCanReadProject, ReferencesController.index
|
||||
webRouter.post "/project/:Project_id/references/indexAll", AuthorizationMiddlewear.ensureUserCanReadProject, ReferencesController.indexAll
|
||||
|
||||
@@ -18,9 +18,7 @@ block content
|
||||
| #{translate("beta_program_badge_description")}
|
||||
span.beta-feature-badge
|
||||
p.text-centered
|
||||
strong We're currently testing track changes and commenting:
|
||||
p.text-centered
|
||||
img(src="/img/teasers/track-changes/track-changes-beta.png", style="max-width: 100%; border-bottom: 1px solid #ddd")
|
||||
strong We're not currently testing anything in beta, but keep checking back!
|
||||
.row.text-centered
|
||||
.col-md-12
|
||||
if user.betaProgram
|
||||
|
||||
@@ -19,7 +19,8 @@ div.full-size(
|
||||
'rp-size-mini': (!ui.reviewPanelOpen && reviewPanel.hasEntries),\
|
||||
'rp-size-expanded': ui.reviewPanelOpen,\
|
||||
'rp-layout-left': reviewPanel.layoutToLeft,\
|
||||
'rp-loading-threads': reviewPanel.loadingThreads\
|
||||
'rp-loading-threads': reviewPanel.loadingThreads,\
|
||||
'rp-new-comment-ui': reviewPanel.newAddCommentUI\
|
||||
}"
|
||||
)
|
||||
.loading-panel(ng-show="!editor.sharejs_doc || editor.opening")
|
||||
|
||||
@@ -94,7 +94,6 @@ header.toolbar.toolbar-header.toolbar-with-labels(
|
||||
i.review-icon
|
||||
p.toolbar-label
|
||||
| #{translate("review")}
|
||||
span(style="vertical-align: 20%; margin-left: 4px; padding: 2px 4px;").beta-feature-badge
|
||||
a.btn.btn-full-height(
|
||||
href,
|
||||
ng-if="permissions.admin",
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
#review-panel
|
||||
.rp-in-editor-widgets
|
||||
a.rp-track-changes-indicator(
|
||||
href
|
||||
ng-if="editor.wantTrackChanges"
|
||||
ng-click="toggleReviewPanel();"
|
||||
ng-class="{ 'rp-track-changes-indicator-on-dark' : darkTheme }"
|
||||
) !{translate("track_changes_is_on")}
|
||||
a.rp-add-comment-btn(
|
||||
href
|
||||
ng-if="reviewPanel.newAddCommentUI && reviewPanel.entries[editor.open_doc_id]['add-comment'] != null"
|
||||
ng-click="addNewComment();"
|
||||
)
|
||||
i.fa.fa-comment
|
||||
| #{translate("add_comment")}
|
||||
#review-panel
|
||||
.review-panel-toolbar
|
||||
resolved-comments-dropdown(
|
||||
class="rp-flex-block"
|
||||
@@ -314,7 +322,7 @@ script(type='text/ng-template', id='resolvedCommentEntryTemplate')
|
||||
script(type='text/ng-template', id='addCommentEntryTemplate')
|
||||
div
|
||||
.rp-entry-callout.rp-entry-callout-add-comment
|
||||
.rp-entry-indicator(
|
||||
.rp-entry-indicator.rp-entry-indicator-add-comment(
|
||||
ng-if="!commentState.adding"
|
||||
ng-click="startNewComment(); onIndicatorClick();"
|
||||
tooltip=translate("add_comment")
|
||||
@@ -340,10 +348,10 @@ script(type='text/ng-template', id='addCommentEntryTemplate')
|
||||
ng-keypress="handleCommentKeyPress($event);"
|
||||
placeholder=translate("add_your_comment_here")
|
||||
focus-on="comment:new:open"
|
||||
ng-blur="submitNewComment()"
|
||||
ng-blur="submitNewComment($event)"
|
||||
)
|
||||
.rp-entry-actions
|
||||
button.rp-entry-button(
|
||||
button.rp-entry-button.rp-entry-button-cancel(
|
||||
ng-click="cancelNewComment();"
|
||||
)
|
||||
i.fa.fa-times
|
||||
|
||||
@@ -4,11 +4,11 @@ block scripts
|
||||
script(src="https://js.recurly.com/v3/recurly.js")
|
||||
|
||||
script(type='text/javascript').
|
||||
window.recomendedCurrency = '#{currency}'
|
||||
window.countryCode = '#{countryCode}'
|
||||
window.plan_code = '#{plan_code}'
|
||||
window.recurlyApiKey = "!{settings.apis.recurly.publicKey}"
|
||||
window.couponCode = "#{couponCode}"
|
||||
window.couponCode = !{JSON.stringify(couponCode)}
|
||||
window.recomendedCurrency = !{JSON.stringify(currency.slice(0,3))}
|
||||
|
||||
block content
|
||||
.content.content-alt
|
||||
|
||||
@@ -353,11 +353,17 @@ define [
|
||||
@ranges.applyOp op, { user_id: track_changes_as }
|
||||
if old_id_seed?
|
||||
@ranges.setIdSeed(old_id_seed)
|
||||
if remote_op
|
||||
# With remote ops, Ace hasn't been updated when we receive this op,
|
||||
# so defer updating track changes until it has
|
||||
setTimeout () => @emit "ranges:dirty"
|
||||
else
|
||||
@emit "ranges:dirty"
|
||||
|
||||
_catchUpRanges: (changes = [], comments = []) ->
|
||||
# We've just been given the current server's ranges, but need to apply any local ops we have.
|
||||
# Reset to the server state then apply our local ops again.
|
||||
@ranges.emit "clear"
|
||||
@emit "ranges:clear"
|
||||
@ranges.changes = changes
|
||||
@ranges.comments = comments
|
||||
@ranges.track_changes = @doc.track_changes
|
||||
@@ -367,4 +373,4 @@ define [
|
||||
for op in @doc.getPendingOp() or []
|
||||
@ranges.setIdSeed(@doc.track_changes_id_seeds.pending)
|
||||
@ranges.applyOp(op, { user_id: @track_changes_as })
|
||||
@ranges.emit "redraw"
|
||||
@emit "ranges:redraw"
|
||||
|
||||
@@ -121,11 +121,22 @@ define [
|
||||
|
||||
_bindToDocumentEvents: (doc, sharejs_doc) ->
|
||||
sharejs_doc.on "error", (error, meta) =>
|
||||
if error?.message?.match "maxDocLength"
|
||||
if error?.message?
|
||||
message = error.message
|
||||
else if typeof error == "string"
|
||||
message = error
|
||||
else
|
||||
message = ""
|
||||
if message.match "maxDocLength"
|
||||
@ide.showGenericMessageModal(
|
||||
"Document Too Long"
|
||||
"Sorry, this file is too long to be edited manually. Please upload it directly."
|
||||
)
|
||||
else if message.match "too many comments or tracked changes"
|
||||
@ide.showGenericMessageModal(
|
||||
"Too many comments or tracked changes"
|
||||
"Sorry, this file has too many comments or tracked changes. Please try accepting or rejecting some existing changes, or resolving and deleting some comments."
|
||||
)
|
||||
else
|
||||
@ide.socket.disconnect()
|
||||
@ide.reportError(error, meta)
|
||||
|
||||
@@ -322,10 +322,6 @@ define [
|
||||
doc = session.getDocument()
|
||||
doc.on "change", onChange
|
||||
|
||||
sharejs_doc.on "remoteop.recordRemote", (op, oldSnapshot, msg) ->
|
||||
undoManager.nextUpdateIsRemote = true
|
||||
trackChangesManager.nextUpdateMetaData = msg?.meta
|
||||
|
||||
editor.initing = true
|
||||
sharejs_doc.attachToAce(editor)
|
||||
editor.initing = false
|
||||
|
||||
@@ -14,11 +14,11 @@ define [
|
||||
return if !track_changes?
|
||||
@setTrackChanges(track_changes)
|
||||
|
||||
@$scope.$watch "sharejsDoc", (doc) =>
|
||||
@$scope.$watch "sharejsDoc", (doc, oldDoc) =>
|
||||
return if !doc?
|
||||
@disconnectFromRangesTracker()
|
||||
@rangesTracker = doc.ranges
|
||||
@connectToRangesTracker()
|
||||
if oldDoc?
|
||||
@disconnectFromDoc(oldDoc)
|
||||
@connectToDoc(doc)
|
||||
|
||||
@$scope.$on "comment:add", (e, thread_id, offset, length) =>
|
||||
@addCommentToSelection(thread_id, offset, length)
|
||||
@@ -36,10 +36,10 @@ define [
|
||||
@removeCommentId(comment_id)
|
||||
|
||||
@$scope.$on "comment:resolve_threads", (e, thread_ids) =>
|
||||
@resolveCommentByThreadIds(thread_ids)
|
||||
@hideCommentsByThreadIds(thread_ids)
|
||||
|
||||
@$scope.$on "comment:unresolve_thread", (e, thread_id) =>
|
||||
@unresolveCommentByThreadId(thread_id)
|
||||
@showCommentByThreadId(thread_id)
|
||||
|
||||
@$scope.$on "review-panel:recalculate-screen-positions", () =>
|
||||
@recalculateReviewEntriesScreenPositions()
|
||||
@@ -73,16 +73,24 @@ define [
|
||||
_scrollTimeout = null
|
||||
, 200
|
||||
|
||||
@_resetCutState()
|
||||
onCut = () => @onCut()
|
||||
onPaste = () => @onPaste()
|
||||
|
||||
bindToAce = () =>
|
||||
@editor.on "changeSelection", onChangeSelection
|
||||
@editor.on "change", onChangeSelection # Selection also moves with updates elsewhere in the document
|
||||
@editor.on "changeSession", onChangeSession
|
||||
@editor.on "cut", onCut
|
||||
@editor.on "paste", onPaste
|
||||
@editor.renderer.on "resize", onResize
|
||||
|
||||
unbindFromAce = () =>
|
||||
@editor.off "changeSelection", onChangeSelection
|
||||
@editor.off "change", onChangeSelection
|
||||
@editor.off "changeSession", onChangeSession
|
||||
@editor.off "cut", onCut
|
||||
@editor.off "paste", onPaste
|
||||
@editor.renderer.off "resize", onResize
|
||||
|
||||
@$scope.$watch "trackChangesEnabled", (enabled) =>
|
||||
@@ -92,18 +100,11 @@ define [
|
||||
else
|
||||
unbindFromAce()
|
||||
|
||||
disconnectFromRangesTracker: () ->
|
||||
disconnectFromDoc: (doc) ->
|
||||
@changeIdToMarkerIdMap = {}
|
||||
|
||||
if @rangesTracker?
|
||||
@rangesTracker.off "insert:added"
|
||||
@rangesTracker.off "insert:removed"
|
||||
@rangesTracker.off "delete:added"
|
||||
@rangesTracker.off "delete:removed"
|
||||
@rangesTracker.off "changes:moved"
|
||||
@rangesTracker.off "comment:added"
|
||||
@rangesTracker.off "comment:moved"
|
||||
@rangesTracker.off "comment:removed"
|
||||
doc.off "ranges:clear"
|
||||
doc.off "ranges:redraw"
|
||||
doc.off "ranges:dirty"
|
||||
|
||||
setTrackChanges: (value) ->
|
||||
if value
|
||||
@@ -111,56 +112,15 @@ define [
|
||||
else
|
||||
@$scope.sharejsDoc?.track_changes_as = null
|
||||
|
||||
connectToRangesTracker: () ->
|
||||
connectToDoc: (doc) ->
|
||||
@rangesTracker = doc.ranges
|
||||
@setTrackChanges(@$scope.trackChanges)
|
||||
|
||||
# Add a timeout because on remote ops, we get these notifications before
|
||||
# ace has updated
|
||||
@rangesTracker.on "insert:added", (change) =>
|
||||
sl_console.log "[insert:added]", change
|
||||
setTimeout () =>
|
||||
@_onInsertAdded(change)
|
||||
@broadcastChange()
|
||||
@rangesTracker.on "insert:removed", (change) =>
|
||||
sl_console.log "[insert:removed]", change
|
||||
setTimeout () =>
|
||||
@_onInsertRemoved(change)
|
||||
@broadcastChange()
|
||||
@rangesTracker.on "delete:added", (change) =>
|
||||
sl_console.log "[delete:added]", change
|
||||
setTimeout () =>
|
||||
@_onDeleteAdded(change)
|
||||
@broadcastChange()
|
||||
@rangesTracker.on "delete:removed", (change) =>
|
||||
sl_console.log "[delete:removed]", change
|
||||
setTimeout () =>
|
||||
@_onDeleteRemoved(change)
|
||||
@broadcastChange()
|
||||
@rangesTracker.on "changes:moved", (changes) =>
|
||||
sl_console.log "[changes:moved]", changes
|
||||
setTimeout () =>
|
||||
@_onChangesMoved(changes)
|
||||
@broadcastChange()
|
||||
|
||||
@rangesTracker.on "comment:added", (comment) =>
|
||||
sl_console.log "[comment:added]", comment
|
||||
setTimeout () =>
|
||||
@_onCommentAdded(comment)
|
||||
@broadcastChange()
|
||||
@rangesTracker.on "comment:moved", (comment) =>
|
||||
sl_console.log "[comment:moved]", comment
|
||||
setTimeout () =>
|
||||
@_onCommentMoved(comment)
|
||||
@broadcastChange()
|
||||
@rangesTracker.on "comment:removed", (comment) =>
|
||||
sl_console.log "[comment:removed]", comment
|
||||
setTimeout () =>
|
||||
@_onCommentRemoved(comment)
|
||||
@broadcastChange()
|
||||
|
||||
@rangesTracker.on "clear", () =>
|
||||
doc.on "ranges:dirty", () =>
|
||||
@updateAnnotations()
|
||||
doc.on "ranges:clear", () =>
|
||||
@clearAnnotations()
|
||||
@rangesTracker.on "redraw", () =>
|
||||
doc.on "ranges:redraw", () =>
|
||||
@redrawAnnotations()
|
||||
|
||||
clearAnnotations: () ->
|
||||
@@ -181,6 +141,55 @@ define [
|
||||
@_onCommentAdded(comment)
|
||||
|
||||
@broadcastChange()
|
||||
|
||||
_doneUpdateThisLoop: false
|
||||
_pendingUpdates: false
|
||||
updateAnnotations: () ->
|
||||
# Doc updates with multiple ops, like search/replace or block comments
|
||||
# will call this with every individual op in a single event loop. So only
|
||||
# do the first this loop, then schedule an update for the next loop for the rest.
|
||||
if !@_doneUpdateThisLoop
|
||||
@_doUpdateAnnotations()
|
||||
@_doneUpdateThisLoop = true
|
||||
setTimeout () =>
|
||||
if @_pendingUpdates
|
||||
@_doUpdateAnnotations()
|
||||
@_doneUpdateThisLoop = false
|
||||
@_pendingUpdates = false
|
||||
else
|
||||
@_pendingUpdates = true
|
||||
|
||||
_doUpdateAnnotations: () ->
|
||||
dirty = @rangesTracker.getDirtyState()
|
||||
|
||||
updateMarkers = false
|
||||
|
||||
for id, change of dirty.change.added
|
||||
if change.op.i?
|
||||
@_onInsertAdded(change)
|
||||
else if change.op.d?
|
||||
@_onDeleteAdded(change)
|
||||
for id, change of dirty.change.removed
|
||||
if change.op.i?
|
||||
@_onInsertRemoved(change)
|
||||
else if change.op.d?
|
||||
@_onDeleteRemoved(change)
|
||||
for id, change of dirty.change.moved
|
||||
updateMarkers = true
|
||||
@_onChangeMoved(change)
|
||||
|
||||
for id, comment of dirty.comment.added
|
||||
@_onCommentAdded(comment)
|
||||
for id, comment of dirty.comment.removed
|
||||
@_onCommentRemoved(comment)
|
||||
for id, comment of dirty.comment.moved
|
||||
updateMarkers = true
|
||||
@_onCommentMoved(comment)
|
||||
|
||||
@rangesTracker.resetDirtyState()
|
||||
if updateMarkers
|
||||
@editor.renderer.updateBackMarkers()
|
||||
@broadcastChange()
|
||||
|
||||
addComment: (offset, content, thread_id) ->
|
||||
op = { c: content, p: offset, t: thread_id }
|
||||
@@ -200,6 +209,7 @@ define [
|
||||
|
||||
acceptChangeId: (change_id) ->
|
||||
@rangesTracker.removeChangeId(change_id)
|
||||
@updateAnnotations()
|
||||
|
||||
rejectChangeId: (change_id) ->
|
||||
change = @rangesTracker.getChange(change_id)
|
||||
@@ -208,21 +218,26 @@ define [
|
||||
if change.op.d?
|
||||
content = change.op.d
|
||||
position = @_shareJsOffsetToAcePosition(change.op.p)
|
||||
session.$fromReject = true # Tell track changes to cancel out delete
|
||||
session.insert(position, content)
|
||||
session.$fromReject = false
|
||||
else if change.op.i?
|
||||
start = @_shareJsOffsetToAcePosition(change.op.p)
|
||||
end = @_shareJsOffsetToAcePosition(change.op.p + change.op.i.length)
|
||||
editor_text = session.getDocument().getTextRange({start, end})
|
||||
if editor_text != change.op.i
|
||||
throw new Error("Op to be removed (#{JSON.stringify(change.op)}), does not match editor text, '#{editor_text}'")
|
||||
session.$fromReject = true
|
||||
session.remove({start, end})
|
||||
session.$fromReject = false
|
||||
else
|
||||
throw new Error("unknown change: #{JSON.stringify(change)}")
|
||||
|
||||
removeCommentId: (comment_id) ->
|
||||
@rangesTracker.removeCommentId(comment_id)
|
||||
@updateAnnotations()
|
||||
|
||||
resolveCommentByThreadIds: (thread_ids) ->
|
||||
hideCommentsByThreadIds: (thread_ids) ->
|
||||
resolve_ids = {}
|
||||
for id in thread_ids
|
||||
resolve_ids[id] = true
|
||||
@@ -231,12 +246,55 @@ define [
|
||||
@_onCommentRemoved(comment)
|
||||
@broadcastChange()
|
||||
|
||||
unresolveCommentByThreadId: (thread_id) ->
|
||||
showCommentByThreadId: (thread_id) ->
|
||||
for comment in @rangesTracker?.comments or []
|
||||
if comment.op.t == thread_id
|
||||
@_onCommentAdded(comment)
|
||||
@broadcastChange()
|
||||
|
||||
_resetCutState: () ->
|
||||
@_cutState = {
|
||||
text: null
|
||||
comments: []
|
||||
docId: null
|
||||
}
|
||||
|
||||
onCut: () ->
|
||||
@_resetCutState()
|
||||
selection = @editor.getSelectionRange()
|
||||
selection_start = @_aceRangeToShareJs(selection.start)
|
||||
selection_end = @_aceRangeToShareJs(selection.end)
|
||||
@_cutState.text = @editor.getSelectedText()
|
||||
@_cutState.docId = @$scope.docId
|
||||
for comment in @rangesTracker.comments
|
||||
comment_start = comment.op.p
|
||||
comment_end = comment_start + comment.op.c.length
|
||||
if selection_start <= comment_start and comment_end <= selection_end
|
||||
@_cutState.comments.push {
|
||||
offset: comment.op.p - selection_start
|
||||
text: comment.op.c
|
||||
comment: comment
|
||||
}
|
||||
|
||||
onPaste: () =>
|
||||
@editor.once "change", (change) =>
|
||||
return if change.action != "insert"
|
||||
pasted_text = change.lines.join("\n")
|
||||
paste_offset = @_aceRangeToShareJs(change.start)
|
||||
# We have to wait until the change has been processed by the range tracker,
|
||||
# since if we move the ops into place beforehand, they will be moved again
|
||||
# when the changes are processed by the range tracker. This ranges:dirty
|
||||
# event is fired after the doc has applied the changes to the range tracker.
|
||||
@$scope.sharejsDoc.on "ranges:dirty.paste", () =>
|
||||
@$scope.sharejsDoc.off "ranges:dirty.paste" # Doc event emitter uses namespaced events
|
||||
if pasted_text == @_cutState.text and @$scope.docId == @_cutState.docId
|
||||
for {comment, offset, text} in @_cutState.comments
|
||||
op = { c: text, p: paste_offset + offset, t: comment.id }
|
||||
@$scope.sharejsDoc.submitOp op # Resubmitting an existing comment op (by thread id) will move it
|
||||
@_resetCutState()
|
||||
# Check that comments still match text. Will throw error if not.
|
||||
@rangesTracker.validate(@editor.getValue())
|
||||
|
||||
checkMapping: () ->
|
||||
# TODO: reintroduce this check
|
||||
session = @editor.getSession()
|
||||
@@ -421,23 +479,18 @@ define [
|
||||
lines = @editor.getSession().getDocument().getAllLines()
|
||||
return AceShareJsCodec.shareJsOffsetToAcePosition(offset, lines)
|
||||
|
||||
_onChangesMoved: (changes) ->
|
||||
# TODO: PERFORMANCE: Only run through the Ace lines once, and calculate all
|
||||
# change positions as we go.
|
||||
for change in changes
|
||||
start = @_shareJsOffsetToAcePosition(change.op.p)
|
||||
if change.op.i?
|
||||
end = @_shareJsOffsetToAcePosition(change.op.p + change.op.i.length)
|
||||
else
|
||||
end = start
|
||||
@_updateMarker(change.id, start, end)
|
||||
@editor.renderer.updateBackMarkers()
|
||||
_onChangeMoved: (change) ->
|
||||
start = @_shareJsOffsetToAcePosition(change.op.p)
|
||||
if change.op.i?
|
||||
end = @_shareJsOffsetToAcePosition(change.op.p + change.op.i.length)
|
||||
else
|
||||
end = start
|
||||
@_updateMarker(change.id, start, end)
|
||||
|
||||
_onCommentMoved: (comment) ->
|
||||
start = @_shareJsOffsetToAcePosition(comment.op.p)
|
||||
end = @_shareJsOffsetToAcePosition(comment.op.p + comment.op.c.length)
|
||||
@_updateMarker(comment.id, start, end)
|
||||
@editor.renderer.updateBackMarkers()
|
||||
|
||||
_updateMarker: (change_id, start, end) ->
|
||||
return if !@changeIdToMarkerIdMap[change_id]?
|
||||
|
||||
@@ -11,10 +11,10 @@ define [
|
||||
show_remote_warning: false
|
||||
|
||||
@reset()
|
||||
@nextUpdateIsRemote = false
|
||||
|
||||
@editor.on "changeSession", (e) =>
|
||||
@reset()
|
||||
@session = e.session
|
||||
e.session.setUndoManager(@)
|
||||
|
||||
showUndoConflictWarning: () ->
|
||||
@@ -38,20 +38,44 @@ define [
|
||||
@firstUpdate = false
|
||||
return
|
||||
aceDeltaSets = options.args[0]
|
||||
@session = options.args[1]
|
||||
return if !aceDeltaSets?
|
||||
@session = options.args[1]
|
||||
|
||||
lines = @session.getDocument().getAllLines()
|
||||
linesBeforeChange = @_revertAceDeltaSetsOnDocLines(aceDeltaSets, lines)
|
||||
simpleDeltaSets = @_aceDeltaSetsToSimpleDeltaSets(aceDeltaSets, linesBeforeChange)
|
||||
@undoStack.push(
|
||||
deltaSets: simpleDeltaSets
|
||||
remote: @nextUpdateIsRemote
|
||||
)
|
||||
# We need to split the delta sets into local or remote groups before pushing onto
|
||||
# the undo stack, since these are treated differently.
|
||||
splitDeltaSets = []
|
||||
currentDeltaSet = null # Make global to this function
|
||||
do newDeltaSet = () ->
|
||||
currentDeltaSet = {group: "doc", deltas: []}
|
||||
splitDeltaSets.push currentDeltaSet
|
||||
currentRemoteState = null
|
||||
|
||||
for deltaSet in aceDeltaSets or []
|
||||
if deltaSet.group == "doc" # ignore code folding etc.
|
||||
for delta in deltaSet.deltas
|
||||
if currentDeltaSet.remote? and currentDeltaSet.remote != !!delta.remote
|
||||
newDeltaSet()
|
||||
currentDeltaSet.deltas.push delta
|
||||
currentDeltaSet.remote = !!delta.remote
|
||||
|
||||
# The lines are currently as they are after applying all these deltas, but to turn into simple deltas,
|
||||
# we need the lines before each delta group.
|
||||
docLines = @session.getDocument().getAllLines()
|
||||
docLines = @_revertAceDeltaSetsOnDocLines(aceDeltaSets, docLines)
|
||||
for deltaSet in splitDeltaSets
|
||||
{simpleDeltaSet, docLines} = @_aceDeltaSetToSimpleDeltaSet(deltaSet, docLines)
|
||||
frame = {
|
||||
deltaSets: [simpleDeltaSet]
|
||||
remote: deltaSet.remote
|
||||
}
|
||||
@undoStack.push frame
|
||||
@redoStack = []
|
||||
@nextUpdateIsRemote = false
|
||||
|
||||
undo: (dontSelect) ->
|
||||
# We rely on the doclines being in sync with the undo stack, so make sure
|
||||
# any pending undo deltas are processed.
|
||||
@session.$syncInformUndoManager()
|
||||
|
||||
localUpdatesMade = @_shiftLocalChangeToTopOfUndoStack()
|
||||
return if !localUpdatesMade
|
||||
|
||||
@@ -206,19 +230,16 @@ define [
|
||||
throw "Unknown delta type"
|
||||
return doc.split("\n")
|
||||
|
||||
_aceDeltaSetsToSimpleDeltaSets: (aceDeltaSets, docLines) ->
|
||||
simpleDeltaSets = []
|
||||
for deltaSet in aceDeltaSets
|
||||
if deltaSet.group == "doc" # ignore fold changes
|
||||
simpleDeltas = []
|
||||
for delta in deltaSet.deltas
|
||||
simpleDeltas.push @_aceDeltaToSimpleDelta(delta, docLines)
|
||||
docLines = @_applyAceDeltasToDocLines([delta], docLines)
|
||||
simpleDeltaSets.push {
|
||||
deltas: simpleDeltas
|
||||
group: deltaSet.group
|
||||
}
|
||||
return simpleDeltaSets
|
||||
_aceDeltaSetToSimpleDeltaSet: (deltaSet, docLines) ->
|
||||
simpleDeltas = []
|
||||
for delta in deltaSet.deltas
|
||||
simpleDeltas.push @_aceDeltaToSimpleDelta(delta, docLines)
|
||||
docLines = @_applyAceDeltasToDocLines([delta], docLines)
|
||||
simpleDeltaSet = {
|
||||
deltas: simpleDeltas
|
||||
group: deltaSet.group
|
||||
}
|
||||
return {simpleDeltaSet, docLines}
|
||||
|
||||
_simpleDeltaSetsToAceDeltaSets: (simpleDeltaSets, docLines) ->
|
||||
for deltaSet in simpleDeltaSets
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Range = ace.require("ace/range").Range
|
||||
|
||||
# Convert an ace delta into an op understood by share.js
|
||||
applyToShareJS = (editorDoc, delta, doc) ->
|
||||
applyToShareJS = (editorDoc, delta, doc, fromUndo) ->
|
||||
# Get the start position of the range, in no. of characters
|
||||
getStartOffsetPosition = (start) ->
|
||||
# This is quite inefficient - getLines makes a copy of the entire
|
||||
@@ -27,11 +27,11 @@ applyToShareJS = (editorDoc, delta, doc) ->
|
||||
switch delta.action
|
||||
when 'insert'
|
||||
text = delta.lines.join('\n')
|
||||
doc.insert pos, text
|
||||
doc.insert pos, text, fromUndo
|
||||
|
||||
when 'remove'
|
||||
text = delta.lines.join('\n')
|
||||
doc.del pos, text.length
|
||||
doc.del pos, text.length, fromUndo
|
||||
|
||||
else throw new Error "unknown action: #{delta.action}"
|
||||
|
||||
@@ -78,8 +78,10 @@ window.sharejs.extendDoc 'attach_ace', (editor, keepEditorContents, maxDocLength
|
||||
if maxDocLength? and editorDoc.getValue().length > maxDocLength
|
||||
doc.emit "error", new Error("document length is greater than maxDocLength")
|
||||
return
|
||||
|
||||
fromUndo = !!(editor.getSession().$fromUndo or editor.getSession().$fromReject)
|
||||
|
||||
applyToShareJS editorDoc, change, doc
|
||||
applyToShareJS editorDoc, change, doc, fromUndo
|
||||
|
||||
check()
|
||||
|
||||
@@ -108,16 +110,46 @@ window.sharejs.extendDoc 'attach_ace', (editor, keepEditorContents, maxDocLength
|
||||
|
||||
row:row, column:offset
|
||||
|
||||
# We want to insert a remote:true into the delta if the op comes from the
|
||||
# underlying sharejs doc (which means it is from a remote op), so we have to do
|
||||
# the work of editorDoc.insert and editorDoc.remove manually. These methods are
|
||||
# copied from ace.js doc#insert and #remove, and then inject the remote:true
|
||||
# flag into the delta.
|
||||
doc.on 'insert', (pos, text) ->
|
||||
if (editorDoc.getLength() <= 1)
|
||||
editorDoc.$detectNewLine(text)
|
||||
|
||||
lines = editorDoc.$split(text)
|
||||
position = offsetToPos(pos)
|
||||
start = editorDoc.clippedPos(position.row, position.column)
|
||||
end = {
|
||||
row: start.row + lines.length - 1,
|
||||
column: (if lines.length == 1 then start.column else 0) + lines[lines.length - 1].length
|
||||
}
|
||||
|
||||
suppress = true
|
||||
editorDoc.insert offsetToPos(pos), text
|
||||
editorDoc.applyDelta({
|
||||
start: start,
|
||||
end: end,
|
||||
action: "insert",
|
||||
lines: lines,
|
||||
remote: true
|
||||
});
|
||||
suppress = false
|
||||
check()
|
||||
|
||||
doc.on 'delete', (pos, text) ->
|
||||
suppress = true
|
||||
range = Range.fromPoints offsetToPos(pos), offsetToPos(pos + text.length)
|
||||
editorDoc.remove range
|
||||
start = editorDoc.clippedPos(range.start.row, range.start.column)
|
||||
end = editorDoc.clippedPos(range.end.row, range.end.column)
|
||||
suppress = true
|
||||
editorDoc.applyDelta({
|
||||
start: start,
|
||||
end: end,
|
||||
action: "remove",
|
||||
lines: editorDoc.getLinesForRange({start: start, end: end})
|
||||
remote: true
|
||||
});
|
||||
suppress = false
|
||||
check()
|
||||
|
||||
|
||||
@@ -11,14 +11,20 @@ text.api =
|
||||
# Get the text contents of a document
|
||||
getText: -> @snapshot
|
||||
|
||||
insert: (pos, text, callback) ->
|
||||
op = [{p:pos, i:text}]
|
||||
insert: (pos, text, fromUndo, callback) ->
|
||||
op = {p:pos, i:text}
|
||||
if fromUndo
|
||||
op.u = true
|
||||
op = [op]
|
||||
|
||||
@submitOp op, callback
|
||||
op
|
||||
|
||||
del: (pos, length, callback) ->
|
||||
op = [{p:pos, d:@snapshot[pos...(pos + length)]}]
|
||||
del: (pos, length, fromUndo, callback) ->
|
||||
op = {p:pos, d:@snapshot[pos...(pos + length)]}
|
||||
if fromUndo
|
||||
op.u = true
|
||||
op = [op]
|
||||
|
||||
@submitOp op, callback
|
||||
op
|
||||
|
||||
@@ -56,6 +56,13 @@ text.apply = (snapshot, op) ->
|
||||
throw new Error "Unknown op type"
|
||||
snapshot
|
||||
|
||||
cloneAndModify = (op, modifications) ->
|
||||
newOp = {}
|
||||
for k,v of op
|
||||
newOp[k] = v
|
||||
for k,v of modifications
|
||||
newOp[k] = v
|
||||
return newOp
|
||||
|
||||
# Exported for use by the random op generator.
|
||||
#
|
||||
@@ -69,10 +76,10 @@ text._append = append = (newOp, c) ->
|
||||
last = newOp[newOp.length - 1]
|
||||
|
||||
# Compose the insert into the previous insert if possible
|
||||
if last.i? && c.i? and last.p <= c.p <= (last.p + last.i.length)
|
||||
newOp[newOp.length - 1] = {i:strInject(last.i, c.p - last.p, c.i), p:last.p}
|
||||
else if last.d? && c.d? and c.p <= last.p <= (c.p + c.d.length)
|
||||
newOp[newOp.length - 1] = {d:strInject(c.d, last.p - c.p, last.d), p:c.p}
|
||||
if last.i? && c.i? and last.p <= c.p <= (last.p + last.i.length) and last.u == c.u
|
||||
newOp[newOp.length - 1] = cloneAndModify(last, {i:strInject(last.i, c.p - last.p, c.i)})
|
||||
else if last.d? && c.d? and c.p <= last.p <= (c.p + c.d.length) and last.u == c.u
|
||||
newOp[newOp.length - 1] = cloneAndModify(last, {d:strInject(c.d, last.p - c.p, last.d), p: c.p})
|
||||
else
|
||||
newOp.push c
|
||||
|
||||
@@ -150,25 +157,25 @@ text._tc = transformComponent = (dest, c, otherC, side) ->
|
||||
checkValidOp [otherC]
|
||||
|
||||
if c.i?
|
||||
append dest, {i:c.i, p:transformPosition(c.p, otherC, side == 'right')}
|
||||
append dest, cloneAndModify(c, {p:transformPosition(c.p, otherC, side == 'right')})
|
||||
|
||||
else if c.d? # Delete
|
||||
if otherC.i? # delete vs insert
|
||||
s = c.d
|
||||
if c.p < otherC.p
|
||||
append dest, {d:s[...otherC.p - c.p], p:c.p}
|
||||
append dest, cloneAndModify(c, {d:s[...otherC.p - c.p]})
|
||||
s = s[(otherC.p - c.p)..]
|
||||
if s != ''
|
||||
append dest, {d:s, p:c.p + otherC.i.length}
|
||||
append dest, cloneAndModify(c, {d:s, p:c.p + otherC.i.length})
|
||||
|
||||
else if otherC.d? # Delete vs delete
|
||||
if c.p >= otherC.p + otherC.d.length
|
||||
append dest, {d:c.d, p:c.p - otherC.d.length}
|
||||
append dest, cloneAndModify(c, {p:c.p - otherC.d.length})
|
||||
else if c.p + c.d.length <= otherC.p
|
||||
append dest, c
|
||||
else
|
||||
# They overlap somewhere.
|
||||
newC = {d:'', p:c.p}
|
||||
newC = cloneAndModify(c, {d:''})
|
||||
if c.p < otherC.p
|
||||
newC.d = c.d[...(otherC.p - c.p)]
|
||||
if c.p + c.d.length > otherC.p + otherC.d.length
|
||||
@@ -198,18 +205,18 @@ text._tc = transformComponent = (dest, c, otherC, side) ->
|
||||
if c.p < otherC.p < c.p + c.c.length
|
||||
offset = otherC.p - c.p
|
||||
new_c = (c.c[0..(offset-1)] + otherC.i + c.c[offset...])
|
||||
append dest, {c:new_c, p:c.p, t: c.t}
|
||||
append dest, cloneAndModify(c, {c:new_c})
|
||||
else
|
||||
append dest, {c:c.c, p:transformPosition(c.p, otherC, true), t: c.t}
|
||||
append dest, cloneAndModify(c, {p:transformPosition(c.p, otherC, true)})
|
||||
|
||||
else if otherC.d?
|
||||
if c.p >= otherC.p + otherC.d.length
|
||||
append dest, {c:c.c, p:c.p - otherC.d.length, t: c.t}
|
||||
append dest, cloneAndModify(c, {p:c.p - otherC.d.length})
|
||||
else if c.p + c.c.length <= otherC.p
|
||||
append dest, c
|
||||
else # Delete overlaps comment
|
||||
# They overlap somewhere.
|
||||
newC = {c:'', p:c.p, t: c.t}
|
||||
newC = cloneAndModify(c, {c:''})
|
||||
if c.p < otherC.p
|
||||
newC.c = c.c[...(otherC.p - c.p)]
|
||||
if c.p + c.c.length > otherC.p + otherC.d.length
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
load = (EventEmitter) ->
|
||||
class RangesTracker extends EventEmitter
|
||||
# This file is shared between document-updater and web, so that the server and client share
|
||||
# an identical track changes implementation. Do not edit it directly in web or document-updater,
|
||||
# instead edit it at https://github.com/sharelatex/ranges-tracker, where it has a suite of tests
|
||||
load = () ->
|
||||
class RangesTracker
|
||||
# The purpose of this class is to track a set of inserts and deletes to a document, like
|
||||
# track changes in Word. We store these as a set of ShareJs style ranges:
|
||||
# {i: "foo", p: 42} # Insert 'foo' at offset 42
|
||||
@@ -36,6 +39,7 @@ load = (EventEmitter) ->
|
||||
# middle of a previous insert by the first user, the original insert will be split into two.
|
||||
constructor: (@changes = [], @comments = []) ->
|
||||
@setIdSeed(RangesTracker.generateIdSeed())
|
||||
@resetDirtyState()
|
||||
|
||||
getIdSeed: () ->
|
||||
return @id_seed
|
||||
@@ -75,8 +79,15 @@ load = (EventEmitter) ->
|
||||
comment = @getComment(comment_id)
|
||||
return if !comment?
|
||||
@comments = @comments.filter (c) -> c.id != comment_id
|
||||
@emit "comment:removed", comment
|
||||
@_markAsDirty comment, "comment", "removed"
|
||||
|
||||
moveCommentId: (comment_id, position, text) ->
|
||||
for comment in @comments
|
||||
if comment.id == comment_id
|
||||
comment.op.p = position
|
||||
comment.op.c = text
|
||||
@_markAsDirty comment, "comment", "moved"
|
||||
|
||||
getChange: (change_id) ->
|
||||
change = null
|
||||
for c in @changes
|
||||
@@ -89,6 +100,18 @@ load = (EventEmitter) ->
|
||||
change = @getChange(change_id)
|
||||
return if !change?
|
||||
@_removeChange(change)
|
||||
|
||||
validate: (text) ->
|
||||
for change in @changes
|
||||
if change.op.i?
|
||||
content = text.slice(change.op.p, change.op.p + change.op.i.length)
|
||||
if content != change.op.i
|
||||
throw new Error("Change (#{JSON.stringify(change)}) doesn't match text (#{JSON.stringify(content)})")
|
||||
for comment in @comments
|
||||
content = text.slice(comment.op.p, comment.op.p + comment.op.c.length)
|
||||
if content != comment.op.c
|
||||
throw new Error("Comment (#{JSON.stringify(comment)}) doesn't match text (#{JSON.stringify(content)})")
|
||||
return true
|
||||
|
||||
applyOp: (op, metadata = {}) ->
|
||||
metadata.ts ?= new Date()
|
||||
@@ -103,29 +126,37 @@ load = (EventEmitter) ->
|
||||
@addComment(op, metadata)
|
||||
else
|
||||
throw new Error("unknown op type")
|
||||
|
||||
|
||||
applyOps: (ops, metadata = {}) ->
|
||||
for op in ops
|
||||
@applyOp(op, metadata)
|
||||
|
||||
addComment: (op, metadata) ->
|
||||
# TODO: Don't allow overlapping comments?
|
||||
@comments.push comment = {
|
||||
id: op.t or @newId()
|
||||
op: # Copy because we'll modify in place
|
||||
c: op.c
|
||||
p: op.p
|
||||
t: op.t
|
||||
metadata
|
||||
}
|
||||
@emit "comment:added", comment
|
||||
return comment
|
||||
existing = @getComment(op.t)
|
||||
if existing?
|
||||
@moveCommentId(op.t, op.p, op.c)
|
||||
return existing
|
||||
else
|
||||
@comments.push comment = {
|
||||
id: op.t or @newId()
|
||||
op: # Copy because we'll modify in place
|
||||
c: op.c
|
||||
p: op.p
|
||||
t: op.t
|
||||
metadata
|
||||
}
|
||||
@_markAsDirty comment, "comment", "added"
|
||||
return comment
|
||||
|
||||
applyInsertToComments: (op) ->
|
||||
for comment in @comments
|
||||
if op.p <= comment.op.p
|
||||
comment.op.p += op.i.length
|
||||
@emit "comment:moved", comment
|
||||
@_markAsDirty comment, "comment", "moved"
|
||||
else if op.p < comment.op.p + comment.op.c.length
|
||||
offset = op.p - comment.op.p
|
||||
comment.op.c = comment.op.c[0..(offset-1)] + op.i + comment.op.c[offset...]
|
||||
@emit "comment:moved", comment
|
||||
@_markAsDirty comment, "comment", "moved"
|
||||
|
||||
applyDeleteToComments: (op) ->
|
||||
op_start = op.p
|
||||
@@ -138,7 +169,7 @@ load = (EventEmitter) ->
|
||||
if op_end <= comment_start
|
||||
# delete is fully before comment
|
||||
comment.op.p -= op_length
|
||||
@emit "comment:moved", comment
|
||||
@_markAsDirty comment, "comment", "moved"
|
||||
else if op_start >= comment_end
|
||||
# delete is fully after comment, nothing to do
|
||||
else
|
||||
@@ -161,12 +192,13 @@ load = (EventEmitter) ->
|
||||
|
||||
comment.op.p = Math.min(comment_start, op_start)
|
||||
comment.op.c = remaining_before + remaining_after
|
||||
@emit "comment:moved", comment
|
||||
@_markAsDirty comment, "comment", "moved"
|
||||
|
||||
applyInsertToChanges: (op, metadata) ->
|
||||
op_start = op.p
|
||||
op_length = op.i.length
|
||||
op_end = op.p + op_length
|
||||
undoing = !!op.u
|
||||
|
||||
|
||||
already_merged = false
|
||||
@@ -184,8 +216,9 @@ load = (EventEmitter) ->
|
||||
change.op.p += op_length
|
||||
moved_changes.push change
|
||||
else if op_start == change_start
|
||||
# If the insert matches the start of the delete, just remove it from the delete instead
|
||||
if change.op.d.length >= op.i.length and change.op.d.slice(0, op.i.length) == op.i
|
||||
# If we are undoing, then we want to cancel any existing delete ranges if we can.
|
||||
# Check if the insert matches the start of the delete, and just remove it from the delete instead if so.
|
||||
if undoing and change.op.d.length >= op.i.length and change.op.d.slice(0, op.i.length) == op.i
|
||||
change.op.d = change.op.d.slice(op.i.length)
|
||||
change.op.p += op.i.length
|
||||
if change.op.d == ""
|
||||
@@ -203,15 +236,15 @@ load = (EventEmitter) ->
|
||||
# Only merge inserts if they are from the same user
|
||||
is_same_user = metadata.user_id == change.metadata.user_id
|
||||
|
||||
# If this is an insert op at the end of an existing insert with a delete following, and it cancels out the following
|
||||
# delete then we shouldn't append it to this insert, but instead only cancel the following delete.
|
||||
# If we are undoing, then our changes will be removed from any delete ops just after. In that case, if there is also
|
||||
# an insert op just before, then we shouldn't append it to this insert, but instead only cancel the following delete.
|
||||
# E.g.
|
||||
# foo|<--- about to insert 'b' here
|
||||
# inserted 'foo' --^ ^-- deleted 'bar'
|
||||
# should become just 'foo' not 'foob' (with the delete marker becoming just 'ar'), .
|
||||
next_change = @changes[i+1]
|
||||
is_op_adjacent_to_next_delete = next_change? and next_change.op.d? and op.p == change_end and next_change.op.p == op.p
|
||||
will_op_cancel_next_delete = is_op_adjacent_to_next_delete and next_change.op.d.slice(0, op.i.length) == op.i
|
||||
will_op_cancel_next_delete = undoing and is_op_adjacent_to_next_delete and next_change.op.d.slice(0, op.i.length) == op.i
|
||||
|
||||
# If there is a delete at the start of the insert, and we're inserting
|
||||
# at the start, we SHOULDN'T merge since the delete acts as a partition.
|
||||
@@ -281,8 +314,8 @@ load = (EventEmitter) ->
|
||||
for change in remove_changes
|
||||
@_removeChange change
|
||||
|
||||
if moved_changes.length > 0
|
||||
@emit "changes:moved", moved_changes
|
||||
for change in moved_changes
|
||||
@_markAsDirty change, "change", "moved"
|
||||
|
||||
applyDeleteToChanges: (op, metadata) ->
|
||||
op_start = op.p
|
||||
@@ -406,8 +439,8 @@ load = (EventEmitter) ->
|
||||
@_removeChange change
|
||||
moved_changes = moved_changes.filter (c) -> c != change
|
||||
|
||||
if moved_changes.length > 0
|
||||
@emit "changes:moved", moved_changes
|
||||
for change in moved_changes
|
||||
@_markAsDirty change, "change", "moved"
|
||||
|
||||
_addOp: (op, metadata) ->
|
||||
change = {
|
||||
@@ -427,17 +460,11 @@ load = (EventEmitter) ->
|
||||
else
|
||||
return -1
|
||||
|
||||
if op.d?
|
||||
@emit "delete:added", change
|
||||
else if op.i?
|
||||
@emit "insert:added", change
|
||||
@_markAsDirty(change, "change", "added")
|
||||
|
||||
_removeChange: (change) ->
|
||||
@changes = @changes.filter (c) -> c.id != change.id
|
||||
if change.op.d?
|
||||
@emit "delete:removed", change
|
||||
else if change.op.i?
|
||||
@emit "insert:removed", change
|
||||
@_markAsDirty change, "change", "removed"
|
||||
|
||||
_applyOpModifications: (content, op_modifications) ->
|
||||
# Put in descending position order, with deleting first if at the same offset
|
||||
@@ -486,13 +513,32 @@ load = (EventEmitter) ->
|
||||
previous_change = change
|
||||
return { moved_changes, remove_changes }
|
||||
|
||||
resetDirtyState: () ->
|
||||
@_dirtyState = {
|
||||
comment: {
|
||||
moved: {}
|
||||
removed: {}
|
||||
added: {}
|
||||
}
|
||||
change: {
|
||||
moved: {}
|
||||
removed: {}
|
||||
added: {}
|
||||
}
|
||||
}
|
||||
|
||||
getDirtyState: () ->
|
||||
return @_dirtyState
|
||||
|
||||
_markAsDirty: (object, type, action) ->
|
||||
@_dirtyState[type][action][object.id] = object
|
||||
|
||||
_clone: (object) ->
|
||||
clone = {}
|
||||
(clone[k] = v for k,v of object)
|
||||
return clone
|
||||
|
||||
if define?
|
||||
define ["utils/EventEmitter"], load
|
||||
define [], load
|
||||
else
|
||||
EventEmitter = require("events").EventEmitter
|
||||
module.exports = load(EventEmitter)
|
||||
module.exports = load()
|
||||
|
||||
@@ -27,6 +27,10 @@ define [
|
||||
layoutToLeft: false
|
||||
rendererData: {}
|
||||
loadingThreads: false
|
||||
newAddCommentUI: false # Test new UI for adding comments; remove afterwards.
|
||||
|
||||
if window.location.search.match /new-comments=true/
|
||||
$scope.reviewPanel.newAddCommentUI = true
|
||||
|
||||
window.addEventListener "beforeunload", () ->
|
||||
collapsedStates = {}
|
||||
@@ -163,7 +167,11 @@ define [
|
||||
|
||||
$scope.$watch (() ->
|
||||
entries = $scope.reviewPanel.entries[$scope.editor.open_doc_id] or {}
|
||||
Object.keys(entries).length
|
||||
permEntries = {}
|
||||
for entry, entryData of entries
|
||||
if entry != "add-comment" or !$scope.reviewPanel.newAddCommentUI
|
||||
permEntries[entry] = entryData
|
||||
Object.keys(permEntries).length
|
||||
), (nEntries) ->
|
||||
$scope.reviewPanel.hasEntries = nEntries > 0 and $scope.project.features.trackChangesVisible
|
||||
|
||||
@@ -323,6 +331,10 @@ define [
|
||||
$scope.$broadcast "change:reject", entry_id
|
||||
event_tracking.sendMB "rp-change-rejected", { view: if $scope.ui.reviewPanelOpen then $scope.reviewPanel.subView else 'mini' }
|
||||
|
||||
$scope.addNewComment = () ->
|
||||
$scope.$broadcast "comment:start_adding"
|
||||
$scope.toggleReviewPanel()
|
||||
|
||||
$scope.startNewComment = () ->
|
||||
$scope.$broadcast "comment:select_line"
|
||||
$timeout () ->
|
||||
|
||||
@@ -8,13 +8,16 @@ define [
|
||||
onStartNew: "&"
|
||||
onSubmit: "&"
|
||||
onCancel: "&"
|
||||
onIndicatorClick: "&"
|
||||
onIndicatorClick: "&"
|
||||
layoutToLeft: "="
|
||||
link: (scope, element, attrs) ->
|
||||
scope.state =
|
||||
isAdding: false
|
||||
content: ""
|
||||
|
||||
scope.$on "comment:start_adding", () ->
|
||||
scope.startNewComment()
|
||||
|
||||
scope.startNewComment = () ->
|
||||
scope.state.isAdding = true
|
||||
scope.onStartNew()
|
||||
@@ -31,7 +34,10 @@ define [
|
||||
if scope.state.content.length > 0
|
||||
scope.submitNewComment()
|
||||
|
||||
scope.submitNewComment = () ->
|
||||
scope.submitNewComment = (event) ->
|
||||
# If this is from a blur event from clicking on cancel, ignore it.
|
||||
if event? and event.type == "blur" and $(event.relatedTarget).hasClass("rp-entry-button-cancel")
|
||||
return true
|
||||
scope.onSubmit { content: scope.state.content }
|
||||
scope.state.isAdding = false
|
||||
scope.state.content = ""
|
||||
|
||||
BIN
services/web/public/img/feature-page/courtney-gibbons.jpg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
services/web/public/img/feature-page/feat-accept-poster.jpg
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
services/web/public/img/feature-page/feat-accept.mp4
Normal file
BIN
services/web/public/img/feature-page/feat-changes-poster.jpg
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
services/web/public/img/feature-page/feat-changes.mp4
Normal file
BIN
services/web/public/img/feature-page/feat-discuss-poster.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
services/web/public/img/feature-page/feat-discuss.mp4
Normal file
BIN
services/web/public/img/feature-page/feat-todos-poster.jpg
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
services/web/public/img/feature-page/feat-todos.mp4
Normal file
BIN
services/web/public/img/feature-page/intro-poster.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
services/web/public/img/feature-page/intro.mp4
Normal file
BIN
services/web/public/img/feature-page/joanna-ellis-monaghan.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
services/web/public/img/feature-page/pamela-marcum.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
services/web/public/img/feature-page/user-caltech.png
Normal file
|
After Width: | Height: | Size: 282 KiB |
BIN
services/web/public/img/feature-page/user-chalmers.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
services/web/public/img/feature-page/user-deepmind.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
services/web/public/img/feature-page/user-nasa-jpl.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
services/web/public/img/logo-white@2x.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -1,593 +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 */
|
||||
|
||||
'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
|
||||
});
|
||||
})();
|
||||
40692
services/web/public/js/libs/pdfjs-1.3.91/pdf.worker.js
vendored
@@ -1,593 +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 */
|
||||
|
||||
'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
|
||||
});
|
||||
})();
|
||||
40698
services/web/public/js/libs/pdfjs-1.3.91p1/pdf.worker.js
vendored
43512
services/web/public/js/libs/pdfjs-1.6.210p1/pdf.worker.js
vendored
177
services/web/public/js/libs/pdfjs-1.7.225/LICENSE
Normal file
@@ -0,0 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||