Merge branch 'master' into pr-fix-link-rewriting
This commit is contained in:
@@ -73,3 +73,4 @@ Gemfile.lock
|
||||
app/views/external
|
||||
|
||||
/modules/
|
||||
docker-shared.yml
|
||||
|
||||
@@ -196,6 +196,7 @@ module.exports = (grunt) ->
|
||||
"mathjax": "/js/libs/mathjax/MathJax.js?config=TeX-AMS_HTML"
|
||||
"pdfjs-dist/build/pdf": "libs/#{PackageVersions.lib('pdfjs')}/pdf"
|
||||
"ace": "#{PackageVersions.lib('ace')}"
|
||||
"fineuploader": "libs/#{PackageVersions.lib('fineuploader')}"
|
||||
shim:
|
||||
"pdfjs-dist/build/pdf":
|
||||
deps: ["libs/#{PackageVersions.lib('pdfjs')}/compatibility"]
|
||||
|
||||
Vendored
+14
-25
@@ -42,7 +42,7 @@ pipeline {
|
||||
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: '*/sk-unlisted-projects']], 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']]])
|
||||
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']]])
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/overleaf-integration'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/overleaf-integration-web-module.git']]])
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/overleaf-account-merge'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/overleaf-account-merge.git']]])
|
||||
}
|
||||
@@ -70,6 +70,19 @@ pipeline {
|
||||
sh 'ls -l node_modules/.bin'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Unit Tests') {
|
||||
steps {
|
||||
sh 'make clean install' // Removes js files, so do before compile
|
||||
sh 'make test_unit MOCHA_ARGS="--reporter=tap"'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Acceptance Tests') {
|
||||
steps {
|
||||
sh 'make test_acceptance MOCHA_ARGS="--reporter=tap"'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Compile') {
|
||||
agent {
|
||||
@@ -109,30 +122,6 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
stage('Unit Test') {
|
||||
agent {
|
||||
docker {
|
||||
image 'node:6.9.5'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
sh 'env NODE_ENV=development ./node_modules/.bin/grunt mochaTest:unit --reporter=tap'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Acceptance Tests') {
|
||||
steps {
|
||||
// This tagged relase of the acceptance test runner is a temporary fix
|
||||
// to get the acceptance tests working before we move to a
|
||||
// docker-compose workflow. See:
|
||||
// https://github.com/sharelatex/web-sharelatex-internal/pull/148
|
||||
|
||||
sh 'docker pull sharelatex/sl-acceptance-test-runner:node-6.9-mongo-3.4'
|
||||
sh 'docker run --rm -v $(pwd):/app --env SHARELATEX_ALLOW_PUBLIC_ACCESS=true sharelatex/sl-acceptance-test-runner:node-6.9-mongo-3.4 || (cat forever/app.log && false)'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Package') {
|
||||
steps {
|
||||
sh 'rm -rf ./node_modules/grunt*'
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
|
||||
NPM := docker-compose ${DOCKER_COMPOSE_FLAGS} run --rm npm npm -q
|
||||
BUILD_NUMBER ?= local
|
||||
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||
PROJECT_NAME = web
|
||||
|
||||
all: install test
|
||||
@echo "Run:"
|
||||
@echo " make install to set up the project dependencies (in docker)"
|
||||
@echo " make test to run all the tests for the project (in docker)"
|
||||
|
||||
add: docker-shared.yml
|
||||
$(NPM) install --save ${P}
|
||||
|
||||
add_dev: docker-shared.yml
|
||||
$(NPM) install --save-dev ${P}
|
||||
|
||||
install: docker-shared.yml
|
||||
$(NPM) install
|
||||
|
||||
clean:
|
||||
rm -f app.js
|
||||
rm -rf app/js
|
||||
rm -rf test/unit/js
|
||||
rm -rf test/acceptance/js
|
||||
for dir in modules/*; \
|
||||
do \
|
||||
rm -f $$dir/index.js; \
|
||||
rm -rf $$dir/app/js; \
|
||||
rm -rf $$dir/test/unit/js; \
|
||||
rm -rf $$dir/test/acceptance/js; \
|
||||
done
|
||||
# Regenerate docker-shared.yml - not stictly a 'clean',
|
||||
# but lets `make clean install` work nicely
|
||||
bin/generate_volumes_file
|
||||
# Deletes node_modules volume
|
||||
docker-compose down --volumes
|
||||
|
||||
# Need regenerating if you change the web modules you have installed
|
||||
docker-shared.yml:
|
||||
bin/generate_volumes_file
|
||||
|
||||
test: test_unit test_acceptance
|
||||
|
||||
test_unit: docker-shared.yml
|
||||
docker-compose ${DOCKER_COMPOSE_FLAGS} run --rm test_unit npm -q run test:unit -- ${MOCHA_ARGS}
|
||||
|
||||
test_acceptance: test_acceptance_app test_acceptance_modules
|
||||
|
||||
test_acceptance_app: test_acceptance_app_start_service test_acceptance_app_run test_acceptance_app_stop_service
|
||||
|
||||
test_acceptance_app_start_service: test_acceptance_app_stop_service docker-shared.yml
|
||||
docker-compose ${DOCKER_COMPOSE_FLAGS} up -d test_acceptance
|
||||
|
||||
test_acceptance_app_stop_service: docker-shared.yml
|
||||
docker-compose ${DOCKER_COMPOSE_FLAGS} stop test_acceptance redis mongo
|
||||
|
||||
test_acceptance_app_run: docker-shared.yml
|
||||
docker-compose ${DOCKER_COMPOSE_FLAGS} exec -T test_acceptance npm -q run test:acceptance -- ${MOCHA_ARGS}
|
||||
|
||||
test_acceptance_modules: docker-shared.yml
|
||||
# Break and error on any module failure
|
||||
set -e; \
|
||||
for dir in modules/*; \
|
||||
do \
|
||||
if [ -e $$dir/Makefile ]; then \
|
||||
(make test_acceptance_module MODULE=$$dir) \
|
||||
fi \
|
||||
done
|
||||
|
||||
test_acceptance_module: docker-shared.yml
|
||||
cd $(MODULE) && make test_acceptance
|
||||
|
||||
.PHONY:
|
||||
all add install update test test_unit test_acceptance \
|
||||
test_acceptance_start_service test_acceptance_stop_service \
|
||||
test_acceptance_run
|
||||
@@ -17,6 +17,69 @@ web-sharelatex uses [Grunt](http://gruntjs.com/) to build its front-end related
|
||||
|
||||
Image processing tasks are commented out in the gruntfile and the needed packages aren't presently in the project's `package.json`. If the images need to be processed again (minified and sprited), start by fetching the packages (`npm install grunt-contrib-imagemin grunt-sprity`), then *decomment* the tasks in `Gruntfile.coffee`. After this, the tasks can be called (explicitly, via `grunt imagemin` and `grunt sprity`).
|
||||
|
||||
New Docker-based build process
|
||||
------------------------------
|
||||
|
||||
Note that the Grunt workflow from above should still work, but we are transitioning to a
|
||||
Docker based testing workflow, which is documented below:
|
||||
|
||||
### Running the app
|
||||
|
||||
The app runs natively using npm and Node on the local system:
|
||||
|
||||
```
|
||||
$ npm install
|
||||
$ npm run start
|
||||
```
|
||||
|
||||
*Ideally the app would run in Docker like the tests below, but with host networking not supported in OS X, we need to run it natively until all services are Dockerised.*
|
||||
|
||||
### Unit Tests
|
||||
|
||||
The test suites run in Docker.
|
||||
|
||||
Unit tests can be run in the `test_unit` container defined in `docker-compose.tests.yml`.
|
||||
|
||||
The makefile contains a short cut to run these:
|
||||
|
||||
```
|
||||
make install # Only needs running once, or when npm packages are updated
|
||||
make unit_test
|
||||
```
|
||||
|
||||
During development it is often useful to only run a subset of tests, which can be configured with arguments to the mocha CLI:
|
||||
|
||||
```
|
||||
make unit_test MOCHA_ARGS='--grep=AuthorizationManager'
|
||||
```
|
||||
|
||||
### Acceptance Tests
|
||||
|
||||
Acceptance tests are run against a live service, which runs in the `acceptance_test` container defined in `docker-compose.tests.yml`.
|
||||
|
||||
To run the tests out-of-the-box, the makefile defines:
|
||||
|
||||
```
|
||||
make install # Only needs running once, or when npm packages are updated
|
||||
make acceptance_test
|
||||
```
|
||||
|
||||
However, during development it is often useful to leave the service running for rapid iteration on the acceptance tests. This can be done with:
|
||||
|
||||
```
|
||||
make acceptance_test_start_service
|
||||
make acceptance_test_run # Run as many times as needed during development
|
||||
make acceptance_test_stop_service
|
||||
```
|
||||
|
||||
`make acceptance_test` just runs these three commands in sequence.
|
||||
|
||||
During development it is often useful to only run a subset of tests, which can be configured with arguments to the mocha CLI:
|
||||
|
||||
```
|
||||
make acceptance_test_run MOCHA_ARGS='--grep=AuthorizationManager'
|
||||
```
|
||||
|
||||
Unit test status
|
||||
----------------
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
EditorRealTimeController = require "../Editor/EditorRealTimeController"
|
||||
MetaHandler = require './MetaHandler'
|
||||
logger = require 'logger-sharelatex'
|
||||
|
||||
|
||||
module.exports = MetaController =
|
||||
|
||||
getMetadata: (req, res, next) ->
|
||||
project_id = req.params.project_id
|
||||
logger.log {project_id}, "getting all labels for project"
|
||||
MetaHandler.getAllMetaForProject project_id, (err, projectMeta) ->
|
||||
if err?
|
||||
logger.err {project_id, err}, "[MetaController] error getting all labels from project"
|
||||
return next err
|
||||
res.json {projectId: project_id, projectMeta: projectMeta}
|
||||
|
||||
broadcastMetadataForDoc: (req, res, next) ->
|
||||
project_id = req.params.project_id
|
||||
doc_id = req.params.doc_id
|
||||
logger.log {project_id, doc_id}, "getting labels for doc"
|
||||
MetaHandler.getMetaForDoc project_id, doc_id, (err, docMeta) ->
|
||||
if err?
|
||||
logger.err {project_id, doc_id, err}, "[MetaController] error getting labels from doc"
|
||||
return next err
|
||||
EditorRealTimeController.emitToRoom project_id, 'broadcastDocMeta', {
|
||||
docId: doc_id, meta: docMeta
|
||||
}
|
||||
res.sendStatus 200
|
||||
@@ -0,0 +1,66 @@
|
||||
ProjectEntityHandler = require "../Project/ProjectEntityHandler"
|
||||
DocumentUpdaterHandler = require '../DocumentUpdater/DocumentUpdaterHandler'
|
||||
packageMapping = require "./packageMapping"
|
||||
|
||||
|
||||
module.exports = MetaHandler =
|
||||
|
||||
labelRegex: () ->
|
||||
/\\label{(.{0,80}?)}/g
|
||||
|
||||
usepackageRegex: () ->
|
||||
/^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
|
||||
|
||||
ReqPackageRegex: () ->
|
||||
/^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
|
||||
|
||||
getAllMetaForProject: (projectId, callback=(err, projectMeta)->) ->
|
||||
DocumentUpdaterHandler.flushProjectToMongo projectId, (err) ->
|
||||
if err?
|
||||
return callback err
|
||||
ProjectEntityHandler.getAllDocs projectId, (err, docs) ->
|
||||
if err?
|
||||
return callback err
|
||||
projectMeta = MetaHandler.extractMetaFromProjectDocs docs
|
||||
callback null, projectMeta
|
||||
|
||||
getMetaForDoc: (projectId, docId, callback=(err, docMeta)->) ->
|
||||
DocumentUpdaterHandler.flushDocToMongo projectId, docId, (err) ->
|
||||
if err?
|
||||
return callback err
|
||||
ProjectEntityHandler.getDoc projectId, docId, (err, lines) ->
|
||||
if err?
|
||||
return callback err
|
||||
docMeta = MetaHandler.extractMetaFromDoc lines
|
||||
callback null, docMeta
|
||||
|
||||
extractMetaFromDoc: (lines) ->
|
||||
docMeta = {labels: [], packages: {}}
|
||||
packages = []
|
||||
label_re = MetaHandler.labelRegex()
|
||||
package_re = MetaHandler.usepackageRegex()
|
||||
req_package_re = MetaHandler.ReqPackageRegex()
|
||||
for line in lines
|
||||
while labelMatch = label_re.exec line
|
||||
if label = labelMatch[1]
|
||||
docMeta.labels.push label
|
||||
while packageMatch = package_re.exec line
|
||||
if messy = packageMatch[1]
|
||||
for pkg in messy.split ','
|
||||
if clean = pkg.trim()
|
||||
packages.push clean
|
||||
while packageMatch = req_package_re.exec line
|
||||
if messy = packageMatch[1]
|
||||
for pkg in messy.split ','
|
||||
if clean = pkg.trim()
|
||||
packages.push clean
|
||||
for pkg in packages
|
||||
if packageMapping[pkg]?
|
||||
docMeta.packages[pkg] = packageMapping[pkg]
|
||||
return docMeta
|
||||
|
||||
extractMetaFromProjectDocs: (projectDocs) ->
|
||||
projectMeta = {}
|
||||
for _path, doc of projectDocs
|
||||
projectMeta[doc._id] = MetaHandler.extractMetaFromDoc doc.lines
|
||||
return projectMeta
|
||||
File diff suppressed because one or more lines are too long
@@ -251,7 +251,7 @@ module.exports = ProjectController =
|
||||
# Extract data from user's ObjectId
|
||||
timestamp = parseInt(user_id.toString().substring(0, 8), 16)
|
||||
|
||||
rolloutPercentage = 10 # Percentage of users to roll out to
|
||||
rolloutPercentage = 20 # Percentage of users to roll out to
|
||||
if !ProjectController._isInPercentageRollout('autocompile', user_id, rolloutPercentage)
|
||||
# Don't show if user is not part of roll out
|
||||
return cb(null, { enabled: false, showOnboarding: false })
|
||||
|
||||
@@ -97,20 +97,39 @@ module.exports = SubscriptionController =
|
||||
logger.log user: user, "redirecting to plans"
|
||||
res.redirect "/user/subscription/plans"
|
||||
else
|
||||
SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel user, (error, subscription, groupSubscriptions) ->
|
||||
return next(error) if error?
|
||||
logger.log user: user, subscription:subscription, hasSubOrIsGroupMember:hasSubOrIsGroupMember, groupSubscriptions:groupSubscriptions, "showing subscription dashboard"
|
||||
plans = SubscriptionViewModelBuilder.buildViewModel()
|
||||
res.render "subscriptions/dashboard",
|
||||
title: "your_subscription"
|
||||
recomendedCurrency: subscription?.currency
|
||||
taxRate:subscription?.taxRate
|
||||
plans: plans
|
||||
subscription: subscription || {}
|
||||
groupSubscriptions: groupSubscriptions
|
||||
subscriptionTabActive: true
|
||||
user:user
|
||||
saved_billing_details: req.query.saved_billing_details?
|
||||
RecurlyWrapper.getSubscription subscription.recurlySubscription_id,
|
||||
includeAccount: true,
|
||||
(err, usersSubscription)->
|
||||
# always render the page, but skip the recurly link if
|
||||
# we can't get it for some reason
|
||||
if err?
|
||||
logger.err {err, userId: user._id}, "error getting billing details link from recurly, proceeding"
|
||||
hostedLoginToken = usersSubscription?.account?.hosted_login_token
|
||||
recurlySubdomain = Settings?.apis?.recurly?.subdomain
|
||||
if err? || !hostedLoginToken || !recurlySubdomain
|
||||
billingDetailsLink = null
|
||||
else
|
||||
billingDetailsLink = [
|
||||
"https://",
|
||||
recurlySubdomain,
|
||||
".recurly.com/account/billing_info/edit?ht=",
|
||||
hostedLoginToken
|
||||
].join("")
|
||||
SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel user, (error, subscription, groupSubscriptions) ->
|
||||
return next(error) if error?
|
||||
logger.log {user, subscription, hasSubOrIsGroupMember, groupSubscriptions, billingDetailsLink}, "showing subscription dashboard"
|
||||
plans = SubscriptionViewModelBuilder.buildViewModel()
|
||||
res.render "subscriptions/dashboard",
|
||||
title: "your_subscription"
|
||||
recomendedCurrency: subscription?.currency
|
||||
taxRate:subscription?.taxRate
|
||||
plans: plans
|
||||
subscription: subscription || {}
|
||||
groupSubscriptions: groupSubscriptions
|
||||
subscriptionTabActive: true
|
||||
user:user
|
||||
saved_billing_details: req.query.saved_billing_details?
|
||||
billingDetailsLink: billingDetailsLink
|
||||
|
||||
userCustomSubscriptionPage: (req, res, next)->
|
||||
user = AuthenticationController.getSessionUser(req)
|
||||
@@ -124,31 +143,6 @@ module.exports = SubscriptionController =
|
||||
title: "your_subscription"
|
||||
subscription: subscription
|
||||
|
||||
|
||||
editBillingDetailsPage: (req, res, next) ->
|
||||
user = AuthenticationController.getSessionUser(req)
|
||||
LimitationsManager.userHasSubscription user, (err, hasSubscription)->
|
||||
return next(err) if err?
|
||||
if !hasSubscription
|
||||
res.redirect "/user/subscription"
|
||||
else
|
||||
RecurlyWrapper.sign {
|
||||
account_code: user._id
|
||||
}, (error, signature) ->
|
||||
return next(error) if error?
|
||||
res.render "subscriptions/edit-billing-details",
|
||||
title : "update_billing_details"
|
||||
recurlyConfig: JSON.stringify
|
||||
currency: "USD"
|
||||
subdomain: Settings.apis.recurly.subdomain
|
||||
signature : signature
|
||||
successURL : "#{Settings.siteUrl}/user/subscription/billing-details/update"
|
||||
user :
|
||||
id : user._id
|
||||
|
||||
updateBillingDetails: (req, res, next) ->
|
||||
res.redirect "/user/subscription?saved_billing_details=true"
|
||||
|
||||
createSubscription: (req, res, next)->
|
||||
user = AuthenticationController.getSessionUser(req)
|
||||
recurly_token_id = req.body.recurly_token_id
|
||||
|
||||
@@ -15,8 +15,6 @@ module.exports =
|
||||
|
||||
|
||||
webRouter.get '/user/subscription/new', AuthenticationController.requireLogin(), SubscriptionController.paymentPage
|
||||
webRouter.get '/user/subscription/billing-details/edit', AuthenticationController.requireLogin(), SubscriptionController.editBillingDetailsPage
|
||||
webRouter.post '/user/subscription/billing-details/update', AuthenticationController.requireLogin(), SubscriptionController.updateBillingDetails
|
||||
|
||||
webRouter.get '/user/subscription/thank-you', AuthenticationController.requireLogin(), SubscriptionController.successful_subscription
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ jsPath =
|
||||
|
||||
ace = PackageVersions.lib('ace')
|
||||
pdfjs = PackageVersions.lib('pdfjs')
|
||||
fineuploader = PackageVersions.lib('fineuploader')
|
||||
|
||||
getFileContent = (filePath)->
|
||||
filePath = Path.join __dirname, "../../../", "public#{filePath}"
|
||||
@@ -37,6 +38,7 @@ getFileContent = (filePath)->
|
||||
|
||||
logger.log "Generating file fingerprints..."
|
||||
pathList = [
|
||||
["#{jsPath}libs/#{fineuploader}.js"]
|
||||
["#{jsPath}libs/require.js"]
|
||||
["#{jsPath}ide.js"]
|
||||
["#{jsPath}main.js"]
|
||||
|
||||
@@ -2,6 +2,7 @@ version = {
|
||||
"pdfjs": "1.7.225"
|
||||
"moment": "2.9.0"
|
||||
"ace": "1.2.5"
|
||||
"fineuploader": "5.15.4"
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -43,6 +43,7 @@ SudoModeController = require('./Features/SudoMode/SudoModeController')
|
||||
SudoModeMiddlewear = require('./Features/SudoMode/SudoModeMiddlewear')
|
||||
AnalyticsRouter = require('./Features/Analytics/AnalyticsRouter')
|
||||
AnnouncementsController = require("./Features/Announcements/AnnouncementsController")
|
||||
MetaController = require('./Features/Metadata/MetaController')
|
||||
LabelsController = require('./Features/Labels/LabelsController')
|
||||
TokenAccessController = require('./Features/TokenAccess/TokenAccessController')
|
||||
Features = require('./infrastructure/Features')
|
||||
@@ -202,6 +203,9 @@ module.exports = class Router
|
||||
webRouter.get '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject
|
||||
webRouter.get '/project/download/zip', AuthorizationMiddlewear.ensureUserCanReadMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
|
||||
|
||||
webRouter.get '/project/:project_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetaController.getMetadata
|
||||
webRouter.post '/project/:project_id/doc/:doc_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetaController.broadcastMetadataForDoc
|
||||
|
||||
webRouter.get '/project/:project_id/labels', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), LabelsController.getAllLabels
|
||||
webRouter.post '/project/:project_id/doc/:doc_id/labels', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), LabelsController.broadcastLabelsForDoc
|
||||
|
||||
|
||||
@@ -132,7 +132,8 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||
// minimal requirejs configuration (can be extended/overridden)
|
||||
window.requirejs = {
|
||||
"paths" : {
|
||||
"moment": "libs/#{lib('moment')}"
|
||||
"moment": "libs/#{lib('moment')}",
|
||||
"fineuploader": "libs/#{lib('fineuploader')}"
|
||||
},
|
||||
"urlArgs": "fingerprint=#{fingerprint(jsPath + 'main.js')}-#{fingerprint(jsPath + 'libs.js')}",
|
||||
"config":{
|
||||
|
||||
@@ -130,7 +130,8 @@ block requirejs
|
||||
"moment": "libs/#{lib('moment')}",
|
||||
"pdfjs-dist/build/pdf": "libs/#{lib('pdfjs')}/pdf",
|
||||
"pdfjs-dist/build/pdf.worker": "#{pdfWorkerPath}",
|
||||
"ace": "#{lib('ace')}"
|
||||
"ace": "#{lib('ace')}",
|
||||
"fineuploader": "libs/#{lib('fineuploader')}"
|
||||
},
|
||||
"urlArgs" : "fingerprint=#{fingerprint(jsPath + 'ide.js')}-#{fingerprint(jsPath + 'libs.js')}",
|
||||
"waitSeconds": 0,
|
||||
@@ -164,5 +165,3 @@ block requirejs
|
||||
data-ace-base=buildJsPath(lib('ace'), {fingerprint:false}),
|
||||
src=buildJsPath('libs/require.js')
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -333,6 +333,7 @@ script(type='text/ng-template', id='newDocModalTemplate')
|
||||
span(ng-hide="state.inflight") #{translate("create")}
|
||||
span(ng-show="state.inflight") #{translate("creating")}...
|
||||
|
||||
|
||||
script(type='text/ng-template', id='newFolderModalTemplate')
|
||||
.modal-header
|
||||
h3 #{translate("new_folder")}
|
||||
@@ -363,6 +364,34 @@ script(type='text/ng-template', id='newFolderModalTemplate')
|
||||
span(ng-hide="state.inflight") #{translate("create")}
|
||||
span(ng-show="state.inflight") #{translate("creating")}...
|
||||
|
||||
|
||||
script(type="text/template", id="qq-file-uploader-template")
|
||||
div.qq-uploader-selector
|
||||
div(qq-hide-dropzone="").qq-upload-drop-area-selector.qq-upload-drop-area
|
||||
span.qq-upload-drop-area-text-selector #{translate('drop_files_here_to_upload')}
|
||||
div.qq-upload-button-selector.btn.btn-primary.btn-lg
|
||||
div #{translate('upload')}
|
||||
span.or.btn-lg #{translate('or')}
|
||||
span.drag-here.btn-lg #{translate('drag_here')}
|
||||
span.qq-drop-processing-selector
|
||||
span #{translate('processing')}
|
||||
span.qq-drop-processing-spinner-selector
|
||||
ul.qq-upload-list-selector
|
||||
li
|
||||
div.qq-progress-bar-container-selector
|
||||
div(
|
||||
role="progressbar"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
class="qq-progress-bar-selector qq-progress-bar"
|
||||
)
|
||||
span.qq-upload-file-selector.qq-upload-file
|
||||
span.qq-upload-size-selector.qq-upload-size
|
||||
a(type="button").qq-btn.qq-upload-cancel-selector.qq-upload-cancel #{translate('cancel')}
|
||||
button(type="button").qq-btn.qq-upload-retry-selector.qq-upload-retry #{translate('retry')}
|
||||
span(role="status").qq-upload-status-text-selector.qq-upload-status-text
|
||||
|
||||
script(type="text/ng-template", id="uploadFileModalTemplate")
|
||||
.modal-header
|
||||
h3 #{translate("upload_files")}
|
||||
@@ -384,11 +413,7 @@ script(type="text/ng-template", id="uploadFileModalTemplate")
|
||||
.modal-body(
|
||||
fine-upload
|
||||
endpoint="/project/{{ project_id }}/upload"
|
||||
waiting-for-response-text="{{inserting_files}}"
|
||||
failed-upload-text="{{upload_failed_sorry}}"
|
||||
upload-button-text="{{select_files}}"
|
||||
drag-area-text="{{drag_files}}"
|
||||
hint-text="{{hint_press_and_hold_control_key}}"
|
||||
template-id="qq-file-uploader-template"
|
||||
multiple="true"
|
||||
auto-upload="false"
|
||||
on-complete-callback="onComplete"
|
||||
@@ -400,10 +425,10 @@ script(type="text/ng-template", id="uploadFileModalTemplate")
|
||||
control="control"
|
||||
params="{'folder_id': parent_folder_id}"
|
||||
)
|
||||
span #{translate("upload_files")}
|
||||
.modal-footer
|
||||
button.btn.btn-default(ng-click="cancel()") #{translate("cancel")}
|
||||
|
||||
|
||||
script(type='text/ng-template', id='deleteEntityModalTemplate')
|
||||
.modal-header
|
||||
h3 #{translate("delete")} {{ entity.name }}
|
||||
|
||||
@@ -6,6 +6,7 @@ block content
|
||||
script#data(type="application/json").
|
||||
!{JSON.stringify({ projects: projects, tags: tags, notifications: notifications }).replace(/\//g, '\\/')}
|
||||
|
||||
|
||||
script(type="text/javascript").
|
||||
window.data = JSON.parse($("#data").text());
|
||||
window.algolia = {
|
||||
|
||||
@@ -213,6 +213,33 @@ script(type='text/ng-template', id='deleteProjectsModalTemplate')
|
||||
ng-click="delete()"
|
||||
) #{translate("confirm")}
|
||||
|
||||
script(type="text/template", id="qq-project-uploader-template")
|
||||
div.qq-uploader-selector
|
||||
div(qq-hide-dropzone="").qq-upload-drop-area-selector.qq-upload-drop-area
|
||||
span.qq-upload-drop-area-text-selector #{translate('drop_files_here_to_upload')}
|
||||
div.qq-upload-button-selector.btn.btn-primary.btn-lg
|
||||
div #{translate('select_a_zip_file')}
|
||||
span.or.btn-lg #{translate('or')}
|
||||
span.drag-here.btn-lg #{translate('drag_a_zip_file')}
|
||||
span.qq-drop-processing-selector
|
||||
span #{translate('creating_project')}
|
||||
span.qq-drop-processing-spinner-selector
|
||||
ul.qq-upload-list-selector
|
||||
li
|
||||
div.qq-progress-bar-container-selector
|
||||
div(
|
||||
role="progressbar"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
class="qq-progress-bar-selector qq-progress-bar"
|
||||
)
|
||||
span.qq-upload-file-selector.qq-upload-file
|
||||
span.qq-upload-size-selector.qq-upload-size
|
||||
a(type="button").qq-btn.qq-upload-cancel-selector.qq-upload-cancel #{translate('cancel')}
|
||||
button(type="button").qq-btn.qq-upload-retry-selector.qq-upload-retry #{translate('retry')}
|
||||
span(role="status").qq-upload-status-text-selector.qq-upload-status-text
|
||||
|
||||
script(type="text/ng-template", id="uploadProjectModalTemplate")
|
||||
.modal-header
|
||||
button.close(
|
||||
@@ -224,15 +251,11 @@ script(type="text/ng-template", id="uploadProjectModalTemplate")
|
||||
.modal-body(
|
||||
fine-upload
|
||||
endpoint="/project/new/upload"
|
||||
waiting-for-response-text="Creating project..."
|
||||
failed-upload-text="Upload failed. Is it a valid zip file?"
|
||||
upload-button-text="Select a .zip file"
|
||||
drag-area-text="drag .zip file"
|
||||
template-id="qq-project-uploader-template"
|
||||
multiple="false"
|
||||
allowed-extensions="['zip']"
|
||||
on-complete-callback="onComplete"
|
||||
)
|
||||
span #{translate("upload_a_zipped_project")}
|
||||
.modal-footer
|
||||
button.btn.btn-default(ng-click="cancel()") #{translate("cancel")}
|
||||
|
||||
|
||||
@@ -61,7 +61,10 @@ block content
|
||||
p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"<strong>" + subscription.price + "</strong>", collectionDate:"<strong>" + subscription.nextPaymentDueAt + "</strong>"})}
|
||||
p.pull-right
|
||||
p
|
||||
a(href="/user/subscription/billing-details/edit").btn.btn-info #{translate("update_your_billing_details")}
|
||||
if billingDetailsLink
|
||||
a(href=billingDetailsLink, target="_blank").btn.btn-info #{translate("update_your_billing_details")}
|
||||
else
|
||||
a(href=billingDetailsLink, disabled).btn.btn-info.btn-disabled #{translate("update_your_billing_details")}
|
||||
|
|
||||
a(href, ng-click="switchToCancelationView()").btn.btn-primary !{translate("cancel_your_subscription")}
|
||||
when "canceled"
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
extends ../layout
|
||||
|
||||
block scripts
|
||||
script(src=buildJsPath('libs/recurly.min.js', {fingerprint:false}))
|
||||
|
||||
block content
|
||||
.content.content-alt
|
||||
.container
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
.card
|
||||
.page-header
|
||||
h1.text-centered #{translate("update_your_billing_details")}
|
||||
#billingDetailsForm #{translate("loading_billing_form")}...
|
||||
|
||||
|
||||
script(type="text/javascript").
|
||||
Recurly.config(!{recurlyConfig})
|
||||
Recurly.buildBillingInfoUpdateForm({
|
||||
target : "#billingDetailsForm",
|
||||
successURL : "#{successURL}?_csrf=#{csrfToken}&origin=editBillingDetails",
|
||||
signature : "!{signature}",
|
||||
accountCode : "#{user.id}"
|
||||
});
|
||||
|
||||
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -e;
|
||||
MOCHA="node_modules/.bin/mocha --recursive --reporter spec --timeout 15000"
|
||||
$MOCHA "$@"
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
set -e;
|
||||
|
||||
COFFEE=node_modules/.bin/coffee
|
||||
|
||||
echo Compiling test/acceptance/coffee;
|
||||
$COFFEE -o test/acceptance/js -c test/acceptance/coffee;
|
||||
|
||||
for dir in modules/*;
|
||||
do
|
||||
|
||||
if [ -d $dir/test/acceptance ]; then
|
||||
echo Compiling $dir/test/acceptance/coffee;
|
||||
$COFFEE -o $dir/test/acceptance/js -c $dir/test/acceptance/coffee;
|
||||
fi
|
||||
done
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
set -e;
|
||||
|
||||
COFFEE=node_modules/.bin/coffee
|
||||
|
||||
echo Compiling app.coffee;
|
||||
$COFFEE -c app.coffee;
|
||||
|
||||
echo Compiling app/coffee;
|
||||
$COFFEE -o app/js -c app/coffee;
|
||||
|
||||
for dir in modules/*;
|
||||
do
|
||||
if [ -d $dir/app/coffee ]; then
|
||||
echo Compiling $dir/app/coffee;
|
||||
$COFFEE -o $dir/app/js -c $dir/app/coffee;
|
||||
fi
|
||||
|
||||
if [ -e $dir/index.coffee ]; then
|
||||
echo Compiling $dir/index.coffee;
|
||||
$COFFEE -c $dir/index.coffee;
|
||||
fi
|
||||
done
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
set -e;
|
||||
|
||||
COFFEE=node_modules/.bin/coffee
|
||||
|
||||
echo Compiling test/unit/coffee;
|
||||
$COFFEE -o test/unit/js -c test/unit/coffee;
|
||||
|
||||
for dir in modules/*;
|
||||
do
|
||||
if [ -d $dir/test/unit ]; then
|
||||
echo Compiling $dir/test/unit/coffee;
|
||||
$COFFEE -o $dir/test/unit/js -c $dir/test/unit/coffee;
|
||||
fi
|
||||
done
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python2
|
||||
|
||||
from os import listdir
|
||||
from os.path import isfile, isdir, join
|
||||
|
||||
volumes = []
|
||||
|
||||
for module in listdir("modules/"):
|
||||
if module[0] != '.':
|
||||
if isfile(join("modules", module, 'index.coffee')):
|
||||
volumes.append(join("modules", module, 'index.coffee'))
|
||||
for directory in ['app/coffee', 'app/views', 'public/coffee', 'test/unit/coffee', 'test/acceptance/coffee', 'test/acceptance/config', 'test/acceptance/files']:
|
||||
if isdir(join("modules", module, directory)):
|
||||
volumes.append(join("modules", module, directory))
|
||||
|
||||
volumes_string = map(lambda vol: "- ./" + vol + ":/app/" + vol + ":ro", volumes)
|
||||
volumes_string = "\n ".join(volumes_string)
|
||||
|
||||
with open("docker-shared.template.yml", "r") as f:
|
||||
docker_shared_file = f.read()
|
||||
|
||||
docker_shared_file = docker_shared_file.replace("MODULE_VOLUMES", volumes_string)
|
||||
|
||||
with open("docker-shared.yml", "w") as f:
|
||||
f.write(docker_shared_file)
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -e;
|
||||
|
||||
MOCHA="node_modules/.bin/mocha --recursive --reporter spec"
|
||||
|
||||
$MOCHA "$@" test/unit/js
|
||||
|
||||
for dir in modules/*;
|
||||
do
|
||||
if [ -d $dir/test/unit/js ]; then
|
||||
$MOCHA "$@" $dir/test/unit/js
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -35,12 +35,12 @@ module.exports = settings =
|
||||
# Databases
|
||||
# ---------
|
||||
mongo:
|
||||
url : 'mongodb://127.0.0.1/sharelatex'
|
||||
url : process.env['MONGO_URL'] || "mongodb://127.0.0.1/sharelatex"
|
||||
|
||||
redis:
|
||||
web:
|
||||
host: "localhost"
|
||||
port: "6379"
|
||||
host: process.env['REDIS_HOST'] || "localhost"
|
||||
port: process.env['REDIS_PORT'] || "6379"
|
||||
password: ""
|
||||
|
||||
# websessions:
|
||||
@@ -74,8 +74,8 @@ module.exports = settings =
|
||||
# ]
|
||||
|
||||
api:
|
||||
host: "localhost"
|
||||
port: "6379"
|
||||
host: process.env['REDIS_HOST'] || "localhost"
|
||||
port: process.env['REDIS_PORT'] || "6379"
|
||||
password: ""
|
||||
|
||||
# Service locations
|
||||
@@ -87,6 +87,7 @@ module.exports = settings =
|
||||
internal:
|
||||
web:
|
||||
port: webPort = 3000
|
||||
host: process.env['LISTEN_ADDRESS'] or 'localhost'
|
||||
documentupdater:
|
||||
port: docUpdaterPort = 3003
|
||||
|
||||
@@ -99,7 +100,7 @@ module.exports = settings =
|
||||
user: httpAuthUser
|
||||
pass: httpAuthPass
|
||||
documentupdater:
|
||||
url : "http://localhost:#{docUpdaterPort}"
|
||||
url : "http://#{process.env['DOCUPDATER_HOST'] or 'localhost'}:#{docUpdaterPort}"
|
||||
thirdPartyDataStore:
|
||||
url : "http://localhost:3002"
|
||||
emptyProjectFlushDelayMiliseconds: 5 * seconds
|
||||
@@ -113,7 +114,7 @@ module.exports = settings =
|
||||
enabled: false
|
||||
url : "http://localhost:3054"
|
||||
docstore:
|
||||
url : "http://localhost:3016"
|
||||
url : "http://#{process.env['DOCSTORE_HOST'] or 'localhost'}:3016"
|
||||
pubUrl: "http://localhost:3016"
|
||||
chat:
|
||||
url: "http://localhost:3010"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
version: "2"
|
||||
|
||||
volumes:
|
||||
node_modules:
|
||||
|
||||
services:
|
||||
npm:
|
||||
extends:
|
||||
file: docker-shared.yml
|
||||
service: app
|
||||
command: npm install
|
||||
|
||||
test_unit:
|
||||
extends:
|
||||
file: docker-shared.yml
|
||||
service: app
|
||||
command: npm run test:unit
|
||||
|
||||
test_acceptance:
|
||||
extends:
|
||||
file: docker-shared.yml
|
||||
service: app
|
||||
environment:
|
||||
REDIS_HOST: redis
|
||||
MONGO_URL: "mongodb://mongo/sharelatex"
|
||||
SHARELATEX_ALLOW_PUBLIC_ACCESS: 'true'
|
||||
depends_on:
|
||||
- redis
|
||||
- mongo
|
||||
command: npm run start
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
|
||||
mongo:
|
||||
image: mongo:3.4.6
|
||||
@@ -0,0 +1,30 @@
|
||||
version: "2"
|
||||
|
||||
# We mount all the directories explicitly so that we are only mounting
|
||||
# the coffee directories, so that the compiled js is only written inside
|
||||
# the container, and not back to the local filesystem, where it would be
|
||||
# root owned, and conflict with working outside of the container.
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:6.9.5
|
||||
volumes:
|
||||
- ./package.json:/app/package.json
|
||||
- ./npm-shrinkwrap.json:/app/npm-shrinkwrap.json
|
||||
- node_modules:/app/node_modules
|
||||
- ./bin:/app/bin
|
||||
# Copying the whole public dir is fine for now, and needed for
|
||||
# some unit tests to pass, but we will want to isolate the coffee
|
||||
# and vendor js files, so that the compiled js files are not written
|
||||
# back to the local filesystem.
|
||||
- ./public:/app/public
|
||||
- ./app.coffee:/app/app.coffee:ro
|
||||
- ./app/coffee:/app/app/coffee:ro
|
||||
- ./app/templates:/app/app/templates:ro
|
||||
- ./app/views:/app/app/views:ro
|
||||
- ./config:/app/config
|
||||
- ./test/unit/coffee:/app/test/unit/coffee:ro
|
||||
- ./test/acceptance/coffee:/app/test/acceptance/coffee:ro
|
||||
- ./test/smoke/coffee:/app/test/smoke/coffee:ro
|
||||
MODULE_VOLUMES
|
||||
working_dir: /app
|
||||
@@ -1,12 +1,6 @@
|
||||
*/app/js
|
||||
*/test/unit/js
|
||||
*/index.js
|
||||
ldap
|
||||
admin-panel
|
||||
groovehq
|
||||
launchpad
|
||||
learn-wiki
|
||||
references-search
|
||||
sharelatex-saml
|
||||
templates
|
||||
tpr-webmodule
|
||||
# Ignore all modules except for a whitelist
|
||||
*
|
||||
!dropbox
|
||||
!github-sync
|
||||
!public-registration
|
||||
!.gitignore
|
||||
|
||||
@@ -9,6 +9,17 @@
|
||||
"directories": {
|
||||
"public": "./public"
|
||||
},
|
||||
"scripts": {
|
||||
"test:acceptance:wait_for_app": "echo 'Waiting for app to be accessible' && while (! curl -s -o /dev/null localhost:3000/status) do sleep 1; done",
|
||||
"test:acceptance:run": "bin/acceptance_test $@",
|
||||
"test:acceptance:dir": "npm -q run compile:acceptance_tests && npm -q run test:acceptance:wait_for_app && npm -q run test:acceptance:run -- $@",
|
||||
"test:acceptance": "npm -q run test:acceptance:dir -- $@ test/acceptance/js",
|
||||
"test:unit": "npm -q run compile:app && npm -q run compile:unit_tests && bin/unit_test $@",
|
||||
"compile:unit_tests": "bin/compile_unit_tests",
|
||||
"compile:acceptance_tests": "bin/compile_acceptance_tests",
|
||||
"compile:app": "bin/compile_app",
|
||||
"start": "npm -q run compile:app && node app.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"archiver": "0.9.0",
|
||||
"async": "0.6.2",
|
||||
@@ -98,6 +109,7 @@
|
||||
"grunt-postcss": "^0.8.0",
|
||||
"grunt-sed": "^0.1.1",
|
||||
"grunt-shell": "^2.1.0",
|
||||
"mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"sandboxed-module": "0.2.0",
|
||||
"sinon": "^1.17.0",
|
||||
"timekeeper": "",
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
"fineuploader"
|
||||
], (App, qq) ->
|
||||
App.directive 'fineUpload', ($timeout) ->
|
||||
return {
|
||||
scope: {
|
||||
multiple: "="
|
||||
endpoint: "@"
|
||||
waitingForResponseText: "@"
|
||||
failedUploadText: "@"
|
||||
uploadButtonText: "@"
|
||||
dragAreaText: "@"
|
||||
hintText: "@"
|
||||
templateId: "@"
|
||||
allowedExtensions: "="
|
||||
onCompleteCallback: "="
|
||||
onUploadCallback: "="
|
||||
@@ -25,17 +22,12 @@ define [
|
||||
link: (scope, element, attrs) ->
|
||||
multiple = scope.multiple or false
|
||||
endpoint = scope.endpoint
|
||||
templateId = scope.templateId
|
||||
if scope.allowedExtensions?
|
||||
validation =
|
||||
validation =
|
||||
allowedExtensions: scope.allowedExtensions
|
||||
else
|
||||
validation = {}
|
||||
text =
|
||||
waitingForResponse: scope.waitingForResponseText or "Processing..."
|
||||
failUpload: scope.failedUploadText or "Failed :("
|
||||
uploadButton: scope.uploadButtonText or "Upload"
|
||||
dragAreaText = scope.dragAreaText or "drag here"
|
||||
hintText = scope.hintText or ""
|
||||
maxConnections = scope.maxConnections or 1
|
||||
onComplete = scope.onCompleteCallback or () ->
|
||||
onUpload = scope.onUploadCallback or () ->
|
||||
@@ -69,21 +61,8 @@ define [
|
||||
onError: onError
|
||||
onSubmit: onSubmit
|
||||
onCancel: onCancel
|
||||
text: text
|
||||
template: """
|
||||
<div class="qq-uploader">
|
||||
<div class="qq-upload-drop-area"><span>{dragZoneText}</span></div>
|
||||
<div class="qq-upload-button btn btn-primary btn-lg">
|
||||
<div>{uploadButtonText}</div>
|
||||
</div>
|
||||
<span class="or btn-lg"> or </span>
|
||||
<span class="drag-here btn-lg">#{dragAreaText}</span>
|
||||
<span class="qq-drop-processing"><span>{dropProcessingText}</span><span class="qq-drop-processing-spinner"></span></span>
|
||||
<div class="small">#{hintText}</div>
|
||||
<ul class="qq-upload-list"></ul>
|
||||
</div>
|
||||
"""
|
||||
template: templateId
|
||||
window.q = q
|
||||
scope.control?.q = q
|
||||
return q
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ define [
|
||||
"ide/pdf/PdfManager"
|
||||
"ide/binary-files/BinaryFilesManager"
|
||||
"ide/references/ReferencesManager"
|
||||
"ide/labels/LabelsManager"
|
||||
"ide/metadata/MetadataManager"
|
||||
"ide/review-panel/ReviewPanelManager"
|
||||
"ide/SafariScrollPatcher"
|
||||
"ide/AutoCompileOnboardingController",
|
||||
@@ -47,12 +47,12 @@ define [
|
||||
PdfManager
|
||||
BinaryFilesManager
|
||||
ReferencesManager
|
||||
LabelsManager
|
||||
MetadataManager
|
||||
ReviewPanelManager
|
||||
SafariScrollPatcher
|
||||
) ->
|
||||
|
||||
App.controller "IdeController", ($scope, $timeout, ide, localStorage, sixpack, event_tracking, labels) ->
|
||||
App.controller "IdeController", ($scope, $timeout, ide, localStorage, sixpack, event_tracking, metadata) ->
|
||||
# Don't freak out if we're already in an apply callback
|
||||
$scope.$originalApply = $scope.$apply
|
||||
$scope.$apply = (fn = () ->) ->
|
||||
@@ -72,10 +72,10 @@ define [
|
||||
view: "editor"
|
||||
chatOpen: false
|
||||
pdfLayout: 'sideBySide'
|
||||
pdfHidden: false,
|
||||
pdfWidth: 0,
|
||||
reviewPanelOpen: localStorage("ui.reviewPanelOpen.#{window.project_id}"),
|
||||
miniReviewPanelVisible: false,
|
||||
pdfHidden: false
|
||||
pdfWidth: 0
|
||||
reviewPanelOpen: localStorage("ui.reviewPanelOpen.#{window.project_id}")
|
||||
miniReviewPanelVisible: false
|
||||
}
|
||||
$scope.onboarding = {
|
||||
autoCompile: if window.showAutoCompileOnboarding then 'unseen' else 'dismissed'
|
||||
@@ -138,7 +138,7 @@ define [
|
||||
ide.pdfManager = new PdfManager(ide, $scope)
|
||||
ide.permissionsManager = new PermissionsManager(ide, $scope)
|
||||
ide.binaryFilesManager = new BinaryFilesManager(ide, $scope)
|
||||
ide.labelsManager = new LabelsManager(ide, $scope, labels)
|
||||
ide.metadataManager = new MetadataManager(ide, $scope, metadata)
|
||||
|
||||
inited = false
|
||||
$scope.$on "project:joined", () ->
|
||||
@@ -153,7 +153,7 @@ define [
|
||||
$timeout(
|
||||
() ->
|
||||
if $scope.permissions.write
|
||||
ide.labelsManager.loadProjectLabelsFromServer()
|
||||
ide.metadataManager.loadProjectMetaFromServer()
|
||||
_labelsInitialLoadDone = true
|
||||
, 200
|
||||
)
|
||||
|
||||
@@ -9,11 +9,14 @@ define [
|
||||
"ide/editor/directives/aceEditor/highlights/HighlightsManager"
|
||||
"ide/editor/directives/aceEditor/cursor-position/CursorPositionManager"
|
||||
"ide/editor/directives/aceEditor/track-changes/TrackChangesManager"
|
||||
"ide/editor/directives/aceEditor/metadata/MetadataManager"
|
||||
"ide/editor/directives/aceEditor/labels/LabelsManager"
|
||||
"ide/labels/services/labels"
|
||||
"ide/metadata/services/metadata"
|
||||
"ide/graphics/services/graphics"
|
||||
"ide/preamble/services/preamble"
|
||||
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, LabelsManager) ->
|
||||
"ide/files/services/files"
|
||||
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager, LabelsManager) ->
|
||||
EditSession = ace.require('ace/edit_session').EditSession
|
||||
ModeList = ace.require('ace/ext/modelist')
|
||||
|
||||
@@ -35,7 +38,7 @@ define [
|
||||
url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}"
|
||||
return url
|
||||
|
||||
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, graphics, preamble, $http, $q) ->
|
||||
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, metadata, graphics, preamble, files, $http, $q) ->
|
||||
monkeyPatchSearch($rootScope, $compile)
|
||||
|
||||
return {
|
||||
@@ -103,7 +106,8 @@ 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, graphics, preamble)
|
||||
metadataManager = new MetadataManager(scope, editor, element, metadata)
|
||||
autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, labelsManager, graphics, preamble, files)
|
||||
|
||||
|
||||
# Prevert Ctrl|Cmd-S from triggering save dialog
|
||||
@@ -115,16 +119,16 @@ define [
|
||||
editor.commands.removeCommand "transposeletters"
|
||||
editor.commands.removeCommand "showSettingsMenu"
|
||||
editor.commands.removeCommand "foldall"
|
||||
|
||||
|
||||
# For European keyboards, the / is above 7 so needs Shift pressing.
|
||||
# This comes through as Command-Shift-/ on OS X, which is mapped to
|
||||
# This comes through as Command-Shift-/ on OS X, which is mapped to
|
||||
# toggleBlockComment.
|
||||
# This doesn't do anything for LaTeX, so remap this to togglecomment to
|
||||
# work for European keyboards as normal.
|
||||
# On Windows, the key combo comes as Ctrl-Shift-7.
|
||||
editor.commands.removeCommand "toggleBlockComment"
|
||||
editor.commands.removeCommand "togglecomment"
|
||||
|
||||
|
||||
editor.commands.addCommand {
|
||||
name: "togglecomment",
|
||||
bindKey: { win: "Ctrl-/|Ctrl-Shift-7", mac: "Command-/|Command-Shift-/" },
|
||||
@@ -140,7 +144,7 @@ define [
|
||||
exec: (editor) ->
|
||||
ace.require("ace/ext/searchbox").Search(editor, true)
|
||||
readOnly: true
|
||||
|
||||
|
||||
# Bold text on CMD+B
|
||||
editor.commands.addCommand
|
||||
name: "bold",
|
||||
@@ -154,7 +158,7 @@ define [
|
||||
text = editor.getCopyText()
|
||||
editor.insert("\\textbf{" + text + "}")
|
||||
readOnly: false
|
||||
|
||||
|
||||
# Italicise text on CMD+I
|
||||
editor.commands.addCommand
|
||||
name: "italics",
|
||||
@@ -171,7 +175,7 @@ define [
|
||||
|
||||
scope.$watch "onCtrlEnter", (callback) ->
|
||||
if callback?
|
||||
editor.commands.addCommand
|
||||
editor.commands.addCommand
|
||||
name: "compile",
|
||||
bindKey: win: "Ctrl-Enter", mac: "Command-Enter"
|
||||
exec: (editor) =>
|
||||
@@ -180,7 +184,7 @@ define [
|
||||
|
||||
scope.$watch "onCtrlJ", (callback) ->
|
||||
if callback?
|
||||
editor.commands.addCommand
|
||||
editor.commands.addCommand
|
||||
name: "toggle-review-panel",
|
||||
bindKey: win: "Ctrl-J", mac: "Command-J"
|
||||
exec: (editor) =>
|
||||
@@ -189,7 +193,7 @@ define [
|
||||
|
||||
scope.$watch "onCtrlShiftC", (callback) ->
|
||||
if callback?
|
||||
editor.commands.addCommand
|
||||
editor.commands.addCommand
|
||||
name: "add-new-comment",
|
||||
bindKey: win: "Ctrl-Shift-C", mac: "Command-Shift-C"
|
||||
exec: (editor) =>
|
||||
@@ -198,7 +202,7 @@ define [
|
||||
|
||||
scope.$watch "onCtrlShiftA", (callback) ->
|
||||
if callback?
|
||||
editor.commands.addCommand
|
||||
editor.commands.addCommand
|
||||
name: "toggle-track-changes",
|
||||
bindKey: win: "Ctrl-Shift-A", mac: "Command-Shift-A"
|
||||
exec: (editor) =>
|
||||
@@ -302,7 +306,7 @@ define [
|
||||
if updateCount == 100
|
||||
event_tracking.send 'editor-interaction', 'multi-doc-update'
|
||||
scope.$emit "#{scope.name}:change"
|
||||
|
||||
|
||||
onScroll = (scrollTop) ->
|
||||
return if !scope.eventsBridge?
|
||||
height = editor.renderer.layerConfig.maxHeight
|
||||
@@ -311,7 +315,7 @@ define [
|
||||
onScrollbarVisibilityChanged = (event, vRenderer) ->
|
||||
return if !scope.eventsBridge?
|
||||
scope.eventsBridge.emit "aceScrollbarVisibilityChanged", vRenderer.scrollBarV.isVisible, vRenderer.scrollBarV.width
|
||||
|
||||
|
||||
if scope.eventsBridge?
|
||||
editor.renderer.on "scrollbarVisibilityChanged", onScrollbarVisibilityChanged
|
||||
|
||||
@@ -404,14 +408,14 @@ define [
|
||||
|
||||
session = editor.getSession()
|
||||
session.off "changeScrollTop"
|
||||
|
||||
|
||||
doc = session.getDocument()
|
||||
doc.off "change", onChange
|
||||
|
||||
|
||||
editor.renderer.on "changeCharacterSize", () ->
|
||||
scope.$apply () ->
|
||||
scope.rendererData.lineHeight = editor.renderer.lineHeight
|
||||
|
||||
|
||||
scope.$watch "rendererData", (rendererData) ->
|
||||
if rendererData?
|
||||
rendererData.lineHeight = editor.renderer.lineHeight
|
||||
|
||||
+44
-17
@@ -1,16 +1,16 @@
|
||||
define [
|
||||
"ide/editor/directives/aceEditor/auto-complete/CommandManager"
|
||||
"ide/editor/directives/aceEditor/auto-complete/EnvironmentManager"
|
||||
"ide/editor/directives/aceEditor/auto-complete/PackageManager"
|
||||
"ide/editor/directives/aceEditor/auto-complete/Helpers"
|
||||
"ace/ace"
|
||||
"ace/ext-language_tools"
|
||||
], (CommandManager, EnvironmentManager, Helpers) ->
|
||||
], (CommandManager, EnvironmentManager, PackageManager, Helpers) ->
|
||||
Range = ace.require("ace/range").Range
|
||||
aceSnippetManager = ace.require('ace/snippets').snippetManager
|
||||
|
||||
class AutoCompleteManager
|
||||
constructor: (@$scope, @editor, @element, @labelsManager, @graphics, @preamble) ->
|
||||
@suggestionManager = new CommandManager()
|
||||
constructor: (@$scope, @editor, @element, @metadataManager, @labelsManager, @graphics, @preamble, @files) ->
|
||||
|
||||
@monkeyPatchAutocomplete()
|
||||
|
||||
@@ -34,10 +34,15 @@ define [
|
||||
enableLiveAutocompletion: false
|
||||
})
|
||||
|
||||
commandCompleter = new CommandManager(@metadataManager)
|
||||
|
||||
SnippetCompleter = new EnvironmentManager()
|
||||
PackageCompleter = new PackageManager()
|
||||
|
||||
Graphics = @graphics
|
||||
Preamble = @preamble
|
||||
Files = @files
|
||||
|
||||
GraphicsCompleter =
|
||||
getCompletions: (editor, session, pos, prefix, callback) ->
|
||||
context = Helpers.getContext(editor, pos)
|
||||
@@ -63,7 +68,28 @@ define [
|
||||
}
|
||||
callback null, result
|
||||
|
||||
labelsManager = @labelsManager
|
||||
metadataManager = @metadataManager
|
||||
FilesCompleter =
|
||||
getCompletions: (editor, session, pos, prefix, callback) =>
|
||||
context = Helpers.getContext(editor, pos)
|
||||
{lineUpToCursor, commandFragment, lineBeyondCursor, needsClosingBrace} = context
|
||||
if commandFragment
|
||||
match = commandFragment.match(/^\\(input|include){(\w*)/)
|
||||
if match
|
||||
commandName = match[1]
|
||||
currentArg = match[2]
|
||||
result = []
|
||||
for file in Files.getTeXFiles()
|
||||
if file.id != @$scope.docId
|
||||
path = file.path
|
||||
result.push {
|
||||
caption: "\\#{commandName}{#{path}#{if needsClosingBrace then '}' else ''}",
|
||||
value: "\\#{commandName}{#{path}#{if needsClosingBrace then '}' else ''}",
|
||||
meta: "file",
|
||||
score: 50
|
||||
}
|
||||
callback null, result
|
||||
|
||||
LabelsCompleter =
|
||||
getCompletions: (editor, session, pos, prefix, callback) ->
|
||||
context = Helpers.getContext(editor, pos)
|
||||
@@ -74,13 +100,14 @@ define [
|
||||
commandName = refMatch[1]
|
||||
currentArg = refMatch[2]
|
||||
result = []
|
||||
result.push {
|
||||
caption: "\\#{commandName}{}",
|
||||
snippet: "\\#{commandName}{}",
|
||||
meta: "cross-reference",
|
||||
score: 60
|
||||
}
|
||||
for label in labelsManager.getAllLabels()
|
||||
if commandName != 'ref' # ref is in top 100 commands
|
||||
result.push {
|
||||
caption: "\\#{commandName}{}",
|
||||
snippet: "\\#{commandName}{}",
|
||||
meta: "cross-reference",
|
||||
score: 60
|
||||
}
|
||||
for label in metadataManager.getAllLabels()
|
||||
result.push {
|
||||
caption: "\\#{commandName}{#{label}#{if needsClosingBrace then '}' else ''}",
|
||||
value: "\\#{commandName}{#{label}#{if needsClosingBrace then '}' else ''}",
|
||||
@@ -126,11 +153,13 @@ define [
|
||||
callback null, result
|
||||
|
||||
@editor.completers = [
|
||||
@suggestionManager,
|
||||
SnippetCompleter,
|
||||
ReferencesCompleter,
|
||||
LabelsCompleter,
|
||||
commandCompleter
|
||||
SnippetCompleter
|
||||
PackageCompleter
|
||||
ReferencesCompleter
|
||||
LabelsCompleter
|
||||
GraphicsCompleter
|
||||
FilesCompleter
|
||||
]
|
||||
|
||||
disable: () ->
|
||||
@@ -152,8 +181,6 @@ define [
|
||||
if lastTwoChars.match(/^\\[^a-zA-Z]$/)
|
||||
@editor?.completer?.detach?()
|
||||
return
|
||||
if commandName in ['begin', 'end']
|
||||
return
|
||||
# Check that this change was made by us, not a collaborator
|
||||
# (Cursor is still one place behind)
|
||||
# NOTE: this is also the case when a user backspaces over a highlighted region
|
||||
|
||||
+17
-82
@@ -1,81 +1,8 @@
|
||||
define [], () ->
|
||||
noArgumentCommands = [
|
||||
'item', 'hline', 'lipsum', 'centering', 'noindent', 'textwidth', 'draw',
|
||||
'maketitle', 'newpage', 'verb', 'bibliography', 'hfill', 'par',
|
||||
'in', 'sum', 'cdot', 'ldots', 'linewidth', 'left', 'right', 'today',
|
||||
'clearpage', 'newline', 'endinput', 'tableofcontents', 'vfill',
|
||||
'bigskip', 'fill', 'cleardoublepage', 'infty', 'leq', 'geq', 'times',
|
||||
'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'varepsilon', 'zeta',
|
||||
'eta', 'theta', 'vartheta', 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi',
|
||||
'pi', 'varpi', 'rho', 'varrho', 'sigma', 'varsigma', 'tau', 'upsilon',
|
||||
'phi', 'varphi', 'chi', 'psi', 'omega', 'Gamma', 'Delta', 'Theta',
|
||||
'Lambda', 'Xi', 'Pi', 'Sigma', 'Upsilon', 'Phi', 'Psi', 'Omega'
|
||||
]
|
||||
singleArgumentCommands = [
|
||||
'chapter', 'usepackage', 'section', 'label', 'textbf', 'subsection',
|
||||
'vspace', 'cite', 'textit', 'documentclass', 'includegraphics', 'input',
|
||||
'emph','caption', 'ref', 'title', 'author', 'texttt', 'include',
|
||||
'hspace', 'bibitem', 'url', 'large', 'subsubsection', 'textsc', 'date',
|
||||
'footnote', 'small', 'thanks', 'underline', 'graphicspath', 'pageref',
|
||||
'section*', 'subsection*', 'subsubsection*', 'sqrt', 'text',
|
||||
'normalsize', 'footnotesize', 'Large', 'paragraph', 'pagestyle',
|
||||
'thispagestyle', 'bibliographystyle', 'hat'
|
||||
]
|
||||
doubleArgumentCommands = [
|
||||
'newcommand', 'frac', 'dfrac', 'renewcommand', 'setlength', 'href',
|
||||
'newtheorem'
|
||||
]
|
||||
tripleArgumentCommands = [
|
||||
'addcontentsline', 'newacronym', 'multicolumn'
|
||||
]
|
||||
special = ['LaTeX', 'TeX']
|
||||
define [
|
||||
"./top_hundred_snippets"
|
||||
], (topHundred) ->
|
||||
|
||||
rawCommands = [].concat(
|
||||
noArgumentCommands,
|
||||
singleArgumentCommands,
|
||||
doubleArgumentCommands,
|
||||
tripleArgumentCommands,
|
||||
special
|
||||
)
|
||||
|
||||
noArgumentCommands = for cmd in noArgumentCommands
|
||||
{
|
||||
caption: "\\#{cmd}"
|
||||
snippet: "\\#{cmd}"
|
||||
meta: "cmd"
|
||||
}
|
||||
singleArgumentCommands = for cmd in singleArgumentCommands
|
||||
{
|
||||
caption: "\\#{cmd}{}"
|
||||
snippet: "\\#{cmd}{$1}"
|
||||
meta: "cmd"
|
||||
}
|
||||
doubleArgumentCommands = for cmd in doubleArgumentCommands
|
||||
{
|
||||
caption: "\\#{cmd}{}{}"
|
||||
snippet: "\\#{cmd}{$1}{$2}"
|
||||
meta: "cmd"
|
||||
}
|
||||
tripleArgumentCommands = for cmd in tripleArgumentCommands
|
||||
{
|
||||
caption: "\\#{cmd}{}{}{}"
|
||||
snippet: "\\#{cmd}{$1}{$2}{$3}"
|
||||
meta: "cmd"
|
||||
}
|
||||
special = for cmd in special
|
||||
{
|
||||
caption: "\\#{cmd}{}"
|
||||
snippet: "\\#{cmd}{}"
|
||||
meta: "cmd"
|
||||
}
|
||||
|
||||
staticCommands = [].concat(
|
||||
noArgumentCommands,
|
||||
singleArgumentCommands,
|
||||
doubleArgumentCommands,
|
||||
tripleArgumentCommands,
|
||||
special
|
||||
)
|
||||
commandNames = (snippet.caption.match(/\w+/)[0] for snippet in topHundred)
|
||||
|
||||
class Parser
|
||||
constructor: (@doc, @prefix) ->
|
||||
@@ -129,10 +56,10 @@ define [], () ->
|
||||
return realCommands
|
||||
|
||||
# Ignore single letter commands since auto complete is moot then.
|
||||
commandRegex: /\\([a-zA-Z][a-zA-Z]+)/
|
||||
commandRegex: /\\([a-zA-Z]{2,})/
|
||||
|
||||
nextCommand: () ->
|
||||
i = @doc.search(@commandRegex)
|
||||
i = @doc.search @commandRegex
|
||||
if i == -1
|
||||
return false
|
||||
else
|
||||
@@ -166,13 +93,21 @@ define [], () ->
|
||||
return false
|
||||
|
||||
class CommandManager
|
||||
constructor: (@metadataManager) ->
|
||||
|
||||
getCompletions: (editor, session, pos, prefix, callback) ->
|
||||
packages = @metadataManager.getAllPackages()
|
||||
packageCommands = []
|
||||
for pkg, snippets of packages
|
||||
for snippet in snippets
|
||||
packageCommands.push snippet
|
||||
|
||||
doc = session.getValue()
|
||||
parser = new Parser(doc, prefix)
|
||||
commands = parser.parse()
|
||||
completions = []
|
||||
for command in commands
|
||||
if command[0] not in rawCommands
|
||||
if command[0] not in commandNames
|
||||
caption = "\\#{command[0]}"
|
||||
score = if caption == prefix then 99 else 50
|
||||
snippet = caption
|
||||
@@ -191,9 +126,9 @@ define [], () ->
|
||||
meta: "cmd"
|
||||
score: score
|
||||
}
|
||||
completions = completions.concat staticCommands
|
||||
completions = completions.concat topHundred, packageCommands
|
||||
|
||||
callback(null, completions)
|
||||
callback null, completions
|
||||
|
||||
loadCommandsFromDoc: (doc) ->
|
||||
parser = new Parser(doc)
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
define () ->
|
||||
packages = [
|
||||
'inputenc', 'graphicx', 'amsmath', 'geometry', 'amssymb', 'hyperref',
|
||||
'babel', 'color', 'xcolor', 'url', 'natbib', 'fontenc', 'fancyhdr',
|
||||
'amsfonts', 'booktabs', 'amsthm', 'float', 'tikz', 'caption',
|
||||
'setspace', 'multirow', 'array', 'multicol', 'titlesec', 'enumitem',
|
||||
'ifthen', 'listings', 'blindtext', 'subcaption', 'times', 'bm',
|
||||
'subfigure', 'algorithm', 'fontspec', 'biblatex', 'tabularx',
|
||||
'microtype', 'etoolbox', 'parskip', 'calc', 'verbatim', 'mathtools',
|
||||
'epsfig', 'wrapfig', 'lipsum', 'cite', 'textcomp', 'longtable',
|
||||
'textpos', 'algpseudocode', 'enumerate', 'subfig', 'pdfpages',
|
||||
'epstopdf', 'latexsym', 'lmodern', 'pifont', 'ragged2e', 'rotating',
|
||||
'dcolumn', 'xltxtra', 'marvosym', 'indentfirst', 'xspace', 'csquotes',
|
||||
'xparse', 'changepage', 'soul', 'xunicode', 'comment', 'mathrsfs',
|
||||
'tocbibind', 'lastpage', 'algorithm2e', 'pgfplots', 'lineno',
|
||||
'graphics', 'algorithmic', 'fullpage', 'mathptmx', 'todonotes',
|
||||
'ulem', 'tweaklist', 'moderncvstyleclassic', 'collection',
|
||||
'moderncvcompatibility', 'gensymb', 'helvet', 'siunitx', 'adjustbox',
|
||||
'placeins', 'colortbl', 'appendix', 'makeidx', 'supertabular', 'ifpdf',
|
||||
'framed', 'aliascnt', 'layaureo', 'authblk'
|
||||
]
|
||||
|
||||
packageSnippets = for pkg in packages
|
||||
{
|
||||
caption: "\\usepackage{#{pkg}}"
|
||||
snippet: "\\usepackage{#{pkg}}"
|
||||
meta: "pkg"
|
||||
}
|
||||
|
||||
packageSnippets.push {
|
||||
caption: "\\usepackage{}"
|
||||
snippet: "\\usepackage{$1}"
|
||||
meta: "pkg"
|
||||
score: 70
|
||||
}
|
||||
|
||||
class PackageManager
|
||||
getCompletions: (editor, session, pos, prefix, callback) ->
|
||||
callback null, packageSnippets
|
||||
|
||||
return PackageManager
|
||||
+691
@@ -0,0 +1,691 @@
|
||||
define -> [{
|
||||
"caption": "\\begin{}",
|
||||
"snippet": "\\begin{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 7.849662248028187
|
||||
}, {
|
||||
"caption": "\\begin{}[]",
|
||||
"snippet": "\\begin{$1}[$2]",
|
||||
"meta": "cmd",
|
||||
"score": 7.849662248028187
|
||||
}, {
|
||||
"caption": "\\begin{}{}",
|
||||
"snippet": "\\begin{$1}{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 7.849662248028187
|
||||
}, {
|
||||
"caption": "\\end{}",
|
||||
"snippet": "\\end{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 7.847906405228455
|
||||
}, {
|
||||
"caption": "\\usepackage{}",
|
||||
"snippet": "\\usepackage{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 5.427890758130527
|
||||
}, {
|
||||
"caption": "\\usepackage[]{}",
|
||||
"snippet": "\\usepackage[$1]{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 5.427890758130527
|
||||
}, {
|
||||
"caption": "\\item",
|
||||
"snippet": "\\item",
|
||||
"meta": "cmd",
|
||||
"score": 3.800886892251021
|
||||
}, {
|
||||
"caption": "\\item[]",
|
||||
"snippet": "\\item[$1]",
|
||||
"meta": "cmd",
|
||||
"score": 3.800886892251021
|
||||
}, {
|
||||
"caption": "\\section{}",
|
||||
"snippet": "\\section{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 3.0952612541683835
|
||||
}, {
|
||||
"caption": "\\textbf{}",
|
||||
"snippet": "\\textbf{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 2.627755982816738
|
||||
}, {
|
||||
"caption": "\\cite{}",
|
||||
"snippet": "\\cite{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 2.341195220791228
|
||||
}, {
|
||||
"caption": "\\label{}",
|
||||
"snippet": "\\label{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 1.897791904799601
|
||||
}, {
|
||||
"caption": "\\textit{}",
|
||||
"snippet": "\\textit{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 1.6842996195493385
|
||||
}, {
|
||||
"caption": "\\includegraphics[]{}",
|
||||
"snippet": "\\includegraphics[$1]{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 1.4595731795525781
|
||||
}, {
|
||||
"caption": "\\documentclass[]{}",
|
||||
"snippet": "\\documentclass[$1]{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 1.4425339817971206
|
||||
}, {
|
||||
"caption": "\\documentclass{}",
|
||||
"snippet": "\\documentclass{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 1.4425339817971206
|
||||
}, {
|
||||
"caption": "\\frac{}{}",
|
||||
"snippet": "\\frac{$1}{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 1.4341091141105058
|
||||
}, {
|
||||
"caption": "\\subsection{}",
|
||||
"snippet": "\\subsection{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 1.3890912739512353
|
||||
}, {
|
||||
"caption": "\\hline",
|
||||
"snippet": "\\hline",
|
||||
"meta": "cmd",
|
||||
"score": 1.3209538327406387
|
||||
}, {
|
||||
"caption": "\\caption{}",
|
||||
"snippet": "\\caption{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 1.2569477427490174
|
||||
}, {
|
||||
"caption": "\\centering",
|
||||
"snippet": "\\centering",
|
||||
"meta": "cmd",
|
||||
"score": 1.1642881814937829
|
||||
}, {
|
||||
"caption": "\\vspace{}",
|
||||
"snippet": "\\vspace{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.9533807826673939
|
||||
}, {
|
||||
"caption": "\\title{}",
|
||||
"snippet": "\\title{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.9202908262245683
|
||||
}, {
|
||||
"caption": "\\author{}",
|
||||
"snippet": "\\author{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.8973590434087177
|
||||
}, {
|
||||
"caption": "\\author[]{}",
|
||||
"snippet": "\\author[$1]{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 0.8973590434087177
|
||||
}, {
|
||||
"caption": "\\maketitle",
|
||||
"snippet": "\\maketitle",
|
||||
"meta": "cmd",
|
||||
"score": 0.7504160124360846
|
||||
}, {
|
||||
"caption": "\\textwidth",
|
||||
"snippet": "\\textwidth",
|
||||
"meta": "cmd",
|
||||
"score": 0.7355328080889112
|
||||
}, {
|
||||
"caption": "\\newcommand{}{}",
|
||||
"snippet": "\\newcommand{$1}{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 0.7264891987129375
|
||||
}, {
|
||||
"caption": "\\newcommand{}[]{}",
|
||||
"snippet": "\\newcommand{$1}[$2]{$3}",
|
||||
"meta": "cmd",
|
||||
"score": 0.7264891987129375
|
||||
}, {
|
||||
"caption": "\\date{}",
|
||||
"snippet": "\\date{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.7225518453076786
|
||||
}, {
|
||||
"caption": "\\emph{}",
|
||||
"snippet": "\\emph{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.7060308784832261
|
||||
}, {
|
||||
"caption": "\\textsc{}",
|
||||
"snippet": "\\textsc{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.6926466355384758
|
||||
}, {
|
||||
"caption": "\\multicolumn{}{}{}",
|
||||
"snippet": "\\multicolumn{$1}{$2}{$3}",
|
||||
"meta": "cmd",
|
||||
"score": 0.5473606021405326
|
||||
}, {
|
||||
"caption": "\\input{}",
|
||||
"snippet": "\\input{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.4966021927742672
|
||||
}, {
|
||||
"caption": "\\alpha",
|
||||
"snippet": "\\alpha",
|
||||
"meta": "cmd",
|
||||
"score": 0.49520006391384913
|
||||
}, {
|
||||
"caption": "\\in",
|
||||
"snippet": "\\in",
|
||||
"meta": "cmd",
|
||||
"score": 0.4716039670146658
|
||||
}, {
|
||||
"caption": "\\mathbf{}",
|
||||
"snippet": "\\mathbf{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.4682018419466319
|
||||
}, {
|
||||
"caption": "\\right",
|
||||
"snippet": "\\right",
|
||||
"meta": "cmd",
|
||||
"score": 0.4299239459457309
|
||||
}, {
|
||||
"caption": "\\left",
|
||||
"snippet": "\\left",
|
||||
"meta": "cmd",
|
||||
"score": 0.42937815279867964
|
||||
}, {
|
||||
"caption": "\\left[]",
|
||||
"snippet": "\\left[$1]",
|
||||
"meta": "cmd",
|
||||
"score": 0.42937815279867964
|
||||
}, {
|
||||
"caption": "\\sum",
|
||||
"snippet": "\\sum",
|
||||
"meta": "cmd",
|
||||
"score": 0.42607994509619934
|
||||
}, {
|
||||
"caption": "\\noindent",
|
||||
"snippet": "\\noindent",
|
||||
"meta": "cmd",
|
||||
"score": 0.42355747798114207
|
||||
}, {
|
||||
"caption": "\\chapter{}",
|
||||
"snippet": "\\chapter{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.422097569591803
|
||||
}, {
|
||||
"caption": "\\par",
|
||||
"snippet": "\\par",
|
||||
"meta": "cmd",
|
||||
"score": 0.413853376001159
|
||||
}, {
|
||||
"caption": "\\lambda",
|
||||
"snippet": "\\lambda",
|
||||
"meta": "cmd",
|
||||
"score": 0.39389600578684125
|
||||
}, {
|
||||
"caption": "\\subsubsection{}",
|
||||
"snippet": "\\subsubsection{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.3727781330132016
|
||||
}, {
|
||||
"caption": "\\bibitem{}",
|
||||
"snippet": "\\bibitem{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.3689547570562042
|
||||
}, {
|
||||
"caption": "\\bibitem[]{}",
|
||||
"snippet": "\\bibitem[$1]{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 0.3689547570562042
|
||||
}, {
|
||||
"caption": "\\text{}",
|
||||
"snippet": "\\text{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.3608680734736821
|
||||
}, {
|
||||
"caption": "\\setlength{}{}",
|
||||
"snippet": "\\setlength{$1}{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 0.354445763583904
|
||||
}, {
|
||||
"caption": "\\setlength",
|
||||
"snippet": "\\setlength",
|
||||
"meta": "cmd",
|
||||
"score": 0.354445763583904
|
||||
}, {
|
||||
"caption": "\\mathcal{}",
|
||||
"snippet": "\\mathcal{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.35084018920966636
|
||||
}, {
|
||||
"caption": "\\newline",
|
||||
"snippet": "\\newline",
|
||||
"meta": "cmd",
|
||||
"score": 0.3311721696201715
|
||||
}, {
|
||||
"caption": "\\newpage",
|
||||
"snippet": "\\newpage",
|
||||
"meta": "cmd",
|
||||
"score": 0.3277033727934986
|
||||
}, {
|
||||
"caption": "\\renewcommand{}{}",
|
||||
"snippet": "\\renewcommand{$1}{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 0.3267437011085663
|
||||
}, {
|
||||
"caption": "\\renewcommand",
|
||||
"snippet": "\\renewcommand",
|
||||
"meta": "cmd",
|
||||
"score": 0.3267437011085663
|
||||
}, {
|
||||
"caption": "\\theta",
|
||||
"snippet": "\\theta",
|
||||
"meta": "cmd",
|
||||
"score": 0.3210417159232142
|
||||
}, {
|
||||
"caption": "\\hspace{}",
|
||||
"snippet": "\\hspace{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.3147206476372336
|
||||
}, {
|
||||
"caption": "\\beta",
|
||||
"snippet": "\\beta",
|
||||
"meta": "cmd",
|
||||
"score": 0.3061799530337638
|
||||
}, {
|
||||
"caption": "\\texttt{}",
|
||||
"snippet": "\\texttt{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.3019066753744355
|
||||
}, {
|
||||
"caption": "\\times",
|
||||
"snippet": "\\times",
|
||||
"meta": "cmd",
|
||||
"score": 0.2957960629411553
|
||||
}, {
|
||||
"caption": "\\citep{}",
|
||||
"snippet": "\\citep{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.2941882834697057
|
||||
}, {
|
||||
"caption": "\\color[]{}",
|
||||
"snippet": "\\color[$1]{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 0.2864294797053033
|
||||
}, {
|
||||
"caption": "\\color{}",
|
||||
"snippet": "\\color{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.2864294797053033
|
||||
}, {
|
||||
"caption": "\\mu",
|
||||
"snippet": "\\mu",
|
||||
"meta": "cmd",
|
||||
"score": 0.27635652476799255
|
||||
}, {
|
||||
"caption": "\\bibliography{}",
|
||||
"snippet": "\\bibliography{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.2659628337907604
|
||||
}, {
|
||||
"caption": "\\linewidth",
|
||||
"snippet": "\\linewidth",
|
||||
"meta": "cmd",
|
||||
"score": 0.2639498312518439
|
||||
}, {
|
||||
"caption": "\\delta",
|
||||
"snippet": "\\delta",
|
||||
"meta": "cmd",
|
||||
"score": 0.2620578600722735
|
||||
}, {
|
||||
"caption": "\\sigma",
|
||||
"snippet": "\\sigma",
|
||||
"meta": "cmd",
|
||||
"score": 0.25940147926344487
|
||||
}, {
|
||||
"caption": "\\pi",
|
||||
"snippet": "\\pi",
|
||||
"meta": "cmd",
|
||||
"score": 0.25920934567729714
|
||||
}, {
|
||||
"caption": "\\hat{}",
|
||||
"snippet": "\\hat{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.25264309033778715
|
||||
}, {
|
||||
"caption": "\\hat",
|
||||
"snippet": "\\hat",
|
||||
"meta": "cmd",
|
||||
"score": 0.25264309033778715
|
||||
}, {
|
||||
"caption": "\\bibliographystyle{}",
|
||||
"snippet": "\\bibliographystyle{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.25122317941387773
|
||||
}, {
|
||||
"caption": "\\small",
|
||||
"snippet": "\\small",
|
||||
"meta": "cmd",
|
||||
"score": 0.2447632045426295
|
||||
}, {
|
||||
"caption": "\\small{}",
|
||||
"snippet": "\\small{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.2447632045426295
|
||||
}, {
|
||||
"caption": "\\LaTeX",
|
||||
"snippet": "\\LaTeX",
|
||||
"meta": "cmd",
|
||||
"score": 0.2334089308452787
|
||||
}, {
|
||||
"caption": "\\LaTeX{}",
|
||||
"snippet": "\\LaTeX{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.2334089308452787
|
||||
}, {
|
||||
"caption": "\\cdot",
|
||||
"snippet": "\\cdot",
|
||||
"meta": "cmd",
|
||||
"score": 0.23029085545522762
|
||||
}, {
|
||||
"caption": "\\footnote{}",
|
||||
"snippet": "\\footnote{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.2253056071787701
|
||||
}, {
|
||||
"caption": "\\newtheorem{}[]{}",
|
||||
"snippet": "\\newtheorem{$1}[$2]{$3}",
|
||||
"meta": "cmd",
|
||||
"score": 0.215689795055434
|
||||
}, {
|
||||
"caption": "\\newtheorem{}{}",
|
||||
"snippet": "\\newtheorem{$1}{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 0.215689795055434
|
||||
}, {
|
||||
"caption": "\\newtheorem{}{}[]",
|
||||
"snippet": "\\newtheorem{$1}{$2}[$3]",
|
||||
"meta": "cmd",
|
||||
"score": 0.215689795055434
|
||||
}, {
|
||||
"caption": "\\Delta",
|
||||
"snippet": "\\Delta",
|
||||
"meta": "cmd",
|
||||
"score": 0.21386475063892618
|
||||
}, {
|
||||
"caption": "\\tau",
|
||||
"snippet": "\\tau",
|
||||
"meta": "cmd",
|
||||
"score": 0.21236188205859796
|
||||
}, {
|
||||
"caption": "\\hfill",
|
||||
"snippet": "\\hfill",
|
||||
"meta": "cmd",
|
||||
"score": 0.2058248088519886
|
||||
}, {
|
||||
"caption": "\\leq",
|
||||
"snippet": "\\leq",
|
||||
"meta": "cmd",
|
||||
"score": 0.20498894440637172
|
||||
}, {
|
||||
"caption": "\\footnotesize",
|
||||
"snippet": "\\footnotesize",
|
||||
"meta": "cmd",
|
||||
"score": 0.2038592081252624
|
||||
}, {
|
||||
"caption": "\\footnotesize{}",
|
||||
"snippet": "\\footnotesize{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.2038592081252624
|
||||
}, {
|
||||
"caption": "\\large",
|
||||
"snippet": "\\large",
|
||||
"meta": "cmd",
|
||||
"score": 0.20377416734108866
|
||||
}, {
|
||||
"caption": "\\large{}",
|
||||
"snippet": "\\large{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.20377416734108866
|
||||
}, {
|
||||
"caption": "\\sqrt{}",
|
||||
"snippet": "\\sqrt{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.20240160977404634
|
||||
}, {
|
||||
"caption": "\\epsilon",
|
||||
"snippet": "\\epsilon",
|
||||
"meta": "cmd",
|
||||
"score": 0.2005136761359043
|
||||
}, {
|
||||
"caption": "\\Large",
|
||||
"snippet": "\\Large",
|
||||
"meta": "cmd",
|
||||
"score": 0.1987771081149759
|
||||
}, {
|
||||
"caption": "\\Large{}",
|
||||
"snippet": "\\Large{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.1987771081149759
|
||||
}, {
|
||||
"caption": "\\cvitem{}{}",
|
||||
"snippet": "\\cvitem{$1}{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 0.19605476980016281
|
||||
}, {
|
||||
"caption": "\\rho",
|
||||
"snippet": "\\rho",
|
||||
"meta": "cmd",
|
||||
"score": 0.1959287380541684
|
||||
}, {
|
||||
"caption": "\\omega",
|
||||
"snippet": "\\omega",
|
||||
"meta": "cmd",
|
||||
"score": 0.19326783415115262
|
||||
}, {
|
||||
"caption": "\\mathrm{}",
|
||||
"snippet": "\\mathrm{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.19117752976172653
|
||||
}, {
|
||||
"caption": "\\boldsymbol{}",
|
||||
"snippet": "\\boldsymbol{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.18137737738638837
|
||||
}, {
|
||||
"caption": "\\boldsymbol",
|
||||
"snippet": "\\boldsymbol",
|
||||
"meta": "cmd",
|
||||
"score": 0.18137737738638837
|
||||
}, {
|
||||
"caption": "\\gamma",
|
||||
"snippet": "\\gamma",
|
||||
"meta": "cmd",
|
||||
"score": 0.17940276535431304
|
||||
}, {
|
||||
"caption": "\\clearpage",
|
||||
"snippet": "\\clearpage",
|
||||
"meta": "cmd",
|
||||
"score": 0.1789117552185788
|
||||
}, {
|
||||
"caption": "\\infty",
|
||||
"snippet": "\\infty",
|
||||
"meta": "cmd",
|
||||
"score": 0.17837290019711305
|
||||
}, {
|
||||
"caption": "\\phi",
|
||||
"snippet": "\\phi",
|
||||
"meta": "cmd",
|
||||
"score": 0.17405809173097808
|
||||
}, {
|
||||
"caption": "\\partial",
|
||||
"snippet": "\\partial",
|
||||
"meta": "cmd",
|
||||
"score": 0.17168102367966637
|
||||
}, {
|
||||
"caption": "\\include{}",
|
||||
"snippet": "\\include{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.1547080054979312
|
||||
}, {
|
||||
"caption": "\\address{}",
|
||||
"snippet": "\\address{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.1525055392611109
|
||||
}, {
|
||||
"caption": "\\address[]{}",
|
||||
"snippet": "\\address[$1]{$2}",
|
||||
"meta": "cmd",
|
||||
"score": 0.1525055392611109
|
||||
}, {
|
||||
"caption": "\\address{}{}{}",
|
||||
"snippet": "\\address{$1}{$2}{$3}",
|
||||
"meta": "cmd",
|
||||
"score": 0.1525055392611109
|
||||
}, {
|
||||
"caption": "\\quad",
|
||||
"snippet": "\\quad",
|
||||
"meta": "cmd",
|
||||
"score": 0.15242755832392743
|
||||
}, {
|
||||
"caption": "\\email{}",
|
||||
"snippet": "\\email{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.1522294670109857
|
||||
}, {
|
||||
"caption": "\\paragraph{}",
|
||||
"snippet": "\\paragraph{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.152074250347974
|
||||
}, {
|
||||
"caption": "\\varepsilon",
|
||||
"snippet": "\\varepsilon",
|
||||
"meta": "cmd",
|
||||
"score": 0.05411564201390573
|
||||
}, {
|
||||
"caption": "\\zeta",
|
||||
"snippet": "\\zeta",
|
||||
"meta": "cmd",
|
||||
"score": 0.023330249803752954
|
||||
}, {
|
||||
"caption": "\\eta",
|
||||
"snippet": "\\eta",
|
||||
"meta": "cmd",
|
||||
"score": 0.11088718379889091
|
||||
}, {
|
||||
"caption": "\\vartheta",
|
||||
"snippet": "\\vartheta",
|
||||
"meta": "cmd",
|
||||
"score": 0.0025822992078068712
|
||||
}, {
|
||||
"caption": "\\iota",
|
||||
"snippet": "\\iota",
|
||||
"meta": "cmd",
|
||||
"score": 0.0024774003791525486
|
||||
}, {
|
||||
"caption": "\\kappa",
|
||||
"snippet": "\\kappa",
|
||||
"meta": "cmd",
|
||||
"score": 0.04887876299369008
|
||||
}, {
|
||||
"caption": "\\nu",
|
||||
"snippet": "\\nu",
|
||||
"meta": "cmd",
|
||||
"score": 0.09206962821059342
|
||||
}, {
|
||||
"caption": "\\xi",
|
||||
"snippet": "\\xi",
|
||||
"meta": "cmd",
|
||||
"score": 0.06496042899265699
|
||||
}, {
|
||||
"caption": "\\varpi",
|
||||
"snippet": "\\varpi",
|
||||
"meta": "cmd",
|
||||
"score": 0.0007039358167790341
|
||||
}, {
|
||||
"caption": "\\varrho",
|
||||
"snippet": "\\varrho",
|
||||
"meta": "cmd",
|
||||
"score": 0.0011279491613898612
|
||||
}, {
|
||||
"caption": "\\varsigma",
|
||||
"snippet": "\\varsigma",
|
||||
"meta": "cmd",
|
||||
"score": 0.0010424880711234978
|
||||
}, {
|
||||
"caption": "\\varsigma{}",
|
||||
"snippet": "\\varsigma{$1}",
|
||||
"meta": "cmd",
|
||||
"score": 0.0010424880711234978
|
||||
}, {
|
||||
"caption": "\\upsilon",
|
||||
"snippet": "\\upsilon",
|
||||
"meta": "cmd",
|
||||
"score": 0.00420715572598688
|
||||
}, {
|
||||
"caption": "\\varphi",
|
||||
"snippet": "\\varphi",
|
||||
"meta": "cmd",
|
||||
"score": 0.03351251516668212
|
||||
}, {
|
||||
"caption": "\\chi",
|
||||
"snippet": "\\chi",
|
||||
"meta": "cmd",
|
||||
"score": 0.043373492287805675
|
||||
}, {
|
||||
"caption": "\\psi",
|
||||
"snippet": "\\psi",
|
||||
"meta": "cmd",
|
||||
"score": 0.09994508706163642
|
||||
}, {
|
||||
"caption": "\\Gamma",
|
||||
"snippet": "\\Gamma",
|
||||
"meta": "cmd",
|
||||
"score": 0.04801549269801977
|
||||
}, {
|
||||
"caption": "\\Theta",
|
||||
"snippet": "\\Theta",
|
||||
"meta": "cmd",
|
||||
"score": 0.038090902146599444
|
||||
}, {
|
||||
"caption": "\\Lambda",
|
||||
"snippet": "\\Lambda",
|
||||
"meta": "cmd",
|
||||
"score": 0.032206594305977686
|
||||
}, {
|
||||
"caption": "\\Xi",
|
||||
"snippet": "\\Xi",
|
||||
"meta": "cmd",
|
||||
"score": 0.01060997225400494
|
||||
}, {
|
||||
"caption": "\\Pi",
|
||||
"snippet": "\\Pi",
|
||||
"meta": "cmd",
|
||||
"score": 0.021264671817473237
|
||||
}, {
|
||||
"caption": "\\Sigma",
|
||||
"snippet": "\\Sigma",
|
||||
"meta": "cmd",
|
||||
"score": 0.05769642802079917
|
||||
}, {
|
||||
"caption": "\\Upsilon",
|
||||
"snippet": "\\Upsilon",
|
||||
"meta": "cmd",
|
||||
"score": 0.00032875192955749566
|
||||
}, {
|
||||
"caption": "\\Phi",
|
||||
"snippet": "\\Phi",
|
||||
"meta": "cmd",
|
||||
"score": 0.0538724950042562
|
||||
}, {
|
||||
"caption": "\\Psi",
|
||||
"snippet": "\\Psi",
|
||||
"meta": "cmd",
|
||||
"score": 0.03056589143021648
|
||||
}, {
|
||||
"caption": "\\Omega",
|
||||
"snippet": "\\Omega",
|
||||
"meta": "cmd",
|
||||
"score": 0.09490387997853639
|
||||
}]
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
define [
|
||||
"ace/ace"
|
||||
], () ->
|
||||
Range = ace.require("ace/range").Range
|
||||
|
||||
getLastCommandFragment = (lineUpToCursor) ->
|
||||
if m = lineUpToCursor.match(/(\\[^\\]+)$/)
|
||||
return m[1]
|
||||
else
|
||||
return null
|
||||
|
||||
class MetadataManager
|
||||
constructor: (@$scope, @editor, @element, @Metadata) ->
|
||||
@debouncer = {} # DocId => Timeout
|
||||
|
||||
onChange = (change) =>
|
||||
if change.remote
|
||||
return
|
||||
if change.action not in ['remove', 'insert']
|
||||
return
|
||||
cursorPosition = @editor.getCursorPosition()
|
||||
end = change.end
|
||||
range = new Range(end.row, 0, end.row, end.column)
|
||||
lineUpToCursor = @editor.getSession().getTextRange range
|
||||
if lineUpToCursor.trim() == '%' or lineUpToCursor.slice(0, 1) == '\\'
|
||||
range = new Range(end.row, 0, end.row, end.column + 80)
|
||||
lineUpToCursor = @editor.getSession().getTextRange range
|
||||
commandFragment = getLastCommandFragment lineUpToCursor
|
||||
|
||||
linesContainPackage = _.any(
|
||||
change.lines,
|
||||
(line) -> line.match(/^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/)
|
||||
)
|
||||
linesContainReqPackage = _.any(
|
||||
change.lines,
|
||||
(line) -> line.match(/^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/)
|
||||
)
|
||||
linesContainLabel = _.any(
|
||||
change.lines,
|
||||
(line) -> line.match(/\\label{(.{0,80}?)}/)
|
||||
)
|
||||
linesContainMeta =
|
||||
linesContainPackage or
|
||||
linesContainLabel or
|
||||
linesContainReqPackage
|
||||
|
||||
lastCommandFragmentIsLabel = commandFragment?.slice(0, 7) == '\\label{'
|
||||
lastCommandFragmentIsPackage = commandFragment?.slice(0, 11) == '\\usepackage'
|
||||
lastCommandFragmentIsReqPack = commandFragment?.slice(0, 15) == '\\RequirePackage'
|
||||
lastCommandFragmentIsMeta =
|
||||
lastCommandFragmentIsPackage or
|
||||
lastCommandFragmentIsLabel or
|
||||
lastCommandFragmentIsReqPack
|
||||
|
||||
if linesContainMeta or lastCommandFragmentIsMeta
|
||||
@scheduleLoadCurrentDocMetaFromServer()
|
||||
|
||||
@editor.on "changeSession", (e) =>
|
||||
e.oldSession.off "change", onChange
|
||||
e.session.on "change", onChange
|
||||
|
||||
|
||||
loadDocMetaFromServer: (docId) ->
|
||||
@Metadata.loadDocMetaFromServer docId
|
||||
|
||||
scheduleLoadCurrentDocMetaFromServer: () ->
|
||||
# De-bounce loading labels with a timeout
|
||||
currentDocId = @$scope.docId
|
||||
existingTimeout = @debouncer[currentDocId]
|
||||
if existingTimeout?
|
||||
clearTimeout(existingTimeout)
|
||||
delete @debouncer[currentDocId]
|
||||
@debouncer[currentDocId] = setTimeout(
|
||||
() =>
|
||||
@loadDocMetaFromServer currentDocId
|
||||
delete @debouncer[currentDocId]
|
||||
, 1000
|
||||
, this
|
||||
)
|
||||
|
||||
getAllLabels: () ->
|
||||
@Metadata.getAllLabels()
|
||||
|
||||
getAllPackages: () ->
|
||||
@Metadata.getAllPackages()
|
||||
@@ -13,15 +13,15 @@ define [
|
||||
@loadRootFolder()
|
||||
@loadDeletedDocs()
|
||||
@$scope.$emit "file-tree:initialized"
|
||||
|
||||
|
||||
@$scope.$watch "rootFolder", (rootFolder) =>
|
||||
if rootFolder?
|
||||
@recalculateDocList()
|
||||
|
||||
@_bindToSocketEvents()
|
||||
|
||||
|
||||
@$scope.multiSelectedCount = 0
|
||||
|
||||
|
||||
$(document).on "click", =>
|
||||
@clearMultiSelectedEntities()
|
||||
$scope.$digest()
|
||||
@@ -46,7 +46,7 @@ define [
|
||||
type: "file"
|
||||
}
|
||||
@recalculateDocList()
|
||||
|
||||
|
||||
@ide.socket.on "reciveNewFolder", (parent_folder_id, folder) =>
|
||||
parent_folder = @findEntityById(parent_folder_id) or @$scope.rootFolder
|
||||
@$scope.$apply () =>
|
||||
@@ -85,25 +85,25 @@ define [
|
||||
@ide.fileTreeManager.forEachEntity (entity) ->
|
||||
entity.selected = false
|
||||
entity.selected = true
|
||||
|
||||
|
||||
toggleMultiSelectEntity: (entity) ->
|
||||
entity.multiSelected = !entity.multiSelected
|
||||
@$scope.multiSelectedCount = @multiSelectedCount()
|
||||
|
||||
|
||||
multiSelectedCount: () ->
|
||||
count = 0
|
||||
@forEachEntity (entity) ->
|
||||
if entity.multiSelected
|
||||
count++
|
||||
return count
|
||||
|
||||
|
||||
getMultiSelectedEntities: () ->
|
||||
entities = []
|
||||
@forEachEntity (e) ->
|
||||
if e.multiSelected
|
||||
entities.push e
|
||||
return entities
|
||||
|
||||
|
||||
getMultiSelectedEntityChildNodes: () ->
|
||||
entities = @getMultiSelectedEntities()
|
||||
paths = {}
|
||||
@@ -125,13 +125,13 @@ define [
|
||||
if !prefixes[path]?
|
||||
child_entities.push entity
|
||||
return child_entities
|
||||
|
||||
|
||||
clearMultiSelectedEntities: () ->
|
||||
return if @$scope.multiSelectedCount == 0 # Be efficient, this is called a lot on 'click'
|
||||
@forEachEntity (entity) ->
|
||||
entity.multiSelected = false
|
||||
@$scope.multiSelectedCount = 0
|
||||
|
||||
|
||||
multiSelectSelectedEntity: () ->
|
||||
@findSelectedEntity()?.multiSelected = true
|
||||
|
||||
@@ -140,7 +140,7 @@ define [
|
||||
return false if !folder?
|
||||
entity = @_findEntityByPathInFolder(folder, name)
|
||||
return entity?
|
||||
|
||||
|
||||
findSelectedEntity: () ->
|
||||
selected = null
|
||||
@forEachEntity (entity) ->
|
||||
@@ -178,7 +178,7 @@ define [
|
||||
parts = path.split("/")
|
||||
name = parts.shift()
|
||||
rest = parts.join("/")
|
||||
|
||||
|
||||
if name == "."
|
||||
return @_findEntityByPathInFolder(folder, rest)
|
||||
|
||||
@@ -268,7 +268,7 @@ define [
|
||||
type: "doc"
|
||||
deleted: true
|
||||
}
|
||||
|
||||
|
||||
recalculateDocList: () ->
|
||||
@$scope.docs = []
|
||||
@forEachEntity (entity, parentFolder, path) =>
|
||||
@@ -287,7 +287,7 @@ define [
|
||||
return -1
|
||||
else
|
||||
return 1
|
||||
|
||||
|
||||
getEntityPath: (entity) ->
|
||||
@_getEntityPathInFolder @$scope.rootFolder, entity
|
||||
|
||||
@@ -349,7 +349,7 @@ define [
|
||||
}
|
||||
|
||||
deleteEntity: (entity, callback = (error) ->) ->
|
||||
# We'll wait for the socket.io notification to
|
||||
# We'll wait for the socket.io notification to
|
||||
# delete from scope.
|
||||
return @ide.queuedHttp {
|
||||
method: "DELETE"
|
||||
@@ -367,7 +367,7 @@ define [
|
||||
folder_id: parent_folder.id
|
||||
_csrf: window.csrfToken
|
||||
}
|
||||
|
||||
|
||||
_isChildFolder: (parent_folder, child_folder) ->
|
||||
parent_path = @getEntityPath(parent_folder) or "" # null if root folder
|
||||
child_path = @getEntityPath(child_folder) or "" # null if root folder
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
|
||||
App.factory 'files', (ide) ->
|
||||
|
||||
Files =
|
||||
getTeXFiles: () ->
|
||||
texFiles = []
|
||||
ide.fileTreeManager.forEachEntity (entity, folder, path) ->
|
||||
if entity.type == 'doc' && entity?.name?.match?(/.*\.(tex|txt|md)/)
|
||||
cloned = _.clone(entity)
|
||||
cloned.path = path
|
||||
texFiles.push cloned
|
||||
return texFiles
|
||||
|
||||
return Files
|
||||
@@ -1,5 +1,4 @@
|
||||
define [
|
||||
], () ->
|
||||
define [], () ->
|
||||
|
||||
class LabelsManager
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
define [], () ->
|
||||
|
||||
class MetadataManager
|
||||
|
||||
constructor: (@ide, @$scope, @metadata) ->
|
||||
|
||||
@ide.socket.on 'broadcastDocMeta', (data) =>
|
||||
@metadata.onBroadcastDocMeta data
|
||||
@$scope.$on 'entity:deleted', @metadata.onEntityDeleted
|
||||
@$scope.$on 'file:upload:complete', @metadata.fileUploadComplete
|
||||
|
||||
loadProjectMetaFromServer: () ->
|
||||
@metadata.loadProjectMetaFromServer()
|
||||
@@ -0,0 +1,49 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
|
||||
App.factory 'metadata', ($http, ide) ->
|
||||
|
||||
state = {documents: {}}
|
||||
|
||||
metadata = {state: state}
|
||||
|
||||
metadata.onBroadcastDocMeta = (data) ->
|
||||
if data.docId? and data.meta?
|
||||
state.documents[data.docId] = data.meta
|
||||
|
||||
metadata.onEntityDeleted = (e, entity) ->
|
||||
if entity.type == 'doc'
|
||||
delete state.documents[entity.id]
|
||||
|
||||
metadata.onFileUploadComplete = (e, upload) ->
|
||||
if upload.entity_type == 'doc'
|
||||
metadata.loadDocMetaFromServer upload.entity_id
|
||||
|
||||
metadata.getAllLabels = () ->
|
||||
_.flatten(meta.labels for docId, meta of state.documents)
|
||||
|
||||
metadata.getAllPackages = () ->
|
||||
packageCommandMapping = {}
|
||||
for _docId, meta of state.documents
|
||||
for packageName, commandSnippets of meta.packages
|
||||
packageCommandMapping[packageName] = commandSnippets
|
||||
return packageCommandMapping
|
||||
|
||||
metadata.loadProjectMetaFromServer = () ->
|
||||
$http
|
||||
.get("/project/#{window.project_id}/metadata")
|
||||
.then (response) ->
|
||||
{ data } = response
|
||||
if data.projectMeta
|
||||
for docId, docMeta of data.projectMeta
|
||||
state.documents[docId] = docMeta
|
||||
|
||||
metadata.loadDocMetaFromServer = (docId) ->
|
||||
$http
|
||||
.post(
|
||||
"/project/#{window.project_id}/doc/#{docId}/metadata",
|
||||
{_csrf: window.csrfToken}
|
||||
)
|
||||
|
||||
return metadata
|
||||
@@ -1,12 +1,10 @@
|
||||
define [
|
||||
"moment"
|
||||
"libs/angular-autocomplete/angular-autocomplete"
|
||||
"libs/ui-bootstrap"
|
||||
"libs/ng-context-menu-0.1.4"
|
||||
"libs/underscore-1.3.3"
|
||||
"libs/algolia-2.5.2"
|
||||
"libs/jquery.storage"
|
||||
"libs/fineuploader"
|
||||
"libs/angular-sanitize-1.2.17"
|
||||
"libs/angular-cookie"
|
||||
"libs/passfield"
|
||||
|
||||
@@ -4,7 +4,7 @@ ace.define("ace/mode/latex_highlight_rules",["require","exports","module","ace/l
|
||||
var oop = require("../lib/oop");
|
||||
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
|
||||
|
||||
var LatexHighlightRules = function() {
|
||||
var LatexHighlightRules = function() {
|
||||
|
||||
this.$rules = {
|
||||
"start" : [{
|
||||
@@ -47,9 +47,9 @@ var LatexHighlightRules = function() {
|
||||
token : "constant.character.escape",
|
||||
regex : "\\\\(?:[^a-zA-Z]|[a-zA-Z]+)"
|
||||
}, {
|
||||
token : "error",
|
||||
regex : "^\\s*$",
|
||||
next : "start"
|
||||
token : "error",
|
||||
regex : "^\\s*$",
|
||||
next : "start"
|
||||
}, {
|
||||
defaultToken : "string"
|
||||
}]
|
||||
@@ -76,16 +76,16 @@ oop.inherits(FoldMode, BaseFoldMode);
|
||||
|
||||
(function() {
|
||||
|
||||
this.foldingStartMarker = /^\s*\\(begin)|(section|subsection|paragraph)\b|{\s*$/;
|
||||
this.foldingStartMarker = /^\s*\\(begin|section|subsection|subsubsection|paragraph|part|chapter)\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])
|
||||
if (match[1] === "begin")
|
||||
return this.latexBlock(session, row, match[0].length - 1);
|
||||
if (match[2])
|
||||
else if (match[1])
|
||||
return this.latexSection(session, row, match[0].length - 1);
|
||||
|
||||
return this.openingBracketBlock(session, "{", row, match.index);
|
||||
@@ -153,7 +153,7 @@ oop.inherits(FoldMode, BaseFoldMode);
|
||||
};
|
||||
|
||||
this.latexSection = function(session, row, column) {
|
||||
var keywords = ["\\subsection", "\\section", "\\begin", "\\end", "\\paragraph"];
|
||||
var keywords = ["\\subsection", "\\section", "\\begin", "\\end", "\\paragraph", "\\part", "\\chapter"];
|
||||
|
||||
var stream = new TokenIterator(session, row, column);
|
||||
var token = stream.getCurrentToken();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,11 +4,11 @@
|
||||
*
|
||||
* Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
|
||||
*/
|
||||
.qq-uploader {
|
||||
.qq-uploader-selector {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.qq-uploader {
|
||||
.qq-uploader-selector {
|
||||
text-align: center;
|
||||
.drag-here {
|
||||
border: 1px dashed #666;
|
||||
@@ -18,7 +18,7 @@
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
/*.qq-upload-button {
|
||||
/*.qq-upload-button-selector {
|
||||
display: block;
|
||||
width: 105px;
|
||||
padding: 7px 0;
|
||||
@@ -27,13 +27,13 @@
|
||||
border-bottom: 1px solid #DDD;
|
||||
color: #FFF;
|
||||
}
|
||||
.qq-upload-button-hover {
|
||||
.qq-upload-button-hover-selector {
|
||||
background: #CC0000;
|
||||
}
|
||||
.qq-upload-button-focus {
|
||||
.qq-upload-button-focus-selector {
|
||||
outline: 1px dotted #000000;
|
||||
}*/
|
||||
.qq-upload-drop-area, .qq-upload-extra-drop-area {
|
||||
.qq-upload-drop-area-selector, .qq-upload-extra-drop-area-selector {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -44,7 +44,7 @@
|
||||
background: @orange;
|
||||
text-align: center;
|
||||
}
|
||||
.qq-upload-drop-area span {
|
||||
.qq-upload-drop-area-selector span {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@@ -53,7 +53,7 @@
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
}
|
||||
.qq-upload-extra-drop-area {
|
||||
.qq-upload-extra-drop-area-selector {
|
||||
position: relative;
|
||||
margin-top: 50px;
|
||||
font-size: 16px;
|
||||
@@ -61,15 +61,15 @@
|
||||
height: 20px;
|
||||
min-height: 40px;
|
||||
}
|
||||
.qq-upload-drop-area-active {
|
||||
.qq-upload-drop-area-active-selector {
|
||||
background: darken(@orange, 15%);
|
||||
}
|
||||
.qq-upload-list {
|
||||
.qq-upload-list-selector {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.qq-upload-list li {
|
||||
.qq-upload-list-selector li {
|
||||
margin: 0;
|
||||
margin-top: 10px;
|
||||
padding: 9px;
|
||||
@@ -77,74 +77,74 @@
|
||||
font-size: 16px;
|
||||
background-color: @gray-lightest;
|
||||
}
|
||||
.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete {
|
||||
.qq-upload-file-selector, .qq-upload-spinner-selector, .qq-upload-size-selector, .qq-upload-cancel-selector, .qq-upload-retry-selector, .qq-upload-failed-text-selector, .qq-upload-finished-selector, .qq-upload-delete-selector {
|
||||
margin-right: 12px;
|
||||
}
|
||||
.qq-upload-file {
|
||||
.qq-upload-file-selector {
|
||||
}
|
||||
.qq-upload-spinner {
|
||||
.qq-upload-spinner-selector {
|
||||
display: inline-block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
.qq-drop-processing {
|
||||
.qq-drop-processing-selector {
|
||||
display: none;
|
||||
}
|
||||
.qq-drop-processing-spinner {
|
||||
.qq-drop-processing-spinner-selector {
|
||||
display: inline-block;
|
||||
background: url("processing.gif");
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
.qq-upload-finished {
|
||||
.qq-upload-finished-selector {
|
||||
display:none;
|
||||
width:15px;
|
||||
height:15px;
|
||||
vertical-align:text-bottom;
|
||||
}
|
||||
.qq-upload-retry, .qq-upload-delete {
|
||||
.qq-upload-retry-selector, .qq-upload-delete-selector {
|
||||
display: none;
|
||||
// color: #000000;
|
||||
}
|
||||
.qq-upload-cancel, .qq-upload-delete {
|
||||
.qq-upload-cancel-selector, .qq-upload-delete-selector {
|
||||
// color: #000000;
|
||||
}
|
||||
.qq-upload-retryable .qq-upload-retry {
|
||||
.qq-upload-retryable-selector .qq-upload-retry-selector {
|
||||
display: inline;
|
||||
}
|
||||
.qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete {
|
||||
.qq-upload-size-selector, .qq-upload-cancel-selector, .qq-upload-retry-selector, .qq-upload-delete-selector {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.qq-upload-failed-text {
|
||||
.qq-upload-failed-text-selector {
|
||||
display: none;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
.qq-upload-failed-icon {
|
||||
.qq-upload-failed-icon-selector {
|
||||
display:none;
|
||||
width:15px;
|
||||
height:15px;
|
||||
vertical-align:text-bottom;
|
||||
}
|
||||
.qq-upload-fail .qq-upload-failed-text {
|
||||
.qq-upload-fail-selector .qq-upload-failed-text-selector {
|
||||
display: inline;
|
||||
}
|
||||
.qq-upload-retrying .qq-upload-failed-text {
|
||||
.qq-upload-retrying-selector .qq-upload-failed-text-selector {
|
||||
display: inline;
|
||||
color: #D60000;
|
||||
}
|
||||
.qq-upload-list li.qq-upload-success {
|
||||
.qq-upload-list-selector li.qq-upload-success-selector {
|
||||
background-color: @green;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.qq-upload-list li.qq-upload-fail {
|
||||
.qq-upload-list-selector li.qq-upload-fail-selector {
|
||||
background-color: @red;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.qq-progress-bar {
|
||||
.qq-progress-bar-selector {
|
||||
width: 0%;
|
||||
height: @line-height-computed;
|
||||
margin-bottom: @line-height-computed / 2;
|
||||
@@ -156,5 +156,9 @@
|
||||
.box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));
|
||||
.transition(width .6s ease);
|
||||
border-radius: @border-radius-base 0 0 @border-radius-base;
|
||||
display: none;
|
||||
}
|
||||
a.qq-btn {
|
||||
&:hover {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Settings = require('settings-sharelatex')
|
||||
redis = require('redis-sharelatex')
|
||||
logger = require("logger-sharelatex")
|
||||
Async = require('async')
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
BASE_URL = "http://localhost:3000"
|
||||
BASE_URL = "http://#{process.env["HTTP_TEST_HOST"] or "localhost"}:3000"
|
||||
module.exports = require("request").defaults({
|
||||
baseUrl: BASE_URL,
|
||||
followRedirect: false
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
chai = require('chai')
|
||||
chai.should()
|
||||
expect = chai.expect
|
||||
sinon = require("sinon")
|
||||
modulePath = "../../../../app/js/Features/Labels/LabelsController"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
|
||||
|
||||
describe 'LabelsController', ->
|
||||
beforeEach ->
|
||||
@projectId = 'somekindofid'
|
||||
@EditorRealTimeController = {
|
||||
emitToRoom: sinon.stub()
|
||||
}
|
||||
@LabelsHandler = {
|
||||
getAllLabelsForProject: sinon.stub()
|
||||
getLabelsForDoc: sinon.stub()
|
||||
}
|
||||
@LabelsController = SandboxedModule.require modulePath, requires:
|
||||
'logger-sharelatex': {log: sinon.stub(), err: sinon.stub()}
|
||||
'../Editor/EditorRealTimeController': @EditorRealTimeController
|
||||
'./LabelsHandler': @LabelsHandler
|
||||
|
||||
describe 'getAllLabels', ->
|
||||
beforeEach ->
|
||||
@fakeLabels = {'somedoc': ['a_label']}
|
||||
@LabelsHandler.getAllLabelsForProject = sinon.stub().callsArgWith(1, null, @fakeLabels)
|
||||
@req = {params: {project_id: @projectId}}
|
||||
@res = {json: sinon.stub()}
|
||||
@next = sinon.stub()
|
||||
|
||||
it 'should call LabelsHandler.getAllLabelsForProject', () ->
|
||||
@LabelsController.getAllLabels(@req, @res, @next)
|
||||
@LabelsHandler.getAllLabelsForProject.callCount.should.equal 1
|
||||
@LabelsHandler.getAllLabelsForProject.calledWith(@projectId).should.equal true
|
||||
|
||||
it 'should call not call next with an error', () ->
|
||||
@LabelsController.getAllLabels(@req, @res, @next)
|
||||
@next.callCount.should.equal 0
|
||||
|
||||
it 'should send a json response', () ->
|
||||
@LabelsController.getAllLabels(@req, @res, @next)
|
||||
@res.json.callCount.should.equal 1
|
||||
expect(@res.json.lastCall.args[0]).to.have.all.keys ['projectId', 'projectLabels']
|
||||
|
||||
describe 'when LabelsHandler.getAllLabelsForProject produces an error', ->
|
||||
beforeEach ->
|
||||
@LabelsHandler.getAllLabelsForProject = sinon.stub().callsArgWith(1, new Error('woops'))
|
||||
@req = {params: {project_id: @projectId}}
|
||||
@res = {json: sinon.stub()}
|
||||
@next = sinon.stub()
|
||||
|
||||
it 'should call LabelsHandler.getAllLabelsForProject', () ->
|
||||
@LabelsController.getAllLabels(@req, @res, @next)
|
||||
@LabelsHandler.getAllLabelsForProject.callCount.should.equal 1
|
||||
@LabelsHandler.getAllLabelsForProject.calledWith(@projectId).should.equal true
|
||||
|
||||
it 'should call next with an error', ->
|
||||
@LabelsController.getAllLabels(@req, @res, @next)
|
||||
@next.callCount.should.equal 1
|
||||
expect(@next.lastCall.args[0]).to.be.instanceof Error
|
||||
|
||||
it 'should not send a json response', ->
|
||||
@LabelsController.getAllLabels(@req, @res, @next)
|
||||
@res.json.callCount.should.equal 0
|
||||
|
||||
describe 'broadcastLabelsForDoc', ->
|
||||
beforeEach ->
|
||||
@LabelsHandler.getLabelsForDoc = sinon.stub().callsArgWith(2, null, @fakeLabels)
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
@docId = 'somedoc'
|
||||
@req = {params: {project_id: @projectId, doc_id: @docId}}
|
||||
@res = {sendStatus: sinon.stub()}
|
||||
@next = sinon.stub()
|
||||
|
||||
it 'should call LabelsHandler.getLabelsForDoc', () ->
|
||||
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
|
||||
@LabelsHandler.getLabelsForDoc.callCount.should.equal 1
|
||||
@LabelsHandler.getLabelsForDoc.calledWith(@projectId).should.equal true
|
||||
|
||||
it 'should call not call next with an error', () ->
|
||||
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
|
||||
@next.callCount.should.equal 0
|
||||
|
||||
it 'should send a success response', () ->
|
||||
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
|
||||
@res.sendStatus.callCount.should.equal 1
|
||||
@res.sendStatus.calledWith(200).should.equal true
|
||||
|
||||
it 'should emit a message to room', () ->
|
||||
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
|
||||
@EditorRealTimeController.emitToRoom.callCount.should.equal 1
|
||||
lastCall = @EditorRealTimeController.emitToRoom.lastCall
|
||||
expect(lastCall.args[0]).to.equal @projectId
|
||||
expect(lastCall.args[1]).to.equal 'broadcastDocLabels'
|
||||
expect(lastCall.args[2]).to.have.all.keys ['docId', 'labels']
|
||||
|
||||
describe 'when LabelsHandler.getLabelsForDoc produces an error', ->
|
||||
beforeEach ->
|
||||
@LabelsHandler.getLabelsForDoc = sinon.stub().callsArgWith(2, new Error('woops'))
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
@docId = 'somedoc'
|
||||
@req = {params: {project_id: @projectId, doc_id: @docId}}
|
||||
@res = {json: sinon.stub()}
|
||||
@next = sinon.stub()
|
||||
|
||||
it 'should call LabelsHandler.getLabelsForDoc', () ->
|
||||
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
|
||||
@LabelsHandler.getLabelsForDoc.callCount.should.equal 1
|
||||
@LabelsHandler.getLabelsForDoc.calledWith(@projectId).should.equal true
|
||||
|
||||
it 'should call next with an error', ->
|
||||
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
|
||||
@next.callCount.should.equal 1
|
||||
expect(@next.lastCall.args[0]).to.be.instanceof Error
|
||||
|
||||
it 'should not send a json response', ->
|
||||
@LabelsController.broadcastLabelsForDoc(@req, @res, @next)
|
||||
@res.json.callCount.should.equal 0
|
||||
@@ -1,134 +0,0 @@
|
||||
chai = require('chai')
|
||||
chai.should()
|
||||
expect = chai.expect
|
||||
sinon = require("sinon")
|
||||
modulePath = "../../../../app/js/Features/Labels/LabelsHandler"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
|
||||
|
||||
describe 'LabelsHandler', ->
|
||||
beforeEach ->
|
||||
@projectId = 'someprojectid'
|
||||
@docId = 'somedocid'
|
||||
@ProjectEntityHandler = {
|
||||
getAllDocs: sinon.stub()
|
||||
getDoc: sinon.stub()
|
||||
}
|
||||
@DocumentUpdaterHandler = {
|
||||
flushDocToMongo: sinon.stub()
|
||||
}
|
||||
@LabelsHandler = SandboxedModule.require modulePath, requires:
|
||||
'../Project/ProjectEntityHandler': @ProjectEntityHandler
|
||||
'../DocumentUpdater/DocumentUpdaterHandler': @DocumentUpdaterHandler
|
||||
|
||||
describe 'extractLabelsFromDoc', ->
|
||||
beforeEach ->
|
||||
@lines = [
|
||||
'one',
|
||||
'two',
|
||||
'three \\label{aaa}',
|
||||
'four five',
|
||||
'\\label{bbb}',
|
||||
'six seven'
|
||||
]
|
||||
|
||||
it 'should extract all the labels', ->
|
||||
docLabels = @LabelsHandler.extractLabelsFromDoc @lines
|
||||
expect(docLabels).to.deep.equal ['aaa', 'bbb']
|
||||
|
||||
describe 'extractLabelsFromProjectDocs', ->
|
||||
beforeEach ->
|
||||
@docs = {
|
||||
'doc_one': {
|
||||
_id: 'id_one',
|
||||
lines: ['one', '\\label{aaa} two', 'three']
|
||||
},
|
||||
'doc_two': {
|
||||
_id: 'id_two',
|
||||
lines: ['four']
|
||||
},
|
||||
'doc_three': {
|
||||
_id: 'id_three',
|
||||
lines: ['\\label{bbb}', 'five six', 'seven eight \\label{ccc} nine']
|
||||
}
|
||||
}
|
||||
|
||||
it 'should extract all the labels', ->
|
||||
projectLabels = @LabelsHandler.extractLabelsFromProjectDocs @docs
|
||||
expect(projectLabels).to.deep.equal {
|
||||
'id_one': ['aaa'],
|
||||
'id_two': [],
|
||||
'id_three': ['bbb', 'ccc']
|
||||
}
|
||||
|
||||
describe 'getLabelsForDoc', ->
|
||||
beforeEach ->
|
||||
@fakeLines = ['one', '\\label{aaa}', 'two']
|
||||
@fakeLabels = ['aaa']
|
||||
@DocumentUpdaterHandler.flushDocToMongo = sinon.stub().callsArgWith(2, null)
|
||||
@ProjectEntityHandler.getDoc = sinon.stub().callsArgWith(2, null, @fakeLines)
|
||||
@LabelsHandler.extractLabelsFromDoc = sinon.stub().returns(@fakeLabels)
|
||||
@call = (callback) =>
|
||||
@LabelsHandler.getLabelsForDoc @projectId, @docId, callback
|
||||
|
||||
it 'should not produce an error', (done) ->
|
||||
@call (err, docLabels) =>
|
||||
expect(err).to.equal null
|
||||
done()
|
||||
|
||||
it 'should produce docLabels', (done) ->
|
||||
@call (err, docLabels) =>
|
||||
expect(docLabels).to.equal @fakeLabels
|
||||
done()
|
||||
|
||||
it 'should call flushDocToMongo', (done) ->
|
||||
@call (err, docLabels) =>
|
||||
@DocumentUpdaterHandler.flushDocToMongo.callCount.should.equal 1
|
||||
@DocumentUpdaterHandler.flushDocToMongo.calledWith(@projectId, @docId).should.equal true
|
||||
done()
|
||||
|
||||
it 'should call getDoc', (done) ->
|
||||
@call (err, docLabels) =>
|
||||
@ProjectEntityHandler.getDoc.callCount.should.equal 1
|
||||
@ProjectEntityHandler.getDoc.calledWith(@projectId, @docId).should.equal true
|
||||
done()
|
||||
|
||||
it 'should call extractLabelsFromDoc', (done) ->
|
||||
@call (err, docLabels) =>
|
||||
@LabelsHandler.extractLabelsFromDoc.callCount.should.equal 1
|
||||
@LabelsHandler.extractLabelsFromDoc.calledWith(@fakeLines).should.equal true
|
||||
done()
|
||||
|
||||
describe 'getAllLabelsForProject', ->
|
||||
beforeEach ->
|
||||
@fakeDocs = {
|
||||
'doc_one': {lines: ['\\label{aaa}']}
|
||||
}
|
||||
@fakeLabels = ['aaa']
|
||||
@DocumentUpdaterHandler.flushProjectToMongo = sinon.stub().callsArgWith(1, null)
|
||||
@ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @fakeDocs)
|
||||
@LabelsHandler.extractLabelsFromProjectDocs = sinon.stub().returns(@fakeLabels)
|
||||
@call = (callback) =>
|
||||
@LabelsHandler.getAllLabelsForProject @projectId, callback
|
||||
|
||||
it 'should not produce an error', (done) ->
|
||||
@call (err, projectLabels) =>
|
||||
expect(err).to.equal null
|
||||
done()
|
||||
|
||||
it 'should produce projectLabels', (done) ->
|
||||
@call (err, projectLabels) =>
|
||||
expect(projectLabels).to.equal @fakeLabels
|
||||
done()
|
||||
|
||||
it 'should call getAllDocs', (done) ->
|
||||
@call (err, projectLabels) =>
|
||||
@ProjectEntityHandler.getAllDocs.callCount.should.equal 1
|
||||
@ProjectEntityHandler.getAllDocs.calledWith(@projectId).should.equal true
|
||||
done()
|
||||
|
||||
it 'should call extractLabelsFromDoc', (done) ->
|
||||
@call (err, docLabels) =>
|
||||
@LabelsHandler.extractLabelsFromProjectDocs.callCount.should.equal 1
|
||||
@LabelsHandler.extractLabelsFromProjectDocs.calledWith(@fakeDocs).should.equal true
|
||||
done()
|
||||
@@ -0,0 +1,119 @@
|
||||
chai = require('chai')
|
||||
chai.should()
|
||||
expect = chai.expect
|
||||
sinon = require("sinon")
|
||||
modulePath = "../../../../app/js/Features/Metadata/MetaController"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
|
||||
|
||||
describe 'MetaController', ->
|
||||
beforeEach ->
|
||||
@projectId = 'somekindofid'
|
||||
@EditorRealTimeController = {
|
||||
emitToRoom: sinon.stub()
|
||||
}
|
||||
@MetaHandler = {
|
||||
getAllMetaForProject: sinon.stub()
|
||||
getMetaForDoc: sinon.stub()
|
||||
}
|
||||
@MetadataController = SandboxedModule.require modulePath, requires:
|
||||
'logger-sharelatex': {log: sinon.stub(), err: sinon.stub()}
|
||||
'../Editor/EditorRealTimeController': @EditorRealTimeController
|
||||
'./MetaHandler': @MetaHandler
|
||||
|
||||
describe 'getMetadata', ->
|
||||
beforeEach ->
|
||||
@fakeLabels = {'somedoc': ['a_label']}
|
||||
@MetaHandler.getAllMetaForProject = sinon.stub().callsArgWith(1, null, @fakeLabels)
|
||||
@req = {params: {project_id: @projectId}}
|
||||
@res = {json: sinon.stub()}
|
||||
@next = sinon.stub()
|
||||
|
||||
it 'should call MetaHandler.getAllMetaForProject', () ->
|
||||
@MetadataController.getMetadata(@req, @res, @next)
|
||||
@MetaHandler.getAllMetaForProject.callCount.should.equal 1
|
||||
@MetaHandler.getAllMetaForProject.calledWith(@projectId).should.equal true
|
||||
|
||||
it 'should call not call next with an error', () ->
|
||||
@MetadataController.getMetadata(@req, @res, @next)
|
||||
@next.callCount.should.equal 0
|
||||
|
||||
it 'should send a json response', () ->
|
||||
@MetadataController.getMetadata(@req, @res, @next)
|
||||
@res.json.callCount.should.equal 1
|
||||
expect(@res.json.lastCall.args[0]).to.have.all.keys ['projectId', 'projectMeta']
|
||||
|
||||
describe 'when MetaHandler.getAllMetaForProject produces an error', ->
|
||||
beforeEach ->
|
||||
@MetaHandler.getAllMetaForProject = sinon.stub().callsArgWith(1, new Error('woops'))
|
||||
@req = {params: {project_id: @projectId}}
|
||||
@res = {json: sinon.stub()}
|
||||
@next = sinon.stub()
|
||||
|
||||
it 'should call MetaHandler.getAllMetaForProject', () ->
|
||||
@MetadataController.getMetadata(@req, @res, @next)
|
||||
@MetaHandler.getAllMetaForProject.callCount.should.equal 1
|
||||
@MetaHandler.getAllMetaForProject.calledWith(@projectId).should.equal true
|
||||
|
||||
it 'should call next with an error', ->
|
||||
@MetadataController.getMetadata(@req, @res, @next)
|
||||
@next.callCount.should.equal 1
|
||||
expect(@next.lastCall.args[0]).to.be.instanceof Error
|
||||
|
||||
it 'should not send a json response', ->
|
||||
@MetadataController.getMetadata(@req, @res, @next)
|
||||
@res.json.callCount.should.equal 0
|
||||
|
||||
describe 'broadcastMetadataForDoc', ->
|
||||
beforeEach ->
|
||||
@MetaHandler.getMetaForDoc = sinon.stub().callsArgWith(2, null, @fakeLabels)
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
@docId = 'somedoc'
|
||||
@req = {params: {project_id: @projectId, doc_id: @docId}}
|
||||
@res = {sendStatus: sinon.stub()}
|
||||
@next = sinon.stub()
|
||||
|
||||
it 'should call MetaHandler.getMetaForDoc', () ->
|
||||
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
|
||||
@MetaHandler.getMetaForDoc.callCount.should.equal 1
|
||||
@MetaHandler.getMetaForDoc.calledWith(@projectId).should.equal true
|
||||
|
||||
it 'should call not call next with an error', () ->
|
||||
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
|
||||
@next.callCount.should.equal 0
|
||||
|
||||
it 'should send a success response', () ->
|
||||
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
|
||||
@res.sendStatus.callCount.should.equal 1
|
||||
@res.sendStatus.calledWith(200).should.equal true
|
||||
|
||||
it 'should emit a message to room', () ->
|
||||
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
|
||||
@EditorRealTimeController.emitToRoom.callCount.should.equal 1
|
||||
lastCall = @EditorRealTimeController.emitToRoom.lastCall
|
||||
expect(lastCall.args[0]).to.equal @projectId
|
||||
expect(lastCall.args[1]).to.equal 'broadcastDocMeta'
|
||||
expect(lastCall.args[2]).to.have.all.keys ['docId', 'meta']
|
||||
|
||||
describe 'when MetaHandler.getMetaForDoc produces an error', ->
|
||||
beforeEach ->
|
||||
@MetaHandler.getMetaForDoc = sinon.stub().callsArgWith(2, new Error('woops'))
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
@docId = 'somedoc'
|
||||
@req = {params: {project_id: @projectId, doc_id: @docId}}
|
||||
@res = {json: sinon.stub()}
|
||||
@next = sinon.stub()
|
||||
|
||||
it 'should call MetaHandler.getMetaForDoc', () ->
|
||||
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
|
||||
@MetaHandler.getMetaForDoc.callCount.should.equal 1
|
||||
@MetaHandler.getMetaForDoc.calledWith(@projectId).should.equal true
|
||||
|
||||
it 'should call next with an error', ->
|
||||
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
|
||||
@next.callCount.should.equal 1
|
||||
expect(@next.lastCall.args[0]).to.be.instanceof Error
|
||||
|
||||
it 'should not send a json response', ->
|
||||
@MetadataController.broadcastMetadataForDoc(@req, @res, @next)
|
||||
@res.json.callCount.should.equal 0
|
||||
@@ -0,0 +1,243 @@
|
||||
chai = require('chai')
|
||||
chai.should()
|
||||
expect = chai.expect
|
||||
sinon = require("sinon")
|
||||
modulePath = "../../../../app/js/Features/Metadata/MetaHandler"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
|
||||
|
||||
describe 'MetaHandler', ->
|
||||
beforeEach ->
|
||||
@projectId = 'someprojectid'
|
||||
@docId = 'somedocid'
|
||||
@ProjectEntityHandler = {
|
||||
getAllDocs: sinon.stub()
|
||||
getDoc: sinon.stub()
|
||||
}
|
||||
@DocumentUpdaterHandler = {
|
||||
flushDocToMongo: sinon.stub()
|
||||
}
|
||||
@packageMapping =
|
||||
foo: [
|
||||
{
|
||||
caption: '\\bar'
|
||||
snippet: '\\bar'
|
||||
meta: 'foo-cmd'
|
||||
score: 12
|
||||
}, {
|
||||
caption: '\\bat[]{}'
|
||||
snippet: '\\bar[$1]{$2}'
|
||||
meta: 'foo-cmd'
|
||||
score: 10
|
||||
}
|
||||
],
|
||||
baz: [
|
||||
{
|
||||
caption: '\\longercommandtest{}'
|
||||
snippet: '\\longercommandtest{$1}'
|
||||
meta: 'baz-cmd'
|
||||
score: 50
|
||||
}
|
||||
]
|
||||
|
||||
@MetaHandler = SandboxedModule.require modulePath, requires:
|
||||
'../Project/ProjectEntityHandler': @ProjectEntityHandler
|
||||
'../DocumentUpdater/DocumentUpdaterHandler': @DocumentUpdaterHandler
|
||||
'./packageMapping': @packageMapping
|
||||
|
||||
describe 'extractMetaFromDoc', ->
|
||||
beforeEach ->
|
||||
@lines = [
|
||||
'\\usepackage{foo}'
|
||||
'\\usepackage{amsmath, booktabs}'
|
||||
'one'
|
||||
'two'
|
||||
'three \\label{aaa}'
|
||||
'four five'
|
||||
'\\label{bbb}'
|
||||
'six seven'
|
||||
]
|
||||
|
||||
it 'should extract all the labels and packages', ->
|
||||
docMeta = @MetaHandler.extractMetaFromDoc @lines
|
||||
expect(docMeta).to.deep.equal {
|
||||
labels: ['aaa', 'bbb']
|
||||
packages:
|
||||
foo: [
|
||||
{
|
||||
caption: '\\bar'
|
||||
snippet: '\\bar'
|
||||
meta: 'foo-cmd'
|
||||
score: 12
|
||||
}, {
|
||||
caption: '\\bat[]{}'
|
||||
snippet: '\\bar[$1]{$2}'
|
||||
meta: 'foo-cmd'
|
||||
score: 10
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
describe 'extractMetaFromProjectDocs', ->
|
||||
beforeEach ->
|
||||
@docs =
|
||||
'doc_one':
|
||||
_id: 'id_one'
|
||||
lines: ['one', '\\label{aaa} two', 'three']
|
||||
'doc_two':
|
||||
_id: 'id_two'
|
||||
lines: ['four']
|
||||
'doc_three':
|
||||
_id: 'id_three'
|
||||
lines: [
|
||||
'\\label{bbb}'
|
||||
'five six'
|
||||
'seven eight \\label{ccc} nine'
|
||||
]
|
||||
'doc_four':
|
||||
_id: 'id_four'
|
||||
lines: [
|
||||
'\\usepackage[width=\\textwidth]{baz}'
|
||||
'\\usepackage{amsmath}'
|
||||
]
|
||||
'doc_five':
|
||||
_id: 'id_five'
|
||||
lines: [
|
||||
'\\usepackage{foo,baz}'
|
||||
'\\usepackage[options=foo]{hello}'
|
||||
'some text'
|
||||
'\\section{this}\\label{sec:intro}'
|
||||
'In Section \\ref{sec:intro} we saw'
|
||||
'nothing'
|
||||
]
|
||||
|
||||
it 'should extract all metadata', ->
|
||||
projectMeta = @MetaHandler.extractMetaFromProjectDocs @docs
|
||||
expect(projectMeta).to.deep.equal {
|
||||
'id_one': {labels: ['aaa'], packages: {}}
|
||||
'id_two': {labels: [], packages: {}}
|
||||
'id_three': {labels: ['bbb', 'ccc'], packages: {}}
|
||||
'id_four':
|
||||
labels: []
|
||||
packages:
|
||||
baz: [{
|
||||
caption: '\\longercommandtest{}'
|
||||
snippet: '\\longercommandtest{$1}'
|
||||
meta: 'baz-cmd'
|
||||
score: 50}]
|
||||
'id_five':
|
||||
labels: ['sec:intro']
|
||||
packages:
|
||||
foo: [
|
||||
{
|
||||
caption: '\\bar'
|
||||
snippet: '\\bar'
|
||||
meta: 'foo-cmd'
|
||||
score: 12
|
||||
}, {
|
||||
caption: '\\bat[]{}'
|
||||
snippet: '\\bar[$1]{$2}'
|
||||
meta: 'foo-cmd'
|
||||
score: 10
|
||||
}
|
||||
]
|
||||
baz: [
|
||||
{
|
||||
caption: '\\longercommandtest{}'
|
||||
snippet: '\\longercommandtest{$1}'
|
||||
meta: 'baz-cmd'
|
||||
score: 50
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
describe 'getMetaForDoc', ->
|
||||
beforeEach ->
|
||||
@fakeLines = ['\\usepackage{abc}', 'one', '\\label{aaa}', 'two']
|
||||
@fakeMeta = {labels: ['aaa'], packages: ['abc']}
|
||||
@DocumentUpdaterHandler.flushDocToMongo = sinon.stub().callsArgWith 2, null
|
||||
@ProjectEntityHandler.getDoc = sinon.stub().callsArgWith 2, null, @fakeLines
|
||||
@MetaHandler.extractMetaFromDoc = sinon.stub().returns @fakeMeta
|
||||
@call = (callback) =>
|
||||
@MetaHandler.getMetaForDoc @projectId, @docId, callback
|
||||
|
||||
it 'should not produce an error', (done) ->
|
||||
@call (err, docMeta) =>
|
||||
expect(err).to.equal null
|
||||
done()
|
||||
|
||||
it 'should produce docMeta', (done) ->
|
||||
@call (err, docMeta) =>
|
||||
expect(docMeta).to.equal @fakeMeta
|
||||
done()
|
||||
|
||||
it 'should call flushDocToMongo', (done) ->
|
||||
@call (err, docMeta) =>
|
||||
@DocumentUpdaterHandler.flushDocToMongo.callCount.should.equal 1
|
||||
@DocumentUpdaterHandler.flushDocToMongo.calledWith(@projectId, @docId).should.equal true
|
||||
done()
|
||||
|
||||
it 'should call getDoc', (done) ->
|
||||
@call (err, docMeta) =>
|
||||
@ProjectEntityHandler.getDoc.callCount.should.equal 1
|
||||
@ProjectEntityHandler.getDoc.calledWith(@projectId, @docId).should.equal true
|
||||
done()
|
||||
|
||||
it 'should call extractMetaFromDoc', (done) ->
|
||||
@call (err, docMeta) =>
|
||||
@MetaHandler.extractMetaFromDoc.callCount.should.equal 1
|
||||
@MetaHandler.extractMetaFromDoc.calledWith(@fakeLines).should.equal true
|
||||
done()
|
||||
|
||||
describe 'getAllMetaForProject', ->
|
||||
beforeEach ->
|
||||
@fakeDocs =
|
||||
'doc_one':
|
||||
lines: [
|
||||
'\\usepackage[some-options,more=foo]{foo}'
|
||||
'\\label{aaa}'
|
||||
]
|
||||
|
||||
@fakeMeta =
|
||||
labels: ['aaa']
|
||||
packages:
|
||||
foo: [
|
||||
{
|
||||
caption: '\\bar'
|
||||
snippet: '\\bar'
|
||||
meta: 'foo-cmd'
|
||||
score: 12
|
||||
}, {
|
||||
caption: '\\bat[]{}'
|
||||
snippet: '\\bar[$1]{$2}'
|
||||
meta: 'foo-cmd'
|
||||
score: 10
|
||||
}
|
||||
]
|
||||
@DocumentUpdaterHandler.flushProjectToMongo = sinon.stub().callsArgWith 1, null
|
||||
@ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith 1, null, @fakeDocs
|
||||
@MetaHandler.extractMetaFromProjectDocs = sinon.stub().returns @fakeMeta
|
||||
@call = (callback) =>
|
||||
@MetaHandler.getAllMetaForProject @projectId, callback
|
||||
|
||||
it 'should not produce an error', (done) ->
|
||||
@call (err, projectMeta) =>
|
||||
expect(err).to.equal null
|
||||
done()
|
||||
|
||||
it 'should produce projectMeta', (done) ->
|
||||
@call (err, projectMeta) =>
|
||||
expect(projectMeta).to.equal @fakeMeta
|
||||
done()
|
||||
|
||||
it 'should call getAllDocs', (done) ->
|
||||
@call (err, projectMeta) =>
|
||||
@ProjectEntityHandler.getAllDocs.callCount.should.equal 1
|
||||
@ProjectEntityHandler.getAllDocs.calledWith(@projectId).should.equal true
|
||||
done()
|
||||
|
||||
it 'should call extractMetaFromDoc', (done) ->
|
||||
@call (err, docMeta) =>
|
||||
@MetaHandler.extractMetaFromProjectDocs.callCount.should.equal 1
|
||||
@MetaHandler.extractMetaFromProjectDocs.calledWith(@fakeDocs).should.equal true
|
||||
done()
|
||||
@@ -44,6 +44,7 @@ describe "SubscriptionController", ->
|
||||
|
||||
@RecurlyWrapper =
|
||||
sign: sinon.stub().callsArgWith(1, null, "somthing")
|
||||
getSubscription: sinon.stub().callsArgWith(2, null, {})
|
||||
|
||||
@SubscriptionViewModelBuilder =
|
||||
buildUsersSubscriptionViewModel:sinon.stub().callsArgWith(1, null, @activeRecurlySubscription)
|
||||
@@ -55,7 +56,7 @@ describe "SubscriptionController", ->
|
||||
collaborator:"COLLABORATORCODEHERE"
|
||||
apis:
|
||||
recurly:
|
||||
subdomain:"sl.recurly.com"
|
||||
subdomain:"sl"
|
||||
siteUrl: "http://de.sharelatex.dev:3000"
|
||||
gaExperiments:{}
|
||||
@GeoIpLookup =
|
||||
@@ -118,34 +119,6 @@ describe "SubscriptionController", ->
|
||||
@UserGetter.getUser.callCount.should.equal 0
|
||||
done()
|
||||
|
||||
|
||||
describe "editBillingDetailsPage", ->
|
||||
describe "with a user with a subscription", ->
|
||||
beforeEach (done) ->
|
||||
@LimitationsManager.userHasSubscription.callsArgWith(1, null, true)
|
||||
@user._id = @activeRecurlySubscription.account.account_code
|
||||
@res.callback = done
|
||||
@SubscriptionController.editBillingDetailsPage(@req, @res)
|
||||
|
||||
it "should render the edit billing details page", ->
|
||||
@res.rendered.should.equal true
|
||||
@res.renderedTemplate.should.equal "subscriptions/edit-billing-details"
|
||||
|
||||
it "should set the correct variables for the template", ->
|
||||
should.exist @res.renderedVariables.signature
|
||||
@res.renderedVariables.successURL.should.equal "#{@settings.siteUrl}/user/subscription/billing-details/update"
|
||||
@res.renderedVariables.user.id.should.equal @user._id
|
||||
|
||||
describe "with a user without subscription", ->
|
||||
beforeEach (done) ->
|
||||
@res.callback = done
|
||||
@LimitationsManager.userHasSubscription.callsArgWith(1, null, false)
|
||||
@SubscriptionController.reactivateSubscription @req, @res
|
||||
|
||||
it "should redirect to the subscription dashboard", ->
|
||||
@res.redirected.should.equal true
|
||||
@res.redirectedTo.should.equal "/user/subscription"
|
||||
|
||||
describe "paymentPage", ->
|
||||
beforeEach ->
|
||||
@req.headers = {}
|
||||
@@ -255,7 +228,12 @@ describe "SubscriptionController", ->
|
||||
describe "with an existing subscription", ->
|
||||
beforeEach (done)->
|
||||
@res.callback = done
|
||||
@LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, true)
|
||||
@settings.apis.recurly.subdomain = 'test'
|
||||
@userSub = {account: {hosted_login_token: 'abcd'}}
|
||||
@RecurlyWrapper.getSubscription = sinon.stub()
|
||||
.callsArgWith(2, null, @userSub)
|
||||
@LimitationsManager.userHasSubscriptionOrIsGroupMember
|
||||
.callsArgWith(1, null, true, {})
|
||||
@SubscriptionController.userSubscriptionPage @req, @res
|
||||
|
||||
|
||||
@@ -266,7 +244,7 @@ describe "SubscriptionController", ->
|
||||
beforeEach (done) ->
|
||||
@res.callback = done
|
||||
@SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel.callsArgWith(1, null, @activeRecurlySubscription)
|
||||
@LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, true)
|
||||
@LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, true, {})
|
||||
@SubscriptionController.userSubscriptionPage @req, @res
|
||||
|
||||
it "should render the dashboard", (done)->
|
||||
@@ -281,7 +259,7 @@ describe "SubscriptionController", ->
|
||||
beforeEach (done) ->
|
||||
@res.callback = done
|
||||
@SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel.callsArgWith(1, null, @activeRecurlySubscription)
|
||||
@LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, true)
|
||||
@LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, true, {})
|
||||
@SubscriptionController.userSubscriptionPage @req, @res
|
||||
|
||||
it "should render the dashboard", ->
|
||||
|
||||
Reference in New Issue
Block a user