Merge branch 'master' into ns-autocomplete

This commit is contained in:
Nate Stemen 2017-08-18 09:14:06 +01:00
commit fb4182cbb1
54 changed files with 1942 additions and 5089 deletions

View file

@ -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/

View file

@ -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:

116
services/web/Jenkinsfile vendored Normal file
View file

@ -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')
}
}

View file

@ -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?

View file

@ -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

View file

@ -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)

View file

@ -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)->

View file

@ -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

View file

@ -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?

View file

@ -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)

View file

@ -7,36 +7,61 @@ script(type='text/ng-template', id='supportModalTemplate')
) &times;
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: "<a href='learn/kb' target='_blank'>__kb__</a>", 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: "<a href='learn/kb' target='_blank'>__kb__</a>", 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")}

View file

@ -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').

View file

@ -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>' -> '<\/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" : {

View file

@ -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()"

View file

@ -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")}
) #{translate("ok")}

View file

@ -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()"
) &times;
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 &nbsp;
| #{translate("turn_tc_on_individuals")}
li
i.fa.fa-check &nbsp;
| #{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(

View file

@ -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"

View file

@ -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",

View file

@ -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",

View file

@ -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

View file

@ -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 }

View file

@ -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 =

View file

@ -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)

View file

@ -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"

View file

@ -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

View file

@ -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()
$modalInstance.dismiss()

View file

@ -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

View file

@ -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"

View file

@ -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: """

View file

@ -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: "<div>browser: #{platform?.name} #{platform?.version}</div>
<div>os: #{platform?.os?.family} #{platform?.os?.version}</div>"
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) ->

View file

@ -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"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 136 157" style="enable-background:new 0 0 136 157;" xml:space="preserve">
<style type="text/css">
.st0{fill:#9B9B9B;}
</style>
<g id="Page-1">
<g id="overleaf">
<g id="Group">
<path id="Fill-1" class="st0" d="M37.2,39.7C14.8,54,0,77.3,0,102.3C0,132.5,24.5,157,54.7,157c30.2,0,54.7-24.5,54.7-54.7
c0-23.3-14.6-43.3-35.2-51.1c-4-1.5-12.6-4.2-19.4-3.6c-9.8,6.2-21.8,19-27.4,31.8c8.4-10.1,21.5-14.5,33.2-12.6
c17.1,2.8,30.2,17.6,30.2,35.6c0,19.9-16.1,36-36,36c-11,0-20.8-4.9-27.4-12.6C17.5,114.3,15,101.9,17,89.8
c6.9-42.4,57.2-66.5,94.6-75.8C99.4,20.5,77.4,31.1,62,42.6c44.9,17.3,52.2-20.5,73.2-37.5C114-3.1,37.3-6.1,37.2,39.7z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 947 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="136px" height="157px" viewBox="0 0 136 157" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="overleaf" fill="#4F9C45">
<g id="Group">
<path d="M37.205,39.652 C14.822,53.982 0,77.339 0,102.326 C0,132.522 24.48,157 54.681,157 C84.879,157 109.355,132.522 109.355,102.326 C109.355,78.986 94.729,59.05 74.151,51.215 C70.193,49.71 61.595,47.002 54.73,47.58 C44.924,53.814 32.979,66.624 27.319,79.389 C35.735,69.296 48.856,64.901 60.489,66.77 C77.615,69.547 90.697,84.408 90.697,102.321 C90.697,122.217 74.571,138.342 54.681,138.342 C43.719,138.342 33.896,133.446 27.293,125.723 C17.516,114.299 15,101.91 16.975,89.809 C23.902,47.434 74.208,23.279 111.611,14.01 C99.404,20.468 77.384,31.084 61.985,42.64 C106.909,59.981 114.169,22.123 135.202,5.181 C114.038,-3.07 37.33,-6.117 37.205,39.652 Z" id="Fill-1"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

View file

@ -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
};

View file

@ -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<len; i++) {
var hint = hints[i];
var suppressedChanges = 0;
var hintRange = new Range(hint.start_row, hint.start_col, hint.end_row, hint.end_col);
var cursorInRange = hintRange.insideEnd(cursor.row, cursor.column);
var cursorAtStart = hintRange.isStart(cursor.row, cursor.column - 1); // cursor after start not before
var cursorAtEnd = hintRange.isEnd(cursor.row, cursor.column);
if (hint.suppressIfEditing && (cursorAtStart || cursorAtEnd)) {
suppressions.push(hintRange);
if (!hint.suppressed) { suppressedChanges++; };
hint.suppressed = true;
continue;
}
var isCascadeError = false;
for (var j = 0, suplen = suppressions.length; j < suplen; j++) {
var badRange = suppressions[j];
if (badRange.intersects(hintRange)) {
isCascadeError = true;
break;
}
}
if(isCascadeError) {
if (!hint.suppressed) { suppressedChanges++; };
hint.suppressed = true;
continue;
};
if (hint.suppressed) { suppressedChanges++; };
hint.suppressed = false;
annotations.push(hint);
if (hint.type === "info") {
continue;
};
var key = hintRange.toString() + (cursorInRange ? "+cursor" : "");
newRange[key] = {hint: hint, cursorInRange: cursorInRange, range: hintRange};
}
for (key in newRange) {
if (!savedRange[key]) { // doesn't exist in already displayed errors
var new_range = newRange[key].range;
cursorInRange = newRange[key].cursorInRange;
hint = newRange[key].hint;
var errorAtStart = (hint.row === hint.start_row && hint.column === hint.start_col);
var movableStart = (cursorInRange && !errorAtStart) && !cursorAtEndOfDocument;
var movableEnd = (cursorInRange && errorAtStart) && !cursorAtEndOfDocument;
var a = movableStart ? cursorAnchor : doc.createAnchor(new_range.start);
var b = movableEnd ? cursorAnchor : doc.createAnchor(new_range.end);
var range = new Range();
range.start = a;
range.end = b;
var cssClass = "ace_error-marker";
if (hint.type === "warning") { cssClass = "ace_highlight-marker"; };
range.id = session.addMarker(range, cssClass, "text");
savedRange[key] = range;
}
}
for (key in savedRange) {
if (!newRange[key]) { // no longer present in list of errors to display
range = savedRange[key];
if (range.start !== cursorAnchor) { range.start.detach(); }
if (range.end !== cursorAnchor) { range.end.detach(); }
session.removeMarker(range.id);
delete savedRange[key];
}
}
if (!cursorMoveOnly || suppressedChanges) {
if (firstPass) {
if (annotations.length > 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;
});

View file

@ -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) {

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

View file

@ -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";

View file

@ -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;
}

View file

@ -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 {

View file

@ -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;

View file

@ -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;
}

View file

@ -0,0 +1,805 @@
//
// Variables
// --------------------------------------------------
//== Scaffolding
//
// ## Settings for some of the most global styles.
//** Background color for `<body>`.
@body-bg: #fff;
//** Global text color on `<body>`.
@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 `<code>`, `<kbd>`, and `<pre>`.
@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 `<body>`.
@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 `<th>`s and `<td>`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
//
//##
//** `<input>` background color
@input-bg: #fff;
//** `<input disabled>` background color
@input-bg-disabled: @gray-lighter;
//** Text color for `<input>`s
@input-color: @gray;
//** `<input>` border color
@input-border: #ccc;
//** `<input>` 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;

View file

@ -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";

View file

@ -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>`.
@body-bg: #fff;
//** Global text color on `<body>`.
@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 `<code>`, `<kbd>`, and `<pre>`.
@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 `<body>`.
@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 `<th>`s and `<td>`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
//
//##
//** `<input>` background color
@input-bg: #fff;
//** `<input disabled>` background color
@input-bg-disabled: @gray-lighter;
//** Text color for `<input>`s
@input-color: @gray;
//** `<input>` border color
@input-border: #ccc;
//** `<input>` 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";

View file

@ -0,0 +1,3 @@
// Core variables and mixins
@import "core/ol-variables.less";
@import "_style_includes.less";

View file

@ -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";

View file

@ -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)=>

View file

@ -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

View file

@ -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("<title>Your Projects - ShareLaTeX, Online LaTeX Editor</title>"), "smoke test: body does not have correct title").to.equal true
expect(!!stdout.match("<title>Your Projects - .*, Online LaTeX Editor</title>"), "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()