Merge branch 'master' into sk-fix-chat-link-color-in-v2
This commit is contained in:
@@ -49,6 +49,8 @@ TpdsWorker.js
|
||||
BackgroundJobsWorker.js
|
||||
UserAndProjectPopulator.coffee
|
||||
|
||||
public/es/modules
|
||||
|
||||
public/js/*.js
|
||||
public/js/*.map
|
||||
public/js/libs/sharejs.js
|
||||
@@ -80,7 +82,7 @@ public/js/libs/require*.js
|
||||
|
||||
app/views/external
|
||||
|
||||
/modules/
|
||||
modules
|
||||
docker-shared.yml
|
||||
|
||||
config/*.coffee
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- "0.10"
|
||||
|
||||
before_install:
|
||||
- npm install -g grunt-cli
|
||||
|
||||
@@ -16,4 +13,3 @@ script:
|
||||
services:
|
||||
- redis-server
|
||||
- mongodb
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ clean_css:
|
||||
rm -f public/stylesheets/*.css*
|
||||
|
||||
clean_ci:
|
||||
docker-compose down
|
||||
docker-compose down -v
|
||||
|
||||
test: test_unit test_frontend test_acceptance
|
||||
|
||||
@@ -221,7 +221,7 @@ test_acceptance_module: $(MODULE_MAKEFILES)
|
||||
fi
|
||||
|
||||
test_clean:
|
||||
docker-compose ${DOCKER_COMPOSE_FLAGS} down
|
||||
docker-compose ${DOCKER_COMPOSE_FLAGS} down -v
|
||||
|
||||
ci:
|
||||
MOCHA_ARGS="--reporter tap" \
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
MODULE_NAME := $(notdir $(shell pwd))
|
||||
MODULE_DIR := modules/$(MODULE_NAME)
|
||||
COFFEE := ../../node_modules/.bin/coffee
|
||||
APP_COFFEE_FILES := $(shell find app/coffee -name '*.coffee') \
|
||||
APP_COFFEE_FILES := $(shell [ -e app/coffee ] && find app/coffee -name '*.coffee') \
|
||||
$(shell [ -e test/unit/coffee ] && find test/unit/coffee -name '*.coffee') \
|
||||
$(shell [ -e test/acceptance/coffee ] && find test/acceptance/coffee -name '*.coffee')
|
||||
APP_JS_FILES := $(subst coffee,js,$(APP_COFFEE_FILES))
|
||||
IDE_COFFEE_FILES := $(shell [ -e public/coffee/ide ] && find public/coffee/ide -name '*.coffee')
|
||||
IDE_JS_FILES := $(subst public/coffee/ide,../../public/js/ide/$(MODULE_NAME),$(IDE_COFFEE_FILES))
|
||||
IDE_JS_FILES := $(subst coffee,js,$(IDE_JS_FILES))
|
||||
IDE_TEST_COFFEE_FILES := $(shell [ -e test/unit_frontend/coffee ] && find test/unit_frontend/coffee -name '*.coffee')
|
||||
IDE_TEST_JS_FILES := $(subst test/unit_frontend/coffee/ide,../../test/unit_frontend/js/ide/$(MODULE_NAME),$(IDE_TEST_COFFEE_FILES))
|
||||
IDE_TEST_JS_FILES := $(subst coffee,js,$(IDE_TEST_JS_FILES))
|
||||
MAIN_COFFEE_FILES := $(shell [ -e public/coffee/main ] && find public/coffee/main -name '*.coffee')
|
||||
MAIN_JS_FILES := $(subst public/coffee/main,../../public/js/main/$(MODULE_NAME),$(MAIN_COFFEE_FILES))
|
||||
MAIN_JS_FILES := $(subst coffee,js,$(MAIN_JS_FILES))
|
||||
@@ -26,6 +29,10 @@ test/acceptance/js/%.js: test/acceptance/coffee/%.coffee
|
||||
@mkdir -p $(dir $@)
|
||||
$(COFFEE) --compile --print $< > $@
|
||||
|
||||
../../test/unit_frontend/js/ide/$(MODULE_NAME)/%.js: test/unit_frontend/coffee/ide/%.coffee
|
||||
@mkdir -p $(dir $@)
|
||||
$(COFFEE) --compile --print $< > $@
|
||||
|
||||
../../public/js/ide/$(MODULE_NAME)/%.js: public/coffee/ide/%.coffee
|
||||
@mkdir -p $(dir $@)
|
||||
$(COFFEE) --compile --print $< > $@
|
||||
@@ -37,11 +44,11 @@ test/acceptance/js/%.js: test/acceptance/coffee/%.coffee
|
||||
index.js: index.coffee
|
||||
$(COFFEE) --compile --print $< > $@
|
||||
|
||||
compile: $(APP_JS_FILES) $(IDE_JS_FILES) $(MAIN_JS_FILES) index.js
|
||||
compile: $(APP_JS_FILES) $(IDE_JS_FILES) $(MAIN_JS_FILES) $(IDE_TEST_JS_FILES) index.js
|
||||
@echo > /dev/null
|
||||
|
||||
compile_full:
|
||||
$(COFFEE) -o app/js -c app/coffee
|
||||
if [ -e app/coffee ]; then $(COFFEE) -o app/js -c app/coffee; fi
|
||||
if [ -e test/unit/coffee ]; then $(COFFEE) -o test/unit/js -c test/unit/coffee; fi
|
||||
if [ -e test/acceptance/coffee ]; then $(COFFEE) -o test/acceptance/js -c test/acceptance/coffee; fi
|
||||
if [ -e public/coffee/ide/ ]; then $(COFFEE) -o ../../public/js/ide/$(MODULE_NAME) -c public/coffee/ide/; fi
|
||||
|
||||
@@ -17,17 +17,17 @@ makeFaultTolerantRequest = (userId, options, callback) ->
|
||||
if settings.overleaf?
|
||||
options.qs = Object.assign({}, options.qs, { fromV2: 1 })
|
||||
|
||||
makeRequest(options, callback)
|
||||
makeRequest options, (err) ->
|
||||
if err?
|
||||
logger.err { err: err }, 'Request to analytics failed'
|
||||
|
||||
callback() # Do not wait for all the attempts
|
||||
|
||||
makeRequest = (opts, callback)->
|
||||
if settings.apis?.analytics?.url?
|
||||
urlPath = opts.url
|
||||
opts.url = "#{settings.apis.analytics.url}#{urlPath}"
|
||||
request opts, (err) ->
|
||||
if err?
|
||||
logger.err { err: err }, 'Request to analytics failed'
|
||||
|
||||
callback() # Do not wait for all the attempts
|
||||
request opts, callback
|
||||
else
|
||||
callback(new Errors.ServiceNotConfiguredError('Analytics service not configured'))
|
||||
|
||||
|
||||
@@ -18,15 +18,16 @@ module.exports = CompileManager =
|
||||
timer.done()
|
||||
_callback(args...)
|
||||
|
||||
@_checkIfAutoCompileLimitHasBeenHit options.isAutoCompile, "everyone", (err, canCompile)->
|
||||
if !canCompile
|
||||
return callback null, "autocompile-backoff", []
|
||||
logger.log project_id: project_id, user_id: user_id, "compiling project"
|
||||
CompileManager._checkIfRecentlyCompiled project_id, user_id, (error, recentlyCompiled) ->
|
||||
return callback(error) if error?
|
||||
if recentlyCompiled
|
||||
logger.warn {project_id, user_id}, "project was recently compiled so not continuing"
|
||||
return callback null, "too-recently-compiled", []
|
||||
logger.log project_id: project_id, user_id: user_id, "compiling project"
|
||||
CompileManager._checkIfRecentlyCompiled project_id, user_id, (error, recentlyCompiled) ->
|
||||
return callback(error) if error?
|
||||
if recentlyCompiled
|
||||
logger.warn {project_id, user_id}, "project was recently compiled so not continuing"
|
||||
return callback null, "too-recently-compiled", []
|
||||
|
||||
CompileManager._checkIfAutoCompileLimitHasBeenHit options.isAutoCompile, "everyone", (err, canCompile)->
|
||||
if !canCompile
|
||||
return callback null, "autocompile-backoff", []
|
||||
|
||||
CompileManager._ensureRootDocumentIsSet project_id, (error) ->
|
||||
return callback(error) if error?
|
||||
|
||||
@@ -121,15 +121,15 @@ module.exports = DocumentUpdaterHandler =
|
||||
method: "DELETE"
|
||||
}, project_id, "delete-thread", callback
|
||||
|
||||
resyncProjectHistory: (project_id, docs, files, callback) ->
|
||||
resyncProjectHistory: (project_id, projectHistoryId, docs, files, callback) ->
|
||||
logger.info {project_id, docs, files}, "resyncing project history in doc updater"
|
||||
DocumentUpdaterHandler._makeRequest {
|
||||
path: "/project/#{project_id}/history/resync"
|
||||
json: { docs, files }
|
||||
json: { docs, files, projectHistoryId }
|
||||
method: "POST"
|
||||
}, project_id, "resync-project-history", callback
|
||||
|
||||
updateProjectStructure : (project_id, userId, changes, callback = (error) ->)->
|
||||
updateProjectStructure: (project_id, projectHistoryId, userId, changes, callback = (error) ->)->
|
||||
return callback() if !settings.apis.project_history?.sendProjectStructureOps
|
||||
|
||||
Project.findOne {_id: project_id}, {version:true}, (err, currentProject) ->
|
||||
@@ -144,7 +144,13 @@ module.exports = DocumentUpdaterHandler =
|
||||
logger.log {project_id}, "updating project structure in doc updater"
|
||||
DocumentUpdaterHandler._makeRequest {
|
||||
path: "/project/#{project_id}"
|
||||
json: { docUpdates, fileUpdates, userId, version: currentProject.version }
|
||||
json: {
|
||||
docUpdates,
|
||||
fileUpdates,
|
||||
userId,
|
||||
version: currentProject.version
|
||||
projectHistoryId
|
||||
}
|
||||
method: "POST"
|
||||
}, project_id, "update-project-structure", callback
|
||||
|
||||
@@ -174,6 +180,23 @@ module.exports = DocumentUpdaterHandler =
|
||||
oldEntitiesHash = _.indexBy oldEntities, (entity) -> entity[entityType]._id.toString()
|
||||
newEntitiesHash = _.indexBy newEntities, (entity) -> entity[entityType]._id.toString()
|
||||
|
||||
# Send deletes before adds (and renames) to keep a 1:1 mapping between
|
||||
# paths and ids
|
||||
#
|
||||
# When a file is replaced, we first delete the old file and then add the
|
||||
# new file. If the 'add' operation is sent to project history before the
|
||||
# 'delete' then we would have two files with the same path at that point
|
||||
# in time.
|
||||
for id, oldEntity of oldEntitiesHash
|
||||
newEntity = newEntitiesHash[id]
|
||||
|
||||
if !newEntity?
|
||||
# entity deleted
|
||||
updates.push
|
||||
id: id
|
||||
pathname: oldEntity.path
|
||||
newPathname: ''
|
||||
|
||||
for id, newEntity of newEntitiesHash
|
||||
oldEntity = oldEntitiesHash[id]
|
||||
|
||||
@@ -191,16 +214,6 @@ module.exports = DocumentUpdaterHandler =
|
||||
pathname: oldEntity.path
|
||||
newPathname: newEntity.path
|
||||
|
||||
for id, oldEntity of oldEntitiesHash
|
||||
newEntity = newEntitiesHash[id]
|
||||
|
||||
if !newEntity?
|
||||
# entity deleted
|
||||
updates.push
|
||||
id: id
|
||||
pathname: oldEntity.path
|
||||
newPathname: ''
|
||||
|
||||
updates
|
||||
|
||||
PENDINGUPDATESKEY = "PendingUpdates"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
ProjectGetter = require "../Project/ProjectGetter"
|
||||
ProjectLocator = require "../Project/ProjectLocator"
|
||||
ProjectEntityHandler = require "../Project/ProjectEntityHandler"
|
||||
ProjectEntityUpdateHandler = require "../Project/ProjectEntityUpdateHandler"
|
||||
logger = require("logger-sharelatex")
|
||||
@@ -8,21 +10,31 @@ module.exports =
|
||||
doc_id = req.params.doc_id
|
||||
plain = req?.query?.plain == 'true'
|
||||
logger.log doc_id:doc_id, project_id:project_id, "receiving get document request from api (docupdater)"
|
||||
ProjectEntityHandler.getDoc project_id, doc_id, {pathname: true}, (error, lines, rev, version, ranges, pathname) ->
|
||||
if error?
|
||||
logger.err err:error, doc_id:doc_id, project_id:project_id, "error finding element for getDocument"
|
||||
return next(error)
|
||||
if plain
|
||||
res.type "text/plain"
|
||||
res.send lines.join('\n')
|
||||
else
|
||||
res.type "json"
|
||||
res.send JSON.stringify {
|
||||
lines: lines
|
||||
version: version
|
||||
ranges: ranges
|
||||
pathname: pathname
|
||||
}
|
||||
ProjectGetter.getProject project_id, rootFolder: true, overleaf: true, (error, project) ->
|
||||
return next(error) if error?
|
||||
return res.sendStatus(404) if !project?
|
||||
ProjectLocator.findElement {project: project, element_id: doc_id, type: 'doc'}, (error, doc, path) ->
|
||||
if error?
|
||||
logger.err err:error, doc_id:doc_id, project_id:project_id, "error finding element for getDocument"
|
||||
return next(error)
|
||||
ProjectEntityHandler.getDoc project_id, doc_id, (error, lines, rev, version, ranges) ->
|
||||
if error?
|
||||
logger.err err:error, doc_id:doc_id, project_id:project_id, "error finding doc contents for getDocument"
|
||||
return next(error)
|
||||
if plain
|
||||
res.type "text/plain"
|
||||
res.send lines.join('\n')
|
||||
|
||||
else
|
||||
projectHistoryId = project?.overleaf?.history?.id
|
||||
res.type "json"
|
||||
res.send JSON.stringify {
|
||||
lines: lines
|
||||
version: version
|
||||
ranges: ranges
|
||||
pathname: path.fileSystem
|
||||
projectHistoryId: projectHistoryId
|
||||
}
|
||||
|
||||
setDocument: (req, res, next = (error) ->) ->
|
||||
project_id = req.params.Project_id
|
||||
|
||||
@@ -41,11 +41,13 @@ module.exports = EditorController =
|
||||
callback err, doc
|
||||
|
||||
upsertFile: (project_id, folder_id, fileName, fsPath, linkedFileData, source, user_id, callback = (err, file) ->) ->
|
||||
ProjectEntityUpdateHandler.upsertFile project_id, folder_id, fileName, fsPath, linkedFileData, user_id, (err, file, didAddFile) ->
|
||||
ProjectEntityUpdateHandler.upsertFile project_id, folder_id, fileName, fsPath, linkedFileData, user_id, (err, newFile, didAddFile, existingFile) ->
|
||||
return callback(err) if err?
|
||||
if didAddFile
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveNewFile', folder_id, file, source, linkedFileData
|
||||
callback null, file
|
||||
if not didAddFile # replacement, so remove the existing file from the client
|
||||
EditorRealTimeController.emitToRoom project_id, 'removeEntity', existingFile._id, source
|
||||
# now add the new file on the client
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveNewFile', folder_id, newFile, source, linkedFileData
|
||||
callback null, newFile
|
||||
|
||||
upsertDocWithPath: (project_id, elementPath, docLines, source, user_id, callback) ->
|
||||
ProjectEntityUpdateHandler.upsertDocWithPath project_id, elementPath, docLines, source, user_id, (err, doc, didAddNewDoc, newFolders, lastFolder) ->
|
||||
@@ -57,12 +59,14 @@ module.exports = EditorController =
|
||||
callback()
|
||||
|
||||
upsertFileWithPath: (project_id, elementPath, fsPath, linkedFileData, source, user_id, callback) ->
|
||||
ProjectEntityUpdateHandler.upsertFileWithPath project_id, elementPath, fsPath, linkedFileData, user_id, (err, file, didAddFile, newFolders, lastFolder) ->
|
||||
ProjectEntityUpdateHandler.upsertFileWithPath project_id, elementPath, fsPath, linkedFileData, user_id, (err, newFile, didAddFile, existingFile, newFolders, lastFolder) ->
|
||||
return callback(err) if err?
|
||||
EditorController._notifyProjectUsersOfNewFolders project_id, newFolders, (err) ->
|
||||
return callback(err) if err?
|
||||
if didAddFile
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveNewFile', lastFolder._id, file, source, linkedFileData
|
||||
if not didAddFile # replacement, so remove the existing file from the client
|
||||
EditorRealTimeController.emitToRoom project_id, 'removeEntity', existingFile._id, source
|
||||
# now add the new file on the client
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveNewFile', lastFolder._id, newFile, source, linkedFileData
|
||||
callback()
|
||||
|
||||
addFolder : (project_id, folder_id, folderName, source, callback = (error, folder)->)->
|
||||
|
||||
@@ -57,26 +57,9 @@ module.exports = EditorHttpController =
|
||||
privilegeLevel
|
||||
)
|
||||
|
||||
restoreDoc: (req, res, next) ->
|
||||
project_id = req.params.Project_id
|
||||
doc_id = req.params.doc_id
|
||||
name = req.body.name
|
||||
|
||||
if !name?
|
||||
return res.sendStatus 400 # Malformed request
|
||||
|
||||
logger.log project_id: project_id, doc_id: doc_id, "restoring doc"
|
||||
ProjectEntityUpdateHandler.restoreDoc project_id, doc_id, name, (err, doc, folder_id) =>
|
||||
return next(error) if error?
|
||||
EditorRealTimeController.emitToRoom(project_id, 'reciveNewDoc', folder_id, doc)
|
||||
res.json {
|
||||
doc_id: doc._id
|
||||
}
|
||||
|
||||
_nameIsAcceptableLength: (name)->
|
||||
return name? and name.length < 150 and name.length != 0
|
||||
|
||||
|
||||
addDoc: (req, res, next) ->
|
||||
project_id = req.params.Project_id
|
||||
name = req.body.name
|
||||
|
||||
@@ -14,8 +14,6 @@ module.exports =
|
||||
webRouter.delete '/project/:Project_id/doc/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteDoc
|
||||
webRouter.delete '/project/:Project_id/folder/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteFolder
|
||||
|
||||
webRouter.post '/project/:Project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.restoreDoc
|
||||
|
||||
# Called by the real-time API to load up the current project state.
|
||||
# This is a post request because it's more than just a getting of data. We take actions
|
||||
# whenever a user joins a project, like updating the deleted status.
|
||||
|
||||
@@ -6,6 +6,7 @@ Errors = require "../Errors/Errors"
|
||||
HistoryManager = require "./HistoryManager"
|
||||
ProjectDetailsHandler = require "../Project/ProjectDetailsHandler"
|
||||
ProjectEntityUpdateHandler = require "../Project/ProjectEntityUpdateHandler"
|
||||
RestoreManager = require "./RestoreManager"
|
||||
|
||||
module.exports = HistoryController =
|
||||
selectHistoryApi: (req, res, next = (error) ->) ->
|
||||
@@ -71,3 +72,29 @@ module.exports = HistoryController =
|
||||
return res.sendStatus(404) if error instanceof Errors.ProjectHistoryDisabledError
|
||||
return next(error) if error?
|
||||
res.sendStatus 204
|
||||
|
||||
restoreFileFromV2: (req, res, next) ->
|
||||
{project_id} = req.params
|
||||
{version, pathname} = req.body
|
||||
user_id = AuthenticationController.getLoggedInUserId req
|
||||
logger.log {project_id, version, pathname}, "restoring file from v2"
|
||||
RestoreManager.restoreFileFromV2 user_id, project_id, version, pathname, (error, entity) ->
|
||||
return next(error) if error?
|
||||
res.json {
|
||||
type: entity.type,
|
||||
id: entity._id
|
||||
}
|
||||
|
||||
restoreDocFromDeletedDoc: (req, res, next) ->
|
||||
{project_id, doc_id} = req.params
|
||||
{name} = req.body
|
||||
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||
if !name?
|
||||
return res.sendStatus 400 # Malformed request
|
||||
logger.log {project_id, doc_id, user_id}, "restoring doc from v1 deleted doc"
|
||||
RestoreManager.restoreDocFromDeletedDoc user_id, project_id, doc_id, name, (err, doc) =>
|
||||
return next(error) if error?
|
||||
res.json {
|
||||
doc_id: doc._id
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
Settings = require 'settings-sharelatex'
|
||||
Path = require 'path'
|
||||
FileWriter = require '../../infrastructure/FileWriter'
|
||||
FileSystemImportManager = require '../Uploads/FileSystemImportManager'
|
||||
ProjectEntityHandler = require '../Project/ProjectEntityHandler'
|
||||
ProjectLocator = require '../Project/ProjectLocator'
|
||||
EditorController = require '../Editor/EditorController'
|
||||
Errors = require '../Errors/Errors'
|
||||
moment = require 'moment'
|
||||
|
||||
module.exports = RestoreManager =
|
||||
restoreDocFromDeletedDoc: (user_id, project_id, doc_id, name, callback = (error, doc, folder_id) ->) ->
|
||||
# This is the legacy method for restoring a doc from the SL track-changes/deletedDocs system.
|
||||
# It looks up the deleted doc's contents, and then creates a new doc with the same content.
|
||||
# We don't actually remove the deleted doc entry, just create a new one from its lines.
|
||||
ProjectEntityHandler.getDoc project_id, doc_id, include_deleted: true, (error, lines) ->
|
||||
return callback(error) if error?
|
||||
addDocWithName = (name, callback) ->
|
||||
EditorController.addDoc project_id, null, name, lines, 'restore', user_id, callback
|
||||
RestoreManager._addEntityWithUniqueName addDocWithName, name, callback
|
||||
|
||||
restoreFileFromV2: (user_id, project_id, version, pathname, callback = (error, entity) ->) ->
|
||||
RestoreManager._writeFileVersionToDisk project_id, version, pathname, (error, fsPath) ->
|
||||
return callback(error) if error?
|
||||
basename = Path.basename(pathname)
|
||||
dirname = Path.dirname(pathname)
|
||||
if dirname == '.' # no directory
|
||||
dirname = ''
|
||||
RestoreManager._findOrCreateFolder project_id, dirname, (error, parent_folder_id) ->
|
||||
return callback(error) if error?
|
||||
addEntityWithName = (name, callback) ->
|
||||
FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, name, fsPath, false, callback
|
||||
RestoreManager._addEntityWithUniqueName addEntityWithName, basename, callback
|
||||
|
||||
_findOrCreateFolder: (project_id, dirname, callback = (error, folder_id) ->) ->
|
||||
EditorController.mkdirp project_id, dirname, (error, newFolders, lastFolder) ->
|
||||
return callback(error) if error?
|
||||
return callback(null, lastFolder?._id)
|
||||
|
||||
_addEntityWithUniqueName: (addEntityWithName, basename, callback = (error) ->) ->
|
||||
addEntityWithName basename, (error, entity) ->
|
||||
if error?
|
||||
if error instanceof Errors.InvalidNameError
|
||||
# likely a duplicate name, so try with a prefix
|
||||
date = moment(new Date()).format('Do MMM YY H:mm:ss')
|
||||
# Move extension to the end so the file type is preserved
|
||||
extension = Path.extname(basename)
|
||||
basename = Path.basename(basename, extension)
|
||||
basename = "#{basename} (Restored on #{date})"
|
||||
if extension != ''
|
||||
basename = "#{basename}#{extension}"
|
||||
addEntityWithName basename, callback
|
||||
else
|
||||
callback(error)
|
||||
else
|
||||
callback(null, entity)
|
||||
|
||||
_writeFileVersionToDisk: (project_id, version, pathname, callback = (error, fsPath) ->) ->
|
||||
url = "#{Settings.apis.project_history.url}/project/#{project_id}/version/#{version}/#{encodeURIComponent(pathname)}"
|
||||
FileWriter.writeUrlToDisk project_id, url, callback
|
||||
@@ -157,7 +157,7 @@ module.exports = ProjectController =
|
||||
hasSubscription: (cb)->
|
||||
LimitationsManager.userHasSubscriptionOrIsGroupMember currentUser, cb
|
||||
user: (cb) ->
|
||||
User.findById user_id, "featureSwitches overleaf awareOfV2", cb
|
||||
User.findById user_id, "featureSwitches overleaf awareOfV2 features", cb
|
||||
}, (err, results)->
|
||||
if err?
|
||||
logger.err err:err, "error getting data for project list page"
|
||||
@@ -172,6 +172,7 @@ module.exports = ProjectController =
|
||||
user = results.user
|
||||
warnings = ProjectController._buildWarningsList results.v1Projects
|
||||
|
||||
|
||||
ProjectController._injectProjectOwners projects, (error, projects) ->
|
||||
return next(error) if error?
|
||||
viewModel = {
|
||||
@@ -193,6 +194,14 @@ module.exports = ProjectController =
|
||||
else
|
||||
viewModel.showUserDetailsArea = false
|
||||
|
||||
paidUser = user.features?.github # use a heuristic for paid account
|
||||
freeUserProportion = 0.10
|
||||
sampleFreeUser = parseInt(user._id.toString().slice(-2), 16) < freeUserProportion * 255
|
||||
showFrontWidget = paidUser or sampleFreeUser
|
||||
logger.log {paidUser, sampleFreeUser, showFrontWidget}, 'deciding whether to show front widget'
|
||||
if showFrontWidget
|
||||
viewModel.frontChatWidgetRoomId = Settings.overleaf?.front_chat_widget_room_id
|
||||
|
||||
res.render 'project/list', viewModel
|
||||
timer.done()
|
||||
|
||||
@@ -301,6 +310,7 @@ module.exports = ProjectController =
|
||||
themes: THEME_LIST
|
||||
maxDocLength: Settings.max_doc_length
|
||||
useV2History: !!project.overleaf?.history?.display
|
||||
showRichText: req.query?.rt == 'true'
|
||||
timer.done()
|
||||
|
||||
_buildProjectList: (allProjects, v1Projects = [])->
|
||||
|
||||
@@ -35,10 +35,17 @@ module.exports = ProjectEditorHandler =
|
||||
compileGroup:"standard"
|
||||
templates: false
|
||||
references: false
|
||||
referencesSearch: false
|
||||
mendeley: false
|
||||
trackChanges: false
|
||||
trackChangesVisible: ProjectEditorHandler.trackChangesAvailable
|
||||
})
|
||||
|
||||
# Originally these two feature flags were both signalled by the now-deprecated `references` flag.
|
||||
# For older users, the presence of the `references` feature flag should still turn on these features.
|
||||
result.features.referencesSearch = result.features.referencesSearch or result.features.references
|
||||
result.features.mendeley = result.features.mendeley or result.features.references
|
||||
|
||||
return result
|
||||
|
||||
buildOwnerAndMembersViews: (members) ->
|
||||
|
||||
@@ -6,7 +6,6 @@ DocstoreManager = require "../Docstore/DocstoreManager"
|
||||
DocumentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler')
|
||||
Errors = require '../Errors/Errors'
|
||||
Project = require('../../models/Project').Project
|
||||
ProjectLocator = require('./ProjectLocator')
|
||||
ProjectGetter = require "./ProjectGetter"
|
||||
TpdsUpdateSender = require('../ThirdPartyDataStore/TpdsUpdateSender')
|
||||
|
||||
@@ -105,14 +104,7 @@ module.exports = ProjectEntityHandler = self =
|
||||
callback = options
|
||||
options = {}
|
||||
|
||||
if options["pathname"]
|
||||
delete options["pathname"]
|
||||
ProjectLocator.findElement {project_id: project_id, element_id: doc_id, type: 'doc'}, (error, doc, path) =>
|
||||
return callback(error) if error?
|
||||
DocstoreManager.getDoc project_id, doc_id, options, (error, lines, rev, version, ranges) =>
|
||||
callback(error, lines, rev, version, ranges, path.fileSystem)
|
||||
else
|
||||
DocstoreManager.getDoc project_id, doc_id, options, callback
|
||||
DocstoreManager.getDoc project_id, doc_id, options, callback
|
||||
|
||||
_getAllFolders: (project_id, callback) ->
|
||||
logger.log project_id:project_id, "getting all folders for project"
|
||||
|
||||
@@ -31,7 +31,7 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
|
||||
LOCK_NAMESPACE: LOCK_NAMESPACE
|
||||
|
||||
addDoc: wrapWithLock (project_id, folder_id, doc, callback = (err, result) ->) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder:true, name:true}, (err, project) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder:true, name:true, overleaf:true}, (err, project) ->
|
||||
if err?
|
||||
logger.err project_id:project_id, err:err, "error getting project for add doc"
|
||||
return callback(err)
|
||||
@@ -40,7 +40,7 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
|
||||
self._putElement project, folder_id, doc, "doc", callback
|
||||
|
||||
addFile: wrapWithLock (project_id, folder_id, fileRef, callback = (error, result, project) ->)->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder:true, name:true}, (err, project) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder:true, name:true, overleaf:true}, (err, project) ->
|
||||
if err?
|
||||
logger.err project_id:project_id, err:err, "error getting project for add file"
|
||||
return callback(err)
|
||||
@@ -48,28 +48,28 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
|
||||
self._confirmFolder project, folder_id, (folder_id)->
|
||||
self._putElement project, folder_id, fileRef, "file", callback
|
||||
|
||||
replaceFile: wrapWithLock (project_id, file_id, linkedFileData, callback) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder: true, name:true}, (err, project) ->
|
||||
replaceFileWithNew: wrapWithLock (project_id, file_id, newFileRef, callback) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder:true, name:true, overleaf:true}, (err, project) ->
|
||||
return callback(err) if err?
|
||||
ProjectLocator.findElement {project:project, element_id: file_id, type: 'file'}, (err, fileRef, path)=>
|
||||
return callback(err) if err?
|
||||
conditions = _id:project._id
|
||||
inc = {}
|
||||
inc["#{path.mongo}.rev"] = 1
|
||||
# currently we do not need to increment the project version number for changes that are replacements
|
||||
# but when we make switch to having immutable files the replace operation will add a new file, and
|
||||
# this will require a version increase. We will start incrementing the project version now as it does
|
||||
# no harm and will help to test it.
|
||||
inc['version'] = 1
|
||||
set = {}
|
||||
set["#{path.mongo}.created"] = new Date()
|
||||
set["#{path.mongo}.linkedFileData"] = linkedFileData
|
||||
update =
|
||||
"$inc": inc
|
||||
"$set": set
|
||||
Project.update conditions, update, {}, (err) ->
|
||||
ProjectEntityMongoUpdateHandler._insertDeletedFileReference project_id, fileRef, (err) ->
|
||||
return callback(err) if err?
|
||||
callback null, fileRef, project, path
|
||||
conditions = _id:project._id
|
||||
inc = {}
|
||||
# increment the project structure version as we are adding a new file here
|
||||
inc['version'] = 1
|
||||
set = {}
|
||||
set["#{path.mongo}._id"] = newFileRef._id
|
||||
set["#{path.mongo}.created"] = new Date()
|
||||
set["#{path.mongo}.linkedFileData"] = newFileRef.linkedFileData
|
||||
set["#{path.mongo}.rev"] = 1
|
||||
update =
|
||||
"$inc": inc
|
||||
"$set": set
|
||||
Project.update conditions, update, {}, (err) ->
|
||||
return callback(err) if err?
|
||||
callback null, fileRef, project, path
|
||||
|
||||
mkdirp: wrapWithLock (project_id, path, callback) ->
|
||||
folders = path.split('/')
|
||||
@@ -110,7 +110,7 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
|
||||
callback null, folders, lastFolder
|
||||
|
||||
moveEntity: wrapWithLock (project_id, entity_id, destFolderId, entityType, callback = (error) ->) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder:true, name:true}, (err, project) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder:true, name:true, overleaf:true}, (err, project) ->
|
||||
return callback(err) if err?
|
||||
ProjectLocator.findElement {project, element_id: entity_id, type: entityType}, (err, entity, entityPath)->
|
||||
return callback(err) if err?
|
||||
@@ -127,10 +127,10 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
|
||||
startPath = entityPath.fileSystem
|
||||
endPath = result.path.fileSystem
|
||||
changes = {oldDocs, newDocs, oldFiles, newFiles}
|
||||
callback null, project.name, startPath, endPath, entity.rev, changes, callback
|
||||
callback null, project, startPath, endPath, entity.rev, changes, callback
|
||||
|
||||
deleteEntity: wrapWithLock (project_id, entity_id, entityType, callback) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {name:true, rootFolder:true}, (error, project) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {name:true, rootFolder:true, overleaf:true}, (error, project) ->
|
||||
return callback(error) if error?
|
||||
ProjectLocator.findElement {project: project, element_id: entity_id, type: entityType}, (error, entity, path) ->
|
||||
return callback(error) if error?
|
||||
@@ -139,7 +139,7 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
|
||||
callback null, entity, path, project
|
||||
|
||||
renameEntity: wrapWithLock (project_id, entity_id, entityType, newName, callback) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder:true, name:true}, (error, project)=>
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder:true, name:true, overleaf:true}, (error, project)=>
|
||||
return callback(error) if error?
|
||||
ProjectEntityHandler.getAllEntitiesFromProject project, (error, oldDocs, oldFiles) =>
|
||||
return callback(error) if error?
|
||||
@@ -161,10 +161,10 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
|
||||
return callback(error) if error?
|
||||
startPath = entPath.fileSystem
|
||||
changes = {oldDocs, newDocs, oldFiles, newFiles}
|
||||
callback null, project.name, startPath, endPath, entity.rev, changes, callback
|
||||
callback null, project, startPath, endPath, entity.rev, changes, callback
|
||||
|
||||
addFolder: wrapWithLock (project_id, parentFolder_id, folderName, callback) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder:true, name:true}, (err, project) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder:true, name:true, overleaf:true}, (err, project) ->
|
||||
if err?
|
||||
logger.err project_id:project_id, err:err, "error getting project for add folder"
|
||||
return callback(err)
|
||||
@@ -300,3 +300,29 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
|
||||
if isNestedFolder
|
||||
return callback(new Errors.InvalidNameError("destination folder is a child folder of me"))
|
||||
callback()
|
||||
|
||||
_insertDeletedDocReference: (project_id, doc, callback = (error) ->) ->
|
||||
Project.update {
|
||||
_id: project_id
|
||||
}, {
|
||||
$push: {
|
||||
deletedDocs: {
|
||||
_id: doc._id
|
||||
name: doc.name
|
||||
}
|
||||
}
|
||||
}, {}, callback
|
||||
|
||||
_insertDeletedFileReference: (project_id, fileRef, callback = (error) ->) ->
|
||||
Project.update {
|
||||
_id: project_id
|
||||
}, {
|
||||
$push: {
|
||||
deletedFiles: {
|
||||
_id: fileRef._id
|
||||
name: fileRef.name
|
||||
linkedFileData: fileRef.linkedFileData
|
||||
deletedAt: new Date()
|
||||
}
|
||||
}
|
||||
}, {}, callback
|
||||
|
||||
@@ -24,17 +24,31 @@ wrapWithLock = (methodWithoutLock) ->
|
||||
# This lock is used to make sure that the project structure updates are made
|
||||
# sequentially. In particular the updates must be made in mongo and sent to
|
||||
# the doc-updater in the same order.
|
||||
methodWithLock = (project_id, args..., callback) ->
|
||||
LockManager.runWithLock LOCK_NAMESPACE, project_id,
|
||||
(cb) -> methodWithoutLock project_id, args..., cb
|
||||
callback
|
||||
methodWithLock.withoutLock = methodWithoutLock
|
||||
methodWithLock
|
||||
if typeof methodWithoutLock is 'function'
|
||||
methodWithLock = (project_id, args..., callback) ->
|
||||
LockManager.runWithLock LOCK_NAMESPACE, project_id,
|
||||
(cb) -> methodWithoutLock project_id, args..., cb
|
||||
callback
|
||||
methodWithLock.withoutLock = methodWithoutLock
|
||||
methodWithLock
|
||||
else
|
||||
# handle case with separate setup and locked stages
|
||||
wrapWithSetup = methodWithoutLock.beforeLock # a function to set things up before the lock
|
||||
mainTask = methodWithoutLock.withLock # function to execute inside the lock
|
||||
methodWithLock = wrapWithSetup (project_id, args..., callback) ->
|
||||
LockManager.runWithLock(LOCK_NAMESPACE, project_id, (cb) ->
|
||||
mainTask(project_id, args..., cb)
|
||||
callback)
|
||||
methodWithLock.withoutLock = wrapWithSetup mainTask
|
||||
methodWithLock.beforeLock = methodWithoutLock.beforeLock
|
||||
methodWithLock.mainTask = methodWithoutLock.withLock
|
||||
methodWithLock
|
||||
|
||||
module.exports = ProjectEntityUpdateHandler = self =
|
||||
# this doesn't need any locking because it's only called by ProjectDuplicator
|
||||
copyFileFromExistingProjectWithProject: (project, folder_id, originalProject_id, origonalFileRef, userId, callback = (error, fileRef, folder_id) ->)->
|
||||
project_id = project._id
|
||||
projectHistoryId = project.overleaf?.history?.id
|
||||
logger.log { project_id, folder_id, originalProject_id, origonalFileRef }, "copying file in s3 with project"
|
||||
return callback(err) if err?
|
||||
ProjectEntityMongoUpdateHandler._confirmFolder project, folder_id, (folder_id)=>
|
||||
@@ -59,7 +73,7 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||
path: result?.path?.fileSystem
|
||||
url: fileStoreUrl
|
||||
]
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, (error) ->
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, {newFiles}, (error) ->
|
||||
return callback(error) if error?
|
||||
callback null, fileRef, folder_id
|
||||
|
||||
@@ -108,52 +122,87 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||
logger.log project_id: project_id, "removing root doc"
|
||||
Project.update {_id:project_id}, {$unset: {rootDoc_id: true}}, {}, callback
|
||||
|
||||
restoreDoc: (project_id, doc_id, name, callback = (error, doc, folder_id) ->) ->
|
||||
if not SafePath.isCleanFilename name
|
||||
return callback new Errors.InvalidNameError("invalid element name")
|
||||
# getDoc will return the deleted doc's lines, but we don't actually remove
|
||||
# the deleted doc, just create a new one from its lines.
|
||||
ProjectEntityHandler.getDoc project_id, doc_id, include_deleted: true, (error, lines) ->
|
||||
return callback(error) if error?
|
||||
self.addDoc project_id, null, name, lines, callback
|
||||
|
||||
addDoc: wrapWithLock (project_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=>
|
||||
self.addDocWithoutUpdatingHistory.withoutLock project_id, folder_id, docName, docLines, userId, (error, doc, folder_id, path) ->
|
||||
self.addDocWithoutUpdatingHistory.withoutLock project_id, folder_id, docName, docLines, userId, (error, doc, folder_id, path, project) ->
|
||||
return callback(error) if error?
|
||||
projectHistoryId = project.overleaf?.history?.id
|
||||
newDocs = [
|
||||
doc: doc
|
||||
path: path
|
||||
docLines: docLines.join('\n')
|
||||
]
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newDocs}, (error) ->
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, {newDocs}, (error) ->
|
||||
return callback(error) if error?
|
||||
callback null, doc, folder_id
|
||||
|
||||
addFile: wrapWithLock (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (error, fileRef, folder_id) ->)->
|
||||
self.addFileWithoutUpdatingHistory.withoutLock project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, folder_id, path, fileStoreUrl) ->
|
||||
return callback(error) if error?
|
||||
newFiles = [
|
||||
file: fileRef
|
||||
path: path
|
||||
url: fileStoreUrl
|
||||
]
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, (error) ->
|
||||
return callback(error) if error?
|
||||
callback null, fileRef, folder_id
|
||||
_uploadFile: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (error, fileRef, fileStoreUrl) ->)->
|
||||
if not SafePath.isCleanFilename fileName
|
||||
return callback new Errors.InvalidNameError("invalid element name")
|
||||
fileRef = new File(
|
||||
name: fileName
|
||||
linkedFileData: linkedFileData
|
||||
)
|
||||
FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
|
||||
if err?
|
||||
logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error uploading image to s3"
|
||||
return callback(err)
|
||||
callback(null, fileRef, fileStoreUrl)
|
||||
|
||||
replaceFile: wrapWithLock (project_id, file_id, fsPath, linkedFileData, userId, callback)->
|
||||
FileStoreHandler.uploadFileFromDisk project_id, file_id, fsPath, (err, fileStoreUrl)->
|
||||
return callback(err) if err?
|
||||
ProjectEntityMongoUpdateHandler.replaceFile project_id, file_id, linkedFileData, (err, fileRef, project, path) ->
|
||||
_addFileAndSendToTpds: (project_id, folder_id, fileName, fileRef, callback = (error) ->)->
|
||||
ProjectEntityMongoUpdateHandler.addFile project_id, folder_id, fileRef, (err, result, project) ->
|
||||
if err?
|
||||
logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error adding file with project"
|
||||
return callback(err)
|
||||
TpdsUpdateSender.addFile {project_id:project_id, file_id:fileRef._id, path:result?.path?.fileSystem, project_name:project.name, rev:fileRef.rev}, (err) ->
|
||||
return callback(err) if err?
|
||||
callback(null, result, project)
|
||||
|
||||
addFile: wrapWithLock
|
||||
beforeLock: (next) ->
|
||||
(project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback) ->
|
||||
ProjectEntityUpdateHandler._uploadFile project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, fileStoreUrl) ->
|
||||
return callback(error) if error?
|
||||
next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback)
|
||||
withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback = (error, fileRef, folder_id) ->)->
|
||||
ProjectEntityUpdateHandler._addFileAndSendToTpds project_id, folder_id, fileName, fileRef, (err, result, project) ->
|
||||
return callback(err) if err?
|
||||
projectHistoryId = project.overleaf?.history?.id
|
||||
newFiles = [
|
||||
file: fileRef
|
||||
path: result?.path?.fileSystem
|
||||
url: fileStoreUrl
|
||||
]
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, {newFiles}, (error) ->
|
||||
return callback(error) if error?
|
||||
callback(null, fileRef, folder_id)
|
||||
|
||||
replaceFile: wrapWithLock
|
||||
beforeLock: (next) ->
|
||||
(project_id, file_id, fsPath, linkedFileData, userId, callback)->
|
||||
# create a new file
|
||||
fileRef = new File(
|
||||
name: "dummy-upload-filename"
|
||||
linkedFileData: linkedFileData
|
||||
)
|
||||
FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
|
||||
return callback(err) if err?
|
||||
next project_id, file_id, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback
|
||||
withLock: (project_id, file_id, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, callback)->
|
||||
ProjectEntityMongoUpdateHandler.replaceFileWithNew project_id, file_id, newFileRef, (err, oldFileRef, project, path) ->
|
||||
return callback(err) if err?
|
||||
oldFiles = [
|
||||
file: oldFileRef
|
||||
path: path.fileSystem
|
||||
]
|
||||
newFiles = [
|
||||
file: newFileRef
|
||||
path: path.fileSystem
|
||||
url: fileStoreUrl
|
||||
]
|
||||
TpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:path.fileSystem, rev:fileRef.rev+1, project_name:project.name}, (err) ->
|
||||
projectHistoryId = project.overleaf?.history?.id
|
||||
TpdsUpdateSender.addFile {project_id:project._id, file_id:newFileRef._id, path:path.fileSystem, rev:newFileRef.rev+1, project_name:project.name}, (err) ->
|
||||
return callback(err) if err?
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, callback
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, {oldFiles, newFiles}, callback
|
||||
|
||||
addDocWithoutUpdatingHistory: wrapWithLock (project_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=>
|
||||
# This method should never be called directly, except when importing a project
|
||||
@@ -178,31 +227,21 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||
rev: 0
|
||||
}, (err) ->
|
||||
return callback(err) if err?
|
||||
callback(null, doc, folder_id, result?.path?.fileSystem)
|
||||
callback(null, doc, folder_id, result?.path?.fileSystem, project)
|
||||
|
||||
addFileWithoutUpdatingHistory: wrapWithLock (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (error, fileRef, folder_id, path, fileStoreUrl) ->)->
|
||||
addFileWithoutUpdatingHistory: wrapWithLock
|
||||
# This method should never be called directly, except when importing a project
|
||||
# from Overleaf. It skips sending updates to the project history, which will break
|
||||
# the history unless you are making sure it is updated in some other way.
|
||||
|
||||
if not SafePath.isCleanFilename fileName
|
||||
return callback new Errors.InvalidNameError("invalid element name")
|
||||
|
||||
fileRef = new File(
|
||||
name: fileName
|
||||
linkedFileData: linkedFileData
|
||||
)
|
||||
FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
|
||||
if err?
|
||||
logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error uploading image to s3"
|
||||
return callback(err)
|
||||
ProjectEntityMongoUpdateHandler.addFile project_id, folder_id, fileRef, (err, result, project) ->
|
||||
if err?
|
||||
logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error adding file with project"
|
||||
return callback(err)
|
||||
TpdsUpdateSender.addFile {project_id:project_id, file_id:fileRef._id, path:result?.path?.fileSystem, project_name:project.name, rev:fileRef.rev}, (err) ->
|
||||
return callback(err) if err?
|
||||
callback(null, fileRef, folder_id, result?.path?.fileSystem, fileStoreUrl)
|
||||
beforeLock: (next) ->
|
||||
(project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback) ->
|
||||
ProjectEntityUpdateHandler._uploadFile project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, fileStoreUrl) ->
|
||||
return callback(error) if error?
|
||||
next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback)
|
||||
withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback = (error, fileRef, folder_id, path, fileStoreUrl) ->)->
|
||||
ProjectEntityUpdateHandler._addFileAndSendToTpds project_id, folder_id, fileName, fileRef, (err, result, project) ->
|
||||
return callback(err) if err?
|
||||
callback(null, fileRef, folder_id, result?.path?.fileSystem, fileStoreUrl)
|
||||
|
||||
upsertDoc: wrapWithLock (project_id, folder_id, docName, docLines, source, userId, callback = (err, doc, folder_id, isNewDoc)->)->
|
||||
ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
|
||||
@@ -224,23 +263,36 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||
return callback(err) if err?
|
||||
callback null, doc, !existingDoc?
|
||||
|
||||
upsertFile: wrapWithLock (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (err, file, isNewFile)->)->
|
||||
ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
|
||||
return callback(error) if error?
|
||||
return callback(new Error("Couldn't find folder")) if !folder?
|
||||
existingFile = null
|
||||
for fileRef in folder.fileRefs
|
||||
if fileRef.name == fileName
|
||||
existingFile = fileRef
|
||||
break
|
||||
if existingFile?
|
||||
self.replaceFile.withoutLock project_id, existingFile._id, fsPath, linkedFileData, userId, (err) ->
|
||||
upsertFile: wrapWithLock
|
||||
beforeLock: (next) ->
|
||||
(project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback)->
|
||||
# create a new file
|
||||
fileRef = new File(
|
||||
name: fileName
|
||||
linkedFileData: linkedFileData
|
||||
)
|
||||
FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
|
||||
return callback(err) if err?
|
||||
callback null, existingFile, !existingFile?
|
||||
else
|
||||
self.addFile.withoutLock project_id, folder_id, fileName, fsPath, linkedFileData, userId, (err, file) ->
|
||||
return callback(err) if err?
|
||||
callback null, file, !existingFile?
|
||||
next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback)
|
||||
withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, callback = (err, file, isNewFile, existingFile)->)->
|
||||
ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
|
||||
return callback(error) if error?
|
||||
return callback(new Error("Couldn't find folder")) if !folder?
|
||||
existingFile = null
|
||||
for fileRef in folder.fileRefs
|
||||
if fileRef.name == fileName
|
||||
existingFile = fileRef
|
||||
break
|
||||
if existingFile?
|
||||
# this calls directly into the replaceFile main task (without the beforeLock part)
|
||||
self.replaceFile.mainTask project_id, existingFile._id, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, (err) ->
|
||||
return callback(err) if err?
|
||||
callback null, newFileRef, !existingFile?, existingFile
|
||||
else
|
||||
# this calls directly into the addFile main task (without the beforeLock part)
|
||||
self.addFile.mainTask project_id, folder_id, fileName, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, (err) ->
|
||||
return callback(err) if err?
|
||||
callback null, newFileRef, !existingFile?, existingFile
|
||||
|
||||
upsertDocWithPath: wrapWithLock (project_id, elementPath, docLines, source, userId, callback) ->
|
||||
docName = path.basename(elementPath)
|
||||
@@ -251,14 +303,26 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||
return callback(err) if err?
|
||||
callback null, doc, isNewDoc, newFolders, folder
|
||||
|
||||
upsertFileWithPath: wrapWithLock (project_id, elementPath, fsPath, linkedFileData, userId, callback) ->
|
||||
fileName = path.basename(elementPath)
|
||||
folderPath = path.dirname(elementPath)
|
||||
self.mkdirp.withoutLock project_id, folderPath, (err, newFolders, folder) ->
|
||||
return callback(err) if err?
|
||||
self.upsertFile.withoutLock project_id, folder._id, fileName, fsPath, linkedFileData, userId, (err, file, isNewFile) ->
|
||||
upsertFileWithPath: wrapWithLock
|
||||
beforeLock: (next) ->
|
||||
(project_id, elementPath, fsPath, linkedFileData, userId, callback)->
|
||||
fileName = path.basename(elementPath)
|
||||
folderPath = path.dirname(elementPath)
|
||||
# create a new file
|
||||
fileRef = new File(
|
||||
name: fileName
|
||||
linkedFileData: linkedFileData
|
||||
)
|
||||
FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
|
||||
return callback(err) if err?
|
||||
next project_id, folderPath, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback
|
||||
withLock: (project_id, folderPath, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback) ->
|
||||
self.mkdirp.withoutLock project_id, folderPath, (err, newFolders, folder) ->
|
||||
return callback(err) if err?
|
||||
callback null, file, isNewFile, newFolders, folder
|
||||
# this calls directly into the upsertFile main task (without the beforeLock part)
|
||||
self.upsertFile.mainTask project_id, folder._id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, (err, newFile, isNewFile, existingFile) ->
|
||||
return callback(err) if err?
|
||||
callback null, newFile, isNewFile, existingFile, newFolders, folder
|
||||
|
||||
deleteEntity: wrapWithLock (project_id, entity_id, entityType, userId, callback = (error) ->)->
|
||||
logger.log entity_id:entity_id, entityType:entityType, project_id:project_id, "deleting project entity"
|
||||
@@ -294,10 +358,11 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||
logger.err {err: "No entityType set", project_id, entity_id}
|
||||
return callback("No entityType set")
|
||||
entityType = entityType.toLowerCase()
|
||||
ProjectEntityMongoUpdateHandler.moveEntity project_id, entity_id, destFolderId, entityType, (err, project_name, startPath, endPath, rev, changes) ->
|
||||
ProjectEntityMongoUpdateHandler.moveEntity project_id, entity_id, destFolderId, entityType, (err, project, startPath, endPath, rev, changes) ->
|
||||
return callback(err) if err?
|
||||
TpdsUpdateSender.moveEntity { project_id, project_name, startPath, endPath, rev }
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback
|
||||
projectHistoryId = project.overleaf?.history?.id
|
||||
TpdsUpdateSender.moveEntity { project_id, project_name: project.name, startPath, endPath, rev }
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, changes, callback
|
||||
|
||||
renameEntity: wrapWithLock (project_id, entity_id, entityType, newName, userId, callback)->
|
||||
if not SafePath.isCleanFilename newName
|
||||
@@ -308,10 +373,11 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||
return callback("No entityType set")
|
||||
entityType = entityType.toLowerCase()
|
||||
|
||||
ProjectEntityMongoUpdateHandler.renameEntity project_id, entity_id, entityType, newName, (err, project_name, startPath, endPath, rev, changes) ->
|
||||
ProjectEntityMongoUpdateHandler.renameEntity project_id, entity_id, entityType, newName, (err, project, startPath, endPath, rev, changes) ->
|
||||
return callback(err) if err?
|
||||
TpdsUpdateSender.moveEntity({project_id, startPath, endPath, project_name, rev})
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback
|
||||
projectHistoryId = project.overleaf?.history?.id
|
||||
TpdsUpdateSender.moveEntity { project_id, project_name: project.name, startPath, endPath, rev }
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, changes, callback
|
||||
|
||||
# This doesn't directly update project structure but we need to take the lock
|
||||
# to prevent anything else being queued before the resync update
|
||||
@@ -319,7 +385,8 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||
ProjectGetter.getProject project_id, rootFolder: true, overleaf: true, (error, project) ->
|
||||
return callback(error) if error?
|
||||
|
||||
if !project?.overleaf?.history?.id?
|
||||
projectHistoryId = project?.overleaf?.history?.id
|
||||
if !projectHistoryId?
|
||||
error = new Errors.ProjectHistoryDisabledError("project history not enabled for #{project_id}")
|
||||
return callback(error)
|
||||
|
||||
@@ -335,7 +402,8 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||
path: file.path
|
||||
url: FileStoreHandler._buildUrl(project_id, file.file._id)
|
||||
|
||||
DocumentUpdaterHandler.resyncProjectHistory project_id, docs, files, callback
|
||||
DocumentUpdaterHandler.resyncProjectHistory project_id, projectHistoryId, docs, files, callback
|
||||
|
||||
_cleanUpEntity: (project, entity, entityType, path, userId, callback = (error) ->) ->
|
||||
if(entityType.indexOf("file") != -1)
|
||||
self._cleanUpFile project, entity, path, userId, callback
|
||||
@@ -357,22 +425,25 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||
|
||||
unsetRootDocIfRequired (error) ->
|
||||
return callback(error) if error?
|
||||
self._insertDeletedDocReference project._id, doc, (error) ->
|
||||
ProjectEntityMongoUpdateHandler._insertDeletedDocReference project._id, doc, (error) ->
|
||||
return callback(error) if error?
|
||||
DocumentUpdaterHandler.deleteDoc project_id, doc_id, (error) ->
|
||||
return callback(error) if error?
|
||||
DocstoreManager.deleteDoc project_id, doc_id, (error) ->
|
||||
return callback(error) if error?
|
||||
changes = oldDocs: [ {doc, path} ]
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback
|
||||
projectHistoryId = project.overleaf?.history?.id
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, changes, callback
|
||||
|
||||
_cleanUpFile: (project, file, path, userId, callback = (error) ->) ->
|
||||
project_id = project._id.toString()
|
||||
file_id = file._id.toString()
|
||||
FileStoreHandler.deleteFile project_id, file_id, (error) ->
|
||||
ProjectEntityMongoUpdateHandler._insertDeletedFileReference project._id, file, (error) ->
|
||||
return callback(error) if error?
|
||||
project_id = project._id.toString()
|
||||
projectHistoryId = project.overleaf?.history?.id
|
||||
changes = oldFiles: [ {file, path} ]
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback
|
||||
# we are now keeping a copy of every file versio so we no longer delete
|
||||
# the file from the filestore
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, changes, callback
|
||||
|
||||
_cleanUpFolder: (project, folder, folderPath, userId, callback = (error) ->) ->
|
||||
jobs = []
|
||||
@@ -392,15 +463,3 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||
jobs.push (callback) -> self._cleanUpFolder project, childFolder, folderPath, userId, callback
|
||||
|
||||
async.series jobs, callback
|
||||
|
||||
_insertDeletedDocReference: (project_id, doc, callback = (error) ->) ->
|
||||
Project.update {
|
||||
_id: project_id
|
||||
}, {
|
||||
$push: {
|
||||
deletedDocs: {
|
||||
_id: doc._id
|
||||
name: doc.name
|
||||
}
|
||||
}
|
||||
}, {}, callback
|
||||
|
||||
@@ -15,6 +15,7 @@ htmlEncoder = new require("node-html-encoder").Encoder("numerical")
|
||||
hashedFiles = {}
|
||||
Path = require 'path'
|
||||
Features = require "./Features"
|
||||
Modules = require "./Modules"
|
||||
|
||||
jsPath =
|
||||
if Settings.useMinifiedJs
|
||||
@@ -41,10 +42,9 @@ pathList = [
|
||||
"#{jsPath}ide.js"
|
||||
"#{jsPath}main.js"
|
||||
"#{jsPath}libraries.js"
|
||||
"#{jsPath}es/richText.js"
|
||||
"/stylesheets/style.css"
|
||||
"/stylesheets/ol-style.css"
|
||||
]
|
||||
].concat(Modules.moduleAssetFiles(jsPath))
|
||||
|
||||
if !Settings.useMinifiedJs
|
||||
logger.log "not using minified JS, not hashing static files"
|
||||
@@ -150,6 +150,8 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)->
|
||||
res.locals.buildWebpackPath = (jsFile, opts = {}) ->
|
||||
if Settings.webpack? and !Settings.useMinifiedJs
|
||||
path = Path.join(jsPath, jsFile)
|
||||
if opts.removeExtension == true
|
||||
path = path.slice(0,-3)
|
||||
return "#{Settings.webpack.url}/public#{path}"
|
||||
else
|
||||
return res.locals.buildJsPath(jsFile, opts)
|
||||
@@ -309,11 +311,11 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)->
|
||||
webRouter.use (req, res, next) ->
|
||||
isOl = (Settings.brandPrefix == 'ol-')
|
||||
res.locals.uiConfig =
|
||||
defaultResizerSizeOpen : if isOl then 2 else 24
|
||||
defaultResizerSizeClosed : if isOl then 2 else 24
|
||||
defaultResizerSizeOpen : if isOl then 7 else 24
|
||||
defaultResizerSizeClosed : if isOl then 7 else 24
|
||||
eastResizerCursor : if isOl then "ew-resize" else null
|
||||
westResizerCursor : if isOl then "ew-resize" else null
|
||||
chatResizerSizeOpen : if isOl then 2 else 12
|
||||
chatResizerSizeOpen : if isOl then 7 else 12
|
||||
chatResizerSizeClosed : 0
|
||||
chatMessageBorderSaturation: if isOl then "85%" else "70%"
|
||||
chatMessageBorderLightness : if isOl then "40%" else "70%"
|
||||
|
||||
@@ -14,7 +14,9 @@ module.exports = Features =
|
||||
return Settings.enableGithubSync
|
||||
when 'v1-return-message'
|
||||
return Settings.accountMerge? and Settings.overleaf?
|
||||
when 'rich-text'
|
||||
return Settings.showRichText
|
||||
when 'publish-modal'
|
||||
return Settings.showPublishModal
|
||||
when 'custom-togglers'
|
||||
return Settings.overleaf?
|
||||
else
|
||||
throw new Error("unknown feature: #{feature}")
|
||||
|
||||
@@ -3,21 +3,40 @@ logger = require 'logger-sharelatex'
|
||||
uuid = require 'uuid'
|
||||
_ = require 'underscore'
|
||||
Settings = require 'settings-sharelatex'
|
||||
request = require 'request'
|
||||
|
||||
module.exports =
|
||||
module.exports = FileWriter =
|
||||
writeStreamToDisk: (identifier, stream, callback = (error, fsPath) ->) ->
|
||||
callback = _.once(callback)
|
||||
fsPath = "#{Settings.path.dumpFolder}/#{identifier}_#{uuid.v4()}"
|
||||
|
||||
writeStream = fs.createWriteStream(fsPath)
|
||||
stream.pipe(writeStream)
|
||||
stream.pause()
|
||||
fs.mkdir Settings.path.dumpFolder, (error) ->
|
||||
stream.resume()
|
||||
if error? and error.code != 'EEXIST'
|
||||
# Ignore error about already existing
|
||||
return callback(error)
|
||||
|
||||
stream.on 'error', (err)->
|
||||
logger.err {err, identifier, fsPath}, "[writeStreamToDisk] something went wrong with incoming stream"
|
||||
callback(err)
|
||||
writeStream.on 'error', (err)->
|
||||
logger.err {err, identifier, fsPath}, "[writeStreamToDisk] something went wrong with writing to disk"
|
||||
callback(err)
|
||||
writeStream.on "finish", ->
|
||||
logger.log {identifier, fsPath}, "[writeStreamToDisk] write stream finished"
|
||||
callback null, fsPath
|
||||
writeStream = fs.createWriteStream(fsPath)
|
||||
stream.pipe(writeStream)
|
||||
|
||||
stream.on 'error', (err)->
|
||||
logger.err {err, identifier, fsPath}, "[writeStreamToDisk] something went wrong with incoming stream"
|
||||
callback(err)
|
||||
writeStream.on 'error', (err)->
|
||||
logger.err {err, identifier, fsPath}, "[writeStreamToDisk] something went wrong with writing to disk"
|
||||
callback(err)
|
||||
writeStream.on "finish", ->
|
||||
logger.log {identifier, fsPath}, "[writeStreamToDisk] write stream finished"
|
||||
callback null, fsPath
|
||||
|
||||
writeUrlToDisk: (identifier, url, callback = (error, fsPath) ->) ->
|
||||
callback = _.once(callback)
|
||||
stream = request.get(url)
|
||||
stream.on 'response', (response) ->
|
||||
if 200 <= response.statusCode < 300
|
||||
FileWriter.writeStreamToDisk identifier, stream, callback
|
||||
else
|
||||
err = new Error("bad response from url: #{response.statusCode}")
|
||||
logger.err {err, identifier, url}, err.message
|
||||
callback(err)
|
||||
@@ -22,6 +22,8 @@ module.exports =
|
||||
}
|
||||
|
||||
docs: (docs) ->
|
||||
if !docs?.map?
|
||||
return
|
||||
docs.map (doc) ->
|
||||
{
|
||||
path: doc.path
|
||||
@@ -29,6 +31,8 @@ module.exports =
|
||||
}
|
||||
|
||||
files: (files) ->
|
||||
if !files?.map?
|
||||
return
|
||||
files.map (file) ->
|
||||
{
|
||||
path: file.path
|
||||
|
||||
@@ -43,6 +43,13 @@ module.exports = Modules =
|
||||
moduleIncludesAvailable: (view) ->
|
||||
return (Modules.viewIncludes[view] or []).length > 0
|
||||
|
||||
moduleAssetFiles: (pathPrefix) ->
|
||||
assetFiles = []
|
||||
for module in @modules
|
||||
for assetFile in module.assetFiles or []
|
||||
assetFiles.push "#{pathPrefix}#{assetFile}"
|
||||
return assetFiles
|
||||
|
||||
attachHooks: () ->
|
||||
for module in @modules
|
||||
if module.hooks?
|
||||
|
||||
@@ -12,6 +12,10 @@ ObjectId = Schema.ObjectId
|
||||
DeletedDocSchema = new Schema
|
||||
name: String
|
||||
|
||||
DeletedFileSchema = new Schema
|
||||
name: String
|
||||
deletedAt: {type: Date}
|
||||
|
||||
ProjectSchema = new Schema
|
||||
name : {type:String, default:'new project'}
|
||||
lastUpdated : {type:Date, default: () -> new Date()}
|
||||
@@ -30,6 +34,7 @@ ProjectSchema = new Schema
|
||||
description : {type:String, default:''}
|
||||
archived : { type: Boolean }
|
||||
deletedDocs : [DeletedDocSchema]
|
||||
deletedFiles : [DeletedFileSchema]
|
||||
imageName : { type: String }
|
||||
track_changes : { type: Object }
|
||||
tokens :
|
||||
|
||||
@@ -20,40 +20,42 @@ UserSchema = new Schema
|
||||
loginCount : {type : Number, default: 0}
|
||||
holdingAccount : {type : Boolean, default: false}
|
||||
ace : {
|
||||
mode : {type : String, default: 'none'}
|
||||
theme : {type : String, default: 'textmate'}
|
||||
fontSize : {type : Number, default:'12'}
|
||||
autoComplete: {type : Boolean, default: true}
|
||||
autoPairDelimiters: {type : Boolean, default: true}
|
||||
spellCheckLanguage : {type : String, default: "en"}
|
||||
pdfViewer : {type : String, default: "pdfjs"}
|
||||
syntaxValidation : {type : Boolean}
|
||||
}
|
||||
mode : {type : String, default: 'none'}
|
||||
theme : {type : String, default: 'textmate'}
|
||||
fontSize : {type : Number, default:'12'}
|
||||
autoComplete: {type : Boolean, default: true}
|
||||
autoPairDelimiters: {type : Boolean, default: true}
|
||||
spellCheckLanguage : {type : String, default: "en"}
|
||||
pdfViewer : {type : String, default: "pdfjs"}
|
||||
syntaxValidation : {type : Boolean}
|
||||
}
|
||||
features : {
|
||||
collaborators: { type:Number, default: Settings.defaultFeatures.collaborators }
|
||||
versioning: { type:Boolean, default: Settings.defaultFeatures.versioning }
|
||||
dropbox: { type:Boolean, default: Settings.defaultFeatures.dropbox }
|
||||
github: { type:Boolean, default: Settings.defaultFeatures.github }
|
||||
compileTimeout: { type:Number, default: Settings.defaultFeatures.compileTimeout }
|
||||
compileGroup: { type:String, default: Settings.defaultFeatures.compileGroup }
|
||||
templates: { type:Boolean, default: Settings.defaultFeatures.templates }
|
||||
references: { type:Boolean, default: Settings.defaultFeatures.references }
|
||||
trackChanges: { type:Boolean, default: Settings.defaultFeatures.trackChanges }
|
||||
}
|
||||
collaborators: { type:Number, default: Settings.defaultFeatures.collaborators }
|
||||
versioning: { type:Boolean, default: Settings.defaultFeatures.versioning }
|
||||
dropbox: { type:Boolean, default: Settings.defaultFeatures.dropbox }
|
||||
github: { type:Boolean, default: Settings.defaultFeatures.github }
|
||||
compileTimeout: { type:Number, default: Settings.defaultFeatures.compileTimeout }
|
||||
compileGroup: { type:String, default: Settings.defaultFeatures.compileGroup }
|
||||
templates: { type:Boolean, default: Settings.defaultFeatures.templates }
|
||||
references: { type:Boolean, default: Settings.defaultFeatures.references }
|
||||
trackChanges: { type:Boolean, default: Settings.defaultFeatures.trackChanges }
|
||||
mendeley: { type:Boolean, default: Settings.defaultFeatures.mendeley }
|
||||
referencesSearch: { type:Boolean, default: Settings.defaultFeatures.referencesSearch }
|
||||
}
|
||||
referal_id : {type:String, default:() -> uuid.v4().split("-")[0]}
|
||||
refered_users: [ type:ObjectId, ref:'User' ]
|
||||
refered_user_count: { type:Number, default: 0 }
|
||||
subscription:
|
||||
recurlyToken : String
|
||||
freeTrialExpiresAt: Date
|
||||
freeTrialDowngraded: Boolean
|
||||
freeTrialPlanCode: String
|
||||
# This is poorly named. It does not directly correspond
|
||||
# to whether the user has has a free trial, but rather
|
||||
# whether they should be allowed one in the future.
|
||||
# For example, a user signing up directly for a paid plan
|
||||
# has this set to true, despite never having had a free trial
|
||||
hadFreeTrial: {type: Boolean, default: false}
|
||||
recurlyToken : String
|
||||
freeTrialExpiresAt: Date
|
||||
freeTrialDowngraded: Boolean
|
||||
freeTrialPlanCode: String
|
||||
# This is poorly named. It does not directly correspond
|
||||
# to whether the user has has a free trial, but rather
|
||||
# whether they should be allowed one in the future.
|
||||
# For example, a user signing up directly for a paid plan
|
||||
# has this set to true, despite never having had a free trial
|
||||
hadFreeTrial: {type: Boolean, default: false}
|
||||
refProviders: {
|
||||
mendeley: Boolean # coerce the refProviders values to Booleans
|
||||
zotero: Boolean
|
||||
|
||||
@@ -201,8 +201,11 @@ module.exports = class Router
|
||||
webRouter.get "/project/:Project_id/doc/:doc_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
||||
webRouter.get "/project/:Project_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApiAndInjectUserDetails
|
||||
webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
||||
webRouter.post '/project/:project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreDocFromDeletedDoc
|
||||
webRouter.post "/project/:project_id/restore_file", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreFileFromV2
|
||||
privateApiRouter.post "/project/:Project_id/history/resync", AuthenticationController.httpAuth, HistoryController.resyncProjectHistory
|
||||
|
||||
|
||||
webRouter.get '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject
|
||||
webRouter.get '/project/download/zip', AuthorizationMiddlewear.ensureUserCanReadMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
|
||||
|
||||
@@ -261,9 +264,10 @@ module.exports = class Router
|
||||
webRouter.post "/project/:Project_id/references/index", AuthorizationMiddlewear.ensureUserCanReadProject, ReferencesController.index
|
||||
webRouter.post "/project/:Project_id/references/indexAll", AuthorizationMiddlewear.ensureUserCanReadProject, ReferencesController.indexAll
|
||||
|
||||
webRouter.get "/beta/participate", AuthenticationController.requireLogin(), BetaProgramController.optInPage
|
||||
webRouter.post "/beta/opt-in", AuthenticationController.requireLogin(), BetaProgramController.optIn
|
||||
webRouter.post "/beta/opt-out", AuthenticationController.requireLogin(), BetaProgramController.optOut
|
||||
# disable beta program while v2 is in beta
|
||||
# webRouter.get "/beta/participate", AuthenticationController.requireLogin(), BetaProgramController.optInPage
|
||||
# webRouter.post "/beta/opt-in", AuthenticationController.requireLogin(), BetaProgramController.optIn
|
||||
# webRouter.post "/beta/opt-out", AuthenticationController.requireLogin(), BetaProgramController.optOut
|
||||
webRouter.get "/confirm-password", AuthenticationController.requireLogin(), SudoModeController.sudoModePrompt
|
||||
webRouter.post "/confirm-password", AuthenticationController.requireLogin(), SudoModeController.submitPassword
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ html.full-height(itemscope, itemtype='http://schema.org/Product')
|
||||
title Something went wrong
|
||||
link(rel="icon", href="/favicon.ico")
|
||||
if buildCssPath
|
||||
link(rel='stylesheet', href=buildCssPath('/style.css'))
|
||||
link(rel="stylesheet", href=buildCssPath("/" + settings.brandPrefix + "style.css"))
|
||||
link(href="//netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css",rel="stylesheet")
|
||||
body.full-height
|
||||
.content.full-height
|
||||
@@ -18,19 +18,4 @@ html.full-height(itemscope, itemtype='http://schema.org/Product')
|
||||
.error-details
|
||||
p.error-status Something went wrong, sorry.
|
||||
p.error-description Our staff are probably looking into this, but if it continues, please contact us at #{settings.adminEmail}
|
||||
a.error-btn(href="/") Home
|
||||
//- .content
|
||||
//- .container
|
||||
//- .row
|
||||
//- .col-md-8.col-md-offset-2.text-center
|
||||
//- .page-header
|
||||
//- h2 Oh dear, something went wrong.
|
||||
//- if buildImgPath
|
||||
//- p
|
||||
//- img(src=buildImgPath("lion-sad-128.png"), alt="Sad Lion")
|
||||
//- p
|
||||
//- | Something went wrong with your request, sorry. Our staff are probably looking into this, but if it continues, please contact us at #{settings.adminEmail}
|
||||
//- p
|
||||
//- a(href="/")
|
||||
//- i.fa.fa-arrow-circle-o-left
|
||||
//- | Take me home
|
||||
a.error-btn(href="/") Home
|
||||
@@ -21,7 +21,7 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||
link(rel="icon", href="/" + settings.brandPrefix + "favicon.ico")
|
||||
link(rel="icon", sizes="192x192", href="/" + settings.brandPrefix + "touch-icon-192x192.png")
|
||||
link(rel="apple-touch-icon-precomposed", href="/" + settings.brandPrefix + "apple-touch-icon-precomposed.png")
|
||||
link(rel="mask-icon", href="/" + settings.brandPrefix + "mask-favicon.svg", color="#a93529")
|
||||
link(rel="mask-icon", href="/" + settings.brandPrefix + "mask-favicon.svg", color=settings.brandPrefix === 'ol-' ? "#4f9c45" : "#a93529")
|
||||
|
||||
link(rel='stylesheet', href=buildCssPath("/" + settings.brandPrefix + "style.css", {hashedPath:true}))
|
||||
|
||||
@@ -48,7 +48,7 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', '#{gaToken}', 'sharelatex.com');
|
||||
ga('create', '#{gaToken}', '#{settings.cookieDomain.replace(/^\./, "")}');
|
||||
ga('send', 'pageview');
|
||||
- else
|
||||
script(type='text/javascript').
|
||||
@@ -111,22 +111,6 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||
data-badge="inline"
|
||||
)
|
||||
|
||||
- if(typeof(suppressSystemMessages) == "undefined")
|
||||
.system-messages(
|
||||
ng-cloak
|
||||
ng-controller="SystemMessagesController"
|
||||
)
|
||||
.system-message(
|
||||
ng-repeat="message in messages"
|
||||
ng-controller="SystemMessageController"
|
||||
ng-hide="hidden"
|
||||
)
|
||||
a(href, ng-click="hide()").pull-right ×
|
||||
.system-message-content(ng-bind-html="htmlContent")
|
||||
|
||||
include translations/translation_message
|
||||
|
||||
|
||||
- if(typeof(suppressNavbar) == "undefined")
|
||||
include layout/navbar
|
||||
|
||||
|
||||
@@ -55,13 +55,18 @@ block content
|
||||
include ./editor/header
|
||||
|
||||
include ./editor/share
|
||||
!= moduleIncludes("publish:body", locals)
|
||||
|
||||
#ide-body(
|
||||
main#ide-body(
|
||||
ng-cloak,
|
||||
role="main",
|
||||
layout="main",
|
||||
ng-hide="state.loading",
|
||||
resize-on="layout:chat:resize",
|
||||
minimum-restore-size-west="130"
|
||||
custom-toggler-pane=hasFeature('custom-togglers') ? "'west'" : "false"
|
||||
custom-toggler-msg-when-open=hasFeature('custom-togglers') ? "'" + translate("tooltip_hide_filetree") + "'" : "false"
|
||||
custom-toggler-msg-when-closed=hasFeature('custom-togglers') ? "'" + translate("tooltip_show_filetree") + "'" : "false"
|
||||
)
|
||||
.ui-layout-west
|
||||
include ./editor/file-tree
|
||||
@@ -133,6 +138,8 @@ block requirejs
|
||||
"fineuploader": "libs/#{lib('fineuploader')}",
|
||||
"ide": "#{buildJsPath('ide.js', {hashedPath:settings.useMinifiedJs, removeExtension:true})}",
|
||||
"libraries": "#{buildJsPath('libraries.js', {hashedPath:settings.useMinifiedJs, removeExtension:true})}",
|
||||
!{moduleIncludes("editor:script", locals)}
|
||||
!{moduleIncludes("publish:script", locals)}
|
||||
},
|
||||
"waitSeconds": 0,
|
||||
"shim": {
|
||||
@@ -159,12 +166,6 @@ block requirejs
|
||||
window.pdfCMapsPath = "#{pdfCMapsPath}"
|
||||
window.uiConfig = JSON.parse('!{JSON.stringify(uiConfig).replace(/\//g, "\\/")}');
|
||||
|
||||
if hasFeature('rich-text')
|
||||
script(
|
||||
src=buildWebpackPath('es/richText.js', {hashedPath:settings.useMinifiedJs})
|
||||
type="text/javascript"
|
||||
)
|
||||
|
||||
script(
|
||||
data-main=buildJsPath("ide.js", {hashedPath:false}),
|
||||
baseurl=fullJsPath,
|
||||
|
||||
@@ -35,7 +35,7 @@ div.binary-file.full-size(
|
||||
|
||||
p.no-preview(
|
||||
ng-if="failedLoad || textPreview.error || isUnpreviewableFile()"
|
||||
) #{translate("no_preview_available")} {{ failedLoad }} {{ textPreview.error }} {{ isUnpreviewableFile() }}
|
||||
) #{translate("no_preview_available")}
|
||||
|
||||
div.binary-file-footer
|
||||
div(ng-if="openFile.linkedFileData.provider == 'url'")
|
||||
|
||||
@@ -8,6 +8,9 @@ div.full-size(
|
||||
initial-size-east="'50%'"
|
||||
minimum-restore-size-east="300"
|
||||
allow-overflow-on="'center'"
|
||||
custom-toggler-pane=hasFeature('custom-togglers') ? "'east'" : "false"
|
||||
custom-toggler-msg-when-open=hasFeature('custom-togglers') ? "'" + translate("tooltip_hide_pdf") + "'" : "false"
|
||||
custom-toggler-msg-when-closed=hasFeature('custom-togglers') ? "'" + translate("tooltip_show_pdf") + "'" : "false"
|
||||
)
|
||||
.ui-layout-center(
|
||||
ng-controller="ReviewPanelController",
|
||||
@@ -30,14 +33,13 @@ div.full-size(
|
||||
i.fa.fa-arrow-left
|
||||
| #{translate("open_a_file_on_the_left")}
|
||||
|
||||
if hasFeature('rich-text')
|
||||
.toolbar.toolbar-editor(ng-controller="EditorToolbarController")
|
||||
button(ng-click="toggleRichText()") Rich Text
|
||||
!= moduleIncludes('editor:toolbar', locals)
|
||||
|
||||
#editor(
|
||||
ace-editor="editor",
|
||||
ng-if="!editor.richText",
|
||||
ng-show="!!editor.sharejs_doc && !editor.opening",
|
||||
style=showRichText ? "top: 32px" : "",
|
||||
theme="settings.theme",
|
||||
keybindings="settings.mode",
|
||||
font-size="settings.fontSize",
|
||||
@@ -68,13 +70,7 @@ div.full-size(
|
||||
renderer-data="reviewPanel.rendererData"
|
||||
)
|
||||
|
||||
if hasFeature('rich-text')
|
||||
#editor-rich-text(
|
||||
cm-editor,
|
||||
ng-if="editor.richText"
|
||||
ng-show="!!editor.sharejs_doc && !editor.opening"
|
||||
sharejs-doc="editor.sharejs_doc"
|
||||
)
|
||||
!= moduleIncludes('editor:body', locals)
|
||||
|
||||
include ./review-panel
|
||||
|
||||
@@ -100,7 +96,6 @@ div.full-size(
|
||||
ng-click="syncToCode()"
|
||||
)
|
||||
i.synctex-control-icon
|
||||
|
||||
div.full-size(
|
||||
ng-if="ui.pdfLayout == 'flat'"
|
||||
ng-show="ui.view == 'pdf'"
|
||||
|
||||
@@ -107,6 +107,9 @@ header.toolbar.toolbar-header.toolbar-with-labels(
|
||||
)
|
||||
i.fa.fa-fw.fa-group
|
||||
p.toolbar-label #{translate("share")}
|
||||
|
||||
!= moduleIncludes('publish:button', locals)
|
||||
|
||||
a.btn.btn-full-height(
|
||||
href,
|
||||
ng-click="toggleHistory();",
|
||||
|
||||
@@ -11,7 +11,7 @@ aside.file-tree.file-tree-history(ng-controller="FileTreeController", ng-class="
|
||||
.entity
|
||||
.entity-name.entity-name-history(
|
||||
ng-click="history.selection.pathname = pathname",
|
||||
ng-class="{ 'deleted': doc.deleted }"
|
||||
ng-class="{ 'deleted': !!doc.deletedAtV }"
|
||||
)
|
||||
i.fa.fa-fw.fa-pencil
|
||||
span {{ pathname }}
|
||||
|
||||
@@ -1,80 +1,39 @@
|
||||
div#history(ng-show="ui.view == 'history'")
|
||||
span(ng-controller="HistoryPremiumPopup")
|
||||
span
|
||||
.upgrade-prompt(ng-if="project.features.versioning === false && ui.view === 'history'")
|
||||
|
||||
div(ng-if="project.owner._id == user.id")
|
||||
div(sixpack-switch="teaser-history")
|
||||
.message(sixpack-default)
|
||||
p.text-center: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})}
|
||||
p.text-center.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")}
|
||||
ul.list-unstyled
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("unlimited_projects")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("full_doc_history")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("sync_to_dropbox")}
|
||||
.message(ng-if="project.owner._id == user.id")
|
||||
p.text-center: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})}
|
||||
p.text-center.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")}
|
||||
ul.list-unstyled
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("unlimited_projects")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("full_doc_history")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("sync_to_dropbox")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("sync_to_github")}
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("sync_to_github")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
|#{translate("compile_larger_projects")}
|
||||
p.text-center(ng-controller="FreeTrialModalController")
|
||||
a.btn.btn-success(
|
||||
href
|
||||
ng-class="buttonClass"
|
||||
ng-click="startFreeTrial('history')"
|
||||
sixpack-convert="teaser-history"
|
||||
) #{translate("start_free_trial")}
|
||||
|
||||
.message.message-wider(sixpack-when="focused")
|
||||
header.message-header
|
||||
h3 History
|
||||
|
||||
.message-body
|
||||
h4.teaser-title See who changed what. Go back to previous versions.
|
||||
img.teaser-img(
|
||||
src="/img/teasers/history/teaser-history.png"
|
||||
alt="History"
|
||||
)
|
||||
p.text-center.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")}
|
||||
.row
|
||||
.col-md-8.col-md-offset-2
|
||||
ul.list-unstyled
|
||||
li
|
||||
i.fa.fa-check
|
||||
| Catch up with your collaborators changes
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| See changes over any time period
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| Revert your documents to previous versions
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| Restore deleted files
|
||||
p.text-center(ng-controller="FreeTrialModalController")
|
||||
a.btn.btn-success(
|
||||
href
|
||||
ng-class="buttonClass"
|
||||
ng-click="startFreeTrial('history')"
|
||||
sixpack-convert="teaser-history"
|
||||
) Try it for free
|
||||
li
|
||||
i.fa.fa-check
|
||||
|#{translate("compile_larger_projects")}
|
||||
p.text-center(ng-controller="FreeTrialModalController")
|
||||
a.btn.btn-success(
|
||||
href
|
||||
ng-class="buttonClass"
|
||||
ng-click="startFreeTrial('history')"
|
||||
) #{translate("start_free_trial")}
|
||||
|
||||
.message(ng-show="project.owner._id != user.id")
|
||||
p #{translate("ask_proj_owner_to_upgrade_for_history")}
|
||||
@@ -163,74 +122,8 @@ div#history(ng-show="ui.view == 'history'")
|
||||
i.fa.fa-spin.fa-refresh
|
||||
| #{translate("loading")}...
|
||||
|
||||
.diff-panel.full-size(ng-controller="HistoryDiffController")
|
||||
.diff(
|
||||
ng-if="!!history.diff && !history.diff.loading && !history.diff.deleted && !history.diff.error && !history.diff.binary"
|
||||
)
|
||||
.toolbar.toolbar-alt
|
||||
span.name
|
||||
| <strong>{{history.diff.highlights.length}} </strong>
|
||||
ng-pluralize(
|
||||
count="history.diff.highlights.length",
|
||||
when="{\
|
||||
'one': 'change',\
|
||||
'other': 'changes'\
|
||||
}"
|
||||
)
|
||||
| in <strong>{{history.diff.pathname}}</strong>
|
||||
.toolbar-right
|
||||
a.btn.btn-danger.btn-sm(
|
||||
href,
|
||||
ng-if="!history.isV2"
|
||||
ng-click="openRestoreDiffModal()"
|
||||
) #{translate("restore_to_before_these_changes")}
|
||||
.deleted-warning(
|
||||
ng-show="history.selection.docs[history.selection.pathname].deleted"
|
||||
) This file was deleted
|
||||
.diff-editor.hide-ace-cursor(
|
||||
ace-editor="history",
|
||||
theme="settings.theme",
|
||||
font-size="settings.fontSize",
|
||||
text="history.diff.text",
|
||||
highlights="history.diff.highlights",
|
||||
read-only="true",
|
||||
resize-on="layout:main:resize",
|
||||
navigate-highlights="true"
|
||||
)
|
||||
|
||||
.diff.diff-binary(ng-show="history.diff.binary")
|
||||
.toolbar.toolbar-alt
|
||||
span.name
|
||||
strong {{history.diff.pathname}}
|
||||
.alert.alert-info We're still working on showing image and binary changes, sorry. Stay tuned!
|
||||
|
||||
.diff-deleted.text-centered(
|
||||
ng-show="history.diff.deleted && !history.diff.restoreDeletedSuccess"
|
||||
)
|
||||
p.text-serif #{translate("file_has_been_deleted", {filename:"{{ history.diff.doc.name }} "})}
|
||||
p
|
||||
a.btn.btn-primary.btn-lg(
|
||||
href,
|
||||
ng-click="restoreDeletedDoc()",
|
||||
ng-disabled="history.diff.restoreInProgress"
|
||||
) #{translate("restore")}
|
||||
|
||||
.diff-deleted.text-centered(
|
||||
ng-show="history.diff.deleted && history.diff.restoreDeletedSuccess"
|
||||
)
|
||||
p.text-serif #{translate("file_restored", {filename:"{{ history.diff.doc.name }} "})}
|
||||
p.text-serif #{translate("file_restored_back_to_editor")}
|
||||
p
|
||||
a.btn.btn-default(
|
||||
href,
|
||||
ng-click="backToEditorAfterRestore()",
|
||||
) #{translate("file_restored_back_to_editor_btn")}
|
||||
|
||||
.loading-panel(ng-show="history.diff.loading")
|
||||
i.fa.fa-spin.fa-refresh
|
||||
| #{translate("loading")}...
|
||||
.error-panel(ng-show="history.diff.error")
|
||||
.alert.alert-danger #{translate("generic_something_went_wrong")}
|
||||
include ./history/diffPanelV1
|
||||
include ./history/diffPanelV2
|
||||
|
||||
script(type="text/ng-template", id="historyRestoreDiffModalTemplate")
|
||||
.modal-header
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
.diff-panel.full-size(ng-if="!history.isV2", ng-controller="HistoryDiffController")
|
||||
.diff(
|
||||
ng-if="!!history.diff && !history.diff.loading && !history.diff.deleted && !history.diff.error && !history.diff.binary"
|
||||
)
|
||||
.toolbar.toolbar-alt
|
||||
span.name
|
||||
| <strong>{{history.diff.highlights.length}} </strong>
|
||||
ng-pluralize(
|
||||
count="history.diff.highlights.length",
|
||||
when="{\
|
||||
'one': 'change',\
|
||||
'other': 'changes'\
|
||||
}"
|
||||
)
|
||||
| in <strong>{{history.diff.pathname}}</strong>
|
||||
.toolbar-right
|
||||
a.btn.btn-danger.btn-sm(
|
||||
href,
|
||||
ng-click="openRestoreDiffModal()"
|
||||
) #{translate("restore_to_before_these_changes")}
|
||||
.diff-editor.hide-ace-cursor(
|
||||
ace-editor="history",
|
||||
theme="settings.theme",
|
||||
font-size="settings.fontSize",
|
||||
text="history.diff.text",
|
||||
highlights="history.diff.highlights",
|
||||
read-only="true",
|
||||
resize-on="layout:main:resize",
|
||||
navigate-highlights="true"
|
||||
)
|
||||
|
||||
.diff-deleted.text-centered(
|
||||
ng-show="history.diff.deleted && !history.diff.restoreDeletedSuccess"
|
||||
)
|
||||
p.text-serif #{translate("file_has_been_deleted", {filename:"{{ history.diff.doc.name }} "})}
|
||||
p
|
||||
a.btn.btn-primary.btn-lg(
|
||||
href,
|
||||
ng-click="restoreDeletedDoc()",
|
||||
ng-disabled="history.diff.restoreInProgress"
|
||||
) #{translate("restore")}
|
||||
|
||||
.diff-deleted.text-centered(
|
||||
ng-show="history.diff.deleted && history.diff.restoreDeletedSuccess"
|
||||
)
|
||||
p.text-serif #{translate("file_restored", {filename:"{{ history.diff.doc.name }} "})}
|
||||
p.text-serif #{translate("file_restored_back_to_editor")}
|
||||
p
|
||||
a.btn.btn-default(
|
||||
href,
|
||||
ng-click="backToEditorAfterRestore()",
|
||||
) #{translate("file_restored_back_to_editor_btn")}
|
||||
|
||||
.loading-panel(ng-show="history.diff.loading")
|
||||
i.fa.fa-spin.fa-refresh
|
||||
| #{translate("loading")}...
|
||||
.error-panel(ng-show="history.diff.error")
|
||||
.alert.alert-danger #{translate("generic_something_went_wrong")}
|
||||
@@ -0,0 +1,50 @@
|
||||
.diff-panel.full-size(ng-if="history.isV2", ng-controller="HistoryV2DiffController")
|
||||
.diff(
|
||||
ng-if="!!history.diff && !history.diff.loading && !history.diff.error",
|
||||
ng-class="{ 'diff-binary': history.diff.binary }"
|
||||
)
|
||||
.toolbar.toolbar-alt
|
||||
span.name(ng-if="history.diff.binary")
|
||||
strong {{history.diff.pathname}}
|
||||
span.name(ng-if="!history.diff.binary")
|
||||
| <strong>{{history.diff.highlights.length}} </strong>
|
||||
ng-pluralize(
|
||||
count="history.diff.highlights.length",
|
||||
when="{\
|
||||
'one': 'change',\
|
||||
'other': 'changes'\
|
||||
}"
|
||||
)
|
||||
| in <strong>{{history.diff.pathname}}</strong>
|
||||
.toolbar-right(ng-if="history.selection.docs[history.selection.pathname].deletedAtV")
|
||||
button.btn.btn-danger.btn-sm(
|
||||
ng-click="restoreDeletedFile()"
|
||||
ng-show="!restoreState.error"
|
||||
ng-disabled="restoreState.inflight"
|
||||
)
|
||||
i.fa.fa-fw.fa-step-backward
|
||||
span(ng-show="!restoreState.inflight")
|
||||
| Restore this deleted file
|
||||
span(ng-show="restoreState.inflight")
|
||||
| Restoring...
|
||||
span.text-danger(ng-show="restoreState.error")
|
||||
| Error restoring, sorry
|
||||
.diff-editor.hide-ace-cursor(
|
||||
ng-if="!history.diff.binary"
|
||||
ace-editor="history",
|
||||
theme="settings.theme",
|
||||
font-size="settings.fontSize",
|
||||
text="history.diff.text",
|
||||
highlights="history.diff.highlights",
|
||||
read-only="true",
|
||||
resize-on="layout:main:resize",
|
||||
navigate-highlights="true"
|
||||
)
|
||||
.alert.alert-info(ng-if="history.diff.binary")
|
||||
| We're still working on showing image and binary changes, sorry. Stay tuned!
|
||||
|
||||
.loading-panel(ng-show="history.diff.loading")
|
||||
i.fa.fa-spin.fa-refresh
|
||||
| #{translate("loading")}...
|
||||
.error-panel(ng-show="history.diff.error")
|
||||
.alert.alert-danger #{translate("generic_something_went_wrong")}
|
||||
@@ -402,7 +402,6 @@ div.full-size.pdf(ng-controller="PdfController")
|
||||
a.btn.btn-success.row-spaced-small(
|
||||
href
|
||||
ng-class="buttonClass"
|
||||
sixpack-convert="track_changes_feature_info"
|
||||
ng-click="startFreeTrial('compile-timeout')"
|
||||
) #{translate("start_free_trial")}
|
||||
|
||||
|
||||
@@ -173,7 +173,6 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
|
||||
a.btn.btn-success(
|
||||
href
|
||||
ng-class="buttonClass"
|
||||
sixpack-convert="track_changes_feature_info"
|
||||
ng-click="startFreeTrial('projectMembers')"
|
||||
) #{translate("start_free_trial")}
|
||||
|
||||
|
||||
@@ -56,9 +56,28 @@ block content
|
||||
href
|
||||
ng-click="showAll();"
|
||||
) Show all
|
||||
.content.content-alt.project-list-page(ng-controller="ProjectPageController")
|
||||
.project-list-content
|
||||
main.content.content-alt.project-list-page(
|
||||
ng-controller="ProjectPageController"
|
||||
role="main"
|
||||
)
|
||||
- if(typeof(suppressSystemMessages) == "undefined")
|
||||
.system-messages(
|
||||
ng-cloak
|
||||
ng-controller="SystemMessagesController"
|
||||
)
|
||||
.system-message(
|
||||
ng-repeat="message in messages"
|
||||
ng-controller="SystemMessageController"
|
||||
ng-hide="hidden"
|
||||
)
|
||||
button(ng-click="hide()").close.pull-right
|
||||
span(aria-hidden="true") ×
|
||||
span.sr-only #{translate("close")}
|
||||
.system-message-content(ng-bind-html="htmlContent")
|
||||
|
||||
include ../translations/translation_message
|
||||
|
||||
.project-list-content(event-tracking=settings.overleaf ? "loads_v2_dash" : "", onboard=settings.overleaf ? "true" : "", event-tracking-trigger=settings.overleaf ? "load" : "", event-tracking-mb="true", event-segmentation="{location: 'dash', v2_onboard: true}")
|
||||
.row.project-list-row(ng-cloak)
|
||||
.project-list-container(ng-if="projects.length > 0")
|
||||
.project-list-sidebar-wrapper.col-md-2.col-xs-3
|
||||
@@ -77,6 +96,7 @@ block content
|
||||
if userIsFromOLv1(user)
|
||||
div(ng-show="visible")
|
||||
| To tag or rename your v1 projects, please go back to Overleaf v1.
|
||||
div(ng-show="visible")
|
||||
a.project-list-sidebar-v1-link(
|
||||
href=settings.overleaf.host + "/dash?prefer-v1-dash=1"
|
||||
) Go back to v1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
- if (settings.overleaf && settings.overleaf.front_chat_widget_room_id != null)
|
||||
- if (frontChatWidgetRoomId)
|
||||
script.
|
||||
window.FCSP = '#{settings.overleaf.front_chat_widget_room_id}';
|
||||
script(src="https://chat-assets.frontapp.com/v1/chat.bundle.js")
|
||||
window.FCSP = '#{frontChatWidgetRoomId}';
|
||||
script(src="https://chat-assets.frontapp.com/v1/chat.bundle.js")
|
||||
|
||||
@@ -38,4 +38,7 @@
|
||||
tooltip-append-to-body="true"
|
||||
)
|
||||
.col-xs-4
|
||||
span.last-modified {{project.lastUpdated | formatDate}}
|
||||
if settings.overleaf
|
||||
span.last-modified(tooltip="{{project.lastUpdated | formatDate}}") {{project.lastUpdated | fromNowDate}}
|
||||
else
|
||||
span.last-modified {{project.lastUpdated | formatDate}}
|
||||
|
||||
@@ -342,17 +342,20 @@ script(type="text/ng-template", id="v1ImportModalTemplate")
|
||||
.v1-import-step-2(ng-show="step === 2")
|
||||
.v1-import-row
|
||||
.v1-import-warning.v1-import-col(aria-label="Warning symbol.")
|
||||
i.fa.fa-exclamation-triangle
|
||||
i.fa.fa-flask
|
||||
.v1-import-col
|
||||
h2.v1-import-title #[strong Warning:] Overleaf v2 is Experimental
|
||||
p We are still working hard to bring some Overleaf v1 features to the v2 editor. If you move this project to v2 now, you will:
|
||||
p We are still working hard to bring some Overleaf v1 features to the v2 editor. In v2 there is:
|
||||
ul
|
||||
li Lose access your project via git
|
||||
li Not be able to use the Journals and Services menu to submit directly to our partners
|
||||
li Not be able to use the Rich Text (WYSIWYG) mode
|
||||
li Not be able to use linked files (to URLs or to files in other Overleaf projects)
|
||||
li Not be able to use some bibliography integrations (Zotero, CiteULike)
|
||||
li Lose access to your labelled versions and not be able to create new labelled versions
|
||||
li <strong>No Journals and Services</strong> menu to submit directly to our partners yet
|
||||
li <strong>No Rich Text (WYSIWYG)</strong> mode yet
|
||||
li <strong>No linked files</strong> (to URLs or to files in other Overleaf projects) yet
|
||||
li <strong>No Zotero and CiteULike</strong> integrations yet
|
||||
li <strong>No labelled versions</strong> yet
|
||||
p.row-spaced-small
|
||||
| If you currently use the <strong>Overleaf Git bridge</strong> with your v1 project, you can migrate your project to the Overleaf v2 GitHub integration.
|
||||
|
|
||||
a(href='https://www.overleaf.com/help/343-working-offline-in-overleaf-v2', target='_blank') Read More.
|
||||
.v1-import-cta
|
||||
p
|
||||
strong Please note: you cannot move this project back to v1 once you have moved it to v2. If this is an important project, please consider making a clone in v1 before you move the project to v2.
|
||||
|
||||
@@ -6,13 +6,13 @@ if (user.awareOfV2 && !settings.overleaf)
|
||||
.col-xs-12
|
||||
.alert.alert-info
|
||||
.notification_inner
|
||||
.notification_body
|
||||
a.btn.btn-info.btn-sm.pull-right(href="/user/login_to_ol_v2") Try Overleaf v2
|
||||
| ShareLaTeX is joining Overleaf and will become <em>Overleaf v2</em> in late 2018 (<a href="https://www.overleaf.com/help/342-overleaf-v2-faq">read more</a>).
|
||||
.notification_body(event-tracking="sees_v2_banner" event-tracking-mb="true" event-segmentation="{location: 'welcome', v2_onboard: true}" event-tracking-trigger="load" event-tracking-send-once="true")
|
||||
a.btn.btn-info.btn-sm.pull-right(event-tracking="go_to_v2" event-tracking-mb="true" event-segmentation="{location: 'welcome', v2_onboard: true}" event-tracking-trigger="click" href="/user/login_to_ol_v2") Try Overleaf v2
|
||||
| ShareLaTeX is joining Overleaf and will become <em>Overleaf v2</em> in late 2018 (<a event-tracking="click_v2_read_more" event-tracking-mb="true" event-segmentation="{location: 'welcome', v2_onboard: true}" event-tracking-trigger="click" href="https://www.overleaf.com/help/342-overleaf-v2-faq">read more</a>).
|
||||
<br/>
|
||||
| We’re beta testing Overleaf v2 now and you can try it out with your ShareLaTeX account.
|
||||
.notification_close
|
||||
button(ng-click="dismiss()").close.pull-right
|
||||
button(ng-click="dismiss()" event-tracking="closes_v2_banner" event-tracking-mb="true" event-segmentation="{location: 'welcome', v2_onboard: true}" event-tracking-trigger="click").close.pull-right
|
||||
span(aria-hidden="true") ×
|
||||
span.sr-only #{translate("close")}
|
||||
|
||||
@@ -49,4 +49,4 @@ span(ng-controller="NotificationsController").userNotifications
|
||||
span().notification_close
|
||||
button(ng-click="dismiss(notification)").close.pull-right
|
||||
span(aria-hidden="true") ×
|
||||
span.sr-only #{translate("close")}
|
||||
span.sr-only #{translate("close")}
|
||||
@@ -61,6 +61,7 @@
|
||||
li(
|
||||
ng-repeat="tag in tags | orderBy:'name'",
|
||||
ng-controller="TagDropdownItemController"
|
||||
ng-if="!tag.isV1"
|
||||
)
|
||||
a(href="#", ng-click="addOrRemoveProjectsFromTag()", stop-propagation="click")
|
||||
i.fa(
|
||||
|
||||
@@ -10,19 +10,16 @@
|
||||
li
|
||||
a(
|
||||
href,
|
||||
sixpack-convert="first_sign_up",
|
||||
ng-click="openCreateProjectModal()"
|
||||
) #{translate("blank_project")}
|
||||
li
|
||||
a(
|
||||
href,
|
||||
sixpack-convert="first_sign_up",
|
||||
ng-click="openCreateProjectModal('example')"
|
||||
) #{translate("example_project")}
|
||||
li
|
||||
a(
|
||||
href,
|
||||
sixpack-convert="first_sign_up",
|
||||
ng-click="openUploadProjectModal()"
|
||||
) #{translate("upload_project")}
|
||||
!= moduleIncludes("newProjectMenu", locals)
|
||||
@@ -31,7 +28,7 @@
|
||||
li.dropdown-header #{translate("templates")}
|
||||
each item in templates
|
||||
li
|
||||
a.menu-indent(href=item.url, sixpack-convert="first_sign_up") #{translate(item.name)}
|
||||
a.menu-indent(href=item.url) #{translate(item.name)}
|
||||
|
||||
.row-spaced(ng-if="projects.length > 0", ng-cloak)
|
||||
ul.list-unstyled.folders-menu(
|
||||
@@ -76,7 +73,7 @@
|
||||
tooltip-template="'v1TagTooltipTemplate'"
|
||||
tooltip-append-to-body="true"
|
||||
)
|
||||
span.dropdown.tag-menu(dropdown)
|
||||
span.dropdown.tag-menu(dropdown)(ng-if="!tag.isV1")
|
||||
a.dropdown-toggle(
|
||||
href="#",
|
||||
data-toggle="dropdown",
|
||||
@@ -133,7 +130,7 @@
|
||||
hr
|
||||
p.small #{translate("on_free_sl")}
|
||||
p
|
||||
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgraed-rotation").btn.btn-primary #{translate("upgrade")}
|
||||
a(href="/user/subscription/plans").btn.btn-primary #{translate("upgrade")}
|
||||
p.small.text-centered
|
||||
| #{translate("or_unlock_features_bonus")}
|
||||
a(href="/user/bonus") #{translate("sharing_sl")} .
|
||||
|
||||
@@ -22,4 +22,4 @@
|
||||
span.owner {{ownerName()}}
|
||||
|
||||
.col-xs-4
|
||||
span.last-modified {{project.lastUpdated | formatDate}}
|
||||
span.last-modified(tooltip="{{project.lastUpdated | formatDate}}") {{project.lastUpdated | fromNowDate}}
|
||||
@@ -59,24 +59,29 @@ block content
|
||||
.col-md-12.text-centered
|
||||
small #{translate("no_members")}
|
||||
|
||||
hr
|
||||
div(ng-if="users.length < groupSize", ng-cloak)
|
||||
hr
|
||||
p
|
||||
.small #{translate("add_more_members")}
|
||||
form.form
|
||||
.row
|
||||
.col-xs-6
|
||||
input.form-control(
|
||||
name="email",
|
||||
type="text",
|
||||
placeholder="jane@example.com, joe@example.com",
|
||||
ng-model="inputs.emails",
|
||||
on-enter="addMembers()"
|
||||
)
|
||||
.col-xs-4
|
||||
button.btn.btn-primary(ng-click="addMembers()") #{translate("add")}
|
||||
.col-xs-2
|
||||
a(href="/subscription/group/export") Export CSV
|
||||
p.small #{translate("add_more_members")}
|
||||
form.form
|
||||
.row
|
||||
.col-xs-6
|
||||
input.form-control(
|
||||
name="email",
|
||||
type="text",
|
||||
placeholder="jane@example.com, joe@example.com",
|
||||
ng-model="inputs.emails",
|
||||
on-enter="addMembers()"
|
||||
)
|
||||
.col-xs-4
|
||||
button.btn.btn-primary(ng-click="addMembers()") #{translate("add")}
|
||||
.col-xs-2
|
||||
a(href="/subscription/group/export") Export CSV
|
||||
|
||||
div(ng-if="users.length >= groupSize && users.length > 0", ng-cloak)
|
||||
.row
|
||||
.col-xs-2.col-xs-offset-10
|
||||
a(href="/subscription/group/export") Export CSV
|
||||
|
||||
|
||||
script(type="text/javascript").
|
||||
window.users = !{JSON.stringify(users)};
|
||||
@@ -84,5 +89,3 @@ block content
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
extends ../layout
|
||||
|
||||
block scripts
|
||||
script(src="https://js.recurly.com/v3/recurly.js")
|
||||
|
||||
script(type='text/javascript').
|
||||
window.countryCode = '#{countryCode}'
|
||||
window.plan_code = '#{plan_code}'
|
||||
@@ -41,19 +39,20 @@ block content
|
||||
span !{translate("first_few_days_free", {trialLen:'{{trialLength}}'})}
|
||||
span(ng-if="discountMonths && discountRate") - {{discountMonths}} #{translate("month")}s {{discountRate}}% Off
|
||||
div(ng-if="price")
|
||||
strong {{price.currency.symbol}}{{price.next.total}}
|
||||
strong {{plans[currencyCode]['symbol']}}{{price.next.total}}
|
||||
span(ng-if="monthlyBilling") #{translate("every")} #{translate("month")}
|
||||
span(ng-if="!monthlyBilling") #{translate("every")} #{translate("year")}
|
||||
div(ng-if="normalPrice")
|
||||
span.small Normally {{price.currency.symbol}}{{normalPrice}}
|
||||
span.small Normally {{plans[currencyCode]['symbol']}}{{normalPrice}}
|
||||
.row
|
||||
div()
|
||||
.col-md-12()
|
||||
form(
|
||||
ng-if="planName"
|
||||
name="simpleCCForm"
|
||||
novalidate
|
||||
)
|
||||
|
||||
|
||||
div.payment-method-toggle
|
||||
a.payment-method-toggle-switch(
|
||||
href
|
||||
@@ -75,10 +74,10 @@ block content
|
||||
.alert.alert-warning.small(ng-show="genericError")
|
||||
strong {{genericError}}
|
||||
|
||||
div(ng-if="paymentMethod.value === 'credit_card'")
|
||||
div(ng-show="paymentMethod.value === 'credit_card'")
|
||||
.row
|
||||
.col-xs-6
|
||||
.form-group(ng-class="validation.errorFields.first_name || inputHasError(simpleCCForm.firstName) ? 'has-error' : ''")
|
||||
.form-group(ng-class="validation.errorFields.first_name || inputHasError(simpleCCForm.firstName) ? 'has-external-error' : ''")
|
||||
label(for="first-name") #{translate('first_name')}
|
||||
input#first-name.form-control(
|
||||
type="text"
|
||||
@@ -90,7 +89,7 @@ block content
|
||||
)
|
||||
span.input-feedback-message {{ simpleCCForm.firstName.$error.required ? 'This field is required' : '' }}
|
||||
.col-xs-6
|
||||
.form-group(for="last-name",ng-class="validation.errorFields.last_name || inputHasError(simpleCCForm.lastName)? 'has-error' : ''")
|
||||
.form-group(for="last-name",ng-class="validation.errorFields.last_name || inputHasError(simpleCCForm.lastName)? 'has-external-error' : ''")
|
||||
label(for="last-name") #{translate('last_name')}
|
||||
input#last-name.form-control(
|
||||
type="text"
|
||||
@@ -100,47 +99,41 @@ block content
|
||||
ng-model="data.last_name"
|
||||
required
|
||||
)
|
||||
span.input-feedback-message {{ simpleCCForm.lastName.$error.required ? 'This field is required' : '' }}
|
||||
|
||||
.form-group(ng-class="validation.correctCardNumber == false || validation.errorFields.number || inputHasError(simpleCCForm.ccNumber) ? 'has-error' : ''")
|
||||
.form-group(ng-class="validation.errorFields.number ? 'has-external-error' : ''")
|
||||
label(for="card-no") #{translate("credit_card_number")}
|
||||
input#card-no.form-control(
|
||||
div#card-no(
|
||||
type="text"
|
||||
ng-model="data.number"
|
||||
name="ccNumber"
|
||||
ng-focus="validation.correctCardNumber = true; validation.errorFields.number = false;"
|
||||
ng-blur="validateCardNumber();"
|
||||
required
|
||||
cc-format-card-number
|
||||
data-recurly='number'
|
||||
)
|
||||
span.input-feedback-message {{ simpleCCForm.ccNumber.$error.required ? 'This field is required' : 'Please re-check the card number' }}
|
||||
|
||||
.row
|
||||
.col-xs-6
|
||||
.form-group.has-feedback(ng-class="validation.correctExpiry == false || validation.errorFields.expiry || inputHasError(simpleCCForm.expiry) ? 'has-error' : ''")
|
||||
label #{translate("expiry")}
|
||||
input.form-control(
|
||||
type="text"
|
||||
ng-model="data.mmYY"
|
||||
name="expiry"
|
||||
placeholder="MM / YY"
|
||||
ng-focus="validation.correctExpiry = true; validation.errorFields.expiry = false;"
|
||||
ng-blur="updateExpiry(); validateExpiry()"
|
||||
required
|
||||
cc-format-expiry
|
||||
.col-xs-3
|
||||
.form-group.has-feedback(ng-class="validation.errorFields.month ? 'has-external-error' : ''")
|
||||
label(for="month").capitalised #{translate("month")}
|
||||
div(
|
||||
type="number"
|
||||
name="month"
|
||||
data-recurly="month"
|
||||
)
|
||||
span.input-feedback-message {{ simpleCCForm.expiry.$error.required ? 'This field is required' : 'Please re-check the expiry date' }}
|
||||
|
||||
.col-xs-3
|
||||
.form-group.has-feedback(ng-class="validation.errorFields.year ? 'has-external-error' : ''")
|
||||
label(for="year").capitalised #{translate("year")}
|
||||
div(
|
||||
type="number"
|
||||
name="year"
|
||||
data-recurly="year"
|
||||
)
|
||||
|
||||
.col-xs-6
|
||||
.form-group.has-feedback(ng-class="validation.correctCvv == false || validation.errorFields.cvv || inputHasError(simpleCCForm.cvv) ? 'has-error' : ''")
|
||||
.form-group.has-feedback(ng-class="validation.errorFields.cvv ? 'has-external-error' : ''")
|
||||
label #{translate("security_code")}
|
||||
input.form-control(
|
||||
type="text"
|
||||
div(
|
||||
type="number"
|
||||
ng-model="data.cvv"
|
||||
ng-focus="validation.correctCvv = true; validation.errorFields.cvv = false;"
|
||||
ng-blur="validateCvv()"
|
||||
data-recurly="cvv"
|
||||
name="cvv"
|
||||
required
|
||||
cc-format-sec-code
|
||||
)
|
||||
.form-control-feedback
|
||||
@@ -151,20 +144,21 @@ block content
|
||||
tooltip-trigger="mouseenter"
|
||||
tooltip-append-to-body="true"
|
||||
) ?
|
||||
span.input-feedback-message {{ simpleCCForm.cvv.$error.required ? 'This field is required' : 'Please re-check the security code' }}
|
||||
|
||||
|
||||
div
|
||||
.form-group(ng-class="validation.errorFields.country || inputHasError(simpleCCForm.country) ? 'has-error' : ''")
|
||||
.form-group(ng-class="validation.errorFields.country || inputHasError(simpleCCForm.country) ? 'has-external-error' : ''")
|
||||
label(for="country") #{translate('country')}
|
||||
select#country.form-control(
|
||||
data-recurly="country"
|
||||
ng-model="data.country"
|
||||
name="country"
|
||||
ng-change="updateCountry()"
|
||||
required
|
||||
required,
|
||||
ng-options="country.code as country.name for country in countries",
|
||||
ng-selected="{{country.code == data.country}}"
|
||||
)
|
||||
+countries_options()
|
||||
option(value='', disabled, selected) #{translate("country")}
|
||||
option(value='-') --------------
|
||||
span.input-feedback-message {{ simpleCCForm.country.$error.required ? 'This field is required' : '' }}
|
||||
|
||||
if (showVatField)
|
||||
@@ -189,8 +183,8 @@ block content
|
||||
div.price-breakdown(ng-if="price.next.tax !== '0.00'")
|
||||
hr.thin
|
||||
span Total:
|
||||
strong {{price.currency.symbol}}{{price.next.total}}
|
||||
span ({{price.currency.symbol}}{{price.next.subtotal}} + {{price.currency.symbol}}{{price.next.tax}} tax)
|
||||
strong {{plans[currencyCode]['symbol']}}{{price.next.total}}
|
||||
span ({{plans[currencyCode]['symbol']}}{{price.next.subtotal}} + {{plans[currencyCode]['symbol']}}{{price.next.tax}} tax)
|
||||
span(ng-if="monthlyBilling") #{translate("every")} #{translate("month")}
|
||||
span(ng-if="!monthlyBilling") #{translate("every")} #{translate("year")}
|
||||
hr.thin
|
||||
@@ -198,12 +192,14 @@ block content
|
||||
div.payment-submit
|
||||
button.btn.btn-success.btn-block(
|
||||
ng-click="submit()"
|
||||
ng-disabled="processing || !isFormValid(simpleCCForm);"
|
||||
ng-disabled="processing || !isFormValid(simpleCCForm);"
|
||||
)
|
||||
span(ng-show="processing")
|
||||
i.fa.fa-spinner.fa-spin
|
||||
|
|
||||
| {{ paymentMethod.value === 'credit_card' ? '#{translate("upgrade_cc_btn")}' : '#{translate("upgrade_paypal_btn")}' }}
|
||||
span(ng-if="paymentMethod.value === 'credit_card'")
|
||||
| {{ monthlyBilling ? '#{translate("upgrade_cc_btn")}' : '#{translate("upgrade_now")}'}}
|
||||
span(ng-if="paymentMethod.value !== 'credit_card'") #{translate("upgrade_paypal_btn")}
|
||||
|
||||
|
||||
.col-md-3.col-md-pull-4
|
||||
@@ -258,258 +254,3 @@ block content
|
||||
)
|
||||
p For #[strong Visa, MasterCard and Discover], the #[strong 3 digits] on the #[strong back] of your card.
|
||||
p For #[strong American Express], the #[strong 4 digits] on the #[strong front] of your card.
|
||||
|
||||
mixin countries_options()
|
||||
option(value='', disabled, selected) #{translate("country")}
|
||||
option(value='-') --------------
|
||||
option(value='AF') Afghanistan
|
||||
option(value='AL') Albania
|
||||
option(value='DZ') Algeria
|
||||
option(value='AS') American Samoa
|
||||
option(value='AD') Andorra
|
||||
option(value='AO') Angola
|
||||
option(value='AI') Anguilla
|
||||
option(value='AQ') Antarctica
|
||||
option(value='AG') Antigua and Barbuda
|
||||
option(value='AR') Argentina
|
||||
option(value='AM') Armenia
|
||||
option(value='AW') Aruba
|
||||
option(value='AC') Ascension Island
|
||||
option(value='AU') Australia
|
||||
option(value='AT') Austria
|
||||
option(value='AZ') Azerbaijan
|
||||
option(value='BS') Bahamas
|
||||
option(value='BH') Bahrain
|
||||
option(value='BD') Bangladesh
|
||||
option(value='BB') Barbados
|
||||
option(value='BE') Belgium
|
||||
option(value='BZ') Belize
|
||||
option(value='BJ') Benin
|
||||
option(value='BM') Bermuda
|
||||
option(value='BT') Bhutan
|
||||
option(value='BO') Bolivia
|
||||
option(value='BA') Bosnia and Herzegovina
|
||||
option(value='BW') Botswana
|
||||
option(value='BV') Bouvet Island
|
||||
option(value='BR') Brazil
|
||||
option(value='BQ') British Antarctic Territory
|
||||
option(value='IO') British Indian Ocean Territory
|
||||
option(value='VG') British Virgin Islands
|
||||
option(value='BN') Brunei
|
||||
option(value='BG') Bulgaria
|
||||
option(value='BF') Burkina Faso
|
||||
option(value='BI') Burundi
|
||||
option(value='KH') Cambodia
|
||||
option(value='CM') Cameroon
|
||||
option(value='CA') Canada
|
||||
option(value='IC') Canary Islands
|
||||
option(value='CT') Canton and Enderbury Islands
|
||||
option(value='CV') Cape Verde
|
||||
option(value='KY') Cayman Islands
|
||||
option(value='CF') Central African Republic
|
||||
option(value='EA') Ceuta and Melilla
|
||||
option(value='TD') Chad
|
||||
option(value='CL') Chile
|
||||
option(value='CN') China
|
||||
option(value='CX') Christmas Island
|
||||
option(value='CP') Clipperton Island
|
||||
option(value='CC') Cocos [Keeling] Islands
|
||||
option(value='CO') Colombia
|
||||
option(value='KM') Comoros
|
||||
option(value='CD') Congo [DRC]
|
||||
option(value='CK') Cook Islands
|
||||
option(value='CR') Costa Rica
|
||||
option(value='HR') Croatia
|
||||
option(value='CU') Cuba
|
||||
option(value='CY') Cyprus
|
||||
option(value='CZ') Czech Republic
|
||||
option(value='DK') Denmark
|
||||
option(value='DG') Diego Garcia
|
||||
option(value='DJ') Djibouti
|
||||
option(value='DM') Dominica
|
||||
option(value='DO') Dominican Republic
|
||||
option(value='NQ') Dronning Maud Land
|
||||
option(value='TL') East Timor
|
||||
option(value='EC') Ecuador
|
||||
option(value='EG') Egypt
|
||||
option(value='SV') El Salvador
|
||||
option(value='EE') Estonia
|
||||
option(value='ET') Ethiopia
|
||||
option(value='FK') Falkland Islands [Islas Malvinas]
|
||||
option(value='FO') Faroe Islands
|
||||
option(value='FJ') Fiji
|
||||
option(value='FI') Finland
|
||||
option(value='FR') France
|
||||
option(value='GF') French Guiana
|
||||
option(value='PF') French Polynesia
|
||||
option(value='TF') French Southern Territories
|
||||
option(value='FQ') French Southern and Antarctic Territories
|
||||
option(value='GA') Gabon
|
||||
option(value='GM') Gambia
|
||||
option(value='GE') Georgia
|
||||
option(value='DE') Germany
|
||||
option(value='GH') Ghana
|
||||
option(value='GI') Gibraltar
|
||||
option(value='GR') Greece
|
||||
option(value='GL') Greenland
|
||||
option(value='GD') Grenada
|
||||
option(value='GP') Guadeloupe
|
||||
option(value='GU') Guam
|
||||
option(value='GT') Guatemala
|
||||
option(value='GG') Guernsey
|
||||
option(value='GW') Guinea-Bissau
|
||||
option(value='GY') Guyana
|
||||
option(value='HT') Haiti
|
||||
option(value='HM') Heard Island and McDonald Islands
|
||||
option(value='HN') Honduras
|
||||
option(value='HK') Hong Kong
|
||||
option(value='HU') Hungary
|
||||
option(value='IS') Iceland
|
||||
option(value='IN') India
|
||||
option(value='ID') Indonesia
|
||||
option(value='IE') Ireland
|
||||
option(value='IM') Isle of Man
|
||||
option(value='IL') Israel
|
||||
option(value='IT') Italy
|
||||
option(value='JM') Jamaica
|
||||
option(value='JP') Japan
|
||||
option(value='JE') Jersey
|
||||
option(value='JT') Johnston Island
|
||||
option(value='JO') Jordan
|
||||
option(value='KZ') Kazakhstan
|
||||
option(value='KE') Kenya
|
||||
option(value='KI') Kiribati
|
||||
option(value='KW') Kuwait
|
||||
option(value='KG') Kyrgyzstan
|
||||
option(value='LA') Laos
|
||||
option(value='LV') Latvia
|
||||
option(value='LS') Lesotho
|
||||
option(value='LY') Libya
|
||||
option(value='LI') Liechtenstein
|
||||
option(value='LT') Lithuania
|
||||
option(value='LU') Luxembourg
|
||||
option(value='MO') Macau
|
||||
option(value='MK') Macedonia [FYROM]
|
||||
option(value='MG') Madagascar
|
||||
option(value='MW') Malawi
|
||||
option(value='MY') Malaysia
|
||||
option(value='MV') Maldives
|
||||
option(value='ML') Mali
|
||||
option(value='MT') Malta
|
||||
option(value='MH') Marshall Islands
|
||||
option(value='MQ') Martinique
|
||||
option(value='MR') Mauritania
|
||||
option(value='MU') Mauritius
|
||||
option(value='YT') Mayotte
|
||||
option(value='FX') Metropolitan France
|
||||
option(value='MX') Mexico
|
||||
option(value='FM') Micronesia
|
||||
option(value='MI') Midway Islands
|
||||
option(value='MD') Moldova
|
||||
option(value='MC') Monaco
|
||||
option(value='MN') Mongolia
|
||||
option(value='ME') Montenegro
|
||||
option(value='MS') Montserrat
|
||||
option(value='MA') Morocco
|
||||
option(value='MZ') Mozambique
|
||||
option(value='NA') Namibia
|
||||
option(value='NR') Nauru
|
||||
option(value='NP') Nepal
|
||||
option(value='NL') Netherlands
|
||||
option(value='AN') Netherlands Antilles
|
||||
option(value='NT') Neutral Zone
|
||||
option(value='NC') New Caledonia
|
||||
option(value='NZ') New Zealand
|
||||
option(value='NI') Nicaragua
|
||||
option(value='NE') Niger
|
||||
option(value='NG') Nigeria
|
||||
option(value='NU') Niue
|
||||
option(value='NF') Norfolk Island
|
||||
option(value='VD') North Vietnam
|
||||
option(value='MP') Northern Mariana Islands
|
||||
option(value='NO') Norway
|
||||
option(value='OM') Oman
|
||||
option(value='QO') Outlying Oceania
|
||||
option(value='PC') Pacific Islands Trust Territory
|
||||
option(value='PK') Pakistan
|
||||
option(value='PW') Palau
|
||||
option(value='PS') Palestinian Territories
|
||||
option(value='PA') Panama
|
||||
option(value='PZ') Panama Canal Zone
|
||||
option(value='PY') Paraguay
|
||||
option(value='YD') People's Democratic Republic of Yemen
|
||||
option(value='PE') Peru
|
||||
option(value='PH') Philippines
|
||||
option(value='PN') Pitcairn Islands
|
||||
option(value='PL') Poland
|
||||
option(value='PT') Portugal
|
||||
option(value='PR') Puerto Rico
|
||||
option(value='QA') Qatar
|
||||
option(value='RO') Romania
|
||||
option(value='RU') Russia
|
||||
option(value='RW') Rwanda
|
||||
option(value='RE') Réunion
|
||||
option(value='BL') Saint Barthélemy
|
||||
option(value='SH') Saint Helena
|
||||
option(value='KN') Saint Kitts and Nevis
|
||||
option(value='LC') Saint Lucia
|
||||
option(value='MF') Saint Martin
|
||||
option(value='PM') Saint Pierre and Miquelon
|
||||
option(value='VC') Saint Vincent and the Grenadines
|
||||
option(value='WS') Samoa
|
||||
option(value='SM') San Marino
|
||||
option(value='SA') Saudi Arabia
|
||||
option(value='SN') Senegal
|
||||
option(value='RS') Serbia
|
||||
option(value='CS') Serbia and Montenegro
|
||||
option(value='SC') Seychelles
|
||||
option(value='SL') Sierra Leone
|
||||
option(value='SG') Singapore
|
||||
option(value='SK') Slovakia
|
||||
option(value='SI') Slovenia
|
||||
option(value='SB') Solomon Islands
|
||||
option(value='ZA') South Africa
|
||||
option(value='GS') South Georgia and the South Sandwich Islands
|
||||
option(value='KR') South Korea
|
||||
option(value='ES') Spain
|
||||
option(value='LK') Sri Lanka
|
||||
option(value='SR') Suriname
|
||||
option(value='SJ') Svalbard and Jan Mayen
|
||||
option(value='SZ') Swaziland
|
||||
option(value='SE') Sweden
|
||||
option(value='CH') Switzerland
|
||||
option(value='ST') São Tomé and Príncipe
|
||||
option(value='TW') Taiwan
|
||||
option(value='TJ') Tajikistan
|
||||
option(value='TZ') Tanzania
|
||||
option(value='TH') Thailand
|
||||
option(value='TG') Togo
|
||||
option(value='TK') Tokelau
|
||||
option(value='TO') Tonga
|
||||
option(value='TT') Trinidad and Tobago
|
||||
option(value='TA') Tristan da Cunha
|
||||
option(value='TN') Tunisia
|
||||
option(value='TR') Turkey
|
||||
option(value='TM') Turkmenistan
|
||||
option(value='TC') Turks and Caicos Islands
|
||||
option(value='TV') Tuvalu
|
||||
option(value='UM') U.S. Minor Outlying Islands
|
||||
option(value='PU') U.S. Miscellaneous Pacific Islands
|
||||
option(value='VI') U.S. Virgin Islands
|
||||
option(value='UG') Uganda
|
||||
option(value='UA') Ukraine
|
||||
option(value='AE') United Arab Emirates
|
||||
option(value='GB') United Kingdom
|
||||
option(value='US') United States
|
||||
option(value='UY') Uruguay
|
||||
option(value='UZ') Uzbekistan
|
||||
option(value='VU') Vanuatu
|
||||
option(value='VA') Vatican City
|
||||
option(value='VE') Venezuela
|
||||
option(value='VN') Vietnam
|
||||
option(value='WK') Wake Island
|
||||
option(value='WF') Wallis and Futuna
|
||||
option(value='EH') Western Sahara
|
||||
option(value='YE') Yemen
|
||||
option(value='ZM') Zambia
|
||||
option(value='AX') Åland Islands
|
||||
|
||||
@@ -76,7 +76,7 @@ block content
|
||||
br
|
||||
a.btn.btn-info(
|
||||
href="/register"
|
||||
style=(getLoggedInUserId() === undefined ? "" : "visibility: hidden")
|
||||
style=(getLoggedInUserId() === null ? "" : "visibility: hidden")
|
||||
) #{translate("sign_up_now")}
|
||||
.col-md-4
|
||||
.card.card-highlighted
|
||||
@@ -90,10 +90,8 @@ block content
|
||||
| {{plans[currencyCode]['collaborator']['annual']}}
|
||||
span.small /yr
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:10})}
|
||||
strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:8})}
|
||||
strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:12})}
|
||||
li
|
||||
strong #{translate("collabs_per_proj", {collabcount:10})}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
@@ -144,7 +142,7 @@ block content
|
||||
br
|
||||
a.btn.btn-info(
|
||||
href="/register"
|
||||
style=(getLoggedInUserId() === undefined ? "" : "visibility: hidden")
|
||||
style=(getLoggedInUserId() === null ? "" : "visibility: hidden")
|
||||
) #{translate("sign_up_now")}
|
||||
|
||||
.col-md-4
|
||||
@@ -157,9 +155,7 @@ block content
|
||||
span.small /mo
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:6})}
|
||||
strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:4})}
|
||||
strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:8})}
|
||||
strong #{translate("collabs_per_proj", {collabcount:6})}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
@@ -180,9 +176,7 @@ block content
|
||||
span.small /yr
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:6})}
|
||||
strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:4})}
|
||||
strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:8})}
|
||||
strong #{translate("collabs_per_proj", {collabcount:6})}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
|
||||
@@ -118,17 +118,19 @@ block content
|
||||
|
||||
| !{moduleIncludes("userSettings", locals)}
|
||||
|
||||
hr
|
||||
|
||||
h3
|
||||
| #{translate("sharelatex_beta_program")}
|
||||
|
||||
if (user.betaProgram)
|
||||
p.small
|
||||
| #{translate("beta_program_already_participating")}
|
||||
|
||||
div
|
||||
a(id="beta-program-participate-link" href="/beta/participate") #{translate("manage_beta_program_membership")}
|
||||
//- The beta program doesn't make much sense to include while v2 is going
|
||||
//- but we may want to add it back in later
|
||||
//- hr
|
||||
//-
|
||||
//- h3
|
||||
//- | #{translate("sharelatex_beta_program")}
|
||||
//-
|
||||
//- if (user.betaProgram)
|
||||
//- p.small
|
||||
//- | #{translate("beta_program_already_participating")}
|
||||
//-
|
||||
//- div
|
||||
//- a(id="beta-program-participate-link" href="/beta/participate") #{translate("manage_beta_program_membership")}
|
||||
|
||||
hr
|
||||
|
||||
|
||||
@@ -154,6 +154,8 @@ module.exports = settings =
|
||||
url: "http://#{process.env['ANALYTICS_HOST'] or 'localhost'}:3050"
|
||||
linkedUrlProxy:
|
||||
url: process.env['LINKED_URL_PROXY']
|
||||
thirdpartyreferences:
|
||||
url: "http://#{process.env['THIRD_PARTY_REFERENCES_HOST'] or 'localhost'}:3046"
|
||||
|
||||
templates:
|
||||
user_id: process.env.TEMPLATES_USER_ID or "5395eb7aad1f29a88756c7f2"
|
||||
|
||||
@@ -18,6 +18,7 @@ module.exports = function (config) {
|
||||
'public/js/libs/angular-1.6.4.min.js',
|
||||
'public/js/libs/angular-mocks.js',
|
||||
'public/js/libs/jquery-1.11.1.min.js',
|
||||
'public/js/libs/underscore-1.3.3.js',
|
||||
// Set up requirejs
|
||||
'test/unit_frontend/js/test-main.js',
|
||||
// Include source & test files, but don't "include" them as requirejs
|
||||
@@ -25,13 +26,18 @@ module.exports = function (config) {
|
||||
{ pattern: 'public/js/**/*.js', included: false },
|
||||
{ pattern: 'test/unit_frontend/js/**/*.js', included: false },
|
||||
// Include ES test files
|
||||
'test/unit_frontend/es/**/*.js'
|
||||
'test/unit_frontend/es/**/*.js',
|
||||
'modules/**/test/unit_frontend/es/**/*.js',
|
||||
// Include CSS (there is some in js/libs dir)
|
||||
'public/stylesheets/**/*.css',
|
||||
'public/js/libs/**/*.css'
|
||||
],
|
||||
middleware: ['fake-img'],
|
||||
preprocessors: {
|
||||
// Run ES test files through webpack (which will then include source
|
||||
// files in bundle)
|
||||
'test/unit_frontend/es/**/*.js': ['webpack']
|
||||
'test/unit_frontend/es/**/*.js': ['webpack'],
|
||||
'modules/**/test/unit_frontend/es/**/*.js': ['webpack']
|
||||
},
|
||||
frameworks: ['requirejs', 'mocha', 'chai-sinon'],
|
||||
// Configure webpack in the tests
|
||||
@@ -72,4 +78,4 @@ function fakeImgMiddlewareFactory () {
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# Ignore all modules except for a whitelist
|
||||
*
|
||||
!dropbox
|
||||
!github-sync
|
||||
!public-registration
|
||||
!.gitignore
|
||||
@@ -5,10 +5,11 @@
|
||||
],
|
||||
"verbose": true,
|
||||
"legacyWatch": true,
|
||||
"exec": "make compile",
|
||||
"exec": "make compile || exit 1",
|
||||
"watch": [
|
||||
"public/coffee/",
|
||||
"public/stylesheets/"
|
||||
"public/stylesheets/",
|
||||
"modules/**/public/coffee/"
|
||||
],
|
||||
"ext": "coffee less"
|
||||
}
|
||||
@@ -11,7 +11,8 @@
|
||||
"watch": [
|
||||
"app/coffee/",
|
||||
"app.coffee",
|
||||
"modules/*/app/coffee/"
|
||||
"modules/*/app/coffee/",
|
||||
"config/"
|
||||
],
|
||||
"ext": "coffee"
|
||||
}
|
||||
Generated
+126
-8
@@ -533,6 +533,12 @@
|
||||
"resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-helper-builder-react-jsx": {
|
||||
"version": "6.26.0",
|
||||
"from": "babel-helper-builder-react-jsx@>=6.24.1 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-helper-call-delegate": {
|
||||
"version": "6.24.1",
|
||||
"from": "babel-helper-call-delegate@>=6.24.1 <7.0.0",
|
||||
@@ -629,6 +635,18 @@
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-syntax-flow": {
|
||||
"version": "6.18.0",
|
||||
"from": "babel-plugin-syntax-flow@>=6.18.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-syntax-jsx": {
|
||||
"version": "6.18.0",
|
||||
"from": "babel-plugin-syntax-jsx@>=6.3.13 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-syntax-trailing-function-commas": {
|
||||
"version": "6.22.0",
|
||||
"from": "babel-plugin-syntax-trailing-function-commas@>=6.22.0 <7.0.0",
|
||||
@@ -779,6 +797,36 @@
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-flow-strip-types": {
|
||||
"version": "6.22.0",
|
||||
"from": "babel-plugin-transform-flow-strip-types@>=6.22.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-react-display-name": {
|
||||
"version": "6.25.0",
|
||||
"from": "babel-plugin-transform-react-display-name@>=6.23.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-react-jsx": {
|
||||
"version": "6.24.1",
|
||||
"from": "babel-plugin-transform-react-jsx@>=6.24.1 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-react-jsx-self": {
|
||||
"version": "6.22.0",
|
||||
"from": "babel-plugin-transform-react-jsx-self@>=6.22.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-react-jsx-source": {
|
||||
"version": "6.22.0",
|
||||
"from": "babel-plugin-transform-react-jsx-source@>=6.22.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-regenerator": {
|
||||
"version": "6.26.0",
|
||||
"from": "babel-plugin-transform-regenerator@>=6.22.0 <7.0.0",
|
||||
@@ -805,6 +853,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-preset-flow": {
|
||||
"version": "6.23.0",
|
||||
"from": "babel-preset-flow@>=6.23.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-preset-react": {
|
||||
"version": "6.24.1",
|
||||
"from": "babel-preset-react@>=6.16.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-register": {
|
||||
"version": "6.26.0",
|
||||
"from": "babel-register@>=6.26.0 <7.0.0",
|
||||
@@ -2031,6 +2091,11 @@
|
||||
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"create-react-class": {
|
||||
"version": "15.6.3",
|
||||
"from": "create-react-class@>=15.6.0 <16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz"
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "5.1.0",
|
||||
"from": "cross-spawn@>=5.0.1 <6.0.0",
|
||||
@@ -3429,6 +3494,23 @@
|
||||
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"fbjs": {
|
||||
"version": "0.8.16",
|
||||
"from": "fbjs@>=0.8.9 <0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "1.2.7",
|
||||
"from": "core-js@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz"
|
||||
},
|
||||
"promise": {
|
||||
"version": "7.3.1",
|
||||
"from": "promise@>=7.1.1 <8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fd-slicer": {
|
||||
"version": "1.0.1",
|
||||
"from": "fd-slicer@>=1.0.1 <1.1.0",
|
||||
@@ -3744,6 +3826,11 @@
|
||||
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"fuse.js": {
|
||||
"version": "3.2.0",
|
||||
"from": "fuse.js@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.2.0.tgz"
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.4",
|
||||
"from": "gauge@>=2.7.3 <2.8.0",
|
||||
@@ -5307,8 +5394,7 @@
|
||||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"from": "is-stream@>=1.1.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"dev": true
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz"
|
||||
},
|
||||
"is-symbol": {
|
||||
"version": "1.0.1",
|
||||
@@ -5355,6 +5441,11 @@
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"isomorphic-fetch": {
|
||||
"version": "2.2.1",
|
||||
"from": "isomorphic-fetch@>=2.1.1 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz"
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"from": "isstream@>=0.1.2 <0.2.0",
|
||||
@@ -5391,8 +5482,7 @@
|
||||
"js-tokens": {
|
||||
"version": "3.0.2",
|
||||
"from": "js-tokens@>=3.0.2 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
|
||||
"dev": true
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz"
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "2.0.5",
|
||||
@@ -6858,8 +6948,7 @@
|
||||
"loose-envify": {
|
||||
"version": "1.3.1",
|
||||
"from": "loose-envify@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
|
||||
"dev": true
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz"
|
||||
},
|
||||
"loud-rejection": {
|
||||
"version": "1.6.0",
|
||||
@@ -7620,6 +7709,11 @@
|
||||
"from": "nocache@2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nocache/-/nocache-2.0.0.tgz"
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "1.7.3",
|
||||
"from": "node-fetch@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz"
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "0.2.24",
|
||||
"from": "node-forge@0.2.24",
|
||||
@@ -8663,6 +8757,11 @@
|
||||
"resolved": "https://registry.npmjs.org/prompt/-/prompt-0.2.14.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "15.6.1",
|
||||
"from": "prop-types@>=15.5.10 <16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz"
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "1.0.10",
|
||||
"from": "proxy-addr@>=1.0.8 <1.1.0",
|
||||
@@ -8967,6 +9066,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "15.6.2",
|
||||
"from": "react@>=15.4.2 <16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz"
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "15.6.2",
|
||||
"from": "react-dom@>=15.4.2 <16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz"
|
||||
},
|
||||
"read": {
|
||||
"version": "1.0.7",
|
||||
"from": "read@>=1.0.0 <1.1.0",
|
||||
@@ -9676,8 +9785,7 @@
|
||||
"setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"from": "setimmediate@>=1.0.4 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"dev": true
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz"
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.0.3",
|
||||
@@ -10945,6 +11053,11 @@
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"ua-parser-js": {
|
||||
"version": "0.7.17",
|
||||
"from": "ua-parser-js@>=0.7.9 <0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz"
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "2.4.24",
|
||||
"from": "uglify-js@>=2.4.0 <2.5.0",
|
||||
@@ -12061,6 +12174,11 @@
|
||||
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
"version": "2.0.4",
|
||||
"from": "whatwg-fetch@>=0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz"
|
||||
},
|
||||
"when": {
|
||||
"version": "3.7.8",
|
||||
"from": "when@>=3.7.7 <4.0.0",
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"express-http-proxy": "^1.1.0",
|
||||
"express-session": "^1.14.2",
|
||||
"fs-extra": "^4.0.2",
|
||||
"fuse.js": "^3.0.0",
|
||||
"heapdump": "^0.3.7",
|
||||
"helmet": "^3.8.1",
|
||||
"http-proxy": "^1.8.1",
|
||||
@@ -72,6 +73,8 @@
|
||||
"passport-oauth2-refresh": "^1.0.0",
|
||||
"passport-saml": "^0.15.0",
|
||||
"pug": "^2.0.0-beta6",
|
||||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2",
|
||||
"redis-sharelatex": "git+https://github.com/sharelatex/redis-sharelatex.git#v1.0.4",
|
||||
"request": "^2.69.0",
|
||||
"requestretry": "^1.13.0",
|
||||
@@ -95,6 +98,7 @@
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-react": "^6.16.0",
|
||||
"bunyan": "0.22.1",
|
||||
"chai": "3.5.0",
|
||||
"chai-spies": "",
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# For sending event data to metabase and google analytics
|
||||
# ---
|
||||
# by default,
|
||||
# event not sent to MB.
|
||||
# for MB, add event-tracking-mb='true'
|
||||
# by default, event sent to MB via sendMB
|
||||
# this can be changed to use sendMBOnce via event-tracking-send-once='true' attribute
|
||||
# event not sent to GA.
|
||||
# for GA, add event-tracking-ga attribute, where the value is the GA category
|
||||
# event-tracking-trigger attribute is required to send event
|
||||
|
||||
define [
|
||||
'base'
|
||||
], (App) ->
|
||||
App.directive 'eventTracking', ['event_tracking', (event_tracking) ->
|
||||
return {
|
||||
scope: {
|
||||
eventTracking: '@',
|
||||
eventSegmentation: '=?'
|
||||
}
|
||||
link: (scope, element, attrs) ->
|
||||
sendGA = attrs.eventTrackingGa || false
|
||||
sendMB = attrs.eventTrackingMb || false
|
||||
sendMBFunction = if attrs.eventTrackingSendOnce then 'sendMBOnce' else 'sendMB'
|
||||
segmentation = scope.eventSegmentation || {}
|
||||
|
||||
segmentation.page = window.location.pathname
|
||||
|
||||
sendEvent = () ->
|
||||
if sendMB
|
||||
event_tracking[sendMBFunction] scope.eventTracking, segmentation
|
||||
if sendGA
|
||||
event_tracking.send attrs.eventTrackingGa, attrs.eventTrackingAction || scope.eventTracking, attrs.eventTrackingLabel || ''
|
||||
|
||||
if attrs.eventTrackingTrigger == 'load'
|
||||
sendEvent()
|
||||
else if attrs.eventTrackingTrigger == 'click'
|
||||
element.on 'click', (e) ->
|
||||
sendEvent()
|
||||
}
|
||||
]
|
||||
@@ -17,3 +17,7 @@ define [
|
||||
App.filter "relativeDate", () ->
|
||||
(date) ->
|
||||
moment(date).calendar()
|
||||
|
||||
App.filter "fromNowDate", () ->
|
||||
(date) ->
|
||||
moment(date).fromNow()
|
||||
|
||||
@@ -8,6 +8,7 @@ define [
|
||||
@openFile(entity)
|
||||
|
||||
openFile: (file) ->
|
||||
@ide.fileTreeManager.selectEntity(file)
|
||||
@$scope.ui.view = "file"
|
||||
@$scope.openFile = null
|
||||
@$scope.$apply()
|
||||
|
||||
@@ -2,12 +2,17 @@ define [
|
||||
"base"
|
||||
"libs/jquery-layout"
|
||||
], (App) ->
|
||||
App.directive "layout", ["$parse", "ide", ($parse, ide) ->
|
||||
App.directive "layout", ["$parse", "$compile", "ide", ($parse, $compile, ide) ->
|
||||
return {
|
||||
compile: () ->
|
||||
pre: (scope, element, attrs) ->
|
||||
name = attrs.layout
|
||||
|
||||
customTogglerPane = scope.$eval(attrs.customTogglerPane or "false")
|
||||
customTogglerMsgWhenOpen = scope.$eval(attrs.customTogglerMsgWhenOpen or "false")
|
||||
customTogglerMsgWhenClosed = scope.$eval(attrs.customTogglerMsgWhenClosed or "false")
|
||||
hasCustomToggler = customTogglerPane != false and customTogglerMsgWhenOpen != false and customTogglerMsgWhenClosed != false
|
||||
|
||||
if attrs.spacingOpen?
|
||||
spacingOpen = parseInt(attrs.spacingOpen, 10)
|
||||
else
|
||||
@@ -23,6 +28,10 @@ define [
|
||||
spacing_closed: spacingClosed
|
||||
slidable: false
|
||||
enableCursorHotkey: false
|
||||
onopen: (pane) =>
|
||||
onPaneOpen(pane)
|
||||
onclose: (pane) =>
|
||||
onPaneClose(pane)
|
||||
onresize: () =>
|
||||
onInternalResize()
|
||||
maskIframesOnResize: scope.$eval(
|
||||
@@ -62,6 +71,15 @@ define [
|
||||
right: state.east.size
|
||||
})
|
||||
|
||||
repositionCustomToggler = () ->
|
||||
if !customTogglerEl?
|
||||
return
|
||||
state = element.layout().readState()
|
||||
positionAnchor = if customTogglerPane == "east" then "right" else "left"
|
||||
paneState = state[customTogglerPane]
|
||||
if paneState?
|
||||
customTogglerEl.css(positionAnchor, if paneState.initClosed then 0 else paneState.size)
|
||||
|
||||
resetOpenStates = () ->
|
||||
state = element.layout().readState()
|
||||
if attrs.openEast? and state.east?
|
||||
@@ -73,6 +91,8 @@ define [
|
||||
state = element.layout().readState()
|
||||
scope.$broadcast "layout:#{name}:resize", state
|
||||
repositionControls()
|
||||
if hasCustomToggler
|
||||
repositionCustomToggler()
|
||||
resetOpenStates()
|
||||
|
||||
oldWidth = element.width()
|
||||
@@ -94,6 +114,46 @@ define [
|
||||
if attrs.resizeOn?
|
||||
scope.$on attrs.resizeOn, () -> onExternalResize()
|
||||
|
||||
if hasCustomToggler
|
||||
state = element.layout().readState()
|
||||
customTogglerScope = scope.$new()
|
||||
|
||||
customTogglerScope.isOpen = true
|
||||
customTogglerScope.isVisible = true
|
||||
|
||||
if state[customTogglerPane]?.initClosed == true
|
||||
customTogglerScope.isOpen = false
|
||||
|
||||
customTogglerScope.tooltipMsgWhenOpen = customTogglerMsgWhenOpen
|
||||
customTogglerScope.tooltipMsgWhenClosed = customTogglerMsgWhenClosed
|
||||
|
||||
customTogglerScope.tooltipPlacement = if customTogglerPane == "east" then "left" else "right"
|
||||
customTogglerScope.handleClick = () ->
|
||||
element.layout().toggle(customTogglerPane)
|
||||
repositionCustomToggler()
|
||||
customTogglerEl = $compile("
|
||||
<a href
|
||||
ng-show=\"isVisible\"
|
||||
class=\"custom-toggler #{ 'custom-toggler-' + customTogglerPane }\"
|
||||
ng-class=\"isOpen ? 'custom-toggler-open' : 'custom-toggler-closed'\"
|
||||
tooltip=\"{{ isOpen ? tooltipMsgWhenOpen : tooltipMsgWhenClosed }}\"
|
||||
tooltip-placement=\"{{ tooltipPlacement }}\"
|
||||
ng-click=\"handleClick()\">
|
||||
")(customTogglerScope)
|
||||
element.append(customTogglerEl)
|
||||
|
||||
onPaneOpen = (pane) ->
|
||||
if !hasCustomToggler and pane != customTogglerPane
|
||||
return
|
||||
customTogglerEl.scope().$applyAsync () ->
|
||||
customTogglerEl.scope().isOpen = true
|
||||
|
||||
onPaneClose = (pane) ->
|
||||
if !hasCustomToggler and pane != customTogglerPane
|
||||
return
|
||||
customTogglerEl.scope().$applyAsync () ->
|
||||
customTogglerEl.scope().isOpen = false
|
||||
|
||||
# Save state when exiting
|
||||
$(window).unload () ->
|
||||
ide.localStorage("layout.#{name}", element.layout().readState())
|
||||
@@ -128,6 +188,11 @@ define [
|
||||
element.layout().hide("east")
|
||||
else
|
||||
element.layout().show("east")
|
||||
if hasCustomToggler
|
||||
customTogglerEl.scope().$applyAsync () ->
|
||||
customTogglerEl.scope().isOpen = !value
|
||||
customTogglerEl.scope().isVisible = !value
|
||||
|
||||
|
||||
post: (scope, element, attrs) ->
|
||||
name = attrs.layout
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
define [
|
||||
"ide/editor/Document"
|
||||
"ide/editor/directives/aceEditor"
|
||||
"ide/editor/directives/cmEditor"
|
||||
"ide/editor/directives/toggleSwitch"
|
||||
"ide/editor/controllers/SavingNotificationController"
|
||||
"ide/editor/controllers/EditorToolbarController"
|
||||
], (Document) ->
|
||||
class EditorManager
|
||||
constructor: (@ide, @$scope) ->
|
||||
@@ -189,6 +188,3 @@ define [
|
||||
@$scope.editor.trackChanges = want
|
||||
else
|
||||
@_syncTimeout = setTimeout tryToggle, 100
|
||||
|
||||
toggleRichText: () ->
|
||||
@$scope.editor.richText = !@$scope.editor.richText
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
define [
|
||||
"base"
|
||||
"ide/editor/Document"
|
||||
], (App, Document) ->
|
||||
App.controller "EditorToolbarController", ($scope, ide) ->
|
||||
$scope.toggleRichText = () ->
|
||||
ide.editorManager.toggleRichText()
|
||||
@@ -1,33 +0,0 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.directive "cmEditor", () ->
|
||||
return {
|
||||
scope: {
|
||||
sharejsDoc: "="
|
||||
}
|
||||
|
||||
link: (scope, element, attrs) ->
|
||||
cm = Frontend.richText.init(element.find('.cm-editor-wrapper')[0])
|
||||
|
||||
scope.$watch "sharejsDoc", (sharejsDoc, oldSharejsDoc) ->
|
||||
if oldSharejsDoc?
|
||||
detachFromCM(oldSharejsDoc)
|
||||
if sharejsDoc?
|
||||
attachToCM(sharejsDoc)
|
||||
|
||||
attachToCM = (sharejsDoc) ->
|
||||
scope.$applyAsync () ->
|
||||
Frontend.richText.openDoc(cm, sharejsDoc.getSnapshot())
|
||||
sharejsDoc.attachToCM(cm)
|
||||
|
||||
detachFromCM = (sharejsDoc) ->
|
||||
sharejsDoc.detachFromCM()
|
||||
|
||||
scope.$on 'destroy', () ->
|
||||
detachFromCM(scope.sharejsDoc)
|
||||
|
||||
template: """
|
||||
<div class="cm-editor-wrapper"></div>
|
||||
"""
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.directive "toggleSwitch", () ->
|
||||
restrict: "E"
|
||||
scope:
|
||||
description: "@"
|
||||
labelFalse: "@"
|
||||
labelTrue: "@"
|
||||
ngModel: "="
|
||||
template: """
|
||||
<fieldset class="toggle-switch">
|
||||
<legend class="sr-only">{{description}}</legend>
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
name="editor-mode"
|
||||
class="toggle-switch-input"
|
||||
id="toggle-switch-false-{{$id}}"
|
||||
ng-value="false"
|
||||
ng-model="ngModel"
|
||||
>
|
||||
<label for="toggle-switch-false-{{$id}}" class="toggle-switch-label">{{labelFalse}}</label>
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
class="toggle-switch-input"
|
||||
name="editor-mode"
|
||||
id="toggle-switch-true-{{$id}}"
|
||||
ng-value="true"
|
||||
ng-model="ngModel"
|
||||
>
|
||||
<label for="toggle-switch-true-{{$id}}" class="toggle-switch-label">{{labelTrue}}</label>
|
||||
|
||||
<span class="toggle-switch-selection" aria-hidden="true"></span>
|
||||
</fieldset>
|
||||
"""
|
||||
@@ -4,6 +4,7 @@ define [
|
||||
"ide/history/util/displayNameForUser"
|
||||
"ide/history/controllers/HistoryListController"
|
||||
"ide/history/controllers/HistoryDiffController"
|
||||
"ide/history/controllers/HistoryV2DiffController"
|
||||
"ide/history/directives/infiniteScroll"
|
||||
], (moment, ColorManager, displayNameForUser) ->
|
||||
class HistoryManager
|
||||
|
||||
@@ -49,6 +49,13 @@ define [
|
||||
diff: null
|
||||
}
|
||||
|
||||
restoreFile: (version, pathname) ->
|
||||
url = "/project/#{@$scope.project_id}/restore_file"
|
||||
@ide.$http.post(url, {
|
||||
version, pathname,
|
||||
_csrf: window.csrfToken
|
||||
})
|
||||
|
||||
MAX_RECENT_UPDATES_TO_SELECT: 5
|
||||
autoSelectRecentUpdates: () ->
|
||||
return if @$scope.history.updates.length == 0
|
||||
@@ -204,7 +211,7 @@ define [
|
||||
# Map of original pathname -> doc summary
|
||||
docs_summary = Object.create(null)
|
||||
|
||||
updatePathnameWithUpdateVersions = (pathname, update, deleted) ->
|
||||
updatePathnameWithUpdateVersions = (pathname, update, deletedAtV) ->
|
||||
# docs_summary is indexed by the original pathname the doc
|
||||
# had at the start, so we have to look this up from the current
|
||||
# pathname via original_pathname first
|
||||
@@ -222,8 +229,8 @@ define [
|
||||
doc_summary.toV,
|
||||
update.toV
|
||||
)
|
||||
if deleted?
|
||||
doc_summary.deleted = true
|
||||
if deletedAtV?
|
||||
doc_summary.deletedAtV = deletedAtV
|
||||
|
||||
# Put updates in ascending chronological order
|
||||
updates = updates.slice().reverse()
|
||||
@@ -241,7 +248,7 @@ define [
|
||||
updatePathnameWithUpdateVersions(add.pathname, update)
|
||||
if project_op.remove?
|
||||
remove = project_op.remove
|
||||
updatePathnameWithUpdateVersions(remove.pathname, update, true)
|
||||
updatePathnameWithUpdateVersions(remove.pathname, update, project_op.atV)
|
||||
|
||||
return docs_summary
|
||||
|
||||
|
||||
@@ -3,15 +3,6 @@ define [
|
||||
"ide/history/util/displayNameForUser"
|
||||
], (App, displayNameForUser) ->
|
||||
|
||||
App.controller "HistoryPremiumPopup", ($scope, ide, sixpack)->
|
||||
$scope.$watch "ui.view", ->
|
||||
if $scope.ui.view == "history"
|
||||
if $scope.project?.features?.versioning
|
||||
$scope.versioningPopupType = "default"
|
||||
else if $scope.ui.view == "history"
|
||||
sixpack.participate 'history-discount', ['default', 'discount'], (chosenVariation, rawResponse)->
|
||||
$scope.versioningPopupType = chosenVariation
|
||||
|
||||
App.controller "HistoryListController", ["$scope", "ide", ($scope, ide) ->
|
||||
$scope.hoveringOverListSelectors = false
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.controller "HistoryV2DiffController", ($scope, ide, event_tracking) ->
|
||||
$scope.restoreState =
|
||||
inflight: false
|
||||
error: false
|
||||
|
||||
$scope.restoreDeletedFile = () ->
|
||||
pathname = $scope.history.selection.pathname
|
||||
return if !pathname?
|
||||
version = $scope.history.selection.docs[pathname]?.deletedAtV
|
||||
return if !version?
|
||||
event_tracking.sendMB "history-v2-restore-deleted"
|
||||
$scope.restoreState.inflight = true
|
||||
ide.historyManager
|
||||
.restoreFile(version, pathname)
|
||||
.then (response) ->
|
||||
{ data } = response
|
||||
openEntity(data)
|
||||
.catch () ->
|
||||
ide.showGenericMessageModal('Sorry, something went wrong with the restore')
|
||||
.finally () ->
|
||||
$scope.restoreState.inflight = false
|
||||
|
||||
openEntity = (data) ->
|
||||
iterations = 0
|
||||
{id, type} = data
|
||||
do tryOpen = () ->
|
||||
if iterations > 5
|
||||
return
|
||||
iterations += 1
|
||||
entity = ide.fileTreeManager.findEntityById(id)
|
||||
if entity? and type == 'doc'
|
||||
ide.editorManager.openDoc(entity)
|
||||
else if entity? and type == 'file'
|
||||
ide.binaryFilesManager.openFile(entity)
|
||||
else
|
||||
setTimeout(tryOpen, 500)
|
||||
|
||||
@@ -24,6 +24,7 @@ define [
|
||||
"directives/stopPropagation"
|
||||
"directives/focus"
|
||||
"directives/equals"
|
||||
"directives/eventTracking"
|
||||
"directives/fineUpload"
|
||||
"directives/onEnter"
|
||||
"directives/selectAll"
|
||||
|
||||
@@ -19,24 +19,8 @@ define [
|
||||
url = "#{url}&cc=#{couponCode}"
|
||||
$scope.startedFreeTrial = true
|
||||
|
||||
switch source
|
||||
when "dropbox"
|
||||
sixpack.participate 'teaser-dropbox-text', ['default', 'dropbox-focused'], (variant) ->
|
||||
event_tracking.sendMB "subscription-start-trial", { source, plan, variant }
|
||||
|
||||
when "history"
|
||||
sixpack.participate 'teaser-history', ['default', 'focused'], (variant) ->
|
||||
event_tracking.sendMB "subscription-start-trial", { source, plan, variant }
|
||||
|
||||
else
|
||||
event_tracking.sendMB "subscription-start-trial", { source, plan }
|
||||
event_tracking.sendMB "subscription-start-trial", { source, plan }
|
||||
|
||||
w.location = url
|
||||
|
||||
if $scope.shouldABTestPlans
|
||||
sixpack.participate 'plans-1610', ['default', 'heron', 'ibis'], (chosenVariation, rawResponse)->
|
||||
if chosenVariation in ['heron', 'ibis']
|
||||
plan = "collaborator_#{chosenVariation}"
|
||||
go()
|
||||
else
|
||||
go()
|
||||
go()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
define [
|
||||
"base",
|
||||
"directives/creditCards"
|
||||
"libs/recurly-4.8.5"
|
||||
], (App)->
|
||||
|
||||
App.controller "NewSubscriptionController", ($scope, MultiCurrencyPricing, abTestManager, $http, sixpack, event_tracking, ccUtils)->
|
||||
@@ -21,10 +22,6 @@ define [
|
||||
value: "credit_card"
|
||||
|
||||
$scope.data =
|
||||
number: ""
|
||||
month: ""
|
||||
year: ""
|
||||
cvv: ""
|
||||
first_name: ""
|
||||
last_name: ""
|
||||
postal_code: ""
|
||||
@@ -34,16 +31,25 @@ define [
|
||||
city:""
|
||||
country:window.countryCode
|
||||
coupon: window.couponCode
|
||||
mmYY: ""
|
||||
|
||||
$scope.validation =
|
||||
correctCardNumber : true
|
||||
correctExpiry: true
|
||||
correctCvv: true
|
||||
|
||||
$scope.validation = {}
|
||||
|
||||
$scope.processing = false
|
||||
|
||||
recurly.configure window.recurlyApiKey
|
||||
recurly.configure
|
||||
publicKey: window.recurlyApiKey
|
||||
style:
|
||||
all:
|
||||
fontFamily: '"Open Sans", sans-serif',
|
||||
fontSize: '16px',
|
||||
fontColor: '#7a7a7a'
|
||||
month:
|
||||
placeholder: 'MM'
|
||||
year:
|
||||
placeholder: 'YY'
|
||||
cvv:
|
||||
placeholder: 'CVV'
|
||||
|
||||
|
||||
pricing = recurly.Pricing()
|
||||
window.pricing = pricing
|
||||
@@ -73,6 +79,8 @@ define [
|
||||
$scope.normalPrice += (basePrice * pricing.price.taxes[0].rate)
|
||||
$scope.$apply()
|
||||
|
||||
|
||||
|
||||
$scope.applyCoupon = ->
|
||||
pricing.coupon($scope.data.coupon).done()
|
||||
|
||||
@@ -83,26 +91,6 @@ define [
|
||||
$scope.currencyCode = newCurrency
|
||||
pricing.currency(newCurrency).done()
|
||||
|
||||
$scope.updateExpiry = () ->
|
||||
parsedDateObj = ccUtils.parseExpiry $scope.data.mmYY
|
||||
if parsedDateObj?
|
||||
$scope.data.month = parsedDateObj.month
|
||||
$scope.data.year = parsedDateObj.year
|
||||
|
||||
$scope.validateCardNumber = validateCardNumber = ->
|
||||
$scope.validation.errorFields = {}
|
||||
if $scope.data.number?.length != 0
|
||||
$scope.validation.correctCardNumber = recurly.validate.cardNumber($scope.data.number)
|
||||
|
||||
$scope.validateExpiry = validateExpiry = ->
|
||||
$scope.validation.errorFields = {}
|
||||
if $scope.data.month?.length != 0 and $scope.data.year?.length != 0
|
||||
$scope.validation.correctExpiry = recurly.validate.expiry($scope.data.month, $scope.data.year)
|
||||
|
||||
$scope.validateCvv = validateCvv = ->
|
||||
$scope.validation.errorFields = {}
|
||||
if $scope.data.cvv?.length != 0
|
||||
$scope.validation.correctCvv = recurly.validate.cvv($scope.data.cvv)
|
||||
|
||||
$scope.inputHasError = inputHasError = (formItem) ->
|
||||
if !formItem?
|
||||
@@ -114,10 +102,7 @@ define [
|
||||
if $scope.paymentMethod.value == 'paypal'
|
||||
return $scope.data.country != ""
|
||||
else
|
||||
return (form.$valid and
|
||||
$scope.validation.correctCardNumber and
|
||||
$scope.validation.correctExpiry and
|
||||
$scope.validation.correctCvv)
|
||||
return form.$valid
|
||||
|
||||
$scope.updateCountry = ->
|
||||
pricing.address({country:$scope.data.country}).done()
|
||||
@@ -178,4 +163,74 @@ define [
|
||||
recurly.token $scope.data, completeSubscription
|
||||
|
||||
|
||||
|
||||
$scope.countries = [
|
||||
{code:'AF',name:'Afghanistan'},{code:'AL',name:'Albania'},{code:'DZ',name:'Algeria'},{code:'AS',name:'American Samoa'},
|
||||
{code:'AD',name:'Andorra'},{code:'AO',name:'Angola'},{code:'AI',name:'Anguilla'},{code:'AQ',name:'Antarctica'},
|
||||
{code:'AG',name:'Antigua and Barbuda'},{code:'AR',name:'Argentina'},{code:'AM',name:'Armenia'},{code:'AW',name:'Aruba'},
|
||||
{code:'AC',name:'Ascension Island'},{code:'AU',name:'Australia'},{code:'AT',name:'Austria'},{code:'AZ',name:'Azerbaijan'},
|
||||
{code:'BS',name:'Bahamas'},{code:'BH',name:'Bahrain'},{code:'BD',name:'Bangladesh'},{code:'BB',name:'Barbados'},
|
||||
{code:'BE',name:'Belgium'},{code:'BZ',name:'Belize'},{code:'BJ',name:'Benin'},{code:'BM',name:'Bermuda'},
|
||||
{code:'BT',name:'Bhutan'},{code:'BO',name:'Bolivia'},{code:'BA',name:'Bosnia and Herzegovina'},{code:'BW',name:'Botswana'},
|
||||
{code:'BV',name:'Bouvet Island'},{code:'BR',name:'Brazil'},{code:'BQ',name:'British Antarctic Territory'},
|
||||
{code:'IO',name:'British Indian Ocean Territory'},{code:'VG',name:'British Virgin Islands'},{code:'BN',name:'Brunei'},
|
||||
{code:'BG',name:'Bulgaria'},{code:'BF',name:'Burkina Faso'},{code:'BI',name:'Burundi'},{code:'KH',name:'Cambodia'},
|
||||
{code:'CM',name:'Cameroon'},{code:'CA',name:'Canada'},{code:'IC',name:'Canary Islands'},
|
||||
{code:'CT',name:'Canton and Enderbury Islands'},{code:'CV',name:'Cape Verde'},{code:'KY',name:'Cayman Islands'},
|
||||
{code:'CF',name:'Central African Republic'},{code:'EA',name:'Ceuta and Melilla'},{code:'TD',name:'Chad'},
|
||||
{code:'CL',name:'Chile'},{code:'CN',name:'China'},{code:'CX',name:'Christmas Island'},{code:'CP',name:'Clipperton Island'},
|
||||
{code:'CC',name:'Cocos [Keeling] Islands'},{code:'CO',name:'Colombia'},{code:'KM',name:'Comoros'},{code:'CD',name:'Congo [DRC]'},
|
||||
{code:'CK',name:'Cook Islands'},{code:'CR',name:'Costa Rica'},{code:'HR',name:'Croatia'},{code:'CU',name:'Cuba'},
|
||||
{code:'CY',name:'Cyprus'},{code:'CZ',name:'Czech Republic'},{code:'DK',name:'Denmark'},{code:'DG',name:'Diego Garcia'},
|
||||
{code:'DJ',name:'Djibouti'},{code:'DM',name:'Dominica'},{code:'DO',name:'Dominican Republic'},
|
||||
{code:'NQ',name:'Dronning Maud Land'},{code:'TL',name:'East Timor'},{code:'EC',name:'Ecuador'},{code:'EG',name:'Egypt'},
|
||||
{code:'SV',name:'El Salvador'},{code:'EE',name:'Estonia'},{code:'ET',name:'Ethiopia'},
|
||||
{code:'FK',name:'Falkland Islands [Islas Malvinas]'},{code:'FO',name:'Faroe Islands'},{code:'FJ',name:'Fiji'},
|
||||
{code:'FI',name:'Finland'},{code:'FR',name:'France'},{code:'GF',name:'French Guiana'},{code:'PF',name:'French Polynesia'},
|
||||
{code:'TF',name:'French Southern Territories'},{code:'FQ',name:'French Southern and Antarctic Territories'},
|
||||
{code:'GA',name:'Gabon'},{code:'GM',name:'Gambia'},{code:'GE',name:'Georgia'},{code:'DE',name:'Germany'},
|
||||
{code:'GH',name:'Ghana'},{code:'GI',name:'Gibraltar'},{code:'GR',name:'Greece'},{code:'GL',name:'Greenland'},
|
||||
{code:'GD',name:'Grenada'},{code:'GP',name:'Guadeloupe'},{code:'GU',name:'Guam'},{code:'GT',name:'Guatemala'},
|
||||
{code:'GG',name:'Guernsey'},{code:'GW',name:'Guinea-Bissau'},{code:'GY',name:'Guyana'},{code:'HT',name:'Haiti'},
|
||||
{code:'HM',name:'Heard Island and McDonald Islands'},{code:'HN',name:'Honduras'},{code:'HK',name:'Hong Kong'},
|
||||
{code:'HU',name:'Hungary'},{code:'IS',name:'Iceland'},{code:'IN',name:'India'},{code:'ID',name:'Indonesia'},
|
||||
{code:'IE',name:'Ireland'},{code:'IM',name:'Isle of Man'},{code:'IL',name:'Israel'},{code:'IT',name:'Italy'},
|
||||
{code:'JM',name:'Jamaica'},{code:'JP',name:'Japan'},{code:'JE',name:'Jersey'},{code:'JT',name:'Johnston Island'},
|
||||
{code:'JO',name:'Jordan'},{code:'KZ',name:'Kazakhstan'},{code:'KE',name:'Kenya'},{code:'KI',name:'Kiribati'},
|
||||
{code:'KW',name:'Kuwait'},{code:'KG',name:'Kyrgyzstan'},{code:'LA',name:'Laos'},{code:'LV',name:'Latvia'},
|
||||
{code:'LS',name:'Lesotho'},{code:'LY',name:'Libya'},{code:'LI',name:'Liechtenstein'},{code:'LT',name:'Lithuania'},
|
||||
{code:'LU',name:'Luxembourg'},{code:'MO',name:'Macau'},{code:'MK',name:'Macedonia [FYROM]'},{code:'MG',name:'Madagascar'},
|
||||
{code:'MW',name:'Malawi'},{code:'MY',name:'Malaysia'},{code:'MV',name:'Maldives'},{code:'ML',name:'Mali'},
|
||||
{code:'MT',name:'Malta'},{code:'MH',name:'Marshall Islands'},{code:'MQ',name:'Martinique'},{code:'MR',name:'Mauritania'},
|
||||
{code:'MU',name:'Mauritius'},{code:'YT',name:'Mayotte'},{code:'FX',name:'Metropolitan France'},{code:'MX',name:'Mexico'},
|
||||
{code:'FM',name:'Micronesia'},{code:'MI',name:'Midway Islands'},{code:'MD',name:'Moldova'},{code:'MC',name:'Monaco'},
|
||||
{code:'MN',name:'Mongolia'},{code:'ME',name:'Montenegro'},{code:'MS',name:'Montserrat'},{code:'MA',name:'Morocco'},
|
||||
{code:'MZ',name:'Mozambique'},{code:'NA',name:'Namibia'},{code:'NR',name:'Nauru'},{code:'NP',name:'Nepal'},
|
||||
{code:'NL',name:'Netherlands'},{code:'AN',name:'Netherlands Antilles'},{code:'NT',name:'Neutral Zone'},
|
||||
{code:'NC',name:'New Caledonia'},{code:'NZ',name:'New Zealand'},{code:'NI',name:'Nicaragua'},{code:'NE',name:'Niger'},
|
||||
{code:'NG',name:'Nigeria'},{code:'NU',name:'Niue'},{code:'NF',name:'Norfolk Island'},{code:'VD',name:'North Vietnam'},
|
||||
{code:'MP',name:'Northern Mariana Islands'},{code:'NO',name:'Norway'},{code:'OM',name:'Oman'},
|
||||
{code:'QO',name:'Outlying Oceania'},{code:'PC',name:'Pacific Islands Trust Territory'},{code:'PK',name:'Pakistan'},
|
||||
{code:'PW',name:'Palau'},{code:'PS',name:'Palestinian Territories'},{code:'PA',name:'Panama'},{code:'PZ',name:'Panama Canal Zone'},
|
||||
{code:'PY',name:'Paraguay'},{code:'YD',name:'People's Democratic Republic of Yemen'},{code:'PE',name:'Peru'},
|
||||
{code:'PH',name:'Philippines'},{code:'PN',name:'Pitcairn Islands'},{code:'PL',name:'Poland'},{code:'PT',name:'Portugal'},
|
||||
{code:'PR',name:'Puerto Rico'},{code:'QA',name:'Qatar'},{code:'RO',name:'Romania'},{code:'RU',name:'Russia'},
|
||||
{code:'RW',name:'Rwanda'},{code:'RE',name:'Réunion'},{code:'BL',name:'Saint Barthélemy'},
|
||||
{code:'SH',name:'Saint Helena'},{code:'KN',name:'Saint Kitts and Nevis'},{code:'LC',name:'Saint Lucia'},
|
||||
{code:'MF',name:'Saint Martin'},{code:'PM',name:'Saint Pierre and Miquelon'},{code:'VC',name:'Saint Vincent and the Grenadines'},
|
||||
{code:'WS',name:'Samoa'},{code:'SM',name:'San Marino'},{code:'SA',name:'Saudi Arabia'},{code:'SN',name:'Senegal'},
|
||||
{code:'RS',name:'Serbia'},{code:'CS',name:'Serbia and Montenegro'},{code:'SC',name:'Seychelles'},{code:'SL',name:'Sierra Leone'},
|
||||
{code:'SG',name:'Singapore'},{code:'SK',name:'Slovakia'},{code:'SI',name:'Slovenia'},{code:'SB',name:'Solomon Islands'},
|
||||
{code:'ZA',name:'South Africa'},{code:'GS',name:'South Georgia and the South Sandwich Islands'},{code:'KR',name:'South Korea'},
|
||||
{code:'ES',name:'Spain'},{code:'LK',name:'Sri Lanka'},{code:'SR',name:'Suriname'},{code:'SJ',name:'Svalbard and Jan Mayen'},
|
||||
{code:'SZ',name:'Swaziland'},{code:'SE',name:'Sweden'},{code:'CH',name:'Switzerland'},
|
||||
{code:'ST',name:'São Tomé and Príncipe'},{code:'TW',name:'Taiwan'},{code:'TJ',name:'Tajikistan'},
|
||||
{code:'TZ',name:'Tanzania'},{code:'TH',name:'Thailand'},{code:'TG',name:'Togo'},{code:'TK',name:'Tokelau'},{code:'TO',name:'Tonga'},
|
||||
{code:'TT',name:'Trinidad and Tobago'},{code:'TA',name:'Tristan da Cunha'},{code:'TN',name:'Tunisia'},{code:'TR',name:'Turkey'},
|
||||
{code:'TM',name:'Turkmenistan'},{code:'TC',name:'Turks and Caicos Islands'},{code:'TV',name:'Tuvalu'},
|
||||
{code:'UM',name:'U.S. Minor Outlying Islands'},{code:'PU',name:'U.S. Miscellaneous Pacific Islands'},
|
||||
{code:'VI',name:'U.S. Virgin Islands'},{code:'UG',name:'Uganda'},{code:'UA',name:'Ukraine'},{code:'AE',name:'United Arab Emirates'},
|
||||
{code:'GB',name:'United Kingdom'},{code:'US',name:'United States'},{code:'UY',name:'Uruguay'},{code:'UZ',name:'Uzbekistan'},
|
||||
{code:'VU',name:'Vanuatu'},{code:'VA',name:'Vatican City'},{code:'VE',name:'Venezuela'},{code:'VN',name:'Vietnam'},
|
||||
{code:'WK',name:'Wake Island'},{code:'WF',name:'Wallis and Futuna'},{code:'EH',name:'Western Sahara'},{code:'YE',name:'Yemen'},
|
||||
{code:'ZM',name:'Zambia'},{code:'AX',name:'Åland Islandscode:'}
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
define [
|
||||
"base"
|
||||
"libs/recurly-3.0.5"
|
||||
"libs/recurly-4.8.5"
|
||||
], (App, recurly) ->
|
||||
|
||||
|
||||
@@ -11,164 +11,6 @@ define [
|
||||
return {
|
||||
currencyCode:currencyCode
|
||||
|
||||
heron:
|
||||
USD:
|
||||
student:
|
||||
monthly: "$6"
|
||||
annual: "$60"
|
||||
collaborator:
|
||||
monthly: "$12"
|
||||
annual: "$144"
|
||||
EUR:
|
||||
student:
|
||||
monthly: "€5"
|
||||
annual: "€50"
|
||||
collaborator:
|
||||
monthly: "€11"
|
||||
annual: "€132"
|
||||
GBP:
|
||||
student:
|
||||
monthly: "£5"
|
||||
annual: "£50"
|
||||
collaborator:
|
||||
monthly: "£10"
|
||||
annual: "£120"
|
||||
SEK:
|
||||
student:
|
||||
monthly: "45 kr"
|
||||
annual: "450 kr"
|
||||
collaborator:
|
||||
monthly: "90 kr"
|
||||
annual: "1080 kr"
|
||||
CAD:
|
||||
student:
|
||||
monthly: "$7"
|
||||
annual: "$70"
|
||||
collaborator:
|
||||
monthly: "$14"
|
||||
annual: "$168"
|
||||
NOK:
|
||||
student:
|
||||
monthly: "45 kr"
|
||||
annual: "450 kr"
|
||||
collaborator:
|
||||
monthly: "90 kr"
|
||||
annual: "1080 kr"
|
||||
DKK:
|
||||
student:
|
||||
monthly: "40 kr"
|
||||
annual: "400 kr"
|
||||
collaborator:
|
||||
monthly: "70 kr"
|
||||
annual: "840 kr"
|
||||
AUD:
|
||||
student:
|
||||
monthly: "$8"
|
||||
annual: "$80"
|
||||
collaborator:
|
||||
monthly: "$15"
|
||||
annual: "$180"
|
||||
NZD:
|
||||
student:
|
||||
monthly: "$8"
|
||||
annual: "$80"
|
||||
collaborator:
|
||||
monthly: "$15"
|
||||
annual: "$180"
|
||||
CHF:
|
||||
student:
|
||||
monthly: "Fr 6"
|
||||
annual: "Fr 60"
|
||||
collaborator:
|
||||
monthly: "Fr 12"
|
||||
annual: "Fr 144"
|
||||
SGD:
|
||||
student:
|
||||
monthly: "$8"
|
||||
annual: "$80"
|
||||
collaborator:
|
||||
monthly: "$16"
|
||||
annual: "$192"
|
||||
|
||||
ibis:
|
||||
USD:
|
||||
student:
|
||||
monthly: "$10"
|
||||
annual: "$100"
|
||||
collaborator:
|
||||
monthly: "$18"
|
||||
annual: "$216"
|
||||
EUR:
|
||||
student:
|
||||
monthly: "€9"
|
||||
annual: "€90"
|
||||
collaborator:
|
||||
monthly: "€17"
|
||||
annual: "€204"
|
||||
GBP:
|
||||
student:
|
||||
monthly: "£7"
|
||||
annual: "£70"
|
||||
collaborator:
|
||||
monthly: "£14"
|
||||
annual: "£168"
|
||||
SEK:
|
||||
student:
|
||||
monthly: "75 kr"
|
||||
annual: "750 kr"
|
||||
collaborator:
|
||||
monthly: "140 kr"
|
||||
annual: "1680 kr"
|
||||
CAD:
|
||||
student:
|
||||
monthly: "$12"
|
||||
annual: "$120"
|
||||
collaborator:
|
||||
monthly: "$22"
|
||||
annual: "$264"
|
||||
NOK:
|
||||
student:
|
||||
monthly: "75 kr"
|
||||
annual: "750 kr"
|
||||
collaborator:
|
||||
monthly: "140 kr"
|
||||
annual: "1680 kr"
|
||||
DKK:
|
||||
student:
|
||||
monthly: "68 kr"
|
||||
annual: "680 kr"
|
||||
collaborator:
|
||||
monthly: "110 kr"
|
||||
annual: "1320 kr"
|
||||
AUD:
|
||||
student:
|
||||
monthly: "$13"
|
||||
annual: "$130"
|
||||
collaborator:
|
||||
monthly: "$22"
|
||||
annual: "$264"
|
||||
NZD:
|
||||
student:
|
||||
monthly: "$14"
|
||||
annual: "$140"
|
||||
collaborator:
|
||||
monthly: "$22"
|
||||
annual: "$264"
|
||||
CHF:
|
||||
student:
|
||||
monthly: "Fr 10"
|
||||
annual: "Fr 100"
|
||||
collaborator:
|
||||
monthly: "Fr 18"
|
||||
annual: "Fr 216"
|
||||
SGD:
|
||||
student:
|
||||
monthly: "$14"
|
||||
annual: "$140"
|
||||
collaborator:
|
||||
monthly: "$25"
|
||||
annual: "$300"
|
||||
|
||||
plans:
|
||||
USD:
|
||||
symbol: "$"
|
||||
@@ -312,14 +154,6 @@ define [
|
||||
$scope.shouldABTestPlans = window.shouldABTestPlans
|
||||
|
||||
if $scope.shouldABTestPlans
|
||||
sixpack.participate 'plans-1610', ['default', 'heron', 'ibis'], (chosenVariation, rawResponse)->
|
||||
$scope.plansVariant = chosenVariation
|
||||
event_tracking.sendMB 'plans-page', {plans_variant: chosenVariation}
|
||||
if chosenVariation in ['heron', 'ibis']
|
||||
# overwrite student plans with alternative
|
||||
for currency, _v of $scope.plans
|
||||
$scope.plans[currency]['student'] = MultiCurrencyPricing[chosenVariation][currency]['student']
|
||||
$scope.plans[currency]['collaborator'] = MultiCurrencyPricing[chosenVariation][currency]['collaborator']
|
||||
$scope.showPlans = true
|
||||
else
|
||||
$scope.showPlans = true
|
||||
|
||||
@@ -3,8 +3,6 @@ define [
|
||||
], (App)->
|
||||
|
||||
App.controller 'SuccessfulSubscriptionController', ($scope, sixpack) ->
|
||||
sixpack.convert 'plans-1610', () ->
|
||||
|
||||
|
||||
SUBSCRIPTION_URL = "/user/subscription/update"
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
define [], () ->
|
||||
# Simple event emitter implementation, but has a slightly unusual API for
|
||||
# removing specific listeners. If a specific listener needs to be removed
|
||||
# (instead of all listeners), then it needs to use a "namespace":
|
||||
# Create a listener on the foo event with bar namespace: .on 'foo.bar'
|
||||
# Trigger all events for the foo event (including namespaces): .trigger 'foo'
|
||||
# Remove all listeners for the foo event (including namespaces): .off 'foo'
|
||||
# Remove a listener for the foo event with the bar namespace: .off 'foo.bar'
|
||||
class EventEmitter
|
||||
on: (event, callback) ->
|
||||
@events ||= {}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import CodeMirror, { Doc } from 'codemirror'
|
||||
|
||||
export function init (rootEl) {
|
||||
CodeMirror.defineMIME('application/x-tex', 'latex')
|
||||
CodeMirror.defineMIME('application/x-latex', 'latex')
|
||||
|
||||
return CodeMirror(rootEl, {
|
||||
mode: 'latex'
|
||||
})
|
||||
}
|
||||
|
||||
export function openDoc (cm, content) {
|
||||
const newDoc = Doc(content, 'latex')
|
||||
cm.swapDoc(newDoc)
|
||||
return newDoc
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 114 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>favicon</title>
|
||||
<path d="M6.12,4.38A7.08,7.08,0,0,0,2.67,10.2,5.07,5.07,0,1,0,9.55,5.46a4.68,4.68,0,0,0-1.8-.34A7.48,7.48,0,0,0,5.2,8.07a3.33,3.33,0,1,1,2.54,5.47A3.33,3.33,0,0,1,5.2,12.37,4,4,0,0,1,4.24,9C4.89,5.11,9.55,2.87,13,2a35.85,35.85,0,0,0-4.6,2.65c4.17,1.61,4.84-1.9,6.79-3.47C13.25.42,6.13.14,6.12,4.38Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
@@ -84,8 +84,4 @@
|
||||
@import "../js/libs/pdfListView/TextLayer.css";
|
||||
@import "../js/libs/pdfListView/AnnotationsLayer.css";
|
||||
@import "../js/libs/pdfListView/HighlightsLayer.css";
|
||||
|
||||
// CodeMirror
|
||||
& when (@show-rich-text) {
|
||||
@import "vendor/codemirror.css";
|
||||
}
|
||||
@import "vendor/codemirror.css";
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
.system-message {
|
||||
padding: (@line-height-computed / 4) (@line-height-computed / 2);
|
||||
background-color: @state-warning-bg;
|
||||
color: #333;
|
||||
border-bottom: 1px solid @common-border-color;
|
||||
padding: (@line-height-computed / 4) (@line-height-computed / 2);
|
||||
background-color: @sys-msg-background;
|
||||
color: @sys-msg-color;
|
||||
border-bottom: @sys-msg-border;
|
||||
}
|
||||
|
||||
.system-message .close when (@is-overleaf = true) {
|
||||
color: #FFF;
|
||||
opacity: 1;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
@import "./editor/online-users.less";
|
||||
@import "./editor/hotkeys.less";
|
||||
@import "./editor/review-panel.less";
|
||||
@import "./editor/rich-text.less";
|
||||
@import "./editor/publish-modal.less";
|
||||
|
||||
@ui-layout-toggler-def-height: 50px;
|
||||
@ui-resizer-extra-hit-area: 8px;
|
||||
@ui-resizer-size: 7px;
|
||||
|
||||
@keyframes blink {
|
||||
0% {
|
||||
@@ -79,11 +81,13 @@
|
||||
.full-size;
|
||||
}
|
||||
|
||||
#editor when (@show-rich-text = true) {
|
||||
top: 40px; // TODO: replace with toolbar height var
|
||||
#editor-rich-text {
|
||||
top: @editor-toolbar-height;
|
||||
}
|
||||
#editor-rich-text when (@show-rich-text = true) {
|
||||
top: 40px; // TODO: replace with toolbar height var
|
||||
|
||||
.toolbar-editor {
|
||||
height: @editor-toolbar-height;
|
||||
background-color: @editor-toolbar-bg;
|
||||
}
|
||||
|
||||
.loading-screen {
|
||||
@@ -107,7 +111,7 @@
|
||||
height: 0;
|
||||
background: @editor-loading-logo-background-url no-repeat bottom / 100%;
|
||||
|
||||
&::after {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: inherit;
|
||||
@@ -116,7 +120,7 @@
|
||||
left: 0;
|
||||
background: @editor-loading-logo-foreground-url no-repeat bottom / 100%;
|
||||
transition: height .5s;
|
||||
}
|
||||
}
|
||||
}
|
||||
.loading-screen-label {
|
||||
margin: 0;
|
||||
@@ -302,40 +306,31 @@
|
||||
}
|
||||
|
||||
.ui-layout-resizer when (@is-overleaf = true) {
|
||||
margin-left: -(@ui-resizer-extra-hit-area) !important;
|
||||
margin-right: -(@ui-resizer-extra-hit-area - 1px) !important;
|
||||
padding-left: @ui-resizer-extra-hit-area !important;
|
||||
padding-right: @ui-resizer-extra-hit-area !important;
|
||||
z-index: 5 !important;
|
||||
box-sizing: content-box;
|
||||
background-image: linear-gradient(90deg,
|
||||
transparent,
|
||||
transparent (@ui-resizer-extra-hit-area - 1px),
|
||||
@editor-resizer-bg-color (@ui-resizer-extra-hit-area - 1px),
|
||||
@editor-resizer-bg-color (@ui-resizer-extra-hit-area + 1px),
|
||||
transparent (@ui-resizer-extra-hit-area + 1px),
|
||||
transparent);
|
||||
|
||||
.ui-layout-toggler {
|
||||
padding: 0 @ui-resizer-extra-hit-area !important;
|
||||
background-image: linear-gradient(90deg,
|
||||
transparent,
|
||||
transparent (@ui-resizer-extra-hit-area - 1px),
|
||||
@editor-toggler-bg-color (@ui-resizer-extra-hit-area - 1px),
|
||||
@editor-toggler-bg-color (@ui-resizer-extra-hit-area + 1px),
|
||||
transparent (@ui-resizer-extra-hit-area + 1px),
|
||||
transparent);
|
||||
|
||||
&:hover {
|
||||
background-image: linear-gradient(90deg,
|
||||
transparent,
|
||||
transparent (@ui-resizer-extra-hit-area - 2px),
|
||||
@editor-toggler-hover-bg-color (@ui-resizer-extra-hit-area - 2px),
|
||||
@editor-toggler-hover-bg-color (@ui-resizer-extra-hit-area + 2px),
|
||||
transparent (@ui-resizer-extra-hit-area + 2px),
|
||||
transparent);
|
||||
width: @ui-resizer-size !important;
|
||||
background-color: @editor-resizer-bg-color;
|
||||
&.ui-layout-resizer-closed {
|
||||
&::before,
|
||||
&::after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '\2847';
|
||||
display: block;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
left: -2px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
width: 100%;
|
||||
font-size: 24px;
|
||||
top: 25%;
|
||||
color: @ol-blue-gray-2;
|
||||
}
|
||||
&::after {
|
||||
top: 75%;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-layout-resizer-west.ui-layout-resizer-open, .ui-layout-resizer-east.ui-layout-resizer-closed {
|
||||
@@ -353,54 +348,66 @@
|
||||
}
|
||||
}
|
||||
|
||||
.ui-layout-toggler.ui-layout-toggler-closed when (@is-overleaf = true) {
|
||||
background-color: @editor-resizer-bg-color;
|
||||
background-image: none;
|
||||
line-height: @ui-layout-toggler-def-height;
|
||||
|
||||
&::before {
|
||||
content: "\22EE"; // Vertical ellipsis
|
||||
display: block;
|
||||
color: #FFF;
|
||||
font-weight: 700;
|
||||
font-size: @font-size-h2;
|
||||
width: @ui-resizer-extra-hit-area / 2;
|
||||
.ui-layout-toggler when (@is-overleaf = true) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.custom-toggler when (@is-overleaf = true) {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: @ui-resizer-size !important;;
|
||||
height: 50px;
|
||||
margin-top: -25px;
|
||||
top: 50%;
|
||||
z-index: 6;
|
||||
background-color: @editor-toggler-bg-color;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
// Increase hit area
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -3px;
|
||||
bottom: 0;
|
||||
left: -3px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
font-family: FontAwesome;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-size: 65%;
|
||||
font-weight: bold;
|
||||
color: #FFF;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @editor-toggler-hover-bg-color;
|
||||
background-image: none;
|
||||
}
|
||||
.ui-layout-resizer-west > & {
|
||||
border-radius: 0 @border-radius-base @border-radius-base 0;
|
||||
&::before {
|
||||
margin-left: -2px;
|
||||
}
|
||||
}
|
||||
.ui-layout-resizer-east > & {
|
||||
border-radius: @border-radius-base 0 0 @border-radius-base;
|
||||
&::before {
|
||||
margin-left: (-1 - @ui-resizer-extra-hit-area);
|
||||
}
|
||||
}
|
||||
}
|
||||
.custom-toggler-east::after {
|
||||
content: '\f105';
|
||||
}
|
||||
.custom-toggler-west::after {
|
||||
content: '\f104';
|
||||
}
|
||||
|
||||
.ui-layout-toggler-east when (@is-overleaf = true) {
|
||||
&.ui-layout-toggler-open {
|
||||
cursor: e-resize !important
|
||||
.custom-toggler-closed.custom-toggler-east::after {
|
||||
content: '\f104';
|
||||
}
|
||||
&.ui-layout-toggler-closed {
|
||||
cursor: w-resize !important
|
||||
.custom-toggler-closed.custom-toggler-west::after {
|
||||
content: '\f105';
|
||||
}
|
||||
}
|
||||
|
||||
.ui-layout-toggler-west when (@is-overleaf = true) {
|
||||
&.ui-layout-toggler-open {
|
||||
cursor: w-resize !important
|
||||
}
|
||||
&.ui-layout-toggler-closed {
|
||||
cursor: e-resize !important
|
||||
}
|
||||
}
|
||||
|
||||
.ui-layout-resizer-dragging {
|
||||
background-color: @editor-resizer-bg-color-dragging;
|
||||
@@ -624,4 +631,3 @@
|
||||
height: auto;
|
||||
border-bottom: 1px solid @modal-header-border-color;
|
||||
}
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@
|
||||
}
|
||||
|
||||
.synctex-controls when (@is-overleaf = true) {
|
||||
margin-right: -11px;
|
||||
margin-right: -8px;
|
||||
}
|
||||
.synctex-control {
|
||||
display: block;
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
.modal-body-publish {
|
||||
#search-input-container {
|
||||
overflow: hidden;
|
||||
margin: 5px 0 10px;
|
||||
}
|
||||
.table-content-name {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 300;
|
||||
}
|
||||
.table-content-category {
|
||||
font-weight: 300;
|
||||
text-align: right;
|
||||
font-style: italic;
|
||||
width: 30%;
|
||||
float: right;
|
||||
text-transform: capitalize
|
||||
}
|
||||
.table-content-category ~ .table-content-name {
|
||||
width: 70%;
|
||||
display: inline-block;
|
||||
}
|
||||
.wl-icon:before{
|
||||
font-size: 14px;
|
||||
}
|
||||
.button-as-link{
|
||||
color: green;
|
||||
text-transform: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
font-size: 14px;
|
||||
@extend a;
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: green;
|
||||
background: none;
|
||||
}
|
||||
text-align: left;
|
||||
}
|
||||
.affix-content-title {
|
||||
color: @gray-light;
|
||||
font-size: 1.2em;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.affix-subcontent {
|
||||
margin: 5px 0 50px;
|
||||
}
|
||||
.overbox {
|
||||
padding: @line-height-computed / 2;
|
||||
background-color: white;
|
||||
margin-top: @line-height-computed / 2;
|
||||
border: 1px solid @gray-lighter;
|
||||
}
|
||||
.content-as-table {
|
||||
.table-content,
|
||||
.table-content > * {
|
||||
display: table;
|
||||
}
|
||||
.table-content-icon {
|
||||
float: left;
|
||||
height: 80px;
|
||||
width: 106px;
|
||||
border: 1px solid @gray-lightest;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
* {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.table-content-text {
|
||||
float: right;
|
||||
width: calc(~'100% - 106px');
|
||||
vertical-align: top;
|
||||
padding-left: 15px;
|
||||
}
|
||||
.table-content-slogan {
|
||||
height: 80px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.table-content-link {
|
||||
padding-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
@rt-font-family: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif;
|
||||
// @rt-font-family-serif: 'Palatino Linotype', 'Book Antiqua', Palatino, serif;
|
||||
|
||||
.rich-text {
|
||||
font-family: @rt-font-family;
|
||||
font-size: 1.15em;
|
||||
|
||||
pre, .CodeMirror-linewidget {
|
||||
font-family: @rt-font-family;
|
||||
}
|
||||
|
||||
// TODO: Change prefix away from wl- ?
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
.preamble h1 {
|
||||
text-align: center;
|
||||
font-size: 2em;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.preamble ul.authors {
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.preamble ul.authors li {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
.wl-indent-0 {
|
||||
padding-left: 2.5em !important;
|
||||
}
|
||||
|
||||
.wl-indent-1 {
|
||||
padding-left: 3.5em !important;
|
||||
}
|
||||
|
||||
.wl-indent-2 {
|
||||
padding-left: 4.5em !important;
|
||||
}
|
||||
|
||||
.wl-indent-3 {
|
||||
padding-left: 5.5em !important;
|
||||
}
|
||||
|
||||
.wl-indent-4 {
|
||||
padding-left: 6.5em !important;
|
||||
}
|
||||
|
||||
.wl-indent-env-0 {
|
||||
padding-left: 4px !important;
|
||||
}
|
||||
|
||||
.wl-indent-env-1 {
|
||||
padding-left: 1.5em !important;
|
||||
}
|
||||
|
||||
.wl-indent-env-2 {
|
||||
padding-left: 2.5em !important;
|
||||
}
|
||||
|
||||
.wl-indent-env-3 {
|
||||
padding-left: 3.5em !important;
|
||||
}
|
||||
|
||||
.wl-indent-env-4 {
|
||||
padding-left: 4.5em !important;
|
||||
}
|
||||
|
||||
.wl-enumerate-item-open {
|
||||
text-align: right;
|
||||
width: 1.5em;
|
||||
display: inline-block;
|
||||
padding-right: 0.2em;
|
||||
}
|
||||
|
||||
.wl-item-open {
|
||||
text-align: right;
|
||||
width: 1.5em;
|
||||
display: inline-block;
|
||||
padding-right: 0.2em;
|
||||
}
|
||||
|
||||
.wl-input {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
.wl-abstract-open, .wl-abstract-close {
|
||||
border-top: 1px solid #999;
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wl-figure {
|
||||
max-height: 120px;
|
||||
width: auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.wl-figure-wrap {
|
||||
padding: 10px 0;
|
||||
background-color: #f5f5f5;
|
||||
box-shadow: 2px 2px 2px #DFDFDF;
|
||||
width: 96%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wl-figure-caption {
|
||||
padding: 3px 0 4px;
|
||||
font-size: small;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
.wl-chapter, .wl-chapter-open, .wl-chapter-close {
|
||||
font-size: 2.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.wl-chapter-open, .wl-chapter-close {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
.wl-section, .wl-section-open, .wl-section-close {
|
||||
font-size: 1.8em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.wl-section-open, .wl-section-close {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
.wl-subsection, .wl-subsection-open, .wl-subsection-close {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.wl-subsection-open, .wl-subsection-close {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
.wl-subsubsection, .wl-subsubsection-open, .wl-subsubsection-close {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.wl-subsubsection-open, .wl-subsubsection-close {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
.wl-textbf {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.wl-textbf-open {
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.wl-textbf-close {
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.wl-label-bracket {
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.wl-label-open,
|
||||
.wl-input-link {
|
||||
.wl-icon {
|
||||
padding-left: 5px;
|
||||
padding-right: 2px;
|
||||
vertical-align: middle;
|
||||
// @include wl-icon-size(inherit);
|
||||
}
|
||||
}
|
||||
|
||||
.wl-img-default {
|
||||
width: 0.9em ;
|
||||
padding: 0 1px 1px;
|
||||
}
|
||||
|
||||
.wl-label-close {
|
||||
background-color: #f7f7f9;
|
||||
border: 1px solid #e1e1e8;
|
||||
border-radius: 4px;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
.wl-textit {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.wl-textit-open {
|
||||
font-style: italic;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.wl-textit-close {
|
||||
font-style: italic;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
@@ -183,3 +183,61 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-wrapper {
|
||||
width: 200px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: @toggle-switch-bg;
|
||||
border-radius: @btn-border-radius-base;
|
||||
}
|
||||
|
||||
.toggle-switch-label {
|
||||
position: relative;
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
z-index: 2;
|
||||
float: left;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
margin-bottom: 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: color 0.12s ease-out;
|
||||
}
|
||||
|
||||
.toggle-switch-input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.toggle-switch-input:checked + .toggle-switch-label {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.toggle-switch-selection {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
right: 2px;
|
||||
width: calc(~"50% - 2px");
|
||||
height: calc(~"100% - 4px");
|
||||
background: @toggle-switch-highlight-color;
|
||||
border-radius: @btn-border-radius-base 0 0 @btn-border-radius-base;
|
||||
transition: transform 0.12s ease-out, border-radius 0.12s ease-out;
|
||||
}
|
||||
|
||||
.toggle-switch-input:checked:nth-child(4) ~ .toggle-switch-selection {
|
||||
transform: translate(100%);
|
||||
border-radius: 0 @btn-border-radius-base @btn-border-radius-base 0;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.error-container.full-height when (@is-overleaf = true) {
|
||||
margin-top: -(@header-height + @content-margin-vertical) / 2;
|
||||
}
|
||||
|
||||
.error-figure {
|
||||
display: none;
|
||||
flex: 0 0 50%;
|
||||
@@ -15,6 +20,11 @@
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.error-figure when (@is-overleaf = true) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.error-figure-500 {
|
||||
&::before {
|
||||
content: '';
|
||||
@@ -56,6 +66,11 @@
|
||||
flex: 0 1 50%;
|
||||
padding: @line-height-computed * 2;
|
||||
}
|
||||
|
||||
.error-details when (@is-overleaf = true) {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.error-status {
|
||||
font-family: @font-family-serif;
|
||||
margin-bottom: (@line-height-computed / 4);
|
||||
@@ -68,7 +83,7 @@
|
||||
color: @gray;
|
||||
margin-bottom: @line-height-computed * 2;
|
||||
}
|
||||
.error-btn {
|
||||
.error-btn when (@is-overleaf = false) {
|
||||
color: @navbar-default-link-color;
|
||||
border: 2px solid @navbar-default-link-color;
|
||||
border-radius: @border-radius-base;
|
||||
@@ -83,4 +98,13 @@
|
||||
background-color: @navbar-default-link-hover-bg;
|
||||
border: 2px solid @navbar-default-link-hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
.error-btn when (@is-overleaf = true) {
|
||||
.btn;
|
||||
.btn-primary;
|
||||
display: block;
|
||||
@media (min-width: @screen-sm-min) {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
.v1-import-title {
|
||||
text-align: center;
|
||||
margin-top: @line-height-computed / 2;
|
||||
}
|
||||
|
||||
.v1-import-row {
|
||||
@@ -8,9 +9,6 @@
|
||||
}
|
||||
|
||||
.v1-import-col {
|
||||
flex-basis: 50%;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
@@ -33,9 +31,8 @@
|
||||
}
|
||||
|
||||
.v1-import-warning {
|
||||
text-align: center;
|
||||
color: #fdce02;
|
||||
font-size: 14em;
|
||||
color: #4B7FD1;
|
||||
font-size: 10em;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
.recurly-hosted-field {
|
||||
&:extend(.form-control);
|
||||
}
|
||||
|
||||
|
||||
.recurly {
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
color: white;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
&:hover, &:active {
|
||||
&:hover, &:active, &:focus {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,4 +71,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.capitalised {
|
||||
text-transform:capitalize;
|
||||
}
|
||||
@@ -1,14 +1,23 @@
|
||||
|
||||
.translations-message {
|
||||
padding: (@line-height-computed / 4) (@line-height-computed / 2);
|
||||
background-color: @state-warning-bg;
|
||||
color: #333;
|
||||
border-bottom: 1px solid @common-border-color;
|
||||
.system-message;
|
||||
text-align:center;
|
||||
|
||||
img {
|
||||
vertical-align: text-bottom;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.translations-message when (@is-overleaf = true) {
|
||||
.close {
|
||||
color: #FFF;
|
||||
opacity: 1;
|
||||
text-shadow: none;
|
||||
}
|
||||
a {
|
||||
color: #FFF;
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,8 +309,9 @@ input[type="checkbox"],
|
||||
.has-warning {
|
||||
.form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);
|
||||
}
|
||||
.has-error {
|
||||
.has-external-error {
|
||||
.form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);
|
||||
color:@red;
|
||||
}
|
||||
|
||||
.form-control.ng-dirty.ng-invalid:not(:focus) {
|
||||
|
||||
@@ -576,10 +576,3 @@
|
||||
padding: @navbar-default-padding-v 0;
|
||||
}
|
||||
}
|
||||
|
||||
.system-messages {
|
||||
padding-top: @header-height;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@@ -932,6 +932,14 @@
|
||||
@synctex-controls-z-index : 3;
|
||||
@synctex-controls-padding : 0 2px;
|
||||
|
||||
// Editor toolbar
|
||||
@editor-toolbar-height : 32px;
|
||||
@editor-toolbar-bg : #fff;
|
||||
|
||||
// Toggle switch
|
||||
@toggle-switch-bg : @gray-lightest;
|
||||
@toggle-switch-highlight-color : @brand-primary;
|
||||
|
||||
// Chat
|
||||
@chat-bg : transparent;
|
||||
@chat-message-color : @text-color;
|
||||
@@ -959,4 +967,9 @@
|
||||
@tag-max-width : 150px;
|
||||
@tag-bg-hover-color : darken(@label-default-bg, 10%);
|
||||
@tag-top-adjustment : -2px;
|
||||
@labels-font-size : 75%;
|
||||
@labels-font-size : 75%;
|
||||
|
||||
// System messages
|
||||
@sys-msg-background : @state-warning-bg;
|
||||
@sys-msg-color : #333;
|
||||
@sys-msg-border : 1px solid @common-border-color;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user