mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
154943ba68
There are a couple of reasons for this: 1. Some linting errors are returned from the server after a compile is run, replacing client-side linting errors. If code check is disabled this does not happen, and therefore linting errors persist until the next compile. This makes it appear as though autocompile is not running 2. It is likely that if code check is disabled, the user is deliberately ignoring linting errors and therefore the linting check is pointless
690 lines
23 KiB
CoffeeScript
690 lines
23 KiB
CoffeeScript
define [
|
|
"base"
|
|
"ace/ace"
|
|
"ide/human-readable-logs/HumanReadableLogs"
|
|
"libs/bib-log-parser"
|
|
"services/log-hints-feedback"
|
|
], (App, Ace, HumanReadableLogs, BibLogParser) ->
|
|
AUTO_COMPILE_TIMEOUT = 5000
|
|
OP_ACKNOWLEDGEMENT_TIMEOUT = 1100
|
|
|
|
App.controller "PdfController", ($scope, $http, ide, $modal, synctex, event_tracking, logHintsFeedback, localStorage) ->
|
|
# enable per-user containers by default
|
|
perUserCompile = true
|
|
autoCompile = true
|
|
|
|
# pdf.view = uncompiled | pdf | errors
|
|
$scope.pdf.view = if $scope?.pdf?.url then 'pdf' else 'uncompiled'
|
|
$scope.shouldShowLogs = false
|
|
$scope.wikiEnabled = window.wikiEnabled;
|
|
|
|
# view logic to check whether the files dropdown should "drop up" or "drop down"
|
|
$scope.shouldDropUp = false
|
|
|
|
logsContainerEl = document.querySelector ".pdf-logs"
|
|
filesDropdownEl = logsContainerEl?.querySelector ".files-dropdown"
|
|
|
|
# get the top coordinate of the files dropdown as a ratio (to the logs container height)
|
|
# logs container supports scrollable content, so it's possible that ratio > 1.
|
|
getFilesDropdownTopCoordAsRatio = () ->
|
|
filesDropdownEl?.getBoundingClientRect().top / logsContainerEl?.getBoundingClientRect().height
|
|
|
|
$scope.$watch "shouldShowLogs", (shouldShow) ->
|
|
if shouldShow
|
|
$scope.$applyAsync () ->
|
|
$scope.shouldDropUp = getFilesDropdownTopCoordAsRatio() > 0.65
|
|
|
|
# log hints tracking
|
|
$scope.logHintsNegFeedbackValues = logHintsFeedback.feedbackOpts
|
|
|
|
$scope.trackLogHintsLearnMore = () ->
|
|
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.sendMB (if isPositive then "log-hints-feedback-positive" else "log-hints-feedback-negative"), { hintId }
|
|
|
|
$scope.trackLogHintsNegFeedbackDetails = (hintId, feedbackOpt, feedbackOtherVal) ->
|
|
logHintsFeedback.submitFeedback hintId, feedbackOpt, feedbackOtherVal
|
|
|
|
$scope.trackLogHintsPositiveFeedback = (hintId) -> trackLogHintsFeedback true, hintId
|
|
$scope.trackLogHintsNegativeFeedback = (hintId) -> trackLogHintsFeedback false, hintId
|
|
|
|
if ace.require("ace/lib/useragent").isMac
|
|
$scope.modifierKey = "Cmd"
|
|
else
|
|
$scope.modifierKey = "Ctrl"
|
|
|
|
# utility for making a query string from a hash, could use jquery $.param
|
|
createQueryString = (args) ->
|
|
qs_args = ("#{k}=#{v}" for k, v of args)
|
|
if qs_args.length then "?" + qs_args.join("&") else ""
|
|
|
|
$scope.stripHTMLFromString = (htmlStr) ->
|
|
tmp = document.createElement("DIV")
|
|
tmp.innerHTML = htmlStr
|
|
return tmp.textContent || tmp.innerText || ""
|
|
|
|
$scope.$on "project:joined", () ->
|
|
return if !autoCompile
|
|
autoCompile = false
|
|
$scope.recompile(isAutoCompileOnLoad: true)
|
|
$scope.hasPremiumCompile = $scope.project.features.compileGroup == "priority"
|
|
|
|
$scope.$on "pdf:error:display", () ->
|
|
$scope.pdf.view = 'errors'
|
|
$scope.pdf.renderingError = true
|
|
|
|
autoCompileTimeout = null
|
|
triggerAutoCompile = () ->
|
|
return if autoCompileTimeout or $scope.ui.pdfHidden
|
|
|
|
timeSinceLastCompile = Date.now() - $scope.recompiledAt
|
|
# If time is non-monotonic, assume that the user's system clock has been
|
|
# changed and continue with recompile
|
|
isTimeNonMonotonic = timeSinceLastCompile < 0
|
|
|
|
if isTimeNonMonotonic || timeSinceLastCompile >= AUTO_COMPILE_TIMEOUT
|
|
# If user has code check disabled, it is likely because they have
|
|
# linting errors that they are ignoring. Therefore it doesn't make sense
|
|
# to block auto compiles. It also causes problems where server-provided
|
|
# linting errors aren't cleared after typing
|
|
if (ide.$scope.settings.syntaxValidation and !ide.$scope.hasLintingError)
|
|
$scope.recompile(isAutoCompileOnChange: true)
|
|
else
|
|
# Extend remainder of timeout
|
|
autoCompileTimeout = setTimeout () ->
|
|
autoCompileTimeout = null
|
|
triggerAutoCompile()
|
|
, AUTO_COMPILE_TIMEOUT - timeSinceLastCompile
|
|
|
|
autoCompileListener = null
|
|
toggleAutoCompile = (enabling) ->
|
|
if enabling
|
|
autoCompileListener = ide.$scope.$on "ide:opAcknowledged", _.debounce(triggerAutoCompile, OP_ACKNOWLEDGEMENT_TIMEOUT)
|
|
else
|
|
autoCompileListener() if autoCompileListener
|
|
autoCompileListener = null
|
|
|
|
$scope.autocompile_enabled = localStorage("autocompile_enabled:#{$scope.project_id}") or false
|
|
$scope.$watch "autocompile_enabled", (newValue, oldValue) ->
|
|
if newValue? and oldValue != newValue
|
|
localStorage("autocompile_enabled:#{$scope.project_id}", newValue)
|
|
toggleAutoCompile(newValue)
|
|
event_tracking.sendMB "autocompile-setting-changed", { value: newValue }
|
|
|
|
if (window.user?.betaProgram or window.autoCompileEnabled) and $scope.autocompile_enabled
|
|
toggleAutoCompile(true)
|
|
|
|
# abort compile if syntax checks fail
|
|
$scope.stop_on_validation_error = localStorage("stop_on_validation_error:#{$scope.project_id}")
|
|
$scope.stop_on_validation_error ?= true # turn on for all users by default
|
|
$scope.$watch "stop_on_validation_error", (new_value, old_value) ->
|
|
if new_value? and old_value != new_value
|
|
localStorage("stop_on_validation_error:#{$scope.project_id}", new_value)
|
|
|
|
$scope.draft = localStorage("draft:#{$scope.project_id}") or false
|
|
$scope.$watch "draft", (new_value, old_value) ->
|
|
if new_value? and old_value != new_value
|
|
localStorage("draft:#{$scope.project_id}", new_value)
|
|
|
|
sendCompileRequest = (options = {}) ->
|
|
url = "/project/#{$scope.project_id}/compile"
|
|
params = {}
|
|
if options.isAutoCompileOnLoad or options.isAutoCompileOnChange
|
|
params["auto_compile"]=true
|
|
# if the previous run was a check, clear the error logs
|
|
$scope.pdf.logEntries = [] if $scope.check
|
|
# keep track of whether this is a compile or check
|
|
$scope.check = if options.check then true else false
|
|
event_tracking.sendMB "syntax-check-request" if options.check
|
|
# send appropriate check type to clsi
|
|
checkType = switch
|
|
when $scope.check then "validate" # validate only
|
|
when options.try then "silent" # allow use to try compile once
|
|
when $scope.stop_on_validation_error then "error" # try to compile
|
|
else "silent" # ignore errors
|
|
return $http.post url, {
|
|
rootDoc_id: options.rootDocOverride_id or null
|
|
draft: $scope.draft
|
|
check: checkType
|
|
# use incremental compile for all users but revert to a full
|
|
# compile if there is a server error
|
|
incrementalCompilesEnabled: not $scope.pdf.error
|
|
_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
|
|
$scope.pdf.failure = false
|
|
$scope.pdf.url = null
|
|
$scope.pdf.clsiMaintenance = false
|
|
$scope.pdf.tooRecentlyCompiled = false
|
|
$scope.pdf.renderingError = false
|
|
$scope.pdf.projectTooLarge = false
|
|
$scope.pdf.compileTerminated = false
|
|
$scope.pdf.compileExited = false
|
|
$scope.pdf.failedCheck = false
|
|
$scope.pdf.compileInProgress = false
|
|
$scope.pdf.autoCompileDisabled = false
|
|
|
|
# make a cache to look up files by name
|
|
fileByPath = {}
|
|
if response?.outputFiles?
|
|
for file in response?.outputFiles
|
|
fileByPath[file.path] = file
|
|
|
|
# prepare query string
|
|
qs = {}
|
|
# add a query string parameter for the compile group
|
|
if response.compileGroup?
|
|
ide.compileGroup = qs.compileGroup = response.compileGroup
|
|
# add a query string parameter for the clsi server id
|
|
if response.clsiServerId?
|
|
ide.clsiServerId = qs.clsiserverid = response.clsiServerId
|
|
|
|
if response.status == "timedout"
|
|
$scope.pdf.view = 'errors'
|
|
$scope.pdf.timedout = true
|
|
fetchLogs(fileByPath)
|
|
else if response.status == "terminated"
|
|
$scope.pdf.view = 'errors'
|
|
$scope.pdf.compileTerminated = true
|
|
fetchLogs(fileByPath)
|
|
else if response.status in ["validation-fail", "validation-pass"]
|
|
$scope.pdf.view = 'pdf'
|
|
$scope.pdf.url = last_pdf_url
|
|
$scope.shouldShowLogs = true
|
|
$scope.pdf.failedCheck = true if response.status is "validation-fail"
|
|
event_tracking.sendMB "syntax-check-#{response.status}"
|
|
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"
|
|
if $scope.pdf.isAutoCompileOnLoad # initial autocompile
|
|
$scope.pdf.view = 'uncompiled'
|
|
else # background autocompile from typing
|
|
$scope.pdf.view = 'errors'
|
|
$scope.pdf.autoCompileDisabled = true
|
|
$scope.autocompile_enabled = false # disable any further autocompiles
|
|
event_tracking.sendMB "autocompile-rate-limited", {hasPremiumCompile: $scope.hasPremiumCompile}
|
|
else if response.status == "project-too-large"
|
|
$scope.pdf.view = 'errors'
|
|
$scope.pdf.projectTooLarge = true
|
|
else if response.status == "failure"
|
|
$scope.pdf.view = 'errors'
|
|
$scope.pdf.failure = true
|
|
$scope.shouldShowLogs = true
|
|
fetchLogs(fileByPath)
|
|
else if response.status == 'clsi-maintenance'
|
|
$scope.pdf.view = 'errors'
|
|
$scope.pdf.clsiMaintenance = true
|
|
else if response.status == "too-recently-compiled"
|
|
$scope.pdf.view = 'errors'
|
|
$scope.pdf.tooRecentlyCompiled = true
|
|
else if response.status == "validation-problems"
|
|
$scope.pdf.view = "validation-problems"
|
|
$scope.pdf.validation = response.validationProblems
|
|
$scope.shouldShowLogs = false
|
|
else if response.status == "compile-in-progress"
|
|
$scope.pdf.view = 'errors'
|
|
$scope.pdf.compileInProgress = true
|
|
else if response.status == "success"
|
|
$scope.pdf.view = 'pdf'
|
|
$scope.shouldShowLogs = false
|
|
|
|
# define the base url. if the pdf file has a build number, pass it to the clsi in the url
|
|
if fileByPath['output.pdf']?.url?
|
|
$scope.pdf.url = fileByPath['output.pdf'].url
|
|
else if fileByPath['output.pdf']?.build?
|
|
build = fileByPath['output.pdf'].build
|
|
$scope.pdf.url = "/project/#{$scope.project_id}/build/#{build}/output/output.pdf"
|
|
else
|
|
$scope.pdf.url = "/project/#{$scope.project_id}/output/output.pdf"
|
|
# check if we need to bust cache (build id is unique so don't need it in that case)
|
|
if not fileByPath['output.pdf']?.build?
|
|
qs.cache_bust = "#{Date.now()}"
|
|
# convert the qs hash into a query string and append it
|
|
$scope.pdf.url += createQueryString qs
|
|
# Save all downloads as files
|
|
qs.popupDownload = true
|
|
$scope.pdf.downloadUrl = "/project/#{$scope.project_id}/output/output.pdf" + createQueryString(qs)
|
|
|
|
fetchLogs(fileByPath)
|
|
|
|
IGNORE_FILES = ["output.fls", "output.fdb_latexmk"]
|
|
$scope.pdf.outputFiles = []
|
|
|
|
if !response.outputFiles?
|
|
return
|
|
|
|
# prepare list of output files for download dropdown
|
|
qs = {}
|
|
if response.clsiServerId?
|
|
qs.clsiserverid = response.clsiServerId
|
|
for file in response.outputFiles
|
|
if IGNORE_FILES.indexOf(file.path) == -1
|
|
isOutputFile = file.path.match(/^output\./)
|
|
$scope.pdf.outputFiles.push {
|
|
# Turn 'output.blg' into 'blg file'.
|
|
name: if isOutputFile then "#{file.path.replace(/^output\./, "")} file" else file.path
|
|
url: "/project/#{project_id}/output/#{file.path}" + createQueryString qs
|
|
main: if isOutputFile then true else false
|
|
}
|
|
|
|
# sort the output files into order, main files first, then others
|
|
$scope.pdf.outputFiles.sort (a,b) -> (b.main - a.main) || a.name.localeCompare(b.name)
|
|
|
|
|
|
fetchLogs = (fileByPath, options) ->
|
|
|
|
if options?.validation
|
|
chktexFile = fileByPath['output.chktex']
|
|
else
|
|
logFile = fileByPath['output.log']
|
|
blgFile = fileByPath['output.blg']
|
|
|
|
getFile = (name, file) ->
|
|
opts =
|
|
method:"GET"
|
|
params:
|
|
compileGroup:ide.compileGroup
|
|
clsiserverid:ide.clsiServerId
|
|
if file?.url? # FIXME clean this up when we have file.urls out consistently
|
|
opts.url = file.url
|
|
else if file?.build?
|
|
opts.url = "/project/#{$scope.project_id}/build/#{file.build}/output/#{name}"
|
|
else
|
|
opts.url = "/project/#{$scope.project_id}/output/#{name}"
|
|
# check if we need to bust cache (build id is unique so don't need it in that case)
|
|
if not file?.build?
|
|
opts.params.cache_bust = "#{Date.now()}"
|
|
return $http(opts)
|
|
|
|
# accumulate the log entries
|
|
logEntries =
|
|
all: []
|
|
errors: []
|
|
warnings: []
|
|
|
|
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
|
|
processLog = (log) ->
|
|
$scope.pdf.rawLog = log
|
|
{errors, warnings, typesetting} = HumanReadableLogs.parse(log, ignoreDuplicates: true)
|
|
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: "Syntax", all, errors, warnings}
|
|
event_tracking.sendMB "syntax-check-return-count", {errors:errors.length, warnings:warnings.length}
|
|
accumulateResults logHints
|
|
|
|
processBiber = (log) ->
|
|
{errors, warnings} = BibLogParser.parse(log, {})
|
|
all = [].concat errors, warnings
|
|
accumulateResults {type: "BibTeX", all, errors, warnings}
|
|
|
|
# output the results
|
|
handleError = () ->
|
|
$scope.pdf.logEntries = []
|
|
$scope.pdf.rawLog = ""
|
|
|
|
annotateFiles = () ->
|
|
$scope.pdf.logEntries = logEntries
|
|
$scope.pdf.logEntryAnnotations = {}
|
|
for entry in logEntries.all
|
|
if entry.file?
|
|
entry.file = normalizeFilePath(entry.file)
|
|
entity = ide.fileTreeManager.findEntityByPath(entry.file)
|
|
if entity?
|
|
$scope.pdf.logEntryAnnotations[entity.id] ||= []
|
|
$scope.pdf.logEntryAnnotations[entity.id].push {
|
|
row: entry.line - 1
|
|
type: if entry.level == "error" then "error" else "warning"
|
|
text: entry.message
|
|
}
|
|
|
|
# retrieve the logfile and process it
|
|
if logFile?
|
|
response = getFile('output.log', logFile)
|
|
.then (response) -> processLog(response.data)
|
|
|
|
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
|
|
if response?
|
|
response.finally annotateFiles
|
|
|
|
getRootDocOverride_id = () ->
|
|
doc = ide.editorManager.getCurrentDocValue()
|
|
return null if !doc?
|
|
for line in doc.split("\n")
|
|
match = line.match /^[^%]*\\documentclass/
|
|
if match
|
|
return ide.editorManager.getCurrentDocId()
|
|
return null
|
|
|
|
normalizeFilePath = (path) ->
|
|
path = path.replace(/^(.*)\/compiles\/[0-9a-f]{24}(-[0-9a-f]{24})?\/(\.\/)?/, "")
|
|
path = path.replace(/^\/compile\//, "")
|
|
|
|
rootDocDirname = ide.fileTreeManager.getRootDocDirname()
|
|
if rootDocDirname?
|
|
path = path.replace(/^\.\//, rootDocDirname + "/")
|
|
|
|
return path
|
|
|
|
$scope.recompile = (options = {}) ->
|
|
return if $scope.pdf.compiling
|
|
|
|
if !options.isAutoCompileOnLoad and $scope.onboarding.autoCompile == 'unseen'
|
|
$scope.onboarding.autoCompile = 'show'
|
|
|
|
event_tracking.sendMBSampled "editor-recompile-sampled", options
|
|
|
|
$scope.pdf.compiling = true
|
|
$scope.pdf.isAutoCompileOnLoad = options?.isAutoCompileOnLoad # initial autocompile
|
|
|
|
if options?.force
|
|
# for forced compile, turn off validation check and ignore errors
|
|
$scope.stop_on_validation_error = false
|
|
$scope.shouldShowLogs = false # hide the logs while compiling
|
|
event_tracking.sendMB "syntax-check-turn-off-checking"
|
|
|
|
if options?.try
|
|
$scope.shouldShowLogs = false # hide the logs while compiling
|
|
event_tracking.sendMB "syntax-check-try-compile-anyway"
|
|
|
|
ide.$scope.$broadcast("flush-changes")
|
|
|
|
options.rootDocOverride_id = getRootDocOverride_id()
|
|
|
|
sendCompileRequest(options)
|
|
.then (response) ->
|
|
{ data } = response
|
|
$scope.pdf.view = "pdf"
|
|
$scope.pdf.compiling = false
|
|
parseCompileResponse(data)
|
|
.catch (response) ->
|
|
{ data, status } = response
|
|
if status == 429
|
|
$scope.pdf.rateLimited = true
|
|
$scope.pdf.compiling = false
|
|
$scope.pdf.renderingError = false
|
|
$scope.pdf.error = true
|
|
$scope.pdf.view = 'errors'
|
|
.finally () ->
|
|
$scope.recompiledAt = Date.now()
|
|
|
|
# This needs to be public.
|
|
ide.$scope.recompile = $scope.recompile
|
|
# This method is a simply wrapper and exists only for tracking purposes.
|
|
ide.$scope.recompileViaKey = () ->
|
|
$scope.recompile { keyShortcut: true }
|
|
|
|
$scope.stop = () ->
|
|
return if !$scope.pdf.compiling
|
|
|
|
$http {
|
|
url: "/project/#{$scope.project_id}/compile/stop"
|
|
method: "POST"
|
|
params:
|
|
clsiserverid:ide.clsiServerId
|
|
headers:
|
|
"X-Csrf-Token": window.csrfToken
|
|
}
|
|
|
|
$scope.clearCache = () ->
|
|
$http {
|
|
url: "/project/#{$scope.project_id}/output"
|
|
method: "DELETE"
|
|
params:
|
|
clsiserverid:ide.clsiServerId
|
|
headers:
|
|
"X-Csrf-Token": window.csrfToken
|
|
}
|
|
|
|
$scope.toggleLogs = () ->
|
|
$scope.shouldShowLogs = !$scope.shouldShowLogs
|
|
event_tracking.sendMBOnce "ide-open-logs-once" if $scope.shouldShowLogs
|
|
|
|
$scope.showPdf = () ->
|
|
$scope.pdf.view = "pdf"
|
|
$scope.shouldShowLogs = false
|
|
|
|
$scope.toggleRawLog = () ->
|
|
$scope.pdf.showRawLog = !$scope.pdf.showRawLog
|
|
event_tracking.sendMB "logs-view-raw" if $scope.pdf.showRawLog
|
|
|
|
$scope.openClearCacheModal = () ->
|
|
modalInstance = $modal.open(
|
|
templateUrl: "clearCacheModalTemplate"
|
|
controller: "ClearCacheModalController"
|
|
scope: $scope
|
|
)
|
|
|
|
$scope.syncToCode = (position) ->
|
|
synctex
|
|
.syncToCode(position)
|
|
.then (data) ->
|
|
{doc, line} = data
|
|
ide.editorManager.openDoc(doc, gotoLine: line)
|
|
|
|
$scope.switchToFlatLayout = () ->
|
|
$scope.ui.pdfLayout = 'flat'
|
|
$scope.ui.view = 'pdf'
|
|
ide.localStorage "pdf.layout", "flat"
|
|
|
|
$scope.switchToSideBySideLayout = () ->
|
|
$scope.ui.pdfLayout = 'sideBySide'
|
|
$scope.ui.view = 'editor'
|
|
localStorage "pdf.layout", "split"
|
|
|
|
if pdfLayout = localStorage("pdf.layout")
|
|
$scope.switchToSideBySideLayout() if pdfLayout == "split"
|
|
$scope.switchToFlatLayout() if pdfLayout == "flat"
|
|
else
|
|
$scope.switchToSideBySideLayout()
|
|
|
|
$scope.startFreeTrial = (source) ->
|
|
ga?('send', 'event', 'subscription-funnel', 'compile-timeout', source)
|
|
|
|
event_tracking.sendMB "subscription-start-trial", { source }
|
|
|
|
window.open("/user/subscription/new?planCode=#{$scope.startTrialPlanCode}")
|
|
$scope.startedFreeTrial = true
|
|
|
|
App.factory "synctex", ["ide", "$http", "$q", (ide, $http, $q) ->
|
|
# enable per-user containers by default
|
|
perUserCompile = true
|
|
|
|
synctex =
|
|
syncToPdf: (cursorPosition) ->
|
|
deferred = $q.defer()
|
|
|
|
doc_id = ide.editorManager.getCurrentDocId()
|
|
if !doc_id?
|
|
deferred.reject()
|
|
return deferred.promise
|
|
doc = ide.fileTreeManager.findEntityById(doc_id)
|
|
if !doc?
|
|
deferred.reject()
|
|
return deferred.promise
|
|
path = ide.fileTreeManager.getEntityPath(doc)
|
|
if !path?
|
|
deferred.reject()
|
|
return deferred.promise
|
|
|
|
# If the root file is folder/main.tex, then synctex sees the
|
|
# path as folder/./main.tex
|
|
rootDocDirname = ide.fileTreeManager.getRootDocDirname()
|
|
if rootDocDirname? and rootDocDirname != ""
|
|
path = path.replace(RegExp("^#{rootDocDirname}"), "#{rootDocDirname}/.")
|
|
|
|
{row, column} = cursorPosition
|
|
|
|
$http({
|
|
url: "/project/#{ide.project_id}/sync/code",
|
|
method: "GET",
|
|
params: {
|
|
file: path
|
|
line: row + 1
|
|
column: column
|
|
clsiserverid:ide.clsiServerId
|
|
}
|
|
})
|
|
.then (response) ->
|
|
{ data } = response
|
|
deferred.resolve(data.pdf or [])
|
|
.catch (response) ->
|
|
error = response.data
|
|
deferred.reject(error)
|
|
|
|
return deferred.promise
|
|
|
|
syncToCode: (position, options = {}) ->
|
|
deferred = $q.defer()
|
|
if !position?
|
|
deferred.reject()
|
|
return deferred.promise
|
|
|
|
# FIXME: this actually works better if it's halfway across the
|
|
# page (or the visible part of the page). Synctex doesn't
|
|
# always find the right place in the file when the point is at
|
|
# the edge of the page, it sometimes returns the start of the
|
|
# next paragraph instead.
|
|
h = position.offset.left
|
|
|
|
# Compute the vertical position to pass to synctex, which
|
|
# works with coordinates increasing from the top of the page
|
|
# down. This matches the browser's DOM coordinate of the
|
|
# click point, but the pdf position is measured from the
|
|
# bottom of the page so we need to invert it.
|
|
if options.fromPdfPosition and position.pageSize?.height?
|
|
v = (position.pageSize.height - position.offset.top) or 0 # measure from pdf point (inverted)
|
|
else
|
|
v = position.offset.top or 0 # measure from html click position
|
|
|
|
# It's not clear exactly where we should sync to if it wasn't directly
|
|
# clicked on, but a little bit down from the very top seems best.
|
|
if options.includeVisualOffset
|
|
v += 72 # use the same value as in pdfViewer highlighting visual offset
|
|
|
|
$http({
|
|
url: "/project/#{ide.project_id}/sync/pdf",
|
|
method: "GET",
|
|
params: {
|
|
page: position.page + 1
|
|
h: h.toFixed(2)
|
|
v: v.toFixed(2)
|
|
clsiserverid:ide.clsiServerId
|
|
}
|
|
})
|
|
.then (response) ->
|
|
{ data } = response
|
|
if data.code? and data.code.length > 0
|
|
doc = ide.fileTreeManager.findEntityByPath(data.code[0].file)
|
|
return if !doc?
|
|
deferred.resolve({doc: doc, line: data.code[0].line})
|
|
.catch (response) ->
|
|
error = response.data
|
|
deferred.reject(error)
|
|
|
|
return deferred.promise
|
|
|
|
return synctex
|
|
]
|
|
|
|
App.controller "PdfSynctexController", ["$scope", "synctex", "ide", ($scope, synctex, ide) ->
|
|
@cursorPosition = null
|
|
ide.$scope.$on "cursor:editor:update", (event, @cursorPosition) =>
|
|
|
|
$scope.syncToPdf = () =>
|
|
return if !@cursorPosition?
|
|
synctex
|
|
.syncToPdf(@cursorPosition)
|
|
.then (highlights) ->
|
|
$scope.pdf.highlights = highlights
|
|
|
|
$scope.syncToCode = () ->
|
|
synctex
|
|
.syncToCode($scope.pdf.position, includeVisualOffset: true, fromPdfPosition: true)
|
|
.then (data) ->
|
|
{doc, line} = data
|
|
ide.editorManager.openDoc(doc, gotoLine: line)
|
|
]
|
|
|
|
App.controller "PdfLogEntryController", ["$scope", "ide", "event_tracking", ($scope, ide, event_tracking) ->
|
|
$scope.openInEditor = (entry) ->
|
|
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
|
|
if entry.column?
|
|
column = entry.column
|
|
ide.editorManager.openDoc(entity, gotoLine: line, gotoColumn: column)
|
|
]
|
|
|
|
App.controller 'ClearCacheModalController', ["$scope", "$modalInstance", ($scope, $modalInstance) ->
|
|
$scope.state =
|
|
inflight: false
|
|
|
|
$scope.clear = () ->
|
|
$scope.state.inflight = true
|
|
$scope
|
|
.clearCache()
|
|
.then () ->
|
|
$scope.state.inflight = false
|
|
$modalInstance.close()
|
|
|
|
$scope.cancel = () ->
|
|
$modalInstance.dismiss('cancel')
|
|
]
|