From 71604ebd5cfc4ffb996cbc47ebe18990759a901e Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 30 Nov 2017 15:12:36 +0000 Subject: [PATCH 01/27] Create uiConfig Pug local; render it in the HTML. --- .../web/app/coffee/infrastructure/ExpressLocals.coffee | 9 +++++++++ services/web/app/views/project/editor.pug | 1 + 2 files changed, 10 insertions(+) diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index fa9a223ad7..318513e5dc 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -290,3 +290,12 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)-> res.locals.moduleIncludes = Modules.moduleIncludes res.locals.moduleIncludesAvailable = Modules.moduleIncludesAvailable next() + + webRouter.use (req, res, next) -> + isOl = (Settings.brandPrefix == 'ol-') + res.locals.uiConfig = + defaultResizerSizeOpen : if isOl then 2 else 24 + defaultResizerSizeClosed : if isOl then 2 else 24 + chatResizerSizeOpen : if isOl then 2 else 6 + chatResizerSizeClosed : 0 + next() diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug index 1e1f2c982b..9d930776a6 100644 --- a/services/web/app/views/project/editor.pug +++ b/services/web/app/views/project/editor.pug @@ -157,6 +157,7 @@ block requirejs window.aceFingerprint = "#{fingerprint(jsPath + lib('ace') + '/ace.js')}" window.aceWorkerPath = "#{aceWorkerPath}"; window.pdfCMapsPath = "#{pdfCMapsPath}" + window.uiConfig = JSON.parse('!{JSON.stringify(uiConfig).replace(/\//g, "\\/")}'); script( data-main=buildJsPath("ide.js", {fingerprint:false}), From 024741ac51c536ba24f9fa2113f96fcb2193e9af Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 30 Nov 2017 15:24:10 +0000 Subject: [PATCH 02/27] Correct chat resizer values. --- services/web/app/coffee/infrastructure/ExpressLocals.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index 318513e5dc..ba4fe961e6 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -296,6 +296,6 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)-> res.locals.uiConfig = defaultResizerSizeOpen : if isOl then 2 else 24 defaultResizerSizeClosed : if isOl then 2 else 24 - chatResizerSizeOpen : if isOl then 2 else 6 + chatResizerSizeOpen : if isOl then 2 else 12 chatResizerSizeClosed : 0 next() From 826f49b9a4b9fe1ea233e57f72d1bab2524069e5 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 30 Nov 2017 15:24:39 +0000 Subject: [PATCH 03/27] Add default resizer values to layout directive. --- services/web/public/coffee/ide/directives/layout.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/public/coffee/ide/directives/layout.coffee b/services/web/public/coffee/ide/directives/layout.coffee index 21d9230b52..d4e98fb0d3 100644 --- a/services/web/public/coffee/ide/directives/layout.coffee +++ b/services/web/public/coffee/ide/directives/layout.coffee @@ -11,12 +11,12 @@ define [ if attrs.spacingOpen? spacingOpen = parseInt(attrs.spacingOpen, 10) else - spacingOpen = 24 + spacingOpen = window.uiConfig.defaultResizerSizeOpen if attrs.spacingClosed? spacingClosed = parseInt(attrs.spacingClosed, 10) else - spacingClosed = 24 + spacingClosed = window.uiConfig.defaultResizerSizeClosed options = spacing_open: spacingOpen From 3a959cccfd6b020710656d7355e4c63076f458b9 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 30 Nov 2017 15:24:48 +0000 Subject: [PATCH 04/27] Use custom chat resizer values. --- services/web/app/views/project/editor.pug | 4 ++-- services/web/public/coffee/ide.coffee | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug index 9d930776a6..a5cb46819f 100644 --- a/services/web/app/views/project/editor.pug +++ b/services/web/app/views/project/editor.pug @@ -43,8 +43,8 @@ block content #chat-wrapper.full-size( layout="chat", - spacing-open="12", - spacing-closed="0", + spacing-open="{{ui.chatResizerSizeOpen}}", + spacing-closed="{{ui.chatResizerSizeClosed}}", initial-size-east="250", init-closed-east="true", open-east="ui.chatOpen", diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee index 728300456c..c934f6297b 100644 --- a/services/web/public/coffee/ide.coffee +++ b/services/web/public/coffee/ide.coffee @@ -76,6 +76,8 @@ define [ pdfWidth: 0 reviewPanelOpen: localStorage("ui.reviewPanelOpen.#{window.project_id}") miniReviewPanelVisible: false + chatResizerSizeOpen: window.uiConfig.chatResizerSizeOpen + chatResizerSizeClosed: window.uiConfig.chatResizerSizeClosed } $scope.onboarding = { autoCompile: if window.showAutoCompileOnboarding then 'unseen' else 'dismissed' From ce3e410250136ee0d75885a9c7be95092c76a3f8 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 30 Nov 2017 17:00:25 +0000 Subject: [PATCH 05/27] Configure resizer colors. --- .../web/public/stylesheets/app/editor.less | 25 ++++++++- .../stylesheets/core/_common-variables.less | 52 ++++++++++--------- .../public/stylesheets/core/ol-variables.less | 2 + 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index e37d829710..3f735e4e5f 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -261,9 +261,9 @@ } } -.ui-layout-resizer { +.ui-layout-resizer when (@is-overleaf = false) { width: 6px; - background-color: #f4f4f4; + background-color: @editor-resizer-bg-color; border-left: 1px solid @editor-border-color; border-right: 1px solid @editor-border-color; .ui-layout-toggler { @@ -282,6 +282,27 @@ } } } + +.ui-layout-resizer when (@is-overleaf = true) { + background-color: @editor-resizer-bg-color; + + // .ui-layout-toggler { + // color: #999; + // font-family: FontAwesome; + // font-style: normal; + // font-weight: normal; + // line-height: 1; + // -webkit-font-smoothing: antialiased; + // -moz-osx-font-smoothing: grayscale; + // font-size: 16px !important; + // line-height: 50px; + // &:hover { + // background-color: #ddd; + // color: #333; + // } + // } +} + .ui-layout-resizer-west.ui-layout-resizer-open, .ui-layout-resizer-east.ui-layout-resizer-closed { .ui-layout-toggler { &:before { diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less index 47030d08ff..11d3e3681c 100644 --- a/services/web/public/stylesheets/core/_common-variables.less +++ b/services/web/public/stylesheets/core/_common-variables.less @@ -889,32 +889,36 @@ @footer-padding : 2em; // Editor header -@toolbar-header-bg-color : transparent; -@toolbar-header-shadow : 0 0 2px #ccc; -@toolbar-btn-color : @link-color; -@toolbar-btn-hover-color : @link-hover-color; -@toolbar-btn-hover-bg-color : darken(white, 10%); -@toolbar-btn-hover-text-shadow : 0 1px 0 rgba(0, 0, 0, 0.15); -@toolbar-btn-active-color : white; -@toolbar-btn-active-bg-color : @link-color; -@toolbar-btn-active-shadow : inset 0 3px 5px rgba(0, 0, 0, 0.225); -@toolbar-alt-bg-color : #fafafa; -@toolbar-icon-btn-color : @gray-light; -@toolbar-icon-btn-hover-color : @gray-dark; -@toolbar-icon-btn-hover-shadow : 0 1px 0 rgba(0, 0, 0, 0.25); -@toolbar-border-bottom : 1px solid @toolbar-border-color; +@toolbar-header-bg-color : transparent; +@toolbar-header-shadow : 0 0 2px #ccc; +@toolbar-btn-color : @link-color; +@toolbar-btn-hover-color : @link-hover-color; +@toolbar-btn-hover-bg-color : darken(white, 10%); +@toolbar-btn-hover-text-shadow : 0 1px 0 rgba(0, 0, 0, 0.15); +@toolbar-btn-active-color : white; +@toolbar-btn-active-bg-color : @link-color; +@toolbar-btn-active-shadow : inset 0 3px 5px rgba(0, 0, 0, 0.225); +@toolbar-alt-bg-color : #fafafa; +@toolbar-icon-btn-color : @gray-light; +@toolbar-icon-btn-hover-color : @gray-dark; +@toolbar-icon-btn-hover-shadow : 0 1px 0 rgba(0, 0, 0, 0.25); +@toolbar-border-bottom : 1px solid @toolbar-border-color; // Editor file-tree -@file-tree-bg : transparent; -@file-tree-item-color : @gray-darker; -@file-tree-item-toggle-color : @gray; -@file-tree-item-icon-color : @gray-light; -@file-tree-item-input-color : inherit; -@file-tree-item-folder-color : lighten(desaturate(@link-color, 10%), 5%); -@file-tree-item-hover-bg : @gray-lightest; -@file-tree-item-selected-bg : transparent; -@file-tree-multiselect-bg : lighten(@brand-info, 40%); -@file-tree-multiselect-hover-bg : lighten(@brand-info, 30%); +@file-tree-bg : transparent; +@file-tree-item-color : @gray-darker; +@file-tree-item-toggle-color : @gray; +@file-tree-item-icon-color : @gray-light; +@file-tree-item-input-color : inherit; +@file-tree-item-folder-color : lighten(desaturate(@link-color, 10%), 5%); +@file-tree-item-hover-bg : @gray-lightest; +@file-tree-item-selected-bg : transparent; +@file-tree-multiselect-bg : lighten(@brand-info, 40%); +@file-tree-multiselect-hover-bg : lighten(@brand-info, 30%); + +// Editor resizers +@editor-resizer-bg-color : #F4F4F4; + // Tags @tag-border-radius : 0.25em; @tag-bg-color : @label-default-bg; diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less index 4911a9d2c4..4cf789c666 100644 --- a/services/web/public/stylesheets/core/ol-variables.less +++ b/services/web/public/stylesheets/core/ol-variables.less @@ -184,6 +184,8 @@ @file-tree-multiselect-bg : @ol-blue; @file-tree-multiselect-hover-bg : @ol-dark-blue; @file-tree-droppable-bg-color : tint(@ol-green, 5%); +// Editor resizers +@editor-resizer-bg-color : @ol-blue-gray-6; //== Colors // //## Gray and brand colors for use across Bootstrap. From cd6b51a1f6474cc915be10e45d495af1f6a659d8 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 30 Nov 2017 17:00:42 +0000 Subject: [PATCH 06/27] Increase v2 resizer hit area. --- services/web/public/stylesheets/app/editor.less | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index 3f735e4e5f..56c620c16d 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -284,7 +284,19 @@ } .ui-layout-resizer when (@is-overleaf = true) { - background-color: @editor-resizer-bg-color; + @extra-hit-area: 8px; + margin-left: -(@extra-hit-area) !important; + margin-right: -(@extra-hit-area - 1px) !important; + padding-left: @extra-hit-area !important; + padding-right: @extra-hit-area !important; + box-sizing: content-box; + background-image: linear-gradient(90deg, + transparent, + transparent (@extra-hit-area - 1px), + @editor-resizer-bg-color (@extra-hit-area - 1px), + @editor-resizer-bg-color (@extra-hit-area + 1px), + transparent (@extra-hit-area + 1px), + transparent); // .ui-layout-toggler { // color: #999; From aeffe1cea9b80937b3d3d6340cf63555c27d08e1 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 1 Dec 2017 11:22:12 +0000 Subject: [PATCH 07/27] Style resizers while dragging. --- .../web/public/stylesheets/app/editor.less | 2 +- .../stylesheets/core/_common-variables.less | 4 +- .../public/stylesheets/core/ol-variables.less | 56 ++++++++++--------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index 56c620c16d..e280f92779 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -330,7 +330,7 @@ } } .ui-layout-resizer-dragging { - background-color: #ddd; + background-color: @editor-resizer-bg-color-dragging; } .context-menu { diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less index 11d3e3681c..c5bd633b15 100644 --- a/services/web/public/stylesheets/core/_common-variables.less +++ b/services/web/public/stylesheets/core/_common-variables.less @@ -917,8 +917,8 @@ @file-tree-multiselect-hover-bg : lighten(@brand-info, 30%); // Editor resizers -@editor-resizer-bg-color : #F4F4F4; - +@editor-resizer-bg-color : #F4F4F4; +@editor-resizer-bg-color-dragging : #ddd; // Tags @tag-border-radius : 0.25em; @tag-bg-color : @label-default-bg; diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less index 4cf789c666..54fdddf309 100644 --- a/services/web/public/stylesheets/core/ol-variables.less +++ b/services/web/public/stylesheets/core/ol-variables.less @@ -157,35 +157,37 @@ @footer-padding : 2em 0; // Editor header -@toolbar-header-bg-color : @ol-blue-gray-6; -@toolbar-header-shadow : none; -@toolbar-btn-color : #FFF; -@toolbar-btn-hover-color : #FFF; -@toolbar-btn-hover-bg-color : @ol-blue-gray-5; -@toolbar-btn-hover-text-shadow : none; -@toolbar-btn-active-color : #FFF; -@toolbar-btn-active-bg-color : @ol-green; -@toolbar-btn-active-shadow : none; -@toolbar-border-color : @ol-blue-gray-5; -@toolbar-alt-bg-color : @ol-blue-gray-5; -@toolbar-icon-btn-color : #FFF; -@toolbar-icon-btn-hover-color : #FFF; -@toolbar-icon-btn-hover-shadow : none; -@toolbar-border-bottom : 1px solid @toolbar-border-color; +@toolbar-header-bg-color : @ol-blue-gray-6; +@toolbar-header-shadow : none; +@toolbar-btn-color : #FFF; +@toolbar-btn-hover-color : #FFF; +@toolbar-btn-hover-bg-color : @ol-blue-gray-5; +@toolbar-btn-hover-text-shadow : none; +@toolbar-btn-active-color : #FFF; +@toolbar-btn-active-bg-color : @ol-green; +@toolbar-btn-active-shadow : none; +@toolbar-border-color : @ol-blue-gray-5; +@toolbar-alt-bg-color : @ol-blue-gray-5; +@toolbar-icon-btn-color : #FFF; +@toolbar-icon-btn-hover-color : #FFF; +@toolbar-icon-btn-hover-shadow : none; +@toolbar-border-bottom : 1px solid @toolbar-border-color; // Editor file-tree -@file-tree-bg : @ol-blue-gray-4; -@file-tree-item-color : #FFF; -@file-tree-item-input-color : @ol-blue-gray-5; -@file-tree-item-toggle-color : @ol-blue-gray-2; -@file-tree-item-icon-color : @ol-blue-gray-2; -@file-tree-item-folder-color : @ol-blue-gray-2; -@file-tree-item-hover-bg : @ol-blue-gray-5; -@file-tree-item-selected-bg : @ol-green; -@file-tree-multiselect-bg : @ol-blue; -@file-tree-multiselect-hover-bg : @ol-dark-blue; -@file-tree-droppable-bg-color : tint(@ol-green, 5%); +@file-tree-bg : @ol-blue-gray-4; +@file-tree-item-color : #FFF; +@file-tree-item-input-color : @ol-blue-gray-5; +@file-tree-item-toggle-color : @ol-blue-gray-2; +@file-tree-item-icon-color : @ol-blue-gray-2; +@file-tree-item-folder-color : @ol-blue-gray-2; +@file-tree-item-hover-bg : @ol-blue-gray-5; +@file-tree-item-selected-bg : @ol-green; +@file-tree-multiselect-bg : @ol-blue; +@file-tree-multiselect-hover-bg : @ol-dark-blue; +@file-tree-droppable-bg-color : tint(@ol-green, 5%); // Editor resizers -@editor-resizer-bg-color : @ol-blue-gray-6; +@editor-resizer-bg-color : @ol-blue-gray-6; +@editor-resizer-bg-color-dragging : transparent; + //== Colors // //## Gray and brand colors for use across Bootstrap. From fd852004fe1055b51480b8a1bb8b5ab8a6922b32 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 4 Dec 2017 12:25:40 +0000 Subject: [PATCH 08/27] Refactor synctex controls z-index handling. --- .../web/app/views/project/editor/editor.pug | 8 ++-- .../coffee/ide/directives/layout.coffee | 4 +- .../web/public/stylesheets/app/editor.less | 1 + .../public/stylesheets/app/editor/pdf.less | 37 ++++++++++++++++++- .../stylesheets/core/_common-variables.less | 2 + .../public/stylesheets/core/ol-variables.less | 3 +- 6 files changed, 46 insertions(+), 9 deletions(-) diff --git a/services/web/app/views/project/editor/editor.pug b/services/web/app/views/project/editor/editor.pug index dc40d7b9ba..787beab01b 100644 --- a/services/web/app/views/project/editor/editor.pug +++ b/services/web/app/views/project/editor/editor.pug @@ -73,21 +73,21 @@ div.full-size( ng-show="!!pdf.url && settings.pdfViewer == 'pdfjs'" ng-controller="PdfSynctexController" ) - a.btn.btn-default.btn-xs( + a.synctex-control-goto-pdf( tooltip=translate('go_to_code_location_in_pdf') tooltip-placement="right" tooltip-append-to-body="true" ng-click="syncToPdf()" ) - i.fa.fa-long-arrow-right + i.synctex-control-icon br - a.btn.btn-default.btn-xs( + a.synctex-control-goto-code( tooltip-html="'"+translate('go_to_pdf_location_in_code', {}, true)+"'" tooltip-placement="right" tooltip-append-to-body="true" ng-click="syncToCode()" ) - i.fa.fa-long-arrow-left + i.synctex-control-icon div.full-size( ng-if="ui.pdfLayout == 'flat'" diff --git a/services/web/public/coffee/ide/directives/layout.coffee b/services/web/public/coffee/ide/directives/layout.coffee index d4e98fb0d3..86058447ed 100644 --- a/services/web/public/coffee/ide/directives/layout.coffee +++ b/services/web/public/coffee/ide/directives/layout.coffee @@ -53,9 +53,7 @@ define [ else controls.show() controls.css({ - position: "absolute" right: state.east.size - "z-index": 3 }) resetOpenStates = () -> @@ -112,7 +110,7 @@ define [ # Set the panel as overflowing (gives it higher z-index and sets overflow rules) layoutObj.allowOverflow overflowPane # Read the given z-index value and increment it, so that it's higher than synctex controls. - overflowPaneZVal = overflowPaneEl.css "z-index" + overflowPaneZVal = overflowPaneEl.zIndex() overflowPaneEl.css "z-index", overflowPaneZVal + 1 resetOpenStates() diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index e280f92779..1c45fd6a1c 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -289,6 +289,7 @@ margin-right: -(@extra-hit-area - 1px) !important; padding-left: @extra-hit-area !important; padding-right: @extra-hit-area !important; + z-index: 5 !important; box-sizing: content-box; background-image: linear-gradient(90deg, transparent, diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index a873cd5e8f..96097df1bf 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -229,14 +229,49 @@ } .synctex-controls { + position: absolute; + z-index: @synctex-controls-z-index; top: 68px; padding: 0px 2px; .btn-xs { line-height: 1.3; - padding: 0 2px 0; + padding: @synctex-controls-btn-padding; } } +.synctex-controls when (@is-overleaf = true) { + margin-right: -9px; +} + .synctex-control-goto-pdf, + .synctex-control-goto-code { + .btn; + .btn-default; + .btn-xs; + + > .synctex-control-icon { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + } + + .synctex-control-goto-pdf > .synctex-control-icon::before when (@is-overleaf = true) { + content: "\f061"; + } + .synctex-control-goto-code > .synctex-control-icon::before when (@is-overleaf = true) { + content: "\f060"; + } + + .synctex-control-goto-pdf > .synctex-control-icon::before when (@is-overleaf = false) { + content: "\f178"; + } + .synctex-control-goto-code > .synctex-control-icon::before when (@is-overleaf = false) { + content: "\f177"; + } + .editor-dark { .pdf-logs { background-color: lighten(@editor-dark-background-color, 10%); diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less index c5bd633b15..958c5a1a10 100644 --- a/services/web/public/stylesheets/core/_common-variables.less +++ b/services/web/public/stylesheets/core/_common-variables.less @@ -919,6 +919,8 @@ // Editor resizers @editor-resizer-bg-color : #F4F4F4; @editor-resizer-bg-color-dragging : #ddd; +@synctex-controls-z-index : 3; +@synctex-controls-btn-padding : 0 2px 0; // Tags @tag-border-radius : 0.25em; @tag-bg-color : @label-default-bg; diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less index 54fdddf309..812cd3e8f9 100644 --- a/services/web/public/stylesheets/core/ol-variables.less +++ b/services/web/public/stylesheets/core/ol-variables.less @@ -187,7 +187,8 @@ // Editor resizers @editor-resizer-bg-color : @ol-blue-gray-6; @editor-resizer-bg-color-dragging : transparent; - +@synctex-controls-z-index : 6; +@synctex-controls-btn-padding : 4px; //== Colors // //## Gray and brand colors for use across Bootstrap. From 0bad7d8549deaf7fe10afd1878a50c8cea4f4733 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 4 Dec 2017 15:12:13 +0000 Subject: [PATCH 09/27] Adjustments to synctex controls. --- .../web/app/views/project/editor/editor.pug | 5 +- .../public/stylesheets/app/editor/pdf.less | 47 ++++++++++++++----- .../stylesheets/core/_common-variables.less | 2 +- .../public/stylesheets/core/ol-variables.less | 2 +- 4 files changed, 38 insertions(+), 18 deletions(-) diff --git a/services/web/app/views/project/editor/editor.pug b/services/web/app/views/project/editor/editor.pug index 787beab01b..6e0e1ecca9 100644 --- a/services/web/app/views/project/editor/editor.pug +++ b/services/web/app/views/project/editor/editor.pug @@ -73,15 +73,14 @@ div.full-size( ng-show="!!pdf.url && settings.pdfViewer == 'pdfjs'" ng-controller="PdfSynctexController" ) - a.synctex-control-goto-pdf( + a.synctex-control.synctex-control-goto-pdf( tooltip=translate('go_to_code_location_in_pdf') tooltip-placement="right" tooltip-append-to-body="true" ng-click="syncToPdf()" ) i.synctex-control-icon - br - a.synctex-control-goto-code( + a.synctex-control.synctex-control-goto-code( tooltip-html="'"+translate('go_to_pdf_location_in_code', {}, true)+"'" tooltip-placement="right" tooltip-append-to-body="true" diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index 96097df1bf..70bf06545c 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -231,19 +231,14 @@ .synctex-controls { position: absolute; z-index: @synctex-controls-z-index; + padding: @synctex-controls-padding; top: 68px; - padding: 0px 2px; - .btn-xs { - line-height: 1.3; - padding: @synctex-controls-btn-padding; - } } .synctex-controls when (@is-overleaf = true) { - margin-right: -9px; + margin-right: -11px; } - .synctex-control-goto-pdf, - .synctex-control-goto-code { + .synctex-control { .btn; .btn-default; .btn-xs; @@ -257,12 +252,38 @@ -moz-osx-font-smoothing: grayscale; } } - - .synctex-control-goto-pdf > .synctex-control-icon::before when (@is-overleaf = true) { - content: "\f061"; + + .synctex-control when (@is-overleaf = true) { + @ol-synctex-control-size: 24px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1em; + width: @ol-synctex-control-size; + height: @ol-synctex-control-size; + border-radius: @ol-synctex-control-size / 2; + padding: 0 0 2px; + background-color: fade(@btn-default-bg, 80%); + transition: background 0.15s ease; + margin-bottom: @ol-synctex-control-size / 2; } - .synctex-control-goto-code > .synctex-control-icon::before when (@is-overleaf = true) { - content: "\f060"; + + .synctex-control when (@is-overleaf = false) { + line-height: 1.3; + padding: 0 2px; + } + + .synctex-control-goto-pdf > .synctex-control-icon when (@is-overleaf = true) { + text-indent: 1px; // "Optical" adjustment. + &::before { + content: "\f061"; + } + } + .synctex-control-goto-code > .synctex-control-icon when (@is-overleaf = true) { + text-indent: -1px; // "Optical" adjustment. + &::before { + content: "\f060"; + } } .synctex-control-goto-pdf > .synctex-control-icon::before when (@is-overleaf = false) { diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less index 958c5a1a10..3689e6c10e 100644 --- a/services/web/public/stylesheets/core/_common-variables.less +++ b/services/web/public/stylesheets/core/_common-variables.less @@ -920,7 +920,7 @@ @editor-resizer-bg-color : #F4F4F4; @editor-resizer-bg-color-dragging : #ddd; @synctex-controls-z-index : 3; -@synctex-controls-btn-padding : 0 2px 0; +@synctex-controls-padding : 0 2px; // Tags @tag-border-radius : 0.25em; @tag-bg-color : @label-default-bg; diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less index 812cd3e8f9..4452e0e956 100644 --- a/services/web/public/stylesheets/core/ol-variables.less +++ b/services/web/public/stylesheets/core/ol-variables.less @@ -188,7 +188,7 @@ @editor-resizer-bg-color : @ol-blue-gray-6; @editor-resizer-bg-color-dragging : transparent; @synctex-controls-z-index : 6; -@synctex-controls-btn-padding : 4px; +@synctex-controls-padding : 0; //== Colors // //## Gray and brand colors for use across Bootstrap. From 963ad4fece710f0963cfba247d246641fb5cd261 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 4 Dec 2017 15:48:23 +0000 Subject: [PATCH 10/27] Style resizer togglers. --- .../web/public/stylesheets/app/editor.less | 38 +++++++++++-------- .../stylesheets/core/_common-variables.less | 2 + .../public/stylesheets/core/ol-variables.less | 2 + 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index 1c45fd6a1c..ccd03aea47 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -276,8 +276,9 @@ -moz-osx-font-smoothing: grayscale; font-size: 16px !important; line-height: 50px; + background-color: @editor-toggler-bg-color; &:hover { - background-color: #ddd; + background-color: @editor-toggler-hover-bg-color; color: #333; } } @@ -299,21 +300,26 @@ transparent (@extra-hit-area + 1px), transparent); - // .ui-layout-toggler { - // color: #999; - // font-family: FontAwesome; - // font-style: normal; - // font-weight: normal; - // line-height: 1; - // -webkit-font-smoothing: antialiased; - // -moz-osx-font-smoothing: grayscale; - // font-size: 16px !important; - // line-height: 50px; - // &:hover { - // background-color: #ddd; - // color: #333; - // } - // } + .ui-layout-toggler { + padding: 0 @extra-hit-area !important; + background-image: linear-gradient(90deg, + transparent, + transparent (@extra-hit-area - 1px), + @editor-toggler-bg-color (@extra-hit-area - 1px), + @editor-toggler-bg-color (@extra-hit-area + 1px), + transparent (@extra-hit-area + 1px), + transparent); + + &:hover { + background-image: linear-gradient(90deg, + transparent, + transparent (@extra-hit-area - 2px), + @editor-toggler-hover-bg-color (@extra-hit-area - 2px), + @editor-toggler-hover-bg-color (@extra-hit-area + 2px), + transparent (@extra-hit-area + 2px), + transparent); + } + } } .ui-layout-resizer-west.ui-layout-resizer-open, .ui-layout-resizer-east.ui-layout-resizer-closed { diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less index 3689e6c10e..d4f5a168ec 100644 --- a/services/web/public/stylesheets/core/_common-variables.less +++ b/services/web/public/stylesheets/core/_common-variables.less @@ -919,6 +919,8 @@ // Editor resizers @editor-resizer-bg-color : #F4F4F4; @editor-resizer-bg-color-dragging : #ddd; +@editor-toggler-bg-color : transparent; +@editor-toggler-hover-bg-color : #DDD; @synctex-controls-z-index : 3; @synctex-controls-padding : 0 2px; // Tags diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less index 4452e0e956..e86b9de8a7 100644 --- a/services/web/public/stylesheets/core/ol-variables.less +++ b/services/web/public/stylesheets/core/ol-variables.less @@ -187,6 +187,8 @@ // Editor resizers @editor-resizer-bg-color : @ol-blue-gray-6; @editor-resizer-bg-color-dragging : transparent; +@editor-toggler-bg-color : @ol-blue-gray-2; +@editor-toggler-hover-bg-color : @ol-green; @synctex-controls-z-index : 6; @synctex-controls-padding : 0; //== Colors From 7498ead1f5e8b2c9b9cbd75a5336fa16d51a7c87 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 4 Dec 2017 15:58:40 +0000 Subject: [PATCH 11/27] Make sure SL synctex controls remain the same. --- services/web/public/stylesheets/app/editor/pdf.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index 70bf06545c..b8ddfc5efd 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -242,6 +242,8 @@ .btn; .btn-default; .btn-xs; + display: block; + margin-bottom: 3px; > .synctex-control-icon { display: inline-block; From 9bf5d1e14c70eac32e594587eddff2db56443fe8 Mon Sep 17 00:00:00 2001 From: Nate Stemen Date: Tue, 5 Dec 2017 13:57:36 -0500 Subject: [PATCH 12/27] removing labels service --- .../Features/Labels/LabelsController.coffee | 28 --------- .../Features/Labels/LabelsHandler.coffee | 43 -------------- services/web/app/coffee/router.coffee | 4 -- .../ide/editor/directives/aceEditor.coffee | 9 +-- .../auto-complete/AutoCompleteManager.coffee | 2 +- .../aceEditor/labels/LabelsManager.coffee | 58 ------------------- .../coffee/ide/labels/LabelsManager.coffee | 13 ----- .../coffee/ide/labels/services/labels.coffee | 44 -------------- 8 files changed, 4 insertions(+), 197 deletions(-) delete mode 100644 services/web/app/coffee/Features/Labels/LabelsController.coffee delete mode 100644 services/web/app/coffee/Features/Labels/LabelsHandler.coffee delete mode 100644 services/web/public/coffee/ide/editor/directives/aceEditor/labels/LabelsManager.coffee delete mode 100644 services/web/public/coffee/ide/labels/LabelsManager.coffee delete mode 100644 services/web/public/coffee/ide/labels/services/labels.coffee diff --git a/services/web/app/coffee/Features/Labels/LabelsController.coffee b/services/web/app/coffee/Features/Labels/LabelsController.coffee deleted file mode 100644 index 7608a40021..0000000000 --- a/services/web/app/coffee/Features/Labels/LabelsController.coffee +++ /dev/null @@ -1,28 +0,0 @@ -EditorRealTimeController = require "../Editor/EditorRealTimeController" -LabelsHandler = require './LabelsHandler' -logger = require 'logger-sharelatex' - - -module.exports = LabelsController = - - getAllLabels: (req, res, next) -> - project_id = req.params.project_id - logger.log {project_id}, "getting all labels for project" - LabelsHandler.getAllLabelsForProject project_id, (err, projectLabels) -> - if err? - logger.err {project_id, err}, "[LabelsController] error getting all labels from project" - return next(err) - res.json {projectId: project_id, projectLabels: projectLabels} - - broadcastLabelsForDoc: (req, res, next) -> - project_id = req.params.project_id - doc_id = req.params.doc_id - logger.log {project_id, doc_id}, "getting labels for doc" - LabelsHandler.getLabelsForDoc project_id, doc_id, (err, docLabels) -> - if err? - logger.err {project_id, doc_id, err}, "[LabelsController] error getting labels from doc" - return next(err) - EditorRealTimeController.emitToRoom project_id, 'broadcastDocLabels', { - docId: doc_id, labels: docLabels - } - res.sendStatus(200) diff --git a/services/web/app/coffee/Features/Labels/LabelsHandler.coffee b/services/web/app/coffee/Features/Labels/LabelsHandler.coffee deleted file mode 100644 index 1d4cc013d5..0000000000 --- a/services/web/app/coffee/Features/Labels/LabelsHandler.coffee +++ /dev/null @@ -1,43 +0,0 @@ -ProjectEntityHandler = require "../Project/ProjectEntityHandler" -DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler') - - -module.exports = LabelsHandler = - - labelCaptureRegex: () -> - /\\label\{([^\}\n\\]{0,80})\}/g - - getAllLabelsForProject: (projectId, callback=(err, projectLabels)->) -> - DocumentUpdaterHandler.flushProjectToMongo projectId, (err) -> - if err? - return callback(err) - ProjectEntityHandler.getAllDocs projectId, (err, docs) -> - if err? - return callback(err) - projectLabels = LabelsHandler.extractLabelsFromProjectDocs docs - callback(null, projectLabels) - - getLabelsForDoc: (projectId, docId, callback=(err, docLabels)->) -> - DocumentUpdaterHandler.flushDocToMongo projectId, docId, (err) -> - if err? - return callback(err) - ProjectEntityHandler.getDoc projectId, docId, (err, lines) -> - if err? - return callback(err) - docLabels = LabelsHandler.extractLabelsFromDoc lines - callback(null, docLabels) - - extractLabelsFromDoc: (lines) -> - docLabels = [] - for line in lines - re = LabelsHandler.labelCaptureRegex() - while (labelMatch = re.exec(line)) - if labelMatch[1] - docLabels.push(labelMatch[1]) - return docLabels - - extractLabelsFromProjectDocs: (projectDocs) -> - projectLabels = {} # docId => List[Label] - for _path, doc of projectDocs - projectLabels[doc._id] = LabelsHandler.extractLabelsFromDoc(doc.lines) - return projectLabels diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 3e3aa1af7a..f8ec65ac53 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -44,7 +44,6 @@ SudoModeMiddlewear = require('./Features/SudoMode/SudoModeMiddlewear') AnalyticsRouter = require('./Features/Analytics/AnalyticsRouter') AnnouncementsController = require("./Features/Announcements/AnnouncementsController") MetaController = require('./Features/Metadata/MetaController') -LabelsController = require('./Features/Labels/LabelsController') TokenAccessController = require('./Features/TokenAccess/TokenAccessController') Features = require('./infrastructure/Features') @@ -206,8 +205,6 @@ module.exports = class Router webRouter.get '/project/:project_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetaController.getMetadata webRouter.post '/project/:project_id/doc/:doc_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetaController.broadcastMetadataForDoc - webRouter.get '/project/:project_id/labels', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), LabelsController.getAllLabels - webRouter.post '/project/:project_id/doc/:doc_id/labels', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), LabelsController.broadcastLabelsForDoc webRouter.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags webRouter.post '/tag', AuthenticationController.requireLogin(), TagsController.createTag @@ -360,4 +357,3 @@ module.exports = class Router TokenAccessController.readAndWriteToken webRouter.get '*', ErrorController.notFound - diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index 55eb2cb179..6908b25bce 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -10,13 +10,11 @@ define [ "ide/editor/directives/aceEditor/cursor-position/CursorPositionManager" "ide/editor/directives/aceEditor/track-changes/TrackChangesManager" "ide/editor/directives/aceEditor/metadata/MetadataManager" - "ide/editor/directives/aceEditor/labels/LabelsManager" - "ide/labels/services/labels" "ide/metadata/services/metadata" "ide/graphics/services/graphics" "ide/preamble/services/preamble" "ide/files/services/files" -], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager, LabelsManager) -> +], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager) -> EditSession = ace.require('ace/edit_session').EditSession ModeList = ace.require('ace/ext/modelist') @@ -38,7 +36,7 @@ define [ url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}" return url - App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, metadata, graphics, preamble, files, $http, $q) -> + App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, metadata, graphics, preamble, files, $http, $q) -> monkeyPatchSearch($rootScope, $compile) return { @@ -105,9 +103,8 @@ define [ highlightsManager = new HighlightsManager(scope, editor, element) cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage) trackChangesManager = new TrackChangesManager(scope, editor, element) - labelsManager = new LabelsManager(scope, editor, element, labels) metadataManager = new MetadataManager(scope, editor, element, metadata) - autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, labelsManager, graphics, preamble, files) + autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, graphics, preamble, files) # 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 542b8ae69f..15a8b51b82 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 @@ -10,7 +10,7 @@ define [ aceSnippetManager = ace.require('ace/snippets').snippetManager class AutoCompleteManager - constructor: (@$scope, @editor, @element, @metadataManager, @labelsManager, @graphics, @preamble, @files) -> + constructor: (@$scope, @editor, @element, @metadataManager, @graphics, @preamble, @files) -> @monkeyPatchAutocomplete() diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/labels/LabelsManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/labels/LabelsManager.coffee deleted file mode 100644 index a5d2c10625..0000000000 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/labels/LabelsManager.coffee +++ /dev/null @@ -1,58 +0,0 @@ -define [ - "ace/ace" -], () -> - Range = ace.require("ace/range").Range - - getLastCommandFragment = (lineUpToCursor) -> - if m = lineUpToCursor.match(/(\\[^\\]+)$/) - return m[1] - else - return null - - class LabelsManager - constructor: (@$scope, @editor, @element, @Labels) -> - @debouncer = {} # DocId => Timeout - - onChange = (change) => - if change.remote - return - if change.action not in ['remove', 'insert'] - return - cursorPosition = @editor.getCursorPosition() - end = change.end - range = new Range(end.row, 0, end.row, end.column) - lineUpToCursor = @editor.getSession().getTextRange(range) - commandFragment = getLastCommandFragment(lineUpToCursor) - linesContainLabel = _.any(change.lines, (line) -> line.match(/\\label\{[^\}\n\\]{0,80}\}/)) - lastCommandFragmentIsLabel = commandFragment?.slice(0,7) == '\\label{' - if linesContainLabel or lastCommandFragmentIsLabel - @scheduleLoadCurrentDocLabelsFromServer() - - @editor.on "changeSession", (e) => - e.oldSession.off "change", onChange - e.session.on "change", onChange - - loadCurrentDocLabelsFromServer: () -> - currentDocId = @$scope.docId - @Labels.loadDocLabelsFromServer(currentDocId) - - loadDocLabelsFromServer: (docId) -> - @Labels.loadDocLabelsFromServer(docId) - - scheduleLoadCurrentDocLabelsFromServer: () -> - # De-bounce loading labels with a timeout - currentDocId = @$scope.docId - existingTimeout = @debouncer[currentDocId] - if existingTimeout? - clearTimeout(existingTimeout) - delete @debouncer[currentDocId] - @debouncer[currentDocId] = setTimeout( - () => - @loadDocLabelsFromServer(currentDocId) - delete @debouncer[currentDocId] - , 1000 - , this - ) - - getAllLabels: () -> - @Labels.getAllLabels() diff --git a/services/web/public/coffee/ide/labels/LabelsManager.coffee b/services/web/public/coffee/ide/labels/LabelsManager.coffee deleted file mode 100644 index d5e7e2892e..0000000000 --- a/services/web/public/coffee/ide/labels/LabelsManager.coffee +++ /dev/null @@ -1,13 +0,0 @@ -define [], () -> - - class LabelsManager - - constructor: (@ide, @$scope, @labels) -> - - @ide.socket.on 'broadcastDocLabels', (data) => - @labels.onBroadcastDocLabels(data) - @$scope.$on 'entity:deleted', @labels.onEntityDeleted - @$scope.$on 'file:upload:complete', @labels.fileUploadComplete - - loadProjectLabelsFromServer: () -> - @labels.loadProjectLabelsFromServer() diff --git a/services/web/public/coffee/ide/labels/services/labels.coffee b/services/web/public/coffee/ide/labels/services/labels.coffee deleted file mode 100644 index 313d48175f..0000000000 --- a/services/web/public/coffee/ide/labels/services/labels.coffee +++ /dev/null @@ -1,44 +0,0 @@ -define [ - "base" -], (App) -> - - App.factory 'labels', ($http, ide) -> - - state = {documents: {}} - - labels = { - state: state - } - - labels.onBroadcastDocLabels = (data) -> - if data.docId and data.labels - state.documents[data.docId] = data.labels - - labels.onEntityDeleted = (e, entity) -> - if entity.type == 'doc' - delete state.documents[entity.id] - - labels.onFileUploadComplete = (e, upload) -> - if upload.entity_type == 'doc' - labels.loadDocLabelsFromServer(upload.entity_id) - - labels.getAllLabels = () -> - _.flatten(labels for docId, labels of state.documents) - - labels.loadProjectLabelsFromServer = () -> - $http - .get("/project/#{window.project_id}/labels") - .then (response) -> - { data } = response - if data.projectLabels - for docId, docLabels of data.projectLabels - state.documents[docId] = docLabels - - labels.loadDocLabelsFromServer = (docId) -> - $http - .post( - "/project/#{window.project_id}/doc/#{docId}/labels", - {_csrf: window.csrfToken} - ) - - return labels From 09dcbcf3b78fbb68b7a39bc41b4f7ecacbe86a7c Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 7 Dec 2017 10:35:43 +0000 Subject: [PATCH 13/27] Fix missing variables. --- services/web/public/stylesheets/core/_common-variables.less | 3 ++- services/web/public/stylesheets/core/ol-variables.less | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less index 84a4f552d2..ab3ef2ccc9 100644 --- a/services/web/public/stylesheets/core/_common-variables.less +++ b/services/web/public/stylesheets/core/_common-variables.less @@ -902,10 +902,12 @@ @toolbar-icon-btn-color : @gray-light; @toolbar-icon-btn-hover-color : @gray-dark; @toolbar-icon-btn-hover-shadow : 0 1px 0 rgba(0, 0, 0, 0.25); +@toolbar-icon-btn-hover-boxshadow : inset 0 3px 5px rgba(0, 0, 0, 0.225); @toolbar-border-bottom : 1px solid @toolbar-border-color; // Editor file-tree @file-tree-bg : transparent; +@file-tree-line-height : 2.6; @file-tree-item-color : @gray-darker; @file-tree-item-toggle-color : @gray; @file-tree-item-icon-color : @gray-light; @@ -923,7 +925,6 @@ @editor-toggler-hover-bg-color : #DDD; @synctex-controls-z-index : 3; @synctex-controls-padding : 0 2px; - // Tags @tag-border-radius : 0.25em; @tag-bg-color : @label-default-bg; diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less index e86b9de8a7..004bc343d8 100644 --- a/services/web/public/stylesheets/core/ol-variables.less +++ b/services/web/public/stylesheets/core/ol-variables.less @@ -172,8 +172,10 @@ @toolbar-icon-btn-hover-color : #FFF; @toolbar-icon-btn-hover-shadow : none; @toolbar-border-bottom : 1px solid @toolbar-border-color; +@toolbar-icon-btn-hover-boxshadow : none; // Editor file-tree @file-tree-bg : @ol-blue-gray-4; +@file-tree-line-height : 2.05; @file-tree-item-color : #FFF; @file-tree-item-input-color : @ol-blue-gray-5; @file-tree-item-toggle-color : @ol-blue-gray-2; @@ -191,8 +193,6 @@ @editor-toggler-hover-bg-color : @ol-green; @synctex-controls-z-index : 6; @synctex-controls-padding : 0; -//== Colors -// //## Gray and brand colors for use across Bootstrap. @gray-darker: #252525; @gray-dark: #505050; From 9575e802915553cf16dd130e4fb386ab896d11b6 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 8 Dec 2017 10:31:31 +0000 Subject: [PATCH 14/27] Pseudo elements in togglers where causing artifacts in v2; only allow those in SL. --- services/web/public/stylesheets/app/editor.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index ccd03aea47..fb8e825cb3 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -323,14 +323,14 @@ } .ui-layout-resizer-west.ui-layout-resizer-open, .ui-layout-resizer-east.ui-layout-resizer-closed { - .ui-layout-toggler { + .ui-layout-toggler when (@is-overleaf = false) { &:before { content: "\f104" } } } .ui-layout-resizer-east.ui-layout-resizer-open, .ui-layout-resizer-west.ui-layout-resizer-closed { - .ui-layout-toggler { + .ui-layout-toggler when (@is-overleaf = false) { &:before { content: "\f105" } From 97129b5dda1a0eae9262d4fd355261799ae0e73c Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 8 Dec 2017 15:29:05 +0000 Subject: [PATCH 15/27] Make the togglers more obvious when panes are closed. --- .../web/public/stylesheets/app/editor.less | 70 ++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index fb8e825cb3..5257e9c519 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -14,6 +14,9 @@ @import "./editor/review-panel.less"; @import "./editor/feature-onboarding.less"; +@ui-layout-toggler-def-height: 50px; +@ui-resizer-extra-hit-area: 8px; + @keyframes blink { 0% { opacity: 0.2; @@ -285,38 +288,37 @@ } .ui-layout-resizer when (@is-overleaf = true) { - @extra-hit-area: 8px; - margin-left: -(@extra-hit-area) !important; - margin-right: -(@extra-hit-area - 1px) !important; - padding-left: @extra-hit-area !important; - padding-right: @extra-hit-area !important; + margin-left: -(@ui-resizer-extra-hit-area) !important; + margin-right: -(@ui-resizer-extra-hit-area - 1px) !important; + padding-left: @ui-resizer-extra-hit-area !important; + padding-right: @ui-resizer-extra-hit-area !important; z-index: 5 !important; box-sizing: content-box; background-image: linear-gradient(90deg, transparent, - transparent (@extra-hit-area - 1px), - @editor-resizer-bg-color (@extra-hit-area - 1px), - @editor-resizer-bg-color (@extra-hit-area + 1px), - transparent (@extra-hit-area + 1px), + transparent (@ui-resizer-extra-hit-area - 1px), + @editor-resizer-bg-color (@ui-resizer-extra-hit-area - 1px), + @editor-resizer-bg-color (@ui-resizer-extra-hit-area + 1px), + transparent (@ui-resizer-extra-hit-area + 1px), transparent); .ui-layout-toggler { - padding: 0 @extra-hit-area !important; + padding: 0 @ui-resizer-extra-hit-area !important; background-image: linear-gradient(90deg, transparent, - transparent (@extra-hit-area - 1px), - @editor-toggler-bg-color (@extra-hit-area - 1px), - @editor-toggler-bg-color (@extra-hit-area + 1px), - transparent (@extra-hit-area + 1px), + transparent (@ui-resizer-extra-hit-area - 1px), + @editor-toggler-bg-color (@ui-resizer-extra-hit-area - 1px), + @editor-toggler-bg-color (@ui-resizer-extra-hit-area + 1px), + transparent (@ui-resizer-extra-hit-area + 1px), transparent); &:hover { background-image: linear-gradient(90deg, transparent, - transparent (@extra-hit-area - 2px), - @editor-toggler-hover-bg-color (@extra-hit-area - 2px), - @editor-toggler-hover-bg-color (@extra-hit-area + 2px), - transparent (@extra-hit-area + 2px), + transparent (@ui-resizer-extra-hit-area - 2px), + @editor-toggler-hover-bg-color (@ui-resizer-extra-hit-area - 2px), + @editor-toggler-hover-bg-color (@ui-resizer-extra-hit-area + 2px), + transparent (@ui-resizer-extra-hit-area + 2px), transparent); } } @@ -336,6 +338,38 @@ } } } + +.ui-layout-toggler.ui-layout-toggler-closed when (@is-overleaf = true) { + background-color: @editor-resizer-bg-color; + background-image: none; + line-height: @ui-layout-toggler-def-height; + + &::before { + content: "\22EE"; // Vertical ellipsis + display: block; + color: #FFF; + font-weight: 700; + font-size: @font-size-h2; + width: @ui-resizer-extra-hit-area / 2; + } + &:hover { + background-color: @editor-toggler-hover-bg-color; + background-image: none; + } + .ui-layout-resizer-west > & { + border-radius: 0 @border-radius-base @border-radius-base 0; + &::before { + margin-left: -2px; + } + } + .ui-layout-resizer-east > & { + border-radius: @border-radius-base 0 0 @border-radius-base; + &::before { + margin-left: (-1 - @ui-resizer-extra-hit-area); + } + } +} + .ui-layout-resizer-dragging { background-color: @editor-resizer-bg-color-dragging; } From bd7e2d956d9602683d14b170f9b2453a961ad39f Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Mon, 11 Dec 2017 11:05:04 +0000 Subject: [PATCH 16/27] add mandrill as email option --- .../coffee/Features/Email/EmailSender.coffee | 11 +- services/web/npm-shrinkwrap.json | 807 ++++++++---------- services/web/package.json | 1 + 3 files changed, 366 insertions(+), 453 deletions(-) diff --git a/services/web/app/coffee/Features/Email/EmailSender.coffee b/services/web/app/coffee/Features/Email/EmailSender.coffee index f65f04ebdf..31237eb804 100644 --- a/services/web/app/coffee/Features/Email/EmailSender.coffee +++ b/services/web/app/coffee/Features/Email/EmailSender.coffee @@ -4,6 +4,7 @@ Settings = require('settings-sharelatex') nodemailer = require("nodemailer") sesTransport = require('nodemailer-ses-transport') sgTransport = require('nodemailer-sendgrid-transport') +mandrillTransport = require('nodemailer-mandrill-transport') rateLimiter = require('../../infrastructure/RateLimiter') _ = require("underscore") @@ -17,22 +18,22 @@ client = sendMail: (options, callback = (err,status) ->) -> logger.log options:options, "Would send email if enabled." callback() - if Settings?.email?.parameters?.AWSAccessKeyID? or Settings?.email?.driver == 'ses' logger.log "using aws ses for email" nm_client = nodemailer.createTransport(sesTransport(Settings.email.parameters)) else if Settings?.email?.parameters?.sendgridApiKey? logger.log "using sendgrid for email" nm_client = nodemailer.createTransport(sgTransport({auth:{api_key:Settings?.email?.parameters?.sendgridApiKey}})) +else if Settings?.email.parameters.MandrillApiKey? + logger.log "using mandril for email" + nm_client = nodemailer.createTransport(mandrillTransport({auth:{apiKey:Settings?.email.parameters.MandrillApiKey}})) else if Settings?.email?.parameters? - smtp = _.pick(Settings?.email?.parameters, "host", "port", "secure", "auth", "ignoreTLS") - - logger.log "using smtp for email" + smtp = _.pick(Settings?.email?.parameters, "host", "port", "secure", "auth", "ignoreTLS") nm_client = nodemailer.createTransport(smtp) else - nm_client = client logger.warn "Email transport and/or parameters not defined. No emails will be sent." + nm_client = client if nm_client? client = nm_client diff --git a/services/web/npm-shrinkwrap.json b/services/web/npm-shrinkwrap.json index 6ece76837e..f72adf81e5 100644 --- a/services/web/npm-shrinkwrap.json +++ b/services/web/npm-shrinkwrap.json @@ -173,6 +173,12 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.0.tgz", "integrity": "sha1-rD76xxew57vcd4zgvec4GsZgQ5M=" }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -565,7 +571,6 @@ "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.1.tgz", "integrity": "sha1-Agw4O+1iWvXGyINN2MSsoN0Pdlw=", "requires": { - "dtrace-provider": "0.2.8", "mv": "0.0.5" } }, @@ -754,6 +759,21 @@ "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.4.tgz", "integrity": "sha1-/hvO2X/h+zknuZjytFYW4GWL4f8=" }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "colors": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", @@ -777,16 +797,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "requires": { - "inherits": "2.0.3", - "readable-stream": "1.0.34", - "typedarray": "0.0.6" - } - }, "connect": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.2.tgz", @@ -1166,6 +1176,11 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, + "denque": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.2.2.tgz", + "integrity": "sha512-x92Ql74lcTbGylXILO9Xf9S0cMpEPP04zVp2bB9e2C7G/n/Q1SgLl78RaSYEPSgpDX9uLgQXCEGAS5BI5dP3yA==" + }, "depd": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", @@ -1277,12 +1292,6 @@ "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" }, - "dtrace-provider": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.2.8.tgz", - "integrity": "sha1-4kPxkhmqlfvw2PL/sH9b1k6U/iA=", - "optional": true - }, "each-series": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/each-series/-/each-series-1.0.0.tgz", @@ -1552,27 +1561,6 @@ "resolved": "https://registry.npmjs.org/extendible/-/extendible-0.1.1.tgz", "integrity": "sha1-4qN+2HEp+0+VM+io11BiMKU5yQU=" }, - "extract-zip": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.5.tgz", - "integrity": "sha1-maBnNbbqIOqbcF13ms/8yHz/BEA=", - "requires": { - "concat-stream": "1.6.0", - "debug": "1.0.5", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" - }, - "dependencies": { - "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", - "requires": { - "fd-slicer": "1.0.1" - } - } - } - }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -1834,7 +1822,7 @@ "requires": { "graceful-fs": "4.1.11", "inherits": "2.0.3", - "mkdirp": "0.5.1", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "rimraf": "2.2.6" } }, @@ -2647,7 +2635,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -2662,7 +2650,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -2762,32 +2750,42 @@ "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" }, "ioredis": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-2.5.0.tgz", - "integrity": "sha1-+2/fChp+CXRhTGe25eETCKjPlbk=", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-3.2.2.tgz", + "integrity": "sha512-g+ShTQYLsCcOUkNOK6CCEZbj3aRDVPw3WOwXk+LxlUKvuS9ujEqP2MppBHyRVYrNNFW/vcPaTBUZ2ctGNSiOCA==", "requires": { "bluebird": "3.5.0", "cluster-key-slot": "1.0.8", - "debug": "2.6.8", - "double-ended-queue": "2.1.0-0", + "debug": "2.6.9", + "denque": "1.2.2", "flexbuffer": "0.0.6", - "lodash": "4.17.4", + "lodash.assign": "4.2.0", + "lodash.bind": "4.2.1", + "lodash.clone": "4.5.0", + "lodash.clonedeep": "4.5.0", + "lodash.defaults": "4.2.0", + "lodash.difference": "4.5.0", + "lodash.flatten": "4.4.0", + "lodash.foreach": "4.5.0", + "lodash.isempty": "4.4.0", + "lodash.keys": "4.2.0", + "lodash.noop": "3.0.1", + "lodash.partial": "4.2.1", + "lodash.pick": "4.4.0", + "lodash.sample": "4.2.1", + "lodash.shuffle": "4.2.0", + "lodash.values": "4.3.0", "redis-commands": "1.3.1", - "redis-parser": "1.3.0" + "redis-parser": "2.6.0" }, "dependencies": { "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { "ms": "2.0.0" } - }, - "redis-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-1.3.0.tgz", - "integrity": "sha1-gG6+e7+3005NfB6e8oLvz60EEmo=" } } }, @@ -3020,9 +3018,9 @@ } }, "just-extend": { - "version": "1.1.22", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.22.tgz", - "integrity": "sha1-MzCvdWyralQnAMZLLk5KoGLVL/8=" + "version": "1.1.27", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", + "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==" }, "jwa": { "version": "1.1.5", @@ -3202,7 +3200,7 @@ "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", "optional": true, "requires": { - "mkdirp": "0.5.1", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "ncp": "2.0.0", "rimraf": "2.4.5" } @@ -3252,7 +3250,6 @@ "asn1": "0.2.1", "assert-plus": "0.1.5", "bunyan": "0.22.1", - "dtrace-provider": "0.2.8", "nopt": "2.1.1", "pooling": "0.4.6" }, @@ -3372,16 +3369,78 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + }, + "lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" + }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", + "dev": true + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" }, + "lodash.isarray": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-4.0.0.tgz", + "integrity": "sha1-KspJayjEym1yZxUxNZDALm6jRAM=", + "dev": true + }, "lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" }, + "lodash.isempty": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=" + }, "lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", @@ -3402,18 +3461,59 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, + "lodash.keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.2.0.tgz", + "integrity": "sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU=" + }, + "lodash.mergewith": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz", + "integrity": "sha1-FQzwoWeR9ZA7iJHqsVRgknS96lU=", + "dev": true + }, + "lodash.noop": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-3.0.1.tgz", + "integrity": "sha1-OBiPTWUKOkdCWEObluxFsyYXEzw=" + }, "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, + "lodash.partial": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.partial/-/lodash.partial-4.2.1.tgz", + "integrity": "sha1-SfPYz9qjv/izqR0SfpIyRUGJYdQ=" + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + }, "lodash.reduce": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" }, + "lodash.sample": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.sample/-/lodash.sample-4.2.1.tgz", + "integrity": "sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20=" + }, + "lodash.shuffle": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz", + "integrity": "sha1-FFtQU8+HX29cKjP0i26ZSMbse0s=" + }, + "lodash.values": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz", + "integrity": "sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=" + }, "logger-sharelatex": { - "version": "git+https://github.com/sharelatex/logger-sharelatex.git#b2956ec56b582b9f4fc8fdda8dc00c06e77c5537", + "version": "git+https://github.com/sharelatex/logger-sharelatex.git#13562f8866708fc86aef8202bf5a2ce4d1c6eed7", "requires": { "bunyan": "1.5.1", "chai": "4.1.2", @@ -3426,8 +3526,8 @@ "grunt-mocha-test": "0.11.0", "raven": "1.2.1", "sandboxed-module": "2.0.3", - "sinon": "3.2.1", - "timekeeper": "1.0.0" + "sinon": "4.1.3", + "timekeeper": "2.0.0" }, "dependencies": { "ansi-regex": { @@ -3460,7 +3560,7 @@ "deep-eql": "3.0.1", "get-func-name": "2.0.0", "pathval": "1.1.0", - "type-detect": "4.0.3" + "type-detect": "4.0.5" } }, "chalk": { @@ -3483,9 +3583,9 @@ "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "requires": { - "type-detect": "4.0.3" + "type-detect": "4.0.5" } }, "dtrace-provider": { @@ -3502,7 +3602,25 @@ "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", "requires": { - "samsam": "1.2.1" + "samsam": "1.1.2" + } + }, + "fs-extra": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.9.1.tgz", + "integrity": "sha1-h9v8ATg6jdzn2dVJbzYIVkiJ8VY=", + "requires": { + "jsonfile": "1.1.1", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "ncp": "0.5.1", + "rimraf": "2.4.5" + }, + "dependencies": { + "ncp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz", + "integrity": "sha1-dDmFMW49tFkoG1hxaehFc1oFQ58=" + } } }, "glob": { @@ -3570,24 +3688,6 @@ "fs-extra": "0.9.1", "hooker": "0.2.3", "mocha": "1.20.1" - }, - "dependencies": { - "fs-extra": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.9.1.tgz", - "integrity": "sha1-h9v8ATg6jdzn2dVJbzYIVkiJ8VY=", - "requires": { - "jsonfile": "1.1.1", - "mkdirp": "0.5.1", - "ncp": "0.5.1", - "rimraf": "2.4.5" - } - }, - "ncp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz", - "integrity": "sha1-dDmFMW49tFkoG1hxaehFc1oFQ58=" - } } }, "has-ansi": { @@ -3598,6 +3698,11 @@ "ansi-regex": "0.2.1" } }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + }, "jade": { "version": "0.26.3", "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", @@ -3630,14 +3735,14 @@ "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" }, "lolex": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.1.2.tgz", - "integrity": "sha1-JpS5U8nqTQE+W4v7qJHJkQJbJik=" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.1.tgz", + "integrity": "sha512-mQuW55GhduF3ppo+ZRUTz1PRjEh1hS5BbqU7d8D0ez2OKxHDod7StPPeAVKisZR5aLkHZjdGWSL42LSONUJsZw==" }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "1.1.8" } @@ -3688,19 +3793,11 @@ "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", "optional": true, "requires": { - "mkdirp": "0.5.1", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "ncp": "2.0.0", "rimraf": "2.4.5" } }, - "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", - "requires": { - "isarray": "0.0.1" - } - }, "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", @@ -3709,11 +3806,6 @@ "glob": "6.0.4" } }, - "samsam": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.2.1.tgz", - "integrity": "sha1-7dOQk6MYQ3DLhZJDsr3yVefY6mc=" - }, "sandboxed-module": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-2.0.3.tgz", @@ -3724,25 +3816,31 @@ } }, "sinon": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-3.2.1.tgz", - "integrity": "sha1-2K2r2QBzD9SXeIoCcEnGSwi+kcI=", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.1.3.tgz", + "integrity": "sha512-c7u0ZuvBRX1eXuB4jN3BRCAOGiUTlM8SE3TxbJHrNiHUKL7wonujMOB6Fi1gQc00U91IscFORQHDga/eccqpbw==", "requires": { - "diff": "3.3.1", + "diff": "3.4.0", "formatio": "1.2.0", - "lolex": "2.1.2", - "native-promise-only": "0.8.1", - "nise": "1.0.1", - "path-to-regexp": "1.7.0", - "samsam": "1.2.1", - "text-encoding": "0.6.4", - "type-detect": "4.0.3" + "lodash.get": "4.4.2", + "lolex": "2.3.1", + "nise": "1.2.0", + "supports-color": "4.5.0", + "type-detect": "4.0.5" }, "dependencies": { "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha1-qoVnpu7QPFMfyJ0/cRzQ5SWd7HU=" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", + "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==" + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "requires": { + "has-flag": "2.0.0" + } } } }, @@ -3759,15 +3857,10 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=" }, - "timekeeper": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-1.0.0.tgz", - "integrity": "sha1-Lziu4elLEd1m2FgP8aqdzGoroNg=" - }, "type-detect": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", - "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=" + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.5.tgz", + "integrity": "sha512-N9IvkQslUGYGC24RkJk1ba99foK6TkwC2FHAEBlQFBP0RxQZS8ZpJuAZcwiY/w9ZJHFQb1aOXBI60OdxhTrwEQ==" } } }, @@ -3816,6 +3909,11 @@ "libmime": "2.0.0" } }, + "mandrill-api": { + "version": "1.0.45", + "resolved": "https://registry.npmjs.org/mandrill-api/-/mandrill-api-1.0.45.tgz", + "integrity": "sha1-Fjk5z0hr0YJ3sPO69BLD5l2Epy0=" + }, "marked": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz", @@ -3929,8 +4027,7 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -4309,11 +4406,6 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.3.5.tgz", "integrity": "sha1-gioNwmYpDOTNOhIoLKPn42Rmigg=" }, - "native-promise-only": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=" - }, "nconf": { "version": "0.6.9", "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.6.9.tgz", @@ -4355,14 +4447,15 @@ "integrity": "sha1-Jp1cR2gQ7JLtvntsLygxY4T5p+g=" }, "nise": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.0.1.tgz", - "integrity": "sha1-DakrEKhU6XwPSW9sKEWjASgLPu8=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.2.0.tgz", + "integrity": "sha512-q9jXh3UNsMV28KeqI43ILz5+c3l+RiNW8mhurEwCKckuHQbL+hTJIKKTiUlCPKlgQ/OukFvSnKB/Jk3+sFbkGA==", "requires": { "formatio": "1.2.0", - "just-extend": "1.1.22", + "just-extend": "1.1.27", "lolex": "1.6.0", - "path-to-regexp": "1.7.0" + "path-to-regexp": "1.7.0", + "text-encoding": "0.6.4" }, "dependencies": { "formatio": { @@ -4408,7 +4501,7 @@ "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.30.tgz", "integrity": "sha1-ZNMHOm9XMANxfM/jDIkCMpe6u6E=", "requires": { - "mkdirp": "0.5.1", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "nopt": "3.0.6", "npmlog": "4.1.2", "rc": "1.1.7", @@ -4477,6 +4570,23 @@ "resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.2.1.tgz", "integrity": "sha1-78GM7CBbT/BR0gWHPXkGStGFCeM=" }, + "nodemailer-mandrill-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/nodemailer-mandrill-transport/-/nodemailer-mandrill-transport-1.2.0.tgz", + "integrity": "sha1-aZaQKdJZtGkhzBLbcMoVUb85SwQ=", + "requires": { + "addressparser": "1.0.1", + "extend": "3.0.1", + "mandrill-api": "1.0.45" + }, + "dependencies": { + "addressparser": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", + "integrity": "sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y=" + } + } + }, "nodemailer-sendgrid-transport": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/nodemailer-sendgrid-transport/-/nodemailer-sendgrid-transport-0.2.0.tgz", @@ -5042,7 +5152,6 @@ "requires": { "assert-plus": "0.1.5", "bunyan": "0.22.1", - "dtrace-provider": "0.2.8", "once": "1.3.0", "vasync": "1.4.0" }, @@ -5464,9 +5573,9 @@ } }, "redis": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/redis/-/redis-0.10.1.tgz", - "integrity": "sha1-TwkliTHZYTdyOf29SV4dmaJjqOw=" + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz", + "integrity": "sha1-ZN92rQ/IrOuuvSoGReikj6xJGF4=" }, "redis-commands": { "version": "1.3.1", @@ -5500,67 +5609,24 @@ } }, "redis-sharelatex": { - "version": "git+https://github.com/sharelatex/redis-sharelatex.git#143b7eb192675f36d835080e534a4ac4899f918a", + "version": "git+https://github.com/sharelatex/redis-sharelatex.git#ca4e906559c1405d132e8edd7db763d64a57be62", "requires": { - "async": "2.5.0", - "chai": "1.9.1", + "async": "2.6.0", "coffee-script": "1.8.0", - "grunt": "0.4.5", - "grunt-contrib-coffee": "0.11.1", - "grunt-mocha-test": "0.12.0", - "ioredis": "2.5.0", - "mocha": "1.21.4", + "ioredis": "3.2.2", "redis": "0.12.1", "redis-sentinel": "0.1.1", - "sandboxed-module": "1.0.1", - "sinon": "1.10.3", "underscore": "1.7.0" }, "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=" - }, - "ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=" - }, - "assertion-error": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", - "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=" - }, "async": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", - "integrity": "sha1-hDGQ/WtzV6C54clW7d3V7IRitU0=", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", "requires": { "lodash": "4.17.4" } }, - "chai": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-1.9.1.tgz", - "integrity": "sha1-NxG7ZwbhVo80wLNgmL+PGUVcga4=", - "requires": { - "assertion-error": "1.0.0", - "deep-eql": "0.1.3" - } - }, - "chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", - "requires": { - "ansi-styles": "1.1.0", - "escape-string-regexp": "1.0.5", - "has-ansi": "0.1.0", - "strip-ansi": "0.3.0", - "supports-color": "0.2.0" - } - }, "coffee-script": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.8.0.tgz", @@ -5569,249 +5635,11 @@ "mkdirp": "0.3.5" } }, - "commander": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", - "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" - }, - "deep-eql": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", - "requires": { - "type-detect": "0.1.1" - } - }, - "formatio": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.0.2.tgz", - "integrity": "sha1-55kcoUT/fYz/B7uayGqbeca6R+8=", - "requires": { - "samsam": "1.1.3" - } - }, - "fs-extra": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.11.1.tgz", - "integrity": "sha1-3xBPlMyEHu+Pr+KkRsiPXTW7Lnk=", - "requires": { - "jsonfile": "2.4.0", - "mkdirp": "0.5.1", - "ncp": "0.6.0", - "rimraf": "2.6.2" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - } - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.3.3", - "path-is-absolute": "1.0.1" - } - }, - "growl": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.8.1.tgz", - "integrity": "sha1-Sy3sjZB+k9szZiTc7AGDUC+MlCg=" - }, - "grunt-contrib-coffee": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.11.1.tgz", - "integrity": "sha1-+v48nuikQryNF9WlwZ/I5i2fP0U=", - "requires": { - "chalk": "0.5.1", - "coffee-script": "1.7.1", - "lodash": "2.4.2" - }, - "dependencies": { - "coffee-script": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.7.1.tgz", - "integrity": "sha1-YplqhheAx15tUGnROCJyO3NAS/w=", - "requires": { - "mkdirp": "0.3.5" - } - }, - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" - } - } - }, - "grunt-mocha-test": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.12.0.tgz", - "integrity": "sha1-nQu5enkEQIr9x64qAj3X1rJg5uM=", - "requires": { - "fs-extra": "0.11.1", - "hooker": "0.2.3" - } - }, - "has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", - "requires": { - "ansi-regex": "0.2.1" - } - }, - "jade": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", - "requires": { - "commander": "0.6.1", - "mkdirp": "0.3.0" - }, - "dependencies": { - "commander": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" - }, - "mkdirp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" - } - } - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "requires": { - "graceful-fs": "4.1.11" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", - "requires": { - "brace-expansion": "1.1.8" - } - }, "mkdirp": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" }, - "mocha": { - "version": "1.21.4", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.21.4.tgz", - "integrity": "sha1-531pw3c7o+K0/mtijCi13UOICtw=", - "requires": { - "commander": "2.0.0", - "debug": "1.0.5", - "diff": "1.0.7", - "glob": "3.2.3", - "growl": "1.8.1", - "jade": "0.26.3", - "mkdirp": "0.3.5" - }, - "dependencies": { - "glob": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", - "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", - "requires": { - "graceful-fs": "2.0.3", - "inherits": "2.0.3", - "minimatch": "0.2.14" - } - }, - "graceful-fs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" - } - } - } - }, - "ncp": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.6.0.tgz", - "integrity": "sha1-34zgIeJiviG1L+s9Plz6qxJJHw0=" - }, - "redis": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz", - "integrity": "sha1-ZN92rQ/IrOuuvSoGReikj6xJGF4=" - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", - "requires": { - "glob": "7.1.2" - } - }, - "samsam": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.3.tgz", - "integrity": "sha1-n1CHQZtNCR8jJXHn+lLpCw9VJiE=" - }, - "sandboxed-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-1.0.1.tgz", - "integrity": "sha1-/XVEsYgexul8kd8r0CE8Nbj8P0s=", - "requires": { - "require-like": "0.1.2", - "stack-trace": "0.0.9" - } - }, - "sinon": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.10.3.tgz", - "integrity": "sha1-wGPg6Z2DJ9wZkROqtS64Oi6ePCw=", - "requires": { - "formatio": "1.0.2", - "util": "0.10.3" - } - }, - "strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", - "requires": { - "ansi-regex": "0.2.1" - } - }, - "supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=" - }, - "type-detect": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=" - }, "underscore": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", @@ -5829,12 +5657,6 @@ "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-0.0.1.tgz", "integrity": "sha1-p8LgmJH9vzj7sQ03b7cwA+aKxYk=" }, - "regexp-quote": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/regexp-quote/-/regexp-quote-0.0.0.tgz", - "integrity": "sha1-Hg9GUMhi3L/tVP1CsUjpuxch/PI=", - "dev": true - }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -6038,14 +5860,73 @@ } }, "sanitize-html": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.14.1.tgz", - "integrity": "sha1-cw/6Ikm98YMz7/5FsoYXPJxa0Lg=", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.16.1.tgz", + "integrity": "sha512-w3++cRkD2krVl8Zn70l7OcrF+zQc6lF0EVzCrcyFA3LR3AofZb2AuC3HRWyyNq225kSvl5K7IxSpQMkTQ+bHkw==", "dev": true, "requires": { "htmlparser2": "3.9.2", - "regexp-quote": "0.0.0", + "lodash.clonedeep": "4.5.0", + "lodash.escaperegexp": "4.1.2", + "lodash.isarray": "4.0.0", + "lodash.mergewith": "4.6.0", + "postcss": "6.0.14", + "srcset": "1.0.0", "xtend": "4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "postcss": { + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", + "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", + "dev": true, + "requires": { + "chalk": "2.3.0", + "source-map": "0.6.1", + "supports-color": "4.5.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } } }, "sanitizer": { @@ -6321,6 +6202,16 @@ "amdefine": "1.0.1" } }, + "srcset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz", + "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=", + "dev": true, + "requires": { + "array-uniq": "1.0.3", + "number-is-nan": "1.0.1" + } + }, "sshpk": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", @@ -6773,6 +6664,29 @@ } } }, + "translations-sharelatex": { + "version": "git+https://github.com/sharelatex/translations-sharelatex.git#507b6b945bedc604f5db7185ad66386209f95c18", + "dev": true, + "requires": { + "async": "2.6.0", + "coffee-script": "1.12.4", + "i18next": "1.7.10", + "onesky": "0.1.6", + "sanitize-html": "1.16.1", + "underscore": "1.6.0" + }, + "dependencies": { + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "dev": true, + "requires": { + "lodash": "4.17.4" + } + } + } + }, "tsscmp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz", @@ -6807,11 +6721,6 @@ "mime-types": "2.1.17" } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, "uglify-js": { "version": "2.4.24", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.24.tgz", @@ -6893,6 +6802,7 @@ "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, "requires": { "inherits": "2.0.1" }, @@ -6900,7 +6810,8 @@ "inherits": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true } } }, @@ -6918,7 +6829,7 @@ "async": "0.2.10", "deep-equal": "1.0.1", "i": "0.3.5", - "mkdirp": "0.5.1", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "ncp": "0.4.2", "rimraf": "2.2.6" }, @@ -7000,7 +6911,7 @@ "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.37.tgz", "integrity": "sha1-PIcrI2suJm5BQFeP4e6I9pMyOgU=", "requires": { - "mkdirp": "0.5.1", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "nopt": "4.0.1", "npmlog": "4.1.2", "rc": "1.1.7", diff --git a/services/web/package.json b/services/web/package.json index 59087dfa89..a1cf0fd6e0 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -56,6 +56,7 @@ "multer": "^0.1.8", "node-html-encoder": "0.0.2", "nodemailer": "2.1.0", + "nodemailer-mandrill-transport": "^1.2.0", "nodemailer-sendgrid-transport": "^0.2.0", "nodemailer-ses-transport": "^1.3.0", "optimist": "0.6.1", From 3e2c997e8b9c9b37bd74b1b42d45b9a4a9938202 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Mon, 11 Dec 2017 11:40:25 +0000 Subject: [PATCH 17/27] added stubs for nodemailer into unit tests --- services/web/test/unit/coffee/Email/EmailSenderTests.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/test/unit/coffee/Email/EmailSenderTests.coffee b/services/web/test/unit/coffee/Email/EmailSenderTests.coffee index 84efd2c0ca..958cf208b3 100644 --- a/services/web/test/unit/coffee/Email/EmailSenderTests.coffee +++ b/services/web/test/unit/coffee/Email/EmailSenderTests.coffee @@ -31,6 +31,8 @@ describe "EmailSender", -> @sender = SandboxedModule.require modulePath, requires: 'nodemailer': @ses + "nodemailer-mandrill-transport":{} + "nodemailer-sendgrid-transport":{} "settings-sharelatex":@settings '../../infrastructure/RateLimiter':@RateLimiter "logger-sharelatex": From 314dcd1901cad9bcdc03aa7ab3bc7fc6bf337d69 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Mon, 11 Dec 2017 11:48:31 +0000 Subject: [PATCH 18/27] added null checks to mandrill --- services/web/app/coffee/Features/Email/EmailSender.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/app/coffee/Features/Email/EmailSender.coffee b/services/web/app/coffee/Features/Email/EmailSender.coffee index 31237eb804..d5ae949f74 100644 --- a/services/web/app/coffee/Features/Email/EmailSender.coffee +++ b/services/web/app/coffee/Features/Email/EmailSender.coffee @@ -24,9 +24,9 @@ if Settings?.email?.parameters?.AWSAccessKeyID? or Settings?.email?.driver == 's else if Settings?.email?.parameters?.sendgridApiKey? logger.log "using sendgrid for email" nm_client = nodemailer.createTransport(sgTransport({auth:{api_key:Settings?.email?.parameters?.sendgridApiKey}})) -else if Settings?.email.parameters.MandrillApiKey? +else if Settings?.email?.parameters?.MandrillApiKey? logger.log "using mandril for email" - nm_client = nodemailer.createTransport(mandrillTransport({auth:{apiKey:Settings?.email.parameters.MandrillApiKey}})) + nm_client = nodemailer.createTransport(mandrillTransport({auth:{apiKey:Settings?.email?.parameters?.MandrillApiKey}})) else if Settings?.email?.parameters? logger.log "using smtp for email" smtp = _.pick(Settings?.email?.parameters, "host", "port", "secure", "auth", "ignoreTLS") From 83086e4a791bbfafb2189f764933ef2956adaa82 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 11 Dec 2017 11:44:34 +0000 Subject: [PATCH 19/27] Add recaptch to share endpoint --- .../CollaboratorsInviteController.coffee | 77 +++++++++++-------- services/web/app/views/layout.pug | 3 + .../web/app/views/project/editor/share.pug | 7 ++ .../ShareProjectModalController.coffee | 29 ++++++- .../ide/share/services/projectInvites.coffee | 3 +- .../public/stylesheets/app/editor/share.less | 4 + .../CollaboratorsInviteControllerTests.coffee | 15 ++++ 7 files changed, 104 insertions(+), 34 deletions(-) diff --git a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee index e00f7807f5..55814980ad 100644 --- a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee +++ b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee @@ -11,6 +11,7 @@ NotificationsBuilder = require("../Notifications/NotificationsBuilder") AnalyticsManger = require("../Analytics/AnalyticsManager") AuthenticationController = require("../Authentication/AuthenticationController") rateLimiter = require("../../infrastructure/RateLimiter") +request = require 'request' module.exports = CollaboratorsInviteController = @@ -46,43 +47,59 @@ module.exports = CollaboratorsInviteController = throttle: collabLimit rateLimiter.addCount opts, callback + _validateCaptcha: (response, callback = (error, valid) ->) -> + if !Settings.recaptcha? + return callback(null, true) + options = + form: + secret: Settings.recaptcha.secretKey + response: response + json: true + request.post "https://www.google.com/recaptcha/api/siteverify", options, (error, response, body) -> + return callback(error) if error? + return callback null, body?.success + inviteToProject: (req, res, next) -> projectId = req.params.Project_id email = req.body.email - sendingUser = AuthenticationController.getSessionUser(req) - sendingUserId = sendingUser._id - if email == sendingUser.email - logger.log {projectId, email, sendingUserId}, "cannot invite yourself to project" - return res.json {invite: null, error: 'cannot_invite_self'} - logger.log {projectId, email, sendingUserId}, "inviting to project" - LimitationsManager.canAddXCollaborators projectId, 1, (error, allowed) => + CollaboratorsInviteController._validateCaptcha req.body['g-recaptcha-response'], (error, valid) -> return next(error) if error? - if !allowed - logger.log {projectId, email, sendingUserId}, "not allowed to invite more users to project" - return res.json {invite: null} - {email, privileges} = req.body - email = EmailHelper.parseEmail(email) - if !email? or email == "" - logger.log {projectId, email, sendingUserId}, "invalid email address" - return res.sendStatus(400) - CollaboratorsInviteController._checkRateLimit sendingUserId, (error, underRateLimit) -> + if !valid + return res.sendStatus 400 + sendingUser = AuthenticationController.getSessionUser(req) + sendingUserId = sendingUser._id + if email == sendingUser.email + logger.log {projectId, email, sendingUserId}, "cannot invite yourself to project" + return res.json {invite: null, error: 'cannot_invite_self'} + logger.log {projectId, email, sendingUserId}, "inviting to project" + LimitationsManager.canAddXCollaborators projectId, 1, (error, allowed) => return next(error) if error? - if !underRateLimit - return res.sendStatus(429) - CollaboratorsInviteController._checkShouldInviteEmail email, (err, shouldAllowInvite)-> - if err? - logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address" - return next(err) - if !shouldAllowInvite - logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address" - return res.json {invite: null, error: 'cannot_invite_non_user'} - CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) -> + if !allowed + logger.log {projectId, email, sendingUserId}, "not allowed to invite more users to project" + return res.json {invite: null} + {email, privileges} = req.body + email = EmailHelper.parseEmail(email) + if !email? or email == "" + logger.log {projectId, email, sendingUserId}, "invalid email address" + return res.sendStatus(400) + CollaboratorsInviteController._checkRateLimit sendingUserId, (error, underRateLimit) -> + return next(error) if error? + if !underRateLimit + return res.sendStatus(429) + CollaboratorsInviteController._checkShouldInviteEmail email, (err, shouldAllowInvite)-> if err? - logger.err {projectId, email, sendingUserId}, "error creating project invite" + logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address" return next(err) - logger.log {projectId, email, sendingUserId}, "invite created" - EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true}) - return res.json {invite: invite} + if !shouldAllowInvite + logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address" + return res.json {invite: null, error: 'cannot_invite_non_user'} + CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) -> + if err? + logger.err {projectId, email, sendingUserId}, "error creating project invite" + return next(err) + logger.log {projectId, email, sendingUserId}, "invite created" + EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true}) + return res.json {invite: invite} revokeInvite: (req, res, next) -> projectId = req.params.Project_id diff --git a/services/web/app/views/layout.pug b/services/web/app/views/layout.pug index 31afc17d29..34e78a65f9 100644 --- a/services/web/app/views/layout.pug +++ b/services/web/app/views/layout.pug @@ -53,6 +53,9 @@ html(itemscope, itemtype='http://schema.org/Product') - else script(type='text/javascript'). window.ga = function() { console.log("would send to GA", arguments) }; + + - if (settings.recaptcha) + script(src="https://www.google.com/recaptcha/api.js") script(type="text/javascript"). window.csrfToken = "#{csrfToken}"; diff --git a/services/web/app/views/project/editor/share.pug b/services/web/app/views/project/editor/share.pug index 98568df122..8f5110fa6f 100644 --- a/services/web/app/views/project/editor/share.pug +++ b/services/web/app/views/project/editor/share.pug @@ -102,6 +102,13 @@ script(type='text/ng-template', id='shareProjectModalTemplate') i.fa.fa-times .row.invite-controls form(ng-show="canAddCollaborators") + div( + id="recaptcha" + class="g-recaptcha" + data-sitekey=settings.recaptcha.siteKey + data-size="invisible" + data-badge="inline" + ) .small #{translate("share_with_your_collabs")} .form-group tags-input( diff --git a/services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee b/services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee index 789f6272ca..70784e63cf 100644 --- a/services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee +++ b/services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee @@ -55,6 +55,25 @@ define [ getCurrentInviteEmails = () -> ($scope.project.invites || []).map (u) -> u.email + _recaptchaCallbacks = [] + onRecaptchaSubmit = (token) -> + for cb in _recaptchaCallbacks + cb(token) + _recaptchaCallbacks = [] + + recaptchaId = null + validateCaptcha = (callback = (response) ->) => + if !grecaptcha? + return callback() + reset = () -> + grecaptcha.reset() + _recaptchaCallbacks.push callback + _recaptchaCallbacks.push reset + if !recaptchaId? + el = $('.g-recaptcha')[0] + recaptchaId = grecaptcha.render(el, {callback: onRecaptchaSubmit}) + grecaptcha.execute(recaptchaId) + $scope.filterAutocompleteUsers = ($query) -> currentMemberEmails = getCurrentMemberEmails() return $scope.autocompleteContacts.filter (contact) -> @@ -100,7 +119,7 @@ define [ if email in currentInviteEmails and inviteId = _.find(($scope.project.invites || []), (invite) -> invite.email == email)?._id request = projectInvites.resendInvite(inviteId) else - request = projectInvites.sendInvite(email, $scope.inputs.privileges) + request = projectInvites.sendInvite(email, $scope.inputs.privileges, $scope.grecaptchaResponse) request .then (response) -> @@ -135,7 +154,9 @@ define [ else $scope.state.errorReason = null - $timeout addMembers, 50 # Give email list a chance to update + validateCaptcha (response) -> + $scope.grecaptchaResponse = response + $timeout addMembers, 50 # Give email list a chance to update $scope.removeMember = (member) -> $scope.state.error = null @@ -210,6 +231,8 @@ define [ $scope.cancel = () -> $modalInstance.dismiss() + + App.controller "MakePublicModalController", ["$scope", "$modalInstance", "settings", ($scope, $modalInstance, settings) -> $scope.inputs = { privileges: "readAndWrite" @@ -244,4 +267,4 @@ define [ $scope.cancel = () -> $modalInstance.dismiss() - ] + ] \ No newline at end of file diff --git a/services/web/public/coffee/ide/share/services/projectInvites.coffee b/services/web/public/coffee/ide/share/services/projectInvites.coffee index 4c0d30add6..84757915df 100644 --- a/services/web/public/coffee/ide/share/services/projectInvites.coffee +++ b/services/web/public/coffee/ide/share/services/projectInvites.coffee @@ -4,11 +4,12 @@ define [ App.factory "projectInvites", ["ide", "$http", (ide, $http) -> return { - sendInvite: (email, privileges) -> + sendInvite: (email, privileges, grecaptchaResponse) -> $http.post("/project/#{ide.project_id}/invite", { email: email privileges: privileges _csrf: window.csrfToken + 'g-recaptcha-response': grecaptchaResponse }) revokeInvite: (inviteId) -> diff --git a/services/web/public/stylesheets/app/editor/share.less b/services/web/public/stylesheets/app/editor/share.less index ba1e79f4b1..d0c9b5cde4 100644 --- a/services/web/public/stylesheets/app/editor/share.less +++ b/services/web/public/stylesheets/app/editor/share.less @@ -66,3 +66,7 @@ text-align: left; } } + +.grecaptcha-badge { + display: none; +} \ No newline at end of file diff --git a/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee b/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee index 0adff748c0..e5452ddb7d 100644 --- a/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee +++ b/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee @@ -38,6 +38,7 @@ describe "CollaboratorsInviteController", -> '../Authentication/AuthenticationController': @AuthenticationController 'settings-sharelatex': @settings = {} "../../infrastructure/RateLimiter":@RateLimiter + 'request': @request = {} @res = new MockResponse() @req = new MockRequest() @@ -96,6 +97,7 @@ describe "CollaboratorsInviteController", -> @req.body = email: @targetEmail privileges: @privileges = "readAndWrite" + 'g-recaptcha-response': @grecaptchaResponse = 'grecaptcha response' @res.json = sinon.stub() @res.sendStatus = sinon.stub() @invite = { @@ -108,6 +110,8 @@ describe "CollaboratorsInviteController", -> } @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true) @CollaboratorsInviteHandler.inviteToProject = sinon.stub().callsArgWith(4, null, @invite) + @CollaboratorsInviteController._validateCaptcha = sinon.stub() + @CollaboratorsInviteController._validateCaptcha.withArgs(@grecaptchaResponse).yields(null, true) @callback = sinon.stub() @next = sinon.stub() @@ -285,6 +289,17 @@ describe "CollaboratorsInviteController", -> it 'should not call emitToRoom', -> @EditorRealTimeController.emitToRoom.called.should.equal false + describe "when recaptcha is not valid", -> + beforeEach -> + @CollaboratorsInviteController._validateCaptcha = sinon.stub().yields(null, false) + @CollaboratorsInviteController.inviteToProject @req, @res, @next + + it "should return 400", -> + @res.sendStatus.calledWith(400).should.equal true + + it "should not inviteToProject", -> + @CollaboratorsInviteHandler.inviteToProject.called.should.equal false + describe "viewInvite", -> beforeEach -> From 0b03bbc7c307febf34393975e905596006ca10d7 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 11 Dec 2017 12:04:33 +0000 Subject: [PATCH 20/27] Don't inject recaptcha element if recaptcha is not enabled --- services/web/app/views/project/editor/share.pug | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/services/web/app/views/project/editor/share.pug b/services/web/app/views/project/editor/share.pug index 8f5110fa6f..0b56bbc4f5 100644 --- a/services/web/app/views/project/editor/share.pug +++ b/services/web/app/views/project/editor/share.pug @@ -102,13 +102,14 @@ script(type='text/ng-template', id='shareProjectModalTemplate') i.fa.fa-times .row.invite-controls form(ng-show="canAddCollaborators") - div( - id="recaptcha" - class="g-recaptcha" - data-sitekey=settings.recaptcha.siteKey - data-size="invisible" - data-badge="inline" - ) + - if (settings.recaptcha) + div( + id="recaptcha" + class="g-recaptcha" + data-sitekey=settings.recaptcha.siteKey + data-size="invisible" + data-badge="inline" + ) .small #{translate("share_with_your_collabs")} .form-group tags-input( From 69499847e413f5dc646c9fed1c35153f296a9dd8 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 11 Dec 2017 12:32:43 +0000 Subject: [PATCH 21/27] Refactor front end code into validateCaptcha service --- services/web/app/views/layout.pug | 12 +++++++--- .../web/app/views/project/editor/share.pug | 8 ------- services/web/public/coffee/ide.coffee | 1 + .../ShareProjectModalController.coffee | 21 +--------------- services/web/public/coffee/main.coffee | 1 + .../coffee/services/validateCaptcha.coffee | 24 +++++++++++++++++++ 6 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 services/web/public/coffee/services/validateCaptcha.coffee diff --git a/services/web/app/views/layout.pug b/services/web/app/views/layout.pug index 34e78a65f9..e12d7d6f81 100644 --- a/services/web/app/views/layout.pug +++ b/services/web/app/views/layout.pug @@ -53,9 +53,6 @@ html(itemscope, itemtype='http://schema.org/Product') - else script(type='text/javascript'). window.ga = function() { console.log("would send to GA", arguments) }; - - - if (settings.recaptcha) - script(src="https://www.google.com/recaptcha/api.js") script(type="text/javascript"). window.csrfToken = "#{csrfToken}"; @@ -100,6 +97,15 @@ html(itemscope, itemtype='http://schema.org/Product') } body + if(settings.recaptcha) + script(src="https://www.google.com/recaptcha/api.js?render=explicit") + div( + id="recaptcha" + class="g-recaptcha" + data-sitekey=settings.recaptcha.siteKey + data-size="invisible" + data-badge="inline" + ) - if(typeof(suppressSystemMessages) == "undefined") .system-messages( diff --git a/services/web/app/views/project/editor/share.pug b/services/web/app/views/project/editor/share.pug index 0b56bbc4f5..98568df122 100644 --- a/services/web/app/views/project/editor/share.pug +++ b/services/web/app/views/project/editor/share.pug @@ -102,14 +102,6 @@ script(type='text/ng-template', id='shareProjectModalTemplate') i.fa.fa-times .row.invite-controls form(ng-show="canAddCollaborators") - - if (settings.recaptcha) - div( - id="recaptcha" - class="g-recaptcha" - data-sitekey=settings.recaptcha.siteKey - data-size="invisible" - data-badge="inline" - ) .small #{translate("share_with_your_collabs")} .form-group tags-input( diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee index 728300456c..e31985ec8a 100644 --- a/services/web/public/coffee/ide.coffee +++ b/services/web/public/coffee/ide.coffee @@ -33,6 +33,7 @@ define [ "directives/expandableTextArea" "directives/videoPlayState" "services/queued-http" + "services/validateCaptcha" "filters/formatDate" "main/event" "main/account-upgrade" diff --git a/services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee b/services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee index 70784e63cf..0c16dcbb39 100644 --- a/services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee +++ b/services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee @@ -1,7 +1,7 @@ define [ "base" ], (App) -> - App.controller "ShareProjectModalController", ($scope, $modalInstance, $timeout, projectMembers, projectInvites, $modal, $http, ide) -> + App.controller "ShareProjectModalController", ($scope, $modalInstance, $timeout, projectMembers, projectInvites, $modal, $http, ide, validateCaptcha) -> $scope.inputs = { privileges: "readAndWrite" contacts: [] @@ -55,25 +55,6 @@ define [ getCurrentInviteEmails = () -> ($scope.project.invites || []).map (u) -> u.email - _recaptchaCallbacks = [] - onRecaptchaSubmit = (token) -> - for cb in _recaptchaCallbacks - cb(token) - _recaptchaCallbacks = [] - - recaptchaId = null - validateCaptcha = (callback = (response) ->) => - if !grecaptcha? - return callback() - reset = () -> - grecaptcha.reset() - _recaptchaCallbacks.push callback - _recaptchaCallbacks.push reset - if !recaptchaId? - el = $('.g-recaptcha')[0] - recaptchaId = grecaptcha.render(el, {callback: onRecaptchaSubmit}) - grecaptcha.execute(recaptchaId) - $scope.filterAutocompleteUsers = ($query) -> currentMemberEmails = getCurrentMemberEmails() return $scope.autocompleteContacts.filter (contact) -> diff --git a/services/web/public/coffee/main.coffee b/services/web/public/coffee/main.coffee index 5ad6f37d34..e361f583c8 100644 --- a/services/web/public/coffee/main.coffee +++ b/services/web/public/coffee/main.coffee @@ -30,6 +30,7 @@ define [ "directives/maxHeight" "directives/creditCards" "services/queued-http" + "services/validateCaptcha" "filters/formatDate" "__MAIN_CLIENTSIDE_INCLUDES__" ], () -> diff --git a/services/web/public/coffee/services/validateCaptcha.coffee b/services/web/public/coffee/services/validateCaptcha.coffee new file mode 100644 index 0000000000..2f00c38f2a --- /dev/null +++ b/services/web/public/coffee/services/validateCaptcha.coffee @@ -0,0 +1,24 @@ +define [ + "base" +], (App) -> + App.factory "validateCaptcha", () -> + _recaptchaCallbacks = [] + onRecaptchaSubmit = (token) -> + for cb in _recaptchaCallbacks + cb(token) + _recaptchaCallbacks = [] + + recaptchaId = null + validateCaptcha = (callback = (response) ->) => + if !grecaptcha? + return callback() + reset = () -> + grecaptcha.reset() + _recaptchaCallbacks.push callback + _recaptchaCallbacks.push reset + if !recaptchaId? + el = $('.g-recaptcha')[0] + recaptchaId = grecaptcha.render(el, {callback: onRecaptchaSubmit}) + grecaptcha.execute(recaptchaId) + + return validateCaptcha From 53dc8cddfcd6652fd6081667b6c2c4d7b9c8ff2f Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 11 Dec 2017 12:58:55 +0000 Subject: [PATCH 22/27] Refactor captcha into middleware and angular service --- .../Features/Captcha/CaptchaMiddleware.coffee | 21 +++++ .../CollaboratorsInviteController.coffee | 78 ++++++++----------- .../Collaborators/CollaboratorsRouter.coffee | 2 + .../public/coffee/directives/asyncForm.coffee | 14 +++- .../coffee/services/validateCaptcha.coffee | 2 +- .../CollaboratorsInviteControllerTests.coffee | 15 ---- 6 files changed, 68 insertions(+), 64 deletions(-) create mode 100644 services/web/app/coffee/Features/Captcha/CaptchaMiddleware.coffee diff --git a/services/web/app/coffee/Features/Captcha/CaptchaMiddleware.coffee b/services/web/app/coffee/Features/Captcha/CaptchaMiddleware.coffee new file mode 100644 index 0000000000..4a3ce18b5e --- /dev/null +++ b/services/web/app/coffee/Features/Captcha/CaptchaMiddleware.coffee @@ -0,0 +1,21 @@ +request = require 'request' +logger = require 'logger-sharelatex' +Settings = require 'settings-sharelatex' + +module.exports = CaptchaMiddleware = + validateCaptcha: (req, res, next) -> + if !Settings.recaptcha? + return next() + response = req.body['g-recaptcha-response'] + options = + form: + secret: Settings.recaptcha.secretKey + response: response + json: true + request.post "https://www.google.com/recaptcha/api/siteverify", options, (error, response, body) -> + return next(error) if error? + if !body?.success + logger.warn {statusCode: response.statusCode, body: body}, 'failed recaptcha siteverify request' + return res.sendStatus 400 + else + return next() diff --git a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee index 55814980ad..28f0cbb5ee 100644 --- a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee +++ b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee @@ -33,7 +33,7 @@ module.exports = CollaboratorsInviteController = callback(null, userExists) else callback(null, true) - + _checkRateLimit: (user_id, callback = (error) ->) -> LimitationsManager.allowedNumberOfCollaboratorsForUser user_id, (err, collabLimit = 1)-> return callback(err) if err? @@ -47,59 +47,43 @@ module.exports = CollaboratorsInviteController = throttle: collabLimit rateLimiter.addCount opts, callback - _validateCaptcha: (response, callback = (error, valid) ->) -> - if !Settings.recaptcha? - return callback(null, true) - options = - form: - secret: Settings.recaptcha.secretKey - response: response - json: true - request.post "https://www.google.com/recaptcha/api/siteverify", options, (error, response, body) -> - return callback(error) if error? - return callback null, body?.success - inviteToProject: (req, res, next) -> projectId = req.params.Project_id email = req.body.email - CollaboratorsInviteController._validateCaptcha req.body['g-recaptcha-response'], (error, valid) -> + sendingUser = AuthenticationController.getSessionUser(req) + sendingUserId = sendingUser._id + if email == sendingUser.email + logger.log {projectId, email, sendingUserId}, "cannot invite yourself to project" + return res.json {invite: null, error: 'cannot_invite_self'} + logger.log {projectId, email, sendingUserId}, "inviting to project" + LimitationsManager.canAddXCollaborators projectId, 1, (error, allowed) => return next(error) if error? - if !valid - return res.sendStatus 400 - sendingUser = AuthenticationController.getSessionUser(req) - sendingUserId = sendingUser._id - if email == sendingUser.email - logger.log {projectId, email, sendingUserId}, "cannot invite yourself to project" - return res.json {invite: null, error: 'cannot_invite_self'} - logger.log {projectId, email, sendingUserId}, "inviting to project" - LimitationsManager.canAddXCollaborators projectId, 1, (error, allowed) => + if !allowed + logger.log {projectId, email, sendingUserId}, "not allowed to invite more users to project" + return res.json {invite: null} + {email, privileges} = req.body + email = EmailHelper.parseEmail(email) + if !email? or email == "" + logger.log {projectId, email, sendingUserId}, "invalid email address" + return res.sendStatus(400) + CollaboratorsInviteController._checkRateLimit sendingUserId, (error, underRateLimit) -> return next(error) if error? - if !allowed - logger.log {projectId, email, sendingUserId}, "not allowed to invite more users to project" - return res.json {invite: null} - {email, privileges} = req.body - email = EmailHelper.parseEmail(email) - if !email? or email == "" - logger.log {projectId, email, sendingUserId}, "invalid email address" - return res.sendStatus(400) - CollaboratorsInviteController._checkRateLimit sendingUserId, (error, underRateLimit) -> - return next(error) if error? - if !underRateLimit - return res.sendStatus(429) - CollaboratorsInviteController._checkShouldInviteEmail email, (err, shouldAllowInvite)-> + if !underRateLimit + return res.sendStatus(429) + CollaboratorsInviteController._checkShouldInviteEmail email, (err, shouldAllowInvite)-> + if err? + logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address" + return next(err) + if !shouldAllowInvite + logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address" + return res.json {invite: null, error: 'cannot_invite_non_user'} + CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) -> if err? - logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address" + logger.err {projectId, email, sendingUserId}, "error creating project invite" return next(err) - if !shouldAllowInvite - logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address" - return res.json {invite: null, error: 'cannot_invite_non_user'} - CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) -> - if err? - logger.err {projectId, email, sendingUserId}, "error creating project invite" - return next(err) - logger.log {projectId, email, sendingUserId}, "invite created" - EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true}) - return res.json {invite: invite} + logger.log {projectId, email, sendingUserId}, "invite created" + EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true}) + return res.json {invite: invite} revokeInvite: (req, res, next) -> projectId = req.params.Project_id diff --git a/services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee b/services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee index 721e5a7b62..90fc704659 100644 --- a/services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee +++ b/services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee @@ -3,6 +3,7 @@ AuthenticationController = require('../Authentication/AuthenticationController') AuthorizationMiddlewear = require('../Authorization/AuthorizationMiddlewear') CollaboratorsInviteController = require('./CollaboratorsInviteController') RateLimiterMiddlewear = require('../Security/RateLimiterMiddlewear') +CaptchaMiddleware = require '../Captcha/CaptchaMiddleware' module.exports = apply: (webRouter, apiRouter) -> @@ -32,6 +33,7 @@ module.exports = maxRequests: 100 timeInterval: 60 * 10 }), + CaptchaMiddleware.validateCaptcha, AuthenticationController.requireLogin(), AuthorizationMiddlewear.ensureUserCanAdminProject, CollaboratorsInviteController.inviteToProject diff --git a/services/web/public/coffee/directives/asyncForm.coffee b/services/web/public/coffee/directives/asyncForm.coffee index 0e6ae19ec2..f092f3a2dc 100644 --- a/services/web/public/coffee/directives/asyncForm.coffee +++ b/services/web/public/coffee/directives/asyncForm.coffee @@ -2,7 +2,7 @@ define [ "base" "libs/passfield" ], (App) -> - App.directive "asyncForm", ($http) -> + App.directive "asyncForm", ($http, validateCaptcha) -> return { controller: ['$scope', ($scope) -> @getEmail = () -> @@ -17,11 +17,23 @@ define [ element.on "submit", (e) -> e.preventDefault() + validateCaptchaIfEnabled (response) -> + submitRequest e, response + validateCaptchaIfEnabled = (callback = (response) ->) -> + if attrs.captcha? + validateCaptcha callback + else + callback() + + submitRequest = (e, grecaptchaResponse) -> formData = {} for data in element.serializeArray() formData[data.name] = data.value + if grecaptchaResponse? + formData['g-recaptcha-response'] = grecaptchaResponse + scope[attrs.name].inflight = true # for asyncForm prevent automatic redirect to /login if diff --git a/services/web/public/coffee/services/validateCaptcha.coffee b/services/web/public/coffee/services/validateCaptcha.coffee index 2f00c38f2a..7dfc038fbc 100644 --- a/services/web/public/coffee/services/validateCaptcha.coffee +++ b/services/web/public/coffee/services/validateCaptcha.coffee @@ -17,7 +17,7 @@ define [ _recaptchaCallbacks.push callback _recaptchaCallbacks.push reset if !recaptchaId? - el = $('.g-recaptcha')[0] + el = $('#recaptcha')[0] recaptchaId = grecaptcha.render(el, {callback: onRecaptchaSubmit}) grecaptcha.execute(recaptchaId) diff --git a/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee b/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee index e5452ddb7d..0adff748c0 100644 --- a/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee +++ b/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee @@ -38,7 +38,6 @@ describe "CollaboratorsInviteController", -> '../Authentication/AuthenticationController': @AuthenticationController 'settings-sharelatex': @settings = {} "../../infrastructure/RateLimiter":@RateLimiter - 'request': @request = {} @res = new MockResponse() @req = new MockRequest() @@ -97,7 +96,6 @@ describe "CollaboratorsInviteController", -> @req.body = email: @targetEmail privileges: @privileges = "readAndWrite" - 'g-recaptcha-response': @grecaptchaResponse = 'grecaptcha response' @res.json = sinon.stub() @res.sendStatus = sinon.stub() @invite = { @@ -110,8 +108,6 @@ describe "CollaboratorsInviteController", -> } @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true) @CollaboratorsInviteHandler.inviteToProject = sinon.stub().callsArgWith(4, null, @invite) - @CollaboratorsInviteController._validateCaptcha = sinon.stub() - @CollaboratorsInviteController._validateCaptcha.withArgs(@grecaptchaResponse).yields(null, true) @callback = sinon.stub() @next = sinon.stub() @@ -289,17 +285,6 @@ describe "CollaboratorsInviteController", -> it 'should not call emitToRoom', -> @EditorRealTimeController.emitToRoom.called.should.equal false - describe "when recaptcha is not valid", -> - beforeEach -> - @CollaboratorsInviteController._validateCaptcha = sinon.stub().yields(null, false) - @CollaboratorsInviteController.inviteToProject @req, @res, @next - - it "should return 400", -> - @res.sendStatus.calledWith(400).should.equal true - - it "should not inviteToProject", -> - @CollaboratorsInviteHandler.inviteToProject.called.should.equal false - describe "viewInvite", -> beforeEach -> From f465a962d45b5faa81bef06685f136f2e92b74b2 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 11 Dec 2017 13:01:21 +0000 Subject: [PATCH 23/27] Put recaptcha css in a base location --- services/web/public/stylesheets/app/base.less | 4 ++++ services/web/public/stylesheets/app/editor/share.less | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/web/public/stylesheets/app/base.less b/services/web/public/stylesheets/app/base.less index 7f2d9a55ed..2f7b939fda 100644 --- a/services/web/public/stylesheets/app/base.less +++ b/services/web/public/stylesheets/app/base.less @@ -104,3 +104,7 @@ -ms-transform-origin: center bottom; transform-origin: center bottom; } + +.grecaptcha-badge { + display: none; +} \ No newline at end of file diff --git a/services/web/public/stylesheets/app/editor/share.less b/services/web/public/stylesheets/app/editor/share.less index d0c9b5cde4..ba1e79f4b1 100644 --- a/services/web/public/stylesheets/app/editor/share.less +++ b/services/web/public/stylesheets/app/editor/share.less @@ -66,7 +66,3 @@ text-align: left; } } - -.grecaptcha-badge { - display: none; -} \ No newline at end of file From de484e1a08cf039193af79a1f9ffb12ac72b7c59 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 11 Dec 2017 13:31:16 +0000 Subject: [PATCH 24/27] Remove unused reference to e --- services/web/public/coffee/directives/asyncForm.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/public/coffee/directives/asyncForm.coffee b/services/web/public/coffee/directives/asyncForm.coffee index f092f3a2dc..15e3c9b3fe 100644 --- a/services/web/public/coffee/directives/asyncForm.coffee +++ b/services/web/public/coffee/directives/asyncForm.coffee @@ -18,7 +18,7 @@ define [ element.on "submit", (e) -> e.preventDefault() validateCaptchaIfEnabled (response) -> - submitRequest e, response + submitRequest response validateCaptchaIfEnabled = (callback = (response) ->) -> if attrs.captcha? @@ -26,7 +26,7 @@ define [ else callback() - submitRequest = (e, grecaptchaResponse) -> + submitRequest = (grecaptchaResponse) -> formData = {} for data in element.serializeArray() formData[data.name] = data.value From 0652fc62a06fbee5f9cd4120efe66137ee3558e7 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 11 Dec 2017 15:33:00 +0000 Subject: [PATCH 25/27] Configure resizer cursors for v2. --- services/web/app/coffee/infrastructure/ExpressLocals.coffee | 2 ++ services/web/public/coffee/ide/directives/layout.coffee | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index 8c2257aa25..b126819f56 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -298,6 +298,8 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)-> res.locals.uiConfig = defaultResizerSizeOpen : if isOl then 2 else 24 defaultResizerSizeClosed : if isOl then 2 else 24 + eastResizerCursor : if isOl then "ew-resize" else null + westResizerCursor : if isOl then "ew-resize" else null chatResizerSizeOpen : if isOl then 2 else 12 chatResizerSizeClosed : 0 next() diff --git a/services/web/public/coffee/ide/directives/layout.coffee b/services/web/public/coffee/ide/directives/layout.coffee index 86058447ed..fcc75b29a1 100644 --- a/services/web/public/coffee/ide/directives/layout.coffee +++ b/services/web/public/coffee/ide/directives/layout.coffee @@ -44,6 +44,12 @@ define [ if !attrs.minimumRestoreSizeWest? or (state.west.size >= attrs.minimumRestoreSizeWest and !state.west.initClosed) options.west = state.west + if window.uiConfig.eastResizerCursor? + options.east.resizerCursor = window.uiConfig.eastResizerCursor + + if window.uiConfig.westResizerCursor? + options.west.resizerCursor = window.uiConfig.westResizerCursor + repositionControls = () -> state = element.layout().readState() if state.east? From ab46e3930ad288a56350cb8cf43acb07bb6de75f Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 11 Dec 2017 15:41:07 +0000 Subject: [PATCH 26/27] Configure toggler cursors for v2. --- .../web/public/stylesheets/app/editor.less | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index 5257e9c519..9224dbe181 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -370,6 +370,24 @@ } } +.ui-layout-toggler-east when (@is-overleaf = true) { + &.ui-layout-toggler-open { + cursor: e-resize !important + } + &.ui-layout-toggler-closed { + cursor: w-resize !important + } +} + +.ui-layout-toggler-west when (@is-overleaf = true) { + &.ui-layout-toggler-open { + cursor: w-resize !important + } + &.ui-layout-toggler-closed { + cursor: e-resize !important + } +} + .ui-layout-resizer-dragging { background-color: @editor-resizer-bg-color-dragging; } From edfc259c5379ec124ac7d05f704d402dedb4563d Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 11 Dec 2017 16:55:50 +0000 Subject: [PATCH 27/27] Fix missing border on SL. --- services/web/app/views/project/editor/editor.pug | 4 ++-- services/web/public/stylesheets/app/editor/pdf.less | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/services/web/app/views/project/editor/editor.pug b/services/web/app/views/project/editor/editor.pug index ad06b6b6ba..1ed95a75ac 100644 --- a/services/web/app/views/project/editor/editor.pug +++ b/services/web/app/views/project/editor/editor.pug @@ -73,14 +73,14 @@ div.full-size( ng-show="!!pdf.url && settings.pdfViewer == 'pdfjs'" ng-controller="PdfSynctexController" ) - a.synctex-control.synctex-control-goto-pdf( + a.btn.btn-default.btn-xs.synctex-control.synctex-control-goto-pdf( tooltip=translate('go_to_code_location_in_pdf') tooltip-placement="right" tooltip-append-to-body="true" ng-click="syncToPdf()" ) i.synctex-control-icon - a.synctex-control.synctex-control-goto-code( + a.btn.btn-default.btn-xs.synctex-control.synctex-control-goto-code( tooltip-html="'"+translate('go_to_pdf_location_in_code', {}, true)+"'" tooltip-placement="right" tooltip-append-to-body="true" diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index b8ddfc5efd..e602d2d99b 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -239,9 +239,6 @@ margin-right: -11px; } .synctex-control { - .btn; - .btn-default; - .btn-xs; display: block; margin-bottom: 3px;