mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge branch 'master' into sk-session-revocation
This commit is contained in:
commit
fc6cf75ad5
27 changed files with 244 additions and 76 deletions
|
@ -17,8 +17,9 @@ module.exports = (grunt) ->
|
||||||
grunt.loadNpmTasks 'grunt-contrib-watch'
|
grunt.loadNpmTasks 'grunt-contrib-watch'
|
||||||
grunt.loadNpmTasks 'grunt-parallel'
|
grunt.loadNpmTasks 'grunt-parallel'
|
||||||
grunt.loadNpmTasks 'grunt-exec'
|
grunt.loadNpmTasks 'grunt-exec'
|
||||||
grunt.loadNpmTasks 'grunt-contrib-imagemin'
|
|
||||||
grunt.loadNpmTasks 'grunt-contrib-cssmin'
|
grunt.loadNpmTasks 'grunt-contrib-cssmin'
|
||||||
|
# grunt.loadNpmTasks 'grunt-contrib-imagemin'
|
||||||
|
# grunt.loadNpmTasks 'grunt-sprity'
|
||||||
|
|
||||||
config =
|
config =
|
||||||
|
|
||||||
|
@ -47,18 +48,26 @@ module.exports = (grunt) ->
|
||||||
stream:true
|
stream:true
|
||||||
|
|
||||||
|
|
||||||
imagemin:
|
# imagemin:
|
||||||
dynamic:
|
# dynamic:
|
||||||
files: [{
|
# files: [{
|
||||||
expand: true
|
# expand: true
|
||||||
cwd: 'public/img/'
|
# cwd: 'public/img/'
|
||||||
src: ['**/*.{png,jpg,gif}']
|
# src: ['**/*.{png,jpg,gif}']
|
||||||
dest: 'public/img/'
|
# dest: 'public/img/'
|
||||||
}]
|
# }]
|
||||||
options:
|
# options:
|
||||||
interlaced:false
|
# interlaced:false
|
||||||
optimizationLevel: 7
|
# optimizationLevel: 7
|
||||||
|
|
||||||
|
# sprity:
|
||||||
|
# sprite:
|
||||||
|
# options:
|
||||||
|
# cssPath:"/img/"
|
||||||
|
# 'style': '../../public/stylesheets/app/sprites.less'
|
||||||
|
# margin: 0
|
||||||
|
# src: ['./public/img/flags/24/*.png']
|
||||||
|
# dest: './public/img/sprite'
|
||||||
|
|
||||||
|
|
||||||
coffee:
|
coffee:
|
||||||
|
@ -220,6 +229,7 @@ module.exports = (grunt) ->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
availabletasks:
|
availabletasks:
|
||||||
tasks:
|
tasks:
|
||||||
options:
|
options:
|
||||||
|
|
|
@ -6,9 +6,17 @@ web-sharelatex is the front-end web service of the open-source web-based collabo
|
||||||
It serves all the HTML pages, CSS and javascript to the client. web-sharelatex also contains
|
It serves all the HTML pages, CSS and javascript to the client. web-sharelatex also contains
|
||||||
a lot of logic around creating and editing projects, and account management.
|
a lot of logic around creating and editing projects, and account management.
|
||||||
|
|
||||||
|
|
||||||
The rest of the ShareLaTeX stack, along with information about contributing can be found in the
|
The rest of the ShareLaTeX stack, along with information about contributing can be found in the
|
||||||
[sharelatex/sharelatex](https://github.com/sharelatex/sharelatex) repository.
|
[sharelatex/sharelatex](https://github.com/sharelatex/sharelatex) repository.
|
||||||
|
|
||||||
|
Build process
|
||||||
|
----------------
|
||||||
|
|
||||||
|
web-sharelatex uses [Grunt](http://gruntjs.com/) to build its front-end related assets.
|
||||||
|
|
||||||
|
Image processing tasks are commented out in the gruntfile and the needed packages aren't presently in the project's `package.json`. If the images need to be processed again (minified and sprited), start by fetching the packages (`npm install grunt-contrib-imagemin grunt-sprity`), then *decomment* the tasks in `Gruntfile.coffee`. After this, the tasks can be called (explicitly, via `grunt imagemin` and `grunt sprity`).
|
||||||
|
|
||||||
Unit test status
|
Unit test status
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ module.exports = BlogController =
|
||||||
|
|
||||||
logger.log url:url, "proxying request to blog api"
|
logger.log url:url, "proxying request to blog api"
|
||||||
request.get blogUrl, (err, r, data)->
|
request.get blogUrl, (err, r, data)->
|
||||||
if r?.statusCode == 404
|
if r?.statusCode == 404 or r?.statusCode == 403
|
||||||
return ErrorController.notFound(req, res, next)
|
return ErrorController.notFound(req, res, next)
|
||||||
if err?
|
if err?
|
||||||
return res.send 500
|
return res.send 500
|
||||||
|
|
|
@ -30,12 +30,17 @@ module.exports = CollaboratorsHandler =
|
||||||
getMembersWithPrivilegeLevels: (project_id, callback = (error, members) ->) ->
|
getMembersWithPrivilegeLevels: (project_id, callback = (error, members) ->) ->
|
||||||
CollaboratorsHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members = []) ->
|
CollaboratorsHandler.getMemberIdsWithPrivilegeLevels project_id, (error, members = []) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
|
result = []
|
||||||
async.mapLimit members, 3,
|
async.mapLimit members, 3,
|
||||||
(member, cb) ->
|
(member, cb) ->
|
||||||
UserGetter.getUser member.id, (error, user) ->
|
UserGetter.getUser member.id, (error, user) ->
|
||||||
return cb(error) if error?
|
return cb(error) if error?
|
||||||
return cb(null, { user: user, privilegeLevel: member.privilegeLevel })
|
if user?
|
||||||
callback
|
result.push { user: user, privilegeLevel: member.privilegeLevel }
|
||||||
|
cb()
|
||||||
|
(error) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
callback null, result
|
||||||
|
|
||||||
getMemberIdPrivilegeLevel: (user_id, project_id, callback = (error, privilegeLevel) ->) ->
|
getMemberIdPrivilegeLevel: (user_id, project_id, callback = (error, privilegeLevel) ->) ->
|
||||||
# In future if the schema changes and getting all member ids is more expensive (multiple documents)
|
# In future if the schema changes and getting all member ids is more expensive (multiple documents)
|
||||||
|
@ -82,6 +87,18 @@ module.exports = CollaboratorsHandler =
|
||||||
logger.error err: err, "problem removing user from project collaberators"
|
logger.error err: err, "problem removing user from project collaberators"
|
||||||
callback(err)
|
callback(err)
|
||||||
|
|
||||||
|
removeUserFromAllProjets: (user_id, callback = (error) ->) ->
|
||||||
|
CollaboratorsHandler.getProjectsUserIsCollaboratorOf user_id, { _id: 1 }, (error, readAndWriteProjects = [], readOnlyProjects = []) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
allProjects = readAndWriteProjects.concat(readOnlyProjects)
|
||||||
|
jobs = []
|
||||||
|
for project in allProjects
|
||||||
|
do (project) ->
|
||||||
|
jobs.push (cb) ->
|
||||||
|
return cb() if !project?
|
||||||
|
CollaboratorsHandler.removeUserFromProject project._id, user_id, cb
|
||||||
|
async.series jobs, callback
|
||||||
|
|
||||||
addEmailToProject: (project_id, adding_user_id, unparsed_email, privilegeLevel, callback = (error, user) ->) ->
|
addEmailToProject: (project_id, adding_user_id, unparsed_email, privilegeLevel, callback = (error, user) ->) ->
|
||||||
emails = mimelib.parseAddresses(unparsed_email)
|
emails = mimelib.parseAddresses(unparsed_email)
|
||||||
email = emails[0]?.address?.toLowerCase()
|
email = emails[0]?.address?.toLowerCase()
|
||||||
|
|
|
@ -29,8 +29,6 @@ module.exports = CompileController =
|
||||||
options.compiler = req.body.compiler
|
options.compiler = req.body.compiler
|
||||||
if req.body?.draft
|
if req.body?.draft
|
||||||
options.draft = req.body.draft
|
options.draft = req.body.draft
|
||||||
if req.query?.isolated is "true"
|
|
||||||
options.isolated = true
|
|
||||||
logger.log {options:options, project_id:project_id, user_id:user_id}, "got compile request"
|
logger.log {options:options, project_id:project_id, user_id:user_id}, "got compile request"
|
||||||
CompileManager.compile project_id, user_id, options, (error, status, outputFiles, clsiServerId, limits, validationProblems) ->
|
CompileManager.compile project_id, user_id, options, (error, status, outputFiles, clsiServerId, limits, validationProblems) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
|
@ -44,17 +42,15 @@ module.exports = CompileController =
|
||||||
}
|
}
|
||||||
|
|
||||||
_compileAsUser: (req, callback) ->
|
_compileAsUser: (req, callback) ->
|
||||||
# callback with user_id if isolated flag is set on request, undefined otherwise
|
# callback with user_id if per-user, undefined otherwise
|
||||||
isolated = req.query?.isolated is "true"
|
if not Settings.disablePerUserCompiles
|
||||||
if isolated
|
|
||||||
AuthenticationController.getLoggedInUserId req, callback # -> (error, user_id)
|
AuthenticationController.getLoggedInUserId req, callback # -> (error, user_id)
|
||||||
else
|
else
|
||||||
callback() # do a per-project compile, not per-user
|
callback() # do a per-project compile, not per-user
|
||||||
|
|
||||||
_downloadAsUser: (req, callback) ->
|
_downloadAsUser: (req, callback) ->
|
||||||
# callback with user_id if isolated flag or user_id param is set on request, undefined otherwise
|
# callback with user_id if per-user, undefined otherwise
|
||||||
isolated = req.query?.isolated is "true" or req.params.user_id?
|
if not Settings.disablePerUserCompiles
|
||||||
if isolated
|
|
||||||
AuthenticationController.getLoggedInUserId req, callback # -> (error, user_id)
|
AuthenticationController.getLoggedInUserId req, callback # -> (error, user_id)
|
||||||
else
|
else
|
||||||
callback() # do a per-project compile, not per-user
|
callback() # do a per-project compile, not per-user
|
||||||
|
|
|
@ -38,7 +38,7 @@ module.exports = CompileManager =
|
||||||
for key, value of limits
|
for key, value of limits
|
||||||
options[key] = value
|
options[key] = value
|
||||||
# only pass user_id down to clsi if this is a per-user compile
|
# only pass user_id down to clsi if this is a per-user compile
|
||||||
compileAsUser = if options.isolated then user_id else undefined
|
compileAsUser = if Settings.disablePerUserCompiles then undefined else user_id
|
||||||
ClsiManager.sendRequest project_id, compileAsUser, options, (error, status, outputFiles, clsiServerId, validationProblems) ->
|
ClsiManager.sendRequest project_id, compileAsUser, options, (error, status, outputFiles, clsiServerId, validationProblems) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
logger.log files: outputFiles, "output files"
|
logger.log files: outputFiles, "output files"
|
||||||
|
|
|
@ -3,6 +3,8 @@ PersonalEmailLayout = require("./Layouts/PersonalEmailLayout")
|
||||||
NotificationEmailLayout = require("./Layouts/NotificationEmailLayout")
|
NotificationEmailLayout = require("./Layouts/NotificationEmailLayout")
|
||||||
settings = require("settings-sharelatex")
|
settings = require("settings-sharelatex")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
templates = {}
|
templates = {}
|
||||||
|
|
||||||
templates.registered =
|
templates.registered =
|
||||||
|
@ -114,6 +116,8 @@ module.exports =
|
||||||
template = templates[templateName]
|
template = templates[templateName]
|
||||||
opts.siteUrl = settings.siteUrl
|
opts.siteUrl = settings.siteUrl
|
||||||
opts.body = template.compiledTemplate(opts)
|
opts.body = template.compiledTemplate(opts)
|
||||||
|
if settings.email?.templates?.customFooter?
|
||||||
|
opts.body += settings.email?.templates?.customFooter
|
||||||
return {
|
return {
|
||||||
subject : template.subject(opts)
|
subject : template.subject(opts)
|
||||||
html: template.layout(opts)
|
html: template.layout(opts)
|
||||||
|
|
|
@ -6,7 +6,7 @@ oneSecond = 1000
|
||||||
|
|
||||||
makeRequest = (opts, callback)->
|
makeRequest = (opts, callback)->
|
||||||
if !settings.apis.notifications?.url?
|
if !settings.apis.notifications?.url?
|
||||||
return callback()
|
return callback(null, statusCode:200)
|
||||||
else
|
else
|
||||||
request(opts, callback)
|
request(opts, callback)
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ module.exports =
|
||||||
json: true
|
json: true
|
||||||
timeout: oneSecond
|
timeout: oneSecond
|
||||||
method: "GET"
|
method: "GET"
|
||||||
request opts, (err, res, unreadNotifications)->
|
makeRequest opts, (err, res, unreadNotifications)->
|
||||||
statusCode = if res? then res.statusCode else 500
|
statusCode = if res? then res.statusCode else 500
|
||||||
if err? or statusCode != 200
|
if err? or statusCode != 200
|
||||||
e = new Error("something went wrong getting notifications, #{err}, #{statusCode}")
|
e = new Error("something went wrong getting notifications, #{err}, #{statusCode}")
|
||||||
|
@ -40,7 +40,7 @@ module.exports =
|
||||||
templateKey:templateKey
|
templateKey:templateKey
|
||||||
}
|
}
|
||||||
logger.log opts:opts, "creating notification for user"
|
logger.log opts:opts, "creating notification for user"
|
||||||
request opts, callback
|
makeRequest opts, callback
|
||||||
|
|
||||||
markAsReadWithKey: (user_id, key, callback)->
|
markAsReadWithKey: (user_id, key, callback)->
|
||||||
opts =
|
opts =
|
||||||
|
@ -51,7 +51,7 @@ module.exports =
|
||||||
key:key
|
key:key
|
||||||
}
|
}
|
||||||
logger.log user_id:user_id, key:key, "sending mark notification as read with key to notifications api"
|
logger.log user_id:user_id, key:key, "sending mark notification as read with key to notifications api"
|
||||||
request opts, callback
|
makeRequest opts, callback
|
||||||
|
|
||||||
|
|
||||||
markAsRead: (user_id, notification_id, callback)->
|
markAsRead: (user_id, notification_id, callback)->
|
||||||
|
@ -60,4 +60,4 @@ module.exports =
|
||||||
uri: "#{settings.apis.notifications?.url}/user/#{user_id}/notification/#{notification_id}"
|
uri: "#{settings.apis.notifications?.url}/user/#{user_id}/notification/#{notification_id}"
|
||||||
timeout:oneSecond
|
timeout:oneSecond
|
||||||
logger.log user_id:user_id, notification_id:notification_id, "sending mark notification as read to notifications api"
|
logger.log user_id:user_id, notification_id:notification_id, "sending mark notification as read to notifications api"
|
||||||
request opts, callback
|
makeRequest opts, callback
|
||||||
|
|
|
@ -24,9 +24,11 @@ module.exports = ProjectDeleter =
|
||||||
update = {deletedByExternalDataSource: false}
|
update = {deletedByExternalDataSource: false}
|
||||||
Project.update conditions, update, {}, callback
|
Project.update conditions, update, {}, callback
|
||||||
|
|
||||||
deleteUsersProjects: (owner_id, callback)->
|
deleteUsersProjects: (user_id, callback)->
|
||||||
logger.log owner_id:owner_id, "deleting users projects"
|
logger.log {user_id}, "deleting users projects"
|
||||||
Project.remove owner_ref:owner_id, callback
|
Project.remove owner_ref:user_id, (error) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
CollaboratorsHandler.removeUserFromAllProjets user_id, callback
|
||||||
|
|
||||||
deleteProject: (project_id, callback = (error) ->) ->
|
deleteProject: (project_id, callback = (error) ->) ->
|
||||||
# archiveProject takes care of the clean-up
|
# archiveProject takes care of the clean-up
|
||||||
|
|
|
@ -15,7 +15,7 @@ footer.site-footer
|
||||||
aria-expanded="false",
|
aria-expanded="false",
|
||||||
tooltip="#{translate('language')}"
|
tooltip="#{translate('language')}"
|
||||||
)
|
)
|
||||||
img(src="/img/flags/24/#{currentLngCode}.png")
|
figure(class="sprite-icon sprite-icon-lang sprite-icon-#{currentLngCode}")
|
||||||
|
|
||||||
ul.dropdown-menu(role="menu")
|
ul.dropdown-menu(role="menu")
|
||||||
li.dropdown-header #{translate("language")}
|
li.dropdown-header #{translate("language")}
|
||||||
|
@ -23,9 +23,9 @@ footer.site-footer
|
||||||
if !subdomainDetails.hide
|
if !subdomainDetails.hide
|
||||||
li.lngOption
|
li.lngOption
|
||||||
a.menu-indent(href=subdomainDetails.url+currentUrl)
|
a.menu-indent(href=subdomainDetails.url+currentUrl)
|
||||||
img(src="/img/flags/24/#{subdomainDetails.lngCode}.png")
|
figure(class="sprite-icon sprite-icon-lang sprite-icon-#{subdomainDetails.lngCode}")
|
||||||
| #{translate(subdomainDetails.lngCode)}
|
| #{translate(subdomainDetails.lngCode)}
|
||||||
|
//- img(src="/img/flags/24/.png")
|
||||||
each item in nav.left_footer
|
each item in nav.left_footer
|
||||||
li
|
li
|
||||||
if item.url
|
if item.url
|
||||||
|
|
|
@ -128,12 +128,12 @@ div.full-size.pdf(ng-controller="PdfController")
|
||||||
)
|
)
|
||||||
label.card-hint-feedback-label #{translate("log_hint_feedback_label")}
|
label.card-hint-feedback-label #{translate("log_hint_feedback_label")}
|
||||||
a.card-hint-feedback-positive(
|
a.card-hint-feedback-positive(
|
||||||
ng-click="feedbackSent = true;"
|
ng-click="trackLogHintsPositiveFeedback(entry.ruleId); feedbackSent = true;"
|
||||||
href
|
href
|
||||||
) #{translate("answer_yes")}
|
) #{translate("answer_yes")}
|
||||||
span /
|
span /
|
||||||
a.card-hint-feedback-negative(
|
a.card-hint-feedback-negative(
|
||||||
ng-click="feedbackSent = true;"
|
ng-click="trackLogHintsNegativeFeedback(entry.ruleId); feedbackSent = true;"
|
||||||
href
|
href
|
||||||
) #{translate("answer_no")}
|
) #{translate("answer_no")}
|
||||||
.card-hint-feedback(ng-show="feedbackSent")
|
.card-hint-feedback(ng-show="feedbackSent")
|
||||||
|
|
|
@ -263,6 +263,10 @@ module.exports = settings =
|
||||||
# public projects, /learn, /templates, about pages, etc.
|
# public projects, /learn, /templates, about pages, etc.
|
||||||
allowPublicAccess: if process.env["SHARELATEX_ALLOW_PUBLIC_ACCESS"] == 'true' then true else false
|
allowPublicAccess: if process.env["SHARELATEX_ALLOW_PUBLIC_ACCESS"] == 'true' then true else false
|
||||||
|
|
||||||
|
# Use a single compile directory for all users in a project
|
||||||
|
# (otherwise each user has their own directory)
|
||||||
|
# disablePerUserCompiles: true
|
||||||
|
|
||||||
# Maximum size of text documents in the real-time editing system.
|
# Maximum size of text documents in the real-time editing system.
|
||||||
max_doc_length: 2 * 1024 * 1024 # 2mb
|
max_doc_length: 2 * 1024 * 1024 # 2mb
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,6 @@
|
||||||
"grunt-contrib-clean": "0.5.0",
|
"grunt-contrib-clean": "0.5.0",
|
||||||
"grunt-contrib-coffee": "0.10.0",
|
"grunt-contrib-coffee": "0.10.0",
|
||||||
"grunt-contrib-cssmin": "^1.0.1",
|
"grunt-contrib-cssmin": "^1.0.1",
|
||||||
"grunt-contrib-imagemin": "^1.0.1",
|
|
||||||
"grunt-contrib-less": "0.9.0",
|
"grunt-contrib-less": "0.9.0",
|
||||||
"grunt-contrib-requirejs": "0.4.1",
|
"grunt-contrib-requirejs": "0.4.1",
|
||||||
"grunt-contrib-watch": "^1.0.0",
|
"grunt-contrib-watch": "^1.0.0",
|
||||||
|
|
|
@ -12,7 +12,8 @@ define [
|
||||||
ruleDetails = _getRule entry.message
|
ruleDetails = _getRule entry.message
|
||||||
|
|
||||||
if (ruleDetails?)
|
if (ruleDetails?)
|
||||||
entry.ruleId = 'hint_' + ruleDetails.regexToMatch.toString().replace(/[^a-zA-Z0-9]/g, '_').toLowerCase() if ruleDetails.regexToMatch?
|
entry.ruleId = 'hint_' + ruleDetails.regexToMatch.toString().replace(/\s/g, '_').slice(1, -1) if ruleDetails.regexToMatch?
|
||||||
|
|
||||||
entry.humanReadableHint = ruleDetails.humanReadableHint if ruleDetails.humanReadableHint?
|
entry.humanReadableHint = ruleDetails.humanReadableHint if ruleDetails.humanReadableHint?
|
||||||
entry.extraInfoURL = ruleDetails.extraInfoURL if ruleDetails.extraInfoURL?
|
entry.extraInfoURL = ruleDetails.extraInfoURL if ruleDetails.extraInfoURL?
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,13 @@ define [
|
||||||
$scope.shouldShowLogs = false
|
$scope.shouldShowLogs = false
|
||||||
$scope.wikiEnabled = window.wikiEnabled;
|
$scope.wikiEnabled = window.wikiEnabled;
|
||||||
|
|
||||||
|
# log hints tracking
|
||||||
|
trackLogHintsFeedback = (isPositive, hintId) ->
|
||||||
|
event_tracking.send 'log-hints', (if isPositive then 'feedback-positive' else 'feedback-negative'), hintId
|
||||||
|
|
||||||
|
$scope.trackLogHintsPositiveFeedback = (hintId) -> trackLogHintsFeedback true, hintId
|
||||||
|
$scope.trackLogHintsNegativeFeedback = (hintId) -> trackLogHintsFeedback false, hintId
|
||||||
|
|
||||||
if ace.require("ace/lib/useragent").isMac
|
if ace.require("ace/lib/useragent").isMac
|
||||||
$scope.modifierKey = "Cmd"
|
$scope.modifierKey = "Cmd"
|
||||||
else
|
else
|
||||||
|
@ -50,8 +57,6 @@ define [
|
||||||
params = {}
|
params = {}
|
||||||
if options.isAutoCompile
|
if options.isAutoCompile
|
||||||
params["auto_compile"]=true
|
params["auto_compile"]=true
|
||||||
if perUserCompile # send ?isolated=true for per-user compiles
|
|
||||||
params["isolated"] = true
|
|
||||||
return $http.post url, {
|
return $http.post url, {
|
||||||
rootDoc_id: options.rootDocOverride_id or null
|
rootDoc_id: options.rootDocOverride_id or null
|
||||||
draft: $scope.draft
|
draft: $scope.draft
|
||||||
|
@ -125,9 +130,6 @@ define [
|
||||||
# convert the qs hash into a query string and append it
|
# convert the qs hash into a query string and append it
|
||||||
$scope.pdf.qs = createQueryString qs
|
$scope.pdf.qs = createQueryString qs
|
||||||
$scope.pdf.url += $scope.pdf.qs
|
$scope.pdf.url += $scope.pdf.qs
|
||||||
# special case for the download url
|
|
||||||
if perUserCompile
|
|
||||||
qs.isolated = true
|
|
||||||
# Save all downloads as files
|
# Save all downloads as files
|
||||||
qs.popupDownload = true
|
qs.popupDownload = true
|
||||||
$scope.pdf.downloadUrl = "/project/#{$scope.project_id}/output/output.pdf" + createQueryString(qs)
|
$scope.pdf.downloadUrl = "/project/#{$scope.project_id}/output/output.pdf" + createQueryString(qs)
|
||||||
|
@ -147,8 +149,6 @@ define [
|
||||||
else
|
else
|
||||||
file.name = file.path
|
file.name = file.path
|
||||||
qs = {}
|
qs = {}
|
||||||
if perUserCompile
|
|
||||||
qs.isolated = true
|
|
||||||
if response.clsiServerId?
|
if response.clsiServerId?
|
||||||
qs.clsiserverid = response.clsiServerId
|
qs.clsiserverid = response.clsiServerId
|
||||||
file.url = "/project/#{project_id}/output/#{file.path}" + createQueryString qs
|
file.url = "/project/#{project_id}/output/#{file.path}" + createQueryString qs
|
||||||
|
@ -237,7 +237,7 @@ define [
|
||||||
return null
|
return null
|
||||||
|
|
||||||
normalizeFilePath = (path) ->
|
normalizeFilePath = (path) ->
|
||||||
path = path.replace(/^(.*)\/compiles\/[0-9a-f]{24}\/(\.\/)?/, "")
|
path = path.replace(/^(.*)\/compiles\/[0-9a-f]{24}(-[0-9a-f]{24})?\/(\.\/)?/, "")
|
||||||
path = path.replace(/^\/compile\//, "")
|
path = path.replace(/^\/compile\//, "")
|
||||||
|
|
||||||
rootDocDirname = ide.fileTreeManager.getRootDocDirname()
|
rootDocDirname = ide.fileTreeManager.getRootDocDirname()
|
||||||
|
@ -274,7 +274,6 @@ define [
|
||||||
method: "DELETE"
|
method: "DELETE"
|
||||||
params:
|
params:
|
||||||
clsiserverid:ide.clsiServerId
|
clsiserverid:ide.clsiServerId
|
||||||
isolated: perUserCompile
|
|
||||||
headers:
|
headers:
|
||||||
"X-Csrf-Token": window.csrfToken
|
"X-Csrf-Token": window.csrfToken
|
||||||
}
|
}
|
||||||
|
@ -361,7 +360,6 @@ define [
|
||||||
line: row + 1
|
line: row + 1
|
||||||
column: column
|
column: column
|
||||||
clsiserverid:ide.clsiServerId
|
clsiserverid:ide.clsiServerId
|
||||||
isolated: perUserCompile
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.success (data) ->
|
.success (data) ->
|
||||||
|
@ -407,7 +405,6 @@ define [
|
||||||
h: h.toFixed(2)
|
h: h.toFixed(2)
|
||||||
v: v.toFixed(2)
|
v: v.toFixed(2)
|
||||||
clsiserverid:ide.clsiServerId
|
clsiserverid:ide.clsiServerId
|
||||||
isolated: perUserCompile
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.success (data) ->
|
.success (data) ->
|
||||||
|
|
|
@ -12,7 +12,8 @@ define [
|
||||||
|
|
||||||
constructor: (@url, @options) ->
|
constructor: (@url, @options) ->
|
||||||
# PDFJS.disableFontFace = true # avoids repaints, uses worker more
|
# PDFJS.disableFontFace = true # avoids repaints, uses worker more
|
||||||
# PDFJS.disableAutoFetch = true # enable this to prevent loading whole file
|
if @options.disableAutoFetch
|
||||||
|
PDFJS.disableAutoFetch = true # prevent loading whole file
|
||||||
# PDFJS.disableStream
|
# PDFJS.disableStream
|
||||||
# PDFJS.disableRange
|
# PDFJS.disableRange
|
||||||
@scale = @options.scale || 1
|
@scale = @options.scale || 1
|
||||||
|
|
|
@ -27,13 +27,21 @@ define [
|
||||||
$scope.document.destroy() if $scope.document?
|
$scope.document.destroy() if $scope.document?
|
||||||
$scope.loadCount = if $scope.loadCount? then $scope.loadCount + 1 else 1
|
$scope.loadCount = if $scope.loadCount? then $scope.loadCount + 1 else 1
|
||||||
# TODO need a proper url manipulation library to add to query string
|
# TODO need a proper url manipulation library to add to query string
|
||||||
$scope.document = new PDFRenderer($scope.pdfSrc + '&pdfng=true' , {
|
url = $scope.pdfSrc
|
||||||
|
# add 'pdfng=true' to show that we are using the angular pdfjs viewer
|
||||||
|
queryStringExists = url.match(/\?/)
|
||||||
|
url = url + (if not queryStringExists then '?' else '&') + 'pdfng=true'
|
||||||
|
# for isolated compiles, load the pdf on-demand because nobody will overwrite it
|
||||||
|
onDemandLoading = window.location?.search?.match(/isolated=true/)?
|
||||||
|
$scope.document = new PDFRenderer(url, {
|
||||||
scale: 1,
|
scale: 1,
|
||||||
|
disableAutoFetch: if onDemandLoading then true else undefined
|
||||||
navigateFn: (ref) ->
|
navigateFn: (ref) ->
|
||||||
# this function captures clicks on the annotation links
|
# this function captures clicks on the annotation links
|
||||||
$scope.navigateTo = ref
|
$scope.navigateTo = ref
|
||||||
$scope.$apply()
|
$scope.$apply()
|
||||||
progressCallback: (progress) ->
|
progressCallback: (progress) ->
|
||||||
|
return if onDemandLoading is true # don't show progress for on-demand page loading
|
||||||
$scope.$emit 'progress', progress
|
$scope.$emit 'progress', progress
|
||||||
loadedCallback: () ->
|
loadedCallback: () ->
|
||||||
$scope.$emit 'loaded'
|
$scope.$emit 'loaded'
|
||||||
|
|
|
@ -5,15 +5,11 @@ define [
|
||||||
$scope.status =
|
$scope.status =
|
||||||
loading:true
|
loading:true
|
||||||
|
|
||||||
# enable per-user containers by default
|
|
||||||
perUserCompile = true
|
|
||||||
|
|
||||||
opts =
|
opts =
|
||||||
url:"/project/#{ide.project_id}/wordcount"
|
url:"/project/#{ide.project_id}/wordcount"
|
||||||
method:"GET"
|
method:"GET"
|
||||||
params:
|
params:
|
||||||
clsiserverid:ide.clsiServerId
|
clsiserverid:ide.clsiServerId
|
||||||
isolated: perUserCompile
|
|
||||||
$http opts
|
$http opts
|
||||||
.success (data) ->
|
.success (data) ->
|
||||||
$scope.status.loading = false
|
$scope.status.loading = false
|
||||||
|
|
BIN
services/web/public/img/sprite.png
Normal file
BIN
services/web/public/img/sprite.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
105
services/web/public/stylesheets/app/sprites.less
Normal file
105
services/web/public/stylesheets/app/sprites.less
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
|
||||||
|
.sprite-icon {
|
||||||
|
background-image: url('/img/sprite.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.sprite-icon-ko {
|
||||||
|
background-position: -0px -0px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-cn {
|
||||||
|
background-position: -0px -24px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-da {
|
||||||
|
background-position: -0px -48px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-de {
|
||||||
|
background-position: -0px -72px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-en {
|
||||||
|
background-position: -0px -96px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-es {
|
||||||
|
background-position: -0px -120px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-fi {
|
||||||
|
background-position: -0px -144px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-fr {
|
||||||
|
background-position: -0px -168px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-it {
|
||||||
|
background-position: -0px -192px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-ja {
|
||||||
|
background-position: -0px -216px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-cs {
|
||||||
|
background-position: -0px -240px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-nl {
|
||||||
|
background-position: -0px -264px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-no {
|
||||||
|
background-position: -0px -288px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-pl {
|
||||||
|
background-position: -0px -312px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-pt {
|
||||||
|
background-position: -0px -336px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-ru {
|
||||||
|
background-position: -0px -360px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-sv {
|
||||||
|
background-position: -0px -384px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-tr {
|
||||||
|
background-position: -0px -408px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-uk {
|
||||||
|
background-position: -0px -432px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.sprite-icon-zh-CN {
|
||||||
|
background-position: -0px -456px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
|
@ -41,11 +41,6 @@
|
||||||
.example {
|
.example {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
||||||
& > div {
|
|
||||||
display: block !important;
|
|
||||||
width: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code {
|
.code {
|
||||||
pre {
|
pre {
|
||||||
background-color: @gray-lightest;
|
background-color: @gray-lightest;
|
||||||
|
@ -60,9 +55,9 @@
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: auto !important;
|
width: auto;
|
||||||
height: auto !important;
|
height: auto;
|
||||||
max-width: 100% !important;
|
max-width: 100%;
|
||||||
box-shadow: 0 1px 3px @gray-light;
|
box-shadow: 0 1px 3px @gray-light;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,3 +23,8 @@ footer.site-footer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sprite-icon-lang {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
|
@ -73,3 +73,4 @@
|
||||||
@import "app/wiki.less";
|
@import "app/wiki.less";
|
||||||
@import "app/translations.less";
|
@import "app/translations.less";
|
||||||
@import "app/contact-us.less";
|
@import "app/contact-us.less";
|
||||||
|
@import "app/sprites.less";
|
||||||
|
|
|
@ -77,17 +77,19 @@ describe "CollaboratorsHandler", ->
|
||||||
{ id: "read-only-ref-2", privilegeLevel: "readOnly" }
|
{ id: "read-only-ref-2", privilegeLevel: "readOnly" }
|
||||||
{ id: "read-write-ref-1", privilegeLevel: "readAndWrite" }
|
{ id: "read-write-ref-1", privilegeLevel: "readAndWrite" }
|
||||||
{ id: "read-write-ref-2", privilegeLevel: "readAndWrite" }
|
{ id: "read-write-ref-2", privilegeLevel: "readAndWrite" }
|
||||||
|
{ id: "doesnt-exist", privilegeLevel: "readAndWrite" }
|
||||||
])
|
])
|
||||||
@UserGetter.getUser = sinon.stub()
|
@UserGetter.getUser = sinon.stub()
|
||||||
@UserGetter.getUser.withArgs("read-only-ref-1").yields(null, { _id: "read-only-ref-1" })
|
@UserGetter.getUser.withArgs("read-only-ref-1").yields(null, { _id: "read-only-ref-1" })
|
||||||
@UserGetter.getUser.withArgs("read-only-ref-2").yields(null, { _id: "read-only-ref-2" })
|
@UserGetter.getUser.withArgs("read-only-ref-2").yields(null, { _id: "read-only-ref-2" })
|
||||||
@UserGetter.getUser.withArgs("read-write-ref-1").yields(null, { _id: "read-write-ref-1" })
|
@UserGetter.getUser.withArgs("read-write-ref-1").yields(null, { _id: "read-write-ref-1" })
|
||||||
@UserGetter.getUser.withArgs("read-write-ref-2").yields(null, { _id: "read-write-ref-2" })
|
@UserGetter.getUser.withArgs("read-write-ref-2").yields(null, { _id: "read-write-ref-2" })
|
||||||
|
@UserGetter.getUser.withArgs("doesnt-exist").yields(null, null)
|
||||||
@CollaboratorHandler.getMembersWithPrivilegeLevels @project_id, @callback
|
@CollaboratorHandler.getMembersWithPrivilegeLevels @project_id, @callback
|
||||||
|
|
||||||
it "should return an array of members with their privilege levels", ->
|
it "should return an array of members with their privilege levels", ->
|
||||||
@callback
|
@callback
|
||||||
.calledWith(undefined, [
|
.calledWith(null, [
|
||||||
{ user: { _id: "read-only-ref-1" }, privilegeLevel: "readOnly" }
|
{ user: { _id: "read-only-ref-1" }, privilegeLevel: "readOnly" }
|
||||||
{ user: { _id: "read-only-ref-2" }, privilegeLevel: "readOnly" }
|
{ user: { _id: "read-only-ref-2" }, privilegeLevel: "readOnly" }
|
||||||
{ user: { _id: "read-write-ref-1" }, privilegeLevel: "readAndWrite" }
|
{ user: { _id: "read-write-ref-1" }, privilegeLevel: "readAndWrite" }
|
||||||
|
@ -274,6 +276,19 @@ describe "CollaboratorsHandler", ->
|
||||||
it "should not add any users to the proejct", ->
|
it "should not add any users to the proejct", ->
|
||||||
@CollaboratorHandler.addUserIdToProject.called.should.equal false
|
@CollaboratorHandler.addUserIdToProject.called.should.equal false
|
||||||
|
|
||||||
|
describe "removeUserFromAllProjects", ->
|
||||||
|
beforeEach (done) ->
|
||||||
|
@CollaboratorHandler.getProjectsUserIsCollaboratorOf = sinon.stub()
|
||||||
|
@CollaboratorHandler.getProjectsUserIsCollaboratorOf.withArgs(@user_id, { _id: 1 }).yields(
|
||||||
|
null,
|
||||||
|
[ { _id: "read-and-write-0" }, { _id: "read-and-write-1" }, null ],
|
||||||
|
[ { _id: "read-only-0" }, { _id: "read-only-1" }, null ]
|
||||||
|
)
|
||||||
|
@CollaboratorHandler.removeUserFromProject = sinon.stub().yields()
|
||||||
|
@CollaboratorHandler.removeUserFromAllProjets @user_id, done
|
||||||
|
|
||||||
|
it "should remove the user from each project", ->
|
||||||
|
for project_id in ["read-and-write-0", "read-and-write-1", "read-only-0", "read-only-1"]
|
||||||
|
@CollaboratorHandler.removeUserFromProject
|
||||||
|
.calledWith(project_id, @user_id)
|
||||||
|
.should.equal true
|
|
@ -139,7 +139,7 @@ describe "CompileController", ->
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should proxy the PDF from the CLSI", ->
|
it "should proxy the PDF from the CLSI", ->
|
||||||
@CompileController.proxyToClsi.calledWith(@project_id, "/project/#{@project_id}/output/output.pdf", @req, @res, @next).should.equal true
|
@CompileController.proxyToClsi.calledWith(@project_id, "/project/#{@project_id}/user/#{@user_id}/output/output.pdf", @req, @res, @next).should.equal true
|
||||||
|
|
||||||
describe "when the pdf is not going to be used in pdfjs viewer", ->
|
describe "when the pdf is not going to be used in pdfjs viewer", ->
|
||||||
|
|
||||||
|
@ -338,8 +338,6 @@ describe "CompileController", ->
|
||||||
@req =
|
@req =
|
||||||
params:
|
params:
|
||||||
project_id:@project_id
|
project_id:@project_id
|
||||||
query:
|
|
||||||
isolated: "true"
|
|
||||||
@CompileManager.compile.callsArgWith(3)
|
@CompileManager.compile.callsArgWith(3)
|
||||||
@CompileController.proxyToClsi = sinon.stub()
|
@CompileController.proxyToClsi = sinon.stub()
|
||||||
@res =
|
@res =
|
||||||
|
@ -362,8 +360,6 @@ describe "CompileController", ->
|
||||||
@CompileManager.wordCount = sinon.stub().callsArgWith(3, null, {content:"body"})
|
@CompileManager.wordCount = sinon.stub().callsArgWith(3, null, {content:"body"})
|
||||||
@req.params =
|
@req.params =
|
||||||
Project_id: @project_id
|
Project_id: @project_id
|
||||||
@req.query =
|
|
||||||
isolated: "true"
|
|
||||||
@res.send = sinon.stub()
|
@res.send = sinon.stub()
|
||||||
@res.contentType = sinon.stub()
|
@res.contentType = sinon.stub()
|
||||||
@CompileController.wordCount @req, @res, @next
|
@CompileController.wordCount @req, @res, @next
|
||||||
|
|
|
@ -71,7 +71,7 @@ describe "CompileManager", ->
|
||||||
|
|
||||||
it "should run the compile with the compile limits", ->
|
it "should run the compile with the compile limits", ->
|
||||||
@ClsiManager.sendRequest
|
@ClsiManager.sendRequest
|
||||||
.calledWith(@project_id, undefined, {
|
.calledWith(@project_id, @user_id, {
|
||||||
timeout: @limits.timeout
|
timeout: @limits.timeout
|
||||||
})
|
})
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
|
@ -27,13 +27,15 @@ describe 'ProjectDeleter', ->
|
||||||
removeProjectFromAllTags: sinon.stub().callsArgWith(2)
|
removeProjectFromAllTags: sinon.stub().callsArgWith(2)
|
||||||
@ProjectGetter =
|
@ProjectGetter =
|
||||||
getProject:sinon.stub()
|
getProject:sinon.stub()
|
||||||
|
@CollaboratorsHandler =
|
||||||
|
removeUserFromAllProjets: sinon.stub().yields()
|
||||||
@deleter = SandboxedModule.require modulePath, requires:
|
@deleter = SandboxedModule.require modulePath, requires:
|
||||||
"../Editor/EditorController": @editorController
|
"../Editor/EditorController": @editorController
|
||||||
'../../models/Project':{Project:@Project}
|
'../../models/Project':{Project:@Project}
|
||||||
'../DocumentUpdater/DocumentUpdaterHandler': @documentUpdaterHandler
|
'../DocumentUpdater/DocumentUpdaterHandler': @documentUpdaterHandler
|
||||||
"../Tags/TagsHandler":@TagsHandler
|
"../Tags/TagsHandler":@TagsHandler
|
||||||
"../FileStore/FileStoreHandler": @FileStoreHandler = {}
|
"../FileStore/FileStoreHandler": @FileStoreHandler = {}
|
||||||
"../Collaborators/CollaboratorsHandler": @CollaboratorsHandler = {}
|
"../Collaborators/CollaboratorsHandler": @CollaboratorsHandler
|
||||||
"./ProjectGetter": @ProjectGetter
|
"./ProjectGetter": @ProjectGetter
|
||||||
'logger-sharelatex':
|
'logger-sharelatex':
|
||||||
log:->
|
log:->
|
||||||
|
@ -74,6 +76,12 @@ describe 'ProjectDeleter', ->
|
||||||
@Project.remove.calledWith(owner_ref:user_id).should.equal true
|
@Project.remove.calledWith(owner_ref:user_id).should.equal true
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
it "should remove all the projects the user is a collaborator of", (done)->
|
||||||
|
user_id = 1234
|
||||||
|
@deleter.deleteUsersProjects user_id, =>
|
||||||
|
@CollaboratorsHandler.removeUserFromAllProjets.calledWith(user_id).should.equal true
|
||||||
|
done()
|
||||||
|
|
||||||
describe "deleteProject", ->
|
describe "deleteProject", ->
|
||||||
beforeEach (done) ->
|
beforeEach (done) ->
|
||||||
@project_id = "mock-project-id-123"
|
@project_id = "mock-project-id-123"
|
||||||
|
|
Loading…
Reference in a new issue