diff --git a/services/web/.gitignore b/services/web/.gitignore
index d7b110e28d..9c2a272a7a 100644
--- a/services/web/.gitignore
+++ b/services/web/.gitignore
@@ -60,6 +60,7 @@ public/js/services/
public/js/utils/
public/stylesheets/style.css
+public/stylesheets/ol-style.css
public/brand/plans.css
public/minjs/
diff --git a/services/web/Gruntfile.coffee b/services/web/Gruntfile.coffee
index 45a6528cdd..479190332f 100644
--- a/services/web/Gruntfile.coffee
+++ b/services/web/Gruntfile.coffee
@@ -139,6 +139,9 @@ module.exports = (grunt) ->
app:
files:
"public/stylesheets/style.css": "public/stylesheets/style.less"
+ ol:
+ files:
+ "public/stylesheets/ol-style.css": "public/stylesheets/ol-style.less"
postcss:
options:
diff --git a/services/web/Jenkinsfile b/services/web/Jenkinsfile
new file mode 100644
index 0000000000..bee222f13b
--- /dev/null
+++ b/services/web/Jenkinsfile
@@ -0,0 +1,116 @@
+pipeline {
+
+ agent {
+ docker {
+ image 'node:6.9.5'
+ args "-v /var/lib/jenkins/.npm:/tmp/.npm"
+ }
+ }
+
+ environment {
+ HOME = "/tmp"
+ }
+
+ triggers {
+ pollSCM('* * * * *')
+ cron('@daily')
+ }
+
+ stages {
+ stage('Set up') {
+ steps {
+ // we need to disable logallrefupdates, else git clones during the npm install will require git to lookup the user id
+ // which does not exist in the container's /etc/passwd file, causing the clone to fail.
+ sh 'git config --global core.logallrefupdates false'
+
+ sh 'rm -rf node_modules/*'
+ }
+ }
+
+ stage('Clone Dependencies') {
+ steps {
+ sh 'rm -rf public/brand modules'
+ checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'public/brand'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/brand-sharelatex']]])
+ checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'app/views/external'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/external-pages-sharelatex']]])
+ checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/web-sharelatex-modules']]])
+ checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/admin-panel'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/admin-panel']]])
+ checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/groovehq'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@bitbucket.org:sharelatex/groovehq']]])
+ checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/references-search'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@bitbucket.org:sharelatex/references-search.git']]])
+ checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/tpr-webmodule'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/tpr-webmodule.git ']]])
+ checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/learn-wiki'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@bitbucket.org:sharelatex/learn-wiki-web-module.git']]])
+ checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/templates'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/templates-webmodule.git']]])
+ checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/track-changes'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/track-changes-web-module.git']]])
+ }
+ }
+
+ stage('Install') {
+ steps {
+ sh 'mv app/views/external/robots.txt public/robots.txt'
+ sh 'mv app/views/external/googlebdb0f8f7f4a17241.html public/googlebdb0f8f7f4a17241.html'
+ sh 'npm install'
+ sh 'npm rebuild'
+ sh 'npm install --quiet grunt'
+ sh 'npm install --quiet grunt-cli'
+ sh 'ls -l node_modules/.bin'
+ }
+ }
+
+ stage('Compile') {
+ steps {
+ sh 'node_modules/.bin/grunt compile --verbose'
+ }
+ }
+
+ stage('Smoke Test') {
+ steps {
+ sh 'node_modules/.bin/grunt compile:smoke_tests'
+ }
+ }
+
+ stage('Minify') {
+ steps {
+ sh 'node_modules/.bin/grunt compile:minify'
+ }
+ }
+
+ stage('Unit Test') {
+ steps {
+ sh 'env NODE_ENV=development ./node_modules/.bin/grunt test:unit --reporter=tap'
+ }
+ }
+
+ stage('Package') {
+ steps {
+ sh 'rm -rf ./node_modules/grunt*'
+ sh 'touch build.tar.gz' // Avoid tar warning about files changing during read
+ sh 'tar -czf build.tar.gz --exclude=build.tar.gz --exclude-vcs .'
+ }
+ }
+ stage('Publish') {
+ steps {
+ withAWS(credentials:'S3_CI_BUILDS_AWS_KEYS', region:"${S3_REGION_BUILD_ARTEFACTS}") {
+ s3Upload(file:'build.tar.gz', bucket:"${S3_BUCKET_BUILD_ARTEFACTS}", path:"${JOB_NAME}/${BUILD_NUMBER}.tar.gz")
+ }
+ }
+ }
+ }
+
+ post {
+ failure {
+ mail(from: "${EMAIL_ALERT_FROM}",
+ to: "${EMAIL_ALERT_TO}",
+ subject: "Jenkins build failed: ${JOB_NAME}:${BUILD_NUMBER}",
+ body: "Build: ${BUILD_URL}")
+ }
+ }
+
+
+ // The options directive is for configuration that applies to the whole job.
+ options {
+ // we'd like to make sure remove old builds, so we don't fill up our storage!
+ buildDiscarder(logRotator(numToKeepStr:'50'))
+
+ // And we'd really like to be sure that this build doesn't hang forever, so let's time it out after:
+ timeout(time: 30, unit: 'MINUTES')
+ }
+}
diff --git a/services/web/app/coffee/Features/Authentication/AuthenticationManager.coffee b/services/web/app/coffee/Features/Authentication/AuthenticationManager.coffee
index b661455028..49bd994b2c 100644
--- a/services/web/app/coffee/Features/Authentication/AuthenticationManager.coffee
+++ b/services/web/app/coffee/Features/Authentication/AuthenticationManager.coffee
@@ -29,8 +29,12 @@ module.exports = AuthenticationManager =
callback null, null
setUserPassword: (user_id, password, callback = (error) ->) ->
- if Settings.passwordStrengthOptions?.length?.max? and Settings.passwordStrengthOptions?.length?.max < password.length
+ if (Settings.passwordStrengthOptions?.length?.max? and
+ Settings.passwordStrengthOptions?.length?.max < password.length)
return callback("password is too long")
+ if (Settings.passwordStrengthOptions?.length?.min? and
+ Settings.passwordStrengthOptions?.length?.min > password.length)
+ return callback("password is too short")
bcrypt.genSalt BCRYPT_ROUNDS, (error, salt) ->
return callback(error) if error?
diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee
index 193965c101..84fa89ea37 100644
--- a/services/web/app/coffee/Features/Project/ProjectController.coffee
+++ b/services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -230,6 +230,24 @@ module.exports = ProjectController =
return cb(null, false)
else
return cb(null, true)
+ showPerUserTCNotice: (cb) ->
+ cb = underscore.once(cb)
+ if !user_id?
+ return cb()
+ timestamp = user_id.toString().substring(0,8)
+ userSignupDate = new Date( parseInt( timestamp, 16 ) * 1000 )
+ if userSignupDate > new Date("2017-08-09")
+ # Don't show for users who registered after it was released
+ return cb(null, false)
+ timeout = setTimeout cb, 500
+ AnalyticsManager.getLastOccurance user_id, "shown-per-user-tc-notice", (error, event) ->
+ clearTimeout timeout
+ if error?
+ return cb(null, false)
+ else if event?
+ return cb(null, false)
+ else
+ return cb(null, true)
}, (err, results)->
if err?
logger.err err:err, "error getting details for project page"
@@ -237,7 +255,7 @@ module.exports = ProjectController =
project = results.project
user = results.user
subscription = results.subscription
- showTrackChangesOnboarding = results.showTrackChangesOnboarding
+ { showTrackChangesOnboarding, showPerUserTCNotice } = results
daysSinceLastUpdated = (new Date() - project.lastUpdated) /86400000
logger.log project_id:project_id, daysSinceLastUpdated:daysSinceLastUpdated, "got db results for loading editor"
@@ -279,8 +297,9 @@ module.exports = ProjectController =
pdfViewer : user.ace.pdfViewer
syntaxValidation: user.ace.syntaxValidation
}
- trackChangesEnabled: !!project.track_changes
+ trackChangesState: project.track_changes
showTrackChangesOnboarding: !!showTrackChangesOnboarding
+ showPerUserTCNotice: !!showPerUserTCNotice
privilegeLevel: privilegeLevel
chatUrl: Settings.apis.chat.url
anonymous: anonymous
diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee
index 7b857c6460..12faf0e234 100644
--- a/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee
+++ b/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee
@@ -62,6 +62,15 @@ module.exports = SubscriptionUpdater =
invited_emails: email
}, callback
+ deleteSubscription: (subscription_id, callback = (error) ->) ->
+ SubscriptionLocator.getSubscription subscription_id, (err, subscription) ->
+ return callback(err) if err?
+ affected_user_ids = [subscription.admin_id].concat(subscription.member_ids or [])
+ logger.log {subscription_id, affected_user_ids}, "deleting subscription and downgrading users"
+ Subscription.remove {_id: ObjectId(subscription_id)}, (err) ->
+ return callback(err) if err?
+ async.mapSeries affected_user_ids, SubscriptionUpdater._setUsersMinimumFeatures, callback
+
_createNewSubscription: (adminUser_id, callback)->
logger.log adminUser_id:adminUser_id, "creating new subscription"
subscription = new Subscription(admin_id:adminUser_id)
diff --git a/services/web/app/coffee/Features/User/UserCreator.coffee b/services/web/app/coffee/Features/User/UserCreator.coffee
index d4ce82cafb..d08b953559 100644
--- a/services/web/app/coffee/Features/User/UserCreator.coffee
+++ b/services/web/app/coffee/Features/User/UserCreator.coffee
@@ -17,23 +17,15 @@ module.exports = UserCreator =
createNewUser: (opts, callback)->
logger.log opts:opts, "creating new user"
user = new User()
- user.email = opts.email
- user.holdingAccount = opts.holdingAccount
- user.ace.syntaxValidation = true
username = opts.email.match(/^[^@]*/)
- if opts.first_name? and opts.first_name.length != 0
- user.first_name = opts.first_name
- else if username?
- user.first_name = username[0]
- else
- user.first_name = ""
-
- if opts.last_name?
- user.last_name = opts.last_name
- else
- user.last_name = ""
+ if !opts.first_name? or opts.first_name == ""
+ opts.first_name = username[0]
+ for key, value of opts
+ user[key] = value
+
+ user.ace.syntaxValidation = true
user.featureSwitches?.pdfng = true
user.save (err)->
diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee
index 4c6b3a7722..498127cdbd 100644
--- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee
+++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee
@@ -11,6 +11,7 @@ async = require("async")
Modules = require "./Modules"
Url = require "url"
PackageVersions = require "./PackageVersions"
+htmlEncoder = new require("node-html-encoder").Encoder("numerical")
fingerprints = {}
Path = require 'path'
@@ -151,9 +152,10 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)->
next()
webRouter.use (req, res, next)->
- res.locals.translate = (key, vars = {}) ->
+ res.locals.translate = (key, vars = {}, htmlEncode = false) ->
vars.appName = Settings.appName
- req.i18n.translate(key, vars)
+ str = req.i18n.translate(key, vars)
+ if htmlEncode then htmlEncoder.htmlEncode(str) else str
# Don't include the query string parameters, otherwise Google
# treats ?nocdn=true as the canonical version
res.locals.currentUrl = Url.parse(req.originalUrl).pathname
diff --git a/services/web/app/coffee/models/Project.coffee b/services/web/app/coffee/models/Project.coffee
index 18387bdc0b..c63f9efef3 100644
--- a/services/web/app/coffee/models/Project.coffee
+++ b/services/web/app/coffee/models/Project.coffee
@@ -32,7 +32,7 @@ ProjectSchema = new Schema
archived : { type: Boolean }
deletedDocs : [DeletedDocSchema]
imageName : { type: String }
- track_changes : { type: Boolean }
+ track_changes : { type: Object }
ProjectSchema.statics.getProject = (project_or_id, fields, callback)->
if project_or_id._id?
diff --git a/services/web/app/coffee/models/User.coffee b/services/web/app/coffee/models/User.coffee
index fce4b18bc0..4dfea8b9cf 100644
--- a/services/web/app/coffee/models/User.coffee
+++ b/services/web/app/coffee/models/User.coffee
@@ -59,6 +59,10 @@ UserSchema = new Schema
zotero: Boolean
}
betaProgram: { type:Boolean, default: false}
+ overleaf:
+ id: { type: Number }
+ accessToken: { type: String }
+ refreshToken: { type: String }
conn = mongoose.createConnection(Settings.mongo.url, server: poolSize: 10)
diff --git a/services/web/app/views/contact-us-modal.pug b/services/web/app/views/contact-us-modal.pug
index d3e5aa0e87..aad68a53d3 100644
--- a/services/web/app/views/contact-us-modal.pug
+++ b/services/web/app/views/contact-us-modal.pug
@@ -7,36 +7,61 @@ script(type='text/ng-template', id='supportModalTemplate')
) ×
h3 #{translate("contact_us")}
.modal-body.contact-us-modal
- span(ng-show="sent == false")
- label
- | #{translate("subject")}
- .form-group
- input.field.text.medium.span8.form-control(
- ng-model="form.subject",
- ng-model-options="{ updateOn: 'default blur', debounce: {'default': 350, 'blur': 0} }"
- maxlength='255',
- tabindex='1',
- onkeyup='')
- .contact-suggestions(ng-show="suggestions.length")
- p.contact-suggestion-label !{translate("kb_suggestions_enquiry", { kbLink: "__kb__", kb: translate("knowledge_base") })}
- ul.contact-suggestion-list
- li(ng-repeat="suggestion in suggestions")
- a.contact-suggestion-list-item(ng-href="{{ suggestion.url }}", ng-click="clickSuggestionLink(suggestion.url);" target="_blank")
- span(ng-bind-html="suggestion.name")
- i.fa.fa-angle-right
- label.desc(ng-show="'"+getUserEmail()+"'.length < 1")
- | #{translate("email")}
- .form-group(ng-show="'"+getUserEmail()+"'.length < 1")
- input.field.text.medium.span8.form-control(ng-model="form.email", ng-init="form.email = '"+getUserEmail()+"'", type='email', spellcheck='false', value='', maxlength='255', tabindex='2')
- label#title12.desc
- | #{translate("project_url")} (#{translate("optional")})
- .form-group
- input.field.text.medium.span8.form-control(ng-model="form.project_url", tabindex='3', onkeyup='')
- label.desc
- | #{translate("contact_message_label")}
- .form-group
- textarea.field.text.medium.span8.form-control(ng-model="form.message",type='text', value='', tabindex='4', onkeyup='')
- .form-group.text-center
- input.btn-success.btn.btn-lg(type='submit', ng-disabled="sending", ng-click="contactUs()" value=translate("contact_us"))
+ form(name="contactForm")
+ span(ng-show="sent == false")
+ .alert.alert-danger(ng-show="error") Something went wrong sending your request :(
+ label
+ | #{translate("subject")}
+ .form-group
+ input.field.text.medium.span8.form-control(
+ name="subject",
+ required
+ ng-model="form.subject",
+ ng-model-options="{ updateOn: 'default blur', debounce: {'default': 350, 'blur': 0} }"
+ maxlength='255',
+ tabindex='1',
+ onkeyup='')
+ .contact-suggestions(ng-show="suggestions.length")
+ p.contact-suggestion-label !{translate("kb_suggestions_enquiry", { kbLink: "__kb__", kb: translate("knowledge_base") })}
+ ul.contact-suggestion-list
+ li(ng-repeat="suggestion in suggestions")
+ a.contact-suggestion-list-item(ng-href="{{ suggestion.url }}", ng-click="clickSuggestionLink(suggestion.url);" target="_blank")
+ span(ng-bind-html="suggestion.name")
+ i.fa.fa-angle-right
+ label.desc(ng-show="'"+getUserEmail()+"'.length < 1")
+ | #{translate("email")}
+ .form-group(ng-show="'"+getUserEmail()+"'.length < 1")
+ input.field.text.medium.span8.form-control(
+ name="email",
+ required
+ ng-model="form.email",
+ ng-init="form.email = '"+getUserEmail()+"'",
+ type='email', spellcheck='false',
+ value='',
+ maxlength='255',
+ tabindex='2')
+ label#title12.desc
+ | #{translate("project_url")} (#{translate("optional")})
+ .form-group
+ input.field.text.medium.span8.form-control(ng-model="form.project_url", tabindex='3', onkeyup='')
+ label.desc
+ | #{translate("contact_message_label")}
+ .form-group
+ textarea.field.text.medium.span8.form-control(
+ name="body",
+ required
+ ng-model="form.message",
+ type='text',
+ value='',
+ tabindex='4',
+ onkeyup=''
+ )
+ .form-group.text-center
+ input.btn-success.btn.btn-lg(
+ type='submit',
+ ng-disabled="contactForm.$invalid || sending",
+ ng-click="contactUs()"
+ value=translate("contact_us")
+ )
span(ng-show="sent")
p #{translate("request_sent_thank_you")}
diff --git a/services/web/app/views/layout.pug b/services/web/app/views/layout.pug
index 459f081c8a..30f454f72a 100644
--- a/services/web/app/views/layout.pug
+++ b/services/web/app/views/layout.pug
@@ -14,16 +14,16 @@ html(itemscope, itemtype='http://schema.org/Product')
-if (typeof(title) == "undefined")
- title= 'ShareLaTeX, '+ translate("online_latex_editor")
+ title= settings.appName + ', '+ translate("online_latex_editor")
-else
- title= translate(title) + ' - ShareLaTeX, ' + translate("online_latex_editor")
+ title= translate(title) + ' - ' + settings.appName + ', ' + translate("online_latex_editor")
- link(rel="icon", href="/favicon.ico")
- link(rel="icon", sizes="192x192", href="/touch-icon-192x192.png")
- link(rel="apple-touch-icon-precomposed", href="/apple-touch-icon-precomposed.png")
- link(rel="mask-icon", href="/mask-favicon.svg", color="#a93529")
+ link(rel="icon", href="/" + settings.brandPrefix + "favicon.ico")
+ link(rel="icon", sizes="192x192", href="/" + settings.brandPrefix + "touch-icon-192x192.png")
+ link(rel="apple-touch-icon-precomposed", href="/" + settings.brandPrefix + "apple-touch-icon-precomposed.png")
+ link(rel="mask-icon", href="/" + settings.brandPrefix + "mask-favicon.svg", color="#a93529")
- link(rel='stylesheet', href=buildCssPath('/style.css'))
+ link(rel='stylesheet', href=buildCssPath("/" + settings.brandPrefix + "style.css"))
block _headLinks
@@ -33,14 +33,14 @@ html(itemscope, itemtype='http://schema.org/Product')
link(rel="alternate", href=subdomainDetails.url+currentUrl, hreflang=subdomainDetails.lngCode)
- meta(itemprop="name", content="ShareLaTeX, the Online LaTeX Editor")
+ meta(itemprop="name", content=settings.appName + ", the Online LaTeX Editor")
-if (typeof(meta) == "undefined")
meta(itemprop="description", name="description", content=translate("site_description"))
-else
meta(itemprop="description", name="description" , content=meta)
- meta(itemprop="image", name="image", content="https://www.sharelatex.com/favicon.ico")
+ meta(itemprop="image", name="image", content="/" + settings.brandPrefix + "favicon.ico")
- if (typeof(gaToken) != "undefined")
script(type='text/javascript').
diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug
index 5b6137738b..190a46b77e 100644
--- a/services/web/app/views/project/editor.pug
+++ b/services/web/app/views/project/editor.pug
@@ -8,8 +8,8 @@ block vars
block content
.editor(ng-controller="IdeController").full-size
.loading-screen(ng-if="state.loading")
- .loading-screen-lion-container
- .loading-screen-lion(
+ .loading-screen-brand-container
+ .loading-screen-brand(
style="height: 20%;"
ng-style="{ 'height': state.load_progress + '%' }"
)
@@ -106,7 +106,7 @@ block requirejs
//- We need to do .replace(/\//g, '\\/') do that '' -> '<\/script>'
//- and doesn't prematurely end the script tag.
script#data(type="application/json").
- !{JSON.stringify({userSettings: userSettings, user: user}).replace(/\//g, '\\/')}
+ !{JSON.stringify({userSettings: userSettings, user: user, trackChangesState: trackChangesState}).replace(/\//g, '\\/')}
script(type="text/javascript").
window.data = JSON.parse($("#data").text());
@@ -118,8 +118,9 @@ block requirejs
window.csrfToken = "!{csrfToken}";
window.anonymous = #{anonymous};
window.maxDocLength = #{maxDocLength};
- window.trackChangesEnabled = #{trackChangesEnabled};
+ window.trackChangesState = data.trackChangesState;
window.showTrackChangesOnboarding = #{!!showTrackChangesOnboarding};
+ window.showPerUserTCNotice = #{!!showPerUserTCNotice};
window.wikiEnabled = #{!!(settings.apis.wiki && settings.apis.wiki.url)};
window.requirejs = {
"paths" : {
diff --git a/services/web/app/views/project/editor/editor.pug b/services/web/app/views/project/editor/editor.pug
index 8b58c98bcd..6007f2e0be 100644
--- a/services/web/app/views/project/editor/editor.pug
+++ b/services/web/app/views/project/editor/editor.pug
@@ -82,7 +82,7 @@ div.full-size(
i.fa.fa-long-arrow-right
br
a.btn.btn-default.btn-xs(
- tooltip-html="'"+translate('go_to_pdf_location_in_code')+"'"
+ tooltip-html="'"+translate('go_to_pdf_location_in_code', {}, true)+"'"
tooltip-placement="right"
tooltip-append-to-body="true"
ng-click="syncToCode()"
diff --git a/services/web/app/views/project/editor/hotkeys.pug b/services/web/app/views/project/editor/hotkeys.pug
index cab8ea09b3..6069394840 100644
--- a/services/web/app/views/project/editor/hotkeys.pug
+++ b/services/web/app/views/project/editor/hotkeys.pug
@@ -90,8 +90,8 @@ script(type="text/ng-template", id="hotkeysModalTemplate")
span.combination Ctrl + Space
span.description Search References
- h3 #{translate("review")}
- .row
+ h3(ng-if="trackChangesVisible") #{translate("review")}
+ .row(ng-if="trackChangesVisible")
.col-xs-4
.hotkey
span.combination {{ctrl}} + J
@@ -108,4 +108,4 @@ script(type="text/ng-template", id="hotkeysModalTemplate")
.modal-footer
button.btn.btn-default(
ng-click="cancel()"
- ) #{translate("ok")}
\ No newline at end of file
+ ) #{translate("ok")}
diff --git a/services/web/app/views/project/editor/review-panel.pug b/services/web/app/views/project/editor/review-panel.pug
index 07f6bd081e..6db177eba8 100644
--- a/services/web/app/views/project/editor/review-panel.pug
+++ b/services/web/app/views/project/editor/review-panel.pug
@@ -46,21 +46,40 @@
is-loading="reviewPanel.dropdown.loading"
permissions="permissions"
)
- span.review-panel-toolbar-label(ng-if="permissions.write")
- span(ng-click="toggleTrackChanges(true)", ng-if="editor.wantTrackChanges === false") !{translate("track_changes_is_off")}
- span(ng-click="toggleTrackChanges(false)", ng-if="editor.wantTrackChanges === true") !{translate("track_changes_is_on")}
- review-panel-toggle(
- ng-if="editor.wantTrackChanges == editor.trackChanges"
- ng-model="editor.wantTrackChanges"
- on-toggle="toggleTrackChanges"
- disabled="!project.features.trackChanges"
- on-disabled-click="openTrackChangesUpgradeModal"
+ span.review-panel-toolbar-label
+ span.review-panel-toolbar-icon-on(
+ ng-if="editor.wantTrackChanges === true"
)
- span.review-panel-toolbar-label.review-panel-toolbar-label-disabled(ng-if="!permissions.write")
- span(ng-if="editor.wantTrackChanges === false") !{translate("track_changes_is_off")}
- span(ng-if="editor.wantTrackChanges === true") !{translate("track_changes_is_on")}
- span.review-panel-toolbar-spinner(ng-if="editor.wantTrackChanges != editor.trackChanges")
- i.fa.fa-spin.fa-spinner
+ i.fa.fa-circle
+ span(ng-click="toggleFullTCStateCollapse();")
+ span(ng-if="editor.wantTrackChanges === false") !{translate("track_changes_is_off")}
+ span(ng-if="editor.wantTrackChanges === true") !{translate("track_changes_is_on")}
+ span.rp-tc-state-collapse(
+ ng-class="{ 'rp-tc-state-collapse-on': reviewPanel.fullTCStateCollapsed }"
+ )
+ i.fa.fa-angle-down
+ ul.rp-tc-state(
+ review-panel-collapse-height="reviewPanel.fullTCStateCollapsed"
+ )
+ li.rp-tc-state-item.rp-tc-state-item-everyone
+ span.rp-tc-state-item-name !{translate("tc_everyone")}
+ review-panel-toggle(
+ ng-model="reviewPanel.trackChangesOnForEveryone"
+ on-toggle="toggleTrackChangesForEveryone(isOn);"
+ disabled="!project.features.trackChanges || !permissions.write"
+ )
+ li.rp-tc-state-item(
+ ng-repeat="member in reviewPanel.formattedProjectMembers"
+ )
+ span.rp-tc-state-item-name(
+ ng-class="{ 'rp-tc-state-item-name-disabled' : reviewPanel.trackChangesOnForEveryone}"
+ style="color: hsl({{ member.hue }}, 70%, 40%);"
+ ) {{ member.name }}
+ review-panel-toggle(
+ ng-model="reviewPanel.trackChangesState[member.id].value"
+ on-toggle="toggleTrackChangesForUser(isOn, member.id);"
+ disabled="reviewPanel.trackChangesOnForEveryone || !project.features.trackChanges || !permissions.write"
+ )
.rp-entry-list(
review-panel-sorted
@@ -494,7 +513,7 @@ script(type="text/ng-template", id="trackChangesUpgradeModalTemplate")
.modal-body
.teaser-video-container
video.teaser-video(autoplay, loop)
- source(src="/img/teasers/track-changes/teaser-track-changes.mp4", type="video/mp4")
+ source(ng-src="{{ '/img/teasers/track-changes/teaser-track-changes.mp4' }}", type="video/mp4")
img(src="/img/teasers/track-changes/teaser-track-changes.gif")
h4.teaser-title #{translate("see_changes_in_your_documents_live")}
@@ -532,6 +551,37 @@ script(type="text/ng-template", id="trackChangesUpgradeModalTemplate")
)
span #{translate("close")}
+script(type="text/ng-template", id="perUserTCNoticeModalTemplate")
+ .modal-header
+ button.close(
+ type="button"
+ data-dismiss="modal"
+ ng-click="$close()"
+ ) ×
+ h3 #{translate("per_user_tc_title")}
+ .modal-body
+ .teaser-video-container
+ video.teaser-video(autoplay, loop)
+ source(ng-src="{{ '/img/teasers/track-changes/per-user-track-changes.mp4' }}", type="video/mp4")
+ img(src="/img/teasers/track-changes/per-user-track-changes.gif")
+ h4.teaser-title #{translate("you_can_use_per_user_tc")}
+ .row
+ .col-md-8.col-md-offset-2
+ ul.list-unstyled
+ li
+ i.fa.fa-check
+ | #{translate("turn_tc_on_individuals")}
+
+ li
+ i.fa.fa-check
+ | #{translate("keep_tc_on_like_before")}
+
+ .modal-footer()
+ button.btn.btn-default(
+ ng-click="$close()"
+ )
+ span #{translate("close")}
+
script(type="text/ng-template", id="bulkActionsModalTemplate")
.modal-header
button.close(
diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee
index 8fb00aff31..2456590709 100644
--- a/services/web/config/settings.defaults.coffee
+++ b/services/web/config/settings.defaults.coffee
@@ -226,8 +226,8 @@ module.exports = settings =
# passwordStrengthOptions:
# pattern: "aA$3"
# length:
- # min: 1
- # max: 10
+ # min: 6
+ # max: 128
# Email support
# -------------
@@ -343,6 +343,8 @@ module.exports = settings =
appName: "ShareLaTeX (Community Edition)"
adminEmail: "placeholder@example.com"
+
+ brandPrefix: "" # Set to 'ol-' for overleaf styles
nav:
title: "ShareLaTeX Community Edition"
diff --git a/services/web/npm-shrinkwrap.json b/services/web/npm-shrinkwrap.json
index a8357be7fd..042a93f8af 100644
--- a/services/web/npm-shrinkwrap.json
+++ b/services/web/npm-shrinkwrap.json
@@ -1509,7 +1509,7 @@
},
"minimatch": {
"version": "3.0.4",
- "from": "minimatch@^3.0.4",
+ "from": "minimatch@^3.0.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"dev": true
},
@@ -1678,7 +1678,7 @@
},
"iconv-lite": {
"version": "0.2.11",
- "from": "iconv-lite@>=0.2.11 <0.3.0",
+ "from": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz"
},
"ieee754": {
@@ -1698,7 +1698,7 @@
},
"inherits": {
"version": "2.0.3",
- "from": "inherits@>=2.0.1 <2.1.0",
+ "from": "inherits@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
},
"ini": {
@@ -2728,6 +2728,11 @@
"from": "node-forge@0.2.24",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.2.24.tgz"
},
+ "node-html-encoder": {
+ "version": "0.0.2",
+ "from": "node-html-encoder@0.0.2",
+ "resolved": "https://registry.npmjs.org/node-html-encoder/-/node-html-encoder-0.0.2.tgz"
+ },
"node-pre-gyp": {
"version": "0.6.30",
"from": "node-pre-gyp@0.6.30",
@@ -2842,6 +2847,11 @@
"from": "number-is-nan@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz"
},
+ "oauth": {
+ "version": "0.9.15",
+ "from": "oauth@>=0.9.0 <0.10.0",
+ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz"
+ },
"oauth-sign": {
"version": "0.8.2",
"from": "oauth-sign@>=0.8.1 <0.9.0",
@@ -3065,6 +3075,11 @@
"from": "passport-local@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz"
},
+ "passport-oauth2": {
+ "version": "1.4.0",
+ "from": "passport-oauth2@latest",
+ "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.4.0.tgz"
+ },
"passport-saml": {
"version": "0.15.0",
"from": "passport-saml@>=0.15.0 <0.16.0",
@@ -4287,6 +4302,11 @@
"from": "uid-safe@2.1.4",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.4.tgz"
},
+ "uid2": {
+ "version": "0.0.3",
+ "from": "uid2@>=0.0.0 <0.1.0",
+ "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz"
+ },
"underscore": {
"version": "1.6.0",
"from": "underscore@1.6.0",
diff --git a/services/web/package.json b/services/web/package.json
index eac07a06e5..2c3dab21d8 100644
--- a/services/web/package.json
+++ b/services/web/package.json
@@ -41,6 +41,7 @@
"mongojs": "2.4.0",
"mongoose": "4.11.4",
"multer": "^0.1.8",
+ "node-html-encoder": "0.0.2",
"nodemailer": "2.1.0",
"nodemailer-sendgrid-transport": "^0.2.0",
"nodemailer-ses-transport": "^1.3.0",
@@ -48,6 +49,7 @@
"passport": "^0.3.2",
"passport-ldapauth": "^0.6.0",
"passport-local": "^1.0.0",
+ "passport-oauth2": "^1.4.0",
"passport-saml": "^0.15.0",
"pug": "^2.0.0-beta6",
"redis": "0.10.1",
diff --git a/services/web/public/coffee/directives/asyncForm.coffee b/services/web/public/coffee/directives/asyncForm.coffee
index 2fc336c934..0e6ae19ec2 100644
--- a/services/web/public/coffee/directives/asyncForm.coffee
+++ b/services/web/public/coffee/directives/asyncForm.coffee
@@ -103,8 +103,8 @@ define [
defaultPasswordOpts =
pattern: ""
length:
- min: 1
- max: 50
+ min: 6
+ max: 128
allowEmpty: false
allowAnyChars: false
isMasked: true
@@ -127,8 +127,6 @@ define [
[asyncFormCtrl, ngModelCtrl] = ctrl
ngModelCtrl.$parsers.unshift (modelValue) ->
-
-
isValid = passField.validatePass()
email = asyncFormCtrl.getEmail() || window.usersEmail
if !isValid
@@ -141,5 +139,8 @@ define [
if opts.length.max? and modelValue.length == opts.length.max
isValid = false
scope.complexPasswordErrorMessage = "Maximum password length #{opts.length.max} reached"
+ if opts.length.min? and modelValue.length < opts.length.min
+ isValid = false
+ scope.complexPasswordErrorMessage = "Password too short, minimum #{opts.length.min}"
ngModelCtrl.$setValidity('complexPassword', isValid)
return modelValue
diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee
index 5824304bff..d37334649c 100644
--- a/services/web/public/coffee/ide.coffee
+++ b/services/web/public/coffee/ide.coffee
@@ -90,6 +90,8 @@ define [
$scope.chat = {}
ide.toggleReviewPanel = $scope.toggleReviewPanel = () ->
+ if !$scope.project.features.trackChangesVisible
+ return
$scope.ui.reviewPanelOpen = !$scope.ui.reviewPanelOpen
event_tracking.sendMB "rp-toggle-panel", { value : $scope.ui.reviewPanelOpen }
diff --git a/services/web/public/coffee/ide/editor/EditorManager.coffee b/services/web/public/coffee/ide/editor/EditorManager.coffee
index a1d00d3180..bf752ccffb 100644
--- a/services/web/public/coffee/ide/editor/EditorManager.coffee
+++ b/services/web/public/coffee/ide/editor/EditorManager.coffee
@@ -37,10 +37,6 @@ define [
@$scope.$watch "editor.wantTrackChanges", (value) =>
return if !value?
@_syncTrackChangesState(@$scope.editor.sharejs_doc)
-
- @$scope.$watch "project.features.trackChanges", (trackChangesFeature) =>
- return if !trackChangesFeature?
- @$scope.editor.wantTrackChanges = window.trackChangesEnabled and trackChangesFeature
autoOpenDoc: () ->
open_doc_id =
diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee
index af633ea369..1cb527fc40 100644
--- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee
+++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee
@@ -11,6 +11,8 @@ define [
"ide/editor/directives/aceEditor/track-changes/TrackChangesManager"
"ide/editor/directives/aceEditor/labels/LabelsManager"
"ide/labels/services/labels"
+ "ide/graphics/services/graphics"
+ "ide/preamble/services/preamble"
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, LabelsManager) ->
EditSession = ace.require('ace/edit_session').EditSession
ModeList = ace.require('ace/ext/modelist')
@@ -33,9 +35,8 @@ define [
url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}"
return url
- App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels) ->
+ App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, graphics, preamble) ->
monkeyPatchSearch($rootScope, $compile)
-
return {
scope: {
@@ -102,7 +103,7 @@ define [
cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage)
trackChangesManager = new TrackChangesManager(scope, editor, element)
labelsManager = new LabelsManager(scope, editor, element, labels)
- autoCompleteManager = new AutoCompleteManager(scope, editor, element, labelsManager)
+ autoCompleteManager = new AutoCompleteManager(scope, editor, element, labelsManager, graphics, preamble)
# Prevert Ctrl|Cmd-S from triggering save dialog
@@ -346,10 +347,6 @@ define [
catch
mode = "ace/mode/plain_text"
- # Give beta users the next release of the syntax checker
- if mode is "ace/mode/latex" and window.user?.betaProgram
- mode = "ace/mode/latex_beta"
-
# create our new session
session = new EditSession(lines, mode)
diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
index 4466f200c6..55104341e8 100644
--- a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
+++ b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
@@ -17,7 +17,7 @@ define [
commandFragment?.match(/\\(\w+)\{/)?[1]
class AutoCompleteManager
- constructor: (@$scope, @editor, @element, @labelsManager) ->
+ constructor: (@$scope, @editor, @element, @labelsManager, @graphics, @preamble) ->
@suggestionManager = new CommandManager()
@monkeyPatchAutocomplete()
@@ -44,6 +44,37 @@ define [
SnippetCompleter = new EnvironmentManager()
+ Graphics = @graphics
+ Preamble = @preamble
+ GraphicsCompleter =
+ getCompletions: (editor, session, pos, prefix, callback) ->
+ upToCursorRange = new Range(pos.row, 0, pos.row, pos.column)
+ lineUpToCursor = editor.getSession().getTextRange(upToCursorRange)
+ commandFragment = getLastCommandFragment(lineUpToCursor)
+ if commandFragment
+ match = commandFragment.match(/^~?\\(includegraphics(?:\[.*])?){([^}]*, *)?(\w*)/)
+ if match
+ beyondCursorRange = new Range(pos.row, pos.column, pos.row, 99999)
+ lineBeyondCursor = editor.getSession().getTextRange(beyondCursorRange)
+ needsClosingBrace = !lineBeyondCursor.match(/^[^{]*}/)
+ commandName = match[1]
+ currentArg = match[3]
+ graphicsPaths = Preamble.getGraphicsPaths()
+ result = []
+ for graphic in Graphics.getGraphicsFiles()
+ path = graphic.path
+ for graphicsPath in graphicsPaths
+ if path.indexOf(graphicsPath) == 0
+ path = path.slice(graphicsPath.length)
+ break
+ result.push {
+ caption: "\\#{commandName}{#{path}#{if needsClosingBrace then '}' else ''}",
+ value: "\\#{commandName}{#{path}#{if needsClosingBrace then '}' else ''}",
+ meta: "graphic",
+ score: 50
+ }
+ callback null, result
+
labelsManager = @labelsManager
LabelsCompleter =
getCompletions: (editor, session, pos, prefix, callback) ->
@@ -113,11 +144,12 @@ define [
callback null, result
@editor.completers = [
- @suggestionManager,
- SnippetCompleter,
- ReferencesCompleter,
- LabelsCompleter
- ]
+ @suggestionManager,
+ SnippetCompleter,
+ ReferencesCompleter,
+ LabelsCompleter,
+ GraphicsCompleter
+ ]
disable: () ->
@editor.setOptions({
@@ -250,7 +282,22 @@ define [
editor.completer.autoSelect = true
editor.completer.showPopup(editor)
editor.completer.cancelContextMenu()
- $(editor.completer.popup?.container).css({'font-size': @$scope.fontSize + 'px'})
+ container = $(editor.completer.popup?.container)
+ container.css({'font-size': @$scope.fontSize + 'px'})
+ # Dynamically set width of autocomplete popup
+ if filtered = editor?.completer?.completions?.filtered
+ longestCaption = _.max(filtered.map( (c) -> c.caption.length ))
+ longestMeta = _.max(filtered.map( (c) -> c.meta.length ))
+ charWidth = editor.renderer.characterWidth
+ # between 280 and 700 px
+ width = Math.max(
+ Math.min(
+ Math.round(longestCaption*charWidth + longestMeta*charWidth + 5*charWidth),
+ 700
+ ),
+ 280
+ )
+ container.css({width: "#{width}px"})
if editor.completer?.completions?.filtered?.length == 0
editor.completer.detach()
bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space"
diff --git a/services/web/public/coffee/ide/graphics/services/graphics.coffee b/services/web/public/coffee/ide/graphics/services/graphics.coffee
new file mode 100644
index 0000000000..355614b7f4
--- /dev/null
+++ b/services/web/public/coffee/ide/graphics/services/graphics.coffee
@@ -0,0 +1,17 @@
+define [
+ "base"
+], (App) ->
+
+ App.factory 'graphics', (ide) ->
+
+ Graphics =
+ getGraphicsFiles: () ->
+ graphicsFiles = []
+ ide.fileTreeManager.forEachEntity (entity, folder, path) ->
+ if entity.type == 'file' && entity?.name?.match?(/.*\.(png|jpg|jpeg|pdf|eps)/)
+ cloned = _.clone(entity)
+ cloned.path = path
+ graphicsFiles.push cloned
+ return graphicsFiles
+
+ return Graphics
diff --git a/services/web/public/coffee/ide/hotkeys/controllers/HotkeysController.coffee b/services/web/public/coffee/ide/hotkeys/controllers/HotkeysController.coffee
index 0ec2c46cf1..65b513eda2 100644
--- a/services/web/public/coffee/ide/hotkeys/controllers/HotkeysController.coffee
+++ b/services/web/public/coffee/ide/hotkeys/controllers/HotkeysController.coffee
@@ -10,13 +10,16 @@ define [
templateUrl: "hotkeysModalTemplate"
controller: "HotkeysModalController"
size: "lg"
+ resolve:
+ trackChangesVisible: () -> $scope.project.features.trackChangesVisible
}
- App.controller "HotkeysModalController", ($scope, $modalInstance)->
+ App.controller "HotkeysModalController", ($scope, $modalInstance, trackChangesVisible)->
+ $scope.trackChangesVisible = trackChangesVisible
if ace.require("ace/lib/useragent").isMac
$scope.ctrl = "Cmd"
else
$scope.ctrl = "Ctrl"
$scope.cancel = () ->
- $modalInstance.dismiss()
\ No newline at end of file
+ $modalInstance.dismiss()
diff --git a/services/web/public/coffee/ide/preamble/services/preamble.coffee b/services/web/public/coffee/ide/preamble/services/preamble.coffee
new file mode 100644
index 0000000000..f95cfd9bbe
--- /dev/null
+++ b/services/web/public/coffee/ide/preamble/services/preamble.coffee
@@ -0,0 +1,22 @@
+define [
+ "base"
+], (App) ->
+
+ App.factory 'preamble', (ide) ->
+
+ Preamble =
+ getPreambleText: () ->
+ text = ide.editorManager.getCurrentDocValue().slice(0, 5000)
+ preamble = text.match(/([^]*)^\\begin\{document\}/m)?[1] || ""
+ return preamble
+
+ getGraphicsPaths: () ->
+ preamble = Preamble.getPreambleText()
+ graphicsPathsArgs = preamble.match(/\\graphicspath\{(.*)\}/)?[1] || ""
+ paths = []
+ re = /\{([^}]*)\}/g
+ while match = re.exec(graphicsPathsArgs)
+ paths.push(match[1])
+ return paths
+
+ return Preamble
diff --git a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee
index e4d7855ed9..67cbedb3e8 100644
--- a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee
+++ b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee
@@ -11,7 +11,13 @@ define [
CUR_FILE : "cur_file"
OVERVIEW : "overview"
+ $scope.UserTCSyncState = UserTCSyncState =
+ SYNCED : "synced"
+ PENDING : "pending"
+
$scope.reviewPanel =
+ trackChangesState: {}
+ trackChangesOnForEveryone: false
entries: {}
resolvedComments: {}
hasEntries: false
@@ -25,6 +31,8 @@ define [
commentThreads: {}
resolvedThreadIds: {}
rendererData: {}
+ formattedProjectMembers: {}
+ fullTCStateCollapsed: true
loadingThreads: false
# All selected changes. If a aggregated change (insertion + deletion) is selection, the two ids
# will be present. The length of this array will differ from the count below (see explanation).
@@ -32,6 +40,7 @@ define [
# A count of user-facing selected changes. An aggregated change (insertion + deletion) will count
# as only one.
nVisibleSelectedChanges: 0
+ showPerUserTCNotice: window.showPerUserTCNotice
window.addEventListener "beforeunload", () ->
collapsedStates = {}
@@ -59,6 +68,15 @@ define [
if !visible
$scope.ui.reviewPanelOpen = false
+ $scope.$watch "project.members", (members) ->
+ $scope.reviewPanel.formattedProjectMembers = {}
+ if $scope.project?.owner?
+ $scope.reviewPanel.formattedProjectMembers[$scope.project.owner._id] = formatUser($scope.project.owner)
+ if $scope.project?.members?
+ for member in members
+ if member.privileges == "readAndWrite"
+ $scope.reviewPanel.formattedProjectMembers[member._id] = formatUser(member)
+
$scope.commentState =
adding: false
content: ""
@@ -416,6 +434,8 @@ define [
$scope.toggleReviewPanel()
$scope.addNewCommentFromKbdShortcut = () ->
+ if !$scope.project.features.trackChangesVisible
+ return
$scope.$broadcast "comment:select_line"
if !$scope.ui.reviewPanelOpen
$scope.toggleReviewPanel()
@@ -565,24 +585,83 @@ define [
$scope.gotoEntry = (doc_id, entry) ->
ide.editorManager.openDocId(doc_id, { gotoOffset: entry.offset })
-
- $scope.toggleTrackChanges = (value) ->
+
+ $scope.toggleFullTCStateCollapse = () ->
if $scope.project.features.trackChanges
- $scope.editor.wantTrackChanges = value
- $http.post "/project/#{$scope.project_id}/track_changes", {_csrf: window.csrfToken, on: value}
- event_tracking.sendMB "rp-trackchanges-toggle", { value }
+ if $scope.reviewPanel.showPerUserTCNotice
+ $scope.openPerUserTCNoticeModal()
+ $scope.reviewPanel.fullTCStateCollapsed = !$scope.reviewPanel.fullTCStateCollapsed
else
$scope.openTrackChangesUpgradeModal()
- $scope.toggleTrackChangesFromKbdShortcut = () ->
- if $scope.editor.wantTrackChanges
- $scope.toggleTrackChanges false
- else
- $scope.toggleTrackChanges true
+ _setUserTCState = (userId, newValue, isLocal = false) ->
+ $scope.reviewPanel.trackChangesState[userId] ?= {}
+ state = $scope.reviewPanel.trackChangesState[userId]
+
+ if !state.syncState? or state.syncState == UserTCSyncState.SYNCED
+ state.value = newValue
+ state.syncState = UserTCSyncState.SYNCED
+ else if state.syncState == UserTCSyncState.PENDING and state.value == newValue
+ state.syncState = UserTCSyncState.SYNCED
+ else if isLocal
+ state.value = newValue
+ state.syncState = UserTCSyncState.PENDING
+
+ if userId == ide.$scope.user.id
+ $scope.editor.wantTrackChanges = newValue
+
+ _setEveryoneTCState = (newValue, isLocal = false) ->
+ $scope.reviewPanel.trackChangesOnForEveryone = newValue
+ for member in $scope.project.members
+ _setUserTCState(member._id, newValue, isLocal)
+ _setUserTCState($scope.project.owner._id, newValue, isLocal)
+
+ applyClientTrackChangesStateToServer = () ->
+ if $scope.reviewPanel.trackChangesOnForEveryone
+ data = {on : true}
+ else
+ data = {on_for: {}}
+ for userId, userState of $scope.reviewPanel.trackChangesState
+ data.on_for[userId] = userState.value
+ data._csrf = window.csrfToken
+ $http.post "/project/#{$scope.project_id}/track_changes", data
+
+ applyTrackChangesStateToClient = (state) ->
+ if typeof state is "boolean"
+ _setEveryoneTCState state
+ else
+ $scope.reviewPanel.trackChangesOnForEveryone = false
+ for member in $scope.project.members
+ _setUserTCState(member._id, state[member._id] ? false)
+ _setUserTCState($scope.project.owner._id, state[$scope.project.owner._id] ? false)
- ide.socket.on "toggle-track-changes", (value) ->
+ $scope.toggleTrackChangesForEveryone = (onForEveryone) ->
+ _setEveryoneTCState onForEveryone, true
+ applyClientTrackChangesStateToServer()
+
+ $scope.toggleTrackChangesForUser = (onForUser, userId) ->
+ _setUserTCState userId, onForUser, true
+ applyClientTrackChangesStateToServer()
+
+ ide.socket.on "toggle-track-changes", (state) ->
$scope.$apply () ->
- $scope.editor.wantTrackChanges = value
+ applyTrackChangesStateToClient(state)
+
+ $scope.toggleTrackChangesFromKbdShortcut = () ->
+ if !$scope.project.features.trackChangesVisible
+ return
+ $scope.toggleTrackChangesForUser !$scope.reviewPanel.trackChangesState[ide.$scope.user.id].value, ide.$scope.user.id
+
+ _inited = false
+ ide.$scope.$on "project:joined", () ->
+ return if _inited
+ project = ide.$scope.project
+ if project.features.trackChanges
+ window.trackChangesState ?= false
+ applyTrackChangesStateToClient(window.trackChangesState)
+ else
+ applyTrackChangesStateToClient(false)
+ _inited = true
_refreshingRangeUsers = false
_refreshedForUserIds = {}
@@ -676,3 +755,11 @@ define [
controller: "TrackChangesUpgradeModalController"
scope: $scope.$new()
}
+
+ $scope.openPerUserTCNoticeModal = () ->
+ $scope.reviewPanel.showPerUserTCNotice = false
+ $modal.open({
+ templateUrl: "perUserTCNoticeModalTemplate"
+ }).result.finally () ->
+ event_tracking.sendMB "shown-per-user-tc-notice"
+
diff --git a/services/web/public/coffee/ide/review-panel/directives/reviewPanelToggle.coffee b/services/web/public/coffee/ide/review-panel/directives/reviewPanelToggle.coffee
index 2b5180dce6..6f2288634b 100644
--- a/services/web/public/coffee/ide/review-panel/directives/reviewPanelToggle.coffee
+++ b/services/web/public/coffee/ide/review-panel/directives/reviewPanelToggle.coffee
@@ -4,20 +4,23 @@ define [
App.directive "reviewPanelToggle", () ->
restrict: "E"
scope:
- onToggle: '='
+ onToggle: '&'
ngModel: '='
+ valWhenUndefined: '=?'
disabled: '=?'
- onDisabledClick: '=?'
+ onDisabledClick: '&?'
link: (scope) ->
if !scope.disabled?
scope.disabled = false
scope.onChange = (args...) ->
- scope.onToggle(scope.localModel)
+ scope.onToggle({ isOn: scope.localModel })
scope.handleClick = () ->
- if scope.disabled
+ if scope.disabled and scope.onDisabledClick?
scope.onDisabledClick()
scope.localModel = scope.ngModel
scope.$watch "ngModel", (value) ->
+ if scope.valWhenUndefined? and !value?
+ value = scope.valWhenUndefined
scope.localModel = value
template: """
diff --git a/services/web/public/coffee/main/contact-us.coffee b/services/web/public/coffee/main/contact-us.coffee
index 2f3a2c61e0..7bb86a6b93 100644
--- a/services/web/public/coffee/main/contact-us.coffee
+++ b/services/web/public/coffee/main/contact-us.coffee
@@ -30,7 +30,7 @@ define [
$scope.suggestions = suggestions
$scope.contactUs = ->
- if !$scope.form.email?
+ if !$scope.form.email? or $scope.form.email == ""
console.log "email not set"
return
$scope.sending = true
@@ -46,8 +46,16 @@ define [
about: "
browser: #{platform?.name} #{platform?.version}
os: #{platform?.os?.family} #{platform?.os?.version}
"
- Groove.createTicket params, (err, json)->
- $scope.sent = true
+ Groove.createTicket params, (response)->
+ $scope.sending = false
+ if response.responseText == "" # Blocked request or similar
+ $scope.error = true
+ else
+ data = JSON.parse(response.responseText)
+ if data.errors?
+ $scope.error = true
+ else
+ $scope.sent = true
$scope.$apply()
$scope.$watch "form.subject", (newVal, oldVal) ->
diff --git a/services/web/public/coffee/main/project-list/project-list.coffee b/services/web/public/coffee/main/project-list/project-list.coffee
index 5d0341656e..cafbd7c81b 100644
--- a/services/web/public/coffee/main/project-list/project-list.coffee
+++ b/services/web/public/coffee/main/project-list/project-list.coffee
@@ -20,7 +20,7 @@ define [
, 10
$scope.$watch((
- () -> $scope.projects.filter((project) -> !project.tags? or project.tags.length == 0).length
+ () -> $scope.projects.filter((project) -> (!project.tags? or project.tags.length == 0) and !project.archived).length
), (newVal) -> $scope.nUntagged = newVal)
storedUIOpts = JSON.parse(localStorage("project_list"))
diff --git a/services/web/public/img/ol-brand/logo-horizontal.png b/services/web/public/img/ol-brand/logo-horizontal.png
new file mode 100644
index 0000000000..6e95b282cb
Binary files /dev/null and b/services/web/public/img/ol-brand/logo-horizontal.png differ
diff --git a/services/web/public/img/ol-brand/overleaf-o-grey.svg b/services/web/public/img/ol-brand/overleaf-o-grey.svg
new file mode 100644
index 0000000000..3b47c37cba
--- /dev/null
+++ b/services/web/public/img/ol-brand/overleaf-o-grey.svg
@@ -0,0 +1,18 @@
+
+
+
diff --git a/services/web/public/img/ol-brand/overleaf-o.svg b/services/web/public/img/ol-brand/overleaf-o.svg
new file mode 100644
index 0000000000..d95cee9ded
--- /dev/null
+++ b/services/web/public/img/ol-brand/overleaf-o.svg
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/services/web/public/img/teasers/track-changes/per-user-track-changes.gif b/services/web/public/img/teasers/track-changes/per-user-track-changes.gif
new file mode 100644
index 0000000000..ca7c636642
Binary files /dev/null and b/services/web/public/img/teasers/track-changes/per-user-track-changes.gif differ
diff --git a/services/web/public/img/teasers/track-changes/per-user-track-changes.mp4 b/services/web/public/img/teasers/track-changes/per-user-track-changes.mp4
new file mode 100644
index 0000000000..9e4d0cbe29
Binary files /dev/null and b/services/web/public/img/teasers/track-changes/per-user-track-changes.mp4 differ
diff --git a/services/web/public/js/ace-1.2.5/mode-latex.js b/services/web/public/js/ace-1.2.5/mode-latex.js
index ede29b9661..df84725189 100644
--- a/services/web/public/js/ace-1.2.5/mode-latex.js
+++ b/services/web/public/js/ace-1.2.5/mode-latex.js
@@ -606,7 +606,7 @@ var createLatexWorker = function (session) {
};
worker.on("lint", function(results) {
if(docChangePending) { docChangePending = false; };
- hints = results.data;
+ hints = results.data.errors;
if (hints.length > 100) {
hints = hints.slice(0, 100); // limit to 100 errors
};
diff --git a/services/web/public/js/ace-1.2.5/mode-latex_beta.js b/services/web/public/js/ace-1.2.5/mode-latex_beta.js
deleted file mode 100644
index d170f0ade5..0000000000
--- a/services/web/public/js/ace-1.2.5/mode-latex_beta.js
+++ /dev/null
@@ -1,655 +0,0 @@
-ace.define("ace/mode/latex_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(require, exports, module) {
-"use strict";
-
-var oop = require("../lib/oop");
-var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
-
-var LatexHighlightRules = function() {
-
- this.$rules = {
- "start" : [{
- token : "comment",
- regex : "%.*$"
- }, {
- token : ["keyword", "lparen", "variable.parameter", "rparen", "lparen", "storage.type", "rparen"],
- regex : "(\\\\(?:documentclass|usepackage|input))(?:(\\[)([^\\]]*)(\\]))?({)([^}]*)(})"
- }, {
- token : ["keyword","lparen", "variable.parameter", "rparen"],
- regex : "(\\\\(?:label|v?ref|cite(?:[^{]*)))(?:({)([^}]*)(}))?"
- }, {
- token : ["storage.type", "lparen", "variable.parameter", "rparen"],
- regex : "(\\\\(?:begin|end))({)(\\w*)(})"
- }, {
- token : "storage.type",
- regex : "\\\\[a-zA-Z]+"
- }, {
- token : "lparen",
- regex : "[[({]"
- }, {
- token : "rparen",
- regex : "[\\])}]"
- }, {
- token : "constant.character.escape",
- regex : "\\\\[^a-zA-Z]?"
- }, {
- token : "string",
- regex : "\\${1,2}",
- next : "equation"
- }],
- "equation" : [{
- token : "comment",
- regex : "%.*$"
- }, {
- token : "string",
- regex : "\\${1,2}",
- next : "start"
- }, {
- token : "constant.character.escape",
- regex : "\\\\(?:[^a-zA-Z]|[a-zA-Z]+)"
- }, {
- token : "error",
- regex : "^\\s*$",
- next : "start"
- }, {
- defaultToken : "string"
- }]
-
- };
-};
-oop.inherits(LatexHighlightRules, TextHighlightRules);
-
-exports.LatexHighlightRules = LatexHighlightRules;
-
-});
-
-ace.define("ace/mode/folding/latex",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range","ace/token_iterator"], function(require, exports, module) {
-"use strict";
-
-var oop = require("../../lib/oop");
-var BaseFoldMode = require("./fold_mode").FoldMode;
-var Range = require("../../range").Range;
-var TokenIterator = require("../../token_iterator").TokenIterator;
-
-var FoldMode = exports.FoldMode = function() {};
-
-oop.inherits(FoldMode, BaseFoldMode);
-
-(function() {
-
- this.foldingStartMarker = /^\s*\\(begin)|(section|subsection|paragraph)\b|{\s*$/;
- this.foldingStopMarker = /^\s*\\(end)\b|^\s*}/;
-
- this.getFoldWidgetRange = function(session, foldStyle, row) {
- var line = session.doc.getLine(row);
- var match = this.foldingStartMarker.exec(line);
- if (match) {
- if (match[1])
- return this.latexBlock(session, row, match[0].length - 1);
- if (match[2])
- return this.latexSection(session, row, match[0].length - 1);
-
- return this.openingBracketBlock(session, "{", row, match.index);
- }
-
- var match = this.foldingStopMarker.exec(line);
- if (match) {
- if (match[1])
- return this.latexBlock(session, row, match[0].length - 1);
-
- return this.closingBracketBlock(session, "}", row, match.index + match[0].length);
- }
- };
-
- this.latexBlock = function(session, row, column) {
- var keywords = {
- "\\begin": 1,
- "\\end": -1
- };
-
- var stream = new TokenIterator(session, row, column);
- var token = stream.getCurrentToken();
- if (!token || !(token.type == "storage.type" || token.type == "constant.character.escape"))
- return;
-
- var val = token.value;
- var dir = keywords[val];
-
- var getType = function() {
- var token = stream.stepForward();
- var type = token.type == "lparen" ?stream.stepForward().value : "";
- if (dir === -1) {
- stream.stepBackward();
- if (type)
- stream.stepBackward();
- }
- return type;
- };
- var stack = [getType()];
- var startColumn = dir === -1 ? stream.getCurrentTokenColumn() : session.getLine(row).length;
- var startRow = row;
-
- stream.step = dir === -1 ? stream.stepBackward : stream.stepForward;
- while(token = stream.step()) {
- if (!token || !(token.type == "storage.type" || token.type == "constant.character.escape"))
- continue;
- var level = keywords[token.value];
- if (!level)
- continue;
- var type = getType();
- if (level === dir)
- stack.unshift(type);
- else if (stack.shift() !== type || !stack.length)
- break;
- }
-
- if (stack.length)
- return;
-
- var row = stream.getCurrentTokenRow();
- if (dir === -1)
- return new Range(row, session.getLine(row).length, startRow, startColumn);
- stream.stepBackward();
- return new Range(startRow, startColumn, row, stream.getCurrentTokenColumn());
- };
-
- this.latexSection = function(session, row, column) {
- var keywords = ["\\subsection", "\\section", "\\begin", "\\end", "\\paragraph"];
-
- var stream = new TokenIterator(session, row, column);
- var token = stream.getCurrentToken();
- if (!token || token.type != "storage.type")
- return;
-
- var startLevel = keywords.indexOf(token.value);
- var stackDepth = 0
- var endRow = row;
-
- while(token = stream.stepForward()) {
- if (token.type !== "storage.type")
- continue;
- var level = keywords.indexOf(token.value);
-
- if (level >= 2) {
- if (!stackDepth)
- endRow = stream.getCurrentTokenRow() - 1;
- stackDepth += level == 2 ? 1 : - 1;
- if (stackDepth < 0)
- break
- } else if (level >= startLevel)
- break;
- }
-
- if (!stackDepth)
- endRow = stream.getCurrentTokenRow() - 1;
-
- while (endRow > row && !/\S/.test(session.getLine(endRow)))
- endRow--;
-
- return new Range(
- row, session.getLine(row).length,
- endRow, session.getLine(endRow).length
- );
- };
-
-}).call(FoldMode.prototype);
-
-});
-
-ace.define("ace/mode/behaviour/latex",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"], function(require, exports, module) {
-"use strict";
-
-var oop = require("../../lib/oop");
-var Behaviour = require("../behaviour").Behaviour;
-var TokenIterator = require("../../token_iterator").TokenIterator;
-var lang = require("../../lib/lang");
-
-var SAFE_INSERT_IN_TOKENS =
- ["text", "paren.rparen", "punctuation.operator"];
-var SAFE_INSERT_BEFORE_TOKENS =
- ["text", "paren.rparen", "punctuation.operator", "comment"];
-
-var context;
-var contextCache = {};
-var initContext = function(editor) {
- var id = -1;
- if (editor.multiSelect) {
- id = editor.selection.index;
- if (contextCache.rangeCount != editor.multiSelect.rangeCount)
- contextCache = {rangeCount: editor.multiSelect.rangeCount};
- }
- if (contextCache[id])
- return context = contextCache[id];
- context = contextCache[id] = {
- autoInsertedBrackets: 0,
- autoInsertedRow: -1,
- autoInsertedLineEnd: "",
- maybeInsertedBrackets: 0,
- maybeInsertedRow: -1,
- maybeInsertedLineStart: "",
- maybeInsertedLineEnd: ""
- };
-};
-
-var getWrapped = function(selection, selected, opening, closing) {
- var rowDiff = selection.end.row - selection.start.row;
- return {
- text: opening + selected + closing,
- selection: [
- 0,
- selection.start.column + 1,
- rowDiff,
- selection.end.column + (rowDiff ? 0 : 1)
- ]
- };
-};
-
-var LatexBehaviour = function() {
- this.add("braces", "insertion", function(state, action, editor, session, text) {
- if (editor.completer && editor.completer.popup && editor.completer.popup.isOpen) {
- return;
- }
- var cursor = editor.getCursorPosition();
- var line = session.doc.getLine(cursor.row);
- var lastChar = line[cursor.column-1];
- if (lastChar === '\\') {
- return;
- }
- if (text == '{') {
- initContext(editor);
- var selection = editor.getSelectionRange();
- var selected = session.doc.getTextRange(selection);
- if (selected !== "" && editor.getWrapBehavioursEnabled()) {
- return getWrapped(selection, selected, '{', '}');
- } else if (LatexBehaviour.isSaneInsertion(editor, session)) {
- LatexBehaviour.recordAutoInsert(editor, session, "}");
- return {
- text: '{}',
- selection: [1, 1]
- };
- }
- } else if (text == '}') {
- initContext(editor);
- var rightChar = line.substring(cursor.column, cursor.column + 1);
- if (rightChar == '}') {
- var matching = session.$findOpeningBracket('}', {column: cursor.column + 1, row: cursor.row});
- if (matching !== null && LatexBehaviour.isAutoInsertedClosing(cursor, line, text)) {
- LatexBehaviour.popAutoInsertedClosing();
- return {
- text: '',
- selection: [1, 1]
- };
- }
- }
- }
- });
-
- this.add("braces", "deletion", function(state, action, editor, session, range) {
- if (editor.completer && editor.completer.popup && editor.completer.popup.isOpen) {
- return;
- }
- var selected = session.doc.getTextRange(range);
- if (!range.isMultiLine() && selected == '{') {
- initContext(editor);
- var line = session.doc.getLine(range.start.row);
- var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
- if (rightChar == '}') {
- range.end.column++;
- return range;
- }
- }
- });
-
- this.add("brackets", "insertion", function(state, action, editor, session, text) {
- if (editor.completer && editor.completer.popup && editor.completer.popup.isOpen) {
- return;
- }
- var cursor = editor.getCursorPosition();
- var line = session.doc.getLine(cursor.row);
- var lastChar = line[cursor.column-1];
- if (lastChar === '\\') {
- return;
- }
- if (text == '[') {
- initContext(editor);
- var selection = editor.getSelectionRange();
- var selected = session.doc.getTextRange(selection);
- if (selected !== "" && editor.getWrapBehavioursEnabled()) {
- return getWrapped(selection, selected, '[', ']');
- } else if (LatexBehaviour.isSaneInsertion(editor, session)) {
- LatexBehaviour.recordAutoInsert(editor, session, "]");
- return {
- text: '[]',
- selection: [1, 1]
- };
- }
- } else if (text == ']') {
- initContext(editor);
- var rightChar = line.substring(cursor.column, cursor.column + 1);
- if (rightChar == ']') {
- var matching = session.$findOpeningBracket(']', {column: cursor.column + 1, row: cursor.row});
- if (matching !== null && LatexBehaviour.isAutoInsertedClosing(cursor, line, text)) {
- LatexBehaviour.popAutoInsertedClosing();
- return {
- text: '',
- selection: [1, 1]
- };
- }
- }
- }
- });
-
- this.add("brackets", "deletion", function(state, action, editor, session, range) {
- if (editor.completer && editor.completer.popup && editor.completer.popup.isOpen) {
- return;
- }
- var selected = session.doc.getTextRange(range);
- if (!range.isMultiLine() && selected == '[') {
- initContext(editor);
- var line = session.doc.getLine(range.start.row);
- var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
- if (rightChar == ']') {
- range.end.column++;
- return range;
- }
- }
- });
-
- this.add("dollars", "insertion", function(state, action, editor, session, text) {
- var cursor = editor.getCursorPosition();
- var line = session.doc.getLine(cursor.row);
- var lastChar = line[cursor.column-1];
- if (lastChar === '\\') {
- return;
- }
- if (text == '$') {
- if (this.lineCommentStart && this.lineCommentStart.indexOf(text) != -1)
- return;
- initContext(editor);
- var quote = text;
- var selection = editor.getSelectionRange();
- var selected = session.doc.getTextRange(selection);
- if (selected !== "" && selected !== "$" && editor.getWrapBehavioursEnabled()) {
- return getWrapped(selection, selected, quote, quote);
- } else if (!selected) {
- var leftChar = line.substring(cursor.column-1, cursor.column);
- var rightChar = line.substring(cursor.column, cursor.column + 1);
-
- var token = session.getTokenAt(cursor.row, cursor.column);
- var rightToken = session.getTokenAt(cursor.row, cursor.column + 1);
-
- var stringBefore = token && /string|escape/.test(token.type);
- var stringAfter = !rightToken || /string|escape/.test(rightToken.type);
-
- var pair;
- if (rightChar == quote) {
- pair = stringBefore !== stringAfter;
- if (pair && /string\.end/.test(rightToken.type))
- pair = false;
- } else {
- if (stringBefore && !stringAfter)
- return null; // wrap string with different quote
- if (stringBefore && stringAfter)
- return null; // do not pair quotes inside strings
- var wordRe = session.$mode.tokenRe;
- wordRe.lastIndex = 0;
- var isWordBefore = wordRe.test(leftChar);
- wordRe.lastIndex = 0;
- var isWordAfter = wordRe.test(leftChar);
- if (isWordBefore || isWordAfter)
- return null; // before or after alphanumeric
- if (rightChar && !/[\s;,.})\]\\]/.test(rightChar))
- return null; // there is rightChar and it isn't closing
- pair = true;
- }
- return {
- text: pair ? quote + quote : "",
- selection: [1,1]
- };
- }
- }
- });
-
- this.add("dollars", "deletion", function(state, action, editor, session, range) {
- var selected = session.doc.getTextRange(range);
- if (!range.isMultiLine() && (selected == '$')) {
- initContext(editor);
- var line = session.doc.getLine(range.start.row);
- var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
- if (rightChar == selected) {
- range.end.column++;
- return range;
- }
- }
- });
-
-};
-
-
-LatexBehaviour.isSaneInsertion = function(editor, session) {
- var cursor = editor.getCursorPosition();
- var iterator = new TokenIterator(session, cursor.row, cursor.column);
- if (!this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) {
- var iterator2 = new TokenIterator(session, cursor.row, cursor.column + 1);
- if (!this.$matchTokenType(iterator2.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS))
- return false;
- }
- iterator.stepForward();
- return iterator.getCurrentTokenRow() !== cursor.row ||
- this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_BEFORE_TOKENS);
-};
-
-LatexBehaviour.$matchTokenType = function(token, types) {
- return types.indexOf(token.type || token) > -1;
-};
-
-LatexBehaviour.recordAutoInsert = function(editor, session, bracket) {
- var cursor = editor.getCursorPosition();
- var line = session.doc.getLine(cursor.row);
- if (!this.isAutoInsertedClosing(cursor, line, context.autoInsertedLineEnd[0]))
- context.autoInsertedBrackets = 0;
- context.autoInsertedRow = cursor.row;
- context.autoInsertedLineEnd = bracket + line.substr(cursor.column);
- context.autoInsertedBrackets++;
-};
-
-LatexBehaviour.isAutoInsertedClosing = function(cursor, line, bracket) {
- return context.autoInsertedBrackets > 0 &&
- cursor.row === context.autoInsertedRow &&
- bracket === context.autoInsertedLineEnd[0] &&
- line.substr(cursor.column) === context.autoInsertedLineEnd;
-};
-
-LatexBehaviour.popAutoInsertedClosing = function() {
- context.autoInsertedLineEnd = context.autoInsertedLineEnd.substr(1);
- context.autoInsertedBrackets--;
-};
-
-
-oop.inherits(LatexBehaviour, Behaviour);
-
-exports.LatexBehaviour = LatexBehaviour;
-});
-
-ace.define("ace/mode/latex_beta",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/latex_highlight_rules","ace/mode/folding/latex","ace/range","ace/worker/worker_client","ace/mode/behaviour/latex"], function(require, exports, module) {
-"use strict";
-
-var oop = require("../lib/oop");
-var TextMode = require("./text").Mode;
-var LatexHighlightRules = require("./latex_highlight_rules").LatexHighlightRules;
-var LatexFoldMode = require("./folding/latex").FoldMode;
-var Range = require("../range").Range;
-var WorkerClient = require("ace/worker/worker_client").WorkerClient;
-var LatexBehaviour = require("./behaviour/latex").LatexBehaviour;
-
-var createLatexWorker = function (session) {
- var doc = session.getDocument();
- var selection = session.getSelection();
- var cursorAnchor = selection.lead;
-
- var savedRange = {};
- var suppressions = [];
- var hints = [];
- var changeHandler = null;
- var docChangePending = false;
- var firstPass = true;
-
- var worker = new WorkerClient(["ace"], "ace/mode/latex_beta_worker", "LatexWorker");
- worker.attachToDocument(doc);
- var docChangeHandler = doc.on("change", function () {
- docChangePending = true;
- if(changeHandler) {
- clearTimeout(changeHandler);
- changeHandler = null;
- }
- });
-
- var cursorHandler = selection.on("changeCursor", function () {
- if (docChangePending) { return; } ;
- changeHandler = setTimeout(function () {
- updateMarkers({cursorMoveOnly:true});
- suppressions = [];
- changeHandler = null;
- }, 100);
- });
-
- var updateMarkers = function (options) {
- if (!options) { options = {};};
- var cursorMoveOnly = options.cursorMoveOnly;
- var annotations = [];
- var newRange = {};
- var cursor = selection.getCursor();
- var maxRow = session.getLength() - 1;
- var maxCol = (maxRow > 0) ? session.getLine(maxRow).length : 0;
- var cursorAtEndOfDocument = (cursor.row == maxRow) && (cursor.column === maxCol);
-
- suppressions = [];
-
- for (var i = 0, len = hints.length; i 0) {
- var originalAnnotations = session.getAnnotations();
- session.setAnnotations(originalAnnotations.concat(annotations));
- };
- firstPass = false;
- } else {
- session.setAnnotations(annotations);
- }
- };
-
- };
- worker.on("lint", function(results) {
- if(docChangePending) { docChangePending = false; };
- hints = results.data;
- if (hints.length > 100) {
- hints = hints.slice(0, 100); // limit to 100 errors
- };
- updateMarkers();
- });
- worker.on("terminate", function() {
- if(changeHandler) {
- clearTimeout(changeHandler);
- changeHandler = null;
- }
- doc.off("change", docChangeHandler);
- selection.off("changeCursor", cursorHandler);
- for (var key in savedRange) {
- var range = savedRange[key];
- if (range.start !== cursorAnchor) { range.start.detach(); }
- if (range.end !== cursorAnchor) { range.end.detach(); }
- session.removeMarker(range.id);
- }
- savedRange = {};
- hints = [];
- suppressions = [];
- session.clearAnnotations();
- });
-
- return worker;
-};
-
-var Mode = function() {
- this.HighlightRules = LatexHighlightRules;
- this.foldingRules = new LatexFoldMode();
- this.$behaviour = new LatexBehaviour();
- this.createWorker = createLatexWorker;
-};
-oop.inherits(Mode, TextMode);
-
-(function() {
- this.type = "text";
-
- this.lineCommentStart = "%";
-
- this.$id = "ace/mode/latex_beta";
-}).call(Mode.prototype);
-
-exports.Mode = Mode;
-
-});
diff --git a/services/web/public/js/ace-1.2.5/worker-latex.js b/services/web/public/js/ace-1.2.5/worker-latex.js
index ccd7e2da9b..dc9f5d3204 100644
--- a/services/web/public/js/ace-1.2.5/worker-latex.js
+++ b/services/web/public/js/ace-1.2.5/worker-latex.js
@@ -1419,6 +1419,34 @@ var LatexWorker = exports.LatexWorker = function(sender) {
oop.inherits(LatexWorker, Mirror);
+(function() {
+ var disabled = false;
+ this.onUpdate = function() {
+ if (disabled) { return ; };
+
+ var value = this.doc.getValue();
+ var errors = [];
+ var contexts = [];
+ try {
+ if (value) {
+ var result = Parse(value);
+ errors = result.errors;
+ contexts = result.contexts;
+ }
+ } catch (e) {
+ console.log(e);
+ disabled = true;
+ this.sender.emit("fatal-error", e);
+ errors = [];
+ }
+ this.sender.emit("lint", {
+ errors: errors,
+ contexts: contexts
+ });
+ };
+
+}).call(LatexWorker.prototype);
+
var Tokenise = function (text) {
var Tokens = [];
var Comments = [];
@@ -1503,22 +1531,8 @@ var Tokenise = function (text) {
}
idx = SPECIAL.lastIndex = nextSpecialPos;
}
- } else if (code === "{") { // open group
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "}") { // close group
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "$") { // math mode
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "&") { // tabalign
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "#") { // macro parameter
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "^") { // superscript
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "_") { // subscript
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "~") { // active character (space)
- Tokens.push([lineNumber, code, pos]);
+ } else if (["{", "}", "$", "&", "#", "^", "_", "~"].indexOf(code) > -1) { // special characters
+ Tokens.push([lineNumber, code, pos, pos+1]);
} else {
throw "unrecognised character " + code;
}
@@ -1539,15 +1553,15 @@ var read1arg = function (TokeniseResult, k, options) {
};
var open = Tokens[k+1];
- var env = Tokens[k+2];
+ var delimiter = Tokens[k+2];
var close = Tokens[k+3];
- var envName;
+ var delimiterName;
if(open && open[1] === "\\") {
- envName = open[4]; // array element 4 is command sequence
+ delimiterName = open[4]; // array element 4 is command sequence
return k + 1;
- } else if(open && open[1] === "{" && env && env[1] === "\\" && close && close[1] === "}") {
- envName = env[4]; // NOTE: if we were actually using this, keep track of * above
+ } else if(open && open[1] === "{" && delimiter && delimiter[1] === "\\" && close && close[1] === "}") {
+ delimiterName = delimiter[4]; // NOTE: if we were actually using this, keep track of * above
return k + 3; // array element 4 is command sequence
} else {
return null;
@@ -1579,21 +1593,21 @@ var read1name = function (TokeniseResult, k) {
var text = TokeniseResult.text;
var open = Tokens[k+1];
- var env = Tokens[k+2];
+ var delimiter = Tokens[k+2];
var close = Tokens[k+3];
- if(open && open[1] === "{" && env && env[1] === "Text" && close && close[1] === "}") {
- var envName = text.substring(env[2], env[3]);
+ if(open && open[1] === "{" && delimiter && delimiter[1] === "Text" && close && close[1] === "}") {
+ var delimiterName = text.substring(delimiter[2], delimiter[3]);
return k + 3;
- } else if (open && open[1] === "{" && env && env[1] === "Text") {
- envName = "";
+ } else if (open && open[1] === "{" && delimiter && delimiter[1] === "Text") {
+ delimiterName = "";
for (var j = k + 2, tok; (tok = Tokens[j]); j++) {
if (tok[1] === "Text") {
var str = text.substring(tok[2], tok[3]);
if (!str.match(/^\S*$/)) { break; }
- envName = envName + str;
+ delimiterName = delimiterName + str;
} else if (tok[1] === "_") {
- envName = envName + "_";
+ delimiterName = delimiterName + "_";
} else {
break;
}
@@ -1644,6 +1658,7 @@ var readOptionalParams = function(TokeniseResult, k) {
};
var count = 0;
var nextToken = Tokens[k+1];
+ if (!nextToken) { return null };
var pos = nextToken[2];
for (var i = pos, end = text.length; i < end; i++) {
@@ -1788,7 +1803,7 @@ var InterpretTokens = function (TokeniseResult, ErrorReporter) {
var TokenErrorFromTo = ErrorReporter.TokenErrorFromTo;
var TokenError = ErrorReporter.TokenError;
- var Environments = new EnvHandler(ErrorReporter);
+ var Environments = new EnvHandler(TokeniseResult, ErrorReporter);
var nextGroupMathMode = null; // if the next group should have
var nextGroupMathModeStack = [] ; // tracking all nextGroupMathModes
@@ -1815,28 +1830,28 @@ var InterpretTokens = function (TokeniseResult, ErrorReporter) {
if (type === "\\") {
if (seq === "begin" || seq === "end") {
var open = Tokens[i+1];
- var env = Tokens[i+2];
+ var delimiter = Tokens[i+2];
var close = Tokens[i+3];
- if(open && open[1] === "{" && env && env[1] === "Text" && close && close[1] === "}") {
- var envName = text.substring(env[2], env[3]);
- Environments.push({command: seq, name: envName, token: token, closeToken: close});
+ if(open && open[1] === "{" && delimiter && delimiter[1] === "Text" && close && close[1] === "}") {
+ var delimiterName = text.substring(delimiter[2], delimiter[3]);
+ Environments.push({command: seq, name: delimiterName, token: token, closeToken: close});
i = i + 3; // advance past these tokens
} else {
- if (open && open[1] === "{" && env && env[1] === "Text") {
- envName = "";
+ if (open && open[1] === "{" && delimiter && delimiter[1] === "Text") {
+ delimiterName = "";
for (var j = i + 2, tok; (tok = Tokens[j]); j++) {
if (tok[1] === "Text") {
var str = text.substring(tok[2], tok[3]);
if (!str.match(/^\S*$/)) { break; }
- envName = envName + str;
+ delimiterName = delimiterName + str;
} else if (tok[1] === "_") {
- envName = envName + "_";
+ delimiterName = delimiterName + "_";
} else {
break;
}
}
if (tok && tok[1] === "}") {
- Environments.push({command: seq, name: envName, token: token, closeToken: close});
+ Environments.push({command: seq, name: delimiterName, token: token, closeToken: close});
i = j; // advance past these tokens
continue;
}
@@ -1844,8 +1859,8 @@ var InterpretTokens = function (TokeniseResult, ErrorReporter) {
var endToken = null;
if (open && open[1] === "{") {
endToken = open; // we've got a {
- if (env && env[1] === "Text") {
- endToken = env.slice(); // we've got some text following the {
+ if (delimiter && delimiter[1] === "Text") {
+ endToken = delimiter.slice(); // we've got some text following the {
start = endToken[2]; end = endToken[3];
for (j = start; j < end; j++) {
var char = text[j];
@@ -1978,7 +1993,12 @@ var InterpretTokens = function (TokeniseResult, ErrorReporter) {
var nextIsDollar = lookAhead && lookAhead[1] === "$";
currentMathMode = Environments.getMathMode() ; // returns null / $(inline) / $$(display)
if (nextIsDollar && (!currentMathMode || currentMathMode.command == "$$")) {
- Environments.push({command:"$$", token:token});
+ if (currentMathMode && currentMathMode.command == "$$") {
+ var delimiterToken = lookAhead;
+ } else {
+ var delimiterToken = token;
+ }
+ Environments.push({command:"$$", token:delimiterToken});
i = i + 1;
} else {
Environments.push({command:"$", token:token});
@@ -1999,74 +2019,174 @@ var InterpretTokens = function (TokeniseResult, ErrorReporter) {
return Environments;
};
-var EnvHandler = function (ErrorReporter) {
+var DocumentTree = function(TokeniseResult) {
+ var tree = {
+ children: []
+ };
+ var stack = [tree];
+
+ this.openEnv = function(startDelimiter) {
+ var currentNode = this.getCurrentNode();
+ var newNode = {
+ startDelimiter: startDelimiter,
+ children: []
+ };
+ currentNode.children.push(newNode);
+ stack.push(newNode);
+ };
+
+ this.closeEnv = function(endDelimiter) {
+ if (stack.length == 1) {
+ return null
+ }
+ var currentNode = stack.pop();
+ currentNode.endDelimiter = endDelimiter;
+ return currentNode.startDelimiter;
+ };
+
+ this.getNthPreviousNode = function(n) {
+ var offset = stack.length - n - 1;
+ if (offset < 0)
+ return null;
+ return stack[offset];
+ }
+
+ this.getCurrentNode = function() {
+ return this.getNthPreviousNode(0);
+ }
+
+ this.getCurrentDelimiter = function() {
+ return this.getCurrentNode().startDelimiter;
+ };
+
+ this.getPreviousDelimiter = function() {
+ var node = this.getNthPreviousNode(1);
+ if (!node)
+ return null
+ return node.startDelimiter;
+ }
+
+ this.getDepth = function() {
+ return (stack.length - 1) // Root node doesn't count
+ }
+
+ this.getContexts = function() {
+ var linePosition = TokeniseResult.linePosition;
+
+ function tokenToRange(token) {
+ var line = token[0], start = token[2], end = token[3];
+ var start_col = start - linePosition[line];
+ if (!end) { end = start + 1; } ;
+ var end_col = end - linePosition[line];
+ return {
+ start: {
+ row: line,
+ column: start_col
+ },
+ end: {
+ row: line,
+ column: end_col
+ }
+ }
+ };
+
+ function getContextsFromNode(node) {
+ if (node.startDelimiter && node.startDelimiter.mathMode) {
+ var context = {
+ type: "math",
+ range: {
+ start: tokenToRange(node.startDelimiter.token).start
+ }
+ };
+ if (node.endDelimiter) {
+ var closeToken = node.endDelimiter.closeToken || node.endDelimiter.token;
+ context.range.end = tokenToRange(closeToken).end;
+ };
+ return [context];
+ } else {
+ var contexts = [];
+ for (var i = 0; i < node.children.length; i++) {
+ var child = node.children[i];
+ contexts = contexts.concat(getContextsFromNode(child));
+ }
+ return contexts;
+ }
+ };
+
+ return getContextsFromNode(tree);
+ }
+}
+
+var EnvHandler = function (TokeniseResult, ErrorReporter) {
var ErrorTo = ErrorReporter.EnvErrorTo;
var ErrorFromTo = ErrorReporter.EnvErrorFromTo;
var ErrorFrom = ErrorReporter.EnvErrorFrom;
- var envs = [];
+ var delimiters = [];
- var state = [];
+ var document = new DocumentTree(TokeniseResult);
var documentClosed = null;
var inVerbatim = false;
var verbatimRanges = [];
-
- this.Environments = envs;
-
- this.push = function (newEnv) {
- this.setEnvProps(newEnv);
- this.checkAndUpdateState(newEnv);
- envs.push(newEnv);
+
+ this.getDocument = function() {
+ return document;
};
- this._endVerbatim = function (thisEnv) {
- var lastEnv = state.pop();
- if (lastEnv && lastEnv.name === thisEnv.name) {
+ this.push = function (newDelimiter) {
+ this.setDelimiterProps(newDelimiter);
+ this.checkAndUpdateState(newDelimiter);
+ delimiters.push(newDelimiter);
+ };
+
+ this._endVerbatim = function (thisDelimiter) {
+ var lastDelimiter = document.getCurrentDelimiter();
+ if (lastDelimiter && lastDelimiter.name === thisDelimiter.name) {
inVerbatim = false;
- verbatimRanges.push({start: lastEnv.token[2], end: thisEnv.token[2]});
- } else {
- if(lastEnv) { state.push(lastEnv); } ;
+ document.closeEnv(thisDelimiter);
+ verbatimRanges.push({start: lastDelimiter.token[2], end: thisDelimiter.token[2]});
}
};
var invalidEnvs = [];
- this._end = function (thisEnv) {
+ this._end = function (thisDelimiter) {
do {
- var lastEnv = state.pop();
+ var lastDelimiter = document.getCurrentDelimiter();
var retry = false;
var i;
- if (closedBy(lastEnv, thisEnv)) {
- if (thisEnv.command === "end" && thisEnv.name === "document" && !documentClosed) {
- documentClosed = thisEnv;
+ if (closedBy(lastDelimiter, thisDelimiter)) {
+ document.closeEnv(thisDelimiter);
+ if (thisDelimiter.command === "end" && thisDelimiter.name === "document" && !documentClosed) {
+ documentClosed = thisDelimiter;
};
return;
- } else if (!lastEnv) {
+ } else if (!lastDelimiter) {
if (documentClosed) {
- ErrorFromTo(documentClosed, thisEnv, "\\end{" + documentClosed.name + "} is followed by unexpected content",{errorAtStart: true, type: "info"});
+ ErrorFromTo(documentClosed, thisDelimiter, "\\end{" + documentClosed.name + "} is followed by unexpected content",{errorAtStart: true, type: "info"});
} else {
- ErrorTo(thisEnv, "unexpected " + getName(thisEnv));
+ ErrorTo(thisDelimiter, "unexpected " + getName(thisDelimiter));
}
- } else if (invalidEnvs.length > 0 && (i = indexOfClosingEnvInArray(invalidEnvs, thisEnv) > -1)) {
+ } else if (invalidEnvs.length > 0 && (i = indexOfClosingEnvInArray(invalidEnvs, thisDelimiter) > -1)) {
invalidEnvs.splice(i, 1);
- if (lastEnv) { state.push(lastEnv); } ;
return;
} else {
- var status = reportError(lastEnv, thisEnv);
- if (envPrecedence(lastEnv) < envPrecedence(thisEnv)) {
- invalidEnvs.push(lastEnv);
+ var status = reportError(lastDelimiter, thisDelimiter);
+ if (delimiterPrecedence(lastDelimiter) < delimiterPrecedence(thisDelimiter)) {
+ document.closeEnv();
+ invalidEnvs.push(lastDelimiter);
retry = true;
} else {
- var prevLastEnv = state.pop();
- if(prevLastEnv) {
- if (thisEnv.name === prevLastEnv.name) {
+ var prevDelimiter = document.getPreviousDelimiter();
+ if(prevDelimiter) {
+ if (thisDelimiter.name === prevDelimiter.name) {
+ document.closeEnv() // Close current env
+ document.closeEnv(thisDelimiter) // Close previous env
return;
- } else {
- state.push(prevLastEnv);
}
}
- invalidEnvs.push(lastEnv);
+ invalidEnvs.push(lastDelimiter);
}
}
@@ -2082,28 +2202,28 @@ var EnvHandler = function (ErrorReporter) {
"$$": "$$"
};
- var closedBy = function (lastEnv, thisEnv) {
- if (!lastEnv) {
+ var closedBy = function (lastDelimiter, thisDelimiter) {
+ if (!lastDelimiter) {
return false ;
- } else if (thisEnv.command === "end") {
- return lastEnv.command === "begin" && lastEnv.name === thisEnv.name;
- } else if (thisEnv.command === CLOSING_DELIMITER[lastEnv.command]) {
+ } else if (thisDelimiter.command === "end") {
+ return lastDelimiter.command === "begin" && lastDelimiter.name === thisDelimiter.name;
+ } else if (thisDelimiter.command === CLOSING_DELIMITER[lastDelimiter.command]) {
return true;
} else {
return false;
}
};
- var indexOfClosingEnvInArray = function (envs, thisEnv) {
- for (var i = 0, n = envs.length; i < n ; i++) {
- if (closedBy(envs[i], thisEnv)) {
+ var indexOfClosingEnvInArray = function (delimiters, thisDelimiter) {
+ for (var i = 0, n = delimiters.length; i < n ; i++) {
+ if (closedBy(delimiters[i], thisDelimiter)) {
return i;
}
}
return -1;
};
- var envPrecedence = function (env) {
+ var delimiterPrecedence = function (delimiter) {
var openScore = {
"{" : 1,
"left" : 2,
@@ -2118,14 +2238,14 @@ var EnvHandler = function (ErrorReporter) {
"$$" : 5,
"end": 4
};
- if (env.command) {
- return openScore[env.command] || closeScore[env.command];
+ if (delimiter.command) {
+ return openScore[delimiter.command] || closeScore[delimiter.command];
} else {
return 0;
}
};
- var getName = function(env) {
+ var getName = function(delimiter) {
var description = {
"{" : "open group {",
"}" : "close group }",
@@ -2138,12 +2258,12 @@ var EnvHandler = function (ErrorReporter) {
"left" : "\\left",
"right" : "\\right"
};
- if (env.command === "begin" || env.command === "end") {
- return "\\" + env.command + "{" + env.name + "}";
- } else if (env.command in description) {
- return description[env.command];
+ if (delimiter.command === "begin" || delimiter.command === "end") {
+ return "\\" + delimiter.command + "{" + delimiter.name + "}";
+ } else if (delimiter.command in description) {
+ return description[delimiter.command];
} else {
- return env.command;
+ return delimiter.command;
}
};
@@ -2151,81 +2271,81 @@ var EnvHandler = function (ErrorReporter) {
var UNCLOSED_GROUP = 2;
var UNCLOSED_ENV = 3;
- var reportError = function(lastEnv, thisEnv) {
- if (!lastEnv) { // unexpected close, nothing was open!
+ var reportError = function(lastDelimiter, thisDelimiter) {
+ if (!lastDelimiter) { // unexpected close, nothing was open!
if (documentClosed) {
- ErrorFromTo(documentClosed, thisEnv, "\\end{" + documentClosed.name + "} is followed by unexpected end group }",{errorAtStart: true, type: "info"});
+ ErrorFromTo(documentClosed, thisDelimiter, "\\end{" + documentClosed.name + "} is followed by unexpected end group }",{errorAtStart: true, type: "info"});
} else {
- ErrorTo(thisEnv, "unexpected " + getName(thisEnv));
+ ErrorTo(thisDelimiter, "unexpected " + getName(thisDelimiter));
};
return EXTRA_CLOSE;
- } else if (lastEnv.command === "{" && thisEnv.command === "end") {
- ErrorFromTo(lastEnv, thisEnv, "unclosed " + getName(lastEnv) + " found at " + getName(thisEnv),
+ } else if (lastDelimiter.command === "{" && thisDelimiter.command === "end") {
+ ErrorFromTo(lastDelimiter, thisDelimiter, "unclosed " + getName(lastDelimiter) + " found at " + getName(thisDelimiter),
{suppressIfEditing:true, errorAtStart: true, type:"warning"});
return UNCLOSED_GROUP;
} else {
- var pLast = envPrecedence(lastEnv);
- var pThis = envPrecedence(thisEnv);
+ var pLast = delimiterPrecedence(lastDelimiter);
+ var pThis = delimiterPrecedence(thisDelimiter);
if (pThis > pLast) {
- ErrorFromTo(lastEnv, thisEnv, "unclosed " + getName(lastEnv) + " found at " + getName(thisEnv),
+ ErrorFromTo(lastDelimiter, thisDelimiter, "unclosed " + getName(lastDelimiter) + " found at " + getName(thisDelimiter),
{suppressIfEditing:true, errorAtStart: true});
} else {
- ErrorFromTo(lastEnv, thisEnv, "unexpected " + getName(thisEnv) + " after " + getName(lastEnv));
+ ErrorFromTo(lastDelimiter, thisDelimiter, "unexpected " + getName(thisDelimiter) + " after " + getName(lastDelimiter));
}
return UNCLOSED_ENV;
};
};
- this._beginMathMode = function (thisEnv) {
+ this._beginMathMode = function (thisDelimiter) {
var currentMathMode = this.getMathMode(); // undefined, null, $, $$, name of mathmode env
if (currentMathMode) {
- ErrorFrom(thisEnv, getName(thisEnv) + " used inside existing math mode " + getName(currentMathMode),
+ ErrorFrom(thisDelimiter, getName(thisDelimiter) + " used inside existing math mode " + getName(currentMathMode),
{suppressIfEditing:true, errorAtStart: true, mathMode:true});
};
- thisEnv.mathMode = thisEnv;
- state.push(thisEnv);
+ thisDelimiter.mathMode = thisDelimiter;
+ document.openEnv(thisDelimiter);
};
- this._toggleMathMode = function (thisEnv) {
- var lastEnv = state.pop();
- if (closedBy(lastEnv, thisEnv)) {
+ this._toggleMathMode = function (thisDelimiter) {
+ var lastDelimiter = document.getCurrentDelimiter();
+ if (closedBy(lastDelimiter, thisDelimiter)) {
+ document.closeEnv(thisDelimiter)
return;
} else {
- if (lastEnv) {state.push(lastEnv);}
- if (lastEnv && lastEnv.mathMode) {
- this._end(thisEnv);
+ if (lastDelimiter && lastDelimiter.mathMode) {
+ this._end(thisDelimiter);
} else {
- thisEnv.mathMode = thisEnv;
- state.push(thisEnv);
+ thisDelimiter.mathMode = thisDelimiter;
+ document.openEnv(thisDelimiter);
}
};
};
this.getMathMode = function () {
- var n = state.length;
- if (n > 0) {
- return state[n-1].mathMode;
+ var currentDelimiter = document.getCurrentDelimiter();
+ if (currentDelimiter) {
+ return currentDelimiter.mathMode;
} else {
return null;
}
};
this.insideGroup = function () {
- var n = state.length;
- if (n > 0) {
- return (state[n-1].command === "{");
+ var currentDelimiter = document.getCurrentDelimiter();
+ if (currentDelimiter) {
+ return (currentDelimiter.command === "{");
} else {
return null;
}
};
var resetMathMode = function () {
- var n = state.length;
- if (n > 0) {
- var lastMathMode = state[n-1].mathMode;
+ var currentDelimiter = document.getCurrentDelimiter();
+ if (currentDelimiter) {
+ var lastMathMode = currentDelimiter.mathMode;
do {
- var lastEnv = state.pop();
- } while (lastEnv && lastEnv !== lastMathMode);
+ var lastDelimiter = document.closeEnv();
+ } while (lastDelimiter && lastDelimiter !== lastMathMode);
} else {
return;
}
@@ -2233,42 +2353,42 @@ var EnvHandler = function (ErrorReporter) {
this.resetMathMode = resetMathMode;
- var getNewMathMode = function (currentMathMode, thisEnv) {
+ var getNewMathMode = function (currentMathMode, thisDelimiter) {
var newMathMode = null;
- if (thisEnv.command === "{") {
- if (thisEnv.mathMode !== null) {
- newMathMode = thisEnv.mathMode;
+ if (thisDelimiter.command === "{") {
+ if (thisDelimiter.mathMode !== null) {
+ newMathMode = thisDelimiter.mathMode;
} else {
newMathMode = currentMathMode;
}
- } else if (thisEnv.command === "left") {
+ } else if (thisDelimiter.command === "left") {
if (currentMathMode === null) {
- ErrorFrom(thisEnv, "\\left can only be used in math mode", {mathMode: true});
+ ErrorFrom(thisDelimiter, "\\left can only be used in math mode", {mathMode: true});
};
newMathMode = currentMathMode;
- } else if (thisEnv.command === "begin") {
- var name = thisEnv.name;
+ } else if (thisDelimiter.command === "begin") {
+ var name = thisDelimiter.name;
if (name) {
if (name.match(/^(document|figure|center|enumerate|itemize|table|abstract|proof|lemma|theorem|definition|proposition|corollary|remark|notation|thebibliography)$/)) {
if (currentMathMode) {
- ErrorFromTo(currentMathMode, thisEnv, thisEnv.name + " used inside " + getName(currentMathMode),
+ ErrorFromTo(currentMathMode, thisDelimiter, thisDelimiter.name + " used inside " + getName(currentMathMode),
{suppressIfEditing:true, errorAtStart: true, mathMode: true});
resetMathMode();
};
newMathMode = null;
} else if (name.match(/^(array|gathered|split|aligned|alignedat)\*?$/)) {
if (currentMathMode === null) {
- ErrorFrom(thisEnv, thisEnv.name + " not inside math mode", {mathMode: true});
+ ErrorFrom(thisDelimiter, thisDelimiter.name + " not inside math mode", {mathMode: true});
};
newMathMode = currentMathMode;
} else if (name.match(/^(math|displaymath|equation|eqnarray|multline|align|gather|flalign|alignat)\*?$/)) {
if (currentMathMode) {
- ErrorFromTo(currentMathMode, thisEnv, thisEnv.name + " used inside " + getName(currentMathMode),
+ ErrorFromTo(currentMathMode, thisDelimiter, thisDelimiter.name + " used inside " + getName(currentMathMode),
{suppressIfEditing:true, errorAtStart: true, mathMode: true});
resetMathMode();
};
- newMathMode = thisEnv;
+ newMathMode = thisDelimiter;
} else {
newMathMode = undefined; // undefined means we don't know if we are in math mode or not
}
@@ -2277,41 +2397,41 @@ var EnvHandler = function (ErrorReporter) {
return newMathMode;
};
- this.checkAndUpdateState = function (thisEnv) {
+ this.checkAndUpdateState = function (thisDelimiter) {
if (inVerbatim) {
- if (thisEnv.command === "end") {
- this._endVerbatim(thisEnv);
+ if (thisDelimiter.command === "end") {
+ this._endVerbatim(thisDelimiter);
} else {
return; // ignore anything in verbatim environments
}
- } else if(thisEnv.command === "begin" || thisEnv.command === "{" || thisEnv.command === "left") {
- if (thisEnv.verbatim) {inVerbatim = true;};
+ } else if(thisDelimiter.command === "begin" || thisDelimiter.command === "{" || thisDelimiter.command === "left") {
+ if (thisDelimiter.verbatim) {inVerbatim = true;};
var currentMathMode = this.getMathMode(); // undefined, null, $, $$, name of mathmode env
- var newMathMode = getNewMathMode(currentMathMode, thisEnv);
- thisEnv.mathMode = newMathMode;
- state.push(thisEnv);
- } else if (thisEnv.command === "end") {
- this._end(thisEnv);
- } else if (thisEnv.command === "(" || thisEnv.command === "[") {
- this._beginMathMode(thisEnv);
- } else if (thisEnv.command === ")" || thisEnv.command === "]") {
- this._end(thisEnv);
- } else if (thisEnv.command === "}") {
- this._end(thisEnv);
- } else if (thisEnv.command === "right") {
- this._end(thisEnv);
- } else if (thisEnv.command === "$" || thisEnv.command === "$$") {
- this._toggleMathMode(thisEnv);
+ var newMathMode = getNewMathMode(currentMathMode, thisDelimiter);
+ thisDelimiter.mathMode = newMathMode;
+ document.openEnv(thisDelimiter);
+ } else if (thisDelimiter.command === "end") {
+ this._end(thisDelimiter);
+ } else if (thisDelimiter.command === "(" || thisDelimiter.command === "[") {
+ this._beginMathMode(thisDelimiter);
+ } else if (thisDelimiter.command === ")" || thisDelimiter.command === "]") {
+ this._end(thisDelimiter);
+ } else if (thisDelimiter.command === "}") {
+ this._end(thisDelimiter);
+ } else if (thisDelimiter.command === "right") {
+ this._end(thisDelimiter);
+ } else if (thisDelimiter.command === "$" || thisDelimiter.command === "$$") {
+ this._toggleMathMode(thisDelimiter);
}
};
this.close = function () {
- while (state.length > 0) {
- var thisEnv = state.pop();
- if (thisEnv.command === "{") {
- ErrorFrom(thisEnv, "unclosed group {", {type:"warning"});
+ while (document.getDepth() > 0) {
+ var thisDelimiter = document.closeEnv();
+ if (thisDelimiter.command === "{") {
+ ErrorFrom(thisDelimiter, "unclosed group {", {type:"warning"});
} else {
- ErrorFrom(thisEnv, "unclosed " + getName(thisEnv));
+ ErrorFrom(thisDelimiter, "unclosed " + getName(thisDelimiter));
}
}
var vlen = verbatimRanges.length;
@@ -2331,10 +2451,10 @@ var EnvHandler = function (ErrorReporter) {
}
};
- this.setEnvProps = function (env) {
- var name = env.name ;
+ this.setDelimiterProps = function (delimiter) {
+ var name = delimiter.name ;
if (name && name.match(/^(verbatim|boxedverbatim|lstlisting|minted|Verbatim)$/)) {
- env.verbatim = true;
+ delimiter.verbatim = true;
}
};
};
@@ -2458,9 +2578,9 @@ var ErrorReporter = function (TokeniseResult) {
errors.push(err);
};
- this.EnvErrorFrom = function (env, message, options) {
+ this.EnvErrorFrom = function (delimiter, message, options) {
if(!options) { options = {} ; };
- var token = env.token;
+ var token = delimiter.token;
var line = token[0], type = token[1], start = token[2], end = token[3];
var start_col = start - linePosition[line];
var end_col = Infinity;
@@ -2481,29 +2601,11 @@ var Parse = function (text) {
var Reporter = new ErrorReporter(TokeniseResult);
var Environments = InterpretTokens(TokeniseResult, Reporter);
Environments.close();
- return Reporter.getErrors();
+ return {
+ errors: Reporter.getErrors(),
+ contexts: Environments.getDocument().getContexts()
+ }
};
-
-(function() {
- var disabled = false;
-
- this.onUpdate = function() {
- if (disabled) { return ; };
-
- var value = this.doc.getValue();
- var errors = [];
- try {
- if (value)
- errors = Parse(value);
- } catch (e) {
- disabled = true;
- errors = [];
- }
- this.sender.emit("lint", errors);
- };
-
-}).call(LatexWorker.prototype);
-
});
ace.define("ace/lib/es5-shim",["require","exports","module"], function(require, exports, module) {
diff --git a/services/web/public/js/ace-1.2.5/worker-latex_beta.js b/services/web/public/js/ace-1.2.5/worker-latex_beta.js
deleted file mode 100644
index 720c3e5009..0000000000
--- a/services/web/public/js/ace-1.2.5/worker-latex_beta.js
+++ /dev/null
@@ -1,3207 +0,0 @@
-"no use strict";
-;(function(window) {
-if (typeof window.window != "undefined" && window.document)
- return;
-if (window.require && window.define)
- return;
-
-if (!window.console) {
- window.console = function() {
- var msgs = Array.prototype.slice.call(arguments, 0);
- postMessage({type: "log", data: msgs});
- };
- window.console.error =
- window.console.warn =
- window.console.log =
- window.console.trace = window.console;
-}
-window.window = window;
-window.ace = window;
-
-window.onerror = function(message, file, line, col, err) {
- postMessage({type: "error", data: {
- message: message,
- data: err.data,
- file: file,
- line: line,
- col: col,
- stack: err.stack
- }});
-};
-
-window.normalizeModule = function(parentId, moduleName) {
- // normalize plugin requires
- if (moduleName.indexOf("!") !== -1) {
- var chunks = moduleName.split("!");
- return window.normalizeModule(parentId, chunks[0]) + "!" + window.normalizeModule(parentId, chunks[1]);
- }
- // normalize relative requires
- if (moduleName.charAt(0) == ".") {
- var base = parentId.split("/").slice(0, -1).join("/");
- moduleName = (base ? base + "/" : "") + moduleName;
-
- while (moduleName.indexOf(".") !== -1 && previous != moduleName) {
- var previous = moduleName;
- moduleName = moduleName.replace(/^\.\//, "").replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, "");
- }
- }
-
- return moduleName;
-};
-
-window.require = function require(parentId, id) {
- if (!id) {
- id = parentId;
- parentId = null;
- }
- if (!id.charAt)
- throw new Error("worker.js require() accepts only (parentId, id) as arguments");
-
- id = window.normalizeModule(parentId, id);
-
- var module = window.require.modules[id];
- if (module) {
- if (!module.initialized) {
- module.initialized = true;
- module.exports = module.factory().exports;
- }
- return module.exports;
- }
-
- if (!window.require.tlns)
- return console.log("unable to load " + id);
-
- var path = resolveModuleId(id, window.require.tlns);
- if (path.slice(-3) != ".js") path += ".js";
-
- window.require.id = id;
- window.require.modules[id] = {}; // prevent infinite loop on broken modules
- importScripts(path);
- return window.require(parentId, id);
-};
-function resolveModuleId(id, paths) {
- var testPath = id, tail = "";
- while (testPath) {
- var alias = paths[testPath];
- if (typeof alias == "string") {
- return alias + tail;
- } else if (alias) {
- return alias.location.replace(/\/*$/, "/") + (tail || alias.main || alias.name);
- } else if (alias === false) {
- return "";
- }
- var i = testPath.lastIndexOf("/");
- if (i === -1) break;
- tail = testPath.substr(i) + tail;
- testPath = testPath.slice(0, i);
- }
- return id;
-}
-window.require.modules = {};
-window.require.tlns = {};
-
-window.define = function(id, deps, factory) {
- if (arguments.length == 2) {
- factory = deps;
- if (typeof id != "string") {
- deps = id;
- id = window.require.id;
- }
- } else if (arguments.length == 1) {
- factory = id;
- deps = [];
- id = window.require.id;
- }
-
- if (typeof factory != "function") {
- window.require.modules[id] = {
- exports: factory,
- initialized: true
- };
- return;
- }
-
- if (!deps.length)
- // If there is no dependencies, we inject "require", "exports" and
- // "module" as dependencies, to provide CommonJS compatibility.
- deps = ["require", "exports", "module"];
-
- var req = function(childId) {
- return window.require(id, childId);
- };
-
- window.require.modules[id] = {
- exports: {},
- factory: function() {
- var module = this;
- var returnExports = factory.apply(this, deps.map(function(dep) {
- switch (dep) {
- // Because "require", "exports" and "module" aren't actual
- // dependencies, we must handle them seperately.
- case "require": return req;
- case "exports": return module.exports;
- case "module": return module;
- // But for all other dependencies, we can just go ahead and
- // require them.
- default: return req(dep);
- }
- }));
- if (returnExports)
- module.exports = returnExports;
- return module;
- }
- };
-};
-window.define.amd = {};
-require.tlns = {};
-window.initBaseUrls = function initBaseUrls(topLevelNamespaces) {
- for (var i in topLevelNamespaces)
- require.tlns[i] = topLevelNamespaces[i];
-};
-
-window.initSender = function initSender() {
-
- var EventEmitter = window.require("ace/lib/event_emitter").EventEmitter;
- var oop = window.require("ace/lib/oop");
-
- var Sender = function() {};
-
- (function() {
-
- oop.implement(this, EventEmitter);
-
- this.callback = function(data, callbackId) {
- postMessage({
- type: "call",
- id: callbackId,
- data: data
- });
- };
-
- this.emit = function(name, data) {
- postMessage({
- type: "event",
- name: name,
- data: data
- });
- };
-
- }).call(Sender.prototype);
-
- return new Sender();
-};
-
-var main = window.main = null;
-var sender = window.sender = null;
-
-window.onmessage = function(e) {
- var msg = e.data;
- if (msg.event && sender) {
- sender._signal(msg.event, msg.data);
- }
- else if (msg.command) {
- if (main[msg.command])
- main[msg.command].apply(main, msg.args);
- else if (window[msg.command])
- window[msg.command].apply(window, msg.args);
- else
- throw new Error("Unknown command:" + msg.command);
- }
- else if (msg.init) {
- window.initBaseUrls(msg.tlns);
- require("ace/lib/es5-shim");
- sender = window.sender = window.initSender();
- var clazz = require(msg.module)[msg.classname];
- main = window.main = new clazz(sender);
- }
-};
-})(this);
-
-ace.define("ace/lib/oop",["require","exports","module"], function(require, exports, module) {
-"use strict";
-
-exports.inherits = function(ctor, superCtor) {
- ctor.super_ = superCtor;
- ctor.prototype = Object.create(superCtor.prototype, {
- constructor: {
- value: ctor,
- enumerable: false,
- writable: true,
- configurable: true
- }
- });
-};
-
-exports.mixin = function(obj, mixin) {
- for (var key in mixin) {
- obj[key] = mixin[key];
- }
- return obj;
-};
-
-exports.implement = function(proto, mixin) {
- exports.mixin(proto, mixin);
-};
-
-});
-
-ace.define("ace/range",["require","exports","module"], function(require, exports, module) {
-"use strict";
-var comparePoints = function(p1, p2) {
- return p1.row - p2.row || p1.column - p2.column;
-};
-var Range = function(startRow, startColumn, endRow, endColumn) {
- this.start = {
- row: startRow,
- column: startColumn
- };
-
- this.end = {
- row: endRow,
- column: endColumn
- };
-};
-
-(function() {
- this.isEqual = function(range) {
- return this.start.row === range.start.row &&
- this.end.row === range.end.row &&
- this.start.column === range.start.column &&
- this.end.column === range.end.column;
- };
- this.toString = function() {
- return ("Range: [" + this.start.row + "/" + this.start.column +
- "] -> [" + this.end.row + "/" + this.end.column + "]");
- };
-
- this.contains = function(row, column) {
- return this.compare(row, column) == 0;
- };
- this.compareRange = function(range) {
- var cmp,
- end = range.end,
- start = range.start;
-
- cmp = this.compare(end.row, end.column);
- if (cmp == 1) {
- cmp = this.compare(start.row, start.column);
- if (cmp == 1) {
- return 2;
- } else if (cmp == 0) {
- return 1;
- } else {
- return 0;
- }
- } else if (cmp == -1) {
- return -2;
- } else {
- cmp = this.compare(start.row, start.column);
- if (cmp == -1) {
- return -1;
- } else if (cmp == 1) {
- return 42;
- } else {
- return 0;
- }
- }
- };
- this.comparePoint = function(p) {
- return this.compare(p.row, p.column);
- };
- this.containsRange = function(range) {
- return this.comparePoint(range.start) == 0 && this.comparePoint(range.end) == 0;
- };
- this.intersects = function(range) {
- var cmp = this.compareRange(range);
- return (cmp == -1 || cmp == 0 || cmp == 1);
- };
- this.isEnd = function(row, column) {
- return this.end.row == row && this.end.column == column;
- };
- this.isStart = function(row, column) {
- return this.start.row == row && this.start.column == column;
- };
- this.setStart = function(row, column) {
- if (typeof row == "object") {
- this.start.column = row.column;
- this.start.row = row.row;
- } else {
- this.start.row = row;
- this.start.column = column;
- }
- };
- this.setEnd = function(row, column) {
- if (typeof row == "object") {
- this.end.column = row.column;
- this.end.row = row.row;
- } else {
- this.end.row = row;
- this.end.column = column;
- }
- };
- this.inside = function(row, column) {
- if (this.compare(row, column) == 0) {
- if (this.isEnd(row, column) || this.isStart(row, column)) {
- return false;
- } else {
- return true;
- }
- }
- return false;
- };
- this.insideStart = function(row, column) {
- if (this.compare(row, column) == 0) {
- if (this.isEnd(row, column)) {
- return false;
- } else {
- return true;
- }
- }
- return false;
- };
- this.insideEnd = function(row, column) {
- if (this.compare(row, column) == 0) {
- if (this.isStart(row, column)) {
- return false;
- } else {
- return true;
- }
- }
- return false;
- };
- this.compare = function(row, column) {
- if (!this.isMultiLine()) {
- if (row === this.start.row) {
- return column < this.start.column ? -1 : (column > this.end.column ? 1 : 0);
- }
- }
-
- if (row < this.start.row)
- return -1;
-
- if (row > this.end.row)
- return 1;
-
- if (this.start.row === row)
- return column >= this.start.column ? 0 : -1;
-
- if (this.end.row === row)
- return column <= this.end.column ? 0 : 1;
-
- return 0;
- };
- this.compareStart = function(row, column) {
- if (this.start.row == row && this.start.column == column) {
- return -1;
- } else {
- return this.compare(row, column);
- }
- };
- this.compareEnd = function(row, column) {
- if (this.end.row == row && this.end.column == column) {
- return 1;
- } else {
- return this.compare(row, column);
- }
- };
- this.compareInside = function(row, column) {
- if (this.end.row == row && this.end.column == column) {
- return 1;
- } else if (this.start.row == row && this.start.column == column) {
- return -1;
- } else {
- return this.compare(row, column);
- }
- };
- this.clipRows = function(firstRow, lastRow) {
- if (this.end.row > lastRow)
- var end = {row: lastRow + 1, column: 0};
- else if (this.end.row < firstRow)
- var end = {row: firstRow, column: 0};
-
- if (this.start.row > lastRow)
- var start = {row: lastRow + 1, column: 0};
- else if (this.start.row < firstRow)
- var start = {row: firstRow, column: 0};
-
- return Range.fromPoints(start || this.start, end || this.end);
- };
- this.extend = function(row, column) {
- var cmp = this.compare(row, column);
-
- if (cmp == 0)
- return this;
- else if (cmp == -1)
- var start = {row: row, column: column};
- else
- var end = {row: row, column: column};
-
- return Range.fromPoints(start || this.start, end || this.end);
- };
-
- this.isEmpty = function() {
- return (this.start.row === this.end.row && this.start.column === this.end.column);
- };
- this.isMultiLine = function() {
- return (this.start.row !== this.end.row);
- };
- this.clone = function() {
- return Range.fromPoints(this.start, this.end);
- };
- this.collapseRows = function() {
- if (this.end.column == 0)
- return new Range(this.start.row, 0, Math.max(this.start.row, this.end.row-1), 0)
- else
- return new Range(this.start.row, 0, this.end.row, 0)
- };
- this.toScreenRange = function(session) {
- var screenPosStart = session.documentToScreenPosition(this.start);
- var screenPosEnd = session.documentToScreenPosition(this.end);
-
- return new Range(
- screenPosStart.row, screenPosStart.column,
- screenPosEnd.row, screenPosEnd.column
- );
- };
- this.moveBy = function(row, column) {
- this.start.row += row;
- this.start.column += column;
- this.end.row += row;
- this.end.column += column;
- };
-
-}).call(Range.prototype);
-Range.fromPoints = function(start, end) {
- return new Range(start.row, start.column, end.row, end.column);
-};
-Range.comparePoints = comparePoints;
-
-Range.comparePoints = function(p1, p2) {
- return p1.row - p2.row || p1.column - p2.column;
-};
-
-
-exports.Range = Range;
-});
-
-ace.define("ace/apply_delta",["require","exports","module"], function(require, exports, module) {
-"use strict";
-
-function throwDeltaError(delta, errorText){
- console.log("Invalid Delta:", delta);
- throw "Invalid Delta: " + errorText;
-}
-
-function positionInDocument(docLines, position) {
- return position.row >= 0 && position.row < docLines.length &&
- position.column >= 0 && position.column <= docLines[position.row].length;
-}
-
-function validateDelta(docLines, delta) {
- if (delta.action != "insert" && delta.action != "remove")
- throwDeltaError(delta, "delta.action must be 'insert' or 'remove'");
- if (!(delta.lines instanceof Array))
- throwDeltaError(delta, "delta.lines must be an Array");
- if (!delta.start || !delta.end)
- throwDeltaError(delta, "delta.start/end must be an present");
- var start = delta.start;
- if (!positionInDocument(docLines, delta.start))
- throwDeltaError(delta, "delta.start must be contained in document");
- var end = delta.end;
- if (delta.action == "remove" && !positionInDocument(docLines, end))
- throwDeltaError(delta, "delta.end must contained in document for 'remove' actions");
- var numRangeRows = end.row - start.row;
- var numRangeLastLineChars = (end.column - (numRangeRows == 0 ? start.column : 0));
- if (numRangeRows != delta.lines.length - 1 || delta.lines[numRangeRows].length != numRangeLastLineChars)
- throwDeltaError(delta, "delta.range must match delta lines");
-}
-
-exports.applyDelta = function(docLines, delta, doNotValidate) {
-
- var row = delta.start.row;
- var startColumn = delta.start.column;
- var line = docLines[row] || "";
- switch (delta.action) {
- case "insert":
- var lines = delta.lines;
- if (lines.length === 1) {
- docLines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn);
- } else {
- var args = [row, 1].concat(delta.lines);
- docLines.splice.apply(docLines, args);
- docLines[row] = line.substring(0, startColumn) + docLines[row];
- docLines[row + delta.lines.length - 1] += line.substring(startColumn);
- }
- break;
- case "remove":
- var endColumn = delta.end.column;
- var endRow = delta.end.row;
- if (row === endRow) {
- docLines[row] = line.substring(0, startColumn) + line.substring(endColumn);
- } else {
- docLines.splice(
- row, endRow - row + 1,
- line.substring(0, startColumn) + docLines[endRow].substring(endColumn)
- );
- }
- break;
- }
-}
-});
-
-ace.define("ace/lib/event_emitter",["require","exports","module"], function(require, exports, module) {
-"use strict";
-
-var EventEmitter = {};
-var stopPropagation = function() { this.propagationStopped = true; };
-var preventDefault = function() { this.defaultPrevented = true; };
-
-EventEmitter._emit =
-EventEmitter._dispatchEvent = function(eventName, e) {
- this._eventRegistry || (this._eventRegistry = {});
- this._defaultHandlers || (this._defaultHandlers = {});
-
- var listeners = this._eventRegistry[eventName] || [];
- var defaultHandler = this._defaultHandlers[eventName];
- if (!listeners.length && !defaultHandler)
- return;
-
- if (typeof e != "object" || !e)
- e = {};
-
- if (!e.type)
- e.type = eventName;
- if (!e.stopPropagation)
- e.stopPropagation = stopPropagation;
- if (!e.preventDefault)
- e.preventDefault = preventDefault;
-
- listeners = listeners.slice();
- for (var i=0; i this.row)
- return;
-
- var point = $getTransformedPoint(delta, {row: this.row, column: this.column}, this.$insertRight);
- this.setPosition(point.row, point.column, true);
- };
-
- function $pointsInOrder(point1, point2, equalPointsInOrder) {
- var bColIsAfter = equalPointsInOrder ? point1.column <= point2.column : point1.column < point2.column;
- return (point1.row < point2.row) || (point1.row == point2.row && bColIsAfter);
- }
-
- function $getTransformedPoint(delta, point, moveIfEqual) {
- var deltaIsInsert = delta.action == "insert";
- var deltaRowShift = (deltaIsInsert ? 1 : -1) * (delta.end.row - delta.start.row);
- var deltaColShift = (deltaIsInsert ? 1 : -1) * (delta.end.column - delta.start.column);
- var deltaStart = delta.start;
- var deltaEnd = deltaIsInsert ? deltaStart : delta.end; // Collapse insert range.
- if ($pointsInOrder(point, deltaStart, moveIfEqual)) {
- return {
- row: point.row,
- column: point.column
- };
- }
- if ($pointsInOrder(deltaEnd, point, !moveIfEqual)) {
- return {
- row: point.row + deltaRowShift,
- column: point.column + (point.row == deltaEnd.row ? deltaColShift : 0)
- };
- }
-
- return {
- row: deltaStart.row,
- column: deltaStart.column
- };
- }
- this.setPosition = function(row, column, noClip) {
- var pos;
- if (noClip) {
- pos = {
- row: row,
- column: column
- };
- } else {
- pos = this.$clipPositionToDocument(row, column);
- }
-
- if (this.row == pos.row && this.column == pos.column)
- return;
-
- var old = {
- row: this.row,
- column: this.column
- };
-
- this.row = pos.row;
- this.column = pos.column;
- this._signal("change", {
- old: old,
- value: pos
- });
- };
- this.detach = function() {
- this.document.removeEventListener("change", this.$onChange);
- };
- this.attach = function(doc) {
- this.document = doc || this.document;
- this.document.on("change", this.$onChange);
- };
- this.$clipPositionToDocument = function(row, column) {
- var pos = {};
-
- if (row >= this.document.getLength()) {
- pos.row = Math.max(0, this.document.getLength() - 1);
- pos.column = this.document.getLine(pos.row).length;
- }
- else if (row < 0) {
- pos.row = 0;
- pos.column = 0;
- }
- else {
- pos.row = row;
- pos.column = Math.min(this.document.getLine(pos.row).length, Math.max(0, column));
- }
-
- if (column < 0)
- pos.column = 0;
-
- return pos;
- };
-
-}).call(Anchor.prototype);
-
-});
-
-ace.define("ace/document",["require","exports","module","ace/lib/oop","ace/apply_delta","ace/lib/event_emitter","ace/range","ace/anchor"], function(require, exports, module) {
-"use strict";
-
-var oop = require("./lib/oop");
-var applyDelta = require("./apply_delta").applyDelta;
-var EventEmitter = require("./lib/event_emitter").EventEmitter;
-var Range = require("./range").Range;
-var Anchor = require("./anchor").Anchor;
-
-var Document = function(textOrLines) {
- this.$lines = [""];
- if (textOrLines.length === 0) {
- this.$lines = [""];
- } else if (Array.isArray(textOrLines)) {
- this.insertMergedLines({row: 0, column: 0}, textOrLines);
- } else {
- this.insert({row: 0, column:0}, textOrLines);
- }
-};
-
-(function() {
-
- oop.implement(this, EventEmitter);
- this.setValue = function(text) {
- var len = this.getLength() - 1;
- this.remove(new Range(0, 0, len, this.getLine(len).length));
- this.insert({row: 0, column: 0}, text);
- };
- this.getValue = function() {
- return this.getAllLines().join(this.getNewLineCharacter());
- };
- this.createAnchor = function(row, column) {
- return new Anchor(this, row, column);
- };
- if ("aaa".split(/a/).length === 0) {
- this.$split = function(text) {
- return text.replace(/\r\n|\r/g, "\n").split("\n");
- };
- } else {
- this.$split = function(text) {
- return text.split(/\r\n|\r|\n/);
- };
- }
-
-
- this.$detectNewLine = function(text) {
- var match = text.match(/^.*?(\r\n|\r|\n)/m);
- this.$autoNewLine = match ? match[1] : "\n";
- this._signal("changeNewLineMode");
- };
- this.getNewLineCharacter = function() {
- switch (this.$newLineMode) {
- case "windows":
- return "\r\n";
- case "unix":
- return "\n";
- default:
- return this.$autoNewLine || "\n";
- }
- };
-
- this.$autoNewLine = "";
- this.$newLineMode = "auto";
- this.setNewLineMode = function(newLineMode) {
- if (this.$newLineMode === newLineMode)
- return;
-
- this.$newLineMode = newLineMode;
- this._signal("changeNewLineMode");
- };
- this.getNewLineMode = function() {
- return this.$newLineMode;
- };
- this.isNewLine = function(text) {
- return (text == "\r\n" || text == "\r" || text == "\n");
- };
- this.getLine = function(row) {
- return this.$lines[row] || "";
- };
- this.getLines = function(firstRow, lastRow) {
- return this.$lines.slice(firstRow, lastRow + 1);
- };
- this.getAllLines = function() {
- return this.getLines(0, this.getLength());
- };
- this.getLength = function() {
- return this.$lines.length;
- };
- this.getTextRange = function(range) {
- return this.getLinesForRange(range).join(this.getNewLineCharacter());
- };
- this.getLinesForRange = function(range) {
- var lines;
- if (range.start.row === range.end.row) {
- lines = [this.getLine(range.start.row).substring(range.start.column, range.end.column)];
- } else {
- lines = this.getLines(range.start.row, range.end.row);
- lines[0] = (lines[0] || "").substring(range.start.column);
- var l = lines.length - 1;
- if (range.end.row - range.start.row == l)
- lines[l] = lines[l].substring(0, range.end.column);
- }
- return lines;
- };
- this.insertLines = function(row, lines) {
- console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead.");
- return this.insertFullLines(row, lines);
- };
- this.removeLines = function(firstRow, lastRow) {
- console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead.");
- return this.removeFullLines(firstRow, lastRow);
- };
- this.insertNewLine = function(position) {
- console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead.");
- return this.insertMergedLines(position, ["", ""]);
- };
- this.insert = function(position, text) {
- if (this.getLength() <= 1)
- this.$detectNewLine(text);
-
- return this.insertMergedLines(position, this.$split(text));
- };
- this.insertInLine = function(position, text) {
- var start = this.clippedPos(position.row, position.column);
- var end = this.pos(position.row, position.column + text.length);
-
- this.applyDelta({
- start: start,
- end: end,
- action: "insert",
- lines: [text]
- }, true);
-
- return this.clonePos(end);
- };
-
- this.clippedPos = function(row, column) {
- var length = this.getLength();
- if (row === undefined) {
- row = length;
- } else if (row < 0) {
- row = 0;
- } else if (row >= length) {
- row = length - 1;
- column = undefined;
- }
- var line = this.getLine(row);
- if (column == undefined)
- column = line.length;
- column = Math.min(Math.max(column, 0), line.length);
- return {row: row, column: column};
- };
-
- this.clonePos = function(pos) {
- return {row: pos.row, column: pos.column};
- };
-
- this.pos = function(row, column) {
- return {row: row, column: column};
- };
-
- this.$clipPosition = function(position) {
- var length = this.getLength();
- if (position.row >= length) {
- position.row = Math.max(0, length - 1);
- position.column = this.getLine(length - 1).length;
- } else {
- position.row = Math.max(0, position.row);
- position.column = Math.min(Math.max(position.column, 0), this.getLine(position.row).length);
- }
- return position;
- };
- this.insertFullLines = function(row, lines) {
- row = Math.min(Math.max(row, 0), this.getLength());
- var column = 0;
- if (row < this.getLength()) {
- lines = lines.concat([""]);
- column = 0;
- } else {
- lines = [""].concat(lines);
- row--;
- column = this.$lines[row].length;
- }
- this.insertMergedLines({row: row, column: column}, lines);
- };
- this.insertMergedLines = function(position, lines) {
- var start = this.clippedPos(position.row, position.column);
- var end = {
- row: start.row + lines.length - 1,
- column: (lines.length == 1 ? start.column : 0) + lines[lines.length - 1].length
- };
-
- this.applyDelta({
- start: start,
- end: end,
- action: "insert",
- lines: lines
- });
-
- return this.clonePos(end);
- };
- this.remove = function(range) {
- var start = this.clippedPos(range.start.row, range.start.column);
- var end = this.clippedPos(range.end.row, range.end.column);
- this.applyDelta({
- start: start,
- end: end,
- action: "remove",
- lines: this.getLinesForRange({start: start, end: end})
- });
- return this.clonePos(start);
- };
- this.removeInLine = function(row, startColumn, endColumn) {
- var start = this.clippedPos(row, startColumn);
- var end = this.clippedPos(row, endColumn);
-
- this.applyDelta({
- start: start,
- end: end,
- action: "remove",
- lines: this.getLinesForRange({start: start, end: end})
- }, true);
-
- return this.clonePos(start);
- };
- this.removeFullLines = function(firstRow, lastRow) {
- firstRow = Math.min(Math.max(0, firstRow), this.getLength() - 1);
- lastRow = Math.min(Math.max(0, lastRow ), this.getLength() - 1);
- var deleteFirstNewLine = lastRow == this.getLength() - 1 && firstRow > 0;
- var deleteLastNewLine = lastRow < this.getLength() - 1;
- var startRow = ( deleteFirstNewLine ? firstRow - 1 : firstRow );
- var startCol = ( deleteFirstNewLine ? this.getLine(startRow).length : 0 );
- var endRow = ( deleteLastNewLine ? lastRow + 1 : lastRow );
- var endCol = ( deleteLastNewLine ? 0 : this.getLine(endRow).length );
- var range = new Range(startRow, startCol, endRow, endCol);
- var deletedLines = this.$lines.slice(firstRow, lastRow + 1);
-
- this.applyDelta({
- start: range.start,
- end: range.end,
- action: "remove",
- lines: this.getLinesForRange(range)
- });
- return deletedLines;
- };
- this.removeNewLine = function(row) {
- if (row < this.getLength() - 1 && row >= 0) {
- this.applyDelta({
- start: this.pos(row, this.getLine(row).length),
- end: this.pos(row + 1, 0),
- action: "remove",
- lines: ["", ""]
- });
- }
- };
- this.replace = function(range, text) {
- if (!(range instanceof Range))
- range = Range.fromPoints(range.start, range.end);
- if (text.length === 0 && range.isEmpty())
- return range.start;
- if (text == this.getTextRange(range))
- return range.end;
-
- this.remove(range);
- var end;
- if (text) {
- end = this.insert(range.start, text);
- }
- else {
- end = range.start;
- }
-
- return end;
- };
- this.applyDeltas = function(deltas) {
- for (var i=0; i=0; i--) {
- this.revertDelta(deltas[i]);
- }
- };
- this.applyDelta = function(delta, doNotValidate) {
- var isInsert = delta.action == "insert";
- if (isInsert ? delta.lines.length <= 1 && !delta.lines[0]
- : !Range.comparePoints(delta.start, delta.end)) {
- return;
- }
-
- if (isInsert && delta.lines.length > 20000)
- this.$splitAndapplyLargeDelta(delta, 20000);
- applyDelta(this.$lines, delta, doNotValidate);
- this._signal("change", delta);
- };
-
- this.$splitAndapplyLargeDelta = function(delta, MAX) {
- var lines = delta.lines;
- var l = lines.length;
- var row = delta.start.row;
- var column = delta.start.column;
- var from = 0, to = 0;
- do {
- from = to;
- to += MAX - 1;
- var chunk = lines.slice(from, to);
- if (to > l) {
- delta.lines = chunk;
- delta.start.row = row + from;
- delta.start.column = column;
- break;
- }
- chunk.push("");
- this.applyDelta({
- start: this.pos(row + from, column),
- end: this.pos(row + to, column = 0),
- action: delta.action,
- lines: chunk
- }, true);
- } while(true);
- };
- this.revertDelta = function(delta) {
- this.applyDelta({
- start: this.clonePos(delta.start),
- end: this.clonePos(delta.end),
- action: (delta.action == "insert" ? "remove" : "insert"),
- lines: delta.lines.slice()
- });
- };
- this.indexToPosition = function(index, startRow) {
- var lines = this.$lines || this.getAllLines();
- var newlineLength = this.getNewLineCharacter().length;
- for (var i = startRow || 0, l = lines.length; i < l; i++) {
- index -= lines[i].length + newlineLength;
- if (index < 0)
- return {row: i, column: index + lines[i].length + newlineLength};
- }
- return {row: l-1, column: lines[l-1].length};
- };
- this.positionToIndex = function(pos, startRow) {
- var lines = this.$lines || this.getAllLines();
- var newlineLength = this.getNewLineCharacter().length;
- var index = 0;
- var row = Math.min(pos.row, lines.length);
- for (var i = startRow || 0; i < row; ++i)
- index += lines[i].length + newlineLength;
-
- return index + pos.column;
- };
-
-}).call(Document.prototype);
-
-exports.Document = Document;
-});
-
-ace.define("ace/lib/lang",["require","exports","module"], function(require, exports, module) {
-"use strict";
-
-exports.last = function(a) {
- return a[a.length - 1];
-};
-
-exports.stringReverse = function(string) {
- return string.split("").reverse().join("");
-};
-
-exports.stringRepeat = function (string, count) {
- var result = '';
- while (count > 0) {
- if (count & 1)
- result += string;
-
- if (count >>= 1)
- string += string;
- }
- return result;
-};
-
-var trimBeginRegexp = /^\s\s*/;
-var trimEndRegexp = /\s\s*$/;
-
-exports.stringTrimLeft = function (string) {
- return string.replace(trimBeginRegexp, '');
-};
-
-exports.stringTrimRight = function (string) {
- return string.replace(trimEndRegexp, '');
-};
-
-exports.copyObject = function(obj) {
- var copy = {};
- for (var key in obj) {
- copy[key] = obj[key];
- }
- return copy;
-};
-
-exports.copyArray = function(array){
- var copy = [];
- for (var i=0, l=array.length; i MAX_TOKENS) {
- throw new Error("exceed max token count of " + MAX_TOKENS);
- break;
- };
- var result = SPECIAL.exec(text);
- if (result == null) {
- if (idx < text.length) {
- Tokens.push([lineNumber, "Text", idx, text.length]);
- }
- break;
- }
- if (result && result.index <= pos) {
- throw new Error("infinite loop in parsing");
- break;
- };
- pos = result.index;
- if (pos > idx) {
- Tokens.push([lineNumber, "Text", idx, pos]);
- }
- for (var i = idx; i < pos; i++) {
- if (text[i] === "\n") {
- lineNumber++;
- linePosition[lineNumber] = i+1;
- }
- }
-
- var newIdx = SPECIAL.lastIndex;
- idx = newIdx;
- var code = result[0];
- if (code === "%") { // comment character
- var newLinePos = text.indexOf("\n", idx);
- if (newLinePos === -1) {
- newLinePos = text.length;
- };
- var commentString = text.substring(idx, newLinePos);
- if (commentString.indexOf("%novalidate") === 0) {
- return [];
- } else if(!checkingDisabled && commentString.indexOf("%begin novalidate") === 0) {
- checkingDisabled = true;
- } else if (checkingDisabled && commentString.indexOf("%end novalidate") === 0) {
- checkingDisabled = false;
- };
- idx = SPECIAL.lastIndex = newLinePos + 1;
- Comments.push([lineNumber, idx, newLinePos]);
- lineNumber++;
- linePosition[lineNumber] = idx;
- } else if (checkingDisabled) {
- continue;
- } else if (code === '\\') { // escape character
- NEXTCS.lastIndex = idx;
- var controlSequence = NEXTCS.exec(text);
- var nextSpecialPos = controlSequence === null ? idx : controlSequence.index;
- if (nextSpecialPos === idx) {
- Tokens.push([lineNumber, code, pos, idx + 1, text[idx], "control-symbol"]);
- idx = SPECIAL.lastIndex = idx + 1;
- char = text[nextSpecialPos];
- if (char === '\n') { lineNumber++; linePosition[lineNumber] = nextSpecialPos;};
- } else {
- Tokens.push([lineNumber, code, pos, nextSpecialPos, text.slice(idx, nextSpecialPos)]);
- var char;
- while ((char = text[nextSpecialPos]) === ' ' || char === '\t' || char === '\r' || char === '\n') {
- nextSpecialPos++;
- if (char === '\n') { lineNumber++; linePosition[lineNumber] = nextSpecialPos;};
- }
- idx = SPECIAL.lastIndex = nextSpecialPos;
- }
- } else if (code === "{") { // open group
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "}") { // close group
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "$") { // math mode
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "&") { // tabalign
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "#") { // macro parameter
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "^") { // superscript
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "_") { // subscript
- Tokens.push([lineNumber, code, pos]);
- } else if (code === "~") { // active character (space)
- Tokens.push([lineNumber, code, pos]);
- } else {
- throw "unrecognised character " + code;
- }
- }
-
- return {tokens: Tokens, comments: Comments, linePosition: linePosition, lineNumber: lineNumber, text: text};
-};
-
-var read1arg = function (TokeniseResult, k, options) {
- var Tokens = TokeniseResult.tokens;
- var text = TokeniseResult.text;
- if (options && options.allowStar) {
- var optional = Tokens[k+1];
- if (optional && optional[1] === "Text") {
- var optionalstr = text.substring(optional[2], optional[3]);
- if (optionalstr === "*") { k++;}
- };
- };
-
- var open = Tokens[k+1];
- var env = Tokens[k+2];
- var close = Tokens[k+3];
- var envName;
-
- if(open && open[1] === "\\") {
- envName = open[4]; // array element 4 is command sequence
- return k + 1;
- } else if(open && open[1] === "{" && env && env[1] === "\\" && close && close[1] === "}") {
- envName = env[4]; // NOTE: if we were actually using this, keep track of * above
- return k + 3; // array element 4 is command sequence
- } else {
- return null;
- }
-};
-
-var readLetDefinition = function (TokeniseResult, k) {
-
- var Tokens = TokeniseResult.tokens;
- var text = TokeniseResult.text;
-
- var first = Tokens[k+1];
- var second = Tokens[k+2];
- var third = Tokens[k+3];
-
- if(first && first[1] === "\\" && second && second[1] === "\\") {
- return k + 2;
- } else if(first && first[1] === "\\" &&
- second && second[1] === "Text" && text.substring(second[2], second[3]) === "=" &&
- third && third[1] === "\\") {
- return k + 3;
- } else {
- return null;
- }
-};
-
-var read1name = function (TokeniseResult, k) {
- var Tokens = TokeniseResult.tokens;
- var text = TokeniseResult.text;
-
- var open = Tokens[k+1];
- var env = Tokens[k+2];
- var close = Tokens[k+3];
-
- if(open && open[1] === "{" && env && env[1] === "Text" && close && close[1] === "}") {
- var envName = text.substring(env[2], env[3]);
- return k + 3;
- } else if (open && open[1] === "{" && env && env[1] === "Text") {
- envName = "";
- for (var j = k + 2, tok; (tok = Tokens[j]); j++) {
- if (tok[1] === "Text") {
- var str = text.substring(tok[2], tok[3]);
- if (!str.match(/^\S*$/)) { break; }
- envName = envName + str;
- } else if (tok[1] === "_") {
- envName = envName + "_";
- } else {
- break;
- }
- }
- if (tok && tok[1] === "}") {
- return j; // advance past these tokens
- } else {
- return null;
- }
- } else {
- return null;
- }
-};
-
-var read1filename = function (TokeniseResult, k) {
- var Tokens = TokeniseResult.tokens;
- var text = TokeniseResult.text;
-
- var fileName = "";
- for (var j = k + 1, tok; (tok = Tokens[j]); j++) {
- if (tok[1] === "Text") {
- var str = text.substring(tok[2], tok[3]);
- if (!str.match(/^\S*$/)) { break; }
- fileName = fileName + str;
- } else if (tok[1] === "_") {
- fileName = fileName + "_";
- } else {
- break;
- }
- }
- if (fileName.length > 0) {
- return j; // advance past these tokens
- } else {
- return null;
- }
-};
-
-var readOptionalParams = function(TokeniseResult, k) {
- var Tokens = TokeniseResult.tokens;
- var text = TokeniseResult.text;
-
- var params = Tokens[k+1];
-
- if(params && params[1] === "Text") {
- var paramNum = text.substring(params[2], params[3]);
- if (paramNum.match(/^\[\d+\](\[[^\]]*\])*\s*$/)) {
- return k + 1; // got it
- };
- };
- var count = 0;
- var nextToken = Tokens[k+1];
- var pos = nextToken[2];
-
- for (var i = pos, end = text.length; i < end; i++) {
- var char = text[i];
- if (nextToken && i >= nextToken[2]) { k++; nextToken = Tokens[k+1];};
- if (char === "[") { count++; }
- if (char === "]") { count--; }
- if (count === 0 && char === "{") { return k - 1; }
- if (count > 0 && (char === '\r' || char === '\n')) { return null; }
- };
- return null;
-};
-
-var readOptionalGeneric = function(TokeniseResult, k) {
- var Tokens = TokeniseResult.tokens;
- var text = TokeniseResult.text;
-
- var params = Tokens[k+1];
-
- if(params && params[1] === "Text") {
- var paramNum = text.substring(params[2], params[3]);
- if (paramNum.match(/^(\[[^\]]*\])+\s*$/)) {
- return k + 1; // got it
- };
- };
- return null;
-};
-
-var readOptionalDef = function (TokeniseResult, k) {
- var Tokens = TokeniseResult.tokens;
- var text = TokeniseResult.text;
-
- var defToken = Tokens[k];
- var pos = defToken[3];
-
- var openBrace = "{";
- var nextToken = Tokens[k+1];
- for (var i = pos, end = text.length; i < end; i++) {
- var char = text[i];
- if (nextToken && i >= nextToken[2]) { k++; nextToken = Tokens[k+1];};
- if (char === openBrace) { return k - 1; }; // move back to the last token of the optional arguments
- if (char === '\r' || char === '\n') { return null; }
- };
-
- return null;
-
-};
-
-var readDefinition = function(TokeniseResult, k) {
- var Tokens = TokeniseResult.tokens;
- var text = TokeniseResult.text;
-
- k = k + 1;
- var count = 0;
- var nextToken = Tokens[k];
- while (nextToken && nextToken[1] === "Text") {
- var start = nextToken[2], end = nextToken[3];
- for (var i = start; i < end; i++) {
- var char = text[i];
- if (char === ' ' || char === '\t' || char === '\r' || char === '\n') { continue; }
- return null; // bail out, should begin with a {
- }
- k++;
- nextToken = Tokens[k];
- }
- if (nextToken && nextToken[1] === "{") {
- count++;
- while (count>0) {
- k++;
- nextToken = Tokens[k];
- if(!nextToken) { break; };
- if (nextToken[1] === "}") { count--; }
- if (nextToken[1] === "{") { count++; }
- }
- return k;
- }
-
- return null;
-};
-
-var readVerb = function(TokeniseResult, k) {
-
- var Tokens = TokeniseResult.tokens;
- var text = TokeniseResult.text;
-
- var verbToken = Tokens[k];
- var verbStr = text.substring(verbToken[2], verbToken[3]);
- var pos = verbToken[3];
- if (text[pos] === "*") { pos++; } // \verb* form of command
- var delimiter = text[pos];
- pos++;
-
- var nextToken = Tokens[k+1];
- for (var i = pos, end = text.length; i < end; i++) {
- var char = text[i];
- if (nextToken && i >= nextToken[2]) { k++; nextToken = Tokens[k+1];};
- if (char === delimiter) { return k; };
- if (char === '\r' || char === '\n') { return null; }
- };
-
- return null;
-};
-
-var readUrl = function(TokeniseResult, k) {
-
- var Tokens = TokeniseResult.tokens;
- var text = TokeniseResult.text;
-
- var urlToken = Tokens[k];
- var urlStr = text.substring(urlToken[2], urlToken[3]);
- var pos = urlToken[3];
- var openDelimiter = text[pos];
- var closeDelimiter = (openDelimiter === "{") ? "}" : openDelimiter;
- var nextToken = Tokens[k+1];
- if (nextToken && pos === nextToken[2]) {
- k++;
- nextToken = Tokens[k+1];
- };
- pos++;
-
- var count = 1;
- for (var i = pos, end = text.length; count > 0 && i < end; i++) {
- var char = text[i];
- if (nextToken && i >= nextToken[2]) { k++; nextToken = Tokens[k+1];};
- if (char === closeDelimiter) {
- count--;
- } else if (char === openDelimiter) {
- count++;
- };
- if (count === 0) { return k; };
- if (char === '\r' || char === '\n') { return null; }
- };
-
- return null;
-};
-
-var InterpretTokens = function (TokeniseResult, ErrorReporter) {
- var Tokens = TokeniseResult.tokens;
- var linePosition = TokeniseResult.linePosition;
- var lineNumber = TokeniseResult.lineNumber;
- var text = TokeniseResult.text;
-
- var TokenErrorFromTo = ErrorReporter.TokenErrorFromTo;
- var TokenError = ErrorReporter.TokenError;
- var Environments = new EnvHandler(ErrorReporter);
-
- var nextGroupMathMode = null; // if the next group should have math mode on or off (for \hbox)
- var nextGroupMathModeStack = [] ; // tracking all nextGroupMathModes
- var seenUserDefinedBeginEquation = false; // if we have seen macros like \beq
- var seenUserDefinedEndEquation = false; // if we have seen macros like \eeq
-
- for (var i = 0, len = Tokens.length; i < len; i++) {
- var token = Tokens[i];
- var line = token[0], type = token[1], start = token[2], end = token[3], seq = token[4];
-
- if (type === "{") {
- Environments.push({command:"{", token:token, mathMode: nextGroupMathMode});
- nextGroupMathModeStack.push(nextGroupMathMode);
- nextGroupMathMode = null;
- continue;
- } else if (type === "}") {
- Environments.push({command:"}", token:token});
- nextGroupMathMode = nextGroupMathModeStack.pop();
- continue;
- } else {
- nextGroupMathMode = null;
- };
-
- if (type === "\\") {
- if (seq === "begin" || seq === "end") {
- var open = Tokens[i+1];
- var env = Tokens[i+2];
- var close = Tokens[i+3];
- if(open && open[1] === "{" && env && env[1] === "Text" && close && close[1] === "}") {
- var envName = text.substring(env[2], env[3]);
- Environments.push({command: seq, name: envName, token: token, closeToken: close});
- i = i + 3; // advance past these tokens
- } else {
- if (open && open[1] === "{" && env && env[1] === "Text") {
- envName = "";
- for (var j = i + 2, tok; (tok = Tokens[j]); j++) {
- if (tok[1] === "Text") {
- var str = text.substring(tok[2], tok[3]);
- if (!str.match(/^\S*$/)) { break; }
- envName = envName + str;
- } else if (tok[1] === "_") {
- envName = envName + "_";
- } else {
- break;
- }
- }
- if (tok && tok[1] === "}") {
- Environments.push({command: seq, name: envName, token: token, closeToken: close});
- i = j; // advance past these tokens
- continue;
- }
- }
- var endToken = null;
- if (open && open[1] === "{") {
- endToken = open; // we've got a {
- if (env && env[1] === "Text") {
- endToken = env.slice(); // we've got some text following the {
- start = endToken[2]; end = endToken[3];
- for (j = start; j < end; j++) {
- var char = text[j];
- if (char === ' ' || char === '\t' || char === '\r' || char === '\n') { break; }
- }
- endToken[3] = j; // the end of partial token is as far as we got looking ahead
- };
- };
-
- if (endToken) {
- TokenErrorFromTo(token, endToken, "invalid environment command " + text.substring(token[2], endToken[3] || endToken[2]));
- } else {
- TokenError(token, "invalid environment command");
- };
- }
- } else if (typeof seq === "string" && seq.match(/^(be|beq|beqa|bea)$/i)) {
- seenUserDefinedBeginEquation = true;
- } else if (typeof seq === "string" && seq.match(/^(ee|eeq|eeqn|eeqa|eeqan|eea)$/i)) {
- seenUserDefinedEndEquation = true;
- } else if (seq === "newcommand" || seq === "renewcommand" || seq === "DeclareRobustCommand") {
- var newPos = read1arg(TokeniseResult, i, {allowStar: true});
- if (newPos === null) { continue; } else {i = newPos;};
- newPos = readOptionalParams(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
-
- } else if (seq === "def") {
- newPos = read1arg(TokeniseResult, i);
- if (newPos === null) { continue; } else {i = newPos;};
- newPos = readOptionalDef(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
-
- } else if (seq === "let") {
- newPos = readLetDefinition(TokeniseResult, i);
- if (newPos === null) { continue; } else {i = newPos;};
-
- } else if (seq === "newcolumntype") {
- newPos = read1name(TokeniseResult, i);
- if (newPos === null) { continue; } else {i = newPos;};
- newPos = readOptionalParams(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
-
- } else if (seq === "newenvironment" || seq === "renewenvironment") {
- newPos = read1name(TokeniseResult, i);
- if (newPos === null) { continue; } else {i = newPos;};
- newPos = readOptionalParams(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- } else if (seq === "verb") {
- newPos = readVerb(TokeniseResult, i);
- if (newPos === null) { TokenError(token, "invalid verbatim command"); } else {i = newPos;};
- } else if (seq === "url") {
- newPos = readUrl(TokeniseResult, i);
- if (newPos === null) { TokenError(token, "invalid url command"); } else {i = newPos;};
- } else if (seq === "left" || seq === "right") {
- var nextToken = Tokens[i+1];
- char = "";
- if (nextToken && nextToken[1] === "Text") {
- char = text.substring(nextToken[2], nextToken[2] + 1);
- } else if (nextToken && nextToken[1] === "\\" && nextToken[5] == "control-symbol") {
- char = nextToken[4];
- } else if (nextToken && nextToken[1] === "\\") {
- char = "unknown";
- }
- if (char === "" || (char !== "unknown" && "(){}[]<>/|\\.".indexOf(char) === -1)) {
- TokenError(token, "invalid bracket command");
- } else {
- i = i + 1;
- Environments.push({command:seq, token:token});
- };
- } else if (seq === "(" || seq === ")" || seq === "[" || seq === "]") {
- Environments.push({command:seq, token:token});
- } else if (seq === "input") {
- newPos = read1filename(TokeniseResult, i);
- if (newPos === null) { continue; } else {i = newPos;};
- } else if (seq === "hbox" || seq === "text" || seq === "mbox" || seq === "footnote" || seq === "intertext" || seq === "shortintertext" || seq === "textnormal" || seq === "tag" || seq === "reflectbox" || seq === "textrm") {
- nextGroupMathMode = false;
- } else if (seq === "rotatebox" || seq === "scalebox") {
- newPos = readOptionalGeneric(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- nextGroupMathMode = false;
- } else if (seq === "resizebox") {
- newPos = readOptionalGeneric(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
-
- nextGroupMathMode = false;
- } else if (seq === "DeclareMathOperator") {
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- } else if (seq === "DeclarePairedDelimiter") {
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- newPos = readDefinition(TokeniseResult, i);
- if (newPos === null) { /* do nothing */ } else {i = newPos;};
- } else if (typeof seq === "string" && seq.match(/^(alpha|beta|gamma|delta|epsilon|varepsilon|zeta|eta|theta|vartheta|iota|kappa|lambda|mu|nu|xi|pi|varpi|rho|varrho|sigma|varsigma|tau|upsilon|phi|varphi|chi|psi|omega|Gamma|Delta|Theta|Lambda|Xi|Pi|Sigma|Upsilon|Phi|Psi|Omega)$/)) {
- var currentMathMode = Environments.getMathMode() ; // returns null / $(inline) / $$(display)
- if (currentMathMode === null) {
- TokenError(token, type + seq + " must be inside math mode", {mathMode:true});
- };
- } else if (typeof seq === "string" && seq.match(/^(chapter|section|subsection|subsubsection)$/)) {
- currentMathMode = Environments.getMathMode() ; // returns null / $(inline) / $$(display)
- if (currentMathMode) {
- TokenError(token, type + seq + " used inside math mode", {mathMode:true});
- Environments.resetMathMode();
- };
- } else if (typeof seq === "string" && seq.match(/^[a-z]+$/)) {
- nextGroupMathMode = undefined;
- };
-
- } else if (type === "$") {
- var lookAhead = Tokens[i+1];
- var nextIsDollar = lookAhead && lookAhead[1] === "$";
- currentMathMode = Environments.getMathMode() ; // returns null / $(inline) / $$(display)
- if (nextIsDollar && (!currentMathMode || currentMathMode.command == "$$")) {
- Environments.push({command:"$$", token:token});
- i = i + 1;
- } else {
- Environments.push({command:"$", token:token});
- }
- } else if (type === "^" || type === "_") {
- currentMathMode = Environments.getMathMode() ; // returns null / $(inline) / $$(display)
- var insideGroup = Environments.insideGroup(); // true if inside {....}
- if (currentMathMode === null && !insideGroup) {
- TokenError(token, type + " must be inside math mode", {mathMode:true});
- };
- }
- };
-
- if (seenUserDefinedBeginEquation && seenUserDefinedEndEquation) {
- ErrorReporter.filterMath = true;
- };
-
- return Environments;
-};
-
-var EnvHandler = function (ErrorReporter) {
- var ErrorTo = ErrorReporter.EnvErrorTo;
- var ErrorFromTo = ErrorReporter.EnvErrorFromTo;
- var ErrorFrom = ErrorReporter.EnvErrorFrom;
-
- var envs = [];
-
- var state = [];
- var documentClosed = null;
- var inVerbatim = false;
- var verbatimRanges = [];
-
- this.Environments = envs;
-
- this.push = function (newEnv) {
- this.setEnvProps(newEnv);
- this.checkAndUpdateState(newEnv);
- envs.push(newEnv);
- };
-
- this._endVerbatim = function (thisEnv) {
- var lastEnv = state.pop();
- if (lastEnv && lastEnv.name === thisEnv.name) {
- inVerbatim = false;
- verbatimRanges.push({start: lastEnv.token[2], end: thisEnv.token[2]});
- } else {
- if(lastEnv) { state.push(lastEnv); } ;
- }
- };
-
- var invalidEnvs = [];
-
- this._end = function (thisEnv) {
- do {
- var lastEnv = state.pop();
- var retry = false;
- var i;
-
- if (closedBy(lastEnv, thisEnv)) {
- if (thisEnv.command === "end" && thisEnv.name === "document" && !documentClosed) {
- documentClosed = thisEnv;
- };
- return;
- } else if (!lastEnv) {
- if (documentClosed) {
- ErrorFromTo(documentClosed, thisEnv, "\\end{" + documentClosed.name + "} is followed by unexpected content",{errorAtStart: true, type: "info"});
- } else {
- ErrorTo(thisEnv, "unexpected " + getName(thisEnv));
- }
- } else if (invalidEnvs.length > 0 && (i = indexOfClosingEnvInArray(invalidEnvs, thisEnv) > -1)) {
- invalidEnvs.splice(i, 1);
- if (lastEnv) { state.push(lastEnv); } ;
- return;
- } else {
- var status = reportError(lastEnv, thisEnv);
- if (envPrecedence(lastEnv) < envPrecedence(thisEnv)) {
- invalidEnvs.push(lastEnv);
- retry = true;
- } else {
- var prevLastEnv = state.pop();
- if(prevLastEnv) {
- if (thisEnv.name === prevLastEnv.name) {
- return;
- } else {
- state.push(prevLastEnv);
- }
- }
- invalidEnvs.push(lastEnv);
- }
-
- }
- } while (retry === true);
- };
-
- var CLOSING_DELIMITER = {
- "{" : "}",
- "left" : "right",
- "[" : "]",
- "(" : ")",
- "$" : "$",
- "$$": "$$"
- };
-
- var closedBy = function (lastEnv, thisEnv) {
- if (!lastEnv) {
- return false ;
- } else if (thisEnv.command === "end") {
- return lastEnv.command === "begin" && lastEnv.name === thisEnv.name;
- } else if (thisEnv.command === CLOSING_DELIMITER[lastEnv.command]) {
- return true;
- } else {
- return false;
- }
- };
-
- var indexOfClosingEnvInArray = function (envs, thisEnv) {
- for (var i = 0, n = envs.length; i < n ; i++) {
- if (closedBy(envs[i], thisEnv)) {
- return i;
- }
- }
- return -1;
- };
-
- var envPrecedence = function (env) {
- var openScore = {
- "{" : 1,
- "left" : 2,
- "$" : 3,
- "$$" : 4,
- "begin": 4
- };
- var closeScore = {
- "}" : 1,
- "right" : 2,
- "$" : 3,
- "$$" : 5,
- "end": 4
- };
- if (env.command) {
- return openScore[env.command] || closeScore[env.command];
- } else {
- return 0;
- }
- };
-
- var getName = function(env) {
- var description = {
- "{" : "open group {",
- "}" : "close group }",
- "[" : "open display math \\[",
- "]" : "close display math \\]",
- "(" : "open inline math \\(",
- ")" : "close inline math \\)",
- "$" : "$",
- "$$" : "$$",
- "left" : "\\left",
- "right" : "\\right"
- };
- if (env.command === "begin" || env.command === "end") {
- return "\\" + env.command + "{" + env.name + "}";
- } else if (env.command in description) {
- return description[env.command];
- } else {
- return env.command;
- }
- };
-
- var EXTRA_CLOSE = 1;
- var UNCLOSED_GROUP = 2;
- var UNCLOSED_ENV = 3;
-
- var reportError = function(lastEnv, thisEnv) {
- if (!lastEnv) { // unexpected close, nothing was open!
- if (documentClosed) {
- ErrorFromTo(documentClosed, thisEnv, "\\end{" + documentClosed.name + "} is followed by unexpected end group }",{errorAtStart: true, type: "info"});
- } else {
- ErrorTo(thisEnv, "unexpected " + getName(thisEnv));
- };
- return EXTRA_CLOSE;
- } else if (lastEnv.command === "{" && thisEnv.command === "end") {
- ErrorFromTo(lastEnv, thisEnv, "unclosed " + getName(lastEnv) + " found at " + getName(thisEnv),
- {suppressIfEditing:true, errorAtStart: true, type:"warning"});
- return UNCLOSED_GROUP;
- } else {
- var pLast = envPrecedence(lastEnv);
- var pThis = envPrecedence(thisEnv);
- if (pThis > pLast) {
- ErrorFromTo(lastEnv, thisEnv, "unclosed " + getName(lastEnv) + " found at " + getName(thisEnv),
- {suppressIfEditing:true, errorAtStart: true});
- } else {
- ErrorFromTo(lastEnv, thisEnv, "unexpected " + getName(thisEnv) + " after " + getName(lastEnv));
- }
- return UNCLOSED_ENV;
- };
- };
-
- this._beginMathMode = function (thisEnv) {
- var currentMathMode = this.getMathMode(); // undefined, null, $, $$, name of mathmode env
- if (currentMathMode) {
- ErrorFrom(thisEnv, thisEnv.name + " used inside existing math mode " + getName(currentMathMode),
- {suppressIfEditing:true, errorAtStart: true, mathMode:true});
- };
- thisEnv.mathMode = thisEnv;
- state.push(thisEnv);
- };
-
- this._toggleMathMode = function (thisEnv) {
- var lastEnv = state.pop();
- if (closedBy(lastEnv, thisEnv)) {
- return;
- } else {
- if (lastEnv) {state.push(lastEnv);}
- if (lastEnv && lastEnv.mathMode) {
- this._end(thisEnv);
- } else {
- thisEnv.mathMode = thisEnv;
- state.push(thisEnv);
- }
- };
- };
-
- this.getMathMode = function () {
- var n = state.length;
- if (n > 0) {
- return state[n-1].mathMode;
- } else {
- return null;
- }
- };
-
- this.insideGroup = function () {
- var n = state.length;
- if (n > 0) {
- return (state[n-1].command === "{");
- } else {
- return null;
- }
- };
-
- var resetMathMode = function () {
- var n = state.length;
- if (n > 0) {
- var lastMathMode = state[n-1].mathMode;
- do {
- var lastEnv = state.pop();
- } while (lastEnv && lastEnv !== lastMathMode);
- } else {
- return;
- }
- };
-
- this.resetMathMode = resetMathMode;
-
- var getNewMathMode = function (currentMathMode, thisEnv) {
- var newMathMode = null;
-
- if (thisEnv.command === "{") {
- if (thisEnv.mathMode !== null) {
- newMathMode = thisEnv.mathMode;
- } else {
- newMathMode = currentMathMode;
- }
- } else if (thisEnv.command === "left") {
- if (currentMathMode === null) {
- ErrorFrom(thisEnv, "\\left can only be used in math mode", {mathMode: true});
- };
- newMathMode = currentMathMode;
- } else if (thisEnv.command === "begin") {
- var name = thisEnv.name;
- if (name) {
- if (name.match(/^(document|figure|center|enumerate|itemize|table|abstract|proof|lemma|theorem|definition|proposition|corollary|remark|notation|thebibliography)$/)) {
- if (currentMathMode) {
- ErrorFromTo(currentMathMode, thisEnv, thisEnv.name + " used inside " + getName(currentMathMode),
- {suppressIfEditing:true, errorAtStart: true, mathMode: true});
- resetMathMode();
- };
- newMathMode = null;
- } else if (name.match(/^(array|gathered|split|aligned|alignedat)\*?$/)) {
- if (currentMathMode === null) {
- ErrorFrom(thisEnv, thisEnv.name + " not inside math mode", {mathMode: true});
- };
- newMathMode = currentMathMode;
- } else if (name.match(/^(math|displaymath|equation|eqnarray|multline|align|gather|flalign|alignat)\*?$/)) {
- if (currentMathMode) {
- ErrorFromTo(currentMathMode, thisEnv, thisEnv.name + " used inside " + getName(currentMathMode),
- {suppressIfEditing:true, errorAtStart: true, mathMode: true});
- resetMathMode();
- };
- newMathMode = thisEnv;
- } else {
- newMathMode = undefined; // undefined means we don't know if we are in math mode or not
- }
- }
- };
- return newMathMode;
- };
-
- this.checkAndUpdateState = function (thisEnv) {
- if (inVerbatim) {
- if (thisEnv.command === "end") {
- this._endVerbatim(thisEnv);
- } else {
- return; // ignore anything in verbatim environments
- }
- } else if(thisEnv.command === "begin" || thisEnv.command === "{" || thisEnv.command === "left") {
- if (thisEnv.verbatim) {inVerbatim = true;};
- var currentMathMode = this.getMathMode(); // undefined, null, $, $$, name of mathmode env
- var newMathMode = getNewMathMode(currentMathMode, thisEnv);
- thisEnv.mathMode = newMathMode;
- state.push(thisEnv);
- } else if (thisEnv.command === "end") {
- this._end(thisEnv);
- } else if (thisEnv.command === "(" || thisEnv.command === "[") {
- this._beginMathMode(thisEnv);
- } else if (thisEnv.command === ")" || thisEnv.command === "]") {
- this._end(thisEnv);
- } else if (thisEnv.command === "}") {
- this._end(thisEnv);
- } else if (thisEnv.command === "right") {
- this._end(thisEnv);
- } else if (thisEnv.command === "$" || thisEnv.command === "$$") {
- this._toggleMathMode(thisEnv);
- }
- };
-
- this.close = function () {
- while (state.length > 0) {
- var thisEnv = state.pop();
- if (thisEnv.command === "{") {
- ErrorFrom(thisEnv, "unclosed group {", {type:"warning"});
- } else {
- ErrorFrom(thisEnv, "unclosed " + getName(thisEnv));
- }
- }
- var vlen = verbatimRanges.length;
- var len = ErrorReporter.tokenErrors.length;
- if (vlen >0 && len > 0) {
- for (var i = 0; i < len; i++) {
- var tokenError = ErrorReporter.tokenErrors[i];
- var startPos = tokenError.startPos;
- var endPos = tokenError.endPos;
- for (var j = 0; j < vlen; j++) {
- if (startPos > verbatimRanges[j].start && startPos < verbatimRanges[j].end) {
- tokenError.ignore = true;
- break;
- }
- }
- }
- }
- };
-
- this.setEnvProps = function (env) {
- var name = env.name ;
- if (name && name.match(/^(verbatim|boxedverbatim|lstlisting|minted|Verbatim)$/)) {
- env.verbatim = true;
- }
- };
-};
-var ErrorReporter = function (TokeniseResult) {
- var text = TokeniseResult.text;
- var linePosition = TokeniseResult.linePosition;
- var lineNumber = TokeniseResult.lineNumber;
-
- var errors = [], tokenErrors = [];
- this.errors = errors;
- this.tokenErrors = tokenErrors;
- this.filterMath = false;
-
- this.getErrors = function () {
- var returnedErrors = [];
- for (var i = 0, len = tokenErrors.length; i < len; i++) {
- if (!tokenErrors[i].ignore) { returnedErrors.push(tokenErrors[i]); }
- }
- var allErrors = returnedErrors.concat(errors);
- var result = [];
-
- var mathErrorCount = 0;
- for (i = 0, len = allErrors.length; i < len; i++) {
- if (allErrors[i].mathMode) {
- mathErrorCount++;
- }
- if (mathErrorCount > 10) {
- return [];
- }
- }
-
- if (this.filterMath && mathErrorCount > 0) {
- for (i = 0, len = allErrors.length; i < len; i++) {
- if (!allErrors[i].mathMode) {
- result.push(allErrors[i]);
- }
- }
- return result;
- } else {
- return allErrors;
- }
- };
-
- this.TokenError = function (token, message, options) {
- if(!options) { options = { suppressIfEditing:true } ; };
- var line = token[0], type = token[1], start = token[2], end = token[3];
- var start_col = start - linePosition[line];
- if (!end) { end = start + 1; } ;
- var end_col = end - linePosition[line];
- tokenErrors.push({row: line,
- column: start_col,
- start_row:line,
- start_col: start_col,
- end_row:line,
- end_col: end_col,
- type:"error",
- text:message,
- startPos: start,
- endPos: end,
- suppressIfEditing:options.suppressIfEditing,
- mathMode: options.mathMode});
- };
-
- this.TokenErrorFromTo = function (fromToken, toToken, message, options) {
- if(!options) { options = {suppressIfEditing:true } ; };
- var fromLine = fromToken[0], fromStart = fromToken[2], fromEnd = fromToken[3];
- var toLine = toToken[0], toStart = toToken[2], toEnd = toToken[3];
- if (!toEnd) { toEnd = toStart + 1;};
- var start_col = fromStart - linePosition[fromLine];
- var end_col = toEnd - linePosition[toLine];
-
- tokenErrors.push({row: fromLine,
- column: start_col,
- start_row: fromLine,
- start_col: start_col,
- end_row: toLine,
- end_col: end_col,
- type:"error",
- text:message,
- startPos: fromStart,
- endPos: toEnd,
- suppressIfEditing:options.suppressIfEditing,
- mathMode: options.mathMode});
- };
-
-
- this.EnvErrorFromTo = function (fromEnv, toEnv, message, options) {
- if(!options) { options = {} ; };
- var fromToken = fromEnv.token, toToken = toEnv.closeToken || toEnv.token;
- var fromLine = fromToken[0], fromStart = fromToken[2], fromEnd = fromToken[3];
- if (!toToken) {toToken = fromToken;};
- var toLine = toToken[0], toStart = toToken[2], toEnd = toToken[3];
- if (!toEnd) { toEnd = toStart + 1;};
- var start_col = fromStart - linePosition[fromLine];
- var end_col = toEnd - linePosition[toLine];
- errors.push({row: options.errorAtStart ? fromLine : toLine,
- column: options.errorAtStart ? start_col: end_col,
- start_row:fromLine,
- start_col: start_col,
- end_row:toLine,
- end_col: end_col,
- type: options.type ? options.type : "error",
- text:message,
- suppressIfEditing:options.suppressIfEditing,
- mathMode: options.mathMode});
- };
-
- this.EnvErrorTo = function (toEnv, message, options) {
- if(!options) { options = {} ; };
- var token = toEnv.closeToken || toEnv.token;
- var line = token[0], type = token[1], start = token[2], end = token[3];
- if (!end) { end = start + 1; };
- var end_col = end - linePosition[line];
- var err = {row: line,
- column: end_col,
- start_row:0,
- start_col: 0,
- end_row: line,
- end_col: end_col,
- type: options.type ? options.type : "error",
- text:message,
- mathMode: options.mathMode};
- errors.push(err);
- };
-
- this.EnvErrorFrom = function (env, message, options) {
- if(!options) { options = {} ; };
- var token = env.token;
- var line = token[0], type = token[1], start = token[2], end = token[3];
- var start_col = start - linePosition[line];
- var end_col = Infinity;
- errors.push({row: line,
- column: start_col,
- start_row:line,
- start_col: start_col,
- end_row: lineNumber,
- end_col: end_col,
- type: options.type ? options.type : "error",
- text:message,
- mathMode: options.mathMode});
- };
-};
-
-var Parse = function (text) {
- var TokeniseResult = Tokenise(text);
- var Reporter = new ErrorReporter(TokeniseResult);
- var Environments = InterpretTokens(TokeniseResult, Reporter);
- Environments.close();
- return Reporter.getErrors();
-};
-
-(function() {
- var disabled = false;
-
- this.onUpdate = function() {
- if (disabled) { return ; };
-
- var value = this.doc.getValue();
- var errors = [];
- try {
- if (value)
- errors = Parse(value);
- } catch (e) {
- disabled = true;
- errors = [];
- }
- this.sender.emit("lint", errors);
- };
-
-}).call(LatexWorker.prototype);
-
-});
-
-ace.define("ace/lib/es5-shim",["require","exports","module"], function(require, exports, module) {
-
-function Empty() {}
-
-if (!Function.prototype.bind) {
- Function.prototype.bind = function bind(that) { // .length is 1
- var target = this;
- if (typeof target != "function") {
- throw new TypeError("Function.prototype.bind called on incompatible " + target);
- }
- var args = slice.call(arguments, 1); // for normal call
- var bound = function () {
-
- if (this instanceof bound) {
-
- var result = target.apply(
- this,
- args.concat(slice.call(arguments))
- );
- if (Object(result) === result) {
- return result;
- }
- return this;
-
- } else {
- return target.apply(
- that,
- args.concat(slice.call(arguments))
- );
-
- }
-
- };
- if(target.prototype) {
- Empty.prototype = target.prototype;
- bound.prototype = new Empty();
- Empty.prototype = null;
- }
- return bound;
- };
-}
-var call = Function.prototype.call;
-var prototypeOfArray = Array.prototype;
-var prototypeOfObject = Object.prototype;
-var slice = prototypeOfArray.slice;
-var _toString = call.bind(prototypeOfObject.toString);
-var owns = call.bind(prototypeOfObject.hasOwnProperty);
-var defineGetter;
-var defineSetter;
-var lookupGetter;
-var lookupSetter;
-var supportsAccessors;
-if ((supportsAccessors = owns(prototypeOfObject, "__defineGetter__"))) {
- defineGetter = call.bind(prototypeOfObject.__defineGetter__);
- defineSetter = call.bind(prototypeOfObject.__defineSetter__);
- lookupGetter = call.bind(prototypeOfObject.__lookupGetter__);
- lookupSetter = call.bind(prototypeOfObject.__lookupSetter__);
-}
-if ([1,2].splice(0).length != 2) {
- if(function() { // test IE < 9 to splice bug - see issue #138
- function makeArray(l) {
- var a = new Array(l+2);
- a[0] = a[1] = 0;
- return a;
- }
- var array = [], lengthBefore;
-
- array.splice.apply(array, makeArray(20));
- array.splice.apply(array, makeArray(26));
-
- lengthBefore = array.length; //46
- array.splice(5, 0, "XXX"); // add one element
-
- lengthBefore + 1 == array.length
-
- if (lengthBefore + 1 == array.length) {
- return true;// has right splice implementation without bugs
- }
- }()) {//IE 6/7
- var array_splice = Array.prototype.splice;
- Array.prototype.splice = function(start, deleteCount) {
- if (!arguments.length) {
- return [];
- } else {
- return array_splice.apply(this, [
- start === void 0 ? 0 : start,
- deleteCount === void 0 ? (this.length - start) : deleteCount
- ].concat(slice.call(arguments, 2)))
- }
- };
- } else {//IE8
- Array.prototype.splice = function(pos, removeCount){
- var length = this.length;
- if (pos > 0) {
- if (pos > length)
- pos = length;
- } else if (pos == void 0) {
- pos = 0;
- } else if (pos < 0) {
- pos = Math.max(length + pos, 0);
- }
-
- if (!(pos+removeCount < length))
- removeCount = length - pos;
-
- var removed = this.slice(pos, pos+removeCount);
- var insert = slice.call(arguments, 2);
- var add = insert.length;
- if (pos === length) {
- if (add) {
- this.push.apply(this, insert);
- }
- } else {
- var remove = Math.min(removeCount, length - pos);
- var tailOldPos = pos + remove;
- var tailNewPos = tailOldPos + add - remove;
- var tailCount = length - tailOldPos;
- var lengthAfterRemove = length - remove;
-
- if (tailNewPos < tailOldPos) { // case A
- for (var i = 0; i < tailCount; ++i) {
- this[tailNewPos+i] = this[tailOldPos+i];
- }
- } else if (tailNewPos > tailOldPos) { // case B
- for (i = tailCount; i--; ) {
- this[tailNewPos+i] = this[tailOldPos+i];
- }
- } // else, add == remove (nothing to do)
-
- if (add && pos === lengthAfterRemove) {
- this.length = lengthAfterRemove; // truncate array
- this.push.apply(this, insert);
- } else {
- this.length = lengthAfterRemove + add; // reserves space
- for (i = 0; i < add; ++i) {
- this[pos+i] = insert[i];
- }
- }
- }
- return removed;
- };
- }
-}
-if (!Array.isArray) {
- Array.isArray = function isArray(obj) {
- return _toString(obj) == "[object Array]";
- };
-}
-var boxedString = Object("a"),
- splitString = boxedString[0] != "a" || !(0 in boxedString);
-
-if (!Array.prototype.forEach) {
- Array.prototype.forEach = function forEach(fun /*, thisp*/) {
- var object = toObject(this),
- self = splitString && _toString(this) == "[object String]" ?
- this.split("") :
- object,
- thisp = arguments[1],
- i = -1,
- length = self.length >>> 0;
- if (_toString(fun) != "[object Function]") {
- throw new TypeError(); // TODO message
- }
-
- while (++i < length) {
- if (i in self) {
- fun.call(thisp, self[i], i, object);
- }
- }
- };
-}
-if (!Array.prototype.map) {
- Array.prototype.map = function map(fun /*, thisp*/) {
- var object = toObject(this),
- self = splitString && _toString(this) == "[object String]" ?
- this.split("") :
- object,
- length = self.length >>> 0,
- result = Array(length),
- thisp = arguments[1];
- if (_toString(fun) != "[object Function]") {
- throw new TypeError(fun + " is not a function");
- }
-
- for (var i = 0; i < length; i++) {
- if (i in self)
- result[i] = fun.call(thisp, self[i], i, object);
- }
- return result;
- };
-}
-if (!Array.prototype.filter) {
- Array.prototype.filter = function filter(fun /*, thisp */) {
- var object = toObject(this),
- self = splitString && _toString(this) == "[object String]" ?
- this.split("") :
- object,
- length = self.length >>> 0,
- result = [],
- value,
- thisp = arguments[1];
- if (_toString(fun) != "[object Function]") {
- throw new TypeError(fun + " is not a function");
- }
-
- for (var i = 0; i < length; i++) {
- if (i in self) {
- value = self[i];
- if (fun.call(thisp, value, i, object)) {
- result.push(value);
- }
- }
- }
- return result;
- };
-}
-if (!Array.prototype.every) {
- Array.prototype.every = function every(fun /*, thisp */) {
- var object = toObject(this),
- self = splitString && _toString(this) == "[object String]" ?
- this.split("") :
- object,
- length = self.length >>> 0,
- thisp = arguments[1];
- if (_toString(fun) != "[object Function]") {
- throw new TypeError(fun + " is not a function");
- }
-
- for (var i = 0; i < length; i++) {
- if (i in self && !fun.call(thisp, self[i], i, object)) {
- return false;
- }
- }
- return true;
- };
-}
-if (!Array.prototype.some) {
- Array.prototype.some = function some(fun /*, thisp */) {
- var object = toObject(this),
- self = splitString && _toString(this) == "[object String]" ?
- this.split("") :
- object,
- length = self.length >>> 0,
- thisp = arguments[1];
- if (_toString(fun) != "[object Function]") {
- throw new TypeError(fun + " is not a function");
- }
-
- for (var i = 0; i < length; i++) {
- if (i in self && fun.call(thisp, self[i], i, object)) {
- return true;
- }
- }
- return false;
- };
-}
-if (!Array.prototype.reduce) {
- Array.prototype.reduce = function reduce(fun /*, initial*/) {
- var object = toObject(this),
- self = splitString && _toString(this) == "[object String]" ?
- this.split("") :
- object,
- length = self.length >>> 0;
- if (_toString(fun) != "[object Function]") {
- throw new TypeError(fun + " is not a function");
- }
- if (!length && arguments.length == 1) {
- throw new TypeError("reduce of empty array with no initial value");
- }
-
- var i = 0;
- var result;
- if (arguments.length >= 2) {
- result = arguments[1];
- } else {
- do {
- if (i in self) {
- result = self[i++];
- break;
- }
- if (++i >= length) {
- throw new TypeError("reduce of empty array with no initial value");
- }
- } while (true);
- }
-
- for (; i < length; i++) {
- if (i in self) {
- result = fun.call(void 0, result, self[i], i, object);
- }
- }
-
- return result;
- };
-}
-if (!Array.prototype.reduceRight) {
- Array.prototype.reduceRight = function reduceRight(fun /*, initial*/) {
- var object = toObject(this),
- self = splitString && _toString(this) == "[object String]" ?
- this.split("") :
- object,
- length = self.length >>> 0;
- if (_toString(fun) != "[object Function]") {
- throw new TypeError(fun + " is not a function");
- }
- if (!length && arguments.length == 1) {
- throw new TypeError("reduceRight of empty array with no initial value");
- }
-
- var result, i = length - 1;
- if (arguments.length >= 2) {
- result = arguments[1];
- } else {
- do {
- if (i in self) {
- result = self[i--];
- break;
- }
- if (--i < 0) {
- throw new TypeError("reduceRight of empty array with no initial value");
- }
- } while (true);
- }
-
- do {
- if (i in this) {
- result = fun.call(void 0, result, self[i], i, object);
- }
- } while (i--);
-
- return result;
- };
-}
-if (!Array.prototype.indexOf || ([0, 1].indexOf(1, 2) != -1)) {
- Array.prototype.indexOf = function indexOf(sought /*, fromIndex */ ) {
- var self = splitString && _toString(this) == "[object String]" ?
- this.split("") :
- toObject(this),
- length = self.length >>> 0;
-
- if (!length) {
- return -1;
- }
-
- var i = 0;
- if (arguments.length > 1) {
- i = toInteger(arguments[1]);
- }
- i = i >= 0 ? i : Math.max(0, length + i);
- for (; i < length; i++) {
- if (i in self && self[i] === sought) {
- return i;
- }
- }
- return -1;
- };
-}
-if (!Array.prototype.lastIndexOf || ([0, 1].lastIndexOf(0, -3) != -1)) {
- Array.prototype.lastIndexOf = function lastIndexOf(sought /*, fromIndex */) {
- var self = splitString && _toString(this) == "[object String]" ?
- this.split("") :
- toObject(this),
- length = self.length >>> 0;
-
- if (!length) {
- return -1;
- }
- var i = length - 1;
- if (arguments.length > 1) {
- i = Math.min(i, toInteger(arguments[1]));
- }
- i = i >= 0 ? i : length - Math.abs(i);
- for (; i >= 0; i--) {
- if (i in self && sought === self[i]) {
- return i;
- }
- }
- return -1;
- };
-}
-if (!Object.getPrototypeOf) {
- Object.getPrototypeOf = function getPrototypeOf(object) {
- return object.__proto__ || (
- object.constructor ?
- object.constructor.prototype :
- prototypeOfObject
- );
- };
-}
-if (!Object.getOwnPropertyDescriptor) {
- var ERR_NON_OBJECT = "Object.getOwnPropertyDescriptor called on a " +
- "non-object: ";
- Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(object, property) {
- if ((typeof object != "object" && typeof object != "function") || object === null)
- throw new TypeError(ERR_NON_OBJECT + object);
- if (!owns(object, property))
- return;
-
- var descriptor, getter, setter;
- descriptor = { enumerable: true, configurable: true };
- if (supportsAccessors) {
- var prototype = object.__proto__;
- object.__proto__ = prototypeOfObject;
-
- var getter = lookupGetter(object, property);
- var setter = lookupSetter(object, property);
- object.__proto__ = prototype;
-
- if (getter || setter) {
- if (getter) descriptor.get = getter;
- if (setter) descriptor.set = setter;
- return descriptor;
- }
- }
- descriptor.value = object[property];
- return descriptor;
- };
-}
-if (!Object.getOwnPropertyNames) {
- Object.getOwnPropertyNames = function getOwnPropertyNames(object) {
- return Object.keys(object);
- };
-}
-if (!Object.create) {
- var createEmpty;
- if (Object.prototype.__proto__ === null) {
- createEmpty = function () {
- return { "__proto__": null };
- };
- } else {
- createEmpty = function () {
- var empty = {};
- for (var i in empty)
- empty[i] = null;
- empty.constructor =
- empty.hasOwnProperty =
- empty.propertyIsEnumerable =
- empty.isPrototypeOf =
- empty.toLocaleString =
- empty.toString =
- empty.valueOf =
- empty.__proto__ = null;
- return empty;
- }
- }
-
- Object.create = function create(prototype, properties) {
- var object;
- if (prototype === null) {
- object = createEmpty();
- } else {
- if (typeof prototype != "object")
- throw new TypeError("typeof prototype["+(typeof prototype)+"] != 'object'");
- var Type = function () {};
- Type.prototype = prototype;
- object = new Type();
- object.__proto__ = prototype;
- }
- if (properties !== void 0)
- Object.defineProperties(object, properties);
- return object;
- };
-}
-
-function doesDefinePropertyWork(object) {
- try {
- Object.defineProperty(object, "sentinel", {});
- return "sentinel" in object;
- } catch (exception) {
- }
-}
-if (Object.defineProperty) {
- var definePropertyWorksOnObject = doesDefinePropertyWork({});
- var definePropertyWorksOnDom = typeof document == "undefined" ||
- doesDefinePropertyWork(document.createElement("div"));
- if (!definePropertyWorksOnObject || !definePropertyWorksOnDom) {
- var definePropertyFallback = Object.defineProperty;
- }
-}
-
-if (!Object.defineProperty || definePropertyFallback) {
- var ERR_NON_OBJECT_DESCRIPTOR = "Property description must be an object: ";
- var ERR_NON_OBJECT_TARGET = "Object.defineProperty called on non-object: "
- var ERR_ACCESSORS_NOT_SUPPORTED = "getters & setters can not be defined " +
- "on this javascript engine";
-
- Object.defineProperty = function defineProperty(object, property, descriptor) {
- if ((typeof object != "object" && typeof object != "function") || object === null)
- throw new TypeError(ERR_NON_OBJECT_TARGET + object);
- if ((typeof descriptor != "object" && typeof descriptor != "function") || descriptor === null)
- throw new TypeError(ERR_NON_OBJECT_DESCRIPTOR + descriptor);
- if (definePropertyFallback) {
- try {
- return definePropertyFallback.call(Object, object, property, descriptor);
- } catch (exception) {
- }
- }
- if (owns(descriptor, "value")) {
-
- if (supportsAccessors && (lookupGetter(object, property) ||
- lookupSetter(object, property)))
- {
- var prototype = object.__proto__;
- object.__proto__ = prototypeOfObject;
- delete object[property];
- object[property] = descriptor.value;
- object.__proto__ = prototype;
- } else {
- object[property] = descriptor.value;
- }
- } else {
- if (!supportsAccessors)
- throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
- if (owns(descriptor, "get"))
- defineGetter(object, property, descriptor.get);
- if (owns(descriptor, "set"))
- defineSetter(object, property, descriptor.set);
- }
-
- return object;
- };
-}
-if (!Object.defineProperties) {
- Object.defineProperties = function defineProperties(object, properties) {
- for (var property in properties) {
- if (owns(properties, property))
- Object.defineProperty(object, property, properties[property]);
- }
- return object;
- };
-}
-if (!Object.seal) {
- Object.seal = function seal(object) {
- return object;
- };
-}
-if (!Object.freeze) {
- Object.freeze = function freeze(object) {
- return object;
- };
-}
-try {
- Object.freeze(function () {});
-} catch (exception) {
- Object.freeze = (function freeze(freezeObject) {
- return function freeze(object) {
- if (typeof object == "function") {
- return object;
- } else {
- return freezeObject(object);
- }
- };
- })(Object.freeze);
-}
-if (!Object.preventExtensions) {
- Object.preventExtensions = function preventExtensions(object) {
- return object;
- };
-}
-if (!Object.isSealed) {
- Object.isSealed = function isSealed(object) {
- return false;
- };
-}
-if (!Object.isFrozen) {
- Object.isFrozen = function isFrozen(object) {
- return false;
- };
-}
-if (!Object.isExtensible) {
- Object.isExtensible = function isExtensible(object) {
- if (Object(object) === object) {
- throw new TypeError(); // TODO message
- }
- var name = '';
- while (owns(object, name)) {
- name += '?';
- }
- object[name] = true;
- var returnValue = owns(object, name);
- delete object[name];
- return returnValue;
- };
-}
-if (!Object.keys) {
- var hasDontEnumBug = true,
- dontEnums = [
- "toString",
- "toLocaleString",
- "valueOf",
- "hasOwnProperty",
- "isPrototypeOf",
- "propertyIsEnumerable",
- "constructor"
- ],
- dontEnumsLength = dontEnums.length;
-
- for (var key in {"toString": null}) {
- hasDontEnumBug = false;
- }
-
- Object.keys = function keys(object) {
-
- if (
- (typeof object != "object" && typeof object != "function") ||
- object === null
- ) {
- throw new TypeError("Object.keys called on a non-object");
- }
-
- var keys = [];
- for (var name in object) {
- if (owns(object, name)) {
- keys.push(name);
- }
- }
-
- if (hasDontEnumBug) {
- for (var i = 0, ii = dontEnumsLength; i < ii; i++) {
- var dontEnum = dontEnums[i];
- if (owns(object, dontEnum)) {
- keys.push(dontEnum);
- }
- }
- }
- return keys;
- };
-
-}
-if (!Date.now) {
- Date.now = function now() {
- return new Date().getTime();
- };
-}
-var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" +
- "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" +
- "\u2029\uFEFF";
-if (!String.prototype.trim || ws.trim()) {
- ws = "[" + ws + "]";
- var trimBeginRegexp = new RegExp("^" + ws + ws + "*"),
- trimEndRegexp = new RegExp(ws + ws + "*$");
- String.prototype.trim = function trim() {
- return String(this).replace(trimBeginRegexp, "").replace(trimEndRegexp, "");
- };
-}
-
-function toInteger(n) {
- n = +n;
- if (n !== n) { // isNaN
- n = 0;
- } else if (n !== 0 && n !== (1/0) && n !== -(1/0)) {
- n = (n > 0 || -1) * Math.floor(Math.abs(n));
- }
- return n;
-}
-
-function isPrimitive(input) {
- var type = typeof input;
- return (
- input === null ||
- type === "undefined" ||
- type === "boolean" ||
- type === "number" ||
- type === "string"
- );
-}
-
-function toPrimitive(input) {
- var val, valueOf, toString;
- if (isPrimitive(input)) {
- return input;
- }
- valueOf = input.valueOf;
- if (typeof valueOf === "function") {
- val = valueOf.call(input);
- if (isPrimitive(val)) {
- return val;
- }
- }
- toString = input.toString;
- if (typeof toString === "function") {
- val = toString.call(input);
- if (isPrimitive(val)) {
- return val;
- }
- }
- throw new TypeError();
-}
-var toObject = function (o) {
- if (o == null) { // this matches both null and undefined
- throw new TypeError("can't convert "+o+" to object");
- }
- return Object(o);
-};
-
-});
diff --git a/services/web/public/ol-favicon.ico b/services/web/public/ol-favicon.ico
new file mode 100644
index 0000000000..6a14f7a5b9
Binary files /dev/null and b/services/web/public/ol-favicon.ico differ
diff --git a/services/web/public/stylesheets/_style_includes.less b/services/web/public/stylesheets/_style_includes.less
new file mode 100644
index 0000000000..a5c7589ef0
--- /dev/null
+++ b/services/web/public/stylesheets/_style_includes.less
@@ -0,0 +1,84 @@
+@import url(https://netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css);
+
+@import "core/mixins.less";
+
+// Reset
+@import "core/normalize.less";
+@import "core/print.less";
+
+// Core CSS
+@import "core/scaffolding.less";
+@import "core/type.less";
+@import "core/grid.less";
+
+// Components
+@import "components/tables.less";
+@import "components/forms.less";
+@import "components/buttons.less";
+@import "components/card.less";
+//@import "components/code.less";
+@import "components/component-animations.less";
+@import "components/glyphicons.less";
+@import "components/dropdowns.less";
+@import "components/button-groups.less";
+@import "components/input-groups.less";
+@import "components/navs.less";
+@import "components/navbar.less";
+@import "components/footer.less";
+//@import "components/breadcrumbs.less";
+//@import "components/pagination.less";
+@import "components/pager.less";
+@import "components/labels.less";
+//@import "components/badges.less";
+//@import "components/jumbotron.less";
+@import "components/thumbnails.less";
+@import "components/alerts.less";
+@import "components/progress-bars.less";
+// @import "components/media.less";
+// @import "components/list-group.less";
+// @import "components/panels.less";
+// @import "components/wells.less";
+@import "components/close.less";
+@import "components/fineupload.less";
+@import "components/hover.less";
+
+// Components w/ JavaScript
+@import "components/modals.less";
+@import "components/tooltip.less";
+@import "components/popovers.less";
+@import "components/carousel.less";
+
+// ngTagsInput
+@import "components/tags-input.less";
+
+// Utility classes
+@import "core/utilities.less";
+@import "core/responsive-utilities.less";
+
+// ShareLaTeX app classes
+@import "app/base.less";
+@import "app/account-settings.less";
+@import "app/beta-program.less";
+@import "app/about-page.less";
+@import "app/project-list.less";
+@import "app/editor.less";
+@import "app/homepage.less";
+@import "app/plans.less";
+@import "app/recurly.less";
+@import "app/bonus.less";
+@import "app/register.less";
+@import "app/blog.less";
+@import "app/features.less";
+@import "app/templates.less";
+@import "app/wiki.less";
+@import "app/translations.less";
+@import "app/contact-us.less";
+@import "app/subscription.less";
+@import "app/sprites.less";
+@import "app/invite.less";
+@import "app/review-features-page.less";
+@import "app/error-pages.less";
+
+@import "../js/libs/pdfListView/TextLayer.css";
+@import "../js/libs/pdfListView/AnnotationsLayer.css";
+@import "../js/libs/pdfListView/HighlightsLayer.css";
diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less
index 3c3aae945a..75b7121094 100644
--- a/services/web/public/stylesheets/app/editor.less
+++ b/services/web/public/stylesheets/app/editor.less
@@ -72,17 +72,17 @@
height: 100%;
background-color: #FFF;
}
- .loading-screen-lion-container {
+ .loading-screen-brand-container {
width: 15%;
min-width: 200px;
text-align: center;
}
- .loading-screen-lion {
+ .loading-screen-brand {
position: relative;
width: 100%;
- padding-top: 86.2%;
+ padding-top: @editor-loading-logo-padding-top;
height: 0;
- background: url(/img/brand/lion-grey.svg) no-repeat bottom / 100%;
+ background: @editor-loading-logo-background-url no-repeat bottom / 100%;
&::after {
content: '';
@@ -91,7 +91,7 @@
right: 0;
bottom: 0;
left: 0;
- background: url(/img/brand/lion.svg) no-repeat bottom / 100%;
+ background: @editor-loading-logo-foreground-url no-repeat bottom / 100%;
transition: height .5s;
}
}
@@ -504,3 +504,4 @@
height: auto;
border-bottom: 1px solid @modal-header-border-color;
}
+
diff --git a/services/web/public/stylesheets/app/editor/review-panel.less b/services/web/public/stylesheets/app/editor/review-panel.less
index 312b933b85..13b8b7e03f 100644
--- a/services/web/public/stylesheets/app/editor/review-panel.less
+++ b/services/web/public/stylesheets/app/editor/review-panel.less
@@ -57,6 +57,15 @@
}
}
+.rp-collapse-arrow() {
+ display: inline-block;
+ transform: rotateZ(0deg);
+ transition: transform 0.15s ease;
+ &-on {
+ transform: rotateZ(-90deg);
+ }
+}
+
.triangle(@_, @width, @height, @color) {
position: absolute;
border-color: transparent;
@@ -131,7 +140,6 @@
}
position: relative;
- height: @rp-toolbar-height;
border-bottom: 1px solid @rp-border-grey;
background-color: @rp-bg-dim-blue;
text-align: center;
@@ -144,6 +152,10 @@
text-align: right;
flex-grow: 1;
}
+ .review-panel-toolbar-icon-on {
+ margin-right: 5px;
+ color: @red;
+ }
.review-panel-toolbar-label-disabled {
cursor: auto;
margin-right: 5px;
@@ -151,6 +163,45 @@
.review-panel-toolbar-spinner {
margin-left: 5px;
}
+ .rp-tc-state {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ overflow: hidden;
+ list-style: none;
+ padding: 0 5px;
+ margin: 0;
+ border-bottom: 1px solid @rp-border-grey;
+ background-color: @rp-bg-dim-blue;
+ text-align: left;
+ }
+ .rp-tc-state-collapse {
+ .rp-collapse-arrow;
+ margin-left: 5px;
+ }
+ .rp-tc-state-item {
+ display: flex;
+ align-items: center;
+ padding: 3px 0;
+ &:last-of-type {
+ padding-bottom: 5px;
+ }
+ }
+ .rp-tc-state-item-everyone {
+ border-bottom: 1px solid @rp-border-grey;
+ color: @red;
+ }
+ .rp-tc-state-item-name {
+ flex-grow: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-weight: @rp-semibold-weight;
+ }
+ .rp-tc-state-item-name-disabled {
+ opacity: .35;
+ }
.rp-entry-list {
display: none;
@@ -629,16 +680,9 @@
font-size: 0.9em;
}
.rp-overview-file-header-collapse {
- display: inline-block;
+ .rp-collapse-arrow;
float: left;
- transform: rotateZ(0deg);
- transition: transform 0.15s ease
}
-
- .rp-overview-file-header-collapse-on {
- transform: rotateZ(-90deg);
- }
-
.rp-overview-file-entries {
overflow: hidden;
}
@@ -771,6 +815,11 @@
background-color: #FFF;
}
}
+
+ &:disabled + .rp-toggle-btn {
+ cursor: default;
+ opacity: .35;
+ }
}
.ace-editor-wrapper {
diff --git a/services/web/public/stylesheets/components/navbar.less b/services/web/public/stylesheets/components/navbar.less
index 9da0dfabc5..52a4a10029 100755
--- a/services/web/public/stylesheets/components/navbar.less
+++ b/services/web/public/stylesheets/components/navbar.less
@@ -386,7 +386,7 @@
bottom: 5px;
width: 180px;
padding: 0;
- background-image: url('/img/brand/logo-horizontal.svg');
+ background-image: @navbar-brand-image-url;
background-size: contain;
background-repeat: no-repeat;
background-position: left center;
diff --git a/services/web/public/stylesheets/components/navs.less b/services/web/public/stylesheets/components/navs.less
index 262524f864..0616da1b8c 100755
--- a/services/web/public/stylesheets/components/navs.less
+++ b/services/web/public/stylesheets/components/navs.less
@@ -225,6 +225,10 @@
// Hide tabbable panes to start, show them when `.active`
.tab-content {
+ background-color: @nav-tabs-active-link-hover-bg;
+ border: 1px solid @nav-tabs-border-color;
+ border-top: none;
+ padding: @line-height-computed / 2;
> .tab-pane {
display: none;
}
diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less
new file mode 100644
index 0000000000..6274b265f9
--- /dev/null
+++ b/services/web/public/stylesheets/core/_common-variables.less
@@ -0,0 +1,805 @@
+//
+// Variables
+// --------------------------------------------------
+
+//== Scaffolding
+//
+// ## Settings for some of the most global styles.
+
+//** Background color for ``.
+@body-bg: #fff;
+//** Global text color on ``.
+@text-color: @gray-dark;
+
+//** Global textual link color.
+@link-color: @brand-primary;
+//** Link hover color set via `darken()` function.
+@link-hover-color: darken(@link-color, 15%);
+
+
+//== Typography
+//
+//## Font, line-height, and color for body text, headings, and more.
+
+@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+//@import url(https://fonts.googleapis.com/css?family=PT+Serif:400,600,700);
+//@import url(https://fonts.googleapis.com/css?family=PT+Serif:400,400i,700,700i);
+@import url(https://fonts.googleapis.com/css?family=Merriweather:400,400i,700,700i);
+
+@font-family-sans-serif: "Open Sans", sans-serif;
+@font-family-serif: "Merriweather", serif;
+//** Default monospace fonts for ``, ``, and ``.
+@font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
+@font-family-base: @font-family-sans-serif;
+
+@font-size-base: 16px;
+@font-size-large: ceil((@font-size-base * 1.25)); // ~18px
+@font-size-small: ceil((@font-size-base * 0.85)); // ~12px
+
+@font-size-h1: floor((@font-size-base * 2)); // ~36px
+@font-size-h2: floor((@font-size-base * 1.6)); // ~30px
+@font-size-h3: ceil((@font-size-base * 1.25)); // ~24px
+@font-size-h4: ceil((@font-size-base * 1.1)); // ~18px
+@font-size-h5: @font-size-base;
+@font-size-h6: ceil((@font-size-base * 0.85)); // ~12px
+
+//** Unit-less `line-height` for use in components like buttons.
+@line-height-base: 1.5625; // 20/14
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+@line-height-computed: floor((@font-size-base * @line-height-base)); // ~20px
+
+//** By default, this inherits from the ``.
+@headings-font-family: @font-family-serif;
+@headings-font-weight: 500;
+@headings-line-height: 1.1;
+@headings-color: @gray-dark;
+
+
+//-- Iconography
+//
+//## Specify custom locations of the include Glyphicons icon font. Useful for those including Bootstrap via Bower.
+
+@icon-font-path: "../fonts/";
+@icon-font-name: "glyphicons-halflings-regular";
+@icon-font-svg-id: "glyphicons_halflingsregular";
+
+//== Components
+//
+//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
+
+@padding-base-vertical: 5px;
+@padding-base-horizontal: 16px;
+
+@padding-large-vertical: 10px;
+@padding-large-horizontal: 16px;
+
+@padding-small-vertical: 5px;
+@padding-small-horizontal: 10px;
+
+@padding-xs-vertical: 1px;
+@padding-xs-horizontal: 5px;
+
+@line-height-large: 1.33;
+@line-height-small: 1.5;
+
+@border-radius-base: 3px;
+@border-radius-large: 5px;
+@border-radius-small: 2px;
+
+//** Global color for active items (e.g., navs or dropdowns).
+@component-active-color: #fff;
+//** Global background color for active items (e.g., navs or dropdowns).
+@component-active-bg: @brand-primary;
+
+//** Width of the `border` for generating carets that indicator dropdowns.
+@caret-width-base: 4px;
+//** Carets increase slightly in size for larger components.
+@caret-width-large: 5px;
+
+
+//== Tables
+//
+//## Customizes the `.table` component with basic values, each used across all table variations.
+
+//** Padding for `| `s and ` | `s.
+@table-cell-padding: 8px;
+//** Padding for cells in `.table-condensed`.
+@table-condensed-cell-padding: 5px;
+
+//** Default background color used for all tables.
+@table-bg: transparent;
+//** Background color used for `.table-striped`.
+@table-bg-accent: #f9f9f9;
+//** Background color used for `.table-hover`.
+@table-bg-hover: #f5f5f5;
+@table-bg-active: @table-bg-hover;
+
+//** Border color for table and cell borders.
+@table-border-color: #ddd;
+
+
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+
+@btn-font-weight: 700;
+
+@btn-default-color: #333;
+@btn-default-bg: #fff;
+@btn-default-border: #ccc;
+
+@btn-primary-color: #fff;
+@btn-primary-bg: @brand-primary;
+@btn-primary-border: darken(@btn-primary-bg, 10%);
+
+@btn-success-color: #fff;
+@btn-success-bg: @brand-success;
+@btn-success-border: darken(@btn-success-bg, 10%);
+
+@btn-info-color: #fff;
+@btn-info-bg: @brand-info;
+@btn-info-border: darken(@btn-info-bg, 15%);
+
+@btn-warning-color: #fff;
+@btn-warning-bg: @brand-warning;
+@btn-warning-border: darken(@btn-warning-bg, 10%);
+
+@btn-danger-color: #fff;
+@btn-danger-bg: @brand-danger;
+@btn-danger-border: darken(@btn-danger-bg, 10%);
+
+@btn-link-disabled-color: @gray-light;
+
+
+//== Forms
+//
+//##
+
+//** `` background color
+@input-bg: #fff;
+//** `` background color
+@input-bg-disabled: @gray-lighter;
+
+//** Text color for ``s
+@input-color: @gray;
+//** `` border color
+@input-border: #ccc;
+//** `` border radius
+@input-border-radius: @border-radius-base;
+//** Border color for inputs on focus
+@input-border-focus: #66afe9;
+
+//** Placeholder text color
+@input-color-placeholder: @gray-light;
+
+//** Default `.form-control` height
+@input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 2);
+//** Large `.form-control` height
+@input-height-large: (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
+//** Small `.form-control` height
+@input-height-small: (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
+
+@legend-color: @gray-dark;
+@legend-border-color: #e5e5e5;
+
+//** Background color for textual input addons
+@input-group-addon-bg: @gray-lighter;
+//** Border color for textual input addons
+@input-group-addon-border-color: @input-border;
+
+
+//== Dropdowns
+//
+//## Dropdown menu container and contents.
+
+//** Background for the dropdown menu.
+@dropdown-bg: #fff;
+//** Dropdown menu `border-color`.
+@dropdown-border: rgba(0,0,0,.15);
+//** Dropdown menu `border-color` **for IE8**.
+@dropdown-fallback-border: #ccc;
+//** Divider color for between dropdown items.
+@dropdown-divider-bg: #e5e5e5;
+
+//** Dropdown link text color.
+@dropdown-link-color: @gray-dark;
+//** Hover color for dropdown links.
+@dropdown-link-hover-color: #fff;
+//** Hover background for dropdown links.
+@dropdown-link-hover-bg: @brand-primary;
+
+//** Active dropdown menu item text color.
+@dropdown-link-active-color: @component-active-color;
+//** Active dropdown menu item background color.
+@dropdown-link-active-bg: @component-active-bg;
+
+//** Disabled dropdown menu item background color.
+@dropdown-link-disabled-color: @gray-light;
+
+//** Text color for headers within dropdown menus.
+@dropdown-header-color: @gray-light;
+
+// Note: Deprecated @dropdown-caret-color as of v3.1.0
+@dropdown-caret-color: #000;
+
+
+//-- Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+//
+// Note: These variables are not generated into the Customizer.
+
+@zindex-navbar: 1000;
+@zindex-dropdown: 1000;
+@zindex-popover: 1010;
+@zindex-tooltip: 1030;
+@zindex-navbar-fixed: 1030;
+@zindex-modal-background: 1040;
+@zindex-modal: 1050;
+
+
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+
+// Extra small screen / phone
+// Note: Deprecated @screen-xs and @screen-phone as of v3.0.1
+@screen-xs: 480px;
+@screen-xs-min: @screen-xs;
+@screen-phone: @screen-xs-min;
+
+// Small screen / tablet
+// Note: Deprecated @screen-sm and @screen-tablet as of v3.0.1
+@screen-sm: 768px;
+@screen-sm-min: @screen-sm;
+@screen-tablet: @screen-sm-min;
+
+// Medium screen / desktop
+// Note: Deprecated @screen-md and @screen-desktop as of v3.0.1
+@screen-md: 992px;
+@screen-md-min: @screen-md;
+@screen-desktop: @screen-md-min;
+
+// Large screen / wide desktop
+// Note: Deprecated @screen-lg and @screen-lg-desktop as of v3.0.1
+@screen-lg: 1200px;
+@screen-lg-min: @screen-lg;
+@screen-lg-desktop: @screen-lg-min;
+
+// So media queries don't overlap when required, provide a maximum
+@screen-xs-max: (@screen-sm-min - 1);
+@screen-sm-max: (@screen-md-min - 1);
+@screen-md-max: (@screen-lg-min - 1);
+
+
+//== Grid system
+//
+//## Define your custom responsive grid.
+
+//** Number of columns in the grid.
+@grid-columns: 12;
+//** Padding between columns. Gets divided in half for the left and right.
+@grid-gutter-width: 30px;
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+@grid-float-breakpoint: @screen-sm-min;
+//** Point at which the navbar begins collapsing.
+@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
+
+
+//== Container sizes
+//
+//## Define the maximum width of `.container` for different screen sizes.
+
+// Small screen / tablet
+@container-tablet: ((720px + @grid-gutter-width));
+//** For `@screen-sm-min` and up.
+@container-sm: @container-tablet;
+
+// Medium screen / desktop
+@container-desktop: ((940px + @grid-gutter-width));
+//** For `@screen-md-min` and up.
+@container-md: @container-desktop;
+
+// Large screen / wide desktop
+@container-large-desktop: ((1140px + @grid-gutter-width));
+//** For `@screen-lg-min` and up.
+@container-lg: @container-large-desktop;
+
+
+//== Navbar
+//
+//##
+
+// Basics of a navbar
+@navbar-height: 60px;
+@navbar-margin-bottom: 0;
+@navbar-border-radius: 0;
+@navbar-padding-horizontal: floor((@grid-gutter-width / 2));
+@navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2);
+@navbar-collapse-max-height: 340px;
+
+@navbar-default-color: #777;
+@navbar-default-bg: #fff;
+@navbar-default-border: @gray-lighter;
+
+// Navbar links
+@navbar-default-link-color: @link-color;
+@navbar-default-link-hover-color: @link-hover-color;
+@navbar-default-link-hover-bg: @link-hover-color;
+@navbar-default-link-active-color: #fff;
+@navbar-default-link-active-bg: @link-hover-color;
+@navbar-default-link-disabled-color: #ccc;
+@navbar-default-link-disabled-bg: transparent;
+
+// Navbar brand label
+@navbar-default-brand-color: @navbar-default-link-color;
+@navbar-default-brand-hover-color: darken(@navbar-default-brand-color, 10%);
+@navbar-default-brand-hover-bg: transparent;
+
+// Navbar toggle
+@navbar-default-toggle-hover-bg: @link-hover-color;
+@navbar-default-toggle-border-color: @link-color;
+
+//== Navs
+//
+//##
+
+//=== Shared nav styles
+@nav-link-padding: 10px 15px;
+@nav-link-hover-bg: @link-color;
+
+@nav-disabled-link-color: @gray-light;
+@nav-disabled-link-hover-color: @gray-light;
+
+@nav-open-link-hover-color: #fff;
+
+//== Tabs
+@nav-tabs-border-color: #ddd;
+
+@nav-tabs-link-hover-border-color: @link-color;
+
+@nav-tabs-active-link-hover-bg: @body-bg;
+@nav-tabs-active-link-hover-color: @gray;
+@nav-tabs-active-link-hover-border-color: #ddd;
+
+@nav-tabs-justified-link-border-color: #ddd;
+@nav-tabs-justified-active-link-border-color: @body-bg;
+
+//== Pills
+@nav-pills-border-radius: @border-radius-base;
+@nav-pills-active-link-hover-bg: @component-active-bg;
+@nav-pills-active-link-hover-color: @component-active-color;
+
+
+//== Pagination
+//
+//##
+
+@pagination-color: @link-color;
+@pagination-bg: #fff;
+@pagination-border: #ddd;
+
+@pagination-hover-color: @link-hover-color;
+@pagination-hover-bg: @gray-lighter;
+@pagination-hover-border: #ddd;
+
+@pagination-active-color: #fff;
+@pagination-active-bg: @brand-primary;
+@pagination-active-border: @brand-primary;
+
+@pagination-disabled-color: @gray-light;
+@pagination-disabled-bg: #fff;
+@pagination-disabled-border: #ddd;
+
+
+//== Pager
+//
+//##
+
+@pager-bg: @pagination-bg;
+@pager-border: @pagination-border;
+@pager-border-radius: 15px;
+
+@pager-hover-bg: @pagination-hover-bg;
+
+@pager-active-bg: @pagination-active-bg;
+@pager-active-color: @pagination-active-color;
+
+@pager-disabled-color: @pagination-disabled-color;
+
+
+//== Jumbotron
+//
+//##
+
+@jumbotron-padding: 30px;
+@jumbotron-color: inherit;
+@jumbotron-bg: @gray-lighter;
+@jumbotron-heading-color: inherit;
+@jumbotron-font-size: ceil((@font-size-base * 1.5));
+
+
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+
+@state-success-text: darken(@brand-success, 20%);
+@state-success-bg: lighten(@brand-success, 50%);
+@state-success-border: darken(@brand-success, 5%);
+
+@state-info-text: darken(@brand-info, 20%);
+@state-info-bg: lighten(@brand-info, 47%);
+@state-info-border: darken(@brand-info, 7%);
+
+@state-warning-text: darken(@brand-warning, 10%);
+@state-warning-bg: lighten(@brand-warning, 45%);
+@state-warning-border: @brand-warning;
+
+@state-danger-text: darken(@brand-danger, 10%);
+@state-danger-bg: lighten(@brand-danger, 50%);
+@state-danger-border: darken(@brand-danger, 5%);
+
+
+//== Tooltips
+//
+//##
+
+//** Tooltip max width
+@tooltip-max-width: 200px;
+//** Tooltip text color
+@tooltip-color: #fff;
+//** Tooltip background color
+@tooltip-bg: #000;
+@tooltip-opacity: .9;
+
+//** Tooltip arrow width
+@tooltip-arrow-width: 5px;
+//** Tooltip arrow color
+@tooltip-arrow-color: @tooltip-bg;
+
+
+//== Popovers
+//
+//##
+
+//** Popover body background color
+@popover-bg: #fff;
+//** Popover maximum width
+@popover-max-width: 276px;
+//** Popover border color
+@popover-border-color: rgba(0,0,0,.2);
+//** Popover fallback border color
+@popover-fallback-border-color: #ccc;
+
+//** Popover title background color
+@popover-title-bg: darken(@popover-bg, 3%);
+
+//** Popover arrow width
+@popover-arrow-width: 10px;
+//** Popover arrow color
+@popover-arrow-color: #fff;
+
+//** Popover outer arrow width
+@popover-arrow-outer-width: (@popover-arrow-width + 1);
+//** Popover outer arrow color
+@popover-arrow-outer-color: fadein(@popover-border-color, 5%);
+//** Popover outer arrow fallback color
+@popover-arrow-outer-fallback-color: darken(@popover-fallback-border-color, 20%);
+
+
+//== Labels
+//
+//##
+
+//** Default label background color
+@label-default-bg: @gray-light;
+//** Primary label background color
+@label-primary-bg: @brand-primary;
+//** Success label background color
+@label-success-bg: @brand-success;
+//** Info label background color
+@label-info-bg: @brand-info;
+//** Warning label background color
+@label-warning-bg: @brand-warning;
+//** Danger label background color
+@label-danger-bg: @brand-danger;
+
+//** Default label text color
+@label-color: #fff;
+//** Default text color of a linked label
+@label-link-hover-color: #fff;
+
+
+//== Modals
+//
+//##
+
+//** Padding applied to the modal body
+@modal-inner-padding: 20px;
+
+//** Padding applied to the modal title
+@modal-title-padding: 15px;
+//** Modal title line-height
+@modal-title-line-height: @line-height-base;
+
+//** Background color of modal content area
+@modal-content-bg: #fff;
+//** Modal content border color
+@modal-content-border-color: rgba(0,0,0,.2);
+//** Modal content border color **for IE8**
+@modal-content-fallback-border-color: #999;
+
+//** Modal backdrop background color
+@modal-backdrop-bg: #000;
+//** Modal backdrop opacity
+@modal-backdrop-opacity: .5;
+//** Modal header border color
+@modal-header-border-color: #e5e5e5;
+//** Modal footer border color
+@modal-footer-border-color: @modal-header-border-color;
+@modal-footer-background-color: @gray-lightest;
+
+@modal-lg: 900px;
+@modal-md: 600px;
+@modal-sm: 300px;
+
+
+//== Alerts
+//
+//## Define alert colors, border radius, and padding.
+
+@alert-padding: 15px;
+@alert-border-radius: @border-radius-base;
+@alert-link-font-weight: bold;
+
+@alert-success-bg: @state-success-bg;
+@alert-success-text: @state-success-text;
+@alert-success-border: @state-success-border;
+
+@alert-info-bg: @state-info-bg;
+@alert-info-text: @state-info-text;
+@alert-info-border: @state-info-border;
+
+@alert-warning-bg: @state-warning-bg;
+@alert-warning-text: @state-warning-text;
+@alert-warning-border: @state-warning-border;
+
+@alert-danger-bg: @state-danger-bg;
+@alert-danger-text: @state-danger-text;
+@alert-danger-border: @state-danger-border;
+
+
+//== Progress bars
+//
+//##
+
+//** Background color of the whole progress component
+@progress-bg: white;
+@progress-border-color: @gray-lighter;
+//** Progress bar text color
+@progress-bar-color: #fff;
+
+//** Default progress bar color
+@progress-bar-bg: @brand-primary;
+//** Success progress bar color
+@progress-bar-success-bg: @brand-success;
+//** Warning progress bar color
+@progress-bar-warning-bg: @brand-warning;
+//** Danger progress bar color
+@progress-bar-danger-bg: @brand-danger;
+//** Info progress bar color
+@progress-bar-info-bg: @brand-info;
+
+
+//== List group
+//
+//##
+
+//** Background color on `.list-group-item`
+@list-group-bg: #fff;
+//** `.list-group-item` border color
+@list-group-border: #ddd;
+//** List group border radius
+@list-group-border-radius: @border-radius-base;
+
+//** Background color of single list elements on hover
+@list-group-hover-bg: #f5f5f5;
+//** Text color of active list elements
+@list-group-active-color: @component-active-color;
+//** Background color of active list elements
+@list-group-active-bg: @component-active-bg;
+//** Border color of active list elements
+@list-group-active-border: @list-group-active-bg;
+@list-group-active-text-color: lighten(@list-group-active-bg, 40%);
+
+@list-group-link-color: #555;
+@list-group-link-heading-color: #333;
+
+
+//== Panels
+//
+//##
+
+@panel-bg: #fff;
+@panel-body-padding: 15px;
+@panel-border-radius: @border-radius-base;
+
+//** Border color for elements within panels
+@panel-inner-border: #ddd;
+@panel-footer-bg: #f5f5f5;
+
+@panel-default-text: @gray-dark;
+@panel-default-border: #ddd;
+@panel-default-heading-bg: #f5f5f5;
+
+@panel-primary-text: #fff;
+@panel-primary-border: @brand-primary;
+@panel-primary-heading-bg: @brand-primary;
+
+@panel-success-text: @state-success-text;
+@panel-success-border: @state-success-border;
+@panel-success-heading-bg: @state-success-bg;
+
+@panel-info-text: @state-info-text;
+@panel-info-border: @state-info-border;
+@panel-info-heading-bg: @state-info-bg;
+
+@panel-warning-text: @state-warning-text;
+@panel-warning-border: @state-warning-border;
+@panel-warning-heading-bg: @state-warning-bg;
+
+@panel-danger-text: @state-danger-text;
+@panel-danger-border: @state-danger-border;
+@panel-danger-heading-bg: @state-danger-bg;
+
+
+//== Thumbnails
+//
+//##
+
+//** Padding around the thumbnail image
+@thumbnail-padding: 4px;
+//** Thumbnail background color
+@thumbnail-bg: @body-bg;
+//** Thumbnail border color
+@thumbnail-border: #ddd;
+//** Thumbnail border radius
+@thumbnail-border-radius: @border-radius-base;
+
+//** Custom text color for thumbnail captions
+@thumbnail-caption-color: @text-color;
+//** Padding around the thumbnail caption
+@thumbnail-caption-padding: 9px;
+
+
+//== Wells
+//
+//##
+
+@well-bg: #f5f5f5;
+@well-border: darken(@well-bg, 7%);
+
+
+//== Badges
+//
+//##
+
+@badge-color: #fff;
+//** Linked badge text color on hover
+@badge-link-hover-color: #fff;
+@badge-bg: @gray-light;
+
+//** Badge text color in active nav link
+@badge-active-color: @link-color;
+//** Badge background color in active nav link
+@badge-active-bg: #fff;
+
+@badge-font-weight: bold;
+@badge-line-height: 1;
+@badge-border-radius: 10px;
+
+
+//== Breadcrumbs
+//
+//##
+
+@breadcrumb-padding-vertical: 8px;
+@breadcrumb-padding-horizontal: 15px;
+//** Breadcrumb background color
+@breadcrumb-bg: #f5f5f5;
+//** Breadcrumb text color
+@breadcrumb-color: #ccc;
+//** Text color of current page in the breadcrumb
+@breadcrumb-active-color: @gray-light;
+//** Textual separator for between breadcrumb elements
+@breadcrumb-separator: "/";
+
+
+//== Carousel
+//
+//##
+
+@carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6);
+
+@carousel-control-color: #fff;
+@carousel-control-width: 15%;
+@carousel-control-opacity: .5;
+@carousel-control-font-size: 20px;
+
+@carousel-indicator-active-bg: #fff;
+@carousel-indicator-border-color: #fff;
+
+@carousel-caption-color: #fff;
+
+
+//== Close
+//
+//##
+
+@close-font-weight: bold;
+@close-color: #000;
+@close-text-shadow: 0 1px 0 #fff;
+
+
+//== Code
+//
+//##
+
+@code-color: #c7254e;
+@code-bg: #f9f2f4;
+
+@kbd-color: #fff;
+@kbd-bg: #333;
+
+@pre-bg: #f5f5f5;
+@pre-color: @gray-dark;
+@pre-border-color: #ccc;
+@pre-scrollable-max-height: 340px;
+
+
+//== Type
+//
+//##
+
+//** Text muted color
+@text-muted: @gray-light;
+//** Abbreviations and acronyms border color
+@abbr-border-color: @gray-light;
+//** Headings small color
+@headings-small-color: @gray-light;
+//** Blockquote small color
+@blockquote-small-color: @gray;
+//** Blockquote font size
+@blockquote-font-size: (@font-size-base * 1.125);
+//** Blockquote border color
+@blockquote-border-color: @gray-lighter;
+//** Page header border color
+@page-header-border-color: @gray-lighter;
+
+
+//== Miscellaneous
+//
+//##
+
+//** Horizontal line color.
+@hr-border: @gray-lighter;
+
+//** Horizontal offset for forms and lists.
+@component-offset-horizontal: 180px;
+
+@content-margin-top: @line-height-computed;
+@content-margin-top: @line-height-computed;
+
+// Custom
+
+@left-menu-width: 260px;
+@left-menu-animation-duration: 0.35s;
+
+@toolbar-border-color: @gray-lighter;
+@file-tree-droppable-background-color: rgb(252, 231, 199);
+
+@editor-dark-background-color: #333;
+@editor-dark-toolbar-border-color: #222;
+@editor-dark-highlight-color: #FFA03A;
diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less
new file mode 100644
index 0000000000..23151bd9c4
--- /dev/null
+++ b/services/web/public/stylesheets/core/ol-variables.less
@@ -0,0 +1,37 @@
+@ol-green: #4A9F48;
+@ol-dark-green: #1C5B26;
+
+//== Colors
+//
+//## Gray and brand colors for use across Bootstrap.
+
+
+@gray-darker: #252525;
+@gray-dark: #505050;
+@gray: #7a7a7a;
+@gray-light: #a4a4a4;
+@gray-lighter: #cfcfcf;
+@gray-lightest: #f0f0f0;
+
+@blue: #405ebf;
+@blueDark: #040D2D;
+@green: #46a546;
+@red: #a93529;
+@yellow: #A1A729;
+@orange: #f89406;
+@pink: #c3325f;
+@purple: #7a43b6;
+
+@brand-primary: @ol-green;
+@brand-success: @green;
+@brand-info: @ol-dark-green;
+@brand-warning: @orange;
+@brand-danger: #E03A06;
+
+@navbar-brand-image-url: url(/img/ol-brand/logo-horizontal.png);
+
+@editor-loading-logo-padding-top: 115.44%;
+@editor-loading-logo-background-url: url(/img/ol-brand/overleaf-o-grey.svg);
+@editor-loading-logo-foreground-url: url(/img/ol-brand/overleaf-o.svg);
+
+@import "./_common-variables.less";
diff --git a/services/web/public/stylesheets/core/variables.less b/services/web/public/stylesheets/core/variables.less
index 7c1876ef9a..5fd9bfeddc 100755
--- a/services/web/public/stylesheets/core/variables.less
+++ b/services/web/public/stylesheets/core/variables.less
@@ -1,8 +1,3 @@
-//
-// Variables
-// --------------------------------------------------
-
-
//== Colors
//
//## Gray and brand colors for use across Bootstrap.
@@ -30,804 +25,10 @@
@brand-warning: @orange;
@brand-danger: #E03A06;
-//== Scaffolding
-//
-// ## Settings for some of the most global styles.
+@navbar-brand-image-url: url(/img/brand/logo-horizontal.svg);
-//** Background color for ``.
-@body-bg: #fff;
-//** Global text color on ``.
-@text-color: @gray-dark;
+@editor-loading-logo-padding-top: 86.2%;
+@editor-loading-logo-background-url: url(/img/brand/lion-grey.svg);
+@editor-loading-logo-foreground-url: url(/img/brand/lion.svg);
-//** Global textual link color.
-@link-color: @brand-primary;
-//** Link hover color set via `darken()` function.
-@link-hover-color: darken(@link-color, 15%);
-
-
-//== Typography
-//
-//## Font, line-height, and color for body text, headings, and more.
-
-@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
-//@import url(https://fonts.googleapis.com/css?family=PT+Serif:400,600,700);
-//@import url(https://fonts.googleapis.com/css?family=PT+Serif:400,400i,700,700i);
-@import url(https://fonts.googleapis.com/css?family=Merriweather:400,400i,700,700i);
-
-@font-family-sans-serif: "Open Sans", sans-serif;
-@font-family-serif: "Merriweather", serif;
-//** Default monospace fonts for ``, ``, and ``.
-@font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
-@font-family-base: @font-family-sans-serif;
-
-@font-size-base: 16px;
-@font-size-large: ceil((@font-size-base * 1.25)); // ~18px
-@font-size-small: ceil((@font-size-base * 0.85)); // ~12px
-
-@font-size-h1: floor((@font-size-base * 2)); // ~36px
-@font-size-h2: floor((@font-size-base * 1.6)); // ~30px
-@font-size-h3: ceil((@font-size-base * 1.25)); // ~24px
-@font-size-h4: ceil((@font-size-base * 1.1)); // ~18px
-@font-size-h5: @font-size-base;
-@font-size-h6: ceil((@font-size-base * 0.85)); // ~12px
-
-//** Unit-less `line-height` for use in components like buttons.
-@line-height-base: 1.5625; // 20/14
-//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
-@line-height-computed: floor((@font-size-base * @line-height-base)); // ~20px
-
-//** By default, this inherits from the ``.
-@headings-font-family: @font-family-serif;
-@headings-font-weight: 500;
-@headings-line-height: 1.1;
-@headings-color: @gray-dark;
-
-
-//-- Iconography
-//
-//## Specify custom locations of the include Glyphicons icon font. Useful for those including Bootstrap via Bower.
-
-@icon-font-path: "../fonts/";
-@icon-font-name: "glyphicons-halflings-regular";
-@icon-font-svg-id: "glyphicons_halflingsregular";
-
-//== Components
-//
-//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
-
-@padding-base-vertical: 5px;
-@padding-base-horizontal: 16px;
-
-@padding-large-vertical: 10px;
-@padding-large-horizontal: 16px;
-
-@padding-small-vertical: 5px;
-@padding-small-horizontal: 10px;
-
-@padding-xs-vertical: 1px;
-@padding-xs-horizontal: 5px;
-
-@line-height-large: 1.33;
-@line-height-small: 1.5;
-
-@border-radius-base: 3px;
-@border-radius-large: 5px;
-@border-radius-small: 2px;
-
-//** Global color for active items (e.g., navs or dropdowns).
-@component-active-color: #fff;
-//** Global background color for active items (e.g., navs or dropdowns).
-@component-active-bg: @brand-primary;
-
-//** Width of the `border` for generating carets that indicator dropdowns.
-@caret-width-base: 4px;
-//** Carets increase slightly in size for larger components.
-@caret-width-large: 5px;
-
-
-//== Tables
-//
-//## Customizes the `.table` component with basic values, each used across all table variations.
-
-//** Padding for `| `s and ` | `s.
-@table-cell-padding: 8px;
-//** Padding for cells in `.table-condensed`.
-@table-condensed-cell-padding: 5px;
-
-//** Default background color used for all tables.
-@table-bg: transparent;
-//** Background color used for `.table-striped`.
-@table-bg-accent: #f9f9f9;
-//** Background color used for `.table-hover`.
-@table-bg-hover: #f5f5f5;
-@table-bg-active: @table-bg-hover;
-
-//** Border color for table and cell borders.
-@table-border-color: #ddd;
-
-
-//== Buttons
-//
-//## For each of Bootstrap's buttons, define text, background and border color.
-
-@btn-font-weight: 700;
-
-@btn-default-color: #333;
-@btn-default-bg: #fff;
-@btn-default-border: #ccc;
-
-@btn-primary-color: #fff;
-@btn-primary-bg: @brand-primary;
-@btn-primary-border: darken(@btn-primary-bg, 10%);
-
-@btn-success-color: #fff;
-@btn-success-bg: @brand-success;
-@btn-success-border: darken(@btn-success-bg, 10%);
-
-@btn-info-color: #fff;
-@btn-info-bg: @brand-info;
-@btn-info-border: darken(@btn-info-bg, 15%);
-
-@btn-warning-color: #fff;
-@btn-warning-bg: @brand-warning;
-@btn-warning-border: darken(@btn-warning-bg, 10%);
-
-@btn-danger-color: #fff;
-@btn-danger-bg: @brand-danger;
-@btn-danger-border: darken(@btn-danger-bg, 10%);
-
-@btn-link-disabled-color: @gray-light;
-
-
-//== Forms
-//
-//##
-
-//** `` background color
-@input-bg: #fff;
-//** `` background color
-@input-bg-disabled: @gray-lighter;
-
-//** Text color for ``s
-@input-color: @gray;
-//** `` border color
-@input-border: #ccc;
-//** `` border radius
-@input-border-radius: @border-radius-base;
-//** Border color for inputs on focus
-@input-border-focus: #66afe9;
-
-//** Placeholder text color
-@input-color-placeholder: @gray-light;
-
-//** Default `.form-control` height
-@input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 2);
-//** Large `.form-control` height
-@input-height-large: (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
-//** Small `.form-control` height
-@input-height-small: (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
-
-@legend-color: @gray-dark;
-@legend-border-color: #e5e5e5;
-
-//** Background color for textual input addons
-@input-group-addon-bg: @gray-lighter;
-//** Border color for textual input addons
-@input-group-addon-border-color: @input-border;
-
-
-//== Dropdowns
-//
-//## Dropdown menu container and contents.
-
-//** Background for the dropdown menu.
-@dropdown-bg: #fff;
-//** Dropdown menu `border-color`.
-@dropdown-border: rgba(0,0,0,.15);
-//** Dropdown menu `border-color` **for IE8**.
-@dropdown-fallback-border: #ccc;
-//** Divider color for between dropdown items.
-@dropdown-divider-bg: #e5e5e5;
-
-//** Dropdown link text color.
-@dropdown-link-color: @gray-dark;
-//** Hover color for dropdown links.
-@dropdown-link-hover-color: #fff;
-//** Hover background for dropdown links.
-@dropdown-link-hover-bg: @brand-primary;
-
-//** Active dropdown menu item text color.
-@dropdown-link-active-color: @component-active-color;
-//** Active dropdown menu item background color.
-@dropdown-link-active-bg: @component-active-bg;
-
-//** Disabled dropdown menu item background color.
-@dropdown-link-disabled-color: @gray-light;
-
-//** Text color for headers within dropdown menus.
-@dropdown-header-color: @gray-light;
-
-// Note: Deprecated @dropdown-caret-color as of v3.1.0
-@dropdown-caret-color: #000;
-
-
-//-- Z-index master list
-//
-// Warning: Avoid customizing these values. They're used for a bird's eye view
-// of components dependent on the z-axis and are designed to all work together.
-//
-// Note: These variables are not generated into the Customizer.
-
-@zindex-navbar: 1000;
-@zindex-dropdown: 1000;
-@zindex-popover: 1010;
-@zindex-tooltip: 1030;
-@zindex-navbar-fixed: 1030;
-@zindex-modal-background: 1040;
-@zindex-modal: 1050;
-
-
-//== Media queries breakpoints
-//
-//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
-
-// Extra small screen / phone
-// Note: Deprecated @screen-xs and @screen-phone as of v3.0.1
-@screen-xs: 480px;
-@screen-xs-min: @screen-xs;
-@screen-phone: @screen-xs-min;
-
-// Small screen / tablet
-// Note: Deprecated @screen-sm and @screen-tablet as of v3.0.1
-@screen-sm: 768px;
-@screen-sm-min: @screen-sm;
-@screen-tablet: @screen-sm-min;
-
-// Medium screen / desktop
-// Note: Deprecated @screen-md and @screen-desktop as of v3.0.1
-@screen-md: 992px;
-@screen-md-min: @screen-md;
-@screen-desktop: @screen-md-min;
-
-// Large screen / wide desktop
-// Note: Deprecated @screen-lg and @screen-lg-desktop as of v3.0.1
-@screen-lg: 1200px;
-@screen-lg-min: @screen-lg;
-@screen-lg-desktop: @screen-lg-min;
-
-// So media queries don't overlap when required, provide a maximum
-@screen-xs-max: (@screen-sm-min - 1);
-@screen-sm-max: (@screen-md-min - 1);
-@screen-md-max: (@screen-lg-min - 1);
-
-
-//== Grid system
-//
-//## Define your custom responsive grid.
-
-//** Number of columns in the grid.
-@grid-columns: 12;
-//** Padding between columns. Gets divided in half for the left and right.
-@grid-gutter-width: 30px;
-// Navbar collapse
-//** Point at which the navbar becomes uncollapsed.
-@grid-float-breakpoint: @screen-sm-min;
-//** Point at which the navbar begins collapsing.
-@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
-
-
-//== Container sizes
-//
-//## Define the maximum width of `.container` for different screen sizes.
-
-// Small screen / tablet
-@container-tablet: ((720px + @grid-gutter-width));
-//** For `@screen-sm-min` and up.
-@container-sm: @container-tablet;
-
-// Medium screen / desktop
-@container-desktop: ((940px + @grid-gutter-width));
-//** For `@screen-md-min` and up.
-@container-md: @container-desktop;
-
-// Large screen / wide desktop
-@container-large-desktop: ((1140px + @grid-gutter-width));
-//** For `@screen-lg-min` and up.
-@container-lg: @container-large-desktop;
-
-
-//== Navbar
-//
-//##
-
-// Basics of a navbar
-@navbar-height: 60px;
-@navbar-margin-bottom: 0;
-@navbar-border-radius: 0;
-@navbar-padding-horizontal: floor((@grid-gutter-width / 2));
-@navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2);
-@navbar-collapse-max-height: 340px;
-
-@navbar-default-color: #777;
-@navbar-default-bg: #fff;
-@navbar-default-border: @gray-lighter;
-
-// Navbar links
-@navbar-default-link-color: @link-color;
-@navbar-default-link-hover-color: @link-hover-color;
-@navbar-default-link-hover-bg: @link-hover-color;
-@navbar-default-link-active-color: #fff;
-@navbar-default-link-active-bg: @link-hover-color;
-@navbar-default-link-disabled-color: #ccc;
-@navbar-default-link-disabled-bg: transparent;
-
-// Navbar brand label
-@navbar-default-brand-color: @navbar-default-link-color;
-@navbar-default-brand-hover-color: darken(@navbar-default-brand-color, 10%);
-@navbar-default-brand-hover-bg: transparent;
-
-// Navbar toggle
-@navbar-default-toggle-hover-bg: @link-hover-color;
-@navbar-default-toggle-border-color: @link-color;
-
-//== Navs
-//
-//##
-
-//=== Shared nav styles
-@nav-link-padding: 10px 15px;
-@nav-link-hover-bg: @link-color;
-
-@nav-disabled-link-color: @gray-light;
-@nav-disabled-link-hover-color: @gray-light;
-
-@nav-open-link-hover-color: #fff;
-
-//== Tabs
-@nav-tabs-border-color: #ddd;
-
-@nav-tabs-link-hover-border-color: @link-color;
-
-@nav-tabs-active-link-hover-bg: @body-bg;
-@nav-tabs-active-link-hover-color: @gray;
-@nav-tabs-active-link-hover-border-color: #ddd;
-
-@nav-tabs-justified-link-border-color: #ddd;
-@nav-tabs-justified-active-link-border-color: @body-bg;
-
-//== Pills
-@nav-pills-border-radius: @border-radius-base;
-@nav-pills-active-link-hover-bg: @component-active-bg;
-@nav-pills-active-link-hover-color: @component-active-color;
-
-
-//== Pagination
-//
-//##
-
-@pagination-color: @link-color;
-@pagination-bg: #fff;
-@pagination-border: #ddd;
-
-@pagination-hover-color: @link-hover-color;
-@pagination-hover-bg: @gray-lighter;
-@pagination-hover-border: #ddd;
-
-@pagination-active-color: #fff;
-@pagination-active-bg: @brand-primary;
-@pagination-active-border: @brand-primary;
-
-@pagination-disabled-color: @gray-light;
-@pagination-disabled-bg: #fff;
-@pagination-disabled-border: #ddd;
-
-
-//== Pager
-//
-//##
-
-@pager-bg: @pagination-bg;
-@pager-border: @pagination-border;
-@pager-border-radius: 15px;
-
-@pager-hover-bg: @pagination-hover-bg;
-
-@pager-active-bg: @pagination-active-bg;
-@pager-active-color: @pagination-active-color;
-
-@pager-disabled-color: @pagination-disabled-color;
-
-
-//== Jumbotron
-//
-//##
-
-@jumbotron-padding: 30px;
-@jumbotron-color: inherit;
-@jumbotron-bg: @gray-lighter;
-@jumbotron-heading-color: inherit;
-@jumbotron-font-size: ceil((@font-size-base * 1.5));
-
-
-//== Form states and alerts
-//
-//## Define colors for form feedback states and, by default, alerts.
-
-@state-success-text: darken(@brand-success, 20%);
-@state-success-bg: lighten(@brand-success, 50%);
-@state-success-border: darken(@brand-success, 5%);
-
-@state-info-text: darken(@brand-info, 20%);
-@state-info-bg: lighten(@brand-info, 47%);
-@state-info-border: darken(@brand-info, 7%);
-
-@state-warning-text: darken(@brand-warning, 10%);
-@state-warning-bg: lighten(@brand-warning, 45%);
-@state-warning-border: @brand-warning;
-
-@state-danger-text: darken(@brand-danger, 10%);
-@state-danger-bg: lighten(@brand-danger, 50%);
-@state-danger-border: darken(@brand-danger, 5%);
-
-
-//== Tooltips
-//
-//##
-
-//** Tooltip max width
-@tooltip-max-width: 200px;
-//** Tooltip text color
-@tooltip-color: #fff;
-//** Tooltip background color
-@tooltip-bg: #000;
-@tooltip-opacity: .9;
-
-//** Tooltip arrow width
-@tooltip-arrow-width: 5px;
-//** Tooltip arrow color
-@tooltip-arrow-color: @tooltip-bg;
-
-
-//== Popovers
-//
-//##
-
-//** Popover body background color
-@popover-bg: #fff;
-//** Popover maximum width
-@popover-max-width: 276px;
-//** Popover border color
-@popover-border-color: rgba(0,0,0,.2);
-//** Popover fallback border color
-@popover-fallback-border-color: #ccc;
-
-//** Popover title background color
-@popover-title-bg: darken(@popover-bg, 3%);
-
-//** Popover arrow width
-@popover-arrow-width: 10px;
-//** Popover arrow color
-@popover-arrow-color: #fff;
-
-//** Popover outer arrow width
-@popover-arrow-outer-width: (@popover-arrow-width + 1);
-//** Popover outer arrow color
-@popover-arrow-outer-color: fadein(@popover-border-color, 5%);
-//** Popover outer arrow fallback color
-@popover-arrow-outer-fallback-color: darken(@popover-fallback-border-color, 20%);
-
-
-//== Labels
-//
-//##
-
-//** Default label background color
-@label-default-bg: @gray-light;
-//** Primary label background color
-@label-primary-bg: @brand-primary;
-//** Success label background color
-@label-success-bg: @brand-success;
-//** Info label background color
-@label-info-bg: @brand-info;
-//** Warning label background color
-@label-warning-bg: @brand-warning;
-//** Danger label background color
-@label-danger-bg: @brand-danger;
-
-//** Default label text color
-@label-color: #fff;
-//** Default text color of a linked label
-@label-link-hover-color: #fff;
-
-
-//== Modals
-//
-//##
-
-//** Padding applied to the modal body
-@modal-inner-padding: 20px;
-
-//** Padding applied to the modal title
-@modal-title-padding: 15px;
-//** Modal title line-height
-@modal-title-line-height: @line-height-base;
-
-//** Background color of modal content area
-@modal-content-bg: #fff;
-//** Modal content border color
-@modal-content-border-color: rgba(0,0,0,.2);
-//** Modal content border color **for IE8**
-@modal-content-fallback-border-color: #999;
-
-//** Modal backdrop background color
-@modal-backdrop-bg: #000;
-//** Modal backdrop opacity
-@modal-backdrop-opacity: .5;
-//** Modal header border color
-@modal-header-border-color: #e5e5e5;
-//** Modal footer border color
-@modal-footer-border-color: @modal-header-border-color;
-@modal-footer-background-color: @gray-lightest;
-
-@modal-lg: 900px;
-@modal-md: 600px;
-@modal-sm: 300px;
-
-
-//== Alerts
-//
-//## Define alert colors, border radius, and padding.
-
-@alert-padding: 15px;
-@alert-border-radius: @border-radius-base;
-@alert-link-font-weight: bold;
-
-@alert-success-bg: @state-success-bg;
-@alert-success-text: @state-success-text;
-@alert-success-border: @state-success-border;
-
-@alert-info-bg: @state-info-bg;
-@alert-info-text: @state-info-text;
-@alert-info-border: @state-info-border;
-
-@alert-warning-bg: @state-warning-bg;
-@alert-warning-text: @state-warning-text;
-@alert-warning-border: @state-warning-border;
-
-@alert-danger-bg: @state-danger-bg;
-@alert-danger-text: @state-danger-text;
-@alert-danger-border: @state-danger-border;
-
-
-//== Progress bars
-//
-//##
-
-//** Background color of the whole progress component
-@progress-bg: white;
-@progress-border-color: @gray-lighter;
-//** Progress bar text color
-@progress-bar-color: #fff;
-
-//** Default progress bar color
-@progress-bar-bg: @brand-primary;
-//** Success progress bar color
-@progress-bar-success-bg: @brand-success;
-//** Warning progress bar color
-@progress-bar-warning-bg: @brand-warning;
-//** Danger progress bar color
-@progress-bar-danger-bg: @brand-danger;
-//** Info progress bar color
-@progress-bar-info-bg: @brand-info;
-
-
-//== List group
-//
-//##
-
-//** Background color on `.list-group-item`
-@list-group-bg: #fff;
-//** `.list-group-item` border color
-@list-group-border: #ddd;
-//** List group border radius
-@list-group-border-radius: @border-radius-base;
-
-//** Background color of single list elements on hover
-@list-group-hover-bg: #f5f5f5;
-//** Text color of active list elements
-@list-group-active-color: @component-active-color;
-//** Background color of active list elements
-@list-group-active-bg: @component-active-bg;
-//** Border color of active list elements
-@list-group-active-border: @list-group-active-bg;
-@list-group-active-text-color: lighten(@list-group-active-bg, 40%);
-
-@list-group-link-color: #555;
-@list-group-link-heading-color: #333;
-
-
-//== Panels
-//
-//##
-
-@panel-bg: #fff;
-@panel-body-padding: 15px;
-@panel-border-radius: @border-radius-base;
-
-//** Border color for elements within panels
-@panel-inner-border: #ddd;
-@panel-footer-bg: #f5f5f5;
-
-@panel-default-text: @gray-dark;
-@panel-default-border: #ddd;
-@panel-default-heading-bg: #f5f5f5;
-
-@panel-primary-text: #fff;
-@panel-primary-border: @brand-primary;
-@panel-primary-heading-bg: @brand-primary;
-
-@panel-success-text: @state-success-text;
-@panel-success-border: @state-success-border;
-@panel-success-heading-bg: @state-success-bg;
-
-@panel-info-text: @state-info-text;
-@panel-info-border: @state-info-border;
-@panel-info-heading-bg: @state-info-bg;
-
-@panel-warning-text: @state-warning-text;
-@panel-warning-border: @state-warning-border;
-@panel-warning-heading-bg: @state-warning-bg;
-
-@panel-danger-text: @state-danger-text;
-@panel-danger-border: @state-danger-border;
-@panel-danger-heading-bg: @state-danger-bg;
-
-
-//== Thumbnails
-//
-//##
-
-//** Padding around the thumbnail image
-@thumbnail-padding: 4px;
-//** Thumbnail background color
-@thumbnail-bg: @body-bg;
-//** Thumbnail border color
-@thumbnail-border: #ddd;
-//** Thumbnail border radius
-@thumbnail-border-radius: @border-radius-base;
-
-//** Custom text color for thumbnail captions
-@thumbnail-caption-color: @text-color;
-//** Padding around the thumbnail caption
-@thumbnail-caption-padding: 9px;
-
-
-//== Wells
-//
-//##
-
-@well-bg: #f5f5f5;
-@well-border: darken(@well-bg, 7%);
-
-
-//== Badges
-//
-//##
-
-@badge-color: #fff;
-//** Linked badge text color on hover
-@badge-link-hover-color: #fff;
-@badge-bg: @gray-light;
-
-//** Badge text color in active nav link
-@badge-active-color: @link-color;
-//** Badge background color in active nav link
-@badge-active-bg: #fff;
-
-@badge-font-weight: bold;
-@badge-line-height: 1;
-@badge-border-radius: 10px;
-
-
-//== Breadcrumbs
-//
-//##
-
-@breadcrumb-padding-vertical: 8px;
-@breadcrumb-padding-horizontal: 15px;
-//** Breadcrumb background color
-@breadcrumb-bg: #f5f5f5;
-//** Breadcrumb text color
-@breadcrumb-color: #ccc;
-//** Text color of current page in the breadcrumb
-@breadcrumb-active-color: @gray-light;
-//** Textual separator for between breadcrumb elements
-@breadcrumb-separator: "/";
-
-
-//== Carousel
-//
-//##
-
-@carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6);
-
-@carousel-control-color: #fff;
-@carousel-control-width: 15%;
-@carousel-control-opacity: .5;
-@carousel-control-font-size: 20px;
-
-@carousel-indicator-active-bg: #fff;
-@carousel-indicator-border-color: #fff;
-
-@carousel-caption-color: #fff;
-
-
-//== Close
-//
-//##
-
-@close-font-weight: bold;
-@close-color: #000;
-@close-text-shadow: 0 1px 0 #fff;
-
-
-//== Code
-//
-//##
-
-@code-color: #c7254e;
-@code-bg: #f9f2f4;
-
-@kbd-color: #fff;
-@kbd-bg: #333;
-
-@pre-bg: #f5f5f5;
-@pre-color: @gray-dark;
-@pre-border-color: #ccc;
-@pre-scrollable-max-height: 340px;
-
-
-//== Type
-//
-//##
-
-//** Text muted color
-@text-muted: @gray-light;
-//** Abbreviations and acronyms border color
-@abbr-border-color: @gray-light;
-//** Headings small color
-@headings-small-color: @gray-light;
-//** Blockquote small color
-@blockquote-small-color: @gray;
-//** Blockquote font size
-@blockquote-font-size: (@font-size-base * 1.125);
-//** Blockquote border color
-@blockquote-border-color: @gray-lighter;
-//** Page header border color
-@page-header-border-color: @gray-lighter;
-
-
-//== Miscellaneous
-//
-//##
-
-//** Horizontal line color.
-@hr-border: @gray-lighter;
-
-//** Horizontal offset for forms and lists.
-@component-offset-horizontal: 180px;
-
-@content-margin-top: @line-height-computed;
-@content-margin-top: @line-height-computed;
-
-// Custom
-
-@left-menu-width: 260px;
-@left-menu-animation-duration: 0.35s;
-
-@toolbar-border-color: @gray-lighter;
-@file-tree-droppable-background-color: rgb(252, 231, 199);
-
-@editor-dark-background-color: #333;
-@editor-dark-toolbar-border-color: #222;
-@editor-dark-highlight-color: #FFA03A;
+@import "_common-variables.less";
diff --git a/services/web/public/stylesheets/ol-style.less b/services/web/public/stylesheets/ol-style.less
new file mode 100644
index 0000000000..ec53e5e8c8
--- /dev/null
+++ b/services/web/public/stylesheets/ol-style.less
@@ -0,0 +1,3 @@
+// Core variables and mixins
+@import "core/ol-variables.less";
+@import "_style_includes.less";
\ No newline at end of file
diff --git a/services/web/public/stylesheets/style.less b/services/web/public/stylesheets/style.less
index b3e0c15294..760f378719 100755
--- a/services/web/public/stylesheets/style.less
+++ b/services/web/public/stylesheets/style.less
@@ -1,86 +1,3 @@
// Core variables and mixins
@import "core/variables.less";
-@import url(https://netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css);
-
-@import "core/mixins.less";
-
-// Reset
-@import "core/normalize.less";
-@import "core/print.less";
-
-// Core CSS
-@import "core/scaffolding.less";
-@import "core/type.less";
-@import "core/grid.less";
-
-// Components
-@import "components/tables.less";
-@import "components/forms.less";
-@import "components/buttons.less";
-@import "components/card.less";
-//@import "components/code.less";
-@import "components/component-animations.less";
-@import "components/glyphicons.less";
-@import "components/dropdowns.less";
-@import "components/button-groups.less";
-@import "components/input-groups.less";
-@import "components/navs.less";
-@import "components/navbar.less";
-@import "components/footer.less";
-//@import "components/breadcrumbs.less";
-//@import "components/pagination.less";
-@import "components/pager.less";
-@import "components/labels.less";
-//@import "components/badges.less";
-//@import "components/jumbotron.less";
-@import "components/thumbnails.less";
-@import "components/alerts.less";
-@import "components/progress-bars.less";
-// @import "components/media.less";
-// @import "components/list-group.less";
-// @import "components/panels.less";
-// @import "components/wells.less";
-@import "components/close.less";
-@import "components/fineupload.less";
-@import "components/hover.less";
-
-// Components w/ JavaScript
-@import "components/modals.less";
-@import "components/tooltip.less";
-@import "components/popovers.less";
-@import "components/carousel.less";
-
-// ngTagsInput
-@import "components/tags-input.less";
-
-// Utility classes
-@import "core/utilities.less";
-@import "core/responsive-utilities.less";
-
-// ShareLaTeX app classes
-@import "app/base.less";
-@import "app/account-settings.less";
-@import "app/beta-program.less";
-@import "app/about-page.less";
-@import "app/project-list.less";
-@import "app/editor.less";
-@import "app/homepage.less";
-@import "app/plans.less";
-@import "app/recurly.less";
-@import "app/bonus.less";
-@import "app/register.less";
-@import "app/blog.less";
-@import "app/features.less";
-@import "app/templates.less";
-@import "app/wiki.less";
-@import "app/translations.less";
-@import "app/contact-us.less";
-@import "app/subscription.less";
-@import "app/sprites.less";
-@import "app/invite.less";
-@import "app/review-features-page.less";
-@import "app/error-pages.less";
-
-@import "../js/libs/pdfListView/TextLayer.css";
-@import "../js/libs/pdfListView/AnnotationsLayer.css";
-@import "../js/libs/pdfListView/HighlightsLayer.css";
+@import "_style_includes.less";
\ No newline at end of file
diff --git a/services/web/test/UnitTests/coffee/Authentication/AuthenticationManagerTests.coffee b/services/web/test/UnitTests/coffee/Authentication/AuthenticationManagerTests.coffee
index 0ebf9f8112..0a041a0865 100644
--- a/services/web/test/UnitTests/coffee/Authentication/AuthenticationManagerTests.coffee
+++ b/services/web/test/UnitTests/coffee/Authentication/AuthenticationManagerTests.coffee
@@ -116,6 +116,24 @@ describe "AuthenticationManager", ->
expect(err).to.exist
done()
+ it "should not start the bcrypt process", (done)->
+ @AuthenticationManager.setUserPassword @user_id, @password, (err)=>
+ @bcrypt.genSalt.called.should.equal false
+ @bcrypt.hash.called.should.equal false
+ done()
+
+ describe "too short", ->
+ beforeEach ->
+ @settings.passwordStrengthOptions =
+ length:
+ max:10
+ min:6
+ @password = "dsd"
+
+ it "should return and error", (done)->
+ @AuthenticationManager.setUserPassword @user_id, @password, (err)->
+ expect(err).to.exist
+ done()
it "should not start the bcrypt process", (done)->
@AuthenticationManager.setUserPassword @user_id, @password, (err)=>
diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionUpdaterTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionUpdaterTests.coffee
index ffef94157b..87ff474a0a 100644
--- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionUpdaterTests.coffee
+++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionUpdaterTests.coffee
@@ -37,6 +37,7 @@ describe "SubscriptionUpdater", ->
constructor: (opts)->
subscription.admin_id = opts.admin_id
return subscription
+ @remove: sinon.stub().yields()
@SubscriptionModel.update = @updateStub
@SubscriptionModel.findAndModify = @findAndModifyStub
@@ -230,3 +231,35 @@ describe "SubscriptionUpdater", ->
@ReferalAllocator.assignBonus.calledWith(@adminuser_id).should.equal true
done()
+ describe "deleteSubscription", ->
+ beforeEach (done) ->
+ @subscription_id = ObjectId().toString()
+ @subscription = {
+ "mock": "subscription",
+ admin_id: ObjectId(),
+ member_ids: [ ObjectId(), ObjectId(), ObjectId() ]
+ }
+ @SubscriptionLocator.getSubscription = sinon.stub().yields(null, @subscription)
+ @SubscriptionUpdater._setUsersMinimumFeatures = sinon.stub().yields()
+ @SubscriptionUpdater.deleteSubscription @subscription_id, done
+
+ it "should look up the subscription", ->
+ @SubscriptionLocator.getSubscription
+ .calledWith(@subscription_id)
+ .should.equal true
+
+ it "should remove the subscription", ->
+ @SubscriptionModel.remove
+ .calledWith({_id: ObjectId(@subscription_id)})
+ .should.equal true
+
+ it "should downgrade the admin_id", ->
+ @SubscriptionUpdater._setUsersMinimumFeatures
+ .calledWith(@subscription.admin_id)
+ .should.equal true
+
+ it "should downgrade all of the members", ->
+ for user_id in @subscription.member_ids
+ @SubscriptionUpdater._setUsersMinimumFeatures
+ .calledWith(user_id)
+ .should.equal true
diff --git a/services/web/test/smoke/coffee/SmokeTests.coffee b/services/web/test/smoke/coffee/SmokeTests.coffee
index cdb63b1a7b..0e6f7d6244 100644
--- a/services/web/test/smoke/coffee/SmokeTests.coffee
+++ b/services/web/test/smoke/coffee/SmokeTests.coffee
@@ -94,7 +94,7 @@ describe "Opening", ->
expect(error, "smoke test: error returned in getting project list").to.not.exist
expect(!!stderr.match("200 OK"), "smoke test: response code is not 200 getting project list").to.equal true
- expect(!!stdout.match("Your Projects - ShareLaTeX, Online LaTeX Editor"), "smoke test: body does not have correct title").to.equal true
+ expect(!!stdout.match("Your Projects - .*, Online LaTeX Editor"), "smoke test: body does not have correct title").to.equal true
expect(!!stdout.match("ProjectPageController"), "smoke test: body does not have correct angular controller").to.equal true
done()
| |