mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 06:03:45 +01:00
Merge branch 'master' into pr-email-tokens
Conflicts: app/coffee/Features/Notifications/NotificationsBuilder.coffee public/coffee/ide/share/controllers/ShareController.coffee
This commit is contained in:
@@ -386,57 +386,3 @@ module.exports = (grunt) ->
|
||||
|
||||
grunt.registerTask 'version', "Write the version number into sentry.jade", ['git-rev-parse', 'sed']
|
||||
|
||||
|
||||
grunt.registerTask 'create-admin-user', "Create a user with the given email address and make them an admin. Update in place if the user already exists", () ->
|
||||
done = @async()
|
||||
email = grunt.option("email")
|
||||
if !email?
|
||||
console.error "Usage: grunt create-admin-user --email joe@example.com"
|
||||
process.exit(1)
|
||||
|
||||
settings = require "settings-sharelatex"
|
||||
UserRegistrationHandler = require "./app/js/Features/User/UserRegistrationHandler"
|
||||
OneTimeTokenHandler = require "./app/js/Features/Security/OneTimeTokenHandler"
|
||||
UserRegistrationHandler.registerNewUser {
|
||||
email: email
|
||||
password: require("crypto").randomBytes(32).toString("hex")
|
||||
}, (error, user) ->
|
||||
if error? and error?.message != "EmailAlreadyRegistered"
|
||||
throw error
|
||||
user.isAdmin = true
|
||||
user.save (error) ->
|
||||
throw error if error?
|
||||
ONE_WEEK = 7 * 24 * 60 * 60 # seconds
|
||||
OneTimeTokenHandler.getNewToken user._id, { expiresIn: ONE_WEEK }, (err, token)->
|
||||
return next(err) if err?
|
||||
|
||||
console.log ""
|
||||
console.log """
|
||||
Successfully created #{email} as an admin user.
|
||||
|
||||
Please visit the following URL to set a password for #{email} and log in:
|
||||
|
||||
#{settings.siteUrl}/user/password/set?passwordResetToken=#{token}
|
||||
|
||||
"""
|
||||
done()
|
||||
|
||||
grunt.registerTask 'delete-user', "deletes a user and all their data", () ->
|
||||
done = @async()
|
||||
email = grunt.option("email")
|
||||
if !email?
|
||||
console.error "Usage: grunt delete-user --email joe@example.com"
|
||||
process.exit(1)
|
||||
settings = require "settings-sharelatex"
|
||||
UserGetter = require "./app/js/Features/User/UserGetter"
|
||||
UserDeleter = require "./app/js/Features/User/UserDeleter"
|
||||
UserGetter.getUser email:email, (error, user) ->
|
||||
if error?
|
||||
throw error
|
||||
if !user?
|
||||
console.log("user #{email} not in database, potentially already deleted")
|
||||
return done()
|
||||
UserDeleter.deleteUser user._id, (err)->
|
||||
if err?
|
||||
throw err
|
||||
done()
|
||||
@@ -0,0 +1,7 @@
|
||||
AnalyticsManager = require "./AnalyticsManager"
|
||||
|
||||
module.exports = AnalyticsController =
|
||||
recordEvent: (req, res, next) ->
|
||||
AnalyticsManager.recordEvent req.session?.user?._id, req.params.event, req.body, (error) ->
|
||||
return next(error) if error?
|
||||
res.send 204
|
||||
@@ -0,0 +1,43 @@
|
||||
Settings = require "settings-sharelatex"
|
||||
logger = require "logger-sharelatex"
|
||||
_ = require "underscore"
|
||||
|
||||
if !Settings.analytics?.postgres?
|
||||
module.exports =
|
||||
recordEvent: (user_id, event, segmentation, callback = () ->) ->
|
||||
logger.log {user_id, event, segmentation}, "no event tracking configured, logging event"
|
||||
callback()
|
||||
else
|
||||
Sequelize = require "sequelize"
|
||||
options = _.extend {logging:false}, Settings.analytics.postgres
|
||||
|
||||
sequelize = new Sequelize(
|
||||
Settings.analytics.postgres.database,
|
||||
Settings.analytics.postgres.username,
|
||||
Settings.analytics.postgres.password,
|
||||
options
|
||||
)
|
||||
|
||||
Event = sequelize.define("Event", {
|
||||
user_id: Sequelize.STRING,
|
||||
event: Sequelize.STRING,
|
||||
segmentation: Sequelize.JSONB
|
||||
})
|
||||
|
||||
module.exports =
|
||||
recordEvent: (user_id, event, segmentation = {}, callback = (error) ->) ->
|
||||
if user_id? and typeof(user_id) != "string"
|
||||
user_id = user_id.toString()
|
||||
if user_id == Settings.smokeTest?.userId
|
||||
# Don't record smoke tests analytics
|
||||
return callback()
|
||||
Event
|
||||
.create({ user_id, event, segmentation })
|
||||
.then(
|
||||
(result) -> callback(),
|
||||
(error) ->
|
||||
logger.err {err: error, user_id, event, segmentation}, "error recording analytics event"
|
||||
callback(error)
|
||||
)
|
||||
|
||||
sync: () -> sequelize.sync()
|
||||
@@ -10,6 +10,7 @@ Settings = require "settings-sharelatex"
|
||||
basicAuth = require('basic-auth-connect')
|
||||
UserHandler = require("../User/UserHandler")
|
||||
UserSessionsManager = require("../User/UserSessionsManager")
|
||||
Analytics = require "../Analytics/AnalyticsManager"
|
||||
|
||||
module.exports = AuthenticationController =
|
||||
login: (req, res, next = (error) ->) ->
|
||||
@@ -37,6 +38,7 @@ module.exports = AuthenticationController =
|
||||
return next(error) if error?
|
||||
req.session.justLoggedIn = true
|
||||
logger.log email: email, user_id: user._id.toString(), "successful log in"
|
||||
Analytics.recordEvent user._id, "user-logged-in"
|
||||
res.json redir: redir
|
||||
else
|
||||
AuthenticationController._recordFailedLogin()
|
||||
|
||||
@@ -147,6 +147,7 @@ module.exports = ClsiManager =
|
||||
timeout: options.timeout
|
||||
imageName: project.imageName
|
||||
draft: !!options.draft
|
||||
check: options.check
|
||||
rootResourcePath: rootResourcePath
|
||||
resources: resources
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ module.exports = CompileController =
|
||||
options.compiler = req.body.compiler
|
||||
if req.body?.draft
|
||||
options.draft = req.body.draft
|
||||
if req.body?.check
|
||||
options.check = if req.body.check is "validate" then "validate" else undefined
|
||||
logger.log {options:options, project_id:project_id, user_id:user_id}, "got compile request"
|
||||
CompileManager.compile project_id, user_id, options, (error, status, outputFiles, clsiServerId, limits, validationProblems) ->
|
||||
return next(error) if error?
|
||||
|
||||
@@ -4,7 +4,7 @@ settings = require("settings-sharelatex")
|
||||
ProjectGetter = require "../Project/ProjectGetter"
|
||||
UserGetter = require "../User/UserGetter"
|
||||
DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
|
||||
U = require('underscore')
|
||||
_ = require('underscore')
|
||||
Async = require('async')
|
||||
|
||||
oneMinInMs = 60 * 1000
|
||||
@@ -22,24 +22,24 @@ module.exports = ReferencesHandler =
|
||||
_findBibFileIds: (project) ->
|
||||
ids = []
|
||||
_process = (folder) ->
|
||||
(folder.fileRefs or []).forEach (file) ->
|
||||
_.each (folder.fileRefs or []), (file) ->
|
||||
if file?.name?.match(/^.*\.bib$/)
|
||||
ids.push(file._id)
|
||||
(folder.folders or []).forEach (folder) ->
|
||||
_.each (folder.folders or []), (folder) ->
|
||||
_process(folder)
|
||||
(project.rootFolder or []).forEach (rootFolder) ->
|
||||
_.each (project.rootFolder or []), (rootFolder) ->
|
||||
_process(rootFolder)
|
||||
return ids
|
||||
|
||||
_findBibDocIds: (project) ->
|
||||
ids = []
|
||||
_process = (folder) ->
|
||||
(folder.docs or []).forEach (doc) ->
|
||||
_.each (folder.docs or []), (doc) ->
|
||||
if doc?.name?.match(/^.*\.bib$/)
|
||||
ids.push(doc._id)
|
||||
(folder.folders or []).forEach (folder) ->
|
||||
_.each (folder.folders or []), (folder) ->
|
||||
_process(folder)
|
||||
(project.rootFolder or []).forEach (rootFolder) ->
|
||||
_.each (project.rootFolder or []), (rootFolder) ->
|
||||
_process(rootFolder)
|
||||
return ids
|
||||
|
||||
|
||||
@@ -30,5 +30,5 @@ module.exports =
|
||||
multi.get buildKey(token)
|
||||
multi.del buildKey(token)
|
||||
multi.exec (err, results)->
|
||||
callback err, results[0]
|
||||
callback err, results?[0]
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ module.exports = ProjectUploadController =
|
||||
|
||||
uploadFile: (req, res, next) ->
|
||||
timer = new metrics.Timer("file-upload")
|
||||
name = req.files.qqfile.originalname
|
||||
path = req.files.qqfile.path
|
||||
name = req.files.qqfile?.originalname
|
||||
path = req.files.qqfile?.path
|
||||
project_id = req.params.Project_id
|
||||
folder_id = req.query.folder_id
|
||||
if !name? or name.length == 0 or name.length > 150
|
||||
|
||||
@@ -8,6 +8,7 @@ logger = require("logger-sharelatex")
|
||||
crypto = require("crypto")
|
||||
EmailHandler = require("../Email/EmailHandler")
|
||||
OneTimeTokenHandler = require "../Security/OneTimeTokenHandler"
|
||||
Analytics = require "../Analytics/AnalyticsManager"
|
||||
settings = require "settings-sharelatex"
|
||||
|
||||
module.exports = UserRegistrationHandler =
|
||||
@@ -62,6 +63,7 @@ module.exports = UserRegistrationHandler =
|
||||
cb() #this can be slow, just fire it off
|
||||
], (err)->
|
||||
logger.log user: user, "registered"
|
||||
Analytics.recordEvent user._id, "user-registered"
|
||||
callback(err, user)
|
||||
|
||||
registerNewUserAndSendActivationEmail: (email, callback = (error, user, setNewPasswordUrl) ->) ->
|
||||
|
||||
@@ -38,6 +38,7 @@ ContactRouter = require("./Features/Contacts/ContactRouter")
|
||||
ReferencesController = require('./Features/References/ReferencesController')
|
||||
AuthorizationMiddlewear = require('./Features/Authorization/AuthorizationMiddlewear')
|
||||
BetaProgramController = require('./Features/BetaProgram/BetaProgramController')
|
||||
AnalyticsRouter = require('./Features/Analytics/AnalyticsRouter')
|
||||
|
||||
logger = require("logger-sharelatex")
|
||||
_ = require("underscore")
|
||||
@@ -68,7 +69,8 @@ module.exports = class Router
|
||||
StaticPagesRouter.apply(webRouter, apiRouter)
|
||||
RealTimeProxyRouter.apply(webRouter, apiRouter)
|
||||
ContactRouter.apply(webRouter, apiRouter)
|
||||
|
||||
AnalyticsRouter.apply(webRouter, apiRouter)
|
||||
|
||||
Modules.applyRouter(webRouter, apiRouter)
|
||||
|
||||
|
||||
@@ -280,4 +282,4 @@ module.exports = class Router
|
||||
metrics.inc("client-side-error")
|
||||
res.sendStatus(204)
|
||||
|
||||
webRouter.get '*', ErrorController.notFound
|
||||
webRouter.get '*', ErrorController.notFound
|
||||
|
||||
@@ -46,76 +46,6 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||
- else
|
||||
script(type='text/javascript').
|
||||
window.ga = function() { console.log("would send to GA", arguments) };
|
||||
|
||||
// Countly Analytics
|
||||
if (settings.analytics && settings.analytics.countly && settings.analytics.countly.token)
|
||||
script(type="text/javascript").
|
||||
var Countly = Countly || {};
|
||||
Countly.q = Countly.q || [];
|
||||
Countly.app_key = '#{settings.analytics.countly.token}';
|
||||
Countly.url = '#{settings.analytics.countly.server}';
|
||||
!{ session.user ? 'Countly.device_id = "' + session.user._id + '";' : '' }
|
||||
|
||||
(function() {
|
||||
var cly = document.createElement('script'); cly.type = 'text/javascript';
|
||||
cly.async = true;
|
||||
//enter url of script here
|
||||
cly.src = 'https://cdnjs.cloudflare.com/ajax/libs/countly-sdk-web/16.6.0/countly.min.js';
|
||||
cly.onload = function(){Countly.init()};
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(cly, s);
|
||||
})();
|
||||
|
||||
script(type="text/javascript")
|
||||
if (session && session.user)
|
||||
- var name = session.user.first_name + (session.user.last_name ? ' ' + session.user.last_name : '');
|
||||
| Countly.q.push(['user_details', { email: '#{session.user.email}', name: '#{name}' }]);
|
||||
|
||||
if (justRegistered)
|
||||
| Countly.q.push(['add_event',{ key: 'user-registered' }]);
|
||||
|
||||
if (justLoggedIn)
|
||||
| Countly.q.push(['add_event',{ key: 'user-logged-in' }]);
|
||||
|
||||
if (user && user.features)
|
||||
- featureFlagSet = false;
|
||||
|
||||
if user.features.hasOwnProperty('collaborators')
|
||||
| Countly.q.push([ 'userData.set', 'features-collaborators', #{ user.features.collaborators } ]);
|
||||
- featureFlagSet = true;
|
||||
|
||||
if user.features.hasOwnProperty('compileGroup')
|
||||
| Countly.q.push([ 'userData.set', 'features-compileGroup', '#{ user.features.compileGroup }' ]);
|
||||
- featureFlagSet = true;
|
||||
|
||||
if user.features.hasOwnProperty('compileTimeout')
|
||||
| Countly.q.push([ 'userData.set', 'features-compileTimeout', #{ user.features.compileTimeout } ]);
|
||||
- featureFlagSet = true;
|
||||
|
||||
if user.features.hasOwnProperty('dropbox')
|
||||
| Countly.q.push([ 'userData.set', 'features-dropbox', #{ user.features.dropbox } ]);
|
||||
- featureFlagSet = true;
|
||||
|
||||
if user.features.hasOwnProperty('github')
|
||||
| Countly.q.push([ 'userData.set', 'features-github', #{ user.features.github } ]);
|
||||
- featureFlagSet = true;
|
||||
|
||||
if user.features.hasOwnProperty('references')
|
||||
| Countly.q.push([ 'userData.set', 'features-references', #{ user.features.references } ]);
|
||||
- featureFlagSet = true;
|
||||
|
||||
if user.features.hasOwnProperty('templates')
|
||||
| Countly.q.push([ 'userData.set', 'features-templates', #{ user.features.templates } ]);
|
||||
- featureFlagSet = true;
|
||||
|
||||
if user.features.hasOwnProperty('versioning')
|
||||
| Countly.q.push([ 'userData.set', 'features-versioning', #{ user.features.versioning } ]);
|
||||
- featureFlagSet = true;
|
||||
|
||||
|
||||
if featureFlagSet
|
||||
| Countly.q.push(['userData.save'])
|
||||
|
||||
// End countly Analytics
|
||||
|
||||
script(type="text/javascript").
|
||||
window.csrfToken = "#{csrfToken}";
|
||||
|
||||
@@ -45,7 +45,7 @@ aside.chat(
|
||||
mathjax,
|
||||
ng-repeat="content in message.contents track by $index"
|
||||
)
|
||||
span(ng-bind-html="content | linky:'_blank'")
|
||||
span(ng-bind-html="content | linky:'_blank' | wrapLongWords")
|
||||
|
||||
.new-message
|
||||
textarea(
|
||||
|
||||
@@ -91,12 +91,14 @@ header.toolbar.toolbar-header(ng-cloak, ng-hide="state.loading")
|
||||
ng-if="permissions.admin",
|
||||
tooltip="#{translate('share')}",
|
||||
tooltip-placement="bottom",
|
||||
ng-mouseenter="trackHover('share')"
|
||||
ng-click="openShareProjectModal()",
|
||||
ng-controller="ShareController"
|
||||
)
|
||||
i.fa.fa-fw.fa-group
|
||||
a.btn.btn-full-height(
|
||||
href,
|
||||
ng-mouseenter="trackHover('track-changes')"
|
||||
ng-click="toggleTrackChanges()",
|
||||
ng-class="{ active: (ui.view == 'track-changes') }"
|
||||
tooltip="#{translate('recent_changes')}",
|
||||
@@ -108,6 +110,7 @@ header.toolbar.toolbar-header(ng-cloak, ng-hide="state.loading")
|
||||
tooltip="#{translate('chat')}",
|
||||
tooltip-placement="bottom",
|
||||
ng-class="{ active: ui.chatOpen }",
|
||||
ng-mouseenter="trackHover('chat')"
|
||||
ng-click="toggleChat()",
|
||||
ng-controller="ChatButtonController",
|
||||
ng-show="!anonymous"
|
||||
|
||||
@@ -25,7 +25,7 @@ div.full-size.pdf(ng-controller="PdfController")
|
||||
dropdown-toggle
|
||||
)
|
||||
span.caret
|
||||
ul.dropdown-menu.dropdown-menu-right
|
||||
ul.dropdown-menu.dropdown-menu-left
|
||||
li.dropdown-header #{translate("compile_mode")}
|
||||
li
|
||||
a(href, ng-click="draft = false")
|
||||
@@ -36,6 +36,13 @@ div.full-size.pdf(ng-controller="PdfController")
|
||||
i.fa.fa-fw(ng-class="{'fa-check': draft}")
|
||||
| #{translate("fast")}
|
||||
span.subdued [draft]
|
||||
if user.betaProgram
|
||||
li.dropdown-header #{translate("file_checks")}
|
||||
li
|
||||
a(href, ng-click="recompile({check:true})")
|
||||
i.fa.fa-fw()
|
||||
| #{translate("run_syntax_check")}
|
||||
span.beta-feature-badge
|
||||
a(
|
||||
href
|
||||
ng-click="stop()"
|
||||
@@ -115,7 +122,8 @@ div.full-size.pdf(ng-controller="PdfController")
|
||||
|
|
||||
span(ng-show="entry.file") {{ entry.file }}
|
||||
span(ng-show="entry.line") , line {{ entry.line }}
|
||||
p.entry-message(ng-show="entry.message") {{ entry.message }}
|
||||
p.entry-message(ng-show="entry.message")
|
||||
| {{ entry.type }} {{ entry.message }}
|
||||
.card.card-hint(
|
||||
ng-if="entry.humanReadableHint"
|
||||
stop-propagation="click"
|
||||
@@ -126,7 +134,7 @@ div.full-size.pdf(ng-controller="PdfController")
|
||||
ng-show="entry.humanReadableHint",
|
||||
ng-bind-html="wikiEnabled ? entry.humanReadableHint : stripHTMLFromString(entry.humanReadableHint)")
|
||||
.card-hint-actions.clearfix
|
||||
.card-hint-ext-link(ng-if="wikiEnabled")
|
||||
.card-hint-ext-link(ng-if="wikiEnabled && entry.extraInfoURL")
|
||||
a(
|
||||
ng-href="{{ entry.extraInfoURL }}",
|
||||
ng-click="trackLogHintsLearnMore()"
|
||||
|
||||
@@ -143,15 +143,27 @@ div#trackChanges(ng-show="ui.view == 'track-changes'")
|
||||
navigate-highlights="true"
|
||||
)
|
||||
.diff-deleted.text-centered(
|
||||
ng-show="trackChanges.diff.deleted"
|
||||
ng-show="trackChanges.diff.deleted && !trackChanges.diff.restoreDeletedSuccess"
|
||||
)
|
||||
p.text-serif #{translate("file_has_been_deleted", {filename:"{{ trackChanges.diff.doc.name }} "})}
|
||||
|
||||
p
|
||||
a.btn.btn-primary.btn-lg(
|
||||
href,
|
||||
ng-click="restoreDeletedDoc()"
|
||||
ng-click="restoreDeletedDoc()",
|
||||
ng-disabled="trackChanges.diff.restoreInProgress"
|
||||
) #{translate("restore")}
|
||||
|
||||
.diff-deleted.text-centered(
|
||||
ng-show="trackChanges.diff.deleted && trackChanges.diff.restoreDeletedSuccess"
|
||||
)
|
||||
p.text-serif #{translate("file_restored", {filename:"{{ trackChanges.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="trackChanges.diff.loading")
|
||||
i.fa.fa-spin.fa-refresh
|
||||
| #{translate("loading")}...
|
||||
|
||||
@@ -106,8 +106,8 @@ module.exports = settings =
|
||||
url: ""
|
||||
# references:
|
||||
# url: "http://localhost:3040"
|
||||
# notifications:
|
||||
# url: "http://localhost:3042"
|
||||
notifications:
|
||||
url: "http://localhost:3042"
|
||||
|
||||
templates:
|
||||
user_id: process.env.TEMPLATES_USER_ID or "5395eb7aad1f29a88756c7f2"
|
||||
|
||||
Generated
+876
-627
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,8 @@
|
||||
"nodemailer-sendgrid-transport": "^0.2.0",
|
||||
"nodemailer-ses-transport": "^1.3.0",
|
||||
"optimist": "0.6.1",
|
||||
"pg": "^6.0.3",
|
||||
"pg-hstore": "^2.3.2",
|
||||
"redback": "0.4.0",
|
||||
"redis": "0.10.1",
|
||||
"redis-sharelatex": "0.0.9",
|
||||
@@ -53,6 +55,7 @@
|
||||
"requests": "^0.1.7",
|
||||
"rimraf": "2.2.6",
|
||||
"sanitizer": "0.1.1",
|
||||
"sequelize": "^3.2.0",
|
||||
"settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0",
|
||||
"sixpack-client": "^1.0.0",
|
||||
"temp": "^0.8.3",
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
DEF_MIN_LENGTH = 20
|
||||
|
||||
_decodeHTMLEntities = (str) ->
|
||||
str.replace /&#(\d+);/g, (match, dec) ->
|
||||
String.fromCharCode dec;
|
||||
|
||||
_getWrappedWordsString = (baseStr, wrapperElName, minLength) ->
|
||||
minLength = minLength || DEF_MIN_LENGTH
|
||||
words = baseStr.split ' '
|
||||
|
||||
wordsWrapped = for word in words
|
||||
if _decodeHTMLEntities(word).length >= minLength
|
||||
"<#{wrapperElName} class=\"break-word\">#{word}</#{wrapperElName}>"
|
||||
else
|
||||
word
|
||||
|
||||
outputStr = wordsWrapped.join ' '
|
||||
|
||||
|
||||
App.filter "wrapLongWords", () ->
|
||||
(input, minLength) ->
|
||||
_getWrappedWordsString input, "span", minLength
|
||||
@@ -9,6 +9,7 @@ define [
|
||||
"ide/pdf/PdfManager"
|
||||
"ide/binary-files/BinaryFilesManager"
|
||||
"ide/references/ReferencesManager"
|
||||
"ide/SafariScrollPatcher"
|
||||
"ide/settings/index"
|
||||
"ide/share/index"
|
||||
"ide/chat/index"
|
||||
@@ -40,6 +41,7 @@ define [
|
||||
PdfManager
|
||||
BinaryFilesManager
|
||||
ReferencesManager
|
||||
SafariScrollPatcher
|
||||
) ->
|
||||
|
||||
App.controller "IdeController", ($scope, $timeout, ide, localStorage, event_tracking) ->
|
||||
@@ -72,13 +74,16 @@ define [
|
||||
# Tracking code.
|
||||
$scope.$watch "ui.view", (newView, oldView) ->
|
||||
if newView? and newView != "editor" and newView != "pdf"
|
||||
event_tracking.sendCountlyOnce "ide-open-view-#{ newView }-once"
|
||||
event_tracking.sendMBOnce "ide-open-view-#{ newView }-once"
|
||||
|
||||
$scope.$watch "ui.chatOpen", (isOpen) ->
|
||||
event_tracking.sendCountlyOnce "ide-open-chat-once" if isOpen
|
||||
event_tracking.sendMBOnce "ide-open-chat-once" if isOpen
|
||||
|
||||
$scope.$watch "ui.leftMenuShown", (isOpen) ->
|
||||
event_tracking.sendCountlyOnce "ide-open-left-menu-once" if isOpen
|
||||
event_tracking.sendMBOnce "ide-open-left-menu-once" if isOpen
|
||||
|
||||
$scope.trackHover = (feature) ->
|
||||
event_tracking.sendMBOnce "ide-hover-#{feature}-once"
|
||||
# End of tracking code.
|
||||
|
||||
window._ide = ide
|
||||
@@ -135,6 +140,9 @@ define [
|
||||
catch err
|
||||
console.error err
|
||||
|
||||
if ide.browserIsSafari
|
||||
ide.safariScrollPatcher = new SafariScrollPatcher($scope)
|
||||
|
||||
# User can append ?ft=somefeature to url to activate a feature toggle
|
||||
ide.featureToggle = location?.search?.match(/^\?ft=(\w+)$/)?[1]
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
define [
|
||||
], () ->
|
||||
class SafariScrollPatcher
|
||||
constructor: ($scope) ->
|
||||
@isOverAce = false # Flag to control if the pointer is over Ace.
|
||||
@pdfDiv = null
|
||||
@aceDiv = null
|
||||
|
||||
# Start listening to PDF wheel events when the pointer leaves the PDF region.
|
||||
# P.S. This is the problem in a nutshell: although the pointer is elsewhere,
|
||||
# wheel events keep being dispatched to the PDF.
|
||||
@handlePdfDivMouseLeave = () =>
|
||||
@pdfDiv.addEventListener "wheel", @dispatchToAce
|
||||
|
||||
# Stop listening to wheel events when the pointer enters the PDF region. If
|
||||
# the pointer is over the PDF, native behaviour is adequate.
|
||||
@handlePdfDivMouseEnter = () =>
|
||||
@pdfDiv.removeEventListener "wheel", @dispatchToAce
|
||||
|
||||
# Set the "pointer over Ace" flag as false, when the mouse leaves its area.
|
||||
@handleAceDivMouseLeave = () =>
|
||||
@isOverAce = false
|
||||
|
||||
# Set the "pointer over Ace" flag as true, when the mouse enters its area.
|
||||
@handleAceDivMouseEnter = () =>
|
||||
@isOverAce = true
|
||||
|
||||
# Grab the elements (pdfDiv, aceDiv) and set the "hover" event listeners.
|
||||
# If elements are already defined, clear existing event listeners and do
|
||||
# the process again (grab elements, set listeners).
|
||||
@setListeners = () =>
|
||||
@isOverAce = false
|
||||
|
||||
# If elements aren't null, remove existing listeners.
|
||||
if @pdfDiv?
|
||||
@pdfDiv.removeEventListener @handlePdfDivMouseLeave
|
||||
@pdfDiv.removeEventListener @handlePdfDivMouseEnter
|
||||
|
||||
if @aceDiv?
|
||||
@aceDiv.removeEventListener @handleAceDivMouseLeave
|
||||
@aceDiv.removeEventListener @handleAceDivMouseEnter
|
||||
|
||||
# Grab elements.
|
||||
@pdfDiv = document.querySelector ".pdfjs-viewer" # Grab the PDF div.
|
||||
@aceDiv = document.querySelector ".ace_content" # Also the editor.
|
||||
|
||||
# Set hover-related listeners.
|
||||
@pdfDiv.addEventListener "mouseleave", @handlePdfDivMouseLeave
|
||||
@pdfDiv.addEventListener "mouseenter", @handlePdfDivMouseEnter
|
||||
@aceDiv.addEventListener "mouseleave", @handleAceDivMouseLeave
|
||||
@aceDiv.addEventListener "mouseenter", @handleAceDivMouseEnter
|
||||
|
||||
# Handler for wheel events on the PDF.
|
||||
# If the pointer is over Ace, grab the event, prevent default behaviour
|
||||
# and dispatch it to Ace.
|
||||
@dispatchToAce = (e) =>
|
||||
if @isOverAce
|
||||
# If this is logged, the problem just happened: the event arrived
|
||||
# here (the PDF wheel handler), but it should've gone to Ace.
|
||||
|
||||
# Small timeout - if we dispatch immediately, an exception is thrown.
|
||||
window.setTimeout(() =>
|
||||
# Dispatch the exact same event to Ace (this will keep values
|
||||
# values e.g. `wheelDelta` consistent with user interaction).
|
||||
@aceDiv.dispatchEvent e
|
||||
, 1)
|
||||
|
||||
# Avoid scrolling the PDF, as we assume this was intended to the
|
||||
# editor.
|
||||
e.preventDefault()
|
||||
|
||||
# "loaded" event is emitted from the pdfViewer controller $scope. This means
|
||||
# that the previous PDF DOM element was destroyed and a new one is available,
|
||||
# so we need to grab the elements and set the listeners again.
|
||||
$scope.$on "loaded", () =>
|
||||
@setListeners()
|
||||
|
||||
|
||||
@@ -3,4 +3,5 @@ define [
|
||||
"ide/chat/controllers/ChatController"
|
||||
"ide/chat/controllers/ChatMessageController"
|
||||
"ide/chat/directives/mathjax"
|
||||
"filters/wrapLongWords"
|
||||
], () ->
|
||||
@@ -46,7 +46,7 @@ define [
|
||||
|
||||
done = () =>
|
||||
if options.gotoLine?
|
||||
@$scope.$broadcast "editor:gotoLine", options.gotoLine
|
||||
@$scope.$broadcast "editor:gotoLine", options.gotoLine, options.gotoColumn
|
||||
|
||||
if doc.id == @$scope.editor.open_doc_id and !options.forceReopen
|
||||
@$scope.$apply () =>
|
||||
|
||||
+6
-6
@@ -23,10 +23,10 @@ define [], () ->
|
||||
@storeCursorPosition(@editor.getSession())
|
||||
@storeScrollTopPosition(@editor.getSession())
|
||||
|
||||
@$scope.$on "#{@$scope.name}:gotoLine", (editor, value) =>
|
||||
if value?
|
||||
@$scope.$on "#{@$scope.name}:gotoLine", (editor, line, column) =>
|
||||
if line?
|
||||
setTimeout () =>
|
||||
@gotoLine(value)
|
||||
@gotoLine(line, column)
|
||||
, 10 # Hack: Must happen after @gotoStoredPosition
|
||||
|
||||
storeScrollTopPosition: (session) ->
|
||||
@@ -53,6 +53,6 @@ define [], () ->
|
||||
@editor.getSession().setScrollTop(pos.scrollTop or 0)
|
||||
delete @ignoreCursorPositionChanges
|
||||
|
||||
gotoLine: (line) ->
|
||||
@editor.gotoLine(line)
|
||||
@editor.focus()
|
||||
gotoLine: (line, column) ->
|
||||
@editor.gotoLine(line, column)
|
||||
@editor.focus()
|
||||
|
||||
@@ -4,7 +4,7 @@ define [
|
||||
], (App) ->
|
||||
App.controller "HotkeysController", ($scope, $modal, event_tracking) ->
|
||||
$scope.openHotkeysModal = ->
|
||||
event_tracking.sendCountly "ide-open-hotkeys-modal"
|
||||
event_tracking.sendMB "ide-open-hotkeys-modal"
|
||||
|
||||
$modal.open {
|
||||
templateUrl: "hotkeysModalTemplate"
|
||||
|
||||
@@ -3,7 +3,10 @@ define [
|
||||
"ide/human-readable-logs/HumanReadableLogsRules"
|
||||
], (LogParser, ruleset) ->
|
||||
parse : (rawLog, options) ->
|
||||
parsedLogEntries = LogParser.parse(rawLog, options)
|
||||
if typeof rawLog is 'string'
|
||||
parsedLogEntries = LogParser.parse(rawLog, options)
|
||||
else
|
||||
parsedLogEntries = rawLog
|
||||
|
||||
_getRule = (logMessage) ->
|
||||
return rule for rule in ruleset when rule.regexToMatch.test logMessage
|
||||
@@ -12,7 +15,12 @@ define [
|
||||
ruleDetails = _getRule entry.message
|
||||
|
||||
if (ruleDetails?)
|
||||
entry.ruleId = 'hint_' + ruleDetails.regexToMatch.toString().replace(/\s/g, '_').slice(1, -1) if ruleDetails.regexToMatch?
|
||||
if ruleDetails.ruleId?
|
||||
entry.ruleId = ruleDetails.ruleId
|
||||
else if ruleDetails.regexToMatch?
|
||||
entry.ruleId = 'hint_' + ruleDetails.regexToMatch.toString().replace(/\s/g, '_').slice(1, -1)
|
||||
if ruleDetails.newMessage?
|
||||
entry.message = entry.message.replace ruleDetails.regexToMatch, ruleDetails.newMessage
|
||||
|
||||
entry.humanReadableHint = ruleDetails.humanReadableHint if ruleDetails.humanReadableHint?
|
||||
entry.extraInfoURL = ruleDetails.extraInfoURL if ruleDetails.extraInfoURL?
|
||||
|
||||
@@ -88,4 +88,19 @@ define -> [
|
||||
humanReadableHint: """
|
||||
You have used a font command which is only available in math mode. To use this command, you must be in maths mode (E.g. $ \u2026 $ or \\begin{math} \u2026 \\end{math}). If you want to use it outside of math mode, use the text version instead: \\textrm, \\textit, etc.
|
||||
"""
|
||||
,
|
||||
ruleId: "hint_mismatched_environment"
|
||||
regexToMatch: /Error: `([^']{2,})' expected, found `([^']{2,})'.*/
|
||||
newMessage: "Error: environment does not match \\begin{$1} ... \\end{$2}"
|
||||
humanReadableHint: """
|
||||
You have used \\begin{...} without a corresponding \\end{...}.
|
||||
"""
|
||||
,
|
||||
ruleId: "hint_mismatched_brackets"
|
||||
regexToMatch: /Error: `([^a-zA-Z0-9])' expected, found `([^a-zA-Z0-9])'.*/
|
||||
newMessage: "Error: brackets do not match, found '$2' instead of '$1'"
|
||||
humanReadableHint: """
|
||||
You have used an open bracket without a corresponding close bracket.
|
||||
"""
|
||||
|
||||
]
|
||||
|
||||
@@ -36,11 +36,11 @@ define [
|
||||
$scope.logHintsNegFeedbackValues = logHintsFeedback.feedbackOpts
|
||||
|
||||
$scope.trackLogHintsLearnMore = () ->
|
||||
event_tracking.sendCountly "logs-hints-learn-more"
|
||||
event_tracking.sendMB "logs-hints-learn-more"
|
||||
|
||||
trackLogHintsFeedback = (isPositive, hintId) ->
|
||||
event_tracking.send "log-hints", (if isPositive then "feedback-positive" else "feedback-negative"), hintId
|
||||
event_tracking.sendCountly (if isPositive then "log-hints-feedback-positive" else "log-hints-feedback-negative"), { hintId }
|
||||
event_tracking.sendMB (if isPositive then "log-hints-feedback-positive" else "log-hints-feedback-negative"), { hintId }
|
||||
|
||||
$scope.trackLogHintsNegFeedbackDetails = (hintId, feedbackOpt, feedbackOtherVal) ->
|
||||
logHintsFeedback.submitFeedback hintId, feedbackOpt, feedbackOtherVal
|
||||
@@ -86,11 +86,15 @@ define [
|
||||
return $http.post url, {
|
||||
rootDoc_id: options.rootDocOverride_id or null
|
||||
draft: $scope.draft
|
||||
check: if options.check then "validate" else null
|
||||
_csrf: window.csrfToken
|
||||
}, {params: params}
|
||||
|
||||
parseCompileResponse = (response) ->
|
||||
|
||||
# keep last url
|
||||
last_pdf_url = $scope.pdf.url
|
||||
|
||||
# Reset everything
|
||||
$scope.pdf.error = false
|
||||
$scope.pdf.timedout = false
|
||||
@@ -120,11 +124,23 @@ define [
|
||||
if response.status == "timedout"
|
||||
$scope.pdf.view = 'errors'
|
||||
$scope.pdf.timedout = true
|
||||
fetchLogs(fileByPath['output.log'], fileByPath['output.blg'])
|
||||
fetchLogs(fileByPath)
|
||||
else if response.status == "terminated"
|
||||
$scope.pdf.view = 'errors'
|
||||
$scope.pdf.compileTerminated = true
|
||||
fetchLogs(fileByPath['output.log'], fileByPath['output.blg'])
|
||||
fetchLogs(fileByPath)
|
||||
else if response.status in ["validation-fail", "validation-pass"]
|
||||
$scope.pdf.view = 'pdf'
|
||||
$scope.pdf.compileExited = true
|
||||
$scope.pdf.url = last_pdf_url
|
||||
$scope.shouldShowLogs = true
|
||||
fetchLogs(fileByPath, { validation: true })
|
||||
else if response.status == "exited"
|
||||
$scope.pdf.view = 'pdf'
|
||||
$scope.pdf.compileExited = true
|
||||
$scope.pdf.url = last_pdf_url
|
||||
$scope.shouldShowLogs = true
|
||||
fetchLogs(fileByPath)
|
||||
else if response.status == "autocompile-backoff"
|
||||
$scope.pdf.view = 'uncompiled'
|
||||
else if response.status == "project-too-large"
|
||||
@@ -134,7 +150,7 @@ define [
|
||||
$scope.pdf.view = 'errors'
|
||||
$scope.pdf.failure = true
|
||||
$scope.shouldShowLogs = true
|
||||
fetchLogs(fileByPath['output.log'], fileByPath['output.blg'])
|
||||
fetchLogs(fileByPath)
|
||||
else if response.status == 'clsi-maintenance'
|
||||
$scope.pdf.view = 'errors'
|
||||
$scope.pdf.clsiMaintenance = true
|
||||
@@ -165,7 +181,7 @@ define [
|
||||
qs.popupDownload = true
|
||||
$scope.pdf.downloadUrl = "/project/#{$scope.project_id}/output/output.pdf" + createQueryString(qs)
|
||||
|
||||
fetchLogs(fileByPath['output.log'], fileByPath['output.blg'])
|
||||
fetchLogs(fileByPath)
|
||||
|
||||
IGNORE_FILES = ["output.fls", "output.fdb_latexmk"]
|
||||
$scope.pdf.outputFiles = []
|
||||
@@ -186,7 +202,13 @@ define [
|
||||
}
|
||||
|
||||
|
||||
fetchLogs = (logFile, blgFile) ->
|
||||
fetchLogs = (fileByPath, options) ->
|
||||
|
||||
if options?.validation
|
||||
chktexFile = fileByPath['output.chktex']
|
||||
else
|
||||
logFile = fileByPath['output.log']
|
||||
blgFile = fileByPath['output.blg']
|
||||
|
||||
getFile = (name, file) ->
|
||||
opts =
|
||||
@@ -213,6 +235,8 @@ define [
|
||||
|
||||
accumulateResults = (newEntries) ->
|
||||
for key in ['all', 'errors', 'warnings']
|
||||
if newEntries.type?
|
||||
entry.type = newEntries.type for entry in newEntries[key]
|
||||
logEntries[key] = logEntries[key].concat newEntries[key]
|
||||
|
||||
# use the parsers for each file type
|
||||
@@ -222,10 +246,24 @@ define [
|
||||
all = [].concat errors, warnings, typesetting
|
||||
accumulateResults {all, errors, warnings}
|
||||
|
||||
processChkTex = (log) ->
|
||||
errors = []
|
||||
warnings = []
|
||||
for line in log.split("\n")
|
||||
if m = line.match /^(\S+):(\d+):(\d+): (Error|Warning): (.*)/
|
||||
result = { file:m[1], line:m[2], column:m[3], level:m[4].toLowerCase(), message: "#{m[4]}: #{m[5]}"}
|
||||
if result.level is 'error'
|
||||
errors.push result
|
||||
else
|
||||
warnings.push result
|
||||
all = [].concat errors, warnings
|
||||
logHints = HumanReadableLogs.parse {type: "Validation", all, errors, warnings}
|
||||
accumulateResults logHints
|
||||
|
||||
processBiber = (log) ->
|
||||
{errors, warnings} = BibLogParser.parse(log, {})
|
||||
all = [].concat errors, warnings
|
||||
accumulateResults {all, errors, warnings}
|
||||
accumulateResults {type: "BibTeX", all, errors, warnings}
|
||||
|
||||
# output the results
|
||||
handleError = () ->
|
||||
@@ -248,19 +286,35 @@ define [
|
||||
}
|
||||
|
||||
# retrieve the logfile and process it
|
||||
response = getFile('output.log', logFile)
|
||||
.success processLog
|
||||
.error handleError
|
||||
if logFile?
|
||||
response = getFile('output.log', logFile)
|
||||
.then (response) -> processLog(response.data)
|
||||
|
||||
if blgFile? # retrieve the blg file if present
|
||||
response.success () ->
|
||||
getFile('output.blg', blgFile)
|
||||
# ignore errors in biber file
|
||||
.success processBiber
|
||||
# display the combined result
|
||||
.then annotateFiles
|
||||
else # otherwise just display the result
|
||||
response.success annotateFiles
|
||||
if blgFile? # retrieve the blg file if present
|
||||
response = response.then () ->
|
||||
getFile('output.blg', blgFile)
|
||||
.then(
|
||||
(response) -> processBiber(response.data),
|
||||
() -> true # ignore errors in biber file
|
||||
)
|
||||
|
||||
if response?
|
||||
response.catch handleError
|
||||
else
|
||||
handleError()
|
||||
|
||||
if chktexFile?
|
||||
getChkTex = () ->
|
||||
getFile('output.chktex', chktexFile)
|
||||
.then (response) -> processChkTex(response.data)
|
||||
# always retrieve the chktex file if present
|
||||
if response?
|
||||
response = response.then getChkTex, getChkTex
|
||||
else
|
||||
response = getChkTex()
|
||||
|
||||
# display the combined result
|
||||
response.finally annotateFiles
|
||||
|
||||
getRootDocOverride_id = () ->
|
||||
doc = ide.editorManager.getCurrentDocValue()
|
||||
@@ -284,7 +338,7 @@ define [
|
||||
$scope.recompile = (options = {}) ->
|
||||
return if $scope.pdf.compiling
|
||||
|
||||
event_tracking.sendCountlySampled "editor-recompile-sampled", options
|
||||
event_tracking.sendMBSampled "editor-recompile-sampled", options
|
||||
|
||||
$scope.pdf.compiling = true
|
||||
|
||||
@@ -333,7 +387,7 @@ define [
|
||||
|
||||
$scope.toggleLogs = () ->
|
||||
$scope.shouldShowLogs = !$scope.shouldShowLogs
|
||||
event_tracking.sendCountlyOnce "ide-open-logs-once" if $scope.shouldShowLogs
|
||||
event_tracking.sendMBOnce "ide-open-logs-once" if $scope.shouldShowLogs
|
||||
|
||||
$scope.showPdf = () ->
|
||||
$scope.pdf.view = "pdf"
|
||||
@@ -341,7 +395,7 @@ define [
|
||||
|
||||
$scope.toggleRawLog = () ->
|
||||
$scope.pdf.showRawLog = !$scope.pdf.showRawLog
|
||||
event_tracking.sendCountly "logs-view-raw" if $scope.pdf.showRawLog
|
||||
event_tracking.sendMB "logs-view-raw" if $scope.pdf.showRawLog
|
||||
|
||||
$scope.openClearCacheModal = () ->
|
||||
modalInstance = $modal.open(
|
||||
@@ -376,7 +430,7 @@ define [
|
||||
$scope.startFreeTrial = (source) ->
|
||||
ga?('send', 'event', 'subscription-funnel', 'compile-timeout', source)
|
||||
|
||||
event_tracking.sendCountly "subscription-start-trial", { source }
|
||||
event_tracking.sendMB "subscription-start-trial", { source }
|
||||
|
||||
window.open("/user/subscription/new?planCode=student_free_trial_7_days")
|
||||
$scope.startedFreeTrial = true
|
||||
@@ -499,12 +553,14 @@ define [
|
||||
|
||||
App.controller "PdfLogEntryController", ["$scope", "ide", "event_tracking", ($scope, ide, event_tracking) ->
|
||||
$scope.openInEditor = (entry) ->
|
||||
event_tracking.sendCountlyOnce "logs-jump-to-location-once"
|
||||
event_tracking.sendMBOnce "logs-jump-to-location-once"
|
||||
entity = ide.fileTreeManager.findEntityByPath(entry.file)
|
||||
return if !entity? or entity.type != "doc"
|
||||
if entry.line?
|
||||
line = entry.line
|
||||
ide.editorManager.openDoc(entity, gotoLine: line)
|
||||
if entry.column?
|
||||
column = entry.column
|
||||
ide.editorManager.openDoc(entity, gotoLine: line, gotoColumn: column)
|
||||
]
|
||||
|
||||
App.controller 'ClearCacheModalController', ["$scope", "$modalInstance", ($scope, $modalInstance) ->
|
||||
|
||||
@@ -8,7 +8,7 @@ define [
|
||||
for key in Object.keys(data)
|
||||
changedSetting = key
|
||||
changedSettingVal = data[key]
|
||||
event_tracking.sendCountly "setting-changed", { changedSetting, changedSettingVal }
|
||||
event_tracking.sendMB "setting-changed", { changedSetting, changedSettingVal }
|
||||
# End of tracking code.
|
||||
|
||||
data._csrf = window.csrfToken
|
||||
@@ -20,7 +20,7 @@ define [
|
||||
for key in Object.keys(data)
|
||||
changedSetting = key
|
||||
changedSettingVal = data[key]
|
||||
event_tracking.sendCountly "project-setting-changed", { changedSetting, changedSettingVal}
|
||||
event_tracking.sendMB "project-setting-changed", { changedSetting, changedSettingVal}
|
||||
# End of tracking code.
|
||||
|
||||
data._csrf = window.csrfToken
|
||||
@@ -32,7 +32,7 @@ define [
|
||||
for key in Object.keys(data)
|
||||
changedSetting = key
|
||||
changedSettingVal = data[key]
|
||||
event_tracking.sendCountly "project-admin-setting-changed", { changedSetting, changedSettingVal }
|
||||
event_tracking.sendMB "project-admin-setting-changed", { changedSetting, changedSettingVal }
|
||||
# End of tracking code.
|
||||
|
||||
data._csrf = window.csrfToken
|
||||
|
||||
@@ -4,7 +4,7 @@ define [
|
||||
App.controller "ShareController", ["$scope", "$modal", "ide", "projectInvites", "projectMembers", "event_tracking",
|
||||
($scope, $modal, ide, projectInvites, projectMembers, event_tracking) ->
|
||||
$scope.openShareProjectModal = () ->
|
||||
event_tracking.sendCountlyOnce "ide-open-share-modal-once"
|
||||
event_tracking.sendMBOnce "ide-open-share-modal-once"
|
||||
|
||||
$modal.open(
|
||||
templateUrl: "shareProjectModalTemplate"
|
||||
|
||||
@@ -117,6 +117,9 @@ define [
|
||||
diff.error = true
|
||||
else
|
||||
diff.deleted = true
|
||||
diff.restoreInProgress = false
|
||||
diff.restoreDeletedSuccess = false
|
||||
diff.restoredDocNewId = null
|
||||
|
||||
restoreDeletedDoc: (doc) ->
|
||||
url = "/project/#{@$scope.project_id}/doc/#{doc.id}/restore"
|
||||
|
||||
+17
-5
@@ -1,13 +1,21 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.controller "TrackChangesDiffController", ($scope, $modal, ide) ->
|
||||
App.controller "TrackChangesDiffController", ($scope, $modal, ide, event_tracking) ->
|
||||
$scope.restoreDeletedDoc = () ->
|
||||
ide.trackChangesManager.restoreDeletedDoc(
|
||||
$scope.trackChanges.diff.doc
|
||||
)
|
||||
event_tracking.sendMB "track-changes-restore-deleted"
|
||||
$scope.trackChanges.diff.restoreInProgress = true
|
||||
ide.trackChangesManager
|
||||
.restoreDeletedDoc(
|
||||
$scope.trackChanges.diff.doc
|
||||
)
|
||||
.success (response) ->
|
||||
$scope.trackChanges.diff.restoredDocNewId = response.doc_id
|
||||
$scope.trackChanges.diff.restoreInProgress = false
|
||||
$scope.trackChanges.diff.restoreDeletedSuccess = true
|
||||
|
||||
$scope.openRestoreDiffModal = () ->
|
||||
event_tracking.sendMB "track-changes-restore-modal"
|
||||
$modal.open {
|
||||
templateUrl: "trackChangesRestoreDiffModalTemplate"
|
||||
controller: "TrackChangesRestoreDiffModalController"
|
||||
@@ -15,13 +23,17 @@ define [
|
||||
diff: () -> $scope.trackChanges.diff
|
||||
}
|
||||
|
||||
App.controller "TrackChangesRestoreDiffModalController", ($scope, $modalInstance, diff, ide) ->
|
||||
$scope.backToEditorAfterRestore = () ->
|
||||
ide.editorManager.openDoc({ id: $scope.trackChanges.diff.restoredDocNewId })
|
||||
|
||||
App.controller "TrackChangesRestoreDiffModalController", ($scope, $modalInstance, diff, ide, event_tracking) ->
|
||||
$scope.state =
|
||||
inflight: false
|
||||
|
||||
$scope.diff = diff
|
||||
|
||||
$scope.restore = () ->
|
||||
event_tracking.sendMB "track-changes-restored"
|
||||
$scope.state.inflight = true
|
||||
ide.trackChangesManager
|
||||
.restoreDiff(diff)
|
||||
|
||||
+2
-1
@@ -78,7 +78,7 @@ define [
|
||||
$scope.recalculateSelectedUpdates()
|
||||
]
|
||||
|
||||
App.controller "TrackChangesListItemController", ["$scope", ($scope) ->
|
||||
App.controller "TrackChangesListItemController", ["$scope", "event_tracking", ($scope, event_tracking) ->
|
||||
$scope.$watch "update.selectedFrom", (selectedFrom, oldSelectedFrom) ->
|
||||
if selectedFrom
|
||||
for update in $scope.trackChanges.updates
|
||||
@@ -92,6 +92,7 @@ define [
|
||||
$scope.recalculateSelectedUpdates()
|
||||
|
||||
$scope.select = () ->
|
||||
event_tracking.sendMB "track-changes-view-change"
|
||||
$scope.update.selectedTo = true
|
||||
$scope.update.selectedFrom = true
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ define [
|
||||
$scope.buttonClass = "btn-primary"
|
||||
|
||||
$scope.startFreeTrial = (source, couponCode) ->
|
||||
event_tracking.sendCountly "subscription-start-trial", { source }
|
||||
event_tracking.sendMB "subscription-start-trial", { source }
|
||||
|
||||
w = window.open()
|
||||
sixpack.convert "track-changes-discount", ->
|
||||
|
||||
@@ -24,7 +24,7 @@ define [
|
||||
url :"/learn/kb/#{page_underscored}"
|
||||
name : hit._highlightResult.pageName.value
|
||||
|
||||
event_tracking.sendCountly "contact-form-suggestions-shown" if results.hits.length
|
||||
event_tracking.sendMB "contact-form-suggestions-shown" if results.hits.length
|
||||
|
||||
$scope.$applyAsync () ->
|
||||
$scope.suggestions = suggestions
|
||||
@@ -60,7 +60,7 @@ define [
|
||||
$scope.suggestions = [];
|
||||
|
||||
$scope.clickSuggestionLink = (url) ->
|
||||
event_tracking.sendCountly "contact-form-suggestions-clicked", { url }
|
||||
event_tracking.sendMB "contact-form-suggestions-clicked", { url }
|
||||
|
||||
$scope.close = () ->
|
||||
$modalInstance.close()
|
||||
|
||||
@@ -2,9 +2,14 @@ define [
|
||||
"base"
|
||||
"modules/localStorage"
|
||||
], (App) ->
|
||||
CACHE_KEY = "countlyEvents"
|
||||
CACHE_KEY = "mbEvents"
|
||||
|
||||
App.factory "event_tracking", (localStorage) ->
|
||||
send = (category, action, attributes = {})->
|
||||
ga('send', 'event', category, action)
|
||||
event_name = "#{action}-#{category}"
|
||||
Intercom?("trackEvent", event_name, attributes)
|
||||
|
||||
App.factory "event_tracking", ($http, localStorage) ->
|
||||
_getEventCache = () ->
|
||||
eventCache = localStorage CACHE_KEY
|
||||
|
||||
@@ -29,18 +34,23 @@ define [
|
||||
send: (category, action, label, value)->
|
||||
ga('send', 'event', category, action, label, value)
|
||||
|
||||
sendCountly: (key, segmentation) ->
|
||||
eventData = { key }
|
||||
eventData.segmentation = segmentation if segmentation?
|
||||
Countly?.q.push([ "add_event", eventData ])
|
||||
sendMB: (key, segmentation = {}) ->
|
||||
$http {
|
||||
url: "/event/#{key}",
|
||||
method: "POST",
|
||||
data: segmentation
|
||||
headers: {
|
||||
"X-CSRF-Token": window.csrfToken
|
||||
}
|
||||
}
|
||||
|
||||
sendCountlySampled: (key, segmentation) ->
|
||||
@sendCountly key, segmentation if Math.random() < .01
|
||||
sendMBSampled: (key, segmentation) ->
|
||||
@sendMB key, segmentation if Math.random() < .01
|
||||
|
||||
sendCountlyOnce: (key, segmentation) ->
|
||||
sendMBOnce: (key, segmentation) ->
|
||||
if ! _eventInCache(key)
|
||||
_addEventToCache(key)
|
||||
@sendCountly key, segmentation
|
||||
@sendMB key, segmentation
|
||||
}
|
||||
|
||||
# App.directive "countlyTrack", () ->
|
||||
|
||||
@@ -51,7 +51,7 @@ define [
|
||||
.done()
|
||||
|
||||
pricing.on "change", =>
|
||||
event_tracking.sendCountly "subscription-form", { plan : pricing.items.plan.code }
|
||||
event_tracking.sendMB "subscription-form", { plan : pricing.items.plan.code }
|
||||
|
||||
$scope.planName = pricing.items.plan.name
|
||||
$scope.price = pricing.price
|
||||
@@ -125,7 +125,7 @@ define [
|
||||
state: $scope.data.state
|
||||
postal_code: $scope.data.postal_code
|
||||
|
||||
event_tracking.sendCountly "subscription-form-submitted", {
|
||||
event_tracking.sendMB "subscription-form-submitted", {
|
||||
currencyCode : postData.subscriptionDetails.currencyCode,
|
||||
plan_code : postData.subscriptionDetails.plan_code,
|
||||
coupon_code : postData.subscriptionDetails.coupon_code,
|
||||
@@ -135,7 +135,7 @@ define [
|
||||
$http.post("/user/subscription/create", postData)
|
||||
.success (data, status, headers)->
|
||||
sixpack.convert "in-editor-free-trial-plan", pricing.items.plan.code, (err)->
|
||||
event_tracking.sendCountly "subscription-submission-success"
|
||||
event_tracking.sendMB "subscription-submission-success"
|
||||
window.location.href = "/user/subscription/thank-you"
|
||||
.error (data, status, headers)->
|
||||
$scope.processing = false
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -129,6 +129,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.break-word {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.editor-dark {
|
||||
.chat {
|
||||
.new-message {
|
||||
|
||||
@@ -267,6 +267,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.diff-deleted {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.editor-dark {
|
||||
#trackChanges {
|
||||
aside.change-list {
|
||||
|
||||
@@ -19,6 +19,7 @@ describe "AuthenticationController", ->
|
||||
"../../infrastructure/Metrics": @Metrics = { inc: sinon.stub() }
|
||||
"../Security/LoginRateLimiter": @LoginRateLimiter = { processLoginRequest:sinon.stub(), recordSuccessfulLogin:sinon.stub() }
|
||||
"../User/UserHandler": @UserHandler = {setupLoginData:sinon.stub()}
|
||||
"../Analytics/AnalyticsManager": @AnalyticsManager = { recordEvent: sinon.stub() }
|
||||
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
|
||||
"settings-sharelatex": {}
|
||||
"../User/UserSessionsManager": @UserSessionsManager =
|
||||
@@ -100,6 +101,11 @@ describe "AuthenticationController", ->
|
||||
.calledWith(email: @email.toLowerCase(), user_id: @user._id.toString(), "successful log in")
|
||||
.should.equal true
|
||||
|
||||
it "should track the login event", ->
|
||||
@AnalyticsManager.recordEvent
|
||||
.calledWith(@user._id, "user-logged-in")
|
||||
.should.equal true
|
||||
|
||||
|
||||
describe 'when the user is not authenticated', ->
|
||||
beforeEach ->
|
||||
|
||||
@@ -174,6 +174,7 @@ describe "ClsiManager", ->
|
||||
timeout : 100
|
||||
imageName: @image
|
||||
draft: false
|
||||
check: undefined
|
||||
rootResourcePath: "main.tex"
|
||||
resources: [{
|
||||
path: "main.tex"
|
||||
|
||||
@@ -322,6 +322,11 @@ describe 'ReferencesHandler', ->
|
||||
result = @handler._findBibDocIds(@fakeProject)
|
||||
expect(result).to.deep.equal @expectedIds
|
||||
|
||||
it 'should not error with a non array of folders from dirty data', ->
|
||||
@fakeProject.rootFolder[0].folders[0].folders = {}
|
||||
result = @handler._findBibDocIds(@fakeProject)
|
||||
expect(result).to.deep.equal @expectedIds
|
||||
|
||||
describe '_findBibFileIds', ->
|
||||
|
||||
beforeEach ->
|
||||
|
||||
@@ -33,6 +33,7 @@ describe "UserRegistrationHandler", ->
|
||||
"crypto": @crypto = {}
|
||||
"../Email/EmailHandler": @EmailHandler
|
||||
"../Security/OneTimeTokenHandler": @OneTimeTokenHandler
|
||||
"../Analytics/AnalyticsManager": @AnalyticsManager = { recordEvent: sinon.stub() }
|
||||
"settings-sharelatex": @settings = {siteUrl: "http://sl.example.com"}
|
||||
|
||||
@passingRequest = {email:"something@email.com", password:"123"}
|
||||
@@ -132,6 +133,13 @@ describe "UserRegistrationHandler", ->
|
||||
@NewsLetterManager.subscribe.calledWith(@user).should.equal true
|
||||
done()
|
||||
|
||||
it "should track the registration event", (done)->
|
||||
@handler.registerNewUser @passingRequest, (err)=>
|
||||
@AnalyticsManager.recordEvent
|
||||
.calledWith(@user._id, "user-registered")
|
||||
.should.equal true
|
||||
done()
|
||||
|
||||
|
||||
it "should call the ReferalAllocator", (done)->
|
||||
done()
|
||||
|
||||
Reference in New Issue
Block a user