From 4276f1544df07a91f579812baa16770f38c60db6 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 6 Jul 2017 11:56:20 +0100 Subject: [PATCH 01/26] Update ace latex worker to include context reporting --- .../web/public/js/ace-1.2.5/mode-latex.js | 2 +- .../web/public/js/ace-1.2.5/worker-latex.js | 480 +++++++++++------- 2 files changed, 291 insertions(+), 191 deletions(-) diff --git a/services/web/public/js/ace-1.2.5/mode-latex.js b/services/web/public/js/ace-1.2.5/mode-latex.js index 8e7bbe4802..7c5f426713 100644 --- a/services/web/public/js/ace-1.2.5/mode-latex.js +++ b/services/web/public/js/ace-1.2.5/mode-latex.js @@ -330,7 +330,7 @@ var createLatexWorker = function (session) { }; worker.on("lint", function(results) { if(docChangePending) { docChangePending = false; }; - hints = results.data; + hints = results.data.errors; if (hints.length > 100) { hints = hints.slice(0, 100); // limit to 100 errors }; diff --git a/services/web/public/js/ace-1.2.5/worker-latex.js b/services/web/public/js/ace-1.2.5/worker-latex.js index ccd7e2da9b..dfb4e69e56 100644 --- a/services/web/public/js/ace-1.2.5/worker-latex.js +++ b/services/web/public/js/ace-1.2.5/worker-latex.js @@ -1419,6 +1419,33 @@ var LatexWorker = exports.LatexWorker = function(sender) { oop.inherits(LatexWorker, Mirror); +(function() { + var disabled = false; + this.onUpdate = function() { + if (disabled) { return ; }; + + var value = this.doc.getValue(); + var errors = []; + var contexts = []; + try { + if (value) { + var result = Parse(value); + errors = result.errors; + contexts = result.contexts; + } + } catch (e) { + console.log(e); + disabled = true; + errors = []; + } + this.sender.emit("lint", { + errors: errors, + contexts: contexts + }); + }; + +}).call(LatexWorker.prototype); + var Tokenise = function (text) { var Tokens = []; var Comments = []; @@ -1503,22 +1530,8 @@ var Tokenise = function (text) { } idx = SPECIAL.lastIndex = nextSpecialPos; } - } else if (code === "{") { // open group - Tokens.push([lineNumber, code, pos]); - } else if (code === "}") { // close group - Tokens.push([lineNumber, code, pos]); - } else if (code === "$") { // math mode - Tokens.push([lineNumber, code, pos]); - } else if (code === "&") { // tabalign - Tokens.push([lineNumber, code, pos]); - } else if (code === "#") { // macro parameter - Tokens.push([lineNumber, code, pos]); - } else if (code === "^") { // superscript - Tokens.push([lineNumber, code, pos]); - } else if (code === "_") { // subscript - Tokens.push([lineNumber, code, pos]); - } else if (code === "~") { // active character (space) - Tokens.push([lineNumber, code, pos]); + } else if (["{", "}", "$", "&", "#", "^", "_", "~"].indexOf(code) > -1) { // special characters + Tokens.push([lineNumber, code, pos, pos+1]); } else { throw "unrecognised character " + code; } @@ -1539,15 +1552,15 @@ var read1arg = function (TokeniseResult, k, options) { }; var open = Tokens[k+1]; - var env = Tokens[k+2]; + var delimiter = Tokens[k+2]; var close = Tokens[k+3]; - var envName; + var delimiterName; if(open && open[1] === "\\") { - envName = open[4]; // array element 4 is command sequence + delimiterName = open[4]; // array element 4 is command sequence return k + 1; - } else if(open && open[1] === "{" && env && env[1] === "\\" && close && close[1] === "}") { - envName = env[4]; // NOTE: if we were actually using this, keep track of * above + } else if(open && open[1] === "{" && delimiter && delimiter[1] === "\\" && close && close[1] === "}") { + delimiterName = delimiter[4]; // NOTE: if we were actually using this, keep track of * above return k + 3; // array element 4 is command sequence } else { return null; @@ -1579,21 +1592,21 @@ var read1name = function (TokeniseResult, k) { var text = TokeniseResult.text; var open = Tokens[k+1]; - var env = Tokens[k+2]; + var delimiter = Tokens[k+2]; var close = Tokens[k+3]; - if(open && open[1] === "{" && env && env[1] === "Text" && close && close[1] === "}") { - var envName = text.substring(env[2], env[3]); + if(open && open[1] === "{" && delimiter && delimiter[1] === "Text" && close && close[1] === "}") { + var delimiterName = text.substring(delimiter[2], delimiter[3]); return k + 3; - } else if (open && open[1] === "{" && env && env[1] === "Text") { - envName = ""; + } else if (open && open[1] === "{" && delimiter && delimiter[1] === "Text") { + delimiterName = ""; for (var j = k + 2, tok; (tok = Tokens[j]); j++) { if (tok[1] === "Text") { var str = text.substring(tok[2], tok[3]); if (!str.match(/^\S*$/)) { break; } - envName = envName + str; + delimiterName = delimiterName + str; } else if (tok[1] === "_") { - envName = envName + "_"; + delimiterName = delimiterName + "_"; } else { break; } @@ -1788,7 +1801,7 @@ var InterpretTokens = function (TokeniseResult, ErrorReporter) { var TokenErrorFromTo = ErrorReporter.TokenErrorFromTo; var TokenError = ErrorReporter.TokenError; - var Environments = new EnvHandler(ErrorReporter); + var Environments = new EnvHandler(TokeniseResult, ErrorReporter); var nextGroupMathMode = null; // if the next group should have var nextGroupMathModeStack = [] ; // tracking all nextGroupMathModes @@ -1815,28 +1828,28 @@ var InterpretTokens = function (TokeniseResult, ErrorReporter) { if (type === "\\") { if (seq === "begin" || seq === "end") { var open = Tokens[i+1]; - var env = Tokens[i+2]; + var delimiter = Tokens[i+2]; var close = Tokens[i+3]; - if(open && open[1] === "{" && env && env[1] === "Text" && close && close[1] === "}") { - var envName = text.substring(env[2], env[3]); - Environments.push({command: seq, name: envName, token: token, closeToken: close}); + if(open && open[1] === "{" && delimiter && delimiter[1] === "Text" && close && close[1] === "}") { + var delimiterName = text.substring(delimiter[2], delimiter[3]); + Environments.push({command: seq, name: delimiterName, token: token, closeToken: close}); i = i + 3; // advance past these tokens } else { - if (open && open[1] === "{" && env && env[1] === "Text") { - envName = ""; + if (open && open[1] === "{" && delimiter && delimiter[1] === "Text") { + delimiterName = ""; for (var j = i + 2, tok; (tok = Tokens[j]); j++) { if (tok[1] === "Text") { var str = text.substring(tok[2], tok[3]); if (!str.match(/^\S*$/)) { break; } - envName = envName + str; + delimiterName = delimiterName + str; } else if (tok[1] === "_") { - envName = envName + "_"; + delimiterName = delimiterName + "_"; } else { break; } } if (tok && tok[1] === "}") { - Environments.push({command: seq, name: envName, token: token, closeToken: close}); + Environments.push({command: seq, name: delimiterName, token: token, closeToken: close}); i = j; // advance past these tokens continue; } @@ -1844,8 +1857,8 @@ var InterpretTokens = function (TokeniseResult, ErrorReporter) { var endToken = null; if (open && open[1] === "{") { endToken = open; // we've got a { - if (env && env[1] === "Text") { - endToken = env.slice(); // we've got some text following the { + if (delimiter && delimiter[1] === "Text") { + endToken = delimiter.slice(); // we've got some text following the { start = endToken[2]; end = endToken[3]; for (j = start; j < end; j++) { var char = text[j]; @@ -1978,7 +1991,12 @@ var InterpretTokens = function (TokeniseResult, ErrorReporter) { var nextIsDollar = lookAhead && lookAhead[1] === "$"; currentMathMode = Environments.getMathMode() ; // returns null / $(inline) / $$(display) if (nextIsDollar && (!currentMathMode || currentMathMode.command == "$$")) { - Environments.push({command:"$$", token:token}); + if (currentMathMode && currentMathMode.command == "$$") { + var delimiterToken = lookAhead; + } else { + var delimiterToken = token; + } + Environments.push({command:"$$", token:delimiterToken}); i = i + 1; } else { Environments.push({command:"$", token:token}); @@ -1999,74 +2017,174 @@ var InterpretTokens = function (TokeniseResult, ErrorReporter) { return Environments; }; -var EnvHandler = function (ErrorReporter) { +var DocumentTree = function(TokeniseResult) { + var tree = { + children: [] + }; + var stack = [tree]; + + this.openEnv = function(startDelimiter) { + var currentNode = this.getCurrentNode(); + var newNode = { + startDelimiter: startDelimiter, + children: [] + }; + currentNode.children.push(newNode); + stack.push(newNode); + }; + + this.closeEnv = function(endDelimiter) { + if (stack.length == 1) { + return null + } + var currentNode = stack.pop(); + currentNode.endDelimiter = endDelimiter; + return currentNode.startDelimiter; + }; + + this.getNthPreviousNode = function(n) { + var offset = stack.length - n - 1; + if (offset < 0) + return null; + return stack[offset]; + } + + this.getCurrentNode = function() { + return this.getNthPreviousNode(0); + } + + this.getCurrentDelimiter = function() { + return this.getCurrentNode().startDelimiter; + }; + + this.getPreviousDelimiter = function() { + var node = this.getNthPreviousNode(1); + if (!node) + return null + return node.startDelimiter; + } + + this.getDepth = function() { + return (stack.length - 1) // Root node doesn't count + } + + this.getContexts = function() { + var linePosition = TokeniseResult.linePosition; + + function tokenToRange(token) { + var line = token[0], start = token[2], end = token[3]; + var start_col = start - linePosition[line]; + if (!end) { end = start + 1; } ; + var end_col = end - linePosition[line]; + return { + start: { + row: line, + column: start_col + }, + end: { + row: line, + column: end_col + } + } + }; + + function getContextsFromNode(node) { + if (node.startDelimiter && node.startDelimiter.mathMode) { + var context = { + type: "math", + range: { + start: tokenToRange(node.startDelimiter.token).start + } + }; + if (node.endDelimiter) { + var closeToken = node.endDelimiter.closeToken || node.endDelimiter.token; + context.range.end = tokenToRange(closeToken).end; + }; + return [context]; + } else { + var contexts = []; + for (var i = 0; i < node.children.length; i++) { + var child = node.children[i]; + contexts = contexts.concat(getContextsFromNode(child)); + } + return contexts; + } + }; + + return getContextsFromNode(tree); + } +} + +var EnvHandler = function (TokeniseResult, ErrorReporter) { var ErrorTo = ErrorReporter.EnvErrorTo; var ErrorFromTo = ErrorReporter.EnvErrorFromTo; var ErrorFrom = ErrorReporter.EnvErrorFrom; - var envs = []; + var delimiters = []; - var state = []; + var document = new DocumentTree(TokeniseResult); var documentClosed = null; var inVerbatim = false; var verbatimRanges = []; - - this.Environments = envs; - - this.push = function (newEnv) { - this.setEnvProps(newEnv); - this.checkAndUpdateState(newEnv); - envs.push(newEnv); + + this.getDocument = function() { + return document; }; - this._endVerbatim = function (thisEnv) { - var lastEnv = state.pop(); - if (lastEnv && lastEnv.name === thisEnv.name) { + this.push = function (newDelimiter) { + this.setDelimiterProps(newDelimiter); + this.checkAndUpdateState(newDelimiter); + delimiters.push(newDelimiter); + }; + + this._endVerbatim = function (thisDelimiter) { + var lastDelimiter = document.getCurrentDelimiter(); + if (lastDelimiter && lastDelimiter.name === thisDelimiter.name) { inVerbatim = false; - verbatimRanges.push({start: lastEnv.token[2], end: thisEnv.token[2]}); - } else { - if(lastEnv) { state.push(lastEnv); } ; + document.closeEnv(thisDelimiter); + verbatimRanges.push({start: lastDelimiter.token[2], end: thisDelimiter.token[2]}); } }; var invalidEnvs = []; - this._end = function (thisEnv) { + this._end = function (thisDelimiter) { do { - var lastEnv = state.pop(); + var lastDelimiter = document.getCurrentDelimiter(); var retry = false; var i; - if (closedBy(lastEnv, thisEnv)) { - if (thisEnv.command === "end" && thisEnv.name === "document" && !documentClosed) { - documentClosed = thisEnv; + if (closedBy(lastDelimiter, thisDelimiter)) { + document.closeEnv(thisDelimiter); + if (thisDelimiter.command === "end" && thisDelimiter.name === "document" && !documentClosed) { + documentClosed = thisDelimiter; }; return; - } else if (!lastEnv) { + } else if (!lastDelimiter) { if (documentClosed) { - ErrorFromTo(documentClosed, thisEnv, "\\end{" + documentClosed.name + "} is followed by unexpected content",{errorAtStart: true, type: "info"}); + ErrorFromTo(documentClosed, thisDelimiter, "\\end{" + documentClosed.name + "} is followed by unexpected content",{errorAtStart: true, type: "info"}); } else { - ErrorTo(thisEnv, "unexpected " + getName(thisEnv)); + ErrorTo(thisDelimiter, "unexpected " + getName(thisDelimiter)); } - } else if (invalidEnvs.length > 0 && (i = indexOfClosingEnvInArray(invalidEnvs, thisEnv) > -1)) { + } else if (invalidEnvs.length > 0 && (i = indexOfClosingEnvInArray(invalidEnvs, thisDelimiter) > -1)) { invalidEnvs.splice(i, 1); - if (lastEnv) { state.push(lastEnv); } ; return; } else { - var status = reportError(lastEnv, thisEnv); - if (envPrecedence(lastEnv) < envPrecedence(thisEnv)) { - invalidEnvs.push(lastEnv); + var status = reportError(lastDelimiter, thisDelimiter); + if (delimiterPrecedence(lastDelimiter) < delimiterPrecedence(thisDelimiter)) { + document.closeEnv(); + invalidEnvs.push(lastDelimiter); retry = true; } else { - var prevLastEnv = state.pop(); - if(prevLastEnv) { - if (thisEnv.name === prevLastEnv.name) { + var prevDelimiter = document.getPreviousDelimiter(); + if(prevDelimiter) { + if (thisDelimiter.name === prevDelimiter.name) { + document.closeEnv() // Close current env + document.closeEnv(thisDelimiter) // Close previous env return; - } else { - state.push(prevLastEnv); } } - invalidEnvs.push(lastEnv); + invalidEnvs.push(lastDelimiter); } } @@ -2082,28 +2200,28 @@ var EnvHandler = function (ErrorReporter) { "$$": "$$" }; - var closedBy = function (lastEnv, thisEnv) { - if (!lastEnv) { + var closedBy = function (lastDelimiter, thisDelimiter) { + if (!lastDelimiter) { return false ; - } else if (thisEnv.command === "end") { - return lastEnv.command === "begin" && lastEnv.name === thisEnv.name; - } else if (thisEnv.command === CLOSING_DELIMITER[lastEnv.command]) { + } else if (thisDelimiter.command === "end") { + return lastDelimiter.command === "begin" && lastDelimiter.name === thisDelimiter.name; + } else if (thisDelimiter.command === CLOSING_DELIMITER[lastDelimiter.command]) { return true; } else { return false; } }; - var indexOfClosingEnvInArray = function (envs, thisEnv) { - for (var i = 0, n = envs.length; i < n ; i++) { - if (closedBy(envs[i], thisEnv)) { + var indexOfClosingEnvInArray = function (delimiters, thisDelimiter) { + for (var i = 0, n = delimiters.length; i < n ; i++) { + if (closedBy(delimiters[i], thisDelimiter)) { return i; } } return -1; }; - var envPrecedence = function (env) { + var delimiterPrecedence = function (delimiter) { var openScore = { "{" : 1, "left" : 2, @@ -2118,14 +2236,14 @@ var EnvHandler = function (ErrorReporter) { "$$" : 5, "end": 4 }; - if (env.command) { - return openScore[env.command] || closeScore[env.command]; + if (delimiter.command) { + return openScore[delimiter.command] || closeScore[delimiter.command]; } else { return 0; } }; - var getName = function(env) { + var getName = function(delimiter) { var description = { "{" : "open group {", "}" : "close group }", @@ -2138,12 +2256,12 @@ var EnvHandler = function (ErrorReporter) { "left" : "\\left", "right" : "\\right" }; - if (env.command === "begin" || env.command === "end") { - return "\\" + env.command + "{" + env.name + "}"; - } else if (env.command in description) { - return description[env.command]; + if (delimiter.command === "begin" || delimiter.command === "end") { + return "\\" + delimiter.command + "{" + delimiter.name + "}"; + } else if (delimiter.command in description) { + return description[delimiter.command]; } else { - return env.command; + return delimiter.command; } }; @@ -2151,81 +2269,81 @@ var EnvHandler = function (ErrorReporter) { var UNCLOSED_GROUP = 2; var UNCLOSED_ENV = 3; - var reportError = function(lastEnv, thisEnv) { - if (!lastEnv) { // unexpected close, nothing was open! + var reportError = function(lastDelimiter, thisDelimiter) { + if (!lastDelimiter) { // unexpected close, nothing was open! if (documentClosed) { - ErrorFromTo(documentClosed, thisEnv, "\\end{" + documentClosed.name + "} is followed by unexpected end group }",{errorAtStart: true, type: "info"}); + ErrorFromTo(documentClosed, thisDelimiter, "\\end{" + documentClosed.name + "} is followed by unexpected end group }",{errorAtStart: true, type: "info"}); } else { - ErrorTo(thisEnv, "unexpected " + getName(thisEnv)); + ErrorTo(thisDelimiter, "unexpected " + getName(thisDelimiter)); }; return EXTRA_CLOSE; - } else if (lastEnv.command === "{" && thisEnv.command === "end") { - ErrorFromTo(lastEnv, thisEnv, "unclosed " + getName(lastEnv) + " found at " + getName(thisEnv), + } else if (lastDelimiter.command === "{" && thisDelimiter.command === "end") { + ErrorFromTo(lastDelimiter, thisDelimiter, "unclosed " + getName(lastDelimiter) + " found at " + getName(thisDelimiter), {suppressIfEditing:true, errorAtStart: true, type:"warning"}); return UNCLOSED_GROUP; } else { - var pLast = envPrecedence(lastEnv); - var pThis = envPrecedence(thisEnv); + var pLast = delimiterPrecedence(lastDelimiter); + var pThis = delimiterPrecedence(thisDelimiter); if (pThis > pLast) { - ErrorFromTo(lastEnv, thisEnv, "unclosed " + getName(lastEnv) + " found at " + getName(thisEnv), + ErrorFromTo(lastDelimiter, thisDelimiter, "unclosed " + getName(lastDelimiter) + " found at " + getName(thisDelimiter), {suppressIfEditing:true, errorAtStart: true}); } else { - ErrorFromTo(lastEnv, thisEnv, "unexpected " + getName(thisEnv) + " after " + getName(lastEnv)); + ErrorFromTo(lastDelimiter, thisDelimiter, "unexpected " + getName(thisDelimiter) + " after " + getName(lastDelimiter)); } return UNCLOSED_ENV; }; }; - this._beginMathMode = function (thisEnv) { + this._beginMathMode = function (thisDelimiter) { var currentMathMode = this.getMathMode(); // undefined, null, $, $$, name of mathmode env if (currentMathMode) { - ErrorFrom(thisEnv, getName(thisEnv) + " used inside existing math mode " + getName(currentMathMode), + ErrorFrom(thisDelimiter, getName(thisDelimiter) + " used inside existing math mode " + getName(currentMathMode), {suppressIfEditing:true, errorAtStart: true, mathMode:true}); }; - thisEnv.mathMode = thisEnv; - state.push(thisEnv); + thisDelimiter.mathMode = thisDelimiter; + document.openEnv(thisDelimiter); }; - this._toggleMathMode = function (thisEnv) { - var lastEnv = state.pop(); - if (closedBy(lastEnv, thisEnv)) { + this._toggleMathMode = function (thisDelimiter) { + var lastDelimiter = document.getCurrentDelimiter(); + if (closedBy(lastDelimiter, thisDelimiter)) { + document.closeEnv(thisDelimiter) return; } else { - if (lastEnv) {state.push(lastEnv);} - if (lastEnv && lastEnv.mathMode) { - this._end(thisEnv); + if (lastDelimiter && lastDelimiter.mathMode) { + this._end(thisDelimiter); } else { - thisEnv.mathMode = thisEnv; - state.push(thisEnv); + thisDelimiter.mathMode = thisDelimiter; + document.openEnv(thisDelimiter); } }; }; this.getMathMode = function () { - var n = state.length; - if (n > 0) { - return state[n-1].mathMode; + var currentDelimiter = document.getCurrentDelimiter(); + if (currentDelimiter) { + return currentDelimiter.mathMode; } else { return null; } }; this.insideGroup = function () { - var n = state.length; - if (n > 0) { - return (state[n-1].command === "{"); + var currentDelimiter = document.getCurrentDelimiter(); + if (currentDelimiter) { + return (currentDelimiter.command === "{"); } else { return null; } }; var resetMathMode = function () { - var n = state.length; - if (n > 0) { - var lastMathMode = state[n-1].mathMode; + var currentDelimiter = document.getCurrentDelimiter(); + if (currentDelimiter) { + var lastMathMode = currentDelimiter.mathMode; do { - var lastEnv = state.pop(); - } while (lastEnv && lastEnv !== lastMathMode); + var lastDelimiter = document.closeEnv(); + } while (lastDelimiter && lastDelimiter !== lastMathMode); } else { return; } @@ -2233,42 +2351,42 @@ var EnvHandler = function (ErrorReporter) { this.resetMathMode = resetMathMode; - var getNewMathMode = function (currentMathMode, thisEnv) { + var getNewMathMode = function (currentMathMode, thisDelimiter) { var newMathMode = null; - if (thisEnv.command === "{") { - if (thisEnv.mathMode !== null) { - newMathMode = thisEnv.mathMode; + if (thisDelimiter.command === "{") { + if (thisDelimiter.mathMode !== null) { + newMathMode = thisDelimiter.mathMode; } else { newMathMode = currentMathMode; } - } else if (thisEnv.command === "left") { + } else if (thisDelimiter.command === "left") { if (currentMathMode === null) { - ErrorFrom(thisEnv, "\\left can only be used in math mode", {mathMode: true}); + ErrorFrom(thisDelimiter, "\\left can only be used in math mode", {mathMode: true}); }; newMathMode = currentMathMode; - } else if (thisEnv.command === "begin") { - var name = thisEnv.name; + } else if (thisDelimiter.command === "begin") { + var name = thisDelimiter.name; if (name) { if (name.match(/^(document|figure|center|enumerate|itemize|table|abstract|proof|lemma|theorem|definition|proposition|corollary|remark|notation|thebibliography)$/)) { if (currentMathMode) { - ErrorFromTo(currentMathMode, thisEnv, thisEnv.name + " used inside " + getName(currentMathMode), + ErrorFromTo(currentMathMode, thisDelimiter, thisDelimiter.name + " used inside " + getName(currentMathMode), {suppressIfEditing:true, errorAtStart: true, mathMode: true}); resetMathMode(); }; newMathMode = null; } else if (name.match(/^(array|gathered|split|aligned|alignedat)\*?$/)) { if (currentMathMode === null) { - ErrorFrom(thisEnv, thisEnv.name + " not inside math mode", {mathMode: true}); + ErrorFrom(thisDelimiter, thisDelimiter.name + " not inside math mode", {mathMode: true}); }; newMathMode = currentMathMode; } else if (name.match(/^(math|displaymath|equation|eqnarray|multline|align|gather|flalign|alignat)\*?$/)) { if (currentMathMode) { - ErrorFromTo(currentMathMode, thisEnv, thisEnv.name + " used inside " + getName(currentMathMode), + ErrorFromTo(currentMathMode, thisDelimiter, thisDelimiter.name + " used inside " + getName(currentMathMode), {suppressIfEditing:true, errorAtStart: true, mathMode: true}); resetMathMode(); }; - newMathMode = thisEnv; + newMathMode = thisDelimiter; } else { newMathMode = undefined; // undefined means we don't know if we are in math mode or not } @@ -2277,41 +2395,41 @@ var EnvHandler = function (ErrorReporter) { return newMathMode; }; - this.checkAndUpdateState = function (thisEnv) { + this.checkAndUpdateState = function (thisDelimiter) { if (inVerbatim) { - if (thisEnv.command === "end") { - this._endVerbatim(thisEnv); + if (thisDelimiter.command === "end") { + this._endVerbatim(thisDelimiter); } else { return; // ignore anything in verbatim environments } - } else if(thisEnv.command === "begin" || thisEnv.command === "{" || thisEnv.command === "left") { - if (thisEnv.verbatim) {inVerbatim = true;}; + } else if(thisDelimiter.command === "begin" || thisDelimiter.command === "{" || thisDelimiter.command === "left") { + if (thisDelimiter.verbatim) {inVerbatim = true;}; var currentMathMode = this.getMathMode(); // undefined, null, $, $$, name of mathmode env - var newMathMode = getNewMathMode(currentMathMode, thisEnv); - thisEnv.mathMode = newMathMode; - state.push(thisEnv); - } else if (thisEnv.command === "end") { - this._end(thisEnv); - } else if (thisEnv.command === "(" || thisEnv.command === "[") { - this._beginMathMode(thisEnv); - } else if (thisEnv.command === ")" || thisEnv.command === "]") { - this._end(thisEnv); - } else if (thisEnv.command === "}") { - this._end(thisEnv); - } else if (thisEnv.command === "right") { - this._end(thisEnv); - } else if (thisEnv.command === "$" || thisEnv.command === "$$") { - this._toggleMathMode(thisEnv); + var newMathMode = getNewMathMode(currentMathMode, thisDelimiter); + thisDelimiter.mathMode = newMathMode; + document.openEnv(thisDelimiter); + } else if (thisDelimiter.command === "end") { + this._end(thisDelimiter); + } else if (thisDelimiter.command === "(" || thisDelimiter.command === "[") { + this._beginMathMode(thisDelimiter); + } else if (thisDelimiter.command === ")" || thisDelimiter.command === "]") { + this._end(thisDelimiter); + } else if (thisDelimiter.command === "}") { + this._end(thisDelimiter); + } else if (thisDelimiter.command === "right") { + this._end(thisDelimiter); + } else if (thisDelimiter.command === "$" || thisDelimiter.command === "$$") { + this._toggleMathMode(thisDelimiter); } }; this.close = function () { - while (state.length > 0) { - var thisEnv = state.pop(); - if (thisEnv.command === "{") { - ErrorFrom(thisEnv, "unclosed group {", {type:"warning"}); + while (document.getDepth() > 0) { + var thisDelimiter = document.closeEnv(); + if (thisDelimiter.command === "{") { + ErrorFrom(thisDelimiter, "unclosed group {", {type:"warning"}); } else { - ErrorFrom(thisEnv, "unclosed " + getName(thisEnv)); + ErrorFrom(thisDelimiter, "unclosed " + getName(thisDelimiter)); } } var vlen = verbatimRanges.length; @@ -2331,10 +2449,10 @@ var EnvHandler = function (ErrorReporter) { } }; - this.setEnvProps = function (env) { - var name = env.name ; + this.setDelimiterProps = function (delimiter) { + var name = delimiter.name ; if (name && name.match(/^(verbatim|boxedverbatim|lstlisting|minted|Verbatim)$/)) { - env.verbatim = true; + delimiter.verbatim = true; } }; }; @@ -2458,9 +2576,9 @@ var ErrorReporter = function (TokeniseResult) { errors.push(err); }; - this.EnvErrorFrom = function (env, message, options) { + this.EnvErrorFrom = function (delimiter, message, options) { if(!options) { options = {} ; }; - var token = env.token; + var token = delimiter.token; var line = token[0], type = token[1], start = token[2], end = token[3]; var start_col = start - linePosition[line]; var end_col = Infinity; @@ -2481,29 +2599,11 @@ var Parse = function (text) { var Reporter = new ErrorReporter(TokeniseResult); var Environments = InterpretTokens(TokeniseResult, Reporter); Environments.close(); - return Reporter.getErrors(); + return { + errors: Reporter.getErrors(), + contexts: Environments.getDocument().getContexts() + } }; - -(function() { - var disabled = false; - - this.onUpdate = function() { - if (disabled) { return ; }; - - var value = this.doc.getValue(); - var errors = []; - try { - if (value) - errors = Parse(value); - } catch (e) { - disabled = true; - errors = []; - } - this.sender.emit("lint", errors); - }; - -}).call(LatexWorker.prototype); - }); ace.define("ace/lib/es5-shim",["require","exports","module"], function(require, exports, module) { From 01d709dff55bc0eafea3aa9aa5e73a0a2d3ff000 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 6 Jul 2017 11:59:44 +0100 Subject: [PATCH 02/26] Remove latex beta mode --- .../ide/editor/directives/aceEditor.coffee | 4 - .../public/js/ace-1.2.5/mode-latex_beta.js | 378 -- .../public/js/ace-1.2.5/worker-latex_beta.js | 3207 ----------------- 3 files changed, 3589 deletions(-) delete mode 100644 services/web/public/js/ace-1.2.5/mode-latex_beta.js delete mode 100644 services/web/public/js/ace-1.2.5/worker-latex_beta.js diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index 6c2b81edc4..8d77ea4a9b 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -339,10 +339,6 @@ define [ catch mode = "ace/mode/plain_text" - # Give beta users the next release of the syntax checker - if mode is "ace/mode/latex" and window.user?.betaProgram - mode = "ace/mode/latex_beta" - # create our new session session = new EditSession(lines, mode) diff --git a/services/web/public/js/ace-1.2.5/mode-latex_beta.js b/services/web/public/js/ace-1.2.5/mode-latex_beta.js deleted file mode 100644 index 1a98491951..0000000000 --- a/services/web/public/js/ace-1.2.5/mode-latex_beta.js +++ /dev/null @@ -1,378 +0,0 @@ -ace.define("ace/mode/latex_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(require, exports, module) { -"use strict"; - -var oop = require("../lib/oop"); -var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; - -var LatexHighlightRules = function() { - - this.$rules = { - "start" : [{ - token : "comment", - regex : "%.*$" - }, { - token : ["keyword", "lparen", "variable.parameter", "rparen", "lparen", "storage.type", "rparen"], - regex : "(\\\\(?:documentclass|usepackage|input))(?:(\\[)([^\\]]*)(\\]))?({)([^}]*)(})" - }, { - token : ["keyword","lparen", "variable.parameter", "rparen"], - regex : "(\\\\(?:label|v?ref|cite(?:[^{]*)))(?:({)([^}]*)(}))?" - }, { - token : ["storage.type", "lparen", "variable.parameter", "rparen"], - regex : "(\\\\(?:begin|end))({)(\\w*)(})" - }, { - token : "storage.type", - regex : "\\\\[a-zA-Z]+" - }, { - token : "lparen", - regex : "[[({]" - }, { - token : "rparen", - regex : "[\\])}]" - }, { - token : "constant.character.escape", - regex : "\\\\[^a-zA-Z]?" - }, { - token : "string", - regex : "\\${1,2}", - next : "equation" - }], - "equation" : [{ - token : "comment", - regex : "%.*$" - }, { - token : "string", - regex : "\\${1,2}", - next : "start" - }, { - token : "constant.character.escape", - regex : "\\\\(?:[^a-zA-Z]|[a-zA-Z]+)" - }, { - token : "error", - regex : "^\\s*$", - next : "start" - }, { - defaultToken : "string" - }] - - }; -}; -oop.inherits(LatexHighlightRules, TextHighlightRules); - -exports.LatexHighlightRules = LatexHighlightRules; - -}); - -ace.define("ace/mode/folding/latex",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range","ace/token_iterator"], function(require, exports, module) { -"use strict"; - -var oop = require("../../lib/oop"); -var BaseFoldMode = require("./fold_mode").FoldMode; -var Range = require("../../range").Range; -var TokenIterator = require("../../token_iterator").TokenIterator; - -var FoldMode = exports.FoldMode = function() {}; - -oop.inherits(FoldMode, BaseFoldMode); - -(function() { - - this.foldingStartMarker = /^\s*\\(begin)|(section|subsection|paragraph)\b|{\s*$/; - this.foldingStopMarker = /^\s*\\(end)\b|^\s*}/; - - this.getFoldWidgetRange = function(session, foldStyle, row) { - var line = session.doc.getLine(row); - var match = this.foldingStartMarker.exec(line); - if (match) { - if (match[1]) - return this.latexBlock(session, row, match[0].length - 1); - if (match[2]) - return this.latexSection(session, row, match[0].length - 1); - - return this.openingBracketBlock(session, "{", row, match.index); - } - - var match = this.foldingStopMarker.exec(line); - if (match) { - if (match[1]) - return this.latexBlock(session, row, match[0].length - 1); - - return this.closingBracketBlock(session, "}", row, match.index + match[0].length); - } - }; - - this.latexBlock = function(session, row, column) { - var keywords = { - "\\begin": 1, - "\\end": -1 - }; - - var stream = new TokenIterator(session, row, column); - var token = stream.getCurrentToken(); - if (!token || !(token.type == "storage.type" || token.type == "constant.character.escape")) - return; - - var val = token.value; - var dir = keywords[val]; - - var getType = function() { - var token = stream.stepForward(); - var type = token.type == "lparen" ?stream.stepForward().value : ""; - if (dir === -1) { - stream.stepBackward(); - if (type) - stream.stepBackward(); - } - return type; - }; - var stack = [getType()]; - var startColumn = dir === -1 ? stream.getCurrentTokenColumn() : session.getLine(row).length; - var startRow = row; - - stream.step = dir === -1 ? stream.stepBackward : stream.stepForward; - while(token = stream.step()) { - if (!token || !(token.type == "storage.type" || token.type == "constant.character.escape")) - continue; - var level = keywords[token.value]; - if (!level) - continue; - var type = getType(); - if (level === dir) - stack.unshift(type); - else if (stack.shift() !== type || !stack.length) - break; - } - - if (stack.length) - return; - - var row = stream.getCurrentTokenRow(); - if (dir === -1) - return new Range(row, session.getLine(row).length, startRow, startColumn); - stream.stepBackward(); - return new Range(startRow, startColumn, row, stream.getCurrentTokenColumn()); - }; - - this.latexSection = function(session, row, column) { - var keywords = ["\\subsection", "\\section", "\\begin", "\\end", "\\paragraph"]; - - var stream = new TokenIterator(session, row, column); - var token = stream.getCurrentToken(); - if (!token || token.type != "storage.type") - return; - - var startLevel = keywords.indexOf(token.value); - var stackDepth = 0 - var endRow = row; - - while(token = stream.stepForward()) { - if (token.type !== "storage.type") - continue; - var level = keywords.indexOf(token.value); - - if (level >= 2) { - if (!stackDepth) - endRow = stream.getCurrentTokenRow() - 1; - stackDepth += level == 2 ? 1 : - 1; - if (stackDepth < 0) - break - } else if (level >= startLevel) - break; - } - - if (!stackDepth) - endRow = stream.getCurrentTokenRow() - 1; - - while (endRow > row && !/\S/.test(session.getLine(endRow))) - endRow--; - - return new Range( - row, session.getLine(row).length, - endRow, session.getLine(endRow).length - ); - }; - -}).call(FoldMode.prototype); - -}); - -ace.define("ace/mode/latex_beta",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/latex_highlight_rules","ace/mode/folding/latex","ace/range","ace/worker/worker_client"], function(require, exports, module) { -"use strict"; - -var oop = require("../lib/oop"); -var TextMode = require("./text").Mode; -var LatexHighlightRules = require("./latex_highlight_rules").LatexHighlightRules; -var LatexFoldMode = require("./folding/latex").FoldMode; -var Range = require("../range").Range; -var WorkerClient = require("ace/worker/worker_client").WorkerClient; - -var createLatexWorker = function (session) { - var doc = session.getDocument(); - var selection = session.getSelection(); - var cursorAnchor = selection.lead; - - var savedRange = {}; - var suppressions = []; - var hints = []; - var changeHandler = null; - var docChangePending = false; - var firstPass = true; - - var worker = new WorkerClient(["ace"], "ace/mode/latex_beta_worker", "LatexWorker"); - worker.attachToDocument(doc); - var docChangeHandler = doc.on("change", function () { - docChangePending = true; - if(changeHandler) { - clearTimeout(changeHandler); - changeHandler = null; - } - }); - - var cursorHandler = selection.on("changeCursor", function () { - if (docChangePending) { return; } ; - changeHandler = setTimeout(function () { - updateMarkers({cursorMoveOnly:true}); - suppressions = []; - changeHandler = null; - }, 100); - }); - - var updateMarkers = function (options) { - if (!options) { options = {};}; - var cursorMoveOnly = options.cursorMoveOnly; - var annotations = []; - var newRange = {}; - var cursor = selection.getCursor(); - var maxRow = session.getLength() - 1; - var maxCol = (maxRow > 0) ? session.getLine(maxRow).length : 0; - var cursorAtEndOfDocument = (cursor.row == maxRow) && (cursor.column === maxCol); - - suppressions = []; - - for (var i = 0, len = hints.length; i 0) { - var originalAnnotations = session.getAnnotations(); - session.setAnnotations(originalAnnotations.concat(annotations)); - }; - firstPass = false; - } else { - session.setAnnotations(annotations); - } - }; - - }; - worker.on("lint", function(results) { - if(docChangePending) { docChangePending = false; }; - hints = results.data; - if (hints.length > 100) { - hints = hints.slice(0, 100); // limit to 100 errors - }; - updateMarkers(); - }); - worker.on("terminate", function() { - if(changeHandler) { - clearTimeout(changeHandler); - changeHandler = null; - } - doc.off("change", docChangeHandler); - selection.off("changeCursor", cursorHandler); - for (var key in savedRange) { - var range = savedRange[key]; - if (range.start !== cursorAnchor) { range.start.detach(); } - if (range.end !== cursorAnchor) { range.end.detach(); } - session.removeMarker(range.id); - } - savedRange = {}; - hints = []; - suppressions = []; - session.clearAnnotations(); - }); - - return worker; -}; - -var Mode = function() { - this.HighlightRules = LatexHighlightRules; - this.foldingRules = new LatexFoldMode(); - this.createWorker = createLatexWorker; -}; -oop.inherits(Mode, TextMode); - -(function() { - this.type = "text"; - - this.lineCommentStart = "%"; - - this.$id = "ace/mode/latex_beta"; -}).call(Mode.prototype); - -exports.Mode = Mode; - -}); diff --git a/services/web/public/js/ace-1.2.5/worker-latex_beta.js b/services/web/public/js/ace-1.2.5/worker-latex_beta.js deleted file mode 100644 index 720c3e5009..0000000000 --- a/services/web/public/js/ace-1.2.5/worker-latex_beta.js +++ /dev/null @@ -1,3207 +0,0 @@ -"no use strict"; -;(function(window) { -if (typeof window.window != "undefined" && window.document) - return; -if (window.require && window.define) - return; - -if (!window.console) { - window.console = function() { - var msgs = Array.prototype.slice.call(arguments, 0); - postMessage({type: "log", data: msgs}); - }; - window.console.error = - window.console.warn = - window.console.log = - window.console.trace = window.console; -} -window.window = window; -window.ace = window; - -window.onerror = function(message, file, line, col, err) { - postMessage({type: "error", data: { - message: message, - data: err.data, - file: file, - line: line, - col: col, - stack: err.stack - }}); -}; - -window.normalizeModule = function(parentId, moduleName) { - // normalize plugin requires - if (moduleName.indexOf("!") !== -1) { - var chunks = moduleName.split("!"); - return window.normalizeModule(parentId, chunks[0]) + "!" + window.normalizeModule(parentId, chunks[1]); - } - // normalize relative requires - if (moduleName.charAt(0) == ".") { - var base = parentId.split("/").slice(0, -1).join("/"); - moduleName = (base ? base + "/" : "") + moduleName; - - while (moduleName.indexOf(".") !== -1 && previous != moduleName) { - var previous = moduleName; - moduleName = moduleName.replace(/^\.\//, "").replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, ""); - } - } - - return moduleName; -}; - -window.require = function require(parentId, id) { - if (!id) { - id = parentId; - parentId = null; - } - if (!id.charAt) - throw new Error("worker.js require() accepts only (parentId, id) as arguments"); - - id = window.normalizeModule(parentId, id); - - var module = window.require.modules[id]; - if (module) { - if (!module.initialized) { - module.initialized = true; - module.exports = module.factory().exports; - } - return module.exports; - } - - if (!window.require.tlns) - return console.log("unable to load " + id); - - var path = resolveModuleId(id, window.require.tlns); - if (path.slice(-3) != ".js") path += ".js"; - - window.require.id = id; - window.require.modules[id] = {}; // prevent infinite loop on broken modules - importScripts(path); - return window.require(parentId, id); -}; -function resolveModuleId(id, paths) { - var testPath = id, tail = ""; - while (testPath) { - var alias = paths[testPath]; - if (typeof alias == "string") { - return alias + tail; - } else if (alias) { - return alias.location.replace(/\/*$/, "/") + (tail || alias.main || alias.name); - } else if (alias === false) { - return ""; - } - var i = testPath.lastIndexOf("/"); - if (i === -1) break; - tail = testPath.substr(i) + tail; - testPath = testPath.slice(0, i); - } - return id; -} -window.require.modules = {}; -window.require.tlns = {}; - -window.define = function(id, deps, factory) { - if (arguments.length == 2) { - factory = deps; - if (typeof id != "string") { - deps = id; - id = window.require.id; - } - } else if (arguments.length == 1) { - factory = id; - deps = []; - id = window.require.id; - } - - if (typeof factory != "function") { - window.require.modules[id] = { - exports: factory, - initialized: true - }; - return; - } - - if (!deps.length) - // If there is no dependencies, we inject "require", "exports" and - // "module" as dependencies, to provide CommonJS compatibility. - deps = ["require", "exports", "module"]; - - var req = function(childId) { - return window.require(id, childId); - }; - - window.require.modules[id] = { - exports: {}, - factory: function() { - var module = this; - var returnExports = factory.apply(this, deps.map(function(dep) { - switch (dep) { - // Because "require", "exports" and "module" aren't actual - // dependencies, we must handle them seperately. - case "require": return req; - case "exports": return module.exports; - case "module": return module; - // But for all other dependencies, we can just go ahead and - // require them. - default: return req(dep); - } - })); - if (returnExports) - module.exports = returnExports; - return module; - } - }; -}; -window.define.amd = {}; -require.tlns = {}; -window.initBaseUrls = function initBaseUrls(topLevelNamespaces) { - for (var i in topLevelNamespaces) - require.tlns[i] = topLevelNamespaces[i]; -}; - -window.initSender = function initSender() { - - var EventEmitter = window.require("ace/lib/event_emitter").EventEmitter; - var oop = window.require("ace/lib/oop"); - - var Sender = function() {}; - - (function() { - - oop.implement(this, EventEmitter); - - this.callback = function(data, callbackId) { - postMessage({ - type: "call", - id: callbackId, - data: data - }); - }; - - this.emit = function(name, data) { - postMessage({ - type: "event", - name: name, - data: data - }); - }; - - }).call(Sender.prototype); - - return new Sender(); -}; - -var main = window.main = null; -var sender = window.sender = null; - -window.onmessage = function(e) { - var msg = e.data; - if (msg.event && sender) { - sender._signal(msg.event, msg.data); - } - else if (msg.command) { - if (main[msg.command]) - main[msg.command].apply(main, msg.args); - else if (window[msg.command]) - window[msg.command].apply(window, msg.args); - else - throw new Error("Unknown command:" + msg.command); - } - else if (msg.init) { - window.initBaseUrls(msg.tlns); - require("ace/lib/es5-shim"); - sender = window.sender = window.initSender(); - var clazz = require(msg.module)[msg.classname]; - main = window.main = new clazz(sender); - } -}; -})(this); - -ace.define("ace/lib/oop",["require","exports","module"], function(require, exports, module) { -"use strict"; - -exports.inherits = function(ctor, superCtor) { - ctor.super_ = superCtor; - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); -}; - -exports.mixin = function(obj, mixin) { - for (var key in mixin) { - obj[key] = mixin[key]; - } - return obj; -}; - -exports.implement = function(proto, mixin) { - exports.mixin(proto, mixin); -}; - -}); - -ace.define("ace/range",["require","exports","module"], function(require, exports, module) { -"use strict"; -var comparePoints = function(p1, p2) { - return p1.row - p2.row || p1.column - p2.column; -}; -var Range = function(startRow, startColumn, endRow, endColumn) { - this.start = { - row: startRow, - column: startColumn - }; - - this.end = { - row: endRow, - column: endColumn - }; -}; - -(function() { - this.isEqual = function(range) { - return this.start.row === range.start.row && - this.end.row === range.end.row && - this.start.column === range.start.column && - this.end.column === range.end.column; - }; - this.toString = function() { - return ("Range: [" + this.start.row + "/" + this.start.column + - "] -> [" + this.end.row + "/" + this.end.column + "]"); - }; - - this.contains = function(row, column) { - return this.compare(row, column) == 0; - }; - this.compareRange = function(range) { - var cmp, - end = range.end, - start = range.start; - - cmp = this.compare(end.row, end.column); - if (cmp == 1) { - cmp = this.compare(start.row, start.column); - if (cmp == 1) { - return 2; - } else if (cmp == 0) { - return 1; - } else { - return 0; - } - } else if (cmp == -1) { - return -2; - } else { - cmp = this.compare(start.row, start.column); - if (cmp == -1) { - return -1; - } else if (cmp == 1) { - return 42; - } else { - return 0; - } - } - }; - this.comparePoint = function(p) { - return this.compare(p.row, p.column); - }; - this.containsRange = function(range) { - return this.comparePoint(range.start) == 0 && this.comparePoint(range.end) == 0; - }; - this.intersects = function(range) { - var cmp = this.compareRange(range); - return (cmp == -1 || cmp == 0 || cmp == 1); - }; - this.isEnd = function(row, column) { - return this.end.row == row && this.end.column == column; - }; - this.isStart = function(row, column) { - return this.start.row == row && this.start.column == column; - }; - this.setStart = function(row, column) { - if (typeof row == "object") { - this.start.column = row.column; - this.start.row = row.row; - } else { - this.start.row = row; - this.start.column = column; - } - }; - this.setEnd = function(row, column) { - if (typeof row == "object") { - this.end.column = row.column; - this.end.row = row.row; - } else { - this.end.row = row; - this.end.column = column; - } - }; - this.inside = function(row, column) { - if (this.compare(row, column) == 0) { - if (this.isEnd(row, column) || this.isStart(row, column)) { - return false; - } else { - return true; - } - } - return false; - }; - this.insideStart = function(row, column) { - if (this.compare(row, column) == 0) { - if (this.isEnd(row, column)) { - return false; - } else { - return true; - } - } - return false; - }; - this.insideEnd = function(row, column) { - if (this.compare(row, column) == 0) { - if (this.isStart(row, column)) { - return false; - } else { - return true; - } - } - return false; - }; - this.compare = function(row, column) { - if (!this.isMultiLine()) { - if (row === this.start.row) { - return column < this.start.column ? -1 : (column > this.end.column ? 1 : 0); - } - } - - if (row < this.start.row) - return -1; - - if (row > this.end.row) - return 1; - - if (this.start.row === row) - return column >= this.start.column ? 0 : -1; - - if (this.end.row === row) - return column <= this.end.column ? 0 : 1; - - return 0; - }; - this.compareStart = function(row, column) { - if (this.start.row == row && this.start.column == column) { - return -1; - } else { - return this.compare(row, column); - } - }; - this.compareEnd = function(row, column) { - if (this.end.row == row && this.end.column == column) { - return 1; - } else { - return this.compare(row, column); - } - }; - this.compareInside = function(row, column) { - if (this.end.row == row && this.end.column == column) { - return 1; - } else if (this.start.row == row && this.start.column == column) { - return -1; - } else { - return this.compare(row, column); - } - }; - this.clipRows = function(firstRow, lastRow) { - if (this.end.row > lastRow) - var end = {row: lastRow + 1, column: 0}; - else if (this.end.row < firstRow) - var end = {row: firstRow, column: 0}; - - if (this.start.row > lastRow) - var start = {row: lastRow + 1, column: 0}; - else if (this.start.row < firstRow) - var start = {row: firstRow, column: 0}; - - return Range.fromPoints(start || this.start, end || this.end); - }; - this.extend = function(row, column) { - var cmp = this.compare(row, column); - - if (cmp == 0) - return this; - else if (cmp == -1) - var start = {row: row, column: column}; - else - var end = {row: row, column: column}; - - return Range.fromPoints(start || this.start, end || this.end); - }; - - this.isEmpty = function() { - return (this.start.row === this.end.row && this.start.column === this.end.column); - }; - this.isMultiLine = function() { - return (this.start.row !== this.end.row); - }; - this.clone = function() { - return Range.fromPoints(this.start, this.end); - }; - this.collapseRows = function() { - if (this.end.column == 0) - return new Range(this.start.row, 0, Math.max(this.start.row, this.end.row-1), 0) - else - return new Range(this.start.row, 0, this.end.row, 0) - }; - this.toScreenRange = function(session) { - var screenPosStart = session.documentToScreenPosition(this.start); - var screenPosEnd = session.documentToScreenPosition(this.end); - - return new Range( - screenPosStart.row, screenPosStart.column, - screenPosEnd.row, screenPosEnd.column - ); - }; - this.moveBy = function(row, column) { - this.start.row += row; - this.start.column += column; - this.end.row += row; - this.end.column += column; - }; - -}).call(Range.prototype); -Range.fromPoints = function(start, end) { - return new Range(start.row, start.column, end.row, end.column); -}; -Range.comparePoints = comparePoints; - -Range.comparePoints = function(p1, p2) { - return p1.row - p2.row || p1.column - p2.column; -}; - - -exports.Range = Range; -}); - -ace.define("ace/apply_delta",["require","exports","module"], function(require, exports, module) { -"use strict"; - -function throwDeltaError(delta, errorText){ - console.log("Invalid Delta:", delta); - throw "Invalid Delta: " + errorText; -} - -function positionInDocument(docLines, position) { - return position.row >= 0 && position.row < docLines.length && - position.column >= 0 && position.column <= docLines[position.row].length; -} - -function validateDelta(docLines, delta) { - if (delta.action != "insert" && delta.action != "remove") - throwDeltaError(delta, "delta.action must be 'insert' or 'remove'"); - if (!(delta.lines instanceof Array)) - throwDeltaError(delta, "delta.lines must be an Array"); - if (!delta.start || !delta.end) - throwDeltaError(delta, "delta.start/end must be an present"); - var start = delta.start; - if (!positionInDocument(docLines, delta.start)) - throwDeltaError(delta, "delta.start must be contained in document"); - var end = delta.end; - if (delta.action == "remove" && !positionInDocument(docLines, end)) - throwDeltaError(delta, "delta.end must contained in document for 'remove' actions"); - var numRangeRows = end.row - start.row; - var numRangeLastLineChars = (end.column - (numRangeRows == 0 ? start.column : 0)); - if (numRangeRows != delta.lines.length - 1 || delta.lines[numRangeRows].length != numRangeLastLineChars) - throwDeltaError(delta, "delta.range must match delta lines"); -} - -exports.applyDelta = function(docLines, delta, doNotValidate) { - - var row = delta.start.row; - var startColumn = delta.start.column; - var line = docLines[row] || ""; - switch (delta.action) { - case "insert": - var lines = delta.lines; - if (lines.length === 1) { - docLines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn); - } else { - var args = [row, 1].concat(delta.lines); - docLines.splice.apply(docLines, args); - docLines[row] = line.substring(0, startColumn) + docLines[row]; - docLines[row + delta.lines.length - 1] += line.substring(startColumn); - } - break; - case "remove": - var endColumn = delta.end.column; - var endRow = delta.end.row; - if (row === endRow) { - docLines[row] = line.substring(0, startColumn) + line.substring(endColumn); - } else { - docLines.splice( - row, endRow - row + 1, - line.substring(0, startColumn) + docLines[endRow].substring(endColumn) - ); - } - break; - } -} -}); - -ace.define("ace/lib/event_emitter",["require","exports","module"], function(require, exports, module) { -"use strict"; - -var EventEmitter = {}; -var stopPropagation = function() { this.propagationStopped = true; }; -var preventDefault = function() { this.defaultPrevented = true; }; - -EventEmitter._emit = -EventEmitter._dispatchEvent = function(eventName, e) { - this._eventRegistry || (this._eventRegistry = {}); - this._defaultHandlers || (this._defaultHandlers = {}); - - var listeners = this._eventRegistry[eventName] || []; - var defaultHandler = this._defaultHandlers[eventName]; - if (!listeners.length && !defaultHandler) - return; - - if (typeof e != "object" || !e) - e = {}; - - if (!e.type) - e.type = eventName; - if (!e.stopPropagation) - e.stopPropagation = stopPropagation; - if (!e.preventDefault) - e.preventDefault = preventDefault; - - listeners = listeners.slice(); - for (var i=0; i this.row) - return; - - var point = $getTransformedPoint(delta, {row: this.row, column: this.column}, this.$insertRight); - this.setPosition(point.row, point.column, true); - }; - - function $pointsInOrder(point1, point2, equalPointsInOrder) { - var bColIsAfter = equalPointsInOrder ? point1.column <= point2.column : point1.column < point2.column; - return (point1.row < point2.row) || (point1.row == point2.row && bColIsAfter); - } - - function $getTransformedPoint(delta, point, moveIfEqual) { - var deltaIsInsert = delta.action == "insert"; - var deltaRowShift = (deltaIsInsert ? 1 : -1) * (delta.end.row - delta.start.row); - var deltaColShift = (deltaIsInsert ? 1 : -1) * (delta.end.column - delta.start.column); - var deltaStart = delta.start; - var deltaEnd = deltaIsInsert ? deltaStart : delta.end; // Collapse insert range. - if ($pointsInOrder(point, deltaStart, moveIfEqual)) { - return { - row: point.row, - column: point.column - }; - } - if ($pointsInOrder(deltaEnd, point, !moveIfEqual)) { - return { - row: point.row + deltaRowShift, - column: point.column + (point.row == deltaEnd.row ? deltaColShift : 0) - }; - } - - return { - row: deltaStart.row, - column: deltaStart.column - }; - } - this.setPosition = function(row, column, noClip) { - var pos; - if (noClip) { - pos = { - row: row, - column: column - }; - } else { - pos = this.$clipPositionToDocument(row, column); - } - - if (this.row == pos.row && this.column == pos.column) - return; - - var old = { - row: this.row, - column: this.column - }; - - this.row = pos.row; - this.column = pos.column; - this._signal("change", { - old: old, - value: pos - }); - }; - this.detach = function() { - this.document.removeEventListener("change", this.$onChange); - }; - this.attach = function(doc) { - this.document = doc || this.document; - this.document.on("change", this.$onChange); - }; - this.$clipPositionToDocument = function(row, column) { - var pos = {}; - - if (row >= this.document.getLength()) { - pos.row = Math.max(0, this.document.getLength() - 1); - pos.column = this.document.getLine(pos.row).length; - } - else if (row < 0) { - pos.row = 0; - pos.column = 0; - } - else { - pos.row = row; - pos.column = Math.min(this.document.getLine(pos.row).length, Math.max(0, column)); - } - - if (column < 0) - pos.column = 0; - - return pos; - }; - -}).call(Anchor.prototype); - -}); - -ace.define("ace/document",["require","exports","module","ace/lib/oop","ace/apply_delta","ace/lib/event_emitter","ace/range","ace/anchor"], function(require, exports, module) { -"use strict"; - -var oop = require("./lib/oop"); -var applyDelta = require("./apply_delta").applyDelta; -var EventEmitter = require("./lib/event_emitter").EventEmitter; -var Range = require("./range").Range; -var Anchor = require("./anchor").Anchor; - -var Document = function(textOrLines) { - this.$lines = [""]; - if (textOrLines.length === 0) { - this.$lines = [""]; - } else if (Array.isArray(textOrLines)) { - this.insertMergedLines({row: 0, column: 0}, textOrLines); - } else { - this.insert({row: 0, column:0}, textOrLines); - } -}; - -(function() { - - oop.implement(this, EventEmitter); - this.setValue = function(text) { - var len = this.getLength() - 1; - this.remove(new Range(0, 0, len, this.getLine(len).length)); - this.insert({row: 0, column: 0}, text); - }; - this.getValue = function() { - return this.getAllLines().join(this.getNewLineCharacter()); - }; - this.createAnchor = function(row, column) { - return new Anchor(this, row, column); - }; - if ("aaa".split(/a/).length === 0) { - this.$split = function(text) { - return text.replace(/\r\n|\r/g, "\n").split("\n"); - }; - } else { - this.$split = function(text) { - return text.split(/\r\n|\r|\n/); - }; - } - - - this.$detectNewLine = function(text) { - var match = text.match(/^.*?(\r\n|\r|\n)/m); - this.$autoNewLine = match ? match[1] : "\n"; - this._signal("changeNewLineMode"); - }; - this.getNewLineCharacter = function() { - switch (this.$newLineMode) { - case "windows": - return "\r\n"; - case "unix": - return "\n"; - default: - return this.$autoNewLine || "\n"; - } - }; - - this.$autoNewLine = ""; - this.$newLineMode = "auto"; - this.setNewLineMode = function(newLineMode) { - if (this.$newLineMode === newLineMode) - return; - - this.$newLineMode = newLineMode; - this._signal("changeNewLineMode"); - }; - this.getNewLineMode = function() { - return this.$newLineMode; - }; - this.isNewLine = function(text) { - return (text == "\r\n" || text == "\r" || text == "\n"); - }; - this.getLine = function(row) { - return this.$lines[row] || ""; - }; - this.getLines = function(firstRow, lastRow) { - return this.$lines.slice(firstRow, lastRow + 1); - }; - this.getAllLines = function() { - return this.getLines(0, this.getLength()); - }; - this.getLength = function() { - return this.$lines.length; - }; - this.getTextRange = function(range) { - return this.getLinesForRange(range).join(this.getNewLineCharacter()); - }; - this.getLinesForRange = function(range) { - var lines; - if (range.start.row === range.end.row) { - lines = [this.getLine(range.start.row).substring(range.start.column, range.end.column)]; - } else { - lines = this.getLines(range.start.row, range.end.row); - lines[0] = (lines[0] || "").substring(range.start.column); - var l = lines.length - 1; - if (range.end.row - range.start.row == l) - lines[l] = lines[l].substring(0, range.end.column); - } - return lines; - }; - this.insertLines = function(row, lines) { - console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."); - return this.insertFullLines(row, lines); - }; - this.removeLines = function(firstRow, lastRow) { - console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead."); - return this.removeFullLines(firstRow, lastRow); - }; - this.insertNewLine = function(position) { - console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead."); - return this.insertMergedLines(position, ["", ""]); - }; - this.insert = function(position, text) { - if (this.getLength() <= 1) - this.$detectNewLine(text); - - return this.insertMergedLines(position, this.$split(text)); - }; - this.insertInLine = function(position, text) { - var start = this.clippedPos(position.row, position.column); - var end = this.pos(position.row, position.column + text.length); - - this.applyDelta({ - start: start, - end: end, - action: "insert", - lines: [text] - }, true); - - return this.clonePos(end); - }; - - this.clippedPos = function(row, column) { - var length = this.getLength(); - if (row === undefined) { - row = length; - } else if (row < 0) { - row = 0; - } else if (row >= length) { - row = length - 1; - column = undefined; - } - var line = this.getLine(row); - if (column == undefined) - column = line.length; - column = Math.min(Math.max(column, 0), line.length); - return {row: row, column: column}; - }; - - this.clonePos = function(pos) { - return {row: pos.row, column: pos.column}; - }; - - this.pos = function(row, column) { - return {row: row, column: column}; - }; - - this.$clipPosition = function(position) { - var length = this.getLength(); - if (position.row >= length) { - position.row = Math.max(0, length - 1); - position.column = this.getLine(length - 1).length; - } else { - position.row = Math.max(0, position.row); - position.column = Math.min(Math.max(position.column, 0), this.getLine(position.row).length); - } - return position; - }; - this.insertFullLines = function(row, lines) { - row = Math.min(Math.max(row, 0), this.getLength()); - var column = 0; - if (row < this.getLength()) { - lines = lines.concat([""]); - column = 0; - } else { - lines = [""].concat(lines); - row--; - column = this.$lines[row].length; - } - this.insertMergedLines({row: row, column: column}, lines); - }; - this.insertMergedLines = function(position, lines) { - var start = this.clippedPos(position.row, position.column); - var end = { - row: start.row + lines.length - 1, - column: (lines.length == 1 ? start.column : 0) + lines[lines.length - 1].length - }; - - this.applyDelta({ - start: start, - end: end, - action: "insert", - lines: lines - }); - - return this.clonePos(end); - }; - this.remove = function(range) { - var start = this.clippedPos(range.start.row, range.start.column); - var end = this.clippedPos(range.end.row, range.end.column); - this.applyDelta({ - start: start, - end: end, - action: "remove", - lines: this.getLinesForRange({start: start, end: end}) - }); - return this.clonePos(start); - }; - this.removeInLine = function(row, startColumn, endColumn) { - var start = this.clippedPos(row, startColumn); - var end = this.clippedPos(row, endColumn); - - this.applyDelta({ - start: start, - end: end, - action: "remove", - lines: this.getLinesForRange({start: start, end: end}) - }, true); - - return this.clonePos(start); - }; - this.removeFullLines = function(firstRow, lastRow) { - firstRow = Math.min(Math.max(0, firstRow), this.getLength() - 1); - lastRow = Math.min(Math.max(0, lastRow ), this.getLength() - 1); - var deleteFirstNewLine = lastRow == this.getLength() - 1 && firstRow > 0; - var deleteLastNewLine = lastRow < this.getLength() - 1; - var startRow = ( deleteFirstNewLine ? firstRow - 1 : firstRow ); - var startCol = ( deleteFirstNewLine ? this.getLine(startRow).length : 0 ); - var endRow = ( deleteLastNewLine ? lastRow + 1 : lastRow ); - var endCol = ( deleteLastNewLine ? 0 : this.getLine(endRow).length ); - var range = new Range(startRow, startCol, endRow, endCol); - var deletedLines = this.$lines.slice(firstRow, lastRow + 1); - - this.applyDelta({ - start: range.start, - end: range.end, - action: "remove", - lines: this.getLinesForRange(range) - }); - return deletedLines; - }; - this.removeNewLine = function(row) { - if (row < this.getLength() - 1 && row >= 0) { - this.applyDelta({ - start: this.pos(row, this.getLine(row).length), - end: this.pos(row + 1, 0), - action: "remove", - lines: ["", ""] - }); - } - }; - this.replace = function(range, text) { - if (!(range instanceof Range)) - range = Range.fromPoints(range.start, range.end); - if (text.length === 0 && range.isEmpty()) - return range.start; - if (text == this.getTextRange(range)) - return range.end; - - this.remove(range); - var end; - if (text) { - end = this.insert(range.start, text); - } - else { - end = range.start; - } - - return end; - }; - this.applyDeltas = function(deltas) { - for (var i=0; i=0; i--) { - this.revertDelta(deltas[i]); - } - }; - this.applyDelta = function(delta, doNotValidate) { - var isInsert = delta.action == "insert"; - if (isInsert ? delta.lines.length <= 1 && !delta.lines[0] - : !Range.comparePoints(delta.start, delta.end)) { - return; - } - - if (isInsert && delta.lines.length > 20000) - this.$splitAndapplyLargeDelta(delta, 20000); - applyDelta(this.$lines, delta, doNotValidate); - this._signal("change", delta); - }; - - this.$splitAndapplyLargeDelta = function(delta, MAX) { - var lines = delta.lines; - var l = lines.length; - var row = delta.start.row; - var column = delta.start.column; - var from = 0, to = 0; - do { - from = to; - to += MAX - 1; - var chunk = lines.slice(from, to); - if (to > l) { - delta.lines = chunk; - delta.start.row = row + from; - delta.start.column = column; - break; - } - chunk.push(""); - this.applyDelta({ - start: this.pos(row + from, column), - end: this.pos(row + to, column = 0), - action: delta.action, - lines: chunk - }, true); - } while(true); - }; - this.revertDelta = function(delta) { - this.applyDelta({ - start: this.clonePos(delta.start), - end: this.clonePos(delta.end), - action: (delta.action == "insert" ? "remove" : "insert"), - lines: delta.lines.slice() - }); - }; - this.indexToPosition = function(index, startRow) { - var lines = this.$lines || this.getAllLines(); - var newlineLength = this.getNewLineCharacter().length; - for (var i = startRow || 0, l = lines.length; i < l; i++) { - index -= lines[i].length + newlineLength; - if (index < 0) - return {row: i, column: index + lines[i].length + newlineLength}; - } - return {row: l-1, column: lines[l-1].length}; - }; - this.positionToIndex = function(pos, startRow) { - var lines = this.$lines || this.getAllLines(); - var newlineLength = this.getNewLineCharacter().length; - var index = 0; - var row = Math.min(pos.row, lines.length); - for (var i = startRow || 0; i < row; ++i) - index += lines[i].length + newlineLength; - - return index + pos.column; - }; - -}).call(Document.prototype); - -exports.Document = Document; -}); - -ace.define("ace/lib/lang",["require","exports","module"], function(require, exports, module) { -"use strict"; - -exports.last = function(a) { - return a[a.length - 1]; -}; - -exports.stringReverse = function(string) { - return string.split("").reverse().join(""); -}; - -exports.stringRepeat = function (string, count) { - var result = ''; - while (count > 0) { - if (count & 1) - result += string; - - if (count >>= 1) - string += string; - } - return result; -}; - -var trimBeginRegexp = /^\s\s*/; -var trimEndRegexp = /\s\s*$/; - -exports.stringTrimLeft = function (string) { - return string.replace(trimBeginRegexp, ''); -}; - -exports.stringTrimRight = function (string) { - return string.replace(trimEndRegexp, ''); -}; - -exports.copyObject = function(obj) { - var copy = {}; - for (var key in obj) { - copy[key] = obj[key]; - } - return copy; -}; - -exports.copyArray = function(array){ - var copy = []; - for (var i=0, l=array.length; i MAX_TOKENS) { - throw new Error("exceed max token count of " + MAX_TOKENS); - break; - }; - var result = SPECIAL.exec(text); - if (result == null) { - if (idx < text.length) { - Tokens.push([lineNumber, "Text", idx, text.length]); - } - break; - } - if (result && result.index <= pos) { - throw new Error("infinite loop in parsing"); - break; - }; - pos = result.index; - if (pos > idx) { - Tokens.push([lineNumber, "Text", idx, pos]); - } - for (var i = idx; i < pos; i++) { - if (text[i] === "\n") { - lineNumber++; - linePosition[lineNumber] = i+1; - } - } - - var newIdx = SPECIAL.lastIndex; - idx = newIdx; - var code = result[0]; - if (code === "%") { // comment character - var newLinePos = text.indexOf("\n", idx); - if (newLinePos === -1) { - newLinePos = text.length; - }; - var commentString = text.substring(idx, newLinePos); - if (commentString.indexOf("%novalidate") === 0) { - return []; - } else if(!checkingDisabled && commentString.indexOf("%begin novalidate") === 0) { - checkingDisabled = true; - } else if (checkingDisabled && commentString.indexOf("%end novalidate") === 0) { - checkingDisabled = false; - }; - idx = SPECIAL.lastIndex = newLinePos + 1; - Comments.push([lineNumber, idx, newLinePos]); - lineNumber++; - linePosition[lineNumber] = idx; - } else if (checkingDisabled) { - continue; - } else if (code === '\\') { // escape character - NEXTCS.lastIndex = idx; - var controlSequence = NEXTCS.exec(text); - var nextSpecialPos = controlSequence === null ? idx : controlSequence.index; - if (nextSpecialPos === idx) { - Tokens.push([lineNumber, code, pos, idx + 1, text[idx], "control-symbol"]); - idx = SPECIAL.lastIndex = idx + 1; - char = text[nextSpecialPos]; - if (char === '\n') { lineNumber++; linePosition[lineNumber] = nextSpecialPos;}; - } else { - Tokens.push([lineNumber, code, pos, nextSpecialPos, text.slice(idx, nextSpecialPos)]); - var char; - while ((char = text[nextSpecialPos]) === ' ' || char === '\t' || char === '\r' || char === '\n') { - nextSpecialPos++; - if (char === '\n') { lineNumber++; linePosition[lineNumber] = nextSpecialPos;}; - } - idx = SPECIAL.lastIndex = nextSpecialPos; - } - } else if (code === "{") { // open group - Tokens.push([lineNumber, code, pos]); - } else if (code === "}") { // close group - Tokens.push([lineNumber, code, pos]); - } else if (code === "$") { // math mode - Tokens.push([lineNumber, code, pos]); - } else if (code === "&") { // tabalign - Tokens.push([lineNumber, code, pos]); - } else if (code === "#") { // macro parameter - Tokens.push([lineNumber, code, pos]); - } else if (code === "^") { // superscript - Tokens.push([lineNumber, code, pos]); - } else if (code === "_") { // subscript - Tokens.push([lineNumber, code, pos]); - } else if (code === "~") { // active character (space) - Tokens.push([lineNumber, code, pos]); - } else { - throw "unrecognised character " + code; - } - } - - return {tokens: Tokens, comments: Comments, linePosition: linePosition, lineNumber: lineNumber, text: text}; -}; - -var read1arg = function (TokeniseResult, k, options) { - var Tokens = TokeniseResult.tokens; - var text = TokeniseResult.text; - if (options && options.allowStar) { - var optional = Tokens[k+1]; - if (optional && optional[1] === "Text") { - var optionalstr = text.substring(optional[2], optional[3]); - if (optionalstr === "*") { k++;} - }; - }; - - var open = Tokens[k+1]; - var env = Tokens[k+2]; - var close = Tokens[k+3]; - var envName; - - if(open && open[1] === "\\") { - envName = open[4]; // array element 4 is command sequence - return k + 1; - } else if(open && open[1] === "{" && env && env[1] === "\\" && close && close[1] === "}") { - envName = env[4]; // NOTE: if we were actually using this, keep track of * above - return k + 3; // array element 4 is command sequence - } else { - return null; - } -}; - -var readLetDefinition = function (TokeniseResult, k) { - - var Tokens = TokeniseResult.tokens; - var text = TokeniseResult.text; - - var first = Tokens[k+1]; - var second = Tokens[k+2]; - var third = Tokens[k+3]; - - if(first && first[1] === "\\" && second && second[1] === "\\") { - return k + 2; - } else if(first && first[1] === "\\" && - second && second[1] === "Text" && text.substring(second[2], second[3]) === "=" && - third && third[1] === "\\") { - return k + 3; - } else { - return null; - } -}; - -var read1name = function (TokeniseResult, k) { - var Tokens = TokeniseResult.tokens; - var text = TokeniseResult.text; - - var open = Tokens[k+1]; - var env = Tokens[k+2]; - var close = Tokens[k+3]; - - if(open && open[1] === "{" && env && env[1] === "Text" && close && close[1] === "}") { - var envName = text.substring(env[2], env[3]); - return k + 3; - } else if (open && open[1] === "{" && env && env[1] === "Text") { - envName = ""; - for (var j = k + 2, tok; (tok = Tokens[j]); j++) { - if (tok[1] === "Text") { - var str = text.substring(tok[2], tok[3]); - if (!str.match(/^\S*$/)) { break; } - envName = envName + str; - } else if (tok[1] === "_") { - envName = envName + "_"; - } else { - break; - } - } - if (tok && tok[1] === "}") { - return j; // advance past these tokens - } else { - return null; - } - } else { - return null; - } -}; - -var read1filename = function (TokeniseResult, k) { - var Tokens = TokeniseResult.tokens; - var text = TokeniseResult.text; - - var fileName = ""; - for (var j = k + 1, tok; (tok = Tokens[j]); j++) { - if (tok[1] === "Text") { - var str = text.substring(tok[2], tok[3]); - if (!str.match(/^\S*$/)) { break; } - fileName = fileName + str; - } else if (tok[1] === "_") { - fileName = fileName + "_"; - } else { - break; - } - } - if (fileName.length > 0) { - return j; // advance past these tokens - } else { - return null; - } -}; - -var readOptionalParams = function(TokeniseResult, k) { - var Tokens = TokeniseResult.tokens; - var text = TokeniseResult.text; - - var params = Tokens[k+1]; - - if(params && params[1] === "Text") { - var paramNum = text.substring(params[2], params[3]); - if (paramNum.match(/^\[\d+\](\[[^\]]*\])*\s*$/)) { - return k + 1; // got it - }; - }; - var count = 0; - var nextToken = Tokens[k+1]; - var pos = nextToken[2]; - - for (var i = pos, end = text.length; i < end; i++) { - var char = text[i]; - if (nextToken && i >= nextToken[2]) { k++; nextToken = Tokens[k+1];}; - if (char === "[") { count++; } - if (char === "]") { count--; } - if (count === 0 && char === "{") { return k - 1; } - if (count > 0 && (char === '\r' || char === '\n')) { return null; } - }; - return null; -}; - -var readOptionalGeneric = function(TokeniseResult, k) { - var Tokens = TokeniseResult.tokens; - var text = TokeniseResult.text; - - var params = Tokens[k+1]; - - if(params && params[1] === "Text") { - var paramNum = text.substring(params[2], params[3]); - if (paramNum.match(/^(\[[^\]]*\])+\s*$/)) { - return k + 1; // got it - }; - }; - return null; -}; - -var readOptionalDef = function (TokeniseResult, k) { - var Tokens = TokeniseResult.tokens; - var text = TokeniseResult.text; - - var defToken = Tokens[k]; - var pos = defToken[3]; - - var openBrace = "{"; - var nextToken = Tokens[k+1]; - for (var i = pos, end = text.length; i < end; i++) { - var char = text[i]; - if (nextToken && i >= nextToken[2]) { k++; nextToken = Tokens[k+1];}; - if (char === openBrace) { return k - 1; }; // move back to the last token of the optional arguments - if (char === '\r' || char === '\n') { return null; } - }; - - return null; - -}; - -var readDefinition = function(TokeniseResult, k) { - var Tokens = TokeniseResult.tokens; - var text = TokeniseResult.text; - - k = k + 1; - var count = 0; - var nextToken = Tokens[k]; - while (nextToken && nextToken[1] === "Text") { - var start = nextToken[2], end = nextToken[3]; - for (var i = start; i < end; i++) { - var char = text[i]; - if (char === ' ' || char === '\t' || char === '\r' || char === '\n') { continue; } - return null; // bail out, should begin with a { - } - k++; - nextToken = Tokens[k]; - } - if (nextToken && nextToken[1] === "{") { - count++; - while (count>0) { - k++; - nextToken = Tokens[k]; - if(!nextToken) { break; }; - if (nextToken[1] === "}") { count--; } - if (nextToken[1] === "{") { count++; } - } - return k; - } - - return null; -}; - -var readVerb = function(TokeniseResult, k) { - - var Tokens = TokeniseResult.tokens; - var text = TokeniseResult.text; - - var verbToken = Tokens[k]; - var verbStr = text.substring(verbToken[2], verbToken[3]); - var pos = verbToken[3]; - if (text[pos] === "*") { pos++; } // \verb* form of command - var delimiter = text[pos]; - pos++; - - var nextToken = Tokens[k+1]; - for (var i = pos, end = text.length; i < end; i++) { - var char = text[i]; - if (nextToken && i >= nextToken[2]) { k++; nextToken = Tokens[k+1];}; - if (char === delimiter) { return k; }; - if (char === '\r' || char === '\n') { return null; } - }; - - return null; -}; - -var readUrl = function(TokeniseResult, k) { - - var Tokens = TokeniseResult.tokens; - var text = TokeniseResult.text; - - var urlToken = Tokens[k]; - var urlStr = text.substring(urlToken[2], urlToken[3]); - var pos = urlToken[3]; - var openDelimiter = text[pos]; - var closeDelimiter = (openDelimiter === "{") ? "}" : openDelimiter; - var nextToken = Tokens[k+1]; - if (nextToken && pos === nextToken[2]) { - k++; - nextToken = Tokens[k+1]; - }; - pos++; - - var count = 1; - for (var i = pos, end = text.length; count > 0 && i < end; i++) { - var char = text[i]; - if (nextToken && i >= nextToken[2]) { k++; nextToken = Tokens[k+1];}; - if (char === closeDelimiter) { - count--; - } else if (char === openDelimiter) { - count++; - }; - if (count === 0) { return k; }; - if (char === '\r' || char === '\n') { return null; } - }; - - return null; -}; - -var InterpretTokens = function (TokeniseResult, ErrorReporter) { - var Tokens = TokeniseResult.tokens; - var linePosition = TokeniseResult.linePosition; - var lineNumber = TokeniseResult.lineNumber; - var text = TokeniseResult.text; - - var TokenErrorFromTo = ErrorReporter.TokenErrorFromTo; - var TokenError = ErrorReporter.TokenError; - var Environments = new EnvHandler(ErrorReporter); - - var nextGroupMathMode = null; // if the next group should have math mode on or off (for \hbox) - var nextGroupMathModeStack = [] ; // tracking all nextGroupMathModes - var seenUserDefinedBeginEquation = false; // if we have seen macros like \beq - var seenUserDefinedEndEquation = false; // if we have seen macros like \eeq - - for (var i = 0, len = Tokens.length; i < len; i++) { - var token = Tokens[i]; - var line = token[0], type = token[1], start = token[2], end = token[3], seq = token[4]; - - if (type === "{") { - Environments.push({command:"{", token:token, mathMode: nextGroupMathMode}); - nextGroupMathModeStack.push(nextGroupMathMode); - nextGroupMathMode = null; - continue; - } else if (type === "}") { - Environments.push({command:"}", token:token}); - nextGroupMathMode = nextGroupMathModeStack.pop(); - continue; - } else { - nextGroupMathMode = null; - }; - - if (type === "\\") { - if (seq === "begin" || seq === "end") { - var open = Tokens[i+1]; - var env = Tokens[i+2]; - var close = Tokens[i+3]; - if(open && open[1] === "{" && env && env[1] === "Text" && close && close[1] === "}") { - var envName = text.substring(env[2], env[3]); - Environments.push({command: seq, name: envName, token: token, closeToken: close}); - i = i + 3; // advance past these tokens - } else { - if (open && open[1] === "{" && env && env[1] === "Text") { - envName = ""; - for (var j = i + 2, tok; (tok = Tokens[j]); j++) { - if (tok[1] === "Text") { - var str = text.substring(tok[2], tok[3]); - if (!str.match(/^\S*$/)) { break; } - envName = envName + str; - } else if (tok[1] === "_") { - envName = envName + "_"; - } else { - break; - } - } - if (tok && tok[1] === "}") { - Environments.push({command: seq, name: envName, token: token, closeToken: close}); - i = j; // advance past these tokens - continue; - } - } - var endToken = null; - if (open && open[1] === "{") { - endToken = open; // we've got a { - if (env && env[1] === "Text") { - endToken = env.slice(); // we've got some text following the { - start = endToken[2]; end = endToken[3]; - for (j = start; j < end; j++) { - var char = text[j]; - if (char === ' ' || char === '\t' || char === '\r' || char === '\n') { break; } - } - endToken[3] = j; // the end of partial token is as far as we got looking ahead - }; - }; - - if (endToken) { - TokenErrorFromTo(token, endToken, "invalid environment command " + text.substring(token[2], endToken[3] || endToken[2])); - } else { - TokenError(token, "invalid environment command"); - }; - } - } else if (typeof seq === "string" && seq.match(/^(be|beq|beqa|bea)$/i)) { - seenUserDefinedBeginEquation = true; - } else if (typeof seq === "string" && seq.match(/^(ee|eeq|eeqn|eeqa|eeqan|eea)$/i)) { - seenUserDefinedEndEquation = true; - } else if (seq === "newcommand" || seq === "renewcommand" || seq === "DeclareRobustCommand") { - var newPos = read1arg(TokeniseResult, i, {allowStar: true}); - if (newPos === null) { continue; } else {i = newPos;}; - newPos = readOptionalParams(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - - } else if (seq === "def") { - newPos = read1arg(TokeniseResult, i); - if (newPos === null) { continue; } else {i = newPos;}; - newPos = readOptionalDef(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - - } else if (seq === "let") { - newPos = readLetDefinition(TokeniseResult, i); - if (newPos === null) { continue; } else {i = newPos;}; - - } else if (seq === "newcolumntype") { - newPos = read1name(TokeniseResult, i); - if (newPos === null) { continue; } else {i = newPos;}; - newPos = readOptionalParams(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - - } else if (seq === "newenvironment" || seq === "renewenvironment") { - newPos = read1name(TokeniseResult, i); - if (newPos === null) { continue; } else {i = newPos;}; - newPos = readOptionalParams(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - } else if (seq === "verb") { - newPos = readVerb(TokeniseResult, i); - if (newPos === null) { TokenError(token, "invalid verbatim command"); } else {i = newPos;}; - } else if (seq === "url") { - newPos = readUrl(TokeniseResult, i); - if (newPos === null) { TokenError(token, "invalid url command"); } else {i = newPos;}; - } else if (seq === "left" || seq === "right") { - var nextToken = Tokens[i+1]; - char = ""; - if (nextToken && nextToken[1] === "Text") { - char = text.substring(nextToken[2], nextToken[2] + 1); - } else if (nextToken && nextToken[1] === "\\" && nextToken[5] == "control-symbol") { - char = nextToken[4]; - } else if (nextToken && nextToken[1] === "\\") { - char = "unknown"; - } - if (char === "" || (char !== "unknown" && "(){}[]<>/|\\.".indexOf(char) === -1)) { - TokenError(token, "invalid bracket command"); - } else { - i = i + 1; - Environments.push({command:seq, token:token}); - }; - } else if (seq === "(" || seq === ")" || seq === "[" || seq === "]") { - Environments.push({command:seq, token:token}); - } else if (seq === "input") { - newPos = read1filename(TokeniseResult, i); - if (newPos === null) { continue; } else {i = newPos;}; - } else if (seq === "hbox" || seq === "text" || seq === "mbox" || seq === "footnote" || seq === "intertext" || seq === "shortintertext" || seq === "textnormal" || seq === "tag" || seq === "reflectbox" || seq === "textrm") { - nextGroupMathMode = false; - } else if (seq === "rotatebox" || seq === "scalebox") { - newPos = readOptionalGeneric(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - nextGroupMathMode = false; - } else if (seq === "resizebox") { - newPos = readOptionalGeneric(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - - nextGroupMathMode = false; - } else if (seq === "DeclareMathOperator") { - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - } else if (seq === "DeclarePairedDelimiter") { - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - newPos = readDefinition(TokeniseResult, i); - if (newPos === null) { /* do nothing */ } else {i = newPos;}; - } else if (typeof seq === "string" && seq.match(/^(alpha|beta|gamma|delta|epsilon|varepsilon|zeta|eta|theta|vartheta|iota|kappa|lambda|mu|nu|xi|pi|varpi|rho|varrho|sigma|varsigma|tau|upsilon|phi|varphi|chi|psi|omega|Gamma|Delta|Theta|Lambda|Xi|Pi|Sigma|Upsilon|Phi|Psi|Omega)$/)) { - var currentMathMode = Environments.getMathMode() ; // returns null / $(inline) / $$(display) - if (currentMathMode === null) { - TokenError(token, type + seq + " must be inside math mode", {mathMode:true}); - }; - } else if (typeof seq === "string" && seq.match(/^(chapter|section|subsection|subsubsection)$/)) { - currentMathMode = Environments.getMathMode() ; // returns null / $(inline) / $$(display) - if (currentMathMode) { - TokenError(token, type + seq + " used inside math mode", {mathMode:true}); - Environments.resetMathMode(); - }; - } else if (typeof seq === "string" && seq.match(/^[a-z]+$/)) { - nextGroupMathMode = undefined; - }; - - } else if (type === "$") { - var lookAhead = Tokens[i+1]; - var nextIsDollar = lookAhead && lookAhead[1] === "$"; - currentMathMode = Environments.getMathMode() ; // returns null / $(inline) / $$(display) - if (nextIsDollar && (!currentMathMode || currentMathMode.command == "$$")) { - Environments.push({command:"$$", token:token}); - i = i + 1; - } else { - Environments.push({command:"$", token:token}); - } - } else if (type === "^" || type === "_") { - currentMathMode = Environments.getMathMode() ; // returns null / $(inline) / $$(display) - var insideGroup = Environments.insideGroup(); // true if inside {....} - if (currentMathMode === null && !insideGroup) { - TokenError(token, type + " must be inside math mode", {mathMode:true}); - }; - } - }; - - if (seenUserDefinedBeginEquation && seenUserDefinedEndEquation) { - ErrorReporter.filterMath = true; - }; - - return Environments; -}; - -var EnvHandler = function (ErrorReporter) { - var ErrorTo = ErrorReporter.EnvErrorTo; - var ErrorFromTo = ErrorReporter.EnvErrorFromTo; - var ErrorFrom = ErrorReporter.EnvErrorFrom; - - var envs = []; - - var state = []; - var documentClosed = null; - var inVerbatim = false; - var verbatimRanges = []; - - this.Environments = envs; - - this.push = function (newEnv) { - this.setEnvProps(newEnv); - this.checkAndUpdateState(newEnv); - envs.push(newEnv); - }; - - this._endVerbatim = function (thisEnv) { - var lastEnv = state.pop(); - if (lastEnv && lastEnv.name === thisEnv.name) { - inVerbatim = false; - verbatimRanges.push({start: lastEnv.token[2], end: thisEnv.token[2]}); - } else { - if(lastEnv) { state.push(lastEnv); } ; - } - }; - - var invalidEnvs = []; - - this._end = function (thisEnv) { - do { - var lastEnv = state.pop(); - var retry = false; - var i; - - if (closedBy(lastEnv, thisEnv)) { - if (thisEnv.command === "end" && thisEnv.name === "document" && !documentClosed) { - documentClosed = thisEnv; - }; - return; - } else if (!lastEnv) { - if (documentClosed) { - ErrorFromTo(documentClosed, thisEnv, "\\end{" + documentClosed.name + "} is followed by unexpected content",{errorAtStart: true, type: "info"}); - } else { - ErrorTo(thisEnv, "unexpected " + getName(thisEnv)); - } - } else if (invalidEnvs.length > 0 && (i = indexOfClosingEnvInArray(invalidEnvs, thisEnv) > -1)) { - invalidEnvs.splice(i, 1); - if (lastEnv) { state.push(lastEnv); } ; - return; - } else { - var status = reportError(lastEnv, thisEnv); - if (envPrecedence(lastEnv) < envPrecedence(thisEnv)) { - invalidEnvs.push(lastEnv); - retry = true; - } else { - var prevLastEnv = state.pop(); - if(prevLastEnv) { - if (thisEnv.name === prevLastEnv.name) { - return; - } else { - state.push(prevLastEnv); - } - } - invalidEnvs.push(lastEnv); - } - - } - } while (retry === true); - }; - - var CLOSING_DELIMITER = { - "{" : "}", - "left" : "right", - "[" : "]", - "(" : ")", - "$" : "$", - "$$": "$$" - }; - - var closedBy = function (lastEnv, thisEnv) { - if (!lastEnv) { - return false ; - } else if (thisEnv.command === "end") { - return lastEnv.command === "begin" && lastEnv.name === thisEnv.name; - } else if (thisEnv.command === CLOSING_DELIMITER[lastEnv.command]) { - return true; - } else { - return false; - } - }; - - var indexOfClosingEnvInArray = function (envs, thisEnv) { - for (var i = 0, n = envs.length; i < n ; i++) { - if (closedBy(envs[i], thisEnv)) { - return i; - } - } - return -1; - }; - - var envPrecedence = function (env) { - var openScore = { - "{" : 1, - "left" : 2, - "$" : 3, - "$$" : 4, - "begin": 4 - }; - var closeScore = { - "}" : 1, - "right" : 2, - "$" : 3, - "$$" : 5, - "end": 4 - }; - if (env.command) { - return openScore[env.command] || closeScore[env.command]; - } else { - return 0; - } - }; - - var getName = function(env) { - var description = { - "{" : "open group {", - "}" : "close group }", - "[" : "open display math \\[", - "]" : "close display math \\]", - "(" : "open inline math \\(", - ")" : "close inline math \\)", - "$" : "$", - "$$" : "$$", - "left" : "\\left", - "right" : "\\right" - }; - if (env.command === "begin" || env.command === "end") { - return "\\" + env.command + "{" + env.name + "}"; - } else if (env.command in description) { - return description[env.command]; - } else { - return env.command; - } - }; - - var EXTRA_CLOSE = 1; - var UNCLOSED_GROUP = 2; - var UNCLOSED_ENV = 3; - - var reportError = function(lastEnv, thisEnv) { - if (!lastEnv) { // unexpected close, nothing was open! - if (documentClosed) { - ErrorFromTo(documentClosed, thisEnv, "\\end{" + documentClosed.name + "} is followed by unexpected end group }",{errorAtStart: true, type: "info"}); - } else { - ErrorTo(thisEnv, "unexpected " + getName(thisEnv)); - }; - return EXTRA_CLOSE; - } else if (lastEnv.command === "{" && thisEnv.command === "end") { - ErrorFromTo(lastEnv, thisEnv, "unclosed " + getName(lastEnv) + " found at " + getName(thisEnv), - {suppressIfEditing:true, errorAtStart: true, type:"warning"}); - return UNCLOSED_GROUP; - } else { - var pLast = envPrecedence(lastEnv); - var pThis = envPrecedence(thisEnv); - if (pThis > pLast) { - ErrorFromTo(lastEnv, thisEnv, "unclosed " + getName(lastEnv) + " found at " + getName(thisEnv), - {suppressIfEditing:true, errorAtStart: true}); - } else { - ErrorFromTo(lastEnv, thisEnv, "unexpected " + getName(thisEnv) + " after " + getName(lastEnv)); - } - return UNCLOSED_ENV; - }; - }; - - this._beginMathMode = function (thisEnv) { - var currentMathMode = this.getMathMode(); // undefined, null, $, $$, name of mathmode env - if (currentMathMode) { - ErrorFrom(thisEnv, thisEnv.name + " used inside existing math mode " + getName(currentMathMode), - {suppressIfEditing:true, errorAtStart: true, mathMode:true}); - }; - thisEnv.mathMode = thisEnv; - state.push(thisEnv); - }; - - this._toggleMathMode = function (thisEnv) { - var lastEnv = state.pop(); - if (closedBy(lastEnv, thisEnv)) { - return; - } else { - if (lastEnv) {state.push(lastEnv);} - if (lastEnv && lastEnv.mathMode) { - this._end(thisEnv); - } else { - thisEnv.mathMode = thisEnv; - state.push(thisEnv); - } - }; - }; - - this.getMathMode = function () { - var n = state.length; - if (n > 0) { - return state[n-1].mathMode; - } else { - return null; - } - }; - - this.insideGroup = function () { - var n = state.length; - if (n > 0) { - return (state[n-1].command === "{"); - } else { - return null; - } - }; - - var resetMathMode = function () { - var n = state.length; - if (n > 0) { - var lastMathMode = state[n-1].mathMode; - do { - var lastEnv = state.pop(); - } while (lastEnv && lastEnv !== lastMathMode); - } else { - return; - } - }; - - this.resetMathMode = resetMathMode; - - var getNewMathMode = function (currentMathMode, thisEnv) { - var newMathMode = null; - - if (thisEnv.command === "{") { - if (thisEnv.mathMode !== null) { - newMathMode = thisEnv.mathMode; - } else { - newMathMode = currentMathMode; - } - } else if (thisEnv.command === "left") { - if (currentMathMode === null) { - ErrorFrom(thisEnv, "\\left can only be used in math mode", {mathMode: true}); - }; - newMathMode = currentMathMode; - } else if (thisEnv.command === "begin") { - var name = thisEnv.name; - if (name) { - if (name.match(/^(document|figure|center|enumerate|itemize|table|abstract|proof|lemma|theorem|definition|proposition|corollary|remark|notation|thebibliography)$/)) { - if (currentMathMode) { - ErrorFromTo(currentMathMode, thisEnv, thisEnv.name + " used inside " + getName(currentMathMode), - {suppressIfEditing:true, errorAtStart: true, mathMode: true}); - resetMathMode(); - }; - newMathMode = null; - } else if (name.match(/^(array|gathered|split|aligned|alignedat)\*?$/)) { - if (currentMathMode === null) { - ErrorFrom(thisEnv, thisEnv.name + " not inside math mode", {mathMode: true}); - }; - newMathMode = currentMathMode; - } else if (name.match(/^(math|displaymath|equation|eqnarray|multline|align|gather|flalign|alignat)\*?$/)) { - if (currentMathMode) { - ErrorFromTo(currentMathMode, thisEnv, thisEnv.name + " used inside " + getName(currentMathMode), - {suppressIfEditing:true, errorAtStart: true, mathMode: true}); - resetMathMode(); - }; - newMathMode = thisEnv; - } else { - newMathMode = undefined; // undefined means we don't know if we are in math mode or not - } - } - }; - return newMathMode; - }; - - this.checkAndUpdateState = function (thisEnv) { - if (inVerbatim) { - if (thisEnv.command === "end") { - this._endVerbatim(thisEnv); - } else { - return; // ignore anything in verbatim environments - } - } else if(thisEnv.command === "begin" || thisEnv.command === "{" || thisEnv.command === "left") { - if (thisEnv.verbatim) {inVerbatim = true;}; - var currentMathMode = this.getMathMode(); // undefined, null, $, $$, name of mathmode env - var newMathMode = getNewMathMode(currentMathMode, thisEnv); - thisEnv.mathMode = newMathMode; - state.push(thisEnv); - } else if (thisEnv.command === "end") { - this._end(thisEnv); - } else if (thisEnv.command === "(" || thisEnv.command === "[") { - this._beginMathMode(thisEnv); - } else if (thisEnv.command === ")" || thisEnv.command === "]") { - this._end(thisEnv); - } else if (thisEnv.command === "}") { - this._end(thisEnv); - } else if (thisEnv.command === "right") { - this._end(thisEnv); - } else if (thisEnv.command === "$" || thisEnv.command === "$$") { - this._toggleMathMode(thisEnv); - } - }; - - this.close = function () { - while (state.length > 0) { - var thisEnv = state.pop(); - if (thisEnv.command === "{") { - ErrorFrom(thisEnv, "unclosed group {", {type:"warning"}); - } else { - ErrorFrom(thisEnv, "unclosed " + getName(thisEnv)); - } - } - var vlen = verbatimRanges.length; - var len = ErrorReporter.tokenErrors.length; - if (vlen >0 && len > 0) { - for (var i = 0; i < len; i++) { - var tokenError = ErrorReporter.tokenErrors[i]; - var startPos = tokenError.startPos; - var endPos = tokenError.endPos; - for (var j = 0; j < vlen; j++) { - if (startPos > verbatimRanges[j].start && startPos < verbatimRanges[j].end) { - tokenError.ignore = true; - break; - } - } - } - } - }; - - this.setEnvProps = function (env) { - var name = env.name ; - if (name && name.match(/^(verbatim|boxedverbatim|lstlisting|minted|Verbatim)$/)) { - env.verbatim = true; - } - }; -}; -var ErrorReporter = function (TokeniseResult) { - var text = TokeniseResult.text; - var linePosition = TokeniseResult.linePosition; - var lineNumber = TokeniseResult.lineNumber; - - var errors = [], tokenErrors = []; - this.errors = errors; - this.tokenErrors = tokenErrors; - this.filterMath = false; - - this.getErrors = function () { - var returnedErrors = []; - for (var i = 0, len = tokenErrors.length; i < len; i++) { - if (!tokenErrors[i].ignore) { returnedErrors.push(tokenErrors[i]); } - } - var allErrors = returnedErrors.concat(errors); - var result = []; - - var mathErrorCount = 0; - for (i = 0, len = allErrors.length; i < len; i++) { - if (allErrors[i].mathMode) { - mathErrorCount++; - } - if (mathErrorCount > 10) { - return []; - } - } - - if (this.filterMath && mathErrorCount > 0) { - for (i = 0, len = allErrors.length; i < len; i++) { - if (!allErrors[i].mathMode) { - result.push(allErrors[i]); - } - } - return result; - } else { - return allErrors; - } - }; - - this.TokenError = function (token, message, options) { - if(!options) { options = { suppressIfEditing:true } ; }; - var line = token[0], type = token[1], start = token[2], end = token[3]; - var start_col = start - linePosition[line]; - if (!end) { end = start + 1; } ; - var end_col = end - linePosition[line]; - tokenErrors.push({row: line, - column: start_col, - start_row:line, - start_col: start_col, - end_row:line, - end_col: end_col, - type:"error", - text:message, - startPos: start, - endPos: end, - suppressIfEditing:options.suppressIfEditing, - mathMode: options.mathMode}); - }; - - this.TokenErrorFromTo = function (fromToken, toToken, message, options) { - if(!options) { options = {suppressIfEditing:true } ; }; - var fromLine = fromToken[0], fromStart = fromToken[2], fromEnd = fromToken[3]; - var toLine = toToken[0], toStart = toToken[2], toEnd = toToken[3]; - if (!toEnd) { toEnd = toStart + 1;}; - var start_col = fromStart - linePosition[fromLine]; - var end_col = toEnd - linePosition[toLine]; - - tokenErrors.push({row: fromLine, - column: start_col, - start_row: fromLine, - start_col: start_col, - end_row: toLine, - end_col: end_col, - type:"error", - text:message, - startPos: fromStart, - endPos: toEnd, - suppressIfEditing:options.suppressIfEditing, - mathMode: options.mathMode}); - }; - - - this.EnvErrorFromTo = function (fromEnv, toEnv, message, options) { - if(!options) { options = {} ; }; - var fromToken = fromEnv.token, toToken = toEnv.closeToken || toEnv.token; - var fromLine = fromToken[0], fromStart = fromToken[2], fromEnd = fromToken[3]; - if (!toToken) {toToken = fromToken;}; - var toLine = toToken[0], toStart = toToken[2], toEnd = toToken[3]; - if (!toEnd) { toEnd = toStart + 1;}; - var start_col = fromStart - linePosition[fromLine]; - var end_col = toEnd - linePosition[toLine]; - errors.push({row: options.errorAtStart ? fromLine : toLine, - column: options.errorAtStart ? start_col: end_col, - start_row:fromLine, - start_col: start_col, - end_row:toLine, - end_col: end_col, - type: options.type ? options.type : "error", - text:message, - suppressIfEditing:options.suppressIfEditing, - mathMode: options.mathMode}); - }; - - this.EnvErrorTo = function (toEnv, message, options) { - if(!options) { options = {} ; }; - var token = toEnv.closeToken || toEnv.token; - var line = token[0], type = token[1], start = token[2], end = token[3]; - if (!end) { end = start + 1; }; - var end_col = end - linePosition[line]; - var err = {row: line, - column: end_col, - start_row:0, - start_col: 0, - end_row: line, - end_col: end_col, - type: options.type ? options.type : "error", - text:message, - mathMode: options.mathMode}; - errors.push(err); - }; - - this.EnvErrorFrom = function (env, message, options) { - if(!options) { options = {} ; }; - var token = env.token; - var line = token[0], type = token[1], start = token[2], end = token[3]; - var start_col = start - linePosition[line]; - var end_col = Infinity; - errors.push({row: line, - column: start_col, - start_row:line, - start_col: start_col, - end_row: lineNumber, - end_col: end_col, - type: options.type ? options.type : "error", - text:message, - mathMode: options.mathMode}); - }; -}; - -var Parse = function (text) { - var TokeniseResult = Tokenise(text); - var Reporter = new ErrorReporter(TokeniseResult); - var Environments = InterpretTokens(TokeniseResult, Reporter); - Environments.close(); - return Reporter.getErrors(); -}; - -(function() { - var disabled = false; - - this.onUpdate = function() { - if (disabled) { return ; }; - - var value = this.doc.getValue(); - var errors = []; - try { - if (value) - errors = Parse(value); - } catch (e) { - disabled = true; - errors = []; - } - this.sender.emit("lint", errors); - }; - -}).call(LatexWorker.prototype); - -}); - -ace.define("ace/lib/es5-shim",["require","exports","module"], function(require, exports, module) { - -function Empty() {} - -if (!Function.prototype.bind) { - Function.prototype.bind = function bind(that) { // .length is 1 - var target = this; - if (typeof target != "function") { - throw new TypeError("Function.prototype.bind called on incompatible " + target); - } - var args = slice.call(arguments, 1); // for normal call - var bound = function () { - - if (this instanceof bound) { - - var result = target.apply( - this, - args.concat(slice.call(arguments)) - ); - if (Object(result) === result) { - return result; - } - return this; - - } else { - return target.apply( - that, - args.concat(slice.call(arguments)) - ); - - } - - }; - if(target.prototype) { - Empty.prototype = target.prototype; - bound.prototype = new Empty(); - Empty.prototype = null; - } - return bound; - }; -} -var call = Function.prototype.call; -var prototypeOfArray = Array.prototype; -var prototypeOfObject = Object.prototype; -var slice = prototypeOfArray.slice; -var _toString = call.bind(prototypeOfObject.toString); -var owns = call.bind(prototypeOfObject.hasOwnProperty); -var defineGetter; -var defineSetter; -var lookupGetter; -var lookupSetter; -var supportsAccessors; -if ((supportsAccessors = owns(prototypeOfObject, "__defineGetter__"))) { - defineGetter = call.bind(prototypeOfObject.__defineGetter__); - defineSetter = call.bind(prototypeOfObject.__defineSetter__); - lookupGetter = call.bind(prototypeOfObject.__lookupGetter__); - lookupSetter = call.bind(prototypeOfObject.__lookupSetter__); -} -if ([1,2].splice(0).length != 2) { - if(function() { // test IE < 9 to splice bug - see issue #138 - function makeArray(l) { - var a = new Array(l+2); - a[0] = a[1] = 0; - return a; - } - var array = [], lengthBefore; - - array.splice.apply(array, makeArray(20)); - array.splice.apply(array, makeArray(26)); - - lengthBefore = array.length; //46 - array.splice(5, 0, "XXX"); // add one element - - lengthBefore + 1 == array.length - - if (lengthBefore + 1 == array.length) { - return true;// has right splice implementation without bugs - } - }()) {//IE 6/7 - var array_splice = Array.prototype.splice; - Array.prototype.splice = function(start, deleteCount) { - if (!arguments.length) { - return []; - } else { - return array_splice.apply(this, [ - start === void 0 ? 0 : start, - deleteCount === void 0 ? (this.length - start) : deleteCount - ].concat(slice.call(arguments, 2))) - } - }; - } else {//IE8 - Array.prototype.splice = function(pos, removeCount){ - var length = this.length; - if (pos > 0) { - if (pos > length) - pos = length; - } else if (pos == void 0) { - pos = 0; - } else if (pos < 0) { - pos = Math.max(length + pos, 0); - } - - if (!(pos+removeCount < length)) - removeCount = length - pos; - - var removed = this.slice(pos, pos+removeCount); - var insert = slice.call(arguments, 2); - var add = insert.length; - if (pos === length) { - if (add) { - this.push.apply(this, insert); - } - } else { - var remove = Math.min(removeCount, length - pos); - var tailOldPos = pos + remove; - var tailNewPos = tailOldPos + add - remove; - var tailCount = length - tailOldPos; - var lengthAfterRemove = length - remove; - - if (tailNewPos < tailOldPos) { // case A - for (var i = 0; i < tailCount; ++i) { - this[tailNewPos+i] = this[tailOldPos+i]; - } - } else if (tailNewPos > tailOldPos) { // case B - for (i = tailCount; i--; ) { - this[tailNewPos+i] = this[tailOldPos+i]; - } - } // else, add == remove (nothing to do) - - if (add && pos === lengthAfterRemove) { - this.length = lengthAfterRemove; // truncate array - this.push.apply(this, insert); - } else { - this.length = lengthAfterRemove + add; // reserves space - for (i = 0; i < add; ++i) { - this[pos+i] = insert[i]; - } - } - } - return removed; - }; - } -} -if (!Array.isArray) { - Array.isArray = function isArray(obj) { - return _toString(obj) == "[object Array]"; - }; -} -var boxedString = Object("a"), - splitString = boxedString[0] != "a" || !(0 in boxedString); - -if (!Array.prototype.forEach) { - Array.prototype.forEach = function forEach(fun /*, thisp*/) { - var object = toObject(this), - self = splitString && _toString(this) == "[object String]" ? - this.split("") : - object, - thisp = arguments[1], - i = -1, - length = self.length >>> 0; - if (_toString(fun) != "[object Function]") { - throw new TypeError(); // TODO message - } - - while (++i < length) { - if (i in self) { - fun.call(thisp, self[i], i, object); - } - } - }; -} -if (!Array.prototype.map) { - Array.prototype.map = function map(fun /*, thisp*/) { - var object = toObject(this), - self = splitString && _toString(this) == "[object String]" ? - this.split("") : - object, - length = self.length >>> 0, - result = Array(length), - thisp = arguments[1]; - if (_toString(fun) != "[object Function]") { - throw new TypeError(fun + " is not a function"); - } - - for (var i = 0; i < length; i++) { - if (i in self) - result[i] = fun.call(thisp, self[i], i, object); - } - return result; - }; -} -if (!Array.prototype.filter) { - Array.prototype.filter = function filter(fun /*, thisp */) { - var object = toObject(this), - self = splitString && _toString(this) == "[object String]" ? - this.split("") : - object, - length = self.length >>> 0, - result = [], - value, - thisp = arguments[1]; - if (_toString(fun) != "[object Function]") { - throw new TypeError(fun + " is not a function"); - } - - for (var i = 0; i < length; i++) { - if (i in self) { - value = self[i]; - if (fun.call(thisp, value, i, object)) { - result.push(value); - } - } - } - return result; - }; -} -if (!Array.prototype.every) { - Array.prototype.every = function every(fun /*, thisp */) { - var object = toObject(this), - self = splitString && _toString(this) == "[object String]" ? - this.split("") : - object, - length = self.length >>> 0, - thisp = arguments[1]; - if (_toString(fun) != "[object Function]") { - throw new TypeError(fun + " is not a function"); - } - - for (var i = 0; i < length; i++) { - if (i in self && !fun.call(thisp, self[i], i, object)) { - return false; - } - } - return true; - }; -} -if (!Array.prototype.some) { - Array.prototype.some = function some(fun /*, thisp */) { - var object = toObject(this), - self = splitString && _toString(this) == "[object String]" ? - this.split("") : - object, - length = self.length >>> 0, - thisp = arguments[1]; - if (_toString(fun) != "[object Function]") { - throw new TypeError(fun + " is not a function"); - } - - for (var i = 0; i < length; i++) { - if (i in self && fun.call(thisp, self[i], i, object)) { - return true; - } - } - return false; - }; -} -if (!Array.prototype.reduce) { - Array.prototype.reduce = function reduce(fun /*, initial*/) { - var object = toObject(this), - self = splitString && _toString(this) == "[object String]" ? - this.split("") : - object, - length = self.length >>> 0; - if (_toString(fun) != "[object Function]") { - throw new TypeError(fun + " is not a function"); - } - if (!length && arguments.length == 1) { - throw new TypeError("reduce of empty array with no initial value"); - } - - var i = 0; - var result; - if (arguments.length >= 2) { - result = arguments[1]; - } else { - do { - if (i in self) { - result = self[i++]; - break; - } - if (++i >= length) { - throw new TypeError("reduce of empty array with no initial value"); - } - } while (true); - } - - for (; i < length; i++) { - if (i in self) { - result = fun.call(void 0, result, self[i], i, object); - } - } - - return result; - }; -} -if (!Array.prototype.reduceRight) { - Array.prototype.reduceRight = function reduceRight(fun /*, initial*/) { - var object = toObject(this), - self = splitString && _toString(this) == "[object String]" ? - this.split("") : - object, - length = self.length >>> 0; - if (_toString(fun) != "[object Function]") { - throw new TypeError(fun + " is not a function"); - } - if (!length && arguments.length == 1) { - throw new TypeError("reduceRight of empty array with no initial value"); - } - - var result, i = length - 1; - if (arguments.length >= 2) { - result = arguments[1]; - } else { - do { - if (i in self) { - result = self[i--]; - break; - } - if (--i < 0) { - throw new TypeError("reduceRight of empty array with no initial value"); - } - } while (true); - } - - do { - if (i in this) { - result = fun.call(void 0, result, self[i], i, object); - } - } while (i--); - - return result; - }; -} -if (!Array.prototype.indexOf || ([0, 1].indexOf(1, 2) != -1)) { - Array.prototype.indexOf = function indexOf(sought /*, fromIndex */ ) { - var self = splitString && _toString(this) == "[object String]" ? - this.split("") : - toObject(this), - length = self.length >>> 0; - - if (!length) { - return -1; - } - - var i = 0; - if (arguments.length > 1) { - i = toInteger(arguments[1]); - } - i = i >= 0 ? i : Math.max(0, length + i); - for (; i < length; i++) { - if (i in self && self[i] === sought) { - return i; - } - } - return -1; - }; -} -if (!Array.prototype.lastIndexOf || ([0, 1].lastIndexOf(0, -3) != -1)) { - Array.prototype.lastIndexOf = function lastIndexOf(sought /*, fromIndex */) { - var self = splitString && _toString(this) == "[object String]" ? - this.split("") : - toObject(this), - length = self.length >>> 0; - - if (!length) { - return -1; - } - var i = length - 1; - if (arguments.length > 1) { - i = Math.min(i, toInteger(arguments[1])); - } - i = i >= 0 ? i : length - Math.abs(i); - for (; i >= 0; i--) { - if (i in self && sought === self[i]) { - return i; - } - } - return -1; - }; -} -if (!Object.getPrototypeOf) { - Object.getPrototypeOf = function getPrototypeOf(object) { - return object.__proto__ || ( - object.constructor ? - object.constructor.prototype : - prototypeOfObject - ); - }; -} -if (!Object.getOwnPropertyDescriptor) { - var ERR_NON_OBJECT = "Object.getOwnPropertyDescriptor called on a " + - "non-object: "; - Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(object, property) { - if ((typeof object != "object" && typeof object != "function") || object === null) - throw new TypeError(ERR_NON_OBJECT + object); - if (!owns(object, property)) - return; - - var descriptor, getter, setter; - descriptor = { enumerable: true, configurable: true }; - if (supportsAccessors) { - var prototype = object.__proto__; - object.__proto__ = prototypeOfObject; - - var getter = lookupGetter(object, property); - var setter = lookupSetter(object, property); - object.__proto__ = prototype; - - if (getter || setter) { - if (getter) descriptor.get = getter; - if (setter) descriptor.set = setter; - return descriptor; - } - } - descriptor.value = object[property]; - return descriptor; - }; -} -if (!Object.getOwnPropertyNames) { - Object.getOwnPropertyNames = function getOwnPropertyNames(object) { - return Object.keys(object); - }; -} -if (!Object.create) { - var createEmpty; - if (Object.prototype.__proto__ === null) { - createEmpty = function () { - return { "__proto__": null }; - }; - } else { - createEmpty = function () { - var empty = {}; - for (var i in empty) - empty[i] = null; - empty.constructor = - empty.hasOwnProperty = - empty.propertyIsEnumerable = - empty.isPrototypeOf = - empty.toLocaleString = - empty.toString = - empty.valueOf = - empty.__proto__ = null; - return empty; - } - } - - Object.create = function create(prototype, properties) { - var object; - if (prototype === null) { - object = createEmpty(); - } else { - if (typeof prototype != "object") - throw new TypeError("typeof prototype["+(typeof prototype)+"] != 'object'"); - var Type = function () {}; - Type.prototype = prototype; - object = new Type(); - object.__proto__ = prototype; - } - if (properties !== void 0) - Object.defineProperties(object, properties); - return object; - }; -} - -function doesDefinePropertyWork(object) { - try { - Object.defineProperty(object, "sentinel", {}); - return "sentinel" in object; - } catch (exception) { - } -} -if (Object.defineProperty) { - var definePropertyWorksOnObject = doesDefinePropertyWork({}); - var definePropertyWorksOnDom = typeof document == "undefined" || - doesDefinePropertyWork(document.createElement("div")); - if (!definePropertyWorksOnObject || !definePropertyWorksOnDom) { - var definePropertyFallback = Object.defineProperty; - } -} - -if (!Object.defineProperty || definePropertyFallback) { - var ERR_NON_OBJECT_DESCRIPTOR = "Property description must be an object: "; - var ERR_NON_OBJECT_TARGET = "Object.defineProperty called on non-object: " - var ERR_ACCESSORS_NOT_SUPPORTED = "getters & setters can not be defined " + - "on this javascript engine"; - - Object.defineProperty = function defineProperty(object, property, descriptor) { - if ((typeof object != "object" && typeof object != "function") || object === null) - throw new TypeError(ERR_NON_OBJECT_TARGET + object); - if ((typeof descriptor != "object" && typeof descriptor != "function") || descriptor === null) - throw new TypeError(ERR_NON_OBJECT_DESCRIPTOR + descriptor); - if (definePropertyFallback) { - try { - return definePropertyFallback.call(Object, object, property, descriptor); - } catch (exception) { - } - } - if (owns(descriptor, "value")) { - - if (supportsAccessors && (lookupGetter(object, property) || - lookupSetter(object, property))) - { - var prototype = object.__proto__; - object.__proto__ = prototypeOfObject; - delete object[property]; - object[property] = descriptor.value; - object.__proto__ = prototype; - } else { - object[property] = descriptor.value; - } - } else { - if (!supportsAccessors) - throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED); - if (owns(descriptor, "get")) - defineGetter(object, property, descriptor.get); - if (owns(descriptor, "set")) - defineSetter(object, property, descriptor.set); - } - - return object; - }; -} -if (!Object.defineProperties) { - Object.defineProperties = function defineProperties(object, properties) { - for (var property in properties) { - if (owns(properties, property)) - Object.defineProperty(object, property, properties[property]); - } - return object; - }; -} -if (!Object.seal) { - Object.seal = function seal(object) { - return object; - }; -} -if (!Object.freeze) { - Object.freeze = function freeze(object) { - return object; - }; -} -try { - Object.freeze(function () {}); -} catch (exception) { - Object.freeze = (function freeze(freezeObject) { - return function freeze(object) { - if (typeof object == "function") { - return object; - } else { - return freezeObject(object); - } - }; - })(Object.freeze); -} -if (!Object.preventExtensions) { - Object.preventExtensions = function preventExtensions(object) { - return object; - }; -} -if (!Object.isSealed) { - Object.isSealed = function isSealed(object) { - return false; - }; -} -if (!Object.isFrozen) { - Object.isFrozen = function isFrozen(object) { - return false; - }; -} -if (!Object.isExtensible) { - Object.isExtensible = function isExtensible(object) { - if (Object(object) === object) { - throw new TypeError(); // TODO message - } - var name = ''; - while (owns(object, name)) { - name += '?'; - } - object[name] = true; - var returnValue = owns(object, name); - delete object[name]; - return returnValue; - }; -} -if (!Object.keys) { - var hasDontEnumBug = true, - dontEnums = [ - "toString", - "toLocaleString", - "valueOf", - "hasOwnProperty", - "isPrototypeOf", - "propertyIsEnumerable", - "constructor" - ], - dontEnumsLength = dontEnums.length; - - for (var key in {"toString": null}) { - hasDontEnumBug = false; - } - - Object.keys = function keys(object) { - - if ( - (typeof object != "object" && typeof object != "function") || - object === null - ) { - throw new TypeError("Object.keys called on a non-object"); - } - - var keys = []; - for (var name in object) { - if (owns(object, name)) { - keys.push(name); - } - } - - if (hasDontEnumBug) { - for (var i = 0, ii = dontEnumsLength; i < ii; i++) { - var dontEnum = dontEnums[i]; - if (owns(object, dontEnum)) { - keys.push(dontEnum); - } - } - } - return keys; - }; - -} -if (!Date.now) { - Date.now = function now() { - return new Date().getTime(); - }; -} -var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" + - "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" + - "\u2029\uFEFF"; -if (!String.prototype.trim || ws.trim()) { - ws = "[" + ws + "]"; - var trimBeginRegexp = new RegExp("^" + ws + ws + "*"), - trimEndRegexp = new RegExp(ws + ws + "*$"); - String.prototype.trim = function trim() { - return String(this).replace(trimBeginRegexp, "").replace(trimEndRegexp, ""); - }; -} - -function toInteger(n) { - n = +n; - if (n !== n) { // isNaN - n = 0; - } else if (n !== 0 && n !== (1/0) && n !== -(1/0)) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); - } - return n; -} - -function isPrimitive(input) { - var type = typeof input; - return ( - input === null || - type === "undefined" || - type === "boolean" || - type === "number" || - type === "string" - ); -} - -function toPrimitive(input) { - var val, valueOf, toString; - if (isPrimitive(input)) { - return input; - } - valueOf = input.valueOf; - if (typeof valueOf === "function") { - val = valueOf.call(input); - if (isPrimitive(val)) { - return val; - } - } - toString = input.toString; - if (typeof toString === "function") { - val = toString.call(input); - if (isPrimitive(val)) { - return val; - } - } - throw new TypeError(); -} -var toObject = function (o) { - if (o == null) { // this matches both null and undefined - throw new TypeError("can't convert "+o+" to object"); - } - return Object(o); -}; - -}); From 0f131d940d88eb8a83422f014d5a909717f46ec2 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Mon, 24 Jul 2017 11:06:47 +0100 Subject: [PATCH 03/26] Enforce stricter password policy. - Check minimum password lengths - Set default policy to 6-128 chars --- .../AuthenticationManager.coffee | 6 +++++- services/web/config/settings.defaults.coffee | 4 ++-- .../public/coffee/directives/asyncForm.coffee | 9 +++++---- .../AuthenticationManagerTests.coffee | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/services/web/app/coffee/Features/Authentication/AuthenticationManager.coffee b/services/web/app/coffee/Features/Authentication/AuthenticationManager.coffee index b661455028..49bd994b2c 100644 --- a/services/web/app/coffee/Features/Authentication/AuthenticationManager.coffee +++ b/services/web/app/coffee/Features/Authentication/AuthenticationManager.coffee @@ -29,8 +29,12 @@ module.exports = AuthenticationManager = callback null, null setUserPassword: (user_id, password, callback = (error) ->) -> - if Settings.passwordStrengthOptions?.length?.max? and Settings.passwordStrengthOptions?.length?.max < password.length + if (Settings.passwordStrengthOptions?.length?.max? and + Settings.passwordStrengthOptions?.length?.max < password.length) return callback("password is too long") + if (Settings.passwordStrengthOptions?.length?.min? and + Settings.passwordStrengthOptions?.length?.min > password.length) + return callback("password is too short") bcrypt.genSalt BCRYPT_ROUNDS, (error, salt) -> return callback(error) if error? diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 8fb00aff31..046903de7b 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -226,8 +226,8 @@ module.exports = settings = # passwordStrengthOptions: # pattern: "aA$3" # length: - # min: 1 - # max: 10 + # min: 6 + # max: 128 # Email support # ------------- diff --git a/services/web/public/coffee/directives/asyncForm.coffee b/services/web/public/coffee/directives/asyncForm.coffee index 2fc336c934..0e6ae19ec2 100644 --- a/services/web/public/coffee/directives/asyncForm.coffee +++ b/services/web/public/coffee/directives/asyncForm.coffee @@ -103,8 +103,8 @@ define [ defaultPasswordOpts = pattern: "" length: - min: 1 - max: 50 + min: 6 + max: 128 allowEmpty: false allowAnyChars: false isMasked: true @@ -127,8 +127,6 @@ define [ [asyncFormCtrl, ngModelCtrl] = ctrl ngModelCtrl.$parsers.unshift (modelValue) -> - - isValid = passField.validatePass() email = asyncFormCtrl.getEmail() || window.usersEmail if !isValid @@ -141,5 +139,8 @@ define [ if opts.length.max? and modelValue.length == opts.length.max isValid = false scope.complexPasswordErrorMessage = "Maximum password length #{opts.length.max} reached" + if opts.length.min? and modelValue.length < opts.length.min + isValid = false + scope.complexPasswordErrorMessage = "Password too short, minimum #{opts.length.min}" ngModelCtrl.$setValidity('complexPassword', isValid) return modelValue diff --git a/services/web/test/UnitTests/coffee/Authentication/AuthenticationManagerTests.coffee b/services/web/test/UnitTests/coffee/Authentication/AuthenticationManagerTests.coffee index 0ebf9f8112..0a041a0865 100644 --- a/services/web/test/UnitTests/coffee/Authentication/AuthenticationManagerTests.coffee +++ b/services/web/test/UnitTests/coffee/Authentication/AuthenticationManagerTests.coffee @@ -116,6 +116,24 @@ describe "AuthenticationManager", -> expect(err).to.exist done() + it "should not start the bcrypt process", (done)-> + @AuthenticationManager.setUserPassword @user_id, @password, (err)=> + @bcrypt.genSalt.called.should.equal false + @bcrypt.hash.called.should.equal false + done() + + describe "too short", -> + beforeEach -> + @settings.passwordStrengthOptions = + length: + max:10 + min:6 + @password = "dsd" + + it "should return and error", (done)-> + @AuthenticationManager.setUserPassword @user_id, @password, (err)-> + expect(err).to.exist + done() it "should not start the bcrypt process", (done)-> @AuthenticationManager.setUserPassword @user_id, @password, (err)=> From b44d0bfb858f83e46948060345feb7cfe3568bc4 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 28 Jul 2017 14:41:33 +0100 Subject: [PATCH 04/26] Ignore deleted projects when determining the number of uncategorized projects. --- .../web/public/coffee/main/project-list/project-list.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/public/coffee/main/project-list/project-list.coffee b/services/web/public/coffee/main/project-list/project-list.coffee index 5d0341656e..cafbd7c81b 100644 --- a/services/web/public/coffee/main/project-list/project-list.coffee +++ b/services/web/public/coffee/main/project-list/project-list.coffee @@ -20,7 +20,7 @@ define [ , 10 $scope.$watch(( - () -> $scope.projects.filter((project) -> !project.tags? or project.tags.length == 0).length + () -> $scope.projects.filter((project) -> (!project.tags? or project.tags.length == 0) and !project.archived).length ), (newVal) -> $scope.nUntagged = newVal) storedUIOpts = JSON.parse(localStorage("project_list")) From 2fcbafa72d56df186465d5d29d1455dbf93d6190 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 28 Jul 2017 17:30:57 +0100 Subject: [PATCH 05/26] Add HTML encoder lib. --- services/web/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/package.json b/services/web/package.json index eac07a06e5..14c7f00070 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -41,6 +41,7 @@ "mongojs": "2.4.0", "mongoose": "4.11.4", "multer": "^0.1.8", + "node-html-encoder": "0.0.2", "nodemailer": "2.1.0", "nodemailer-sendgrid-transport": "^0.2.0", "nodemailer-ses-transport": "^1.3.0", From 4849c705de8368d036639cf3ecb5b884d7f9023d Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 28 Jul 2017 17:31:28 +0100 Subject: [PATCH 06/26] Optionally ask the translate local method to HTML encode; use it in the problematic tooltip. --- services/web/app/coffee/infrastructure/ExpressLocals.coffee | 6 ++++-- services/web/app/views/project/editor/editor.pug | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index 4c6b3a7722..498127cdbd 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -11,6 +11,7 @@ async = require("async") Modules = require "./Modules" Url = require "url" PackageVersions = require "./PackageVersions" +htmlEncoder = new require("node-html-encoder").Encoder("numerical") fingerprints = {} Path = require 'path' @@ -151,9 +152,10 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)-> next() webRouter.use (req, res, next)-> - res.locals.translate = (key, vars = {}) -> + res.locals.translate = (key, vars = {}, htmlEncode = false) -> vars.appName = Settings.appName - req.i18n.translate(key, vars) + str = req.i18n.translate(key, vars) + if htmlEncode then htmlEncoder.htmlEncode(str) else str # Don't include the query string parameters, otherwise Google # treats ?nocdn=true as the canonical version res.locals.currentUrl = Url.parse(req.originalUrl).pathname diff --git a/services/web/app/views/project/editor/editor.pug b/services/web/app/views/project/editor/editor.pug index 8b58c98bcd..6007f2e0be 100644 --- a/services/web/app/views/project/editor/editor.pug +++ b/services/web/app/views/project/editor/editor.pug @@ -82,7 +82,7 @@ div.full-size( i.fa.fa-long-arrow-right br a.btn.btn-default.btn-xs( - tooltip-html="'"+translate('go_to_pdf_location_in_code')+"'" + tooltip-html="'"+translate('go_to_pdf_location_in_code', {}, true)+"'" tooltip-placement="right" tooltip-append-to-body="true" ng-click="syncToCode()" From eaf9ae5b948767db3f10d3e4ccc08d8b4abd17cb Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Thu, 27 Jul 2017 11:57:01 +0100 Subject: [PATCH 07/26] Begin work on autocomplete for `includegraphics` --- .../ide/editor/directives/aceEditor.coffee | 6 +-- .../auto-complete/AutoCompleteManager.coffee | 40 ++++++++++++++++++- .../ide/file-tree/FileTreeManager.coffee | 2 + .../ide/graphics/services/graphics.coffee | 16 ++++++++ .../ide/preamble/services/preamble.coffee | 12 ++++++ 5 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 services/web/public/coffee/ide/graphics/services/graphics.coffee create mode 100644 services/web/public/coffee/ide/preamble/services/preamble.coffee diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index af633ea369..990678ddc4 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -11,6 +11,7 @@ define [ "ide/editor/directives/aceEditor/track-changes/TrackChangesManager" "ide/editor/directives/aceEditor/labels/LabelsManager" "ide/labels/services/labels" + "ide/graphics/services/graphics" ], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, LabelsManager) -> EditSession = ace.require('ace/edit_session').EditSession ModeList = ace.require('ace/ext/modelist') @@ -33,9 +34,8 @@ define [ url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}" return url - App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels) -> + App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, graphics) -> monkeyPatchSearch($rootScope, $compile) - return { scope: { @@ -102,7 +102,7 @@ define [ cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage) trackChangesManager = new TrackChangesManager(scope, editor, element) labelsManager = new LabelsManager(scope, editor, element, labels) - autoCompleteManager = new AutoCompleteManager(scope, editor, element, labelsManager) + autoCompleteManager = new AutoCompleteManager(scope, editor, element, labelsManager, graphics) # Prevert Ctrl|Cmd-S from triggering save dialog diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee index c320266a5a..6259e893ab 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee @@ -17,7 +17,7 @@ define [ commandFragment?.match(/\\(\w+)\{/)?[1] class AutoCompleteManager - constructor: (@$scope, @editor, @element, @labelsManager) -> + constructor: (@$scope, @editor, @element, @labelsManager, @graphics) -> @suggestionManager = new SuggestionManager() @monkeyPatchAutocomplete() @@ -44,6 +44,36 @@ define [ SnippetCompleter = new SnippetManager() + Graphics = @graphics + GraphicsCompleter = + getCompletions: (editor, session, pos, prefix, callback) -> + upToCursorRange = new Range(pos.row, 0, pos.row, pos.column) + lineUpToCursor = editor.getSession().getTextRange(upToCursorRange) + commandFragment = getLastCommandFragment(lineUpToCursor) + if commandFragment + match = commandFragment.match(/^~?\\(includegraphics(?:\[.*])?){([^}]*, *)?(\w*)/) + if match + beyondCursorRange = new Range(pos.row, pos.column, pos.row, 99999) + lineBeyondCursor = editor.getSession().getTextRange(beyondCursorRange) + needsClosingBrace = !lineBeyondCursor.match(/^[^{]*}/) + commandName = match[1] + currentArg = match[3] + result = [] + # result.push { + # caption: "\\#{commandName}{}", + # snippet: "\\#{commandName}{}", + # meta: "graphic", + # score: 60 + # } + for graphic in Graphics.getGraphicsFiles() + result.push { + caption: "\\#{commandName}{#{graphic.path}#{if needsClosingBrace then '}' else ''}", + value: "\\#{commandName}{#{graphic.path}#{if needsClosingBrace then '}' else ''}", + meta: "graphic", + score: 50 + } + callback null, result + labelsManager = @labelsManager LabelsCompleter = getCompletions: (editor, session, pos, prefix, callback) -> @@ -112,7 +142,13 @@ define [ else callback null, result - @editor.completers = [@suggestionManager, SnippetCompleter, ReferencesCompleter, LabelsCompleter] + @editor.completers = [ + @suggestionManager, + SnippetCompleter, + ReferencesCompleter, + LabelsCompleter, + GraphicsCompleter + ] disable: () -> @editor.setOptions({ diff --git a/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee b/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee index 0881e75cc8..f6b8a92710 100644 --- a/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee +++ b/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee @@ -202,6 +202,8 @@ define [ childPath = path + "/" + entity.name else childPath = entity.name + # FIXME: this is a hack + entity.path = childPath callback(entity, folder, childPath) if entity.children? @_forEachEntityInFolder(entity, childPath, callback) diff --git a/services/web/public/coffee/ide/graphics/services/graphics.coffee b/services/web/public/coffee/ide/graphics/services/graphics.coffee new file mode 100644 index 0000000000..6e0a03e849 --- /dev/null +++ b/services/web/public/coffee/ide/graphics/services/graphics.coffee @@ -0,0 +1,16 @@ +define [ + "base" +], (App) -> + + App.factory 'graphics', (ide) -> + + graphics = { + getGraphicsFiles: () -> + graphicsFiles = [] + ide.fileTreeManager.forEachEntity (f) -> + if f?.name?.match?(/.*\.(png|jpg|jpeg)/) + graphicsFiles.push f + return graphicsFiles + } + + return graphics diff --git a/services/web/public/coffee/ide/preamble/services/preamble.coffee b/services/web/public/coffee/ide/preamble/services/preamble.coffee new file mode 100644 index 0000000000..114f530d77 --- /dev/null +++ b/services/web/public/coffee/ide/preamble/services/preamble.coffee @@ -0,0 +1,12 @@ +define [ + "base" +], (App) -> + + App.factory 'preamble', (ide) -> + + Preamble = { + getPreambleText: () -> + + } + + return Preamble From a841646559b7d4495bec59e3c16442b6d8ffb2f9 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Mon, 31 Jul 2017 11:28:21 +0100 Subject: [PATCH 08/26] Add preamble parser --- .../ide/preamble/preamble/services/preamble.coffee | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 services/web/public/coffee/ide/preamble/preamble/services/preamble.coffee diff --git a/services/web/public/coffee/ide/preamble/preamble/services/preamble.coffee b/services/web/public/coffee/ide/preamble/preamble/services/preamble.coffee new file mode 100644 index 0000000000..bd52713952 --- /dev/null +++ b/services/web/public/coffee/ide/preamble/preamble/services/preamble.coffee @@ -0,0 +1,14 @@ +define [ + "base" +], (App) -> + + App.factory 'preamble', (ide) -> + + Preamble = { + getPreambleText: () -> + text = ide.editorManager.getCurrentDocValue().slice(0, 5000) + preamble = text.match(/([^]*)^\\begin\{document\}/m)[1] + + } + + return Preamble From eeabac76988815ed7d34e065f2f831df354aca83 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Mon, 31 Jul 2017 11:28:52 +0100 Subject: [PATCH 09/26] Get graphics paths from preamble --- .../ide/editor/directives/aceEditor.coffee | 3 ++- .../ide/graphics/services/graphics.coffee | 5 ++--- .../ide/preamble/services/preamble.coffee | 18 +++++++++++++++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index 990678ddc4..19c34142c5 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -12,6 +12,7 @@ define [ "ide/editor/directives/aceEditor/labels/LabelsManager" "ide/labels/services/labels" "ide/graphics/services/graphics" + "ide/preamble/services/preamble" ], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, LabelsManager) -> EditSession = ace.require('ace/edit_session').EditSession ModeList = ace.require('ace/ext/modelist') @@ -34,7 +35,7 @@ define [ url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}" return url - App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, graphics) -> + App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, graphics, preamble) -> monkeyPatchSearch($rootScope, $compile) return { diff --git a/services/web/public/coffee/ide/graphics/services/graphics.coffee b/services/web/public/coffee/ide/graphics/services/graphics.coffee index 6e0a03e849..fdd4126b16 100644 --- a/services/web/public/coffee/ide/graphics/services/graphics.coffee +++ b/services/web/public/coffee/ide/graphics/services/graphics.coffee @@ -4,13 +4,12 @@ define [ App.factory 'graphics', (ide) -> - graphics = { + Graphics = getGraphicsFiles: () -> graphicsFiles = [] ide.fileTreeManager.forEachEntity (f) -> if f?.name?.match?(/.*\.(png|jpg|jpeg)/) graphicsFiles.push f return graphicsFiles - } - return graphics + return Graphics diff --git a/services/web/public/coffee/ide/preamble/services/preamble.coffee b/services/web/public/coffee/ide/preamble/services/preamble.coffee index 114f530d77..a8312ca4d7 100644 --- a/services/web/public/coffee/ide/preamble/services/preamble.coffee +++ b/services/web/public/coffee/ide/preamble/services/preamble.coffee @@ -4,9 +4,21 @@ define [ App.factory 'preamble', (ide) -> - Preamble = { + Preamble = getPreambleText: () -> - - } + text = ide.editorManager.getCurrentDocValue().slice(0, 5000) + preamble = text.match(/([^]*)^\\begin\{document\}/m)?[1] || "" + return preamble + + getGraphicsPaths: () -> + preamble = Preamble.getPreambleText() + graphicsPathsArgs = preamble.match(/\\graphicspath\{(.*)\}/)?[1] || "" + paths = [] + re = /\{([^}]*)\}/g + while match = re.exec(graphicsPathsArgs) + paths.push(match[1]) + return paths + + window.Preamble = Preamble return Preamble From b5486155c0e1e411c9f7a65fcbd864e15fec629a Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 31 Jul 2017 14:01:22 +0100 Subject: [PATCH 10/26] Update shrinkwrap to include the HTML encoder. --- services/web/npm-shrinkwrap.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/web/npm-shrinkwrap.json b/services/web/npm-shrinkwrap.json index a8357be7fd..6f6d9e475e 100644 --- a/services/web/npm-shrinkwrap.json +++ b/services/web/npm-shrinkwrap.json @@ -2728,6 +2728,11 @@ "from": "node-forge@0.2.24", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.2.24.tgz" }, + "node-html-encoder": { + "version": "0.0.2", + "from": "node-html-encoder@0.0.2", + "resolved": "https://registry.npmjs.org/node-html-encoder/-/node-html-encoder-0.0.2.tgz" + }, "node-pre-gyp": { "version": "0.6.30", "from": "node-pre-gyp@0.6.30", From f057f788e3270130678261e3bef34c5ca3d75c3e Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Mon, 31 Jul 2017 14:51:22 +0100 Subject: [PATCH 11/26] Autocomplete for graphics --- .../ide/editor/directives/aceEditor.coffee | 2 +- .../auto-complete/AutoCompleteManager.coffee | 19 ++++++++++--------- .../ide/file-tree/FileTreeManager.coffee | 2 -- .../ide/graphics/services/graphics.coffee | 8 +++++--- .../ide/preamble/services/preamble.coffee | 2 -- .../web/public/stylesheets/app/editor.less | 5 +++++ 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index 19c34142c5..9b272841be 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -103,7 +103,7 @@ define [ cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage) trackChangesManager = new TrackChangesManager(scope, editor, element) labelsManager = new LabelsManager(scope, editor, element, labels) - autoCompleteManager = new AutoCompleteManager(scope, editor, element, labelsManager, graphics) + autoCompleteManager = new AutoCompleteManager(scope, editor, element, labelsManager, graphics, preamble) # Prevert Ctrl|Cmd-S from triggering save dialog diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee index 6259e893ab..7cc89c785a 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee @@ -17,7 +17,7 @@ define [ commandFragment?.match(/\\(\w+)\{/)?[1] class AutoCompleteManager - constructor: (@$scope, @editor, @element, @labelsManager, @graphics) -> + constructor: (@$scope, @editor, @element, @labelsManager, @graphics, @preamble) -> @suggestionManager = new SuggestionManager() @monkeyPatchAutocomplete() @@ -45,6 +45,7 @@ define [ SnippetCompleter = new SnippetManager() Graphics = @graphics + Preamble = @preamble GraphicsCompleter = getCompletions: (editor, session, pos, prefix, callback) -> upToCursorRange = new Range(pos.row, 0, pos.row, pos.column) @@ -58,17 +59,17 @@ define [ needsClosingBrace = !lineBeyondCursor.match(/^[^{]*}/) commandName = match[1] currentArg = match[3] + graphicsPaths = Preamble.getGraphicsPaths() result = [] - # result.push { - # caption: "\\#{commandName}{}", - # snippet: "\\#{commandName}{}", - # meta: "graphic", - # score: 60 - # } for graphic in Graphics.getGraphicsFiles() + path = graphic.path + for graphicsPath in graphicsPaths + if path.indexOf(graphicsPath) == 0 + path = path.slice(graphicsPath.length) + break result.push { - caption: "\\#{commandName}{#{graphic.path}#{if needsClosingBrace then '}' else ''}", - value: "\\#{commandName}{#{graphic.path}#{if needsClosingBrace then '}' else ''}", + caption: "\\#{commandName}{#{path}#{if needsClosingBrace then '}' else ''}", + value: "\\#{commandName}{#{path}#{if needsClosingBrace then '}' else ''}", meta: "graphic", score: 50 } diff --git a/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee b/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee index f6b8a92710..0881e75cc8 100644 --- a/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee +++ b/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee @@ -202,8 +202,6 @@ define [ childPath = path + "/" + entity.name else childPath = entity.name - # FIXME: this is a hack - entity.path = childPath callback(entity, folder, childPath) if entity.children? @_forEachEntityInFolder(entity, childPath, callback) diff --git a/services/web/public/coffee/ide/graphics/services/graphics.coffee b/services/web/public/coffee/ide/graphics/services/graphics.coffee index fdd4126b16..355614b7f4 100644 --- a/services/web/public/coffee/ide/graphics/services/graphics.coffee +++ b/services/web/public/coffee/ide/graphics/services/graphics.coffee @@ -7,9 +7,11 @@ define [ Graphics = getGraphicsFiles: () -> graphicsFiles = [] - ide.fileTreeManager.forEachEntity (f) -> - if f?.name?.match?(/.*\.(png|jpg|jpeg)/) - graphicsFiles.push f + ide.fileTreeManager.forEachEntity (entity, folder, path) -> + if entity.type == 'file' && entity?.name?.match?(/.*\.(png|jpg|jpeg|pdf|eps)/) + cloned = _.clone(entity) + cloned.path = path + graphicsFiles.push cloned return graphicsFiles return Graphics diff --git a/services/web/public/coffee/ide/preamble/services/preamble.coffee b/services/web/public/coffee/ide/preamble/services/preamble.coffee index a8312ca4d7..f95cfd9bbe 100644 --- a/services/web/public/coffee/ide/preamble/services/preamble.coffee +++ b/services/web/public/coffee/ide/preamble/services/preamble.coffee @@ -19,6 +19,4 @@ define [ paths.push(match[1]) return paths - window.Preamble = Preamble - return Preamble diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index 3c3aae945a..979bdd2bf6 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -504,3 +504,8 @@ height: auto; border-bottom: 1px solid @modal-header-border-color; } + +// Widen autocomplete popup +.ace_autocomplete { + width: 380px !important; +} From e3bf4c539b847798d3b0cf5d91d07dfc5440c713 Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 1 Aug 2017 09:51:27 +0200 Subject: [PATCH 12/26] Style tab panes --- services/web/public/stylesheets/components/navs.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/web/public/stylesheets/components/navs.less b/services/web/public/stylesheets/components/navs.less index 262524f864..0616da1b8c 100755 --- a/services/web/public/stylesheets/components/navs.less +++ b/services/web/public/stylesheets/components/navs.less @@ -225,6 +225,10 @@ // Hide tabbable panes to start, show them when `.active` .tab-content { + background-color: @nav-tabs-active-link-hover-bg; + border: 1px solid @nav-tabs-border-color; + border-top: none; + padding: @line-height-computed / 2; > .tab-pane { display: none; } From 9a85e42ddbdfc4dc3020c6b1e135b1776c4ade2f Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Tue, 1 Aug 2017 09:53:43 +0100 Subject: [PATCH 13/26] Remove stray file --- .../ide/preamble/preamble/services/preamble.coffee | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 services/web/public/coffee/ide/preamble/preamble/services/preamble.coffee diff --git a/services/web/public/coffee/ide/preamble/preamble/services/preamble.coffee b/services/web/public/coffee/ide/preamble/preamble/services/preamble.coffee deleted file mode 100644 index bd52713952..0000000000 --- a/services/web/public/coffee/ide/preamble/preamble/services/preamble.coffee +++ /dev/null @@ -1,14 +0,0 @@ -define [ - "base" -], (App) -> - - App.factory 'preamble', (ide) -> - - Preamble = { - getPreambleText: () -> - text = ide.editorManager.getCurrentDocValue().slice(0, 5000) - preamble = text.match(/([^]*)^\\begin\{document\}/m)[1] - - } - - return Preamble From 3ffef7fe56d70038c2268bca63109d7f86521ff6 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Tue, 1 Aug 2017 13:40:30 +0100 Subject: [PATCH 14/26] Dynamically adjust the width of autocomplete popup. --- .../auto-complete/AutoCompleteManager.coffee | 13 ++++++++++++- services/web/public/stylesheets/app/editor.less | 4 ---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee index 7cc89c785a..348fe32f6b 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee @@ -282,7 +282,18 @@ define [ editor.completer.autoSelect = true editor.completer.showPopup(editor) editor.completer.cancelContextMenu() - $(editor.completer.popup?.container).css({'font-size': @$scope.fontSize + 'px'}) + container = $(editor.completer.popup?.container) + container.css({'font-size': @$scope.fontSize + 'px'}) + # Dynamically set width of autocomplete popup + if filtered = editor?.completer?.completions?.filtered + longestCaption = _.max(filtered.map( (c) -> c.caption.length )) + longestMeta = _.max(filtered.map( (c) -> c.meta.length )) + charScale = @$scope.fontSize * 0.7 + width = Math.min( + Math.round(longestCaption*charScale + longestMeta*charScale + 25), + 700 + ) + container.css({width: "#{width}px"}) if editor.completer?.completions?.filtered?.length == 0 editor.completer.detach() bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space" diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index 979bdd2bf6..aae2ecdc9f 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -505,7 +505,3 @@ border-bottom: 1px solid @modal-header-border-color; } -// Widen autocomplete popup -.ace_autocomplete { - width: 380px !important; -} From 8a0f58c63d70fc42d155f7ff6e9a429accf46e34 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Tue, 1 Aug 2017 14:28:10 +0100 Subject: [PATCH 15/26] Use correct character width --- .../auto-complete/AutoCompleteManager.coffee | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee index 348fe32f6b..751e534a32 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee @@ -288,10 +288,14 @@ define [ if filtered = editor?.completer?.completions?.filtered longestCaption = _.max(filtered.map( (c) -> c.caption.length )) longestMeta = _.max(filtered.map( (c) -> c.meta.length )) - charScale = @$scope.fontSize * 0.7 - width = Math.min( - Math.round(longestCaption*charScale + longestMeta*charScale + 25), - 700 + charWidth = editor.renderer.characterWidth + # between 280 and 700 px + width = Math.max( + Math.min( + Math.round(longestCaption*charWidth + longestMeta*charWidth + 5*charWidth), + 700 + ), + 280 ) container.css({width: "#{width}px"}) if editor.completer?.completions?.filtered?.length == 0 From 10f362a77a3415dd45a82a1dd3988041697b780e Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 2 Aug 2017 09:59:45 +0200 Subject: [PATCH 16/26] Add SubscriptionUpdater.deleteSubscription --- .../Subscription/SubscriptionUpdater.coffee | 9 +++++ .../SubscriptionUpdaterTests.coffee | 33 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee index 7b857c6460..12faf0e234 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee @@ -62,6 +62,15 @@ module.exports = SubscriptionUpdater = invited_emails: email }, callback + deleteSubscription: (subscription_id, callback = (error) ->) -> + SubscriptionLocator.getSubscription subscription_id, (err, subscription) -> + return callback(err) if err? + affected_user_ids = [subscription.admin_id].concat(subscription.member_ids or []) + logger.log {subscription_id, affected_user_ids}, "deleting subscription and downgrading users" + Subscription.remove {_id: ObjectId(subscription_id)}, (err) -> + return callback(err) if err? + async.mapSeries affected_user_ids, SubscriptionUpdater._setUsersMinimumFeatures, callback + _createNewSubscription: (adminUser_id, callback)-> logger.log adminUser_id:adminUser_id, "creating new subscription" subscription = new Subscription(admin_id:adminUser_id) diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionUpdaterTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionUpdaterTests.coffee index ffef94157b..87ff474a0a 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionUpdaterTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionUpdaterTests.coffee @@ -37,6 +37,7 @@ describe "SubscriptionUpdater", -> constructor: (opts)-> subscription.admin_id = opts.admin_id return subscription + @remove: sinon.stub().yields() @SubscriptionModel.update = @updateStub @SubscriptionModel.findAndModify = @findAndModifyStub @@ -230,3 +231,35 @@ describe "SubscriptionUpdater", -> @ReferalAllocator.assignBonus.calledWith(@adminuser_id).should.equal true done() + describe "deleteSubscription", -> + beforeEach (done) -> + @subscription_id = ObjectId().toString() + @subscription = { + "mock": "subscription", + admin_id: ObjectId(), + member_ids: [ ObjectId(), ObjectId(), ObjectId() ] + } + @SubscriptionLocator.getSubscription = sinon.stub().yields(null, @subscription) + @SubscriptionUpdater._setUsersMinimumFeatures = sinon.stub().yields() + @SubscriptionUpdater.deleteSubscription @subscription_id, done + + it "should look up the subscription", -> + @SubscriptionLocator.getSubscription + .calledWith(@subscription_id) + .should.equal true + + it "should remove the subscription", -> + @SubscriptionModel.remove + .calledWith({_id: ObjectId(@subscription_id)}) + .should.equal true + + it "should downgrade the admin_id", -> + @SubscriptionUpdater._setUsersMinimumFeatures + .calledWith(@subscription.admin_id) + .should.equal true + + it "should downgrade all of the members", -> + for user_id in @subscription.member_ids + @SubscriptionUpdater._setUsersMinimumFeatures + .calledWith(user_id) + .should.equal true From 66d18bcbcd9433dc09ac60efd8d8cf507268460d Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 2 Aug 2017 14:59:03 +0200 Subject: [PATCH 17/26] Validate and add error status to contact form --- services/web/app/views/contact-us-modal.pug | 87 ++++++++++++------- .../web/public/coffee/main/contact-us.coffee | 14 ++- 2 files changed, 67 insertions(+), 34 deletions(-) diff --git a/services/web/app/views/contact-us-modal.pug b/services/web/app/views/contact-us-modal.pug index d3e5aa0e87..aad68a53d3 100644 --- a/services/web/app/views/contact-us-modal.pug +++ b/services/web/app/views/contact-us-modal.pug @@ -7,36 +7,61 @@ script(type='text/ng-template', id='supportModalTemplate') ) × h3 #{translate("contact_us")} .modal-body.contact-us-modal - span(ng-show="sent == false") - label - | #{translate("subject")} - .form-group - input.field.text.medium.span8.form-control( - ng-model="form.subject", - ng-model-options="{ updateOn: 'default blur', debounce: {'default': 350, 'blur': 0} }" - maxlength='255', - tabindex='1', - onkeyup='') - .contact-suggestions(ng-show="suggestions.length") - p.contact-suggestion-label !{translate("kb_suggestions_enquiry", { kbLink: "__kb__", kb: translate("knowledge_base") })} - ul.contact-suggestion-list - li(ng-repeat="suggestion in suggestions") - a.contact-suggestion-list-item(ng-href="{{ suggestion.url }}", ng-click="clickSuggestionLink(suggestion.url);" target="_blank") - span(ng-bind-html="suggestion.name") - i.fa.fa-angle-right - label.desc(ng-show="'"+getUserEmail()+"'.length < 1") - | #{translate("email")} - .form-group(ng-show="'"+getUserEmail()+"'.length < 1") - input.field.text.medium.span8.form-control(ng-model="form.email", ng-init="form.email = '"+getUserEmail()+"'", type='email', spellcheck='false', value='', maxlength='255', tabindex='2') - label#title12.desc - | #{translate("project_url")} (#{translate("optional")}) - .form-group - input.field.text.medium.span8.form-control(ng-model="form.project_url", tabindex='3', onkeyup='') - label.desc - | #{translate("contact_message_label")} - .form-group - textarea.field.text.medium.span8.form-control(ng-model="form.message",type='text', value='', tabindex='4', onkeyup='') - .form-group.text-center - input.btn-success.btn.btn-lg(type='submit', ng-disabled="sending", ng-click="contactUs()" value=translate("contact_us")) + form(name="contactForm") + span(ng-show="sent == false") + .alert.alert-danger(ng-show="error") Something went wrong sending your request :( + label + | #{translate("subject")} + .form-group + input.field.text.medium.span8.form-control( + name="subject", + required + ng-model="form.subject", + ng-model-options="{ updateOn: 'default blur', debounce: {'default': 350, 'blur': 0} }" + maxlength='255', + tabindex='1', + onkeyup='') + .contact-suggestions(ng-show="suggestions.length") + p.contact-suggestion-label !{translate("kb_suggestions_enquiry", { kbLink: "__kb__", kb: translate("knowledge_base") })} + ul.contact-suggestion-list + li(ng-repeat="suggestion in suggestions") + a.contact-suggestion-list-item(ng-href="{{ suggestion.url }}", ng-click="clickSuggestionLink(suggestion.url);" target="_blank") + span(ng-bind-html="suggestion.name") + i.fa.fa-angle-right + label.desc(ng-show="'"+getUserEmail()+"'.length < 1") + | #{translate("email")} + .form-group(ng-show="'"+getUserEmail()+"'.length < 1") + input.field.text.medium.span8.form-control( + name="email", + required + ng-model="form.email", + ng-init="form.email = '"+getUserEmail()+"'", + type='email', spellcheck='false', + value='', + maxlength='255', + tabindex='2') + label#title12.desc + | #{translate("project_url")} (#{translate("optional")}) + .form-group + input.field.text.medium.span8.form-control(ng-model="form.project_url", tabindex='3', onkeyup='') + label.desc + | #{translate("contact_message_label")} + .form-group + textarea.field.text.medium.span8.form-control( + name="body", + required + ng-model="form.message", + type='text', + value='', + tabindex='4', + onkeyup='' + ) + .form-group.text-center + input.btn-success.btn.btn-lg( + type='submit', + ng-disabled="contactForm.$invalid || sending", + ng-click="contactUs()" + value=translate("contact_us") + ) span(ng-show="sent") p #{translate("request_sent_thank_you")} diff --git a/services/web/public/coffee/main/contact-us.coffee b/services/web/public/coffee/main/contact-us.coffee index 2f3a2c61e0..7bb86a6b93 100644 --- a/services/web/public/coffee/main/contact-us.coffee +++ b/services/web/public/coffee/main/contact-us.coffee @@ -30,7 +30,7 @@ define [ $scope.suggestions = suggestions $scope.contactUs = -> - if !$scope.form.email? + if !$scope.form.email? or $scope.form.email == "" console.log "email not set" return $scope.sending = true @@ -46,8 +46,16 @@ define [ about: "
browser: #{platform?.name} #{platform?.version}
os: #{platform?.os?.family} #{platform?.os?.version}
" - Groove.createTicket params, (err, json)-> - $scope.sent = true + Groove.createTicket params, (response)-> + $scope.sending = false + if response.responseText == "" # Blocked request or similar + $scope.error = true + else + data = JSON.parse(response.responseText) + if data.errors? + $scope.error = true + else + $scope.sent = true $scope.$apply() $scope.$watch "form.subject", (newVal, oldVal) -> From 670d4438dc6f86f8a457f26a2950b96b4b793b6b Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Thu, 3 Aug 2017 15:16:54 +0100 Subject: [PATCH 18/26] If track-changes is not enabled, don't show hotkeys for it. --- services/web/app/views/project/editor/hotkeys.pug | 6 +++--- .../ide/hotkeys/controllers/HotkeysController.coffee | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/services/web/app/views/project/editor/hotkeys.pug b/services/web/app/views/project/editor/hotkeys.pug index cab8ea09b3..6069394840 100644 --- a/services/web/app/views/project/editor/hotkeys.pug +++ b/services/web/app/views/project/editor/hotkeys.pug @@ -90,8 +90,8 @@ script(type="text/ng-template", id="hotkeysModalTemplate") span.combination Ctrl + Space span.description Search References - h3 #{translate("review")} - .row + h3(ng-if="trackChangesVisible") #{translate("review")} + .row(ng-if="trackChangesVisible") .col-xs-4 .hotkey span.combination {{ctrl}} + J @@ -108,4 +108,4 @@ script(type="text/ng-template", id="hotkeysModalTemplate") .modal-footer button.btn.btn-default( ng-click="cancel()" - ) #{translate("ok")} \ No newline at end of file + ) #{translate("ok")} diff --git a/services/web/public/coffee/ide/hotkeys/controllers/HotkeysController.coffee b/services/web/public/coffee/ide/hotkeys/controllers/HotkeysController.coffee index 0ec2c46cf1..65b513eda2 100644 --- a/services/web/public/coffee/ide/hotkeys/controllers/HotkeysController.coffee +++ b/services/web/public/coffee/ide/hotkeys/controllers/HotkeysController.coffee @@ -10,13 +10,16 @@ define [ templateUrl: "hotkeysModalTemplate" controller: "HotkeysModalController" size: "lg" + resolve: + trackChangesVisible: () -> $scope.project.features.trackChangesVisible } - App.controller "HotkeysModalController", ($scope, $modalInstance)-> + App.controller "HotkeysModalController", ($scope, $modalInstance, trackChangesVisible)-> + $scope.trackChangesVisible = trackChangesVisible if ace.require("ace/lib/useragent").isMac $scope.ctrl = "Cmd" else $scope.ctrl = "Ctrl" $scope.cancel = () -> - $modalInstance.dismiss() \ No newline at end of file + $modalInstance.dismiss() From 9cc461605ec3bed51f56b6b2f3916fcb047a7729 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Thu, 3 Aug 2017 15:17:28 +0100 Subject: [PATCH 19/26] Prevent track-changes hotkey actions when not enabled. --- services/web/public/coffee/ide.coffee | 2 ++ .../ide/review-panel/controllers/ReviewPanelController.coffee | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee index 5824304bff..d37334649c 100644 --- a/services/web/public/coffee/ide.coffee +++ b/services/web/public/coffee/ide.coffee @@ -90,6 +90,8 @@ define [ $scope.chat = {} ide.toggleReviewPanel = $scope.toggleReviewPanel = () -> + if !$scope.project.features.trackChangesVisible + return $scope.ui.reviewPanelOpen = !$scope.ui.reviewPanelOpen event_tracking.sendMB "rp-toggle-panel", { value : $scope.ui.reviewPanelOpen } diff --git a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee index e4d7855ed9..dc75fe1a19 100644 --- a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee +++ b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee @@ -416,6 +416,8 @@ define [ $scope.toggleReviewPanel() $scope.addNewCommentFromKbdShortcut = () -> + if !$scope.project.features.trackChangesVisible + return $scope.$broadcast "comment:select_line" if !$scope.ui.reviewPanelOpen $scope.toggleReviewPanel() @@ -575,6 +577,8 @@ define [ $scope.openTrackChangesUpgradeModal() $scope.toggleTrackChangesFromKbdShortcut = () -> + if !$scope.project.features.trackChangesVisible + return if $scope.editor.wantTrackChanges $scope.toggleTrackChanges false else From 48266164116a3810b3345b84b38bc22bb5b462a3 Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 4 Aug 2017 10:38:54 +0200 Subject: [PATCH 20/26] Update with latex mode bracket matching changes --- .../web/public/js/ace-1.2.5/mode-latex.js | 279 +++++++++++++++++- .../web/public/js/ace-1.2.5/worker-latex.js | 2 + 2 files changed, 280 insertions(+), 1 deletion(-) diff --git a/services/web/public/js/ace-1.2.5/mode-latex.js b/services/web/public/js/ace-1.2.5/mode-latex.js index 7c5f426713..df84725189 100644 --- a/services/web/public/js/ace-1.2.5/mode-latex.js +++ b/services/web/public/js/ace-1.2.5/mode-latex.js @@ -195,7 +195,282 @@ oop.inherits(FoldMode, BaseFoldMode); }); -ace.define("ace/mode/latex",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/latex_highlight_rules","ace/mode/folding/latex","ace/range","ace/worker/worker_client"], function(require, exports, module) { +ace.define("ace/mode/behaviour/latex",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"], function(require, exports, module) { +"use strict"; + +var oop = require("../../lib/oop"); +var Behaviour = require("../behaviour").Behaviour; +var TokenIterator = require("../../token_iterator").TokenIterator; +var lang = require("../../lib/lang"); + +var SAFE_INSERT_IN_TOKENS = + ["text", "paren.rparen", "punctuation.operator"]; +var SAFE_INSERT_BEFORE_TOKENS = + ["text", "paren.rparen", "punctuation.operator", "comment"]; + +var context; +var contextCache = {}; +var initContext = function(editor) { + var id = -1; + if (editor.multiSelect) { + id = editor.selection.index; + if (contextCache.rangeCount != editor.multiSelect.rangeCount) + contextCache = {rangeCount: editor.multiSelect.rangeCount}; + } + if (contextCache[id]) + return context = contextCache[id]; + context = contextCache[id] = { + autoInsertedBrackets: 0, + autoInsertedRow: -1, + autoInsertedLineEnd: "", + maybeInsertedBrackets: 0, + maybeInsertedRow: -1, + maybeInsertedLineStart: "", + maybeInsertedLineEnd: "" + }; +}; + +var getWrapped = function(selection, selected, opening, closing) { + var rowDiff = selection.end.row - selection.start.row; + return { + text: opening + selected + closing, + selection: [ + 0, + selection.start.column + 1, + rowDiff, + selection.end.column + (rowDiff ? 0 : 1) + ] + }; +}; + +var LatexBehaviour = function() { + this.add("braces", "insertion", function(state, action, editor, session, text) { + if (editor.completer && editor.completer.popup && editor.completer.popup.isOpen) { + return; + } + var cursor = editor.getCursorPosition(); + var line = session.doc.getLine(cursor.row); + var lastChar = line[cursor.column-1]; + if (lastChar === '\\') { + return; + } + if (text == '{') { + initContext(editor); + var selection = editor.getSelectionRange(); + var selected = session.doc.getTextRange(selection); + if (selected !== "" && editor.getWrapBehavioursEnabled()) { + return getWrapped(selection, selected, '{', '}'); + } else if (LatexBehaviour.isSaneInsertion(editor, session)) { + LatexBehaviour.recordAutoInsert(editor, session, "}"); + return { + text: '{}', + selection: [1, 1] + }; + } + } else if (text == '}') { + initContext(editor); + var rightChar = line.substring(cursor.column, cursor.column + 1); + if (rightChar == '}') { + var matching = session.$findOpeningBracket('}', {column: cursor.column + 1, row: cursor.row}); + if (matching !== null && LatexBehaviour.isAutoInsertedClosing(cursor, line, text)) { + LatexBehaviour.popAutoInsertedClosing(); + return { + text: '', + selection: [1, 1] + }; + } + } + } + }); + + this.add("braces", "deletion", function(state, action, editor, session, range) { + if (editor.completer && editor.completer.popup && editor.completer.popup.isOpen) { + return; + } + var selected = session.doc.getTextRange(range); + if (!range.isMultiLine() && selected == '{') { + initContext(editor); + var line = session.doc.getLine(range.start.row); + var rightChar = line.substring(range.start.column + 1, range.start.column + 2); + if (rightChar == '}') { + range.end.column++; + return range; + } + } + }); + + this.add("brackets", "insertion", function(state, action, editor, session, text) { + if (editor.completer && editor.completer.popup && editor.completer.popup.isOpen) { + return; + } + var cursor = editor.getCursorPosition(); + var line = session.doc.getLine(cursor.row); + var lastChar = line[cursor.column-1]; + if (lastChar === '\\') { + return; + } + if (text == '[') { + initContext(editor); + var selection = editor.getSelectionRange(); + var selected = session.doc.getTextRange(selection); + if (selected !== "" && editor.getWrapBehavioursEnabled()) { + return getWrapped(selection, selected, '[', ']'); + } else if (LatexBehaviour.isSaneInsertion(editor, session)) { + LatexBehaviour.recordAutoInsert(editor, session, "]"); + return { + text: '[]', + selection: [1, 1] + }; + } + } else if (text == ']') { + initContext(editor); + var rightChar = line.substring(cursor.column, cursor.column + 1); + if (rightChar == ']') { + var matching = session.$findOpeningBracket(']', {column: cursor.column + 1, row: cursor.row}); + if (matching !== null && LatexBehaviour.isAutoInsertedClosing(cursor, line, text)) { + LatexBehaviour.popAutoInsertedClosing(); + return { + text: '', + selection: [1, 1] + }; + } + } + } + }); + + this.add("brackets", "deletion", function(state, action, editor, session, range) { + if (editor.completer && editor.completer.popup && editor.completer.popup.isOpen) { + return; + } + var selected = session.doc.getTextRange(range); + if (!range.isMultiLine() && selected == '[') { + initContext(editor); + var line = session.doc.getLine(range.start.row); + var rightChar = line.substring(range.start.column + 1, range.start.column + 2); + if (rightChar == ']') { + range.end.column++; + return range; + } + } + }); + + this.add("dollars", "insertion", function(state, action, editor, session, text) { + var cursor = editor.getCursorPosition(); + var line = session.doc.getLine(cursor.row); + var lastChar = line[cursor.column-1]; + if (lastChar === '\\') { + return; + } + if (text == '$') { + if (this.lineCommentStart && this.lineCommentStart.indexOf(text) != -1) + return; + initContext(editor); + var quote = text; + var selection = editor.getSelectionRange(); + var selected = session.doc.getTextRange(selection); + if (selected !== "" && selected !== "$" && editor.getWrapBehavioursEnabled()) { + return getWrapped(selection, selected, quote, quote); + } else if (!selected) { + var leftChar = line.substring(cursor.column-1, cursor.column); + var rightChar = line.substring(cursor.column, cursor.column + 1); + + var token = session.getTokenAt(cursor.row, cursor.column); + var rightToken = session.getTokenAt(cursor.row, cursor.column + 1); + + var stringBefore = token && /string|escape/.test(token.type); + var stringAfter = !rightToken || /string|escape/.test(rightToken.type); + + var pair; + if (rightChar == quote) { + pair = stringBefore !== stringAfter; + if (pair && /string\.end/.test(rightToken.type)) + pair = false; + } else { + if (stringBefore && !stringAfter) + return null; // wrap string with different quote + if (stringBefore && stringAfter) + return null; // do not pair quotes inside strings + var wordRe = session.$mode.tokenRe; + wordRe.lastIndex = 0; + var isWordBefore = wordRe.test(leftChar); + wordRe.lastIndex = 0; + var isWordAfter = wordRe.test(leftChar); + if (isWordBefore || isWordAfter) + return null; // before or after alphanumeric + if (rightChar && !/[\s;,.})\]\\]/.test(rightChar)) + return null; // there is rightChar and it isn't closing + pair = true; + } + return { + text: pair ? quote + quote : "", + selection: [1,1] + }; + } + } + }); + + this.add("dollars", "deletion", function(state, action, editor, session, range) { + var selected = session.doc.getTextRange(range); + if (!range.isMultiLine() && (selected == '$')) { + initContext(editor); + var line = session.doc.getLine(range.start.row); + var rightChar = line.substring(range.start.column + 1, range.start.column + 2); + if (rightChar == selected) { + range.end.column++; + return range; + } + } + }); + +}; + + +LatexBehaviour.isSaneInsertion = function(editor, session) { + var cursor = editor.getCursorPosition(); + var iterator = new TokenIterator(session, cursor.row, cursor.column); + if (!this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) { + var iterator2 = new TokenIterator(session, cursor.row, cursor.column + 1); + if (!this.$matchTokenType(iterator2.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) + return false; + } + iterator.stepForward(); + return iterator.getCurrentTokenRow() !== cursor.row || + this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_BEFORE_TOKENS); +}; + +LatexBehaviour.$matchTokenType = function(token, types) { + return types.indexOf(token.type || token) > -1; +}; + +LatexBehaviour.recordAutoInsert = function(editor, session, bracket) { + var cursor = editor.getCursorPosition(); + var line = session.doc.getLine(cursor.row); + if (!this.isAutoInsertedClosing(cursor, line, context.autoInsertedLineEnd[0])) + context.autoInsertedBrackets = 0; + context.autoInsertedRow = cursor.row; + context.autoInsertedLineEnd = bracket + line.substr(cursor.column); + context.autoInsertedBrackets++; +}; + +LatexBehaviour.isAutoInsertedClosing = function(cursor, line, bracket) { + return context.autoInsertedBrackets > 0 && + cursor.row === context.autoInsertedRow && + bracket === context.autoInsertedLineEnd[0] && + line.substr(cursor.column) === context.autoInsertedLineEnd; +}; + +LatexBehaviour.popAutoInsertedClosing = function() { + context.autoInsertedLineEnd = context.autoInsertedLineEnd.substr(1); + context.autoInsertedBrackets--; +}; + + +oop.inherits(LatexBehaviour, Behaviour); + +exports.LatexBehaviour = LatexBehaviour; +}); + +ace.define("ace/mode/latex",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/latex_highlight_rules","ace/mode/folding/latex","ace/range","ace/worker/worker_client","ace/mode/behaviour/latex"], function(require, exports, module) { "use strict"; var oop = require("../lib/oop"); @@ -204,6 +479,7 @@ var LatexHighlightRules = require("./latex_highlight_rules").LatexHighlightRules var LatexFoldMode = require("./folding/latex").FoldMode; var Range = require("../range").Range; var WorkerClient = require("ace/worker/worker_client").WorkerClient; +var LatexBehaviour = require("./behaviour/latex").LatexBehaviour; var createLatexWorker = function (session) { var doc = session.getDocument(); @@ -361,6 +637,7 @@ var createLatexWorker = function (session) { var Mode = function() { this.HighlightRules = LatexHighlightRules; this.foldingRules = new LatexFoldMode(); + this.$behaviour = new LatexBehaviour(); this.createWorker = createLatexWorker; }; oop.inherits(Mode, TextMode); diff --git a/services/web/public/js/ace-1.2.5/worker-latex.js b/services/web/public/js/ace-1.2.5/worker-latex.js index dfb4e69e56..dc9f5d3204 100644 --- a/services/web/public/js/ace-1.2.5/worker-latex.js +++ b/services/web/public/js/ace-1.2.5/worker-latex.js @@ -1436,6 +1436,7 @@ oop.inherits(LatexWorker, Mirror); } catch (e) { console.log(e); disabled = true; + this.sender.emit("fatal-error", e); errors = []; } this.sender.emit("lint", { @@ -1657,6 +1658,7 @@ var readOptionalParams = function(TokeniseResult, k) { }; var count = 0; var nextToken = Tokens[k+1]; + if (!nextToken) { return null }; var pos = nextToken[2]; for (var i = pos, end = text.length; i < end; i++) { From 5c8442a86f0be50e6a1eb8d30aef86a5288eea4d Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 7 Aug 2017 17:01:59 +0200 Subject: [PATCH 21/26] Allow configureable brand prefix with overleaf styles --- services/web/.gitignore | 1 + services/web/Gruntfile.coffee | 3 + services/web/app/views/layout.pug | 18 +- services/web/config/settings.defaults.coffee | 2 + services/web/public/ol-favicon.ico | Bin 0 -> 938 bytes .../public/stylesheets/_style_includes.less | 84 ++ .../stylesheets/core/_common-variables.less | 805 +++++++++++++++++ .../public/stylesheets/core/ol-variables.less | 31 + .../public/stylesheets/core/variables.less | 807 +----------------- services/web/public/stylesheets/ol-style.less | 3 + services/web/public/stylesheets/style.less | 85 +- 11 files changed, 940 insertions(+), 899 deletions(-) create mode 100644 services/web/public/ol-favicon.ico create mode 100644 services/web/public/stylesheets/_style_includes.less create mode 100644 services/web/public/stylesheets/core/_common-variables.less create mode 100644 services/web/public/stylesheets/core/ol-variables.less create mode 100644 services/web/public/stylesheets/ol-style.less diff --git a/services/web/.gitignore b/services/web/.gitignore index d7b110e28d..9c2a272a7a 100644 --- a/services/web/.gitignore +++ b/services/web/.gitignore @@ -60,6 +60,7 @@ public/js/services/ public/js/utils/ public/stylesheets/style.css +public/stylesheets/ol-style.css public/brand/plans.css public/minjs/ diff --git a/services/web/Gruntfile.coffee b/services/web/Gruntfile.coffee index 45a6528cdd..479190332f 100644 --- a/services/web/Gruntfile.coffee +++ b/services/web/Gruntfile.coffee @@ -139,6 +139,9 @@ module.exports = (grunt) -> app: files: "public/stylesheets/style.css": "public/stylesheets/style.less" + ol: + files: + "public/stylesheets/ol-style.css": "public/stylesheets/ol-style.less" postcss: options: diff --git a/services/web/app/views/layout.pug b/services/web/app/views/layout.pug index 459f081c8a..30f454f72a 100644 --- a/services/web/app/views/layout.pug +++ b/services/web/app/views/layout.pug @@ -14,16 +14,16 @@ html(itemscope, itemtype='http://schema.org/Product') -if (typeof(title) == "undefined") - title= 'ShareLaTeX, '+ translate("online_latex_editor") + title= settings.appName + ', '+ translate("online_latex_editor") -else - title= translate(title) + ' - ShareLaTeX, ' + translate("online_latex_editor") + title= translate(title) + ' - ' + settings.appName + ', ' + translate("online_latex_editor") - link(rel="icon", href="/favicon.ico") - link(rel="icon", sizes="192x192", href="/touch-icon-192x192.png") - link(rel="apple-touch-icon-precomposed", href="/apple-touch-icon-precomposed.png") - link(rel="mask-icon", href="/mask-favicon.svg", color="#a93529") + link(rel="icon", href="/" + settings.brandPrefix + "favicon.ico") + link(rel="icon", sizes="192x192", href="/" + settings.brandPrefix + "touch-icon-192x192.png") + link(rel="apple-touch-icon-precomposed", href="/" + settings.brandPrefix + "apple-touch-icon-precomposed.png") + link(rel="mask-icon", href="/" + settings.brandPrefix + "mask-favicon.svg", color="#a93529") - link(rel='stylesheet', href=buildCssPath('/style.css')) + link(rel='stylesheet', href=buildCssPath("/" + settings.brandPrefix + "style.css")) block _headLinks @@ -33,14 +33,14 @@ html(itemscope, itemtype='http://schema.org/Product') link(rel="alternate", href=subdomainDetails.url+currentUrl, hreflang=subdomainDetails.lngCode) - meta(itemprop="name", content="ShareLaTeX, the Online LaTeX Editor") + meta(itemprop="name", content=settings.appName + ", the Online LaTeX Editor") -if (typeof(meta) == "undefined") meta(itemprop="description", name="description", content=translate("site_description")) -else meta(itemprop="description", name="description" , content=meta) - meta(itemprop="image", name="image", content="https://www.sharelatex.com/favicon.ico") + meta(itemprop="image", name="image", content="/" + settings.brandPrefix + "favicon.ico") - if (typeof(gaToken) != "undefined") script(type='text/javascript'). diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 046903de7b..2456590709 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -343,6 +343,8 @@ module.exports = settings = appName: "ShareLaTeX (Community Edition)" adminEmail: "placeholder@example.com" + + brandPrefix: "" # Set to 'ol-' for overleaf styles nav: title: "ShareLaTeX Community Edition" diff --git a/services/web/public/ol-favicon.ico b/services/web/public/ol-favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6a14f7a5b9f7e1f231826fb3bf660351b8c4c903 GIT binary patch literal 938 zcmV;b16BNqP)6bJBMSw6GKg3Je6K_3!Em^FFSAtDM6 zk(1mK6;@CP4JxQXU=ox*Is+A|M{=A_THEOx|}r1qOvML3~4xCge5o&r{Po_!(se}{rEA4 zG$^a$?{y;qXgVmXVl6h{0*qlk<~95t#h)3-02^XRTmB6OR^w$X?Jyl+L4MyFLkc4^ zFr*!uu(88@3l89Zbvi!6bupx4ZNroM0?4Q@ohA4r?!c{BiHCCV0q#`S#0hdr->=UA zUYut7UEHot$NN~;nqL(|`hF(gy%=cZ?D~fKV_c1OcphI)6a4NN(pQc2jRv5s3T0K? z*idbZAziDkp@uRrl7I-X*`TWxFLqLEw|twSc8jkNuK`#lQE?I zUBT(-f=t~jSdU*b#f$K*y61=TDjta;P4!`PE-82_2ZsqP&R;mcWw#zblvVL~2Hv~9 zXEe+?{~!<7;Vt}8kSf1Ama9#}Qr#Ul_<+O!q0hXfx3QlF>wL|WmPQ2j_l;U7}8B$U(sOVww$UVz|LZvj)C+2vp;O> za?oS~pQzh)2y9{$0B1Jzhxz~*g1}z<-cT>=S~nelCT*AZ1(2nE;*FGoDfLYT*7l8@ zQ*e%YZy3t1Q2>0W-s53mSru#g^mIByYhFgsg}Jq^`2xJ-S)JZ>Y(vH;t$B9D*a+!)fnIWv%ZdM`FN)XVWT?!kCNKaP9V z2h@s&{u%7+le(E081{C;HJx6Lmv>@FH}^H}Y?HDoHsjHr{x-#s9&7(`85ux>i6z*I ztJDQKFPj|6&vx7rL)zWv7Bp7|nngE`i*UAj)0t8qRJ+@@|4U!_3;$0ZJgfdkEC2ui M07*qoM6N<$f>2qcegFUf literal 0 HcmV?d00001 diff --git a/services/web/public/stylesheets/_style_includes.less b/services/web/public/stylesheets/_style_includes.less new file mode 100644 index 0000000000..a5c7589ef0 --- /dev/null +++ b/services/web/public/stylesheets/_style_includes.less @@ -0,0 +1,84 @@ +@import url(https://netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css); + +@import "core/mixins.less"; + +// Reset +@import "core/normalize.less"; +@import "core/print.less"; + +// Core CSS +@import "core/scaffolding.less"; +@import "core/type.less"; +@import "core/grid.less"; + +// Components +@import "components/tables.less"; +@import "components/forms.less"; +@import "components/buttons.less"; +@import "components/card.less"; +//@import "components/code.less"; +@import "components/component-animations.less"; +@import "components/glyphicons.less"; +@import "components/dropdowns.less"; +@import "components/button-groups.less"; +@import "components/input-groups.less"; +@import "components/navs.less"; +@import "components/navbar.less"; +@import "components/footer.less"; +//@import "components/breadcrumbs.less"; +//@import "components/pagination.less"; +@import "components/pager.less"; +@import "components/labels.less"; +//@import "components/badges.less"; +//@import "components/jumbotron.less"; +@import "components/thumbnails.less"; +@import "components/alerts.less"; +@import "components/progress-bars.less"; +// @import "components/media.less"; +// @import "components/list-group.less"; +// @import "components/panels.less"; +// @import "components/wells.less"; +@import "components/close.less"; +@import "components/fineupload.less"; +@import "components/hover.less"; + +// Components w/ JavaScript +@import "components/modals.less"; +@import "components/tooltip.less"; +@import "components/popovers.less"; +@import "components/carousel.less"; + +// ngTagsInput +@import "components/tags-input.less"; + +// Utility classes +@import "core/utilities.less"; +@import "core/responsive-utilities.less"; + +// ShareLaTeX app classes +@import "app/base.less"; +@import "app/account-settings.less"; +@import "app/beta-program.less"; +@import "app/about-page.less"; +@import "app/project-list.less"; +@import "app/editor.less"; +@import "app/homepage.less"; +@import "app/plans.less"; +@import "app/recurly.less"; +@import "app/bonus.less"; +@import "app/register.less"; +@import "app/blog.less"; +@import "app/features.less"; +@import "app/templates.less"; +@import "app/wiki.less"; +@import "app/translations.less"; +@import "app/contact-us.less"; +@import "app/subscription.less"; +@import "app/sprites.less"; +@import "app/invite.less"; +@import "app/review-features-page.less"; +@import "app/error-pages.less"; + +@import "../js/libs/pdfListView/TextLayer.css"; +@import "../js/libs/pdfListView/AnnotationsLayer.css"; +@import "../js/libs/pdfListView/HighlightsLayer.css"; diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less new file mode 100644 index 0000000000..6274b265f9 --- /dev/null +++ b/services/web/public/stylesheets/core/_common-variables.less @@ -0,0 +1,805 @@ +// +// Variables +// -------------------------------------------------- + +//== Scaffolding +// +// ## Settings for some of the most global styles. + +//** Background color for ``. +@body-bg: #fff; +//** Global text color on ``. +@text-color: @gray-dark; + +//** Global textual link color. +@link-color: @brand-primary; +//** Link hover color set via `darken()` function. +@link-hover-color: darken(@link-color, 15%); + + +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. + +@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); +//@import url(https://fonts.googleapis.com/css?family=PT+Serif:400,600,700); +//@import url(https://fonts.googleapis.com/css?family=PT+Serif:400,400i,700,700i); +@import url(https://fonts.googleapis.com/css?family=Merriweather:400,400i,700,700i); + +@font-family-sans-serif: "Open Sans", sans-serif; +@font-family-serif: "Merriweather", serif; +//** Default monospace fonts for ``, ``, and `
`.
+@font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
+@font-family-base:        @font-family-sans-serif;
+
+@font-size-base:          16px;
+@font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
+@font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
+
+@font-size-h1:            floor((@font-size-base * 2)); // ~36px
+@font-size-h2:            floor((@font-size-base * 1.6)); // ~30px
+@font-size-h3:            ceil((@font-size-base * 1.25)); // ~24px
+@font-size-h4:            ceil((@font-size-base * 1.1)); // ~18px
+@font-size-h5:            @font-size-base;
+@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
+
+//** Unit-less `line-height` for use in components like buttons.
+@line-height-base:        1.5625; // 20/14
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+@line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
+
+//** By default, this inherits from the ``.
+@headings-font-family:    @font-family-serif;
+@headings-font-weight:    500;
+@headings-line-height:    1.1;
+@headings-color:          @gray-dark;
+
+
+//-- Iconography
+//
+//## Specify custom locations of the include Glyphicons icon font. Useful for those including Bootstrap via Bower.
+
+@icon-font-path:          "../fonts/";
+@icon-font-name:          "glyphicons-halflings-regular";
+@icon-font-svg-id:        "glyphicons_halflingsregular";
+
+//== Components
+//
+//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
+
+@padding-base-vertical:     5px;
+@padding-base-horizontal:   16px;
+
+@padding-large-vertical:    10px;
+@padding-large-horizontal:  16px;
+
+@padding-small-vertical:    5px;
+@padding-small-horizontal:  10px;
+
+@padding-xs-vertical:       1px;
+@padding-xs-horizontal:     5px;
+
+@line-height-large:         1.33;
+@line-height-small:         1.5;
+
+@border-radius-base:        3px;
+@border-radius-large:       5px;
+@border-radius-small:       2px;
+
+//** Global color for active items (e.g., navs or dropdowns).
+@component-active-color:    #fff;
+//** Global background color for active items (e.g., navs or dropdowns).
+@component-active-bg:       @brand-primary;
+
+//** Width of the `border` for generating carets that indicator dropdowns.
+@caret-width-base:          4px;
+//** Carets increase slightly in size for larger components.
+@caret-width-large:         5px;
+
+
+//== Tables
+//
+//## Customizes the `.table` component with basic values, each used across all table variations.
+
+//** Padding for ``s and ``s.
+@table-cell-padding:            8px;
+//** Padding for cells in `.table-condensed`.
+@table-condensed-cell-padding:  5px;
+
+//** Default background color used for all tables.
+@table-bg:                      transparent;
+//** Background color used for `.table-striped`.
+@table-bg-accent:               #f9f9f9;
+//** Background color used for `.table-hover`.
+@table-bg-hover:                #f5f5f5;
+@table-bg-active:               @table-bg-hover;
+
+//** Border color for table and cell borders.
+@table-border-color:            #ddd;
+
+
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+
+@btn-font-weight:                700;
+
+@btn-default-color:              #333;
+@btn-default-bg:                 #fff;
+@btn-default-border:             #ccc;
+
+@btn-primary-color:              #fff;
+@btn-primary-bg:                 @brand-primary;
+@btn-primary-border:             darken(@btn-primary-bg, 10%);
+
+@btn-success-color:              #fff;
+@btn-success-bg:                 @brand-success;
+@btn-success-border:             darken(@btn-success-bg, 10%);
+
+@btn-info-color:                 #fff;
+@btn-info-bg:                    @brand-info;
+@btn-info-border:                darken(@btn-info-bg, 15%);
+
+@btn-warning-color:              #fff;
+@btn-warning-bg:                 @brand-warning;
+@btn-warning-border:             darken(@btn-warning-bg, 10%);
+
+@btn-danger-color:               #fff;
+@btn-danger-bg:                  @brand-danger;
+@btn-danger-border:              darken(@btn-danger-bg, 10%);
+
+@btn-link-disabled-color:        @gray-light;
+
+
+//== Forms
+//
+//##
+
+//** `` background color
+@input-bg:                       #fff;
+//** `` background color
+@input-bg-disabled:              @gray-lighter;
+
+//** Text color for ``s
+@input-color:                    @gray;
+//** `` border color
+@input-border:                   #ccc;
+//** `` border radius
+@input-border-radius:            @border-radius-base;
+//** Border color for inputs on focus
+@input-border-focus:             #66afe9;
+
+//** Placeholder text color
+@input-color-placeholder:        @gray-light;
+
+//** Default `.form-control` height
+@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
+//** Large `.form-control` height
+@input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
+//** Small `.form-control` height
+@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
+
+@legend-color:                   @gray-dark;
+@legend-border-color:            #e5e5e5;
+
+//** Background color for textual input addons
+@input-group-addon-bg:           @gray-lighter;
+//** Border color for textual input addons
+@input-group-addon-border-color: @input-border;
+
+
+//== Dropdowns
+//
+//## Dropdown menu container and contents.
+
+//** Background for the dropdown menu.
+@dropdown-bg:                    #fff;
+//** Dropdown menu `border-color`.
+@dropdown-border:                rgba(0,0,0,.15);
+//** Dropdown menu `border-color` **for IE8**.
+@dropdown-fallback-border:       #ccc;
+//** Divider color for between dropdown items.
+@dropdown-divider-bg:            #e5e5e5;
+
+//** Dropdown link text color.
+@dropdown-link-color:            @gray-dark;
+//** Hover color for dropdown links.
+@dropdown-link-hover-color:      #fff;
+//** Hover background for dropdown links.
+@dropdown-link-hover-bg:         @brand-primary;
+
+//** Active dropdown menu item text color.
+@dropdown-link-active-color:     @component-active-color;
+//** Active dropdown menu item background color.
+@dropdown-link-active-bg:        @component-active-bg;
+
+//** Disabled dropdown menu item background color.
+@dropdown-link-disabled-color:   @gray-light;
+
+//** Text color for headers within dropdown menus.
+@dropdown-header-color:          @gray-light;
+
+// Note: Deprecated @dropdown-caret-color as of v3.1.0
+@dropdown-caret-color:           #000;
+
+
+//-- Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+//
+// Note: These variables are not generated into the Customizer.
+
+@zindex-navbar:            1000;
+@zindex-dropdown:          1000;
+@zindex-popover:           1010;
+@zindex-tooltip:           1030;
+@zindex-navbar-fixed:      1030;
+@zindex-modal-background:  1040;
+@zindex-modal:             1050;
+
+
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+
+// Extra small screen / phone
+// Note: Deprecated @screen-xs and @screen-phone as of v3.0.1
+@screen-xs:                  480px;
+@screen-xs-min:              @screen-xs;
+@screen-phone:               @screen-xs-min;
+
+// Small screen / tablet
+// Note: Deprecated @screen-sm and @screen-tablet as of v3.0.1
+@screen-sm:                  768px;
+@screen-sm-min:              @screen-sm;
+@screen-tablet:              @screen-sm-min;
+
+// Medium screen / desktop
+// Note: Deprecated @screen-md and @screen-desktop as of v3.0.1
+@screen-md:                  992px;
+@screen-md-min:              @screen-md;
+@screen-desktop:             @screen-md-min;
+
+// Large screen / wide desktop
+// Note: Deprecated @screen-lg and @screen-lg-desktop as of v3.0.1
+@screen-lg:                  1200px;
+@screen-lg-min:              @screen-lg;
+@screen-lg-desktop:          @screen-lg-min;
+
+// So media queries don't overlap when required, provide a maximum
+@screen-xs-max:              (@screen-sm-min - 1);
+@screen-sm-max:              (@screen-md-min - 1);
+@screen-md-max:              (@screen-lg-min - 1);
+
+
+//== Grid system
+//
+//## Define your custom responsive grid.
+
+//** Number of columns in the grid.
+@grid-columns:              12;
+//** Padding between columns. Gets divided in half for the left and right.
+@grid-gutter-width:         30px;
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+@grid-float-breakpoint:     @screen-sm-min;
+//** Point at which the navbar begins collapsing.
+@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
+
+
+//== Container sizes
+//
+//## Define the maximum width of `.container` for different screen sizes.
+
+// Small screen / tablet
+@container-tablet:             ((720px + @grid-gutter-width));
+//** For `@screen-sm-min` and up.
+@container-sm:                 @container-tablet;
+
+// Medium screen / desktop
+@container-desktop:            ((940px + @grid-gutter-width));
+//** For `@screen-md-min` and up.
+@container-md:                 @container-desktop;
+
+// Large screen / wide desktop
+@container-large-desktop:      ((1140px + @grid-gutter-width));
+//** For `@screen-lg-min` and up.
+@container-lg:                 @container-large-desktop;
+
+
+//== Navbar
+//
+//##
+
+// Basics of a navbar
+@navbar-height:                    60px;
+@navbar-margin-bottom:             0;
+@navbar-border-radius:             0;
+@navbar-padding-horizontal:        floor((@grid-gutter-width / 2));
+@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
+@navbar-collapse-max-height:       340px;
+
+@navbar-default-color:             #777;
+@navbar-default-bg:                #fff;
+@navbar-default-border:            @gray-lighter;
+
+// Navbar links
+@navbar-default-link-color:                @link-color;
+@navbar-default-link-hover-color:          @link-hover-color;
+@navbar-default-link-hover-bg:             @link-hover-color;
+@navbar-default-link-active-color:         #fff;
+@navbar-default-link-active-bg:            @link-hover-color;
+@navbar-default-link-disabled-color:       #ccc;
+@navbar-default-link-disabled-bg:          transparent;
+
+// Navbar brand label
+@navbar-default-brand-color:               @navbar-default-link-color;
+@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);
+@navbar-default-brand-hover-bg:            transparent;
+
+// Navbar toggle
+@navbar-default-toggle-hover-bg:           @link-hover-color;
+@navbar-default-toggle-border-color:       @link-color;
+
+//== Navs
+//
+//##
+
+//=== Shared nav styles
+@nav-link-padding:                          10px 15px;
+@nav-link-hover-bg:                         @link-color;
+
+@nav-disabled-link-color:                   @gray-light;
+@nav-disabled-link-hover-color:             @gray-light;
+
+@nav-open-link-hover-color:                 #fff;
+
+//== Tabs
+@nav-tabs-border-color:                     #ddd;
+
+@nav-tabs-link-hover-border-color:          @link-color;
+
+@nav-tabs-active-link-hover-bg:             @body-bg;
+@nav-tabs-active-link-hover-color:          @gray;
+@nav-tabs-active-link-hover-border-color:   #ddd;
+
+@nav-tabs-justified-link-border-color:            #ddd;
+@nav-tabs-justified-active-link-border-color:     @body-bg;
+
+//== Pills
+@nav-pills-border-radius:                   @border-radius-base;
+@nav-pills-active-link-hover-bg:            @component-active-bg;
+@nav-pills-active-link-hover-color:         @component-active-color;
+
+
+//== Pagination
+//
+//##
+
+@pagination-color:                     @link-color;
+@pagination-bg:                        #fff;
+@pagination-border:                    #ddd;
+
+@pagination-hover-color:               @link-hover-color;
+@pagination-hover-bg:                  @gray-lighter;
+@pagination-hover-border:              #ddd;
+
+@pagination-active-color:              #fff;
+@pagination-active-bg:                 @brand-primary;
+@pagination-active-border:             @brand-primary;
+
+@pagination-disabled-color:            @gray-light;
+@pagination-disabled-bg:               #fff;
+@pagination-disabled-border:           #ddd;
+
+
+//== Pager
+//
+//##
+
+@pager-bg:                             @pagination-bg;
+@pager-border:                         @pagination-border;
+@pager-border-radius:                  15px;
+
+@pager-hover-bg:                       @pagination-hover-bg;
+
+@pager-active-bg:                      @pagination-active-bg;
+@pager-active-color:                   @pagination-active-color;
+
+@pager-disabled-color:                 @pagination-disabled-color;
+
+
+//== Jumbotron
+//
+//##
+
+@jumbotron-padding:              30px;
+@jumbotron-color:                inherit;
+@jumbotron-bg:                   @gray-lighter;
+@jumbotron-heading-color:        inherit;
+@jumbotron-font-size:            ceil((@font-size-base * 1.5));
+
+
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+
+@state-success-text:             darken(@brand-success, 20%);
+@state-success-bg:               lighten(@brand-success, 50%);
+@state-success-border:           darken(@brand-success, 5%);
+
+@state-info-text:                darken(@brand-info, 20%);
+@state-info-bg:                  lighten(@brand-info, 47%);
+@state-info-border:              darken(@brand-info, 7%);
+
+@state-warning-text:             darken(@brand-warning, 10%);
+@state-warning-bg:               lighten(@brand-warning, 45%);
+@state-warning-border:           @brand-warning;
+
+@state-danger-text:              darken(@brand-danger, 10%);
+@state-danger-bg:                lighten(@brand-danger, 50%);
+@state-danger-border:            darken(@brand-danger, 5%);
+
+
+//== Tooltips
+//
+//##
+
+//** Tooltip max width
+@tooltip-max-width:           200px;
+//** Tooltip text color
+@tooltip-color:               #fff;
+//** Tooltip background color
+@tooltip-bg:                  #000;
+@tooltip-opacity:             .9;
+
+//** Tooltip arrow width
+@tooltip-arrow-width:         5px;
+//** Tooltip arrow color
+@tooltip-arrow-color:         @tooltip-bg;
+
+
+//== Popovers
+//
+//##
+
+//** Popover body background color
+@popover-bg:                          #fff;
+//** Popover maximum width
+@popover-max-width:                   276px;
+//** Popover border color
+@popover-border-color:                rgba(0,0,0,.2);
+//** Popover fallback border color
+@popover-fallback-border-color:       #ccc;
+
+//** Popover title background color
+@popover-title-bg:                    darken(@popover-bg, 3%);
+
+//** Popover arrow width
+@popover-arrow-width:                 10px;
+//** Popover arrow color
+@popover-arrow-color:                 #fff;
+
+//** Popover outer arrow width
+@popover-arrow-outer-width:           (@popover-arrow-width + 1);
+//** Popover outer arrow color
+@popover-arrow-outer-color:           fadein(@popover-border-color, 5%);
+//** Popover outer arrow fallback color
+@popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);
+
+
+//== Labels
+//
+//##
+
+//** Default label background color
+@label-default-bg:            @gray-light;
+//** Primary label background color
+@label-primary-bg:            @brand-primary;
+//** Success label background color
+@label-success-bg:            @brand-success;
+//** Info label background color
+@label-info-bg:               @brand-info;
+//** Warning label background color
+@label-warning-bg:            @brand-warning;
+//** Danger label background color
+@label-danger-bg:             @brand-danger;
+
+//** Default label text color
+@label-color:                 #fff;
+//** Default text color of a linked label
+@label-link-hover-color:      #fff;
+
+
+//== Modals
+//
+//##
+
+//** Padding applied to the modal body
+@modal-inner-padding:         20px;
+
+//** Padding applied to the modal title
+@modal-title-padding:         15px;
+//** Modal title line-height
+@modal-title-line-height:     @line-height-base;
+
+//** Background color of modal content area
+@modal-content-bg:                             #fff;
+//** Modal content border color
+@modal-content-border-color:                   rgba(0,0,0,.2);
+//** Modal content border color **for IE8**
+@modal-content-fallback-border-color:          #999;
+
+//** Modal backdrop background color
+@modal-backdrop-bg:           #000;
+//** Modal backdrop opacity
+@modal-backdrop-opacity:      .5;
+//** Modal header border color
+@modal-header-border-color:   #e5e5e5;
+//** Modal footer border color
+@modal-footer-border-color:   @modal-header-border-color;
+@modal-footer-background-color:   @gray-lightest;
+
+@modal-lg:                    900px;
+@modal-md:                    600px;
+@modal-sm:                    300px;
+
+
+//== Alerts
+//
+//## Define alert colors, border radius, and padding.
+
+@alert-padding:               15px;
+@alert-border-radius:         @border-radius-base;
+@alert-link-font-weight:      bold;
+
+@alert-success-bg:            @state-success-bg;
+@alert-success-text:          @state-success-text;
+@alert-success-border:        @state-success-border;
+
+@alert-info-bg:               @state-info-bg;
+@alert-info-text:             @state-info-text;
+@alert-info-border:           @state-info-border;
+
+@alert-warning-bg:            @state-warning-bg;
+@alert-warning-text:          @state-warning-text;
+@alert-warning-border:        @state-warning-border;
+
+@alert-danger-bg:             @state-danger-bg;
+@alert-danger-text:           @state-danger-text;
+@alert-danger-border:         @state-danger-border;
+
+
+//== Progress bars
+//
+//##
+
+//** Background color of the whole progress component
+@progress-bg:                 white;
+@progress-border-color:       @gray-lighter;
+//** Progress bar text color
+@progress-bar-color:          #fff;
+
+//** Default progress bar color
+@progress-bar-bg:             @brand-primary;
+//** Success progress bar color
+@progress-bar-success-bg:     @brand-success;
+//** Warning progress bar color
+@progress-bar-warning-bg:     @brand-warning;
+//** Danger progress bar color
+@progress-bar-danger-bg:      @brand-danger;
+//** Info progress bar color
+@progress-bar-info-bg:        @brand-info;
+
+
+//== List group
+//
+//##
+
+//** Background color on `.list-group-item`
+@list-group-bg:                 #fff;
+//** `.list-group-item` border color
+@list-group-border:             #ddd;
+//** List group border radius
+@list-group-border-radius:      @border-radius-base;
+
+//** Background color of single list elements on hover
+@list-group-hover-bg:           #f5f5f5;
+//** Text color of active list elements
+@list-group-active-color:       @component-active-color;
+//** Background color of active list elements
+@list-group-active-bg:          @component-active-bg;
+//** Border color of active list elements
+@list-group-active-border:      @list-group-active-bg;
+@list-group-active-text-color:  lighten(@list-group-active-bg, 40%);
+
+@list-group-link-color:         #555;
+@list-group-link-heading-color: #333;
+
+
+//== Panels
+//
+//##
+
+@panel-bg:                    #fff;
+@panel-body-padding:          15px;
+@panel-border-radius:         @border-radius-base;
+
+//** Border color for elements within panels
+@panel-inner-border:          #ddd;
+@panel-footer-bg:             #f5f5f5;
+
+@panel-default-text:          @gray-dark;
+@panel-default-border:        #ddd;
+@panel-default-heading-bg:    #f5f5f5;
+
+@panel-primary-text:          #fff;
+@panel-primary-border:        @brand-primary;
+@panel-primary-heading-bg:    @brand-primary;
+
+@panel-success-text:          @state-success-text;
+@panel-success-border:        @state-success-border;
+@panel-success-heading-bg:    @state-success-bg;
+
+@panel-info-text:             @state-info-text;
+@panel-info-border:           @state-info-border;
+@panel-info-heading-bg:       @state-info-bg;
+
+@panel-warning-text:          @state-warning-text;
+@panel-warning-border:        @state-warning-border;
+@panel-warning-heading-bg:    @state-warning-bg;
+
+@panel-danger-text:           @state-danger-text;
+@panel-danger-border:         @state-danger-border;
+@panel-danger-heading-bg:     @state-danger-bg;
+
+
+//== Thumbnails
+//
+//##
+
+//** Padding around the thumbnail image
+@thumbnail-padding:           4px;
+//** Thumbnail background color
+@thumbnail-bg:                @body-bg;
+//** Thumbnail border color
+@thumbnail-border:            #ddd;
+//** Thumbnail border radius
+@thumbnail-border-radius:     @border-radius-base;
+
+//** Custom text color for thumbnail captions
+@thumbnail-caption-color:     @text-color;
+//** Padding around the thumbnail caption
+@thumbnail-caption-padding:   9px;
+
+
+//== Wells
+//
+//##
+
+@well-bg:                     #f5f5f5;
+@well-border:                 darken(@well-bg, 7%);
+
+
+//== Badges
+//
+//##
+
+@badge-color:                 #fff;
+//** Linked badge text color on hover
+@badge-link-hover-color:      #fff;
+@badge-bg:                    @gray-light;
+
+//** Badge text color in active nav link
+@badge-active-color:          @link-color;
+//** Badge background color in active nav link
+@badge-active-bg:             #fff;
+
+@badge-font-weight:           bold;
+@badge-line-height:           1;
+@badge-border-radius:         10px;
+
+
+//== Breadcrumbs
+//
+//##
+
+@breadcrumb-padding-vertical:   8px;
+@breadcrumb-padding-horizontal: 15px;
+//** Breadcrumb background color
+@breadcrumb-bg:                 #f5f5f5;
+//** Breadcrumb text color
+@breadcrumb-color:              #ccc;
+//** Text color of current page in the breadcrumb
+@breadcrumb-active-color:       @gray-light;
+//** Textual separator for between breadcrumb elements
+@breadcrumb-separator:          "/";
+
+
+//== Carousel
+//
+//##
+
+@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);
+
+@carousel-control-color:                      #fff;
+@carousel-control-width:                      15%;
+@carousel-control-opacity:                    .5;
+@carousel-control-font-size:                  20px;
+
+@carousel-indicator-active-bg:                #fff;
+@carousel-indicator-border-color:             #fff;
+
+@carousel-caption-color:                      #fff;
+
+
+//== Close
+//
+//##
+
+@close-font-weight:           bold;
+@close-color:                 #000;
+@close-text-shadow:           0 1px 0 #fff;
+
+
+//== Code
+//
+//##
+
+@code-color:                  #c7254e;
+@code-bg:                     #f9f2f4;
+
+@kbd-color:                   #fff;
+@kbd-bg:                      #333;
+
+@pre-bg:                      #f5f5f5;
+@pre-color:                   @gray-dark;
+@pre-border-color:            #ccc;
+@pre-scrollable-max-height:   340px;
+
+
+//== Type
+//
+//##
+
+//** Text muted color
+@text-muted:                  @gray-light;
+//** Abbreviations and acronyms border color
+@abbr-border-color:           @gray-light;
+//** Headings small color
+@headings-small-color:        @gray-light;
+//** Blockquote small color
+@blockquote-small-color:      @gray;
+//** Blockquote font size
+@blockquote-font-size:        (@font-size-base * 1.125);
+//** Blockquote border color
+@blockquote-border-color:     @gray-lighter;
+//** Page header border color
+@page-header-border-color:    @gray-lighter;
+
+
+//== Miscellaneous
+//
+//##
+
+//** Horizontal line color.
+@hr-border:                   @gray-lighter;
+
+//** Horizontal offset for forms and lists.
+@component-offset-horizontal: 180px;
+
+@content-margin-top: @line-height-computed;
+@content-margin-top: @line-height-computed;
+
+// Custom
+
+@left-menu-width: 260px;
+@left-menu-animation-duration: 0.35s;
+
+@toolbar-border-color: @gray-lighter;
+@file-tree-droppable-background-color: rgb(252, 231, 199);
+
+@editor-dark-background-color: #333;
+@editor-dark-toolbar-border-color: #222;
+@editor-dark-highlight-color: #FFA03A;
diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less
new file mode 100644
index 0000000000..d6516fdf63
--- /dev/null
+++ b/services/web/public/stylesheets/core/ol-variables.less
@@ -0,0 +1,31 @@
+@ol-green:      #4A9F48;
+@ol-dark-green: #1C5B26;
+
+//== Colors
+//
+//## Gray and brand colors for use across Bootstrap.
+
+
+@gray-darker:           #252525;
+@gray-dark:             #505050;
+@gray:                  #7a7a7a;
+@gray-light:            #a4a4a4;
+@gray-lighter:          #cfcfcf;
+@gray-lightest:         #f0f0f0;
+
+@blue:                  #405ebf;
+@blueDark:              #040D2D;
+@green:                 #46a546;
+@red:                   #a93529;
+@yellow:                #A1A729;
+@orange:                #f89406;
+@pink:                  #c3325f;
+@purple:                #7a43b6;
+
+@brand-primary:         @ol-green;
+@brand-success:         @green;
+@brand-info:            @ol-dark-green;
+@brand-warning:         @orange;
+@brand-danger:          #E03A06;
+
+@import "./_common-variables.less";
\ No newline at end of file
diff --git a/services/web/public/stylesheets/core/variables.less b/services/web/public/stylesheets/core/variables.less
index 7c1876ef9a..aca526bb19 100755
--- a/services/web/public/stylesheets/core/variables.less
+++ b/services/web/public/stylesheets/core/variables.less
@@ -1,8 +1,3 @@
-//
-// Variables
-// --------------------------------------------------
-
-
 //== Colors
 //
 //## Gray and brand colors for use across Bootstrap.
@@ -30,804 +25,4 @@
 @brand-warning:         @orange;
 @brand-danger:          #E03A06;
 
-//== Scaffolding
-//
-// ## Settings for some of the most global styles.
-
-//** Background color for ``.
-@body-bg:               #fff;
-//** Global text color on ``.
-@text-color:            @gray-dark;
-
-//** Global textual link color.
-@link-color:            @brand-primary;
-//** Link hover color set via `darken()` function.
-@link-hover-color:      darken(@link-color, 15%);
-
-
-//== Typography
-//
-//## Font, line-height, and color for body text, headings, and more.
-
-@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
-//@import url(https://fonts.googleapis.com/css?family=PT+Serif:400,600,700);
-//@import url(https://fonts.googleapis.com/css?family=PT+Serif:400,400i,700,700i);
-@import url(https://fonts.googleapis.com/css?family=Merriweather:400,400i,700,700i);
-
-@font-family-sans-serif:  "Open Sans", sans-serif;
-@font-family-serif:       "Merriweather", serif;
-//** Default monospace fonts for ``, ``, and `
`.
-@font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
-@font-family-base:        @font-family-sans-serif;
-
-@font-size-base:          16px;
-@font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
-@font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
-
-@font-size-h1:            floor((@font-size-base * 2)); // ~36px
-@font-size-h2:            floor((@font-size-base * 1.6)); // ~30px
-@font-size-h3:            ceil((@font-size-base * 1.25)); // ~24px
-@font-size-h4:            ceil((@font-size-base * 1.1)); // ~18px
-@font-size-h5:            @font-size-base;
-@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
-
-//** Unit-less `line-height` for use in components like buttons.
-@line-height-base:        1.5625; // 20/14
-//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
-@line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
-
-//** By default, this inherits from the ``.
-@headings-font-family:    @font-family-serif;
-@headings-font-weight:    500;
-@headings-line-height:    1.1;
-@headings-color:          @gray-dark;
-
-
-//-- Iconography
-//
-//## Specify custom locations of the include Glyphicons icon font. Useful for those including Bootstrap via Bower.
-
-@icon-font-path:          "../fonts/";
-@icon-font-name:          "glyphicons-halflings-regular";
-@icon-font-svg-id:        "glyphicons_halflingsregular";
-
-//== Components
-//
-//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
-
-@padding-base-vertical:     5px;
-@padding-base-horizontal:   16px;
-
-@padding-large-vertical:    10px;
-@padding-large-horizontal:  16px;
-
-@padding-small-vertical:    5px;
-@padding-small-horizontal:  10px;
-
-@padding-xs-vertical:       1px;
-@padding-xs-horizontal:     5px;
-
-@line-height-large:         1.33;
-@line-height-small:         1.5;
-
-@border-radius-base:        3px;
-@border-radius-large:       5px;
-@border-radius-small:       2px;
-
-//** Global color for active items (e.g., navs or dropdowns).
-@component-active-color:    #fff;
-//** Global background color for active items (e.g., navs or dropdowns).
-@component-active-bg:       @brand-primary;
-
-//** Width of the `border` for generating carets that indicator dropdowns.
-@caret-width-base:          4px;
-//** Carets increase slightly in size for larger components.
-@caret-width-large:         5px;
-
-
-//== Tables
-//
-//## Customizes the `.table` component with basic values, each used across all table variations.
-
-//** Padding for ``s and ``s.
-@table-cell-padding:            8px;
-//** Padding for cells in `.table-condensed`.
-@table-condensed-cell-padding:  5px;
-
-//** Default background color used for all tables.
-@table-bg:                      transparent;
-//** Background color used for `.table-striped`.
-@table-bg-accent:               #f9f9f9;
-//** Background color used for `.table-hover`.
-@table-bg-hover:                #f5f5f5;
-@table-bg-active:               @table-bg-hover;
-
-//** Border color for table and cell borders.
-@table-border-color:            #ddd;
-
-
-//== Buttons
-//
-//## For each of Bootstrap's buttons, define text, background and border color.
-
-@btn-font-weight:                700;
-
-@btn-default-color:              #333;
-@btn-default-bg:                 #fff;
-@btn-default-border:             #ccc;
-
-@btn-primary-color:              #fff;
-@btn-primary-bg:                 @brand-primary;
-@btn-primary-border:             darken(@btn-primary-bg, 10%);
-
-@btn-success-color:              #fff;
-@btn-success-bg:                 @brand-success;
-@btn-success-border:             darken(@btn-success-bg, 10%);
-
-@btn-info-color:                 #fff;
-@btn-info-bg:                    @brand-info;
-@btn-info-border:                darken(@btn-info-bg, 15%);
-
-@btn-warning-color:              #fff;
-@btn-warning-bg:                 @brand-warning;
-@btn-warning-border:             darken(@btn-warning-bg, 10%);
-
-@btn-danger-color:               #fff;
-@btn-danger-bg:                  @brand-danger;
-@btn-danger-border:              darken(@btn-danger-bg, 10%);
-
-@btn-link-disabled-color:        @gray-light;
-
-
-//== Forms
-//
-//##
-
-//** `` background color
-@input-bg:                       #fff;
-//** `` background color
-@input-bg-disabled:              @gray-lighter;
-
-//** Text color for ``s
-@input-color:                    @gray;
-//** `` border color
-@input-border:                   #ccc;
-//** `` border radius
-@input-border-radius:            @border-radius-base;
-//** Border color for inputs on focus
-@input-border-focus:             #66afe9;
-
-//** Placeholder text color
-@input-color-placeholder:        @gray-light;
-
-//** Default `.form-control` height
-@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
-//** Large `.form-control` height
-@input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
-//** Small `.form-control` height
-@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
-
-@legend-color:                   @gray-dark;
-@legend-border-color:            #e5e5e5;
-
-//** Background color for textual input addons
-@input-group-addon-bg:           @gray-lighter;
-//** Border color for textual input addons
-@input-group-addon-border-color: @input-border;
-
-
-//== Dropdowns
-//
-//## Dropdown menu container and contents.
-
-//** Background for the dropdown menu.
-@dropdown-bg:                    #fff;
-//** Dropdown menu `border-color`.
-@dropdown-border:                rgba(0,0,0,.15);
-//** Dropdown menu `border-color` **for IE8**.
-@dropdown-fallback-border:       #ccc;
-//** Divider color for between dropdown items.
-@dropdown-divider-bg:            #e5e5e5;
-
-//** Dropdown link text color.
-@dropdown-link-color:            @gray-dark;
-//** Hover color for dropdown links.
-@dropdown-link-hover-color:      #fff;
-//** Hover background for dropdown links.
-@dropdown-link-hover-bg:         @brand-primary;
-
-//** Active dropdown menu item text color.
-@dropdown-link-active-color:     @component-active-color;
-//** Active dropdown menu item background color.
-@dropdown-link-active-bg:        @component-active-bg;
-
-//** Disabled dropdown menu item background color.
-@dropdown-link-disabled-color:   @gray-light;
-
-//** Text color for headers within dropdown menus.
-@dropdown-header-color:          @gray-light;
-
-// Note: Deprecated @dropdown-caret-color as of v3.1.0
-@dropdown-caret-color:           #000;
-
-
-//-- Z-index master list
-//
-// Warning: Avoid customizing these values. They're used for a bird's eye view
-// of components dependent on the z-axis and are designed to all work together.
-//
-// Note: These variables are not generated into the Customizer.
-
-@zindex-navbar:            1000;
-@zindex-dropdown:          1000;
-@zindex-popover:           1010;
-@zindex-tooltip:           1030;
-@zindex-navbar-fixed:      1030;
-@zindex-modal-background:  1040;
-@zindex-modal:             1050;
-
-
-//== Media queries breakpoints
-//
-//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
-
-// Extra small screen / phone
-// Note: Deprecated @screen-xs and @screen-phone as of v3.0.1
-@screen-xs:                  480px;
-@screen-xs-min:              @screen-xs;
-@screen-phone:               @screen-xs-min;
-
-// Small screen / tablet
-// Note: Deprecated @screen-sm and @screen-tablet as of v3.0.1
-@screen-sm:                  768px;
-@screen-sm-min:              @screen-sm;
-@screen-tablet:              @screen-sm-min;
-
-// Medium screen / desktop
-// Note: Deprecated @screen-md and @screen-desktop as of v3.0.1
-@screen-md:                  992px;
-@screen-md-min:              @screen-md;
-@screen-desktop:             @screen-md-min;
-
-// Large screen / wide desktop
-// Note: Deprecated @screen-lg and @screen-lg-desktop as of v3.0.1
-@screen-lg:                  1200px;
-@screen-lg-min:              @screen-lg;
-@screen-lg-desktop:          @screen-lg-min;
-
-// So media queries don't overlap when required, provide a maximum
-@screen-xs-max:              (@screen-sm-min - 1);
-@screen-sm-max:              (@screen-md-min - 1);
-@screen-md-max:              (@screen-lg-min - 1);
-
-
-//== Grid system
-//
-//## Define your custom responsive grid.
-
-//** Number of columns in the grid.
-@grid-columns:              12;
-//** Padding between columns. Gets divided in half for the left and right.
-@grid-gutter-width:         30px;
-// Navbar collapse
-//** Point at which the navbar becomes uncollapsed.
-@grid-float-breakpoint:     @screen-sm-min;
-//** Point at which the navbar begins collapsing.
-@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
-
-
-//== Container sizes
-//
-//## Define the maximum width of `.container` for different screen sizes.
-
-// Small screen / tablet
-@container-tablet:             ((720px + @grid-gutter-width));
-//** For `@screen-sm-min` and up.
-@container-sm:                 @container-tablet;
-
-// Medium screen / desktop
-@container-desktop:            ((940px + @grid-gutter-width));
-//** For `@screen-md-min` and up.
-@container-md:                 @container-desktop;
-
-// Large screen / wide desktop
-@container-large-desktop:      ((1140px + @grid-gutter-width));
-//** For `@screen-lg-min` and up.
-@container-lg:                 @container-large-desktop;
-
-
-//== Navbar
-//
-//##
-
-// Basics of a navbar
-@navbar-height:                    60px;
-@navbar-margin-bottom:             0;
-@navbar-border-radius:             0;
-@navbar-padding-horizontal:        floor((@grid-gutter-width / 2));
-@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
-@navbar-collapse-max-height:       340px;
-
-@navbar-default-color:             #777;
-@navbar-default-bg:                #fff;
-@navbar-default-border:            @gray-lighter;
-
-// Navbar links
-@navbar-default-link-color:                @link-color;
-@navbar-default-link-hover-color:          @link-hover-color;
-@navbar-default-link-hover-bg:             @link-hover-color;
-@navbar-default-link-active-color:         #fff;
-@navbar-default-link-active-bg:            @link-hover-color;
-@navbar-default-link-disabled-color:       #ccc;
-@navbar-default-link-disabled-bg:          transparent;
-
-// Navbar brand label
-@navbar-default-brand-color:               @navbar-default-link-color;
-@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);
-@navbar-default-brand-hover-bg:            transparent;
-
-// Navbar toggle
-@navbar-default-toggle-hover-bg:           @link-hover-color;
-@navbar-default-toggle-border-color:       @link-color;
-
-//== Navs
-//
-//##
-
-//=== Shared nav styles
-@nav-link-padding:                          10px 15px;
-@nav-link-hover-bg:                         @link-color;
-
-@nav-disabled-link-color:                   @gray-light;
-@nav-disabled-link-hover-color:             @gray-light;
-
-@nav-open-link-hover-color:                 #fff;
-
-//== Tabs
-@nav-tabs-border-color:                     #ddd;
-
-@nav-tabs-link-hover-border-color:          @link-color;
-
-@nav-tabs-active-link-hover-bg:             @body-bg;
-@nav-tabs-active-link-hover-color:          @gray;
-@nav-tabs-active-link-hover-border-color:   #ddd;
-
-@nav-tabs-justified-link-border-color:            #ddd;
-@nav-tabs-justified-active-link-border-color:     @body-bg;
-
-//== Pills
-@nav-pills-border-radius:                   @border-radius-base;
-@nav-pills-active-link-hover-bg:            @component-active-bg;
-@nav-pills-active-link-hover-color:         @component-active-color;
-
-
-//== Pagination
-//
-//##
-
-@pagination-color:                     @link-color;
-@pagination-bg:                        #fff;
-@pagination-border:                    #ddd;
-
-@pagination-hover-color:               @link-hover-color;
-@pagination-hover-bg:                  @gray-lighter;
-@pagination-hover-border:              #ddd;
-
-@pagination-active-color:              #fff;
-@pagination-active-bg:                 @brand-primary;
-@pagination-active-border:             @brand-primary;
-
-@pagination-disabled-color:            @gray-light;
-@pagination-disabled-bg:               #fff;
-@pagination-disabled-border:           #ddd;
-
-
-//== Pager
-//
-//##
-
-@pager-bg:                             @pagination-bg;
-@pager-border:                         @pagination-border;
-@pager-border-radius:                  15px;
-
-@pager-hover-bg:                       @pagination-hover-bg;
-
-@pager-active-bg:                      @pagination-active-bg;
-@pager-active-color:                   @pagination-active-color;
-
-@pager-disabled-color:                 @pagination-disabled-color;
-
-
-//== Jumbotron
-//
-//##
-
-@jumbotron-padding:              30px;
-@jumbotron-color:                inherit;
-@jumbotron-bg:                   @gray-lighter;
-@jumbotron-heading-color:        inherit;
-@jumbotron-font-size:            ceil((@font-size-base * 1.5));
-
-
-//== Form states and alerts
-//
-//## Define colors for form feedback states and, by default, alerts.
-
-@state-success-text:             darken(@brand-success, 20%);
-@state-success-bg:               lighten(@brand-success, 50%);
-@state-success-border:           darken(@brand-success, 5%);
-
-@state-info-text:                darken(@brand-info, 20%);
-@state-info-bg:                  lighten(@brand-info, 47%);
-@state-info-border:              darken(@brand-info, 7%);
-
-@state-warning-text:             darken(@brand-warning, 10%);
-@state-warning-bg:               lighten(@brand-warning, 45%);
-@state-warning-border:           @brand-warning;
-
-@state-danger-text:              darken(@brand-danger, 10%);
-@state-danger-bg:                lighten(@brand-danger, 50%);
-@state-danger-border:            darken(@brand-danger, 5%);
-
-
-//== Tooltips
-//
-//##
-
-//** Tooltip max width
-@tooltip-max-width:           200px;
-//** Tooltip text color
-@tooltip-color:               #fff;
-//** Tooltip background color
-@tooltip-bg:                  #000;
-@tooltip-opacity:             .9;
-
-//** Tooltip arrow width
-@tooltip-arrow-width:         5px;
-//** Tooltip arrow color
-@tooltip-arrow-color:         @tooltip-bg;
-
-
-//== Popovers
-//
-//##
-
-//** Popover body background color
-@popover-bg:                          #fff;
-//** Popover maximum width
-@popover-max-width:                   276px;
-//** Popover border color
-@popover-border-color:                rgba(0,0,0,.2);
-//** Popover fallback border color
-@popover-fallback-border-color:       #ccc;
-
-//** Popover title background color
-@popover-title-bg:                    darken(@popover-bg, 3%);
-
-//** Popover arrow width
-@popover-arrow-width:                 10px;
-//** Popover arrow color
-@popover-arrow-color:                 #fff;
-
-//** Popover outer arrow width
-@popover-arrow-outer-width:           (@popover-arrow-width + 1);
-//** Popover outer arrow color
-@popover-arrow-outer-color:           fadein(@popover-border-color, 5%);
-//** Popover outer arrow fallback color
-@popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);
-
-
-//== Labels
-//
-//##
-
-//** Default label background color
-@label-default-bg:            @gray-light;
-//** Primary label background color
-@label-primary-bg:            @brand-primary;
-//** Success label background color
-@label-success-bg:            @brand-success;
-//** Info label background color
-@label-info-bg:               @brand-info;
-//** Warning label background color
-@label-warning-bg:            @brand-warning;
-//** Danger label background color
-@label-danger-bg:             @brand-danger;
-
-//** Default label text color
-@label-color:                 #fff;
-//** Default text color of a linked label
-@label-link-hover-color:      #fff;
-
-
-//== Modals
-//
-//##
-
-//** Padding applied to the modal body
-@modal-inner-padding:         20px;
-
-//** Padding applied to the modal title
-@modal-title-padding:         15px;
-//** Modal title line-height
-@modal-title-line-height:     @line-height-base;
-
-//** Background color of modal content area
-@modal-content-bg:                             #fff;
-//** Modal content border color
-@modal-content-border-color:                   rgba(0,0,0,.2);
-//** Modal content border color **for IE8**
-@modal-content-fallback-border-color:          #999;
-
-//** Modal backdrop background color
-@modal-backdrop-bg:           #000;
-//** Modal backdrop opacity
-@modal-backdrop-opacity:      .5;
-//** Modal header border color
-@modal-header-border-color:   #e5e5e5;
-//** Modal footer border color
-@modal-footer-border-color:   @modal-header-border-color;
-@modal-footer-background-color:   @gray-lightest;
-
-@modal-lg:                    900px;
-@modal-md:                    600px;
-@modal-sm:                    300px;
-
-
-//== Alerts
-//
-//## Define alert colors, border radius, and padding.
-
-@alert-padding:               15px;
-@alert-border-radius:         @border-radius-base;
-@alert-link-font-weight:      bold;
-
-@alert-success-bg:            @state-success-bg;
-@alert-success-text:          @state-success-text;
-@alert-success-border:        @state-success-border;
-
-@alert-info-bg:               @state-info-bg;
-@alert-info-text:             @state-info-text;
-@alert-info-border:           @state-info-border;
-
-@alert-warning-bg:            @state-warning-bg;
-@alert-warning-text:          @state-warning-text;
-@alert-warning-border:        @state-warning-border;
-
-@alert-danger-bg:             @state-danger-bg;
-@alert-danger-text:           @state-danger-text;
-@alert-danger-border:         @state-danger-border;
-
-
-//== Progress bars
-//
-//##
-
-//** Background color of the whole progress component
-@progress-bg:                 white;
-@progress-border-color:       @gray-lighter;
-//** Progress bar text color
-@progress-bar-color:          #fff;
-
-//** Default progress bar color
-@progress-bar-bg:             @brand-primary;
-//** Success progress bar color
-@progress-bar-success-bg:     @brand-success;
-//** Warning progress bar color
-@progress-bar-warning-bg:     @brand-warning;
-//** Danger progress bar color
-@progress-bar-danger-bg:      @brand-danger;
-//** Info progress bar color
-@progress-bar-info-bg:        @brand-info;
-
-
-//== List group
-//
-//##
-
-//** Background color on `.list-group-item`
-@list-group-bg:                 #fff;
-//** `.list-group-item` border color
-@list-group-border:             #ddd;
-//** List group border radius
-@list-group-border-radius:      @border-radius-base;
-
-//** Background color of single list elements on hover
-@list-group-hover-bg:           #f5f5f5;
-//** Text color of active list elements
-@list-group-active-color:       @component-active-color;
-//** Background color of active list elements
-@list-group-active-bg:          @component-active-bg;
-//** Border color of active list elements
-@list-group-active-border:      @list-group-active-bg;
-@list-group-active-text-color:  lighten(@list-group-active-bg, 40%);
-
-@list-group-link-color:         #555;
-@list-group-link-heading-color: #333;
-
-
-//== Panels
-//
-//##
-
-@panel-bg:                    #fff;
-@panel-body-padding:          15px;
-@panel-border-radius:         @border-radius-base;
-
-//** Border color for elements within panels
-@panel-inner-border:          #ddd;
-@panel-footer-bg:             #f5f5f5;
-
-@panel-default-text:          @gray-dark;
-@panel-default-border:        #ddd;
-@panel-default-heading-bg:    #f5f5f5;
-
-@panel-primary-text:          #fff;
-@panel-primary-border:        @brand-primary;
-@panel-primary-heading-bg:    @brand-primary;
-
-@panel-success-text:          @state-success-text;
-@panel-success-border:        @state-success-border;
-@panel-success-heading-bg:    @state-success-bg;
-
-@panel-info-text:             @state-info-text;
-@panel-info-border:           @state-info-border;
-@panel-info-heading-bg:       @state-info-bg;
-
-@panel-warning-text:          @state-warning-text;
-@panel-warning-border:        @state-warning-border;
-@panel-warning-heading-bg:    @state-warning-bg;
-
-@panel-danger-text:           @state-danger-text;
-@panel-danger-border:         @state-danger-border;
-@panel-danger-heading-bg:     @state-danger-bg;
-
-
-//== Thumbnails
-//
-//##
-
-//** Padding around the thumbnail image
-@thumbnail-padding:           4px;
-//** Thumbnail background color
-@thumbnail-bg:                @body-bg;
-//** Thumbnail border color
-@thumbnail-border:            #ddd;
-//** Thumbnail border radius
-@thumbnail-border-radius:     @border-radius-base;
-
-//** Custom text color for thumbnail captions
-@thumbnail-caption-color:     @text-color;
-//** Padding around the thumbnail caption
-@thumbnail-caption-padding:   9px;
-
-
-//== Wells
-//
-//##
-
-@well-bg:                     #f5f5f5;
-@well-border:                 darken(@well-bg, 7%);
-
-
-//== Badges
-//
-//##
-
-@badge-color:                 #fff;
-//** Linked badge text color on hover
-@badge-link-hover-color:      #fff;
-@badge-bg:                    @gray-light;
-
-//** Badge text color in active nav link
-@badge-active-color:          @link-color;
-//** Badge background color in active nav link
-@badge-active-bg:             #fff;
-
-@badge-font-weight:           bold;
-@badge-line-height:           1;
-@badge-border-radius:         10px;
-
-
-//== Breadcrumbs
-//
-//##
-
-@breadcrumb-padding-vertical:   8px;
-@breadcrumb-padding-horizontal: 15px;
-//** Breadcrumb background color
-@breadcrumb-bg:                 #f5f5f5;
-//** Breadcrumb text color
-@breadcrumb-color:              #ccc;
-//** Text color of current page in the breadcrumb
-@breadcrumb-active-color:       @gray-light;
-//** Textual separator for between breadcrumb elements
-@breadcrumb-separator:          "/";
-
-
-//== Carousel
-//
-//##
-
-@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);
-
-@carousel-control-color:                      #fff;
-@carousel-control-width:                      15%;
-@carousel-control-opacity:                    .5;
-@carousel-control-font-size:                  20px;
-
-@carousel-indicator-active-bg:                #fff;
-@carousel-indicator-border-color:             #fff;
-
-@carousel-caption-color:                      #fff;
-
-
-//== Close
-//
-//##
-
-@close-font-weight:           bold;
-@close-color:                 #000;
-@close-text-shadow:           0 1px 0 #fff;
-
-
-//== Code
-//
-//##
-
-@code-color:                  #c7254e;
-@code-bg:                     #f9f2f4;
-
-@kbd-color:                   #fff;
-@kbd-bg:                      #333;
-
-@pre-bg:                      #f5f5f5;
-@pre-color:                   @gray-dark;
-@pre-border-color:            #ccc;
-@pre-scrollable-max-height:   340px;
-
-
-//== Type
-//
-//##
-
-//** Text muted color
-@text-muted:                  @gray-light;
-//** Abbreviations and acronyms border color
-@abbr-border-color:           @gray-light;
-//** Headings small color
-@headings-small-color:        @gray-light;
-//** Blockquote small color
-@blockquote-small-color:      @gray;
-//** Blockquote font size
-@blockquote-font-size:        (@font-size-base * 1.125);
-//** Blockquote border color
-@blockquote-border-color:     @gray-lighter;
-//** Page header border color
-@page-header-border-color:    @gray-lighter;
-
-
-//== Miscellaneous
-//
-//##
-
-//** Horizontal line color.
-@hr-border:                   @gray-lighter;
-
-//** Horizontal offset for forms and lists.
-@component-offset-horizontal: 180px;
-
-@content-margin-top: @line-height-computed;
-@content-margin-top: @line-height-computed;
-
-// Custom
-
-@left-menu-width: 260px;
-@left-menu-animation-duration: 0.35s;
-
-@toolbar-border-color: @gray-lighter;
-@file-tree-droppable-background-color: rgb(252, 231, 199);
-
-@editor-dark-background-color: #333;
-@editor-dark-toolbar-border-color: #222;
-@editor-dark-highlight-color: #FFA03A;
+@import "_common-variables.less";
\ No newline at end of file
diff --git a/services/web/public/stylesheets/ol-style.less b/services/web/public/stylesheets/ol-style.less
new file mode 100644
index 0000000000..ec53e5e8c8
--- /dev/null
+++ b/services/web/public/stylesheets/ol-style.less
@@ -0,0 +1,3 @@
+// Core variables and mixins
+@import "core/ol-variables.less";
+@import "_style_includes.less";
\ No newline at end of file
diff --git a/services/web/public/stylesheets/style.less b/services/web/public/stylesheets/style.less
index b3e0c15294..760f378719 100755
--- a/services/web/public/stylesheets/style.less
+++ b/services/web/public/stylesheets/style.less
@@ -1,86 +1,3 @@
 // Core variables and mixins
 @import "core/variables.less";
-@import url(https://netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css);
-
-@import "core/mixins.less";
-
-// Reset
-@import "core/normalize.less";
-@import "core/print.less";
-
-// Core CSS
-@import "core/scaffolding.less";
-@import "core/type.less";
-@import "core/grid.less";
-
-// Components
-@import "components/tables.less";
-@import "components/forms.less";
-@import "components/buttons.less";
-@import "components/card.less";
-//@import "components/code.less";
-@import "components/component-animations.less";
-@import "components/glyphicons.less";
-@import "components/dropdowns.less";
-@import "components/button-groups.less";
-@import "components/input-groups.less";
-@import "components/navs.less";
-@import "components/navbar.less";
-@import "components/footer.less";
-//@import "components/breadcrumbs.less";
-//@import "components/pagination.less";
-@import "components/pager.less";
-@import "components/labels.less";
-//@import "components/badges.less";
-//@import "components/jumbotron.less";
-@import "components/thumbnails.less";
-@import "components/alerts.less";
-@import "components/progress-bars.less";
-// @import "components/media.less";
-// @import "components/list-group.less";
-// @import "components/panels.less";
-// @import "components/wells.less";
-@import "components/close.less";
-@import "components/fineupload.less";
-@import "components/hover.less";
-
-// Components w/ JavaScript
-@import "components/modals.less";
-@import "components/tooltip.less";
-@import "components/popovers.less";
-@import "components/carousel.less";
-
-// ngTagsInput
-@import "components/tags-input.less";
-
-// Utility classes
-@import "core/utilities.less";
-@import "core/responsive-utilities.less";
-
-// ShareLaTeX app classes
-@import "app/base.less";
-@import "app/account-settings.less";
-@import "app/beta-program.less";
-@import "app/about-page.less";
-@import "app/project-list.less";
-@import "app/editor.less";
-@import "app/homepage.less";
-@import "app/plans.less";
-@import "app/recurly.less";
-@import "app/bonus.less";
-@import "app/register.less";
-@import "app/blog.less";
-@import "app/features.less";
-@import "app/templates.less";
-@import "app/wiki.less";
-@import "app/translations.less";
-@import "app/contact-us.less";
-@import "app/subscription.less";
-@import "app/sprites.less";
-@import "app/invite.less";
-@import "app/review-features-page.less";
-@import "app/error-pages.less";
-
-@import	"../js/libs/pdfListView/TextLayer.css";
-@import	"../js/libs/pdfListView/AnnotationsLayer.css";
-@import	"../js/libs/pdfListView/HighlightsLayer.css";
+@import "_style_includes.less";
\ No newline at end of file

From d9e1254a6d7dbb1ca9d6565a5fbbc5948faad524 Mon Sep 17 00:00:00 2001
From: Paulo Reis 
Date: Tue, 1 Aug 2017 16:02:19 +0100
Subject: [PATCH 22/26] Update loading screen.

---
 services/web/app/views/project/editor.pug      |  4 ++--
 .../public/img/ol-brand/overleaf-o-grey.svg    | 18 ++++++++++++++++++
 .../web/public/img/ol-brand/overleaf-o.svg     | 10 ++++++++++
 .../web/public/stylesheets/app/editor.less     | 10 +++++-----
 4 files changed, 35 insertions(+), 7 deletions(-)
 create mode 100644 services/web/public/img/ol-brand/overleaf-o-grey.svg
 create mode 100644 services/web/public/img/ol-brand/overleaf-o.svg

diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug
index 5b6137738b..7cf3e40e78 100644
--- a/services/web/app/views/project/editor.pug
+++ b/services/web/app/views/project/editor.pug
@@ -8,8 +8,8 @@ block vars
 block content
 	.editor(ng-controller="IdeController").full-size
 		.loading-screen(ng-if="state.loading")
-			.loading-screen-lion-container
-				.loading-screen-lion(
+			.loading-screen-brand-container
+				.loading-screen-brand(
 					style="height: 20%;"
 					ng-style="{ 'height': state.load_progress + '%' }"
 				)
diff --git a/services/web/public/img/ol-brand/overleaf-o-grey.svg b/services/web/public/img/ol-brand/overleaf-o-grey.svg
new file mode 100644
index 0000000000..3b47c37cba
--- /dev/null
+++ b/services/web/public/img/ol-brand/overleaf-o-grey.svg
@@ -0,0 +1,18 @@
+
+
+
+
+
+	
+		
+			
+		
+	
+
+
diff --git a/services/web/public/img/ol-brand/overleaf-o.svg b/services/web/public/img/ol-brand/overleaf-o.svg
new file mode 100644
index 0000000000..d95cee9ded
--- /dev/null
+++ b/services/web/public/img/ol-brand/overleaf-o.svg
@@ -0,0 +1,10 @@
+
+
+  
+    
+      
+        
+      
+    
+  
+
\ No newline at end of file
diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less
index aae2ecdc9f..2b2347aac0 100644
--- a/services/web/public/stylesheets/app/editor.less
+++ b/services/web/public/stylesheets/app/editor.less
@@ -72,17 +72,17 @@
 	height: 100%;
 	background-color: #FFF;
 }
-	.loading-screen-lion-container {
+	.loading-screen-brand-container {
 		width: 15%;
 		min-width: 200px;
 		text-align: center;
 	}
-		.loading-screen-lion {
+		.loading-screen-brand {
 			position: relative;
 			width: 100%;
-			padding-top: 86.2%;
+			padding-top: 115.44%;
 			height: 0;
-			background: url(/img/brand/lion-grey.svg) no-repeat bottom / 100%;
+			background: url(/img/ol-brand/overleaf-o-grey.svg) no-repeat bottom / 100%;
 
     		&::after {
 				content: '';
@@ -91,7 +91,7 @@
 				right: 0;
 				bottom: 0;
 				left: 0;
-				background: url(/img/brand/lion.svg) no-repeat bottom / 100%;
+				background: url(/img/ol-brand/overleaf-o.svg) no-repeat bottom / 100%;
 				transition: height .5s;
     		}
 		}

From 4ab5451827dcb43864b58254faac2a7ad0710763 Mon Sep 17 00:00:00 2001
From: James Allen 
Date: Mon, 7 Aug 2017 16:45:33 +0200
Subject: [PATCH 23/26] Keep both editor loading logos

---
 services/web/public/stylesheets/app/editor.less        | 6 +++---
 services/web/public/stylesheets/core/ol-variables.less | 5 ++++-
 services/web/public/stylesheets/core/variables.less    | 5 ++++-
 3 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less
index 2b2347aac0..184e50270c 100644
--- a/services/web/public/stylesheets/app/editor.less
+++ b/services/web/public/stylesheets/app/editor.less
@@ -80,9 +80,9 @@
 		.loading-screen-brand {
 			position: relative;
 			width: 100%;
-			padding-top: 115.44%;
+			padding-top: @editor-loading-logo-padding-top;
 			height: 0;
-			background: url(/img/ol-brand/overleaf-o-grey.svg) no-repeat bottom / 100%;
+			background: @editor-loading-logo-background-url no-repeat bottom / 100%;
 
     		&::after {
 				content: '';
@@ -91,7 +91,7 @@
 				right: 0;
 				bottom: 0;
 				left: 0;
-				background: url(/img/ol-brand/overleaf-o.svg) no-repeat bottom / 100%;
+				background: @editor-loading-logo-background-url no-repeat bottom / 100%;
 				transition: height .5s;
     		}
 		}
diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less
index d6516fdf63..3cefc3dad3 100644
--- a/services/web/public/stylesheets/core/ol-variables.less
+++ b/services/web/public/stylesheets/core/ol-variables.less
@@ -28,4 +28,7 @@
 @brand-warning:         @orange;
 @brand-danger:          #E03A06;
 
-@import "./_common-variables.less";
\ No newline at end of file
+@editor-loading-logo-padding-top: 115.44%;
+@editor-loading-logo-background-url: url(/img/ol-brand/overleaf-o-grey.svg);
+
+@import "./_common-variables.less";
diff --git a/services/web/public/stylesheets/core/variables.less b/services/web/public/stylesheets/core/variables.less
index aca526bb19..e75c947738 100755
--- a/services/web/public/stylesheets/core/variables.less
+++ b/services/web/public/stylesheets/core/variables.less
@@ -25,4 +25,7 @@
 @brand-warning:         @orange;
 @brand-danger:          #E03A06;
 
-@import "_common-variables.less";
\ No newline at end of file
+@editor-loading-logo-padding-top: 86.2%;
+@editor-loading-logo-background-url: url(/img/brand/lion-grey.svg);
+
+@import "_common-variables.less";

From b4029359bcb8e3ecc4a60d755e81bbd2277c2b42 Mon Sep 17 00:00:00 2001
From: James Allen 
Date: Tue, 8 Aug 2017 08:17:16 +0200
Subject: [PATCH 24/26] Fix loading indicator

---
 services/web/public/stylesheets/app/editor.less        | 2 +-
 services/web/public/stylesheets/core/ol-variables.less | 1 +
 services/web/public/stylesheets/core/variables.less    | 1 +
 3 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less
index 184e50270c..75b7121094 100644
--- a/services/web/public/stylesheets/app/editor.less
+++ b/services/web/public/stylesheets/app/editor.less
@@ -91,7 +91,7 @@
 				right: 0;
 				bottom: 0;
 				left: 0;
-				background: @editor-loading-logo-background-url no-repeat bottom / 100%;
+				background: @editor-loading-logo-foreground-url no-repeat bottom / 100%;
 				transition: height .5s;
     		}
 		}
diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less
index 3cefc3dad3..a126479a42 100644
--- a/services/web/public/stylesheets/core/ol-variables.less
+++ b/services/web/public/stylesheets/core/ol-variables.less
@@ -30,5 +30,6 @@
 
 @editor-loading-logo-padding-top: 115.44%;
 @editor-loading-logo-background-url: url(/img/ol-brand/overleaf-o-grey.svg);
+@editor-loading-logo-foreground-url: url(/img/ol-brand/overleaf-o.svg);
 
 @import "./_common-variables.less";
diff --git a/services/web/public/stylesheets/core/variables.less b/services/web/public/stylesheets/core/variables.less
index e75c947738..3ec05ccebd 100755
--- a/services/web/public/stylesheets/core/variables.less
+++ b/services/web/public/stylesheets/core/variables.less
@@ -27,5 +27,6 @@
 
 @editor-loading-logo-padding-top: 86.2%;
 @editor-loading-logo-background-url: url(/img/brand/lion-grey.svg);
+@editor-loading-logo-foreground-url: url(/img/brand/lion.svg);
 
 @import "_common-variables.less";

From f9690f9db0a2972266d9c661e58b998f30d2a8b7 Mon Sep 17 00:00:00 2001
From: James Allen 
Date: Tue, 8 Aug 2017 08:26:26 +0200
Subject: [PATCH 25/26] Add Overleaf header logo

---
 .../web/public/img/ol-brand/logo-horizontal.png | Bin 0 -> 16961 bytes
 .../public/stylesheets/components/navbar.less   |   2 +-
 .../public/stylesheets/core/ol-variables.less   |   2 ++
 .../web/public/stylesheets/core/variables.less  |   2 ++
 4 files changed, 5 insertions(+), 1 deletion(-)
 create mode 100644 services/web/public/img/ol-brand/logo-horizontal.png

diff --git a/services/web/public/img/ol-brand/logo-horizontal.png b/services/web/public/img/ol-brand/logo-horizontal.png
new file mode 100644
index 0000000000000000000000000000000000000000..6e95b282cb1f1d0c254cc0cd1abc3fdaa36edb3f
GIT binary patch
literal 16961
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	i_CtSJ(sarqRcQ+*Cab_SErrkI
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

literal 0
HcmV?d00001

diff --git a/services/web/public/stylesheets/components/navbar.less b/services/web/public/stylesheets/components/navbar.less
index 9da0dfabc5..52a4a10029 100755
--- a/services/web/public/stylesheets/components/navbar.less
+++ b/services/web/public/stylesheets/components/navbar.less
@@ -386,7 +386,7 @@
     bottom: 5px;
     width: 180px;
     padding: 0;
-    background-image: url('/img/brand/logo-horizontal.svg');
+    background-image: @navbar-brand-image-url;
     background-size: contain;
     background-repeat: no-repeat;
     background-position: left center;
diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less
index a126479a42..23151bd9c4 100644
--- a/services/web/public/stylesheets/core/ol-variables.less
+++ b/services/web/public/stylesheets/core/ol-variables.less
@@ -28,6 +28,8 @@
 @brand-warning:         @orange;
 @brand-danger:          #E03A06;
 
+@navbar-brand-image-url: url(/img/ol-brand/logo-horizontal.png);
+
 @editor-loading-logo-padding-top: 115.44%;
 @editor-loading-logo-background-url: url(/img/ol-brand/overleaf-o-grey.svg);
 @editor-loading-logo-foreground-url: url(/img/ol-brand/overleaf-o.svg);
diff --git a/services/web/public/stylesheets/core/variables.less b/services/web/public/stylesheets/core/variables.less
index 3ec05ccebd..5fd9bfeddc 100755
--- a/services/web/public/stylesheets/core/variables.less
+++ b/services/web/public/stylesheets/core/variables.less
@@ -25,6 +25,8 @@
 @brand-warning:         @orange;
 @brand-danger:          #E03A06;
 
+@navbar-brand-image-url: url(/img/brand/logo-horizontal.svg);
+
 @editor-loading-logo-padding-top: 86.2%;
 @editor-loading-logo-background-url: url(/img/brand/lion-grey.svg);
 @editor-loading-logo-foreground-url: url(/img/brand/lion.svg);

From 0be1ee6b6ceb6c6d915cf8f4fc8684482cafd023 Mon Sep 17 00:00:00 2001
From: James Allen 
Date: Tue, 8 Aug 2017 12:31:36 +0200
Subject: [PATCH 26/26] Fix smoke test when product name changes

---
 services/web/test/smoke/coffee/SmokeTests.coffee | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/services/web/test/smoke/coffee/SmokeTests.coffee b/services/web/test/smoke/coffee/SmokeTests.coffee
index cdb63b1a7b..0e6f7d6244 100644
--- a/services/web/test/smoke/coffee/SmokeTests.coffee
+++ b/services/web/test/smoke/coffee/SmokeTests.coffee
@@ -94,7 +94,7 @@ describe "Opening", ->
 		
 			expect(error, "smoke test: error returned in getting project list").to.not.exist
 			expect(!!stderr.match("200 OK"), "smoke test: response code is not 200 getting project list").to.equal true
-			expect(!!stdout.match("Your Projects - ShareLaTeX, Online LaTeX Editor"), "smoke test: body does not have correct title").to.equal true
+			expect(!!stdout.match("Your Projects - .*, Online LaTeX Editor"), "smoke test: body does not have correct title").to.equal true
 			expect(!!stdout.match("ProjectPageController"), "smoke test: body does not have correct angular controller").to.equal true
 			done()